feat: add aliyun qwen backup provider
This commit is contained in:
@@ -127,7 +127,7 @@ export type LoginMethod = "password" | "code";
|
||||
export type OtaUpdateStatus = "available" | "scheduled" | "applied" | "skipped";
|
||||
export type OtaLogStatus = "checked" | "applied" | "skipped";
|
||||
export type AppLogLevel = "info" | "warn" | "error";
|
||||
export type AiProvider = "master_codex_node" | "openai_api";
|
||||
export type AiProvider = "master_codex_node" | "openai_api" | "aliyun_qwen_api";
|
||||
export type AiAccountRole = "primary" | "backup" | "api_fallback";
|
||||
export type AiAccountStatus = "ready" | "needs_login" | "needs_api_key" | "degraded" | "disabled";
|
||||
export type MasterAgentTaskStatus = "queued" | "running" | "completed" | "failed";
|
||||
@@ -2254,6 +2254,8 @@ export function aiProviderLabel(provider: AiProvider) {
|
||||
return "Master Codex Node / ChatGPT Plus 节点";
|
||||
case "openai_api":
|
||||
return "OpenAI API";
|
||||
case "aliyun_qwen_api":
|
||||
return "阿里百炼 Qwen";
|
||||
default:
|
||||
return provider;
|
||||
}
|
||||
@@ -2283,9 +2285,13 @@ function maskApiKey(value?: string) {
|
||||
return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
|
||||
}
|
||||
|
||||
function isApiKeyProvider(provider: AiProvider) {
|
||||
return provider === "openai_api" || provider === "aliyun_qwen_api";
|
||||
}
|
||||
|
||||
function deriveAiAccountStatus(account: AiAccount): AiAccountStatus {
|
||||
if (!account.enabled) return "disabled";
|
||||
if (account.provider === "openai_api") {
|
||||
if (isApiKeyProvider(account.provider)) {
|
||||
if (!account.apiKey?.trim()) return "needs_api_key";
|
||||
return account.status === "disabled" ? "ready" : account.status;
|
||||
}
|
||||
@@ -2458,7 +2464,7 @@ export function getMasterIdentitySummaryFromState(state: BossState): MasterIdent
|
||||
status: "needs_api_key",
|
||||
statusLabel: aiStatusLabel("needs_api_key"),
|
||||
canGenerate: false,
|
||||
note: "请到“我的 > AI 账号”至少配置一个可用的 Master Codex Node 或 OpenAI API 账号。",
|
||||
note: "请到“我的 > AI 账号”至少配置一个可用的 Master Codex Node、OpenAI API 或阿里百炼 Qwen 账号。",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4662,6 +4668,12 @@ export async function saveAiAccount(payload: {
|
||||
existing?.accountId ??
|
||||
payload.accountId?.trim() ??
|
||||
`ai-${slugify(`${payload.label}-${payload.displayName}`)}`;
|
||||
const defaultModel =
|
||||
payload.provider === "aliyun_qwen_api"
|
||||
? "qwen3.5-plus"
|
||||
: payload.provider === "openai_api"
|
||||
? "gpt-5.4"
|
||||
: undefined;
|
||||
const next: AiAccount = normalizeAiAccount({
|
||||
accountId,
|
||||
label: payload.label.trim() || aiRoleLabel(payload.role),
|
||||
@@ -4671,21 +4683,21 @@ export async function saveAiAccount(payload: {
|
||||
accountIdentifier: payload.accountIdentifier?.trim() || undefined,
|
||||
nodeId: payload.nodeId?.trim() || undefined,
|
||||
nodeLabel: payload.nodeLabel?.trim() || undefined,
|
||||
model: payload.model?.trim() || (payload.provider === "openai_api" ? "gpt-5.4" : undefined),
|
||||
model: payload.model?.trim() || defaultModel,
|
||||
apiKey:
|
||||
payload.provider === "openai_api"
|
||||
isApiKeyProvider(payload.provider)
|
||||
? payload.apiKey?.trim()
|
||||
? payload.apiKey.trim()
|
||||
: existing?.apiKey
|
||||
: undefined,
|
||||
apiKeyMasked:
|
||||
payload.provider === "openai_api"
|
||||
isApiKeyProvider(payload.provider)
|
||||
? maskApiKey(payload.apiKey?.trim() || existing?.apiKey)
|
||||
: undefined,
|
||||
enabled: payload.enabled ?? existing?.enabled ?? true,
|
||||
isActive: existing?.isActive ?? false,
|
||||
status:
|
||||
payload.provider === "openai_api"
|
||||
isApiKeyProvider(payload.provider)
|
||||
? payload.apiKey?.trim() || existing?.apiKey
|
||||
? existing?.status === "degraded"
|
||||
? "degraded"
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
updateAttachmentAnalysisResult,
|
||||
updateAiAccountHealth,
|
||||
} from "@/lib/boss-data";
|
||||
import type { DispatchPlanTarget, Project, ProjectAgentControls, ReasoningEffort } from "@/lib/boss-data";
|
||||
import type { AiProvider, DispatchPlanTarget, Project, ProjectAgentControls, ReasoningEffort } from "@/lib/boss-data";
|
||||
import { canInlineAttachmentText, extractAttachmentTextExcerpt } from "@/lib/boss-attachments";
|
||||
import { readAliyunOssObjectBuffer } from "@/lib/boss-storage-aliyun-oss";
|
||||
import { readServerFileAttachmentBuffer } from "@/lib/boss-storage-server-file";
|
||||
@@ -31,6 +31,32 @@ import {
|
||||
|
||||
type MasterAgentReplyState = "queued" | "running" | "completed";
|
||||
const OPENAI_MASTER_AGENT_DEVICE_ID = "master-agent-openai";
|
||||
const ALIYUN_QWEN_DEVICE_ID = "master-agent-aliyun-qwen";
|
||||
|
||||
type ApiCompatibleProvider = Extract<AiProvider, "openai_api" | "aliyun_qwen_api">;
|
||||
|
||||
const API_PROVIDER_CONFIG: Record<
|
||||
ApiCompatibleProvider,
|
||||
{
|
||||
label: string;
|
||||
endpoint: string;
|
||||
defaultModel: string;
|
||||
loginLabel: string;
|
||||
}
|
||||
> = {
|
||||
openai_api: {
|
||||
label: "OpenAI API",
|
||||
endpoint: "https://api.openai.com/v1/responses",
|
||||
defaultModel: "gpt-5.4",
|
||||
loginLabel: "OpenAI API Key",
|
||||
},
|
||||
aliyun_qwen_api: {
|
||||
label: "阿里百炼 Qwen",
|
||||
endpoint: "https://dashscope.aliyuncs.com/compatible-mode/v1/responses",
|
||||
defaultModel: "qwen3.5-plus",
|
||||
loginLabel: "阿里百炼 API Key",
|
||||
},
|
||||
};
|
||||
|
||||
type QueuedMasterAgentReplyEnvelope = {
|
||||
ok: true;
|
||||
@@ -378,6 +404,51 @@ function normalizeOpenAiFetchFailure(error: unknown) {
|
||||
return normalizeOpenAiError(String(error));
|
||||
}
|
||||
|
||||
function isApiCompatibleProvider(provider: AiProvider): provider is ApiCompatibleProvider {
|
||||
return provider === "openai_api" || provider === "aliyun_qwen_api";
|
||||
}
|
||||
|
||||
function apiProviderConfig(provider: ApiCompatibleProvider) {
|
||||
return API_PROVIDER_CONFIG[provider];
|
||||
}
|
||||
|
||||
function normalizeApiProviderError(provider: ApiCompatibleProvider, message: string) {
|
||||
if (provider === "openai_api") {
|
||||
return normalizeOpenAiError(message);
|
||||
}
|
||||
|
||||
const trimmed = message.trim();
|
||||
const lowered = trimmed.toLowerCase();
|
||||
if (
|
||||
lowered.includes("network is unreachable") ||
|
||||
lowered.includes("enetunreach") ||
|
||||
lowered.includes("timed out") ||
|
||||
lowered.includes("fetch failed") ||
|
||||
lowered.includes("connect timeout")
|
||||
) {
|
||||
return "服务器当前无法连接阿里百炼兼容接口,请检查出网、代理或防火墙配置。";
|
||||
}
|
||||
if (!trimmed) return "主 Agent 当前调用阿里百炼模型失败。";
|
||||
if (trimmed.length <= 240) return trimmed;
|
||||
return `${trimmed.slice(0, 237)}...`;
|
||||
}
|
||||
|
||||
function normalizeApiProviderFetchFailure(provider: ApiCompatibleProvider, error: unknown) {
|
||||
if (provider === "openai_api") {
|
||||
return normalizeOpenAiFetchFailure(error);
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
const causeCode =
|
||||
typeof (error as Error & { cause?: { code?: string } }).cause?.code === "string"
|
||||
? (error as Error & { cause?: { code?: string } }).cause?.code
|
||||
: "";
|
||||
const causeMessage =
|
||||
(error as Error & { cause?: { message?: string } }).cause?.message?.trim() || "";
|
||||
return normalizeApiProviderError(provider, [error.message, causeCode, causeMessage].filter(Boolean).join(" "));
|
||||
}
|
||||
return normalizeApiProviderError(provider, String(error));
|
||||
}
|
||||
|
||||
function fallbackAiRolePriority(role: "primary" | "backup" | "api_fallback") {
|
||||
switch (role) {
|
||||
case "primary":
|
||||
@@ -391,14 +462,14 @@ function fallbackAiRolePriority(role: "primary" | "backup" | "api_fallback") {
|
||||
}
|
||||
}
|
||||
|
||||
async function findFallbackOpenAiAccount(excludedAccountId?: string) {
|
||||
async function findFallbackApiAccount(excludedAccountId?: string) {
|
||||
const state = await readState();
|
||||
return [...state.aiAccounts]
|
||||
.filter(
|
||||
(account) =>
|
||||
account.accountId !== excludedAccountId &&
|
||||
account.enabled &&
|
||||
account.provider === "openai_api" &&
|
||||
isApiCompatibleProvider(account.provider) &&
|
||||
Boolean(account.apiKey?.trim()),
|
||||
)
|
||||
.sort((left, right) => {
|
||||
@@ -409,7 +480,7 @@ async function findFallbackOpenAiAccount(excludedAccountId?: string) {
|
||||
}
|
||||
|
||||
async function replyViaOpenAiAccount(params: {
|
||||
account: Awaited<ReturnType<typeof findFallbackOpenAiAccount>>;
|
||||
account: Awaited<ReturnType<typeof findFallbackApiAccount>>;
|
||||
requestText: string;
|
||||
currentSessionExpiresAt?: string;
|
||||
senderLabel: string;
|
||||
@@ -419,13 +490,17 @@ async function replyViaOpenAiAccount(params: {
|
||||
projectMemories?: Awaited<ReturnType<typeof listUserMasterMemoriesView>>;
|
||||
userMemories?: Awaited<ReturnType<typeof listUserMasterMemoriesView>>;
|
||||
}) {
|
||||
if (!params.account?.apiKey?.trim()) {
|
||||
if (!params.account?.apiKey?.trim() || !isApiCompatibleProvider(params.account.provider)) {
|
||||
throw new Error("OPENAI_ACCOUNT_NOT_CONFIGURED");
|
||||
}
|
||||
|
||||
const generated = await generateOpenAiReply({
|
||||
const generated = await generateApiProviderReply({
|
||||
provider: params.account.provider,
|
||||
apiKey: params.account.apiKey,
|
||||
model: params.agentControls?.modelOverride || params.account.model || "gpt-5.4",
|
||||
model:
|
||||
params.agentControls?.modelOverride ||
|
||||
params.account.model ||
|
||||
apiProviderConfig(params.account.provider).defaultModel,
|
||||
reasoningEffort: params.agentControls?.reasoningEffortOverride || "medium",
|
||||
requestText: params.requestText,
|
||||
currentSessionExpiresAt: params.currentSessionExpiresAt,
|
||||
@@ -451,7 +526,8 @@ async function replyViaOpenAiAccount(params: {
|
||||
};
|
||||
}
|
||||
|
||||
async function generateOpenAiReply(params: {
|
||||
async function generateApiProviderReply(params: {
|
||||
provider: ApiCompatibleProvider;
|
||||
apiKey: string;
|
||||
model: string;
|
||||
reasoningEffort: ReasoningEffort;
|
||||
@@ -475,34 +551,38 @@ async function generateOpenAiReply(params: {
|
||||
params.requestText,
|
||||
);
|
||||
let response: Response;
|
||||
const config = apiProviderConfig(params.provider);
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: params.model,
|
||||
instructions: buildMasterAgentExecutionPrompt({
|
||||
state,
|
||||
projectId: "master-agent",
|
||||
requestText: params.requestText,
|
||||
currentSessionExpiresAt: params.currentSessionExpiresAt,
|
||||
agentControls: params.agentControls,
|
||||
accountId: "master-agent",
|
||||
promptPolicy: params.promptPolicy ?? null,
|
||||
userPrompt: params.userPrompt ?? null,
|
||||
projectMemories: effectiveProjectMemories,
|
||||
userMemories: params.userMemories ?? [],
|
||||
}),
|
||||
input: params.requestText,
|
||||
};
|
||||
if (params.provider === "openai_api") {
|
||||
requestBody.reasoning = { effort: params.reasoningEffort };
|
||||
}
|
||||
try {
|
||||
response = await fetch("https://api.openai.com/v1/responses", {
|
||||
response = await fetch(config.endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: params.model,
|
||||
reasoning: { effort: params.reasoningEffort },
|
||||
instructions: buildMasterAgentExecutionPrompt({
|
||||
state,
|
||||
projectId: "master-agent",
|
||||
requestText: params.requestText,
|
||||
currentSessionExpiresAt: params.currentSessionExpiresAt,
|
||||
agentControls: params.agentControls,
|
||||
accountId: "master-agent",
|
||||
promptPolicy: params.promptPolicy ?? null,
|
||||
userPrompt: params.userPrompt ?? null,
|
||||
projectMemories: effectiveProjectMemories,
|
||||
userMemories: params.userMemories ?? [],
|
||||
}),
|
||||
input: params.requestText,
|
||||
}),
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: AbortSignal.timeout(45_000),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(normalizeOpenAiFetchFailure(error));
|
||||
throw new Error(normalizeApiProviderFetchFailure(params.provider, error));
|
||||
}
|
||||
|
||||
const requestId = response.headers.get("x-request-id") ?? undefined;
|
||||
@@ -516,8 +596,9 @@ async function generateOpenAiReply(params: {
|
||||
? payload.error?.message
|
||||
: undefined;
|
||||
throw new Error(
|
||||
normalizeOpenAiError(
|
||||
`${apiError ?? `OpenAI API ${response.status}`}${requestId ? ` (request_id=${requestId})` : ""}`,
|
||||
normalizeApiProviderError(
|
||||
params.provider,
|
||||
`${apiError ?? `${config.label} ${response.status}`}${requestId ? ` (request_id=${requestId})` : ""}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -525,7 +606,8 @@ async function generateOpenAiReply(params: {
|
||||
const content = extractResponseText(payload);
|
||||
if (!content) {
|
||||
throw new Error(
|
||||
normalizeOpenAiError(
|
||||
normalizeApiProviderError(
|
||||
params.provider,
|
||||
`模型已返回成功状态,但没有可用文本输出${requestId ? ` (request_id=${requestId})` : ""}`,
|
||||
),
|
||||
);
|
||||
@@ -562,6 +644,7 @@ function buildMasterOpenAiReplyPrompt(
|
||||
}
|
||||
|
||||
async function queueAndStartOpenAiMasterAgentReply(params: {
|
||||
provider: ApiCompatibleProvider;
|
||||
taskId: string;
|
||||
deviceId: string;
|
||||
requestText: string;
|
||||
@@ -583,7 +666,8 @@ async function queueAndStartOpenAiMasterAgentReply(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
const generated = await generateOpenAiReply({
|
||||
const generated = await generateApiProviderReply({
|
||||
provider: params.provider,
|
||||
apiKey: params.apiKey,
|
||||
model: params.model,
|
||||
reasoningEffort: params.reasoningEffort,
|
||||
@@ -617,6 +701,7 @@ async function queueAndStartOpenAiMasterAgentReply(params: {
|
||||
}
|
||||
|
||||
async function enqueueOpenAiMasterAgentReply(params: {
|
||||
provider: ApiCompatibleProvider;
|
||||
accountId: string;
|
||||
accountLabel: string;
|
||||
requestMessageId?: string;
|
||||
@@ -649,13 +734,14 @@ async function enqueueOpenAiMasterAgentReply(params: {
|
||||
),
|
||||
requestedBy: params.requestedBy,
|
||||
requestedByAccount: params.requestedByAccount,
|
||||
deviceId: OPENAI_MASTER_AGENT_DEVICE_ID,
|
||||
deviceId: params.provider === "aliyun_qwen_api" ? ALIYUN_QWEN_DEVICE_ID : OPENAI_MASTER_AGENT_DEVICE_ID,
|
||||
accountId: params.accountId,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
void queueAndStartOpenAiMasterAgentReply({
|
||||
provider: params.provider,
|
||||
taskId: task.taskId,
|
||||
deviceId: OPENAI_MASTER_AGENT_DEVICE_ID,
|
||||
deviceId: params.provider === "aliyun_qwen_api" ? ALIYUN_QWEN_DEVICE_ID : OPENAI_MASTER_AGENT_DEVICE_ID,
|
||||
requestText: params.requestText,
|
||||
currentSessionExpiresAt: params.currentSessionExpiresAt,
|
||||
apiKey: params.apiKey,
|
||||
@@ -682,34 +768,39 @@ async function enqueueOpenAiMasterAgentReply(params: {
|
||||
return queuedReply;
|
||||
}
|
||||
|
||||
export async function probeOpenAiApiAccount(params: {
|
||||
export async function probeApiCompatibleAccount(params: {
|
||||
provider: ApiCompatibleProvider;
|
||||
apiKey: string;
|
||||
model?: string;
|
||||
}) {
|
||||
const apiKey = params.apiKey.trim();
|
||||
if (!apiKey) {
|
||||
throw new Error("当前账号还没有可用的 OpenAI API Key。");
|
||||
throw new Error(`当前账号还没有可用的 ${apiProviderConfig(params.provider).loginLabel}。`);
|
||||
}
|
||||
|
||||
const model = params.model?.trim() || "gpt-5.4";
|
||||
const config = apiProviderConfig(params.provider);
|
||||
const model = params.model?.trim() || config.defaultModel;
|
||||
let response: Response;
|
||||
const body: Record<string, unknown> = {
|
||||
model,
|
||||
instructions: `你正在执行${config.label}连接自检。请只回复“连接正常”。`,
|
||||
input: "请只回复“连接正常”。",
|
||||
};
|
||||
if (params.provider === "openai_api") {
|
||||
body.reasoning = { effort: "low" };
|
||||
}
|
||||
try {
|
||||
response = await fetch("https://api.openai.com/v1/responses", {
|
||||
response = await fetch(config.endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
reasoning: { effort: "low" },
|
||||
instructions: "你正在执行 OpenAI API 连接自检。请只回复“连接正常”。",
|
||||
input: "请只回复“连接正常”。",
|
||||
}),
|
||||
body: JSON.stringify(body),
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(normalizeOpenAiFetchFailure(error));
|
||||
throw new Error(normalizeApiProviderFetchFailure(params.provider, error));
|
||||
}
|
||||
|
||||
const requestId = response.headers.get("x-request-id") ?? undefined;
|
||||
@@ -723,8 +814,9 @@ export async function probeOpenAiApiAccount(params: {
|
||||
? payload.error?.message
|
||||
: undefined;
|
||||
throw new Error(
|
||||
normalizeOpenAiError(
|
||||
`${apiError ?? `OpenAI API ${response.status}`}${requestId ? ` (request_id=${requestId})` : ""}`,
|
||||
normalizeApiProviderError(
|
||||
params.provider,
|
||||
`${apiError ?? `${config.label} ${response.status}`}${requestId ? ` (request_id=${requestId})` : ""}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -738,6 +830,13 @@ export async function probeOpenAiApiAccount(params: {
|
||||
};
|
||||
}
|
||||
|
||||
export async function probeOpenAiApiAccount(params: { apiKey: string; model?: string }) {
|
||||
return probeApiCompatibleAccount({
|
||||
provider: "openai_api",
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
async function appendMasterAgentSystemReply(body: string, senderLabel = "主 Agent") {
|
||||
return appendProjectMessage({
|
||||
projectId: "master-agent",
|
||||
@@ -1332,17 +1431,18 @@ export async function validateAiAccountConnection(accountId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
if (account.provider !== "openai_api" || !account.apiKey?.trim()) {
|
||||
if (!isApiCompatibleProvider(account.provider) || !account.apiKey?.trim()) {
|
||||
return {
|
||||
ok: false as const,
|
||||
status: "needs_api_key",
|
||||
message: "当前账号还没有可用的 OpenAI API Key。",
|
||||
message: `当前账号还没有可用的${isApiCompatibleProvider(account.provider) ? apiProviderConfig(account.provider).loginLabel : " API Key"}。`,
|
||||
};
|
||||
}
|
||||
|
||||
const generated = await probeOpenAiApiAccount({
|
||||
const generated = await probeApiCompatibleAccount({
|
||||
provider: account.provider,
|
||||
apiKey: account.apiKey,
|
||||
model: account.model || "gpt-5.4",
|
||||
model: account.model || apiProviderConfig(account.provider).defaultModel,
|
||||
});
|
||||
|
||||
await updateAiAccountHealth({
|
||||
@@ -1372,7 +1472,7 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
|
||||
if (!runtime?.account) {
|
||||
await appendMasterAgentSystemReply(
|
||||
"我已经收到你的消息,但当前没有可用的主控 AI 账号。请到“我的 > AI 账号”至少配置一个可用的 OpenAI API 账号,再继续对话。",
|
||||
"我已经收到你的消息,但当前没有可用的主控 AI 账号。请到“我的 > AI 账号”至少配置一个可用的 OpenAI API、阿里百炼 Qwen,或接回 Master Codex Node 后,再继续对话。",
|
||||
);
|
||||
return { ok: false as const, reason: "NO_AI_ACCOUNT" };
|
||||
}
|
||||
@@ -1403,9 +1503,10 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
lastValidatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const fallbackAccount = await findFallbackOpenAiAccount(runtime.account.accountId);
|
||||
if (fallbackAccount?.apiKey?.trim()) {
|
||||
const fallbackAccount = await findFallbackApiAccount(runtime.account.accountId);
|
||||
if (fallbackAccount?.apiKey?.trim() && isApiCompatibleProvider(fallbackAccount.provider)) {
|
||||
return enqueueOpenAiMasterAgentReply({
|
||||
provider: fallbackAccount.provider,
|
||||
accountId: fallbackAccount.accountId,
|
||||
accountLabel: fallbackAccount.label || aiRoleLabel(fallbackAccount.role),
|
||||
requestMessageId: params.requestMessageId,
|
||||
@@ -1414,7 +1515,10 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
requestedByAccount: params.requestedByAccount,
|
||||
currentSessionExpiresAt: params.currentSessionExpiresAt,
|
||||
apiKey: fallbackAccount.apiKey,
|
||||
model: agentControls?.modelOverride || fallbackAccount.model || "gpt-5.4",
|
||||
model:
|
||||
agentControls?.modelOverride ||
|
||||
fallbackAccount.model ||
|
||||
apiProviderConfig(fallbackAccount.provider).defaultModel,
|
||||
reasoningEffort: agentControls?.reasoningEffortOverride || "medium",
|
||||
agentControls,
|
||||
promptPolicy: executionConfig.promptPolicy,
|
||||
@@ -1465,8 +1569,9 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
return queuedReply;
|
||||
}
|
||||
|
||||
if (runtime.account.provider === "openai_api" && runtime.account.apiKey?.trim()) {
|
||||
if (isApiCompatibleProvider(runtime.account.provider) && runtime.account.apiKey?.trim()) {
|
||||
return enqueueOpenAiMasterAgentReply({
|
||||
provider: runtime.account.provider,
|
||||
accountId: runtime.account.accountId,
|
||||
accountLabel: runtime.account.label || runtime.summary.roleLabel,
|
||||
requestMessageId: params.requestMessageId,
|
||||
@@ -1503,7 +1608,7 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
lastError: !boundDevice ? "MASTER_CODEX_NODE_DEVICE_NOT_FOUND" : "MASTER_CODEX_NODE_DEVICE_OFFLINE",
|
||||
lastValidatedAt: new Date().toISOString(),
|
||||
});
|
||||
const fallbackAccount = await findFallbackOpenAiAccount(runtime.account.accountId);
|
||||
const fallbackAccount = await findFallbackApiAccount(runtime.account.accountId);
|
||||
if (fallbackAccount) {
|
||||
try {
|
||||
return await replyViaOpenAiAccount({
|
||||
@@ -1557,7 +1662,7 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
};
|
||||
}
|
||||
if (completedTask?.status === "failed") {
|
||||
const fallbackAccount = await findFallbackOpenAiAccount(runtime.account.accountId);
|
||||
const fallbackAccount = await findFallbackApiAccount(runtime.account.accountId);
|
||||
if (fallbackAccount) {
|
||||
try {
|
||||
return await replyViaOpenAiAccount({
|
||||
@@ -1593,12 +1698,12 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
return { ok: true as const, accountId: runtime.account.accountId, taskId: task.taskId };
|
||||
}
|
||||
|
||||
if (runtime.account.provider !== "openai_api" || !runtime.account.apiKey?.trim()) {
|
||||
if (!isApiCompatibleProvider(runtime.account.provider) || !runtime.account.apiKey?.trim()) {
|
||||
await appendMasterAgentSystemReply(
|
||||
[
|
||||
`当前主控身份是 ${runtime.summary.roleLabel},来源 ${aiProviderLabel(runtime.account.provider)}。`,
|
||||
"当前账号既没有接入 Master Codex Node 执行器,也没有可用的 OpenAI API Key。",
|
||||
"请到“我的 > AI 账号”补一个可用的 OpenAI API 账号,或者把当前节点接回 Master Codex Node relay。",
|
||||
"当前账号既没有接入 Master Codex Node 执行器,也没有可用的 API 兼容账号。",
|
||||
"请到“我的 > AI 账号”补一个可用的 OpenAI API 或阿里百炼 Qwen 账号,或者把当前节点接回 Master Codex Node relay。",
|
||||
].join(""),
|
||||
`主 Agent · ${runtime.summary.roleLabel}`,
|
||||
);
|
||||
@@ -1606,7 +1711,8 @@ export async function replyToMasterAgentUserMessage(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
const generated = await generateOpenAiReply({
|
||||
const generated = await generateApiProviderReply({
|
||||
provider: runtime.account.provider,
|
||||
apiKey: runtime.account.apiKey,
|
||||
model: executionConfig.model,
|
||||
reasoningEffort: executionConfig.reasoningEffort,
|
||||
|
||||
@@ -234,6 +234,8 @@ function aiProviderLabel(provider: AiProvider) {
|
||||
return "Master Codex Node";
|
||||
case "openai_api":
|
||||
return "OpenAI API";
|
||||
case "aliyun_qwen_api":
|
||||
return "阿里百炼 Qwen";
|
||||
default:
|
||||
return provider;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user