import type { AiAccountStatus, AiProvider } from "@/lib/boss-data"; import { ALIYUN_QWEN_BACKEND, isReadyAliyunQwenBackend, } from "@/lib/execution/backends/aliyun-qwen-backend"; import { CLAW_BACKEND, type ClawBackendSelectionState, isClawRequestKindSupported, } from "@/lib/execution/backends/claw-backend"; import { MASTER_CODEX_NODE_BACKEND, isReadyMasterCodexNodeBackend, } from "@/lib/execution/backends/master-codex-node-backend"; import { OPENAI_BACKEND, isReadyOpenAiBackend } from "@/lib/execution/backends/openai-backend"; import type { ExecutionRequestKind } from "@/lib/execution/types"; export interface ExecutionBackendSelectionInput { primary: { provider: AiProvider; status: AiAccountStatus; }; backups: Array<{ provider: AiProvider; status: AiAccountStatus; }>; requestKind?: ExecutionRequestKind; requestedBackendId?: string; claw?: ClawBackendSelectionState; } export type ExecutionBackendChoice = | typeof CLAW_BACKEND | typeof MASTER_CODEX_NODE_BACKEND | typeof OPENAI_BACKEND | typeof ALIYUN_QWEN_BACKEND; function resolveBackendByProvider(provider: AiProvider): ExecutionBackendChoice { switch (provider) { case "master_codex_node": return MASTER_CODEX_NODE_BACKEND; case "openai_api": return OPENAI_BACKEND; case "aliyun_qwen_api": return ALIYUN_QWEN_BACKEND; default: return MASTER_CODEX_NODE_BACKEND; } } function isReadyBackend(choice: ExecutionBackendChoice, input: ExecutionBackendSelectionInput) { if (choice.backendId === CLAW_BACKEND.backendId) { const requestKind = input.requestKind; if (!input.claw?.enabled || !requestKind) { return false; } return isClawRequestKindSupported(requestKind); } const candidates = [ ...(input.primary.provider === choice.provider ? [input.primary] : []), ...input.backups.filter((item) => item.provider === choice.provider), ]; return candidates.some((candidate) => { switch (choice.backendId) { case "aliyun-qwen": return isReadyAliyunQwenBackend(candidate); case "openai-api": return isReadyOpenAiBackend(candidate); case "master-codex-node": return isReadyMasterCodexNodeBackend(candidate); default: return false; } }); } export async function selectExecutionBackend( input: ExecutionBackendSelectionInput, ): Promise { return listExecutionBackendChoices(input)[0] ?? resolveBackendByProvider(input.primary.provider); } export function listExecutionBackendChoices( input: ExecutionBackendSelectionInput, ): ExecutionBackendChoice[] { const primaryBackend = resolveBackendByProvider(input.primary.provider); const ordered: ExecutionBackendChoice[] = []; const seen = new Set(); const pushBackend = (backend: ExecutionBackendChoice) => { if (seen.has(backend.backendId)) { return; } ordered.push(backend); seen.add(backend.backendId); }; if ( input.requestedBackendId === CLAW_BACKEND.backendId && isReadyBackend(CLAW_BACKEND, input) ) { pushBackend(CLAW_BACKEND); } if (input.primary.status === "ready") { pushBackend(primaryBackend); } const orderedBackends: ExecutionBackendChoice[] = [ALIYUN_QWEN_BACKEND, OPENAI_BACKEND, MASTER_CODEX_NODE_BACKEND]; for (const backend of orderedBackends) { if (isReadyBackend(backend, input)) { pushBackend(backend); } } pushBackend(primaryBackend); return ordered; } export const selectExecutionBackendForTesting = selectExecutionBackend;