From 7fde2a3e1aed8f048fd034f9c7a6cb20b26ddb29 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 21 May 2026 14:00:36 +0000 Subject: [PATCH] fix: expose deepseek model source contract --- ...code-queue-submit-routing-contract-test.ts | 27 ++++++++ .../code-queue/src/code-agent/common.ts | 68 +++++++++++++++++++ .../microservices/code-queue/src/index.ts | 3 + .../microservices/code-queue/src/queue-api.ts | 5 +- 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/scripts/code-queue-submit-routing-contract-test.ts b/scripts/code-queue-submit-routing-contract-test.ts index 947ee371..9e02ad35 100644 --- a/scripts/code-queue-submit-routing-contract-test.ts +++ b/scripts/code-queue-submit-routing-contract-test.ts @@ -1,3 +1,4 @@ +import { codeModelProviderSourceContract } from "../src/components/microservices/code-queue/src/code-agent/common"; import { codexSubmitModelRegistryForTest, codexSubmitRoutingRecommendationForTest } from "./src/code-queue"; type JsonRecord = Record; @@ -77,6 +78,31 @@ export function runCodeQueueSubmitRoutingContract(): JsonRecord { assertCondition(registry.opencodeModels.includes("deepseek-chat"), "opencodeModels should include deepseek-chat", registry); assertCondition(registry.opencodeModels.includes("minimax-m2.7"), "opencodeModels should include MiniMax", registry); assertCondition(registry.codexModels.includes("gpt-5.5"), "codexModels should include default GPT", registry); + const providerSource = codeModelProviderSourceContract({ + codeModels: ["gpt-5.5", "deepseek", "minimax-m2.7"], + deepseekApiBase: "https://api.deepseek.example", + deepseekApiKey: "ds-secret-must-not-print", + deepseekModel: "deepseek-chat", + minimaxApiBase: "https://api.minimax.example", + minimaxApiKey: "", + minimaxModel: "MiniMax-M2.7", + }, { + DEEPSEEK_API_KEY: "ds-secret-must-not-print", + DEEPSEEK_API_BASE: "https://api.deepseek.example", + DEEPSEEK_MODEL: "deepseek-chat", + }); + const providerSourceJson = JSON.stringify(providerSource); + assertCondition(!providerSourceJson.includes("ds-secret-must-not-print"), "provider source contract must not print API key values", providerSource); + assertCondition(!providerSourceJson.includes("https://api.deepseek.example"), "provider source contract must not print baseURL values", providerSource); + assertCondition(asRecord(providerSource).valuesPrinted === false, "provider source contract must declare valuesPrinted=false", providerSource); + const providers = asRecord(providerSource.providers); + const deepseekProvider = asRecord(providers.deepseek); + const deepseekCredentialSource = asRecord(deepseekProvider.credentialSource); + assertCondition(deepseekProvider.runner === "opencode", "DeepSeek provider source should use OpenCode", providerSource); + assertCondition(deepseekProvider.publicModel === "deepseek-chat", "DeepSeek provider source should expose public model alias", providerSource); + assertCondition(asRecord(deepseekCredentialSource.apiKey).ref === "env:DEEPSEEK_API_KEY", "DeepSeek apiKey source should expose env ref only", providerSource); + assertCondition(asRecord(deepseekCredentialSource.apiKey).present === true, "DeepSeek apiKey presence should be true when configured", providerSource); + assertCondition(asRecord(deepseekCredentialSource.baseURL).ref === "env:DEEPSEEK_API_BASE", "DeepSeek baseURL source should expose env ref only", providerSource); const commanderOnly = codexSubmitRoutingRecommendationForTest(commanderOnlyPrompt); assertCondition(commanderOnly.route === "commander-human-only", "prod restart/secrets/DB work should be commander-only", commanderOnly); @@ -96,6 +122,7 @@ export function runCodeQueueSubmitRoutingContract(): JsonRecord { "runtime/core work recommends GPT-5.5/Codex", "medium bounded frontend work recommends deepseek-chat/OpenCode", "model registry maps deepseek-chat and minimax-m2.7 to OpenCode and GPT-5.5 to Codex", + "model provider source contract exposes DeepSeek refs/presence without secret values", "dry-run policy contract exposes model-tier concurrency", "prod/restart/secret/DB work is commander-only", "explicit --model mismatch is visible and payload is unchanged", diff --git a/src/components/microservices/code-queue/src/code-agent/common.ts b/src/components/microservices/code-queue/src/code-agent/common.ts index c7e7f9d2..01acfb9a 100644 --- a/src/components/microservices/code-queue/src/code-agent/common.ts +++ b/src/components/microservices/code-queue/src/code-agent/common.ts @@ -39,6 +39,17 @@ export const opencodeNpmPackage = "opencode-ai@1.14.48"; export const defaultCodeExecutionMode: CodeExecutionMode = "default"; export const codeExecutionModes: CodeExecutionMode[] = ["default", "windows-native"]; +export interface CodeModelProviderSourceConfig { + codeModels: string[]; + codexModels?: string[]; + deepseekApiBase: string; + deepseekApiKey: string; + deepseekModel: string; + minimaxApiBase: string; + minimaxApiKey: string; + minimaxModel: string; +} + function proxyUrlFromEnv(env: NodeJS.ProcessEnv): string { for (const key of gitProxyEnvKeys) { const value = env[key]; @@ -122,6 +133,63 @@ export function codeModelPorts(models: string[]): Record [model, codeAgentPortForModel(model)])); } +function envRef(name: string, env: NodeJS.ProcessEnv): Record { + const value = env[name]; + return { + ref: `env:${name}`, + present: typeof value === "string" && value.trim().length > 0, + valuesPrinted: false, + }; +} + +function configRef(name: string, configuredValue: string, env: NodeJS.ProcessEnv): Record { + return { + ...envRef(name, env), + present: configuredValue.trim().length > 0, + envPresent: envRef(name, env).present, + }; +} + +export function codeModelProviderSourceContract(config: CodeModelProviderSourceConfig, env: NodeJS.ProcessEnv = process.env): Record { + const codeModels = Array.from(new Set(config.codeModels.map(normalizeCodeModel).filter(Boolean))); + return { + check: "code-model-provider-source-contract", + valuesPrinted: false, + codeModels, + codexModels: config.codexModels ?? codeModels.filter((model) => codeAgentPortForModel(model) === "codex"), + opencodeModels: opencodeModels(codeModels), + modelPorts: codeModelPorts(codeModels) as unknown as JsonValue, + providers: { + deepseek: { + runner: "opencode", + publicModel: deepseekChatModel, + providerModel: config.deepseekModel.trim() || deepseekChatModel, + credentialSource: { + apiKey: { + ...configRef("DEEPSEEK_API_KEY", config.deepseekApiKey, env), + present: config.deepseekApiKey.trim().length > 0, + }, + baseURL: configRef("DEEPSEEK_API_BASE", config.deepseekApiBase, env), + model: configRef("DEEPSEEK_MODEL", config.deepseekModel, env), + }, + }, + minimax: { + runner: "opencode", + publicModel: minimaxM27Model, + providerModel: config.minimaxModel.trim() || "MiniMax-M2.7", + credentialSource: { + apiKey: { + ...configRef("MINIMAX_API_KEY", config.minimaxApiKey, env), + present: config.minimaxApiKey.trim().length > 0, + }, + baseURL: configRef("MINIMAX_API_BASE", config.minimaxApiBase, env), + model: configRef("MINIMAX_MODEL", config.minimaxModel, env), + }, + }, + }, + }; +} + export function codeAgentPortInfo(kind: CodeAgentPortKind): Record { return { kind, diff --git a/src/components/microservices/code-queue/src/index.ts b/src/components/microservices/code-queue/src/index.ts index bd4fb06d..970f3f69 100644 --- a/src/components/microservices/code-queue/src/index.ts +++ b/src/components/microservices/code-queue/src/index.ts @@ -34,6 +34,7 @@ import { defaultCodeModels, extractRecord, extractString, + codeModelProviderSourceContract, normalizeCodeExecutionMode, normalizeCodeModel, terminalStatus, @@ -5255,6 +5256,7 @@ async function route(req: Request): Promise { status: "starting", databaseReady, databaseLastError, + modelProviderConfig: codeModelProviderSourceContract(config), skills: collectSkillsStatus(), startedAt: serviceStartedAt, }, 503); @@ -5271,6 +5273,7 @@ async function route(req: Request): Promise { schedulerEnabled: config.schedulerEnabled, schedulerPollIntervalMs: config.schedulerPollIntervalMs, queue: queueSummary(false, state.tasks), + modelProviderConfig: codeModelProviderSourceContract(config), executionDiagnostics: executionDiagnosticsForTasks(state.tasks), skills: collectSkillsStatus(), egressProxy: await providerGatewayEgressProxyStatus(), diff --git a/src/components/microservices/code-queue/src/queue-api.ts b/src/components/microservices/code-queue/src/queue-api.ts index feb94188..0a99c783 100644 --- a/src/components/microservices/code-queue/src/queue-api.ts +++ b/src/components/microservices/code-queue/src/queue-api.ts @@ -1,7 +1,7 @@ // 重构前 index.ts 只读参考:commit 6a04144d3f5103014f75b637d7e6bc2f45bf007f,blob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58;可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。 import postgres from "postgres"; -import { codeAgentPortForModel, codeAgentPortInfo, codeExecutionModeInfo, codeExecutionModes, codeModelPorts as codeModelPortsFor, opencodeModels as opencodeModelsFor } from "./code-agent/common"; +import { codeAgentPortForModel, codeAgentPortInfo, codeExecutionModeInfo, codeExecutionModes, codeModelProviderSourceContract, codeModelPorts as codeModelPortsFor, opencodeModels as opencodeModelsFor } from "./code-agent/common"; import { claudeQqNotificationOutboxStats, notificationTargetConfigured, notificationTargetLabel } from "./notifications"; import { executionModeOptions, executionProviderOptions } from "./provider-runtime"; import { taskFullOutput } from "./task-output"; @@ -13,7 +13,7 @@ import type { ActiveRun, ActiveRunSlotWaiter } from "./code-agent/common"; import type { JsonValue, QueueRecord, QueuedStatusReason, QueueTask, RuntimeConfig, TaskStatus, TranscriptLine } from "./types"; export interface QueueApiContext { - config: Pick; + config: Pick; activeRunSlotQueueIds: () => string[]; activeRunSlotWaiterSummaries: () => JsonValue[]; activeRuns: Map; @@ -478,6 +478,7 @@ function queueSummary(includeDevReady = true, tasks: QueueTask[] = ctx().tasks() codexModels: ctx().config.codexModels, opencodeModels: opencodeModelsFor(ctx().config.codeModels), modelPorts: codeModelPortsFor(ctx().config.codeModels) as unknown as JsonValue, + modelProviderConfig: codeModelProviderSourceContract(ctx().config) as unknown as JsonValue, executionModes: executionModeOptions(), executionModeInfo: Object.fromEntries(codeExecutionModes.map((mode) => [mode, codeExecutionModeInfo(mode)])) as unknown as JsonValue, agentPorts: {