diff --git a/src/lib/ai/__tests__/json-dialect.test.ts b/src/lib/ai/__tests__/json-dialect.test.ts new file mode 100644 index 000000000..427909c4d --- /dev/null +++ b/src/lib/ai/__tests__/json-dialect.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; + +import { isResponseFormatRejection } from "../json-dialect"; + +describe("isResponseFormatRejection", () => { + it("reads a complaint about response_format as a JSON-mode rejection", () => { + expect( + isResponseFormatRejection(400, "Unsupported value: 'response_format'"), + ).toBe(true); + expect(isResponseFormatRejection(422, "unknown parameter")).toBe(true); + }); + + it("does not read a complaint about reasoning_effort as one", () => { + expect( + isResponseFormatRejection( + 400, + "Unrecognized request argument supplied: reasoning_effort", + ), + ).toBe(false); + expect( + isResponseFormatRejection(400, "unknown field `reasoning effort`"), + ).toBe(false); + }); + + it("still flips when both fields are named", () => { + expect( + isResponseFormatRejection( + 400, + "unknown parameters: response_format, reasoning_effort", + ), + ).toBe(true); + }); + + it("ignores server errors", () => { + expect(isResponseFormatRejection(500, "response_format")).toBe(false); + }); +}); diff --git a/src/lib/ai/json-dialect.ts b/src/lib/ai/json-dialect.ts index f63a5a481..0c1859105 100644 --- a/src/lib/ai/json-dialect.ts +++ b/src/lib/ai/json-dialect.ts @@ -56,13 +56,21 @@ export function resetJsonModeDialectCache(): void { /** * True when a 4xx error body reads as "this endpoint rejects the * `response_format` field" — either it names the field outright or it - * complains about an unknown/unexpected parameter. + * complains about an unknown/unexpected parameter. A complaint that names + * `reasoning_effort` and not `response_format` is about the reasoning field, + * so it must not switch the endpoint's JSON mode off. */ export function isResponseFormatRejection( status: number, bodyExcerpt: string, ): boolean { if (status < 400 || status >= 500) return false; + if ( + /reasoning[\s_-]*effort/i.test(bodyExcerpt) && + !/response_format/i.test(bodyExcerpt) + ) { + return false; + } return ( /response_format/i.test(bodyExcerpt) || /(unknown|unexpected|unrecognized|unsupported|extra)[\s_-]*(parameter|field|argument|property|key)/i.test(