Procházet zdrojové kódy

chore: remove production-dead code (agent model resolution, disabled-agents, retryable-error, dead type aliases)

GoldJohnKing před 5 dny
rodič
revize
2de84a2a50

+ 1 - 2
src/agents/codemap.md

@@ -132,10 +132,9 @@ export function getAgentConfigs(config?: PluginConfig): Record<string, SDKAgentC
 The main plugin entry point (`src/index.ts`) consumes the agent system:
 
 ```typescript
-import { createAgents, getAgentConfigs, getDisabledAgents } from './agents';
+import { createAgents, getAgentConfigs } from './agents';
 
 // During plugin initialization:
-const disabledAgents = getDisabledAgents(config);
 const agentDefs = createAgents(config);
 const agents = getAgentConfigs(config);
 

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

@@ -14,9 +14,7 @@ import {
   applyModelInheritanceToConfig,
   createAgents,
   getAgentConfigs,
-  getDisabledAgents,
   isSubagent,
-  resolveAgentConfigModel,
 } from './index';
 import { TASK_REJECTION_INSTRUCTION } from './task-rejection';
 
@@ -1622,16 +1620,6 @@ describe('disabled_agents', () => {
     expect(disabledAgents.length).toBe(5);
   });
 
-  test('getDisabledAgents respects protection rules', () => {
-    const config: PluginConfig = {
-      disabled_agents: ['orchestrator', 'designer', 'councillor'],
-    };
-    const disabled = getDisabledAgents(config);
-    expect(disabled.has('designer')).toBe(true);
-    expect(disabled.has('orchestrator')).toBe(false);
-    expect(disabled.has('councillor')).toBe(false);
-  });
-
   test('empty disabled_agents creates observer but not unconfigured council', () => {
     const config: PluginConfig = {
       disabled_agents: [],
@@ -1776,34 +1764,6 @@ describe('AgentOverrideConfigSchema permission validation', () => {
   });
 });
 
-describe('getDisabledAgents with malformed config', () => {
-  test('falls back to DEFAULT_DISABLED_AGENTS when disabled_agents is not an array', () => {
-    const config: PluginConfig = {
-      disabled_agents: 'not-an-array' as any,
-    };
-    const disabled = getDisabledAgents(config);
-    const expected = getDisabledAgents(undefined);
-    expect(disabled).toEqual(expected);
-  });
-
-  test('falls back to DEFAULT_DISABLED_AGENTS when disabled_agents is an object', () => {
-    const config: PluginConfig = {
-      disabled_agents: { invalid: 'object' } as any,
-    };
-    const disabled = getDisabledAgents(config);
-    const expected = getDisabledAgents(undefined);
-    expect(disabled).toEqual(expected);
-  });
-
-  test('handles valid array normally', () => {
-    const config: PluginConfig = {
-      disabled_agents: ['explorer'],
-    };
-    const disabled = getDisabledAgents(config);
-    expect(disabled.has('explorer')).toBe(true);
-  });
-});
-
 describe('createAgents with malformed disabled_tools', () => {
   test('does not throw when disabled_tools is not an array', () => {
     const config: PluginConfig = {
@@ -1836,115 +1796,3 @@ describe('createAgents with malformed disabled_tools', () => {
     );
   });
 });
-
-describe('resolveAgentConfigModel', () => {
-  test('returns the explicit model when configured', () => {
-    const config: PluginConfig = {
-      agents: { oracle: { model: 'test/oracle-explicit' } },
-    };
-    expect(resolveAgentConfigModel(runtimeFor(config), 'oracle')).toBe(
-      'test/oracle-explicit',
-    );
-  });
-
-  test('returns the primary model of an explicit model array', () => {
-    const config: PluginConfig = {
-      agents: {
-        oracle: { model: ['test/primary', 'test/fallback'] },
-      },
-    };
-    expect(resolveAgentConfigModel(runtimeFor(config), 'oracle')).toBe(
-      'test/primary',
-    );
-  });
-
-  test('session inheritance resolves to no config model (parent session serves)', () => {
-    const config: PluginConfig = {
-      agents: { oracle: { inheritModelFrom: 'session' } },
-    };
-    expect(
-      resolveAgentConfigModel(runtimeFor(config), 'oracle'),
-    ).toBeUndefined();
-  });
-
-  test('orchestrator inheritance uses the configured orchestrator model', () => {
-    const config: PluginConfig = {
-      agents: {
-        orchestrator: { model: 'test/orch' },
-        oracle: { inheritModelFrom: 'orchestrator' },
-      },
-    };
-    expect(resolveAgentConfigModel(runtimeFor(config), 'oracle')).toBe(
-      'test/orch',
-    );
-  });
-
-  test('orchestrator inheritance without an orchestrator model leaves the config model-less', () => {
-    const config: PluginConfig = {
-      agents: { oracle: { inheritModelFrom: 'orchestrator' } },
-    };
-    expect(
-      resolveAgentConfigModel(runtimeFor(config), 'oracle'),
-    ).toBeUndefined();
-  });
-
-  test('fixer with no model inherits the librarian model', () => {
-    const config: PluginConfig = {
-      agents: { librarian: { model: 'anthropic/lib' } },
-    };
-    expect(resolveAgentConfigModel(runtimeFor(config), 'fixer')).toBe(
-      'anthropic/lib',
-    );
-  });
-
-  test('fixer without librarian falls back to the preset primary model', () => {
-    const config: PluginConfig = {
-      preset: 'default',
-      presets: {
-        default: { oracle: { model: 'test/primary' } },
-      },
-    };
-    expect(resolveAgentConfigModel(runtimeFor(config), 'fixer')).toBe(
-      'test/primary',
-    );
-  });
-
-  test('matches the final config model createAgents produces for the fixer case', () => {
-    const config: PluginConfig = {
-      agents: { librarian: { model: 'anthropic/lib' } },
-    };
-    const runtime = runtimeFor(config);
-    const fixer = createAgents(runtime).find((a) => a.name === 'fixer');
-    expect(fixer?.config.model).toBe('anthropic/lib');
-    expect(resolveAgentConfigModel(runtime, 'fixer')).toBe(fixer?.config.model);
-  });
-
-  test('resolves a dynamic councillor primary model from the active council preset', () => {
-    const config: PluginConfig = {
-      council: CouncilConfigSchema.parse({
-        presets: {
-          default: {
-            alpha: { model: ['openai/primary', 'google/fallback'] },
-          },
-        },
-      }),
-    };
-    expect(
-      resolveAgentConfigModel(runtimeFor(config), 'councillor-alpha'),
-    ).toBe('openai/primary');
-  });
-
-  test('resolves an ACP wrapper model for background admission', () => {
-    const config: PluginConfig = {
-      acpAgents: {
-        research: {
-          command: 'research-agent',
-          wrapperModel: 'anthropic/sonnet',
-        },
-      },
-    };
-    expect(resolveAgentConfigModel(runtimeFor(config), 'research')).toBe(
-      'anthropic/sonnet',
-    );
-  });
-});

+ 0 - 75
src/agents/index.ts

@@ -4,11 +4,9 @@ import {
   AGENT_ALIASES,
   type AgentOverrideConfig,
   ALL_AGENT_NAMES,
-  DEFAULT_DISABLED_AGENTS,
   DEFAULT_MODELS,
   loadAgentPrompt,
   type PluginConfig,
-  PROTECTED_AGENTS,
   SUBAGENT_NAMES,
 } from '../config';
 import { getAgentMcpList } from '../config/agent-mcps';
@@ -224,62 +222,6 @@ function applyModelInheritance(
   }
 }
 
-/**
- * Resolve the model an agent's final config carries, mirroring the combined
- * effect of `createAgents` fallbacks, `applyOverrides`, and the inheritance
- * passes. Returns `undefined` exactly when the agent config ends up with NO
- * model key — i.e. `inheritModelFrom: 'session'` (or `'orchestrator'` with no
- * configured orchestrator model) — in which case OpenCode serves the agent
- * with the parent session's current model.
- *
- * This is the single resolution source for both agent definition building and
- * background-task admission, so provider/model concurrency accounting keys off
- * the model the spawned subagent actually uses. Explicit `model` wins; then
- * `inheritModelFrom`; then the historical fixer → librarian fallback; then
- * the preset primary model; then the per-agent default.
- */
-export function resolveAgentConfigModel(
-  runtime: RuntimeConfig,
-  name: string,
-): string | undefined {
-  const mergedAgents = runtime.agents();
-  const override = getOverrideFromAgents(mergedAgents, name);
-  if (override?.model !== undefined) {
-    return getPrimaryModelFromOverride(override);
-  }
-  if (override?.inheritModelFrom === 'session') {
-    return undefined;
-  }
-  if (override?.inheritModelFrom === 'orchestrator') {
-    return getPrimaryModelFromOverride(
-      getOverrideFromAgents(mergedAgents, 'orchestrator'),
-    );
-  }
-  // Dynamic councillors are defined outside `agents()` under the selected
-  // council preset. Their generated agent config carries the preset model.
-  if (name.startsWith('councillor-')) {
-    const seat = name.slice('councillor-'.length);
-    const preset =
-      runtime.council?.presets?.[runtime.council.default_preset ?? 'default'];
-    return preset?.[seat]?.models?.[0]?.id;
-  }
-  // ACP agents are generated from `acpAgents`; admission is for the wrapper
-  // session, so account for its configured wrapper model when present.
-  if (runtime.acpAgents[name]?.wrapperModel) {
-    return runtime.acpAgents[name].wrapperModel;
-  }
-  if (name === 'fixer') {
-    const librarianModel = getPrimaryModelFromOverride(
-      getOverrideFromAgents(mergedAgents, 'librarian'),
-    );
-    return librarianModel ?? runtime.primaryModel ?? DEFAULT_MODELS.librarian;
-  }
-  return (
-    runtime.primaryModel ??
-    (DEFAULT_MODELS as Record<string, string | undefined>)[name]
-  );
-}
-
 /**
  * Apply model inheritance to the final host agent config after the host layer
  * has been merged. This clears stale host models for `session` inheritance,
@@ -919,20 +861,3 @@ export function getAgentConfigs(
 
   return Object.fromEntries(entries);
 }
-
-/**
- * Get the set of disabled agent names from config, applying protection rules.
- */
-export function getDisabledAgents(config?: PluginConfig): Set<string> {
-  const userDisabled = config?.disabled_agents;
-  const disabledSource = Array.isArray(userDisabled)
-    ? userDisabled
-    : DEFAULT_DISABLED_AGENTS;
-  const disabled = new Set<string>();
-  for (const name of disabledSource) {
-    if (!PROTECTED_AGENTS.has(name)) {
-      disabled.add(name);
-    }
-  }
-  return disabled;
-}

+ 0 - 2
src/config/constants.ts

@@ -30,8 +30,6 @@ export const AGENT_THEME_COLORS = [
   'info',
 ] as const;
 
-export type AgentThemeColor = (typeof AGENT_THEME_COLORS)[number];
-
 /** Agents that cannot be disabled even if listed in disabled_agents config. */
 export const PROTECTED_AGENTS = new Set(['orchestrator', 'councillor']);
 

+ 0 - 6
src/config/schema.ts

@@ -57,9 +57,6 @@ export const AgentColorSchema = z.union([
 
 // Agent override configuration (distinct from SDK's AgentConfig)
 export const ModelInheritanceSourceSchema = z.enum(['session', 'orchestrator']);
-export type ModelInheritanceSource = z.infer<
-  typeof ModelInheritanceSourceSchema
->;
 
 export const AgentOverrideConfigSchema = z
   .object({
@@ -434,9 +431,6 @@ export const AcpAgentConfigSchema = z
 
 export const AcpAgentsConfigSchema = z.record(z.string(), AcpAgentConfigSchema);
 
-export type AcpAgentPermissionMode = z.infer<
-  typeof AcpAgentPermissionModeSchema
->;
 export type AcpAgentConfig = z.infer<typeof AcpAgentConfigSchema>;
 export type AcpAgentsConfig = z.infer<typeof AcpAgentsConfigSchema>;
 

+ 1 - 1
src/hooks/cache-safe-injection.ts

@@ -43,7 +43,7 @@ export interface SyntheticPartCacheHint {
   ttlSeconds?: number;
 }
 
-export interface TaggedSyntheticPartSpec {
+interface TaggedSyntheticPartSpec {
   /** Text content of the injected part. */
   text: string;
   /**

+ 1 - 1
src/hooks/foreground-fallback/codemap.md

@@ -48,7 +48,7 @@ OpenCode Event (message.updated/session.error/session.status)
 ForegroundFallbackManager.handleEvent()
-Retryable error detection via isRetryableError() / isFailoverError()
+Failover error detection via isFailoverError()
 tryFallback(sessionID) [deduplicated, in-progress guarded]

+ 44 - 48
src/hooks/foreground-fallback/index.test.ts

@@ -1,11 +1,7 @@
 import { beforeEach, describe, expect, mock, test } from 'bun:test';
 import { isInternalInitiatorPart } from '../../utils';
 import { SessionLifecycle } from '../session-lifecycle';
-import {
-  ForegroundFallbackManager,
-  isFailoverError,
-  isRetryableError,
-} from './index';
+import { ForegroundFallbackManager, isFailoverError } from './index';
 
 // ACCEPTANCE GAP: config() hook behaviour is not covered by CI — verify live.
 
@@ -108,22 +104,22 @@ describe('isFailoverError', () => {
   });
 
   test('returns true for 429 status code', () => {
-    expect(isRetryableError({ data: { statusCode: 429 } })).toBe(true);
+    expect(isFailoverError({ data: { statusCode: 429 } })).toBe(true);
   });
 
   test('returns true for "rate limit" in message', () => {
-    expect(isRetryableError({ message: 'Rate limit exceeded' })).toBe(true);
+    expect(isFailoverError({ message: 'Rate limit exceeded' })).toBe(true);
   });
 
   test('returns true for "quota exceeded" in responseBody', () => {
-    expect(isRetryableError({ data: { responseBody: 'quota exceeded' } })).toBe(
+    expect(isFailoverError({ data: { responseBody: 'quota exceeded' } })).toBe(
       true,
     );
   });
 
   test('returns true for bailian "quota has been exhausted" (issue #1083)', () => {
     expect(
-      isRetryableError({
+      isFailoverError({
         message:
           'Your token-plan 1-week quota has been exhausted. The quota will reset at 08-27 15:33:00 UTC.',
       }),
@@ -198,24 +194,24 @@ describe('isFailoverError', () => {
   });
 
   test('returns true for "usage exceeded"', () => {
-    expect(isRetryableError({ message: 'usage exceeded' })).toBe(true);
+    expect(isFailoverError({ message: 'usage exceeded' })).toBe(true);
   });
 
   test('returns true for "overloaded"', () => {
-    expect(isRetryableError({ message: 'overloaded_error' })).toBe(true);
+    expect(isFailoverError({ message: 'overloaded_error' })).toBe(true);
   });
 
   test('returns true for "Insufficient balance."', () => {
-    expect(isRetryableError({ message: 'Insufficient balance.' })).toBe(true);
+    expect(isFailoverError({ message: 'Insufficient balance.' })).toBe(true);
   });
 
   test('returns true for "Service Unavailable"', () => {
-    expect(isRetryableError({ message: 'Service Unavailable' })).toBe(true);
+    expect(isFailoverError({ message: 'Service Unavailable' })).toBe(true);
   });
 
   test('returns true for "Monthly usage limit reached"', () => {
     expect(
-      isRetryableError({
+      isFailoverError({
         message: 'Monthly usage limit reached. Resets in X days.',
       }),
     ).toBe(true);
@@ -223,7 +219,7 @@ describe('isFailoverError', () => {
 
   test('returns true for "5-hour usage limit reached"', () => {
     expect(
-      isRetryableError({
+      isFailoverError({
         message: '5-hour usage limit reached. Resets in 36min.',
       }),
     ).toBe(true);
@@ -231,90 +227,90 @@ describe('isFailoverError', () => {
 
   test('returns true for "Weekly usage limit reached"', () => {
     expect(
-      isRetryableError({
+      isFailoverError({
         message: 'Weekly usage limit reached. Resets in 2 days.',
       }),
     ).toBe(true);
   });
 
   test('returns false for non-rate-limit error', () => {
-    expect(isRetryableError({ message: 'invalid API key' })).toBe(false);
+    expect(isFailoverError({ message: 'invalid API key' })).toBe(false);
   });
 
   test('returns false for null', () => {
-    expect(isRetryableError(null)).toBe(false);
+    expect(isFailoverError(null)).toBe(false);
   });
 
   test('returns true for string error with rate-limit message', () => {
-    expect(isRetryableError('Usage exceeded')).toBe(true);
-    expect(isRetryableError('rate limit exceeded')).toBe(true);
-    expect(isRetryableError('quota exceeded')).toBe(true);
+    expect(isFailoverError('Usage exceeded')).toBe(true);
+    expect(isFailoverError('rate limit exceeded')).toBe(true);
+    expect(isFailoverError('quota exceeded')).toBe(true);
   });
 
   test('returns false for non-object', () => {
-    expect(isRetryableError(42)).toBe(false);
+    expect(isFailoverError(42)).toBe(false);
   });
 
   test('returns true for 403 status code', () => {
-    expect(isRetryableError({ data: { statusCode: 403 } })).toBe(true);
+    expect(isFailoverError({ data: { statusCode: 403 } })).toBe(true);
   });
 
   test('returns true for 401 status code', () => {
-    expect(isRetryableError({ statusCode: 401 })).toBe(true);
-    expect(isRetryableError({ data: { statusCode: 401 } })).toBe(true);
+    expect(isFailoverError({ statusCode: 401 })).toBe(true);
+    expect(isFailoverError({ data: { statusCode: 401 } })).toBe(true);
   });
 
   test('returns true for 410 Gone (model end-of-life)', () => {
-    expect(isRetryableError({ statusCode: 410 })).toBe(true);
-    expect(isRetryableError({ data: { statusCode: 410 } })).toBe(true);
+    expect(isFailoverError({ statusCode: 410 })).toBe(true);
+    expect(isFailoverError({ data: { statusCode: 410 } })).toBe(true);
     expect(
-      isRetryableError({
+      isFailoverError({
         message:
           "The model 'mistralai/mistral-small-4-119b-2603' has reached its end of life on 2026-07-27T00:00:00Z and is no longer available.",
       }),
     ).toBe(true);
     // The AI SDK surfaces HTTP 410 as the bare title "Gone" in the message.
-    expect(isRetryableError({ message: 'AI_APICallError: Gone' })).toBe(true);
-    expect(isRetryableError('Gone')).toBe(true);
+    expect(isFailoverError({ message: 'AI_APICallError: Gone' })).toBe(true);
+    expect(isFailoverError('Gone')).toBe(true);
   });
 
   test('returns true for 401 upstream provider error message', () => {
     expect(
-      isRetryableError(
+      isFailoverError(
         'AI_APICallError: Upstream request failed: [401] Provider returned error',
       ),
     ).toBe(true);
     expect(
-      isRetryableError({
+      isFailoverError({
         message:
           'AI_APICallError: Upstream request failed: [401] Provider returned error',
       }),
     ).toBe(true);
     expect(
-      isRetryableError({ data: { message: 'Upstream request failed [401]' } }),
+      isFailoverError({ data: { message: 'Upstream request failed [401]' } }),
     ).toBe(true);
   });
 
   test('returns true for "Forbidden" in message', () => {
-    expect(isRetryableError({ message: '403 Forbidden' })).toBe(true);
+    expect(isFailoverError({ message: '403 Forbidden' })).toBe(true);
   });
 
   test('returns true for "blocked by gateway" in message', () => {
-    expect(isRetryableError({ message: 'blocked by gateway' })).toBe(true);
+    expect(isFailoverError({ message: 'blocked by gateway' })).toBe(true);
   });
 
   test('returns true for "forbidden" (lowercase) in message', () => {
-    expect(isRetryableError({ message: 'forbidden' })).toBe(true);
+    expect(isFailoverError({ message: 'forbidden' })).toBe(true);
   });
 
   test('returns true for NewAPI "no available channel" error shapes', () => {
     const message =
       'No available channel for model gpt-5.6-luna under group Codex专用 (distributor) (request id: abc123)';
 
-    expect(isRetryableError(message)).toBe(true);
-    expect(isRetryableError({ message })).toBe(true);
+    expect(isFailoverError(message)).toBe(true);
+    expect(isFailoverError({ message })).toBe(true);
     expect(
-      isRetryableError({
+      isFailoverError({
         data: { statusCode: 400, responseBody: message },
       }),
     ).toBe(true);
@@ -324,15 +320,15 @@ describe('isFailoverError', () => {
     const message =
       'auth_unavailable: no auth available (providers=cli-proxy-api, model=gemini-3.6-flash)';
 
-    expect(isRetryableError(message)).toBe(true);
-    expect(isRetryableError({ message })).toBe(true);
+    expect(isFailoverError(message)).toBe(true);
+    expect(isFailoverError({ message })).toBe(true);
     expect(
-      isRetryableError({
+      isFailoverError({
         data: { statusCode: 400, responseBody: message },
       }),
     ).toBe(true);
     expect(
-      isRetryableError({
+      isFailoverError({
         data: {
           responseBody:
             '{"error":{"message":"auth_unavailable: no auth available","type":"server_error","code":"internal_server_error"}}',
@@ -342,20 +338,20 @@ describe('isFailoverError', () => {
   });
 
   test('returns true for "cannot connect to API" transport errors', () => {
-    expect(isRetryableError('Cannot connect to API')).toBe(true);
-    expect(isRetryableError('stream error: Cannot connect to API')).toBe(true);
+    expect(isFailoverError('Cannot connect to API')).toBe(true);
+    expect(isFailoverError('stream error: Cannot connect to API')).toBe(true);
     expect(
-      isRetryableError({ message: 'stream error: Cannot connect to API' }),
+      isFailoverError({ message: 'stream error: Cannot connect to API' }),
     ).toBe(true);
   });
 
   test('returns false for non-API connection errors', () => {
-    expect(isRetryableError('Cannot connect to database')).toBe(false);
+    expect(isFailoverError('Cannot connect to database')).toBe(false);
   });
 
   test('returns false for permanent channel-not-found errors', () => {
     expect(
-      isRetryableError({
+      isFailoverError({
         message: 'channel not found for model gpt-5.6-luna',
       }),
     ).toBe(false);

+ 0 - 8
src/hooks/foreground-fallback/index.ts

@@ -215,14 +215,6 @@ export function isFailoverError(error: unknown): boolean {
   return hasFailoverReason;
 }
 
-/**
- * Checks whether an error is a transient/retryable error (rate-limit,
- * 403/Forbidden, etc.) that should trigger model fallback.
- */
-export function isRetryableError(error: unknown): boolean {
-  return isFailoverError(error);
-}
-
 const INLINE_STATUS_CODES = new Set([401, 410]);
 
 /**

+ 0 - 2
src/hooks/index.ts

@@ -13,7 +13,6 @@ export {
   isTaggedPart,
   isVolatileTaggedMessage,
   stripTaggedContent,
-  type TaggedSyntheticPartSpec,
 } from './cache-safe-injection';
 export { createChatHeadersHook } from './chat-headers';
 export { createDeepworkCommandHook } from './deepwork';
@@ -21,7 +20,6 @@ export { createFilterAvailableSkillsHook } from './filter-available-skills';
 export {
   ForegroundFallbackManager,
   isFailoverError,
-  isRetryableError,
 } from './foreground-fallback';
 export { processImageAttachments } from './image-hook';
 export { createJsonErrorRecoveryHook } from './json-error-recovery/hook';