fix(agents): land #31002 from @yfge

Co-authored-by: yfge <geyunfei@gmail.com>
This commit is contained in:
Peter Steinberger
2026-03-02 01:08:58 +00:00
parent 250f9e15f5
commit 81ca309ee6
3 changed files with 69 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/Thinking fallback: when providers reject unsupported thinking levels without enumerating alternatives, retry with `think=off` to avoid hard failure during model/provider fallback chains. Landed from contributor PR #31002 by @yfge. Thanks @yfge.
- Agents/Failover reason classification: avoid false rate-limit classification from incidental `tpm` substrings by matching TPM as a standalone token/phrase and keeping auth-context errors on the auth path. Landed from contributor PR #31007 by @HOYALIM. Thanks @HOYALIM.
- Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct `accountId` instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002.
- Tools/Edit workspace boundary errors: preserve the real `Path escapes workspace root` failure path instead of surfacing a misleading access/file-not-found error when editing outside workspace roots. Landed from contributor PR #31015 by @haosenwang1018. Thanks @haosenwang1018.

View File

@@ -0,0 +1,60 @@
import { describe, expect, it } from "vitest";
import { pickFallbackThinkingLevel } from "./thinking.js";
describe("pickFallbackThinkingLevel", () => {
it("returns undefined for empty message", () => {
expect(pickFallbackThinkingLevel({ message: "", attempted: new Set() })).toBeUndefined();
});
it("returns undefined for undefined message", () => {
expect(pickFallbackThinkingLevel({ message: undefined, attempted: new Set() })).toBeUndefined();
});
it("extracts supported values from error message", () => {
const result = pickFallbackThinkingLevel({
message: 'Supported values are: "high", "medium"',
attempted: new Set(),
});
expect(result).toBe("high");
});
it("skips already attempted values", () => {
const result = pickFallbackThinkingLevel({
message: 'Supported values are: "high", "medium"',
attempted: new Set(["high"]),
});
expect(result).toBe("medium");
});
it('falls back to "off" when error says "not supported" without listing values', () => {
const result = pickFallbackThinkingLevel({
message: '400 think value "low" is not supported for this model',
attempted: new Set(),
});
expect(result).toBe("off");
});
it('falls back to "off" for generic not-supported messages', () => {
const result = pickFallbackThinkingLevel({
message: "thinking level not supported by this provider",
attempted: new Set(),
});
expect(result).toBe("off");
});
it('returns undefined if "off" was already attempted', () => {
const result = pickFallbackThinkingLevel({
message: '400 think value "low" is not supported for this model',
attempted: new Set(["off"]),
});
expect(result).toBeUndefined();
});
it("returns undefined for unrelated error messages", () => {
const result = pickFallbackThinkingLevel({
message: "rate limit exceeded, please retry after 30 seconds",
attempted: new Set(),
});
expect(result).toBeUndefined();
});
});

View File

@@ -29,6 +29,14 @@ export function pickFallbackThinkingLevel(params: {
}
const supported = extractSupportedValues(raw);
if (supported.length === 0) {
// When the error clearly indicates the thinking level is unsupported but doesn't
// list supported values (e.g. OpenAI's "think value \"low\" is not supported for
// this model"), fall back to "off" to allow the request to succeed.
// This commonly happens during model fallback when switching from Anthropic
// (which supports thinking levels) to providers that don't.
if (/not supported/i.test(raw) && !params.attempted.has("off")) {
return "off";
}
return undefined;
}
for (const entry of supported) {