























@@ -70,6 +70,43 @@ function logCliBackendLiveStep(step: string, details?: Record<string, unknown>):
7070console.error(`[gateway-cli-live] ${step}${suffix}`);
7171}
727273+function sleep(ms: number): Promise<void> {
74+return new Promise((resolve) => setTimeout(resolve, ms));
75+}
76+77+function isProviderCapacityError(error: unknown): boolean {
78+const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
79+const normalized = message.toLowerCase();
80+return (
81+normalized.includes("529") &&
82+(normalized.includes("overloaded") || normalized.includes("capacity"))
83+);
84+}
85+86+async function requestWithProviderCapacityRetry<T>(
87+providerId: string,
88+label: string,
89+request: () => Promise<T>,
90+): Promise<T | undefined> {
91+const maxAttempts = providerId === "claude-cli" ? 3 : 1;
92+for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
93+try {
94+return await request();
95+} catch (error) {
96+if (!isProviderCapacityError(error) || attempt >= maxAttempts) {
97+if (providerId === "claude-cli" && isProviderCapacityError(error)) {
98+console.warn(`SKIP: ${label} skipped because Claude API stayed overloaded.`);
99+return undefined;
100+}
101+throw error;
102+}
103+logCliBackendLiveStep("provider-capacity-retry", { label, attempt });
104+await sleep(15_000 * attempt);
105+}
106+}
107+return undefined;
108+}
109+73110async function createMcpSchemaProbePlugin(tempDir: string): Promise<string> {
74111const pluginDir = path.join(tempDir, MCP_SCHEMA_PROBE_PLUGIN_ID);
75112await fs.mkdir(pluginDir, { recursive: true });
@@ -310,21 +347,26 @@ describeLive("gateway live (cli backend)", () => {
310347const memoryNonce = randomBytes(3).toString("hex").toUpperCase();
311348const memoryToken = `CLI-MEM-${memoryNonce}`;
312349logCliBackendLiveStep("agent-request:start", { sessionKey, nonce });
313-const payload = await client.request(
314-"agent",
315-{
316- sessionKey,
317-idempotencyKey: `idem-${randomUUID()}`,
318-message: enableCliModelSwitchProbe
319- ? `Please include the token CLI-BACKEND-${nonce} in your reply.` +
320-` Also remember this session note for later: ${memoryToken}.` +
321-" Do not include the note in your reply."
322- : `Please include the token CLI-BACKEND-${nonce} in your reply.`,
323-deliver: false,
324-timeout: CLI_BACKEND_AGENT_TIMEOUT_SECONDS,
325-},
326-{ expectFinal: true, timeoutMs: CLI_BACKEND_REQUEST_TIMEOUT_MS },
350+const payload = await requestWithProviderCapacityRetry(providerId, "agent request", () =>
351+client.request(
352+"agent",
353+{
354+ sessionKey,
355+idempotencyKey: `idem-${randomUUID()}`,
356+message: enableCliModelSwitchProbe
357+ ? `Please include the token CLI-BACKEND-${nonce} in your reply.` +
358+` Also remember this session note for later: ${memoryToken}.` +
359+" Do not include the note in your reply."
360+ : `Please include the token CLI-BACKEND-${nonce} in your reply.`,
361+deliver: false,
362+timeout: CLI_BACKEND_AGENT_TIMEOUT_SECONDS,
363+},
364+{ expectFinal: true, timeoutMs: CLI_BACKEND_REQUEST_TIMEOUT_MS },
365+),
327366);
367+if (!payload) {
368+return;
369+}
328370if (payload?.status !== "ok") {
329371throw new Error(`agent status=${String(payload?.status)}`);
330372}
@@ -367,20 +409,28 @@ describeLive("gateway live (cli backend)", () => {
367409`sessions.patch failed for model switch: ${JSON.stringify(patchPayload)}`,
368410);
369411}
370-const switchPayload = await client.request(
371-"agent",
372-{
373- sessionKey,
374-idempotencyKey: `idem-${randomUUID()}`,
375-message:
376-"We just switched from Claude Sonnet to Claude Opus in the same session. " +
377-`What session note did I ask you to remember earlier? ` +
378-`Reply with exactly: CLI backend SWITCH OK ${switchNonce} <remembered-note>.`,
379-deliver: false,
380-timeout: CLI_BACKEND_AGENT_TIMEOUT_SECONDS,
381-},
382-{ expectFinal: true, timeoutMs: CLI_BACKEND_REQUEST_TIMEOUT_MS },
412+const switchPayload = await requestWithProviderCapacityRetry(
413+providerId,
414+"agent model-switch request",
415+() =>
416+client.request(
417+"agent",
418+{
419+ sessionKey,
420+idempotencyKey: `idem-${randomUUID()}`,
421+message:
422+"We just switched from Claude Sonnet to Claude Opus in the same session. " +
423+`What session note did I ask you to remember earlier? ` +
424+`Reply with exactly: CLI backend SWITCH OK ${switchNonce} <remembered-note>.`,
425+deliver: false,
426+timeout: CLI_BACKEND_AGENT_TIMEOUT_SECONDS,
427+},
428+{ expectFinal: true, timeoutMs: CLI_BACKEND_REQUEST_TIMEOUT_MS },
429+),
383430);
431+if (!switchPayload) {
432+return;
433+}
384434if (switchPayload?.status !== "ok") {
385435throw new Error(`switch status=${String(switchPayload?.status)}`);
386436}
@@ -395,20 +445,28 @@ describeLive("gateway live (cli backend)", () => {
395445} else if (CLI_RESUME) {
396446const resumeNonce = randomBytes(3).toString("hex").toUpperCase();
397447logCliBackendLiveStep("agent-resume:start", { sessionKey, resumeNonce });
398-const resumePayload = await client.request(
399-"agent",
400-{
401- sessionKey,
402-idempotencyKey: `idem-${randomUUID()}`,
403-message:
404-providerId === "codex-cli"
405- ? `Please include the token CLI-RESUME-${resumeNonce} in your reply.`
406- : `Reply with exactly: CLI backend RESUME OK ${resumeNonce}.`,
407-deliver: false,
408-timeout: CLI_BACKEND_AGENT_TIMEOUT_SECONDS,
409-},
410-{ expectFinal: true, timeoutMs: CLI_BACKEND_REQUEST_TIMEOUT_MS },
448+const resumePayload = await requestWithProviderCapacityRetry(
449+providerId,
450+"agent resume request",
451+() =>
452+client.request(
453+"agent",
454+{
455+ sessionKey,
456+idempotencyKey: `idem-${randomUUID()}`,
457+message:
458+providerId === "codex-cli"
459+ ? `Please include the token CLI-RESUME-${resumeNonce} in your reply.`
460+ : `Reply with exactly: CLI backend RESUME OK ${resumeNonce}.`,
461+deliver: false,
462+timeout: CLI_BACKEND_AGENT_TIMEOUT_SECONDS,
463+},
464+{ expectFinal: true, timeoutMs: CLI_BACKEND_REQUEST_TIMEOUT_MS },
465+),
411466);
467+if (!resumePayload) {
468+return;
469+}
412470if (resumePayload?.status !== "ok") {
413471throw new Error(`resume status=${String(resumePayload?.status)}`);
414472}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。