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:
@@ -95,12 +95,14 @@ export async function getAnthropicClient({
|
||||
model,
|
||||
fetchOverride,
|
||||
source,
|
||||
providerOverride,
|
||||
}: {
|
||||
apiKey?: string
|
||||
maxRetries: number
|
||||
model?: string
|
||||
fetchOverride?: ClientOptions['fetch']
|
||||
source?: string
|
||||
providerOverride?: { model: string; baseURL: string; apiKey: string }
|
||||
}): Promise<Anthropic> {
|
||||
const containerId = process.env.CLAUDE_CODE_CONTAINER_ID
|
||||
const remoteSessionId = process.env.CLAUDE_CODE_REMOTE_SESSION_ID
|
||||
@@ -154,6 +156,24 @@ export async function getAnthropicClient({
|
||||
fetch: resolvedFetch,
|
||||
}),
|
||||
}
|
||||
// Agent routing override: use per-agent provider when configured.
|
||||
// Strip auth-related headers to prevent leaking Anthropic credentials
|
||||
// to third-party endpoints (SSRF / credential forwarding mitigation).
|
||||
if (providerOverride) {
|
||||
const { createOpenAIShimClient } = await import('./openaiShim.js')
|
||||
const safeHeaders: Record<string, string> = {}
|
||||
for (const [k, v] of Object.entries(defaultHeaders)) {
|
||||
const lower = k.toLowerCase()
|
||||
if (lower === 'authorization' || lower === 'x-api-key' || lower === 'api-key') continue
|
||||
safeHeaders[k] = v
|
||||
}
|
||||
return createOpenAIShimClient({
|
||||
defaultHeaders: safeHeaders,
|
||||
maxRetries,
|
||||
timeout: parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10),
|
||||
providerOverride,
|
||||
}) as unknown as Anthropic
|
||||
}
|
||||
if (
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB) ||
|
||||
|
||||
Reference in New Issue
Block a user