Bläddra i källkod

fix: inherit preset primary model for subagents

Berserk Agent 1 månad sedan
förälder
incheckning
ad68cf9d64
3 ändrade filer med 134 tillägg och 14 borttagningar
  1. 28 0
      src/agents/custom.test.ts
  2. 45 0
      src/agents/index.test.ts
  3. 61 14
      src/agents/index.ts

+ 28 - 0
src/agents/custom.test.ts

@@ -152,6 +152,34 @@ describe('custom-agent creation', () => {
     expect(orchestrator?.config.prompt).toContain('@claude-research');
   });
 
+  test('falls back to active preset primary model for ACP wrappers', () => {
+    const config: PluginConfig = {
+      preset: 'opencode-go',
+      presets: {
+        'opencode-go': {
+          orchestrator: { model: 'opencode-go/glm-5.1' },
+        },
+      },
+      agents: {
+        orchestrator: { model: 'opencode-go/glm-5.1' },
+      },
+      acpAgents: {
+        bridge: {
+          command: 'bridge-acp',
+          args: [],
+          env: {},
+          timeoutMs: 0,
+          permissionMode: 'ask',
+        },
+      },
+    };
+
+    const agents = createAgents(config);
+    const wrapper = agents.find((agent) => agent.name === 'bridge');
+
+    expect(wrapper?.config.model).toBe('opencode-go/glm-5.1');
+  });
+
   test('falls back to oracle model for ACP wrappers', () => {
     const defaults = {
       fixer: DEFAULT_MODELS.fixer,

+ 45 - 0
src/agents/index.test.ts

@@ -99,6 +99,51 @@ describe('agent alias backward compatibility', () => {
   });
 });
 
+describe('built-in subagent preset fallback', () => {
+  test('subagents missing from the active preset inherit the preset orchestrator model', () => {
+    const config: PluginConfig = {
+      preset: 'opencode-go',
+      presets: {
+        'opencode-go': {
+          orchestrator: { model: 'opencode-go/glm-5.1' },
+        },
+      },
+      agents: {
+        orchestrator: { model: 'opencode-go/glm-5.1' },
+      },
+      council: councilConfig(),
+      disabled_agents: [],
+    };
+
+    const agents = createAgents(config);
+
+    for (const name of ['observer', 'council', 'councillor'] as const) {
+      expect(agents.find((a) => a.name === name)?.config.model).toBe(
+        'opencode-go/glm-5.1',
+      );
+    }
+  });
+
+  test('subagents missing from the active preset inherit the first preset model when orchestrator is absent', () => {
+    const config: PluginConfig = {
+      preset: 'minimal',
+      presets: {
+        minimal: {
+          oracle: { model: 'anthropic/claude-sonnet-4-6' },
+        },
+      },
+      agents: {
+        oracle: { model: 'anthropic/claude-sonnet-4-6' },
+      },
+      disabled_agents: [],
+    };
+
+    const agents = createAgents(config);
+    const observer = agents.find((a) => a.name === 'observer');
+
+    expect(observer?.config.model).toBe('anthropic/claude-sonnet-4-6');
+  });
+});
 describe('fixer agent fallback', () => {
   test('fixer inherits librarian model when no fixer config provided', () => {
     const config: PluginConfig = {

+ 61 - 14
src/agents/index.ts

@@ -47,9 +47,60 @@ function normalizeDisplayName(displayName: string): string {
   return trimmed.startsWith('@') ? trimmed.slice(1) : trimmed;
 }
 
+function getPrimaryModelFromOverride(
+  override: AgentOverrideConfig | undefined,
+): string | undefined {
+  const model = override?.model;
+  if (typeof model === 'string') {
+    return model;
+  }
+  if (Array.isArray(model) && model.length > 0) {
+    const first = model[0];
+    return typeof first === 'string' ? first : first?.id;
+  }
+  return undefined;
+}
+
+function getActivePresetPrimaryModel(
+  config: PluginConfig | undefined,
+): string | undefined {
+  const activePreset = config?.preset
+    ? config.presets?.[config.preset]
+    : undefined;
+  if (!activePreset) {
+    return undefined;
+  }
+
+  const orchestratorModel = getPrimaryModelFromOverride(
+    activePreset.orchestrator,
+  );
+  if (orchestratorModel) {
+    return orchestratorModel;
+  }
+
+  for (const override of Object.values(activePreset)) {
+    const model = getPrimaryModelFromOverride(override);
+    if (model) {
+      return model;
+    }
+  }
+
+  return undefined;
+}
+
+function getConfigPrimaryModel(
+  config: PluginConfig | undefined,
+): string | undefined {
+  return (
+    getPrimaryModelFromOverride(getAgentOverride(config, 'orchestrator')) ??
+    getActivePresetPrimaryModel(config)
+  );
+}
+
 function buildAcpAgentDefinition(
   name: string,
   config: NonNullable<PluginConfig['acpAgents']>[string],
+  fallbackModel?: string,
 ): AgentDefinition {
   const description =
     config.description ?? `External ACP agent '${name}' via ${config.command}`;
@@ -67,12 +118,7 @@ function buildAcpAgentDefinition(
     name,
     description,
     config: {
-      model:
-        config.wrapperModel ??
-        DEFAULT_MODELS.fixer ??
-        DEFAULT_MODELS.librarian ??
-        DEFAULT_MODELS.orchestrator ??
-        DEFAULT_MODELS.oracle,
+      model: config.wrapperModel ?? fallbackModel ?? DEFAULT_MODELS.oracle,
       temperature: 0,
       prompt,
       permission: {
@@ -167,14 +213,12 @@ function buildCustomAgentDefinition(
   fileAppendPrompt?: string,
 ): AgentDefinition {
   const basePrompt = override.prompt ?? `You are the ${name} specialist.`;
+  const primaryModel = getPrimaryModelFromOverride(override);
 
   return {
     name,
     config: {
-      model:
-        typeof override.model === 'string'
-          ? override.model
-          : (DEFAULT_MODELS.orchestrator ?? DEFAULT_MODELS.oracle),
+      model: primaryModel ?? DEFAULT_MODELS.oracle,
       temperature: 0.2,
       prompt: resolvePrompt(basePrompt, filePrompt, fileAppendPrompt),
     },
@@ -280,6 +324,8 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
     disabled.add('council');
   }
 
+  const primaryModel = getConfigPrimaryModel(config);
+
   // TEMP: If fixer has no config, inherit from librarian's model to avoid breaking
   // existing users who don't have fixer in their config yet
   const getModelForAgent = (name: SubagentName): string => {
@@ -292,10 +338,11 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
       } else {
         librarianModel = librarianOverride;
       }
-      return librarianModel ?? (DEFAULT_MODELS.librarian as string);
+      return (
+        librarianModel ?? primaryModel ?? (DEFAULT_MODELS.librarian as string)
+      );
     }
-    // Subagents always have a defined default model; cast is safe here
-    return DEFAULT_MODELS[name] as string;
+    return primaryModel ?? (DEFAULT_MODELS[name] as string);
   };
 
   // 1. Gather all sub-agent definitions with custom prompts
@@ -372,7 +419,7 @@ export function createAgents(config?: PluginConfig): AgentDefinition[] {
   const protoAcpAgents = acpAgentNames.map((name) => {
     const acp = config?.acpAgents?.[name];
     if (!acp) throw new Error(`ACP agent '${name}' is missing config`);
-    return buildAcpAgentDefinition(name, acp);
+    return buildAcpAgentDefinition(name, acp, primaryModel);
   });
 
   // 2. Apply overrides and default permissions to built-in subagents