feat: per-agent model routing — route different agents to different providers (#238)
* feat: add agentModels and agentRouting to SettingsSchema Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add agentRouting module for per-agent provider resolution Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: thread providerOverride through OpenAI shim for per-agent routing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: getAnthropicClient accepts providerOverride for agent routing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: thread providerOverride through Options and queryModel calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: thread providerOverride through query loop and ToolUseContext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: resolve agent routing in runAgent and inject providerOverride Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add Agent Routing configuration guide to README Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: add unit tests for resolveAgentProvider + plaintext api_key note - 15 tests covering priority chain (name > subagentType > default > null) - normalize() case-insensitive and hyphen/underscore equivalence - Edge cases: null settings, missing config sections, non-existent model - README note about api_key stored in plaintext Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * security: address code review — SSRF, credential leak, key collision - base_url schema now uses z.string().url() for SSRF mitigation - Strip auth headers (Authorization, x-api-key, api-key) from defaultHeaders when providerOverride is active, preventing Anthropic credentials from leaking to third-party endpoints - Warn on duplicate normalized routing keys to prevent silent shadowing - providerOverride.apiKey is never logged (verified via grep) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: 冯俊辉 <fengjunhui@shiyanjia.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -644,7 +644,8 @@ export const AgentTool = buildTool({
|
||||
useExactTools: true
|
||||
}),
|
||||
worktreePath: worktreeInfo?.worktreePath,
|
||||
description
|
||||
description,
|
||||
agentName: name,
|
||||
};
|
||||
|
||||
// Helper to wrap execution with a cwd override: explicit cwd arg (KAIROS)
|
||||
|
||||
@@ -57,6 +57,8 @@ import { clearSessionHooks } from '../../utils/hooks/sessionHooks.js'
|
||||
import { executeSubagentStartHooks } from '../../utils/hooks.js'
|
||||
import { createUserMessage } from '../../utils/messages.js'
|
||||
import { getAgentModel } from '../../utils/model/agent.js'
|
||||
import { resolveAgentProvider } from '../../services/api/agentRouting.js'
|
||||
import { getInitialSettings } from '../../utils/settings/settings.js'
|
||||
import type { ModelAlias } from '../../utils/model/aliases.js'
|
||||
import {
|
||||
clearAgentTranscriptSubdir,
|
||||
@@ -267,6 +269,7 @@ export async function* runAgent({
|
||||
description,
|
||||
transcriptSubdir,
|
||||
onQueryProgress,
|
||||
agentName,
|
||||
}: {
|
||||
agentDefinition: AgentDefinition
|
||||
promptMessages: Message[]
|
||||
@@ -326,6 +329,8 @@ export async function* runAgent({
|
||||
* during long single-block streams (e.g. thinking) where no assistant
|
||||
* message is yielded for >60s. */
|
||||
onQueryProgress?: () => void
|
||||
/** Agent name (team member name) for routing resolution */
|
||||
agentName?: string
|
||||
}): AsyncGenerator<Message, void> {
|
||||
// Track subagent usage for feature discovery
|
||||
|
||||
@@ -344,6 +349,14 @@ export async function* runAgent({
|
||||
permissionMode,
|
||||
)
|
||||
|
||||
// Resolve per-agent provider routing from settings
|
||||
const providerOverride = resolveAgentProvider(
|
||||
agentName,
|
||||
agentDefinition.agentType,
|
||||
getInitialSettings(),
|
||||
)
|
||||
const effectiveModel = providerOverride ? providerOverride.model : resolvedAgentModel
|
||||
|
||||
const agentId = override?.agentId ? override.agentId : createAgentId()
|
||||
|
||||
// Route this agent's transcript into a grouping subdirectory if requested
|
||||
@@ -675,7 +688,8 @@ export async function* runAgent({
|
||||
commands: [],
|
||||
debug: toolUseContext.options.debug,
|
||||
verbose: toolUseContext.options.verbose,
|
||||
mainLoopModel: resolvedAgentModel,
|
||||
mainLoopModel: effectiveModel,
|
||||
providerOverride: providerOverride ?? undefined,
|
||||
// For fork children (useExactTools), inherit thinking config to match the
|
||||
// parent's API request prefix for prompt cache hits. For regular
|
||||
// sub-agents, disable thinking to control output token costs.
|
||||
|
||||
Reference in New Issue
Block a user