refactor: add execution backend selection

This commit is contained in:
kris
2026-04-03 00:21:19 +08:00
parent a3a4f3e980
commit 8a62e72fd5
11 changed files with 1067 additions and 318 deletions

View File

@@ -2,12 +2,13 @@ import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm } from "node:fs/promises";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
let runtimeRoot = "";
let replyToMasterAgentUserMessage: (typeof import("../src/lib/boss-master-agent"))["replyToMasterAgentUserMessage"];
let saveAiAccount: (typeof import("../src/lib/boss-data"))["saveAiAccount"];
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let updateAiAccountHealth: (typeof import("../src/lib/boss-data"))["updateAiAccountHealth"];
async function setup() {
if (runtimeRoot) return;
@@ -24,6 +25,7 @@ async function setup() {
replyToMasterAgentUserMessage = masterAgent.replyToMasterAgentUserMessage;
saveAiAccount = data.saveAiAccount;
readState = data.readState;
updateAiAccountHealth = data.updateAiAccountHealth;
}
test.after(async () => {
@@ -32,9 +34,13 @@ test.after(async () => {
}
});
test("replyToMasterAgentUserMessage falls back to a runnable OpenAI API account when the master node is offline", async () => {
test.beforeEach(async () => {
await setup();
await rm(runtimeRoot, { recursive: true, force: true });
await mkdir(runtimeRoot, { recursive: true });
});
test("replyToMasterAgentUserMessage falls back to a runnable OpenAI API account when the master node is offline", async () => {
await saveAiAccount({
accountId: "master-codex-primary",
label: "主 GPT",
@@ -101,9 +107,81 @@ test("replyToMasterAgentUserMessage falls back to a runnable OpenAI API account
}
});
test("replyToMasterAgentUserMessage falls back to a runnable aliyun qwen backup account when the master node is offline", async () => {
await setup();
test("replyToMasterAgentUserMessage can retry the same degraded API account when it is the only available backend", async () => {
await saveAiAccount({
accountId: "master-codex-primary",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "offline-node",
nodeLabel: "离线节点",
model: "gpt-5.4",
enabled: true,
setActive: false,
loginStatusNote: "测试中显式模拟默认主节点离线。",
});
await updateAiAccountHealth({
accountId: "master-codex-primary",
status: "degraded",
lastError: "MASTER_CODEX_NODE_DEVICE_OFFLINE",
lastValidatedAt: new Date().toISOString(),
});
await saveAiAccount({
accountId: "openai-primary-degraded",
label: "OpenAI 主控",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主账号",
model: "gpt-5.4",
apiKey: "sk-openai-only",
enabled: true,
setActive: true,
loginStatusNote: "唯一可用的 OpenAI 账号。",
});
await updateAiAccountHealth({
accountId: "openai-primary-degraded",
status: "degraded",
lastError: "temporary failure",
lastValidatedAt: new Date().toISOString(),
});
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
if (typeof input === "string" && input === "https://api.openai.com/v1/responses") {
return new Response(JSON.stringify({ output_text: "仍然可以重试同一个 API 账号。" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-openai-degraded-retry",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const result = await replyToMasterAgentUserMessage({
requestMessageId: "msg-openai-degraded-retry",
requestText: "请只回复:仍然可以重试同一个 API 账号。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
});
assert.equal(result.ok, true);
assert.equal(result.accountId, "openai-primary-degraded");
const state = await readState();
const account = state.aiAccounts.find((item) => item.accountId === "openai-primary-degraded");
assert.equal(account?.status, "ready");
assert.equal(account?.isActive, true);
} finally {
globalThis.fetch = originalFetch;
}
});
test("replyToMasterAgentUserMessage falls back to a runnable aliyun qwen backup account when the master node is offline", async () => {
await saveAiAccount({
accountId: "master-codex-primary",
label: "主 GPT",
@@ -169,3 +247,125 @@ test("replyToMasterAgentUserMessage falls back to a runnable aliyun qwen backup
globalThis.fetch = originalFetch;
}
});
test("replyToMasterAgentUserMessage retries the next ready API backup when the first API backend call fails", async () => {
await saveAiAccount({
accountId: "openai-primary-ready",
label: "OpenAI 主控",
role: "primary",
provider: "openai_api",
displayName: "OpenAI 主账号",
model: "gpt-5.4",
apiKey: "sk-openai-primary",
enabled: true,
setActive: true,
loginStatusNote: "主 OpenAI 账号。",
});
await saveAiAccount({
accountId: "aliyun-qwen-backup",
label: "阿里备用",
role: "backup",
provider: "aliyun_qwen_api",
displayName: "阿里百炼备用账号",
accountIdentifier: "dashscope-demo",
model: "qwen3.5-plus",
apiKey: "sk-aliyun-demo-123456",
enabled: true,
setActive: false,
loginStatusNote: "阿里百炼 Qwen 备用账号。",
});
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input) => {
if (typeof input === "string" && input === "https://api.openai.com/v1/responses") {
return new Response(JSON.stringify({ error: { message: "openai temporary failure" } }), {
status: 500,
headers: { "content-type": "application/json" },
});
}
if (typeof input === "string" && input === "https://dashscope.aliyuncs.com/compatible-mode/v1/responses") {
return new Response(JSON.stringify({ output_text: "阿里备用接管成功。" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-master-api-chain",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const result = await replyToMasterAgentUserMessage({
requestMessageId: "msg-master-api-chain",
requestText: "请只回复:阿里备用接管成功。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
});
assert.equal(result.ok, true);
assert.equal(result.accountId, "aliyun-qwen-backup");
assert.equal(result.requestId, "req-master-api-chain");
const state = await readState();
const openaiAccount = state.aiAccounts.find((item) => item.accountId === "openai-primary-ready");
assert.equal(openaiAccount?.status, "degraded");
const aliyunAccount = state.aiAccounts.find((item) => item.accountId === "aliyun-qwen-backup");
assert.equal(aliyunAccount?.isActive, true);
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected a fallback reply to be appended");
assert.match(reply?.body ?? "", /阿里备用接管成功/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("replyToMasterAgentUserMessage falls back to a ready backup master node account when API backends are unavailable", async () => {
await saveAiAccount({
accountId: "master-codex-primary-offline",
label: "主 GPT",
role: "primary",
provider: "master_codex_node",
displayName: "离线主节点",
nodeId: "offline-node",
nodeLabel: "离线节点",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "离线主节点。",
});
await saveAiAccount({
accountId: "master-codex-backup-ready",
label: "备用主节点",
role: "backup",
provider: "master_codex_node",
displayName: "在线备用 Master Codex Node",
nodeId: "mac-studio",
nodeLabel: "Mac Studio",
model: "gpt-5.4",
enabled: true,
setActive: false,
loginStatusNote: "在线备用主节点。",
});
const result = await replyToMasterAgentUserMessage({
requestMessageId: "msg-master-node-backup-fallback",
requestText: "请切到备用主节点。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
mode: "enqueue",
});
assert.equal(result.ok, true);
assert.equal(result.accountId, "master-codex-backup-ready");
assert.ok(result.taskId, "expected a queued master-agent task");
const state = await readState();
const task = state.masterAgentTasks.find((item) => item.taskId === result.taskId);
assert.ok(task, "expected queued task to be written into state");
assert.equal(task?.accountId, "master-codex-backup-ready");
assert.equal(task?.deviceId, "mac-studio");
});