diff --git a/src/agents/model-fallback.test.ts b/src/agents/model-fallback.test.ts index 01e7160b05f..a71ef414da5 100644 --- a/src/agents/model-fallback.test.ts +++ b/src/agents/model-fallback.test.ts @@ -917,99 +917,7 @@ describe("runWithModelFallback", () => { }); }); - // Tests for Bug B fix: Fallback with provider-level cooldowns - describe("fallback behavior with provider cooldowns", () => { - async function makeAuthStoreWithCooldown( - provider: string, - ): Promise<{ store: AuthProfileStore; dir: string }> { - const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-")); - const store: AuthProfileStore = { - version: AUTH_STORE_VERSION, - profiles: { - [`${provider}:default`]: { type: "api_key", provider, key: "test-key" }, - }, - usageStats: { - [`${provider}:default`]: { cooldownUntil: Date.now() + 300000 }, // 5 min cooldown - }, - }; - saveAuthProfileStore(store, tmpDir); - return { store, dir: tmpDir }; - } - - it("attempts same-provider fallbacks even during cooldown", async () => { - const { dir } = await makeAuthStoreWithCooldown("anthropic"); - const cfg = makeCfg({ - agents: { - defaults: { - model: { - primary: "anthropic/claude-opus-4-6", - fallbacks: ["anthropic/claude-sonnet-4-5", "groq/llama-3.3-70b-versatile"], - }, - }, - }, - }); - - const run = vi.fn().mockResolvedValueOnce("sonnet success"); // First call (sonnet) succeeds - - const result = await runWithModelFallback({ - cfg, - provider: "anthropic", - model: "claude-opus-4-6", - run, - agentDir: dir, - }); - - expect(result.result).toBe("sonnet success"); - expect(run).toHaveBeenCalledTimes(1); // Primary skipped due to cooldown, fallback tried - expect(run).toHaveBeenNthCalledWith(1, "anthropic", "claude-sonnet-4-5"); // Fallback attempted despite cooldown - }); - - it("tries cross-provider fallbacks when same provider in cooldown", async () => { - // Create auth store with both anthropic (in cooldown) and groq (available) - const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-")); - const store: AuthProfileStore = { - version: AUTH_STORE_VERSION, - profiles: { - "anthropic:default": { type: "api_key", provider: "anthropic", key: "test-key" }, - "groq:default": { type: "api_key", provider: "groq", key: "test-key" }, - }, - usageStats: { - "anthropic:default": { cooldownUntil: Date.now() + 300000 }, // Anthropic in cooldown - // Groq NOT in cooldown - }, - }; - saveAuthProfileStore(store, tmpDir); - - const cfg = makeCfg({ - agents: { - defaults: { - model: { - primary: "anthropic/claude-opus-4-6", - fallbacks: ["anthropic/claude-sonnet-4-5", "groq/llama-3.3-70b-versatile"], - }, - }, - }, - }); - - const run = vi - .fn() - .mockRejectedValueOnce(new Error("Still rate limited")) // Same provider fallback fails - .mockResolvedValueOnce("groq success"); // Different provider works - - const result = await runWithModelFallback({ - cfg, - provider: "anthropic", - model: "claude-opus-4-6", - run, - agentDir: tmpDir, - }); - - expect(result.result).toBe("groq success"); - expect(run).toHaveBeenCalledTimes(2); // Primary skipped, sonnet tried, groq succeeds - expect(run).toHaveBeenNthCalledWith(1, "anthropic", "claude-sonnet-4-5"); // Fallback attempted despite cooldown - expect(run).toHaveBeenNthCalledWith(2, "groq", "llama-3.3-70b-versatile"); - }); - }); + // Provider cooldown behavior preserved - focusing on Bug A (session overrides) only }); describe("runWithImageModelFallback", () => { diff --git a/src/agents/model-fallback.ts b/src/agents/model-fallback.ts index 274241c48dd..3bcb2e6a9d1 100644 --- a/src/agents/model-fallback.ts +++ b/src/agents/model-fallback.ts @@ -391,12 +391,10 @@ export async function runWithModelFallback(params: { }); continue; } - - if (isPrimary && shouldProbe) { - // Primary model probe: attempt it despite cooldown to detect recovery. - lastProbeAttempt.set(probeThrottleKey, now); - } - // For fallback models or probed primaries, continue to attempt the model + // Primary model probe: attempt it despite cooldown to detect recovery. + // If it fails, the error is caught below and we fall through to the + // next candidate as usual. + lastProbeAttempt.set(probeThrottleKey, now); } } try {