Files
boss/tests/master-agent-openai-fallback.test.ts
2026-04-03 00:21:19 +08:00

372 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
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;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-master-agent-fallback-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const [masterAgent, data] = await Promise.all([
import("../src/lib/boss-master-agent.ts"),
import("../src/lib/boss-data.ts"),
]);
replyToMasterAgentUserMessage = masterAgent.replyToMasterAgentUserMessage;
saveAiAccount = data.saveAiAccount;
readState = data.readState;
updateAiAccountHealth = data.updateAiAccountHealth;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
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",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "offline-node",
nodeLabel: "离线节点",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "通过绑定的 Master Codex Node 对话。",
});
await saveAiAccount({
accountId: "openai-backup",
label: "备用 GPT",
role: "backup",
provider: "openai_api",
displayName: "OpenAI API 备用账号",
accountIdentifier: "sk-demo",
model: "gpt-5.4",
apiKey: "sk-live-demo-123456",
enabled: true,
setActive: false,
loginStatusNote: "备用 API 账号。",
});
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: "主Agent链路正常。" }), {
status: 200,
headers: {
"content-type": "application/json",
"x-request-id": "req-master-fallback",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const result = await replyToMasterAgentUserMessage({
requestMessageId: "msg-master-fallback",
requestText: "请只回复主Agent链路正常。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
});
assert.equal(result.ok, true);
assert.equal(result.accountId, "openai-backup");
assert.equal(result.requestId, "req-master-fallback");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected a master-agent reply to be appended");
assert.equal(reply?.sender, "master");
assert.equal(reply?.senderLabel, "主 Agent · 备用 GPT");
assert.match(reply?.body ?? "", /主Agent链路正常/);
} finally {
globalThis.fetch = originalFetch;
}
});
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",
role: "primary",
provider: "master_codex_node",
displayName: "Mac 上的 Master Codex Node",
nodeId: "offline-node",
nodeLabel: "离线节点",
model: "gpt-5.4",
enabled: true,
setActive: true,
loginStatusNote: "通过绑定的 Master Codex Node 对话。",
});
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://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-aliyun-fallback",
},
});
}
throw new Error(`unexpected fetch: ${String(input)}`);
}) as typeof fetch;
try {
const result = await replyToMasterAgentUserMessage({
requestMessageId: "msg-master-aliyun-fallback",
requestText: "请只回复:阿里备用链路正常。",
requestedBy: "Boss 超级管理员",
requestedByAccount: "17600003315",
});
assert.equal(result.ok, true);
assert.equal(result.accountId, "aliyun-qwen-backup");
assert.equal(result.requestId, "req-master-aliyun-fallback");
const state = await readState();
const masterProject = state.projects.find((project) => project.id === "master-agent");
const reply = masterProject?.messages.at(-1);
assert.ok(reply, "expected a master-agent reply to be appended");
assert.equal(reply?.sender, "master");
assert.equal(reply?.senderLabel, "主 Agent · 阿里备用");
assert.match(reply?.body ?? "", /阿里备用链路正常/);
} finally {
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");
});