diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 05392ec20..2b82afa26 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,13 +1,13 @@ # Changelog ## [Unreleased] - ### Breaking Changes - Removed `coerceNullStrings` function and its automatic null-string coercion behavior from JSON parsing ### Added +- Added support for OpenRouter provider with strict mode detection - Added automatic cleaning of literal escape sequences (`\n`, `\t`, `\r`) in JSON parsing to handle LLM encoding confusion - Added support for healing JSON with trailing junk after balanced containers (e.g., `]\n`) - Added `CODEX_STARTUP_EVENT_CHANNEL` constant and `CodexStartupEvent` type for monitoring Codex provider initialization status diff --git a/packages/ai/src/providers/openai-completions-compat.ts b/packages/ai/src/providers/openai-completions-compat.ts index f18adc84a..c8d1e8dbb 100644 --- a/packages/ai/src/providers/openai-completions-compat.ts +++ b/packages/ai/src/providers/openai-completions-compat.ts @@ -13,6 +13,7 @@ export type ResolvedOpenAICompat = Required< function detectStrictModeSupport(provider: string, baseUrl: string): boolean { if ( provider === "openai" || + provider === "openrouter" || provider === "cerebras" || provider === "together" || provider === "github-copilot" || @@ -28,6 +29,7 @@ function detectStrictModeSupport(provider: string, baseUrl: string): boolean { normalizedBaseUrl.includes("models.inference.ai.azure.com") || normalizedBaseUrl.includes("api.cerebras.ai") || normalizedBaseUrl.includes("api.together.xyz") || + normalizedBaseUrl.includes("openrouter.ai") || normalizedBaseUrl.includes("api.deepseek.com") || normalizedBaseUrl.includes("deepseek.com") ); diff --git a/packages/ai/test/openai-tool-strict-mode.test.ts b/packages/ai/test/openai-tool-strict-mode.test.ts index f107943cd..ee6aec333 100644 --- a/packages/ai/test/openai-tool-strict-mode.test.ts +++ b/packages/ai/test/openai-tool-strict-mode.test.ts @@ -91,6 +91,16 @@ describe("OpenAI tool strict mode", () => { }; expect(payload.tools?.[0]?.function?.strict).toBe(true); }); + + it("sends strict=true for openai-completions tool schemas on OpenRouter", async () => { + const model = getBundledModel("openrouter", "anthropic/claude-sonnet-4") as Model<"openai-completions">; + + const payload = (await captureCompletionsPayload(model)) as { + tools?: Array<{ function?: { strict?: boolean } }>; + }; + expect(payload.tools?.[0]?.function?.strict).toBe(true); + }); + it("omits stream_options usage requests for Cerebras chat completions", async () => { const model = getBundledModel("cerebras", "gpt-oss-120b") as Model<"openai-completions">;