fix: expose deepseek model source contract
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { codeModelProviderSourceContract } from "../src/components/microservices/code-queue/src/code-agent/common";
|
||||||
import { codexSubmitModelRegistryForTest, codexSubmitRoutingRecommendationForTest } from "./src/code-queue";
|
import { codexSubmitModelRegistryForTest, codexSubmitRoutingRecommendationForTest } from "./src/code-queue";
|
||||||
|
|
||||||
type JsonRecord = Record<string, unknown>;
|
type JsonRecord = Record<string, unknown>;
|
||||||
@@ -77,6 +78,31 @@ export function runCodeQueueSubmitRoutingContract(): JsonRecord {
|
|||||||
assertCondition(registry.opencodeModels.includes("deepseek-chat"), "opencodeModels should include deepseek-chat", registry);
|
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.opencodeModels.includes("minimax-m2.7"), "opencodeModels should include MiniMax", registry);
|
||||||
assertCondition(registry.codexModels.includes("gpt-5.5"), "codexModels should include default GPT", 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);
|
const commanderOnly = codexSubmitRoutingRecommendationForTest(commanderOnlyPrompt);
|
||||||
assertCondition(commanderOnly.route === "commander-human-only", "prod restart/secrets/DB work should be commander-only", commanderOnly);
|
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",
|
"runtime/core work recommends GPT-5.5/Codex",
|
||||||
"medium bounded frontend work recommends deepseek-chat/OpenCode",
|
"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 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",
|
"dry-run policy contract exposes model-tier concurrency",
|
||||||
"prod/restart/secret/DB work is commander-only",
|
"prod/restart/secret/DB work is commander-only",
|
||||||
"explicit --model mismatch is visible and payload is unchanged",
|
"explicit --model mismatch is visible and payload is unchanged",
|
||||||
|
|||||||
@@ -39,6 +39,17 @@ export const opencodeNpmPackage = "opencode-ai@1.14.48";
|
|||||||
export const defaultCodeExecutionMode: CodeExecutionMode = "default";
|
export const defaultCodeExecutionMode: CodeExecutionMode = "default";
|
||||||
export const codeExecutionModes: CodeExecutionMode[] = ["default", "windows-native"];
|
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 {
|
function proxyUrlFromEnv(env: NodeJS.ProcessEnv): string {
|
||||||
for (const key of gitProxyEnvKeys) {
|
for (const key of gitProxyEnvKeys) {
|
||||||
const value = env[key];
|
const value = env[key];
|
||||||
@@ -122,6 +133,63 @@ export function codeModelPorts(models: string[]): Record<string, CodeAgentPortKi
|
|||||||
return Object.fromEntries(models.map((model) => [model, codeAgentPortForModel(model)]));
|
return Object.fromEntries(models.map((model) => [model, codeAgentPortForModel(model)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function envRef(name: string, env: NodeJS.ProcessEnv): Record<string, JsonValue> {
|
||||||
|
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<string, JsonValue> {
|
||||||
|
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<string, JsonValue> {
|
||||||
|
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<string, JsonValue> {
|
export function codeAgentPortInfo(kind: CodeAgentPortKind): Record<string, JsonValue> {
|
||||||
return {
|
return {
|
||||||
kind,
|
kind,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
defaultCodeModels,
|
defaultCodeModels,
|
||||||
extractRecord,
|
extractRecord,
|
||||||
extractString,
|
extractString,
|
||||||
|
codeModelProviderSourceContract,
|
||||||
normalizeCodeExecutionMode,
|
normalizeCodeExecutionMode,
|
||||||
normalizeCodeModel,
|
normalizeCodeModel,
|
||||||
terminalStatus,
|
terminalStatus,
|
||||||
@@ -5255,6 +5256,7 @@ async function route(req: Request): Promise<Response> {
|
|||||||
status: "starting",
|
status: "starting",
|
||||||
databaseReady,
|
databaseReady,
|
||||||
databaseLastError,
|
databaseLastError,
|
||||||
|
modelProviderConfig: codeModelProviderSourceContract(config),
|
||||||
skills: collectSkillsStatus(),
|
skills: collectSkillsStatus(),
|
||||||
startedAt: serviceStartedAt,
|
startedAt: serviceStartedAt,
|
||||||
}, 503);
|
}, 503);
|
||||||
@@ -5271,6 +5273,7 @@ async function route(req: Request): Promise<Response> {
|
|||||||
schedulerEnabled: config.schedulerEnabled,
|
schedulerEnabled: config.schedulerEnabled,
|
||||||
schedulerPollIntervalMs: config.schedulerPollIntervalMs,
|
schedulerPollIntervalMs: config.schedulerPollIntervalMs,
|
||||||
queue: queueSummary(false, state.tasks),
|
queue: queueSummary(false, state.tasks),
|
||||||
|
modelProviderConfig: codeModelProviderSourceContract(config),
|
||||||
executionDiagnostics: executionDiagnosticsForTasks(state.tasks),
|
executionDiagnostics: executionDiagnosticsForTasks(state.tasks),
|
||||||
skills: collectSkillsStatus(),
|
skills: collectSkillsStatus(),
|
||||||
egressProxy: await providerGatewayEgressProxyStatus(),
|
egressProxy: await providerGatewayEgressProxyStatus(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// 重构前 index.ts 只读参考:commit 6a04144d3f5103014f75b637d7e6bc2f45bf007f,blob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58;可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。
|
// 重构前 index.ts 只读参考:commit 6a04144d3f5103014f75b637d7e6bc2f45bf007f,blob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58;可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。
|
||||||
|
|
||||||
import postgres from "postgres";
|
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 { claudeQqNotificationOutboxStats, notificationTargetConfigured, notificationTargetLabel } from "./notifications";
|
||||||
import { executionModeOptions, executionProviderOptions } from "./provider-runtime";
|
import { executionModeOptions, executionProviderOptions } from "./provider-runtime";
|
||||||
import { taskFullOutput } from "./task-output";
|
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";
|
import type { JsonValue, QueueRecord, QueuedStatusReason, QueueTask, RuntimeConfig, TaskStatus, TranscriptLine } from "./types";
|
||||||
|
|
||||||
export interface QueueApiContext {
|
export interface QueueApiContext {
|
||||||
config: Pick<RuntimeConfig, "codeModels" | "codexModels" | "codexSqliteLogExportBatchSize" | "codexSqliteLogExportEnabled" | "codexSqliteLogExportIntervalMs" | "codexSqliteLogMaxBytes" | "defaultModel" | "defaultReasoningEffort" | "defaultWorkdir" | "judgeMaxTokens" | "judgeRepairAttempts" | "mainProviderId" | "maxActiveQueues" | "maxInMemoryEventRecords" | "maxInMemoryOutputRecords" | "minimaxApiKey" | "minimaxModel" | "modelReasoningEfforts" | "notifyClaudeQqBaseUrl" | "notifyClaudeQqEnabled" | "notifyClaudeQqMaxResponseChars" | "notifyClaudeQqRetryIntervalMs" | "notifyClaudeQqSendAttempts" | "notifyClaudeQqTargetType" | "notifyClaudeQqTimeoutMs" | "outputArchiveDir" | "remoteDefaultWorkdir" | "schedulerEnabled" | "schedulerPollIntervalMs" | "windowsNativeCodexDefaultWorkdir">;
|
config: Pick<RuntimeConfig, "codeModels" | "codexModels" | "codexSqliteLogExportBatchSize" | "codexSqliteLogExportEnabled" | "codexSqliteLogExportIntervalMs" | "codexSqliteLogMaxBytes" | "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultModel" | "defaultReasoningEffort" | "defaultWorkdir" | "judgeMaxTokens" | "judgeRepairAttempts" | "mainProviderId" | "maxActiveQueues" | "maxInMemoryEventRecords" | "maxInMemoryOutputRecords" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "modelReasoningEfforts" | "notifyClaudeQqBaseUrl" | "notifyClaudeQqEnabled" | "notifyClaudeQqMaxResponseChars" | "notifyClaudeQqRetryIntervalMs" | "notifyClaudeQqSendAttempts" | "notifyClaudeQqTargetType" | "notifyClaudeQqTimeoutMs" | "outputArchiveDir" | "remoteDefaultWorkdir" | "schedulerEnabled" | "schedulerPollIntervalMs" | "windowsNativeCodexDefaultWorkdir">;
|
||||||
activeRunSlotQueueIds: () => string[];
|
activeRunSlotQueueIds: () => string[];
|
||||||
activeRunSlotWaiterSummaries: () => JsonValue[];
|
activeRunSlotWaiterSummaries: () => JsonValue[];
|
||||||
activeRuns: Map<string, ActiveRun>;
|
activeRuns: Map<string, ActiveRun>;
|
||||||
@@ -478,6 +478,7 @@ function queueSummary(includeDevReady = true, tasks: QueueTask[] = ctx().tasks()
|
|||||||
codexModels: ctx().config.codexModels,
|
codexModels: ctx().config.codexModels,
|
||||||
opencodeModels: opencodeModelsFor(ctx().config.codeModels),
|
opencodeModels: opencodeModelsFor(ctx().config.codeModels),
|
||||||
modelPorts: codeModelPortsFor(ctx().config.codeModels) as unknown as JsonValue,
|
modelPorts: codeModelPortsFor(ctx().config.codeModels) as unknown as JsonValue,
|
||||||
|
modelProviderConfig: codeModelProviderSourceContract(ctx().config) as unknown as JsonValue,
|
||||||
executionModes: executionModeOptions(),
|
executionModes: executionModeOptions(),
|
||||||
executionModeInfo: Object.fromEntries(codeExecutionModes.map((mode) => [mode, codeExecutionModeInfo(mode)])) as unknown as JsonValue,
|
executionModeInfo: Object.fromEntries(codeExecutionModes.map((mode) => [mode, codeExecutionModeInfo(mode)])) as unknown as JsonValue,
|
||||||
agentPorts: {
|
agentPorts: {
|
||||||
|
|||||||
Reference in New Issue
Block a user