Просмотр исходного кода

fix: reuse agent model schema for webfetch, pass variant through pipeline

- Remove WebfetchModelEntrySchema, reuse AgentOverrideConfigSchema.shape.model
- SmartfetchOptions.webfetchModels now preserves variant alongside id
- SecondaryModel includes optional variant field
- readSecondaryModelFromConfig parses variant from dedicated config
- runSecondaryModel passes variant at API body level (body.variant)
- Normalization in index.ts preserves variant instead of dropping it
- Frontmatter includes #variant suffix when present
adikpb 1 месяц назад
Родитель
Сommit
7a84a964db

+ 5 - 19
src/config/schema.ts

@@ -293,16 +293,6 @@ export const CompanionConfigSchema = z.object({
 
 export type CompanionConfig = z.infer<typeof CompanionConfigSchema>;
 
-const WebfetchModelEntrySchema = z.union([
-  ProviderModelIdSchema,
-  z
-    .object({
-      id: ProviderModelIdSchema,
-      variant: z.string().optional(),
-    })
-    .strict(),
-]);
-
 export const WebfetchConfigSchema = z
   .object({
     enabled: z
@@ -311,15 +301,11 @@ export const WebfetchConfigSchema = z
       .describe(
         'When false, skip registering this enhanced webfetch so OpenCode uses its built-in version.',
       ),
-    model: z
-      .union([WebfetchModelEntrySchema, z.array(WebfetchModelEntrySchema).min(1)])
-      .optional()
-      .describe(
-        'Dedicated model(s) for smartfetch secondary-model summarization. ' +
-          'Accepts a single entry or an array for fallback (each entry can be ' +
-          'a provider/model string or { id, variant? }). ' +
-          'Takes priority over small_model, agents.explorer.model, and agents.librarian.model.',
-      ),
+    model: AgentOverrideConfigSchema.shape.model.describe(
+      'Dedicated model(s) for smartfetch secondary-model summarization. ' +
+        'Same shape as agent model config (string, array of strings/objects with id+variant). ' +
+        'Takes priority over small_model, agents.explorer.model, and agents.librarian.model.',
+    ),
   })
   .strict();
 

+ 13 - 7
src/index.ts

@@ -291,15 +291,21 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       const entries = Array.isArray(webfetchModel)
         ? webfetchModel
         : [webfetchModel];
-      const ids: string[] = [];
-      for (const entry of entries) {
-        // Object form { id, variant? } is accepted for schema consistency
-        // with agent model config. Variant is not forwarded — the secondary
-        // model API uses providerID/modelID only.
+      type ModelRefInput =
+        | string
+        | { id: string; variant?: string };
+      const models: Array<{ id: string; variant?: string }> = [];
+      for (const entry of entries as ModelRefInput[]) {
         const id = typeof entry === 'string' ? entry : entry.id;
-        if (id) ids.push(id);
+        if (!id) continue;
+        models.push({
+          id,
+          ...(typeof entry === 'object' && entry.variant
+            ? { variant: entry.variant }
+            : {}),
+        });
       }
-      return ids.length > 0 ? ids : undefined;
+      return models.length > 0 ? models : undefined;
     })();
     webfetch = createWebfetchTool(ctx, {
       binaryDir: undefined,

+ 22 - 12
src/tools/smartfetch/secondary-model.ts

@@ -74,32 +74,34 @@ async function readEffectiveOpenCodeConfig(directory: string) {
 
 export async function readSecondaryModelFromConfig(
   directory: string,
-  webfetchModels?: string[],
+  webfetchModels?: Array<{ id: string; variant?: string }>,
 ) {
   try {
     const models: SecondaryModel[] = [];
     const seen = new Set<string>();
-    const pushModel = (value: unknown) => {
-      if (typeof value !== 'string') return;
-      const parsedModel = parseModelRef(value);
-      if (!parsedModel) return;
-      const key = `${parsedModel.providerID}/${parsedModel.modelID}`;
+    const addModel = (model: SecondaryModel) => {
+      const key = `${model.providerID}/${model.modelID}${model.variant ? `#${model.variant}` : ''}`;
       if (seen.has(key)) return;
       seen.add(key);
-      models.push(parsedModel);
+      models.push(model);
     };
 
     // Dedicated webfetch model(s) take highest priority, in order
     if (webfetchModels) {
-      for (const model of webfetchModels) pushModel(model);
+      for (const ref of webfetchModels) {
+        const parsedModel = parseModelRef(ref.id);
+        if (!parsedModel) continue;
+        addModel({ ...parsedModel, variant: ref.variant });
+      }
     }
 
     const opencodeConfig = await readEffectiveOpenCodeConfig(directory);
-    pushModel(
+    const parsedSmall = parseModelRef(
       typeof opencodeConfig.small_model === 'string'
         ? opencodeConfig.small_model
         : undefined,
     );
+    if (parsedSmall) addModel(parsedSmall);
 
     const pluginConfig = loadPluginConfig(directory);
     const explorerModel = pickAgentModelRef(
@@ -109,8 +111,15 @@ export async function readSecondaryModelFromConfig(
       pluginConfig.agents?.librarian?.model,
     );
 
-    pushModel(explorerModel);
-    pushModel(librarianModel);
+    const parsedExplorer = explorerModel
+      ? parseModelRef(explorerModel)
+      : undefined;
+    if (parsedExplorer) addModel(parsedExplorer);
+
+    const parsedLibrarian = librarianModel
+      ? parseModelRef(librarianModel)
+      : undefined;
+    if (parsedLibrarian) addModel(parsedLibrarian);
 
     return models;
   } catch {
@@ -263,7 +272,8 @@ async function runSecondaryModel(
         path: { id: sessionId },
         query: { directory },
         body: {
-          model,
+          model: { providerID: model.providerID, modelID: model.modelID },
+          ...(model.variant ? { variant: model.variant } : {}),
           system:
             'Answer only from the supplied content. Do not use tools or outside knowledge.',
           tools: disabledTools,

+ 1 - 1
src/tools/smartfetch/tool.ts

@@ -806,7 +806,7 @@ export function createWebfetchTool(
               secondary_model_input_truncated: secondaryRun.inputTruncated,
               secondary_model_input_chars: secondaryRun.inputChars,
               secondary_model_source_chars: secondaryRun.sourceChars,
-              secondary_model: `${secondaryRun.model.providerID}/${secondaryRun.model.modelID}`,
+              secondary_model: `${secondaryRun.model.providerID}/${secondaryRun.model.modelID}${secondaryRun.model.variant ? `#${secondaryRun.model.variant}` : ''}`,
             })
           : '';
         const secondaryRaw =

+ 11 - 2
src/tools/smartfetch/types.ts

@@ -1,15 +1,24 @@
+export type ModelRef = {
+  /** Provider/model string (e.g. "openai/gpt-4o-mini"). */
+  id: string;
+  /** Optional model variant annotation. */
+  variant?: string;
+};
+
 export type SmartfetchOptions = {
   binaryDir?: string;
   /**
-   * Dedicated model(s) for secondary-model summarization (provider/model format).
+   * Dedicated model(s) for secondary-model summarization.
    * Each entry is tried in order; the first to return usable text is used.
    */
-  webfetchModels?: string[];
+  webfetchModels?: ModelRef[];
 };
 
 export type SecondaryModel = {
   providerID: string;
   modelID: string;
+  /** Optional model variant passed at the body level. */
+  variant?: string;
 };
 
 export type RedirectStep = {