123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
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<ExecutionBackendChoice> {
|
|
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<string>();
|
|
|
|
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;
|