From ca084b2430fdbd171fca0499e6cc7e077cc596db Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:55:06 -0500 Subject: [PATCH] fix(ai): give provider routes distinct IDs (#51976) --- packages/ai/src/cache-policy.ts | 1 + .../ai/src/providers/anthropic-compatible.ts | 5 +++-- packages/ai/src/providers/xai.ts | 6 ++--- packages/ai/test/cache-policy.test.ts | 22 ++++++++++++++++++- packages/ai/test/exports.test.ts | 4 ++-- packages/ai/test/provider-package.test.ts | 5 ++++- .../ai/test/provider/xai-responses.test.ts | 13 +++++++++++ packages/core/test/model-resolver.test.ts | 4 ++-- 8 files changed, 49 insertions(+), 11 deletions(-) diff --git a/packages/ai/src/cache-policy.ts b/packages/ai/src/cache-policy.ts index d2b2e9fdb83..3fc42bdc243 100644 --- a/packages/ai/src/cache-policy.ts +++ b/packages/ai/src/cache-policy.ts @@ -39,6 +39,7 @@ const resolve = (policy: CachePolicy | undefined): CachePolicyObject => { // whole policy pass for these — emitting hints would be harmless but pointless. const RESPECTS_INLINE_HINTS = new Set([ "anthropic-messages", + "anthropic-compatible-messages", "google-vertex-messages", "bedrock-converse", "openrouter", diff --git a/packages/ai/src/providers/anthropic-compatible.ts b/packages/ai/src/providers/anthropic-compatible.ts index 49889903225..2a4cb1b8a9d 100644 --- a/packages/ai/src/providers/anthropic-compatible.ts +++ b/packages/ai/src/providers/anthropic-compatible.ts @@ -28,7 +28,8 @@ export type Settings = ProviderPackage.Settings & readonly provider?: string } -export const routes = [AnthropicMessages.route] +const compatibleRoute = AnthropicMessages.route.with({ id: "anthropic-compatible-messages", provider: id }) +export const routes = [compatibleRoute] const auth = (input: ProviderAuthOption<"optional">) => { if ("auth" in input && input.auth) return input.auth @@ -43,7 +44,7 @@ export const configure = (input: Config) => { message: "Anthropic-compatible providers require a baseURL", }) const { provider: _, baseURL, apiKey: _apiKey, auth: _auth, ...rest } = input - const route = AnthropicMessages.route.with({ + const route = (provider === "anthropic" ? AnthropicMessages.route : compatibleRoute).with({ ...rest, provider, endpoint: { baseURL }, diff --git a/packages/ai/src/providers/xai.ts b/packages/ai/src/providers/xai.ts index 6a8705d7e4b..879d2ca0bee 100644 --- a/packages/ai/src/providers/xai.ts +++ b/packages/ai/src/providers/xai.ts @@ -35,13 +35,13 @@ const RESPONSES_WEBSOCKET_ROTATE_AFTER_MS = 24 * 60 * 1000 const responsesRoute = Route.make({ compact: { endpoint: XAIResponses.compact }, - id: "openai-responses", + id: "xai-responses", provider: id, providerMetadataKey: "xai", protocol: XAIResponses.protocol, endpoint: Endpoint.path("/responses", { baseURL }), transport: OpenResponsesChannel.transport({ - id: "openai-responses", + id: "xai-responses", name: "xAI Responses", rotateAfterMs: RESPONSES_WEBSOCKET_ROTATE_AFTER_MS, // xAI continues a chain only from stored responses: with `store: false` (the route default) `previous_response_id` @@ -53,7 +53,7 @@ const responsesRoute = Route.make({ }) const chatRoute = Route.make({ - id: "openai-compatible-chat", + id: "xai-chat", provider: id, providerMetadataKey: "xai", protocol: OpenAIChat.protocol, diff --git a/packages/ai/test/cache-policy.test.ts b/packages/ai/test/cache-policy.test.ts index 6c5f00c5fc4..3677f8dcfaa 100644 --- a/packages/ai/test/cache-policy.test.ts +++ b/packages/ai/test/cache-policy.test.ts @@ -3,7 +3,7 @@ import { Effect } from "effect" import { CacheHint, LLM, Message } from "../src/index.js" import { Auth } from "../src/route.js" import { compileRequest } from "../src/route/client.js" -import { AmazonBedrock, GoogleVertexMessages } from "../src/providers.js" +import { AmazonBedrock, AnthropicCompatible, GoogleVertexMessages } from "../src/providers.js" import * as AnthropicMessages from "../src/protocols/anthropic-messages.js" import * as Gemini from "../src/protocols/gemini.js" import * as OpenAIChat from "../src/protocols/openai-chat.js" @@ -107,6 +107,26 @@ describe("applyCachePolicy", () => { }), ) + it.effect("'auto' emits Anthropic cache markers on Anthropic-compatible routes", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ + model: AnthropicCompatible.configure({ apiKey: "test", baseURL: "https://messages.example.test/v1" }).model( + "compatible", + ), + system: "You are concise.", + prompt: "hi", + }), + ) + + expect(prepared.route).toBe("anthropic-compatible-messages") + expect(prepared.body).toMatchObject({ + system: [{ type: "text", text: "You are concise.", cache_control: { type: "ephemeral" } }], + messages: [{ role: "user", content: [{ type: "text", text: "hi", cache_control: { type: "ephemeral" } }] }], + }) + }), + ) + it.effect("'auto' is a no-op on OpenAI (implicit caching protocol)", () => Effect.gen(function* () { const prepared = yield* compileRequest( diff --git a/packages/ai/test/exports.test.ts b/packages/ai/test/exports.test.ts index 90ec61cc889..7dea93cac3a 100644 --- a/packages/ai/test/exports.test.ts +++ b/packages/ai/test/exports.test.ts @@ -154,8 +154,8 @@ describe("public exports", () => { expect(XAI.model).toBeFunction() expect(XAI.provider.responses).toBe(XAI.responses) expect(XAI.provider.chat).toBe(XAI.chat) - expect(XAI.configure({ apiKey: "fixture" }).responses("grok-4.3").route.id).toBe("openai-responses") - expect(XAI.configure({ apiKey: "fixture" }).chat("grok-4.3").route.id).toBe("openai-compatible-chat") + expect(XAI.configure({ apiKey: "fixture" }).responses("grok-4.3").route.id).toBe("xai-responses") + expect(XAI.configure({ apiKey: "fixture" }).chat("grok-4.3").route.id).toBe("xai-chat") expect(OpenAI.configure({ apiKey: "fixture" }).image("gpt-image-2").route.id).toBe("openai-images") expect(OpenAI.provider.image).toBe(OpenAI.image) expect(Google.configure({ apiKey: "fixture" }).image("imagen-4.0-generate-001").route.id).toBe("google-images") diff --git a/packages/ai/test/provider-package.test.ts b/packages/ai/test/provider-package.test.ts index 070dd082b9f..a75d8061863 100644 --- a/packages/ai/test/provider-package.test.ts +++ b/packages/ai/test/provider-package.test.ts @@ -300,7 +300,9 @@ describe("provider package entrypoints", () => { }) expect(String(selected.provider)).toBe("example") - expect(selected.route.id).toBe("anthropic-messages") + expect(selected.route.id).toBe("anthropic-compatible-messages") + expect(selected.route.protocol).toBe("anthropic-messages") + expect(selected.route.providerMetadataKey).toBe("example") expect(selected.route.endpoint).toMatchObject({ baseURL: "https://messages.example.test/v1", }) @@ -319,6 +321,7 @@ describe("provider package entrypoints", () => { thinking: { type: "adaptive" }, }) + expect(selected.route.id).toBe("anthropic-messages") expect(selected.route.defaults.providerOptions).toEqual({ thinking: { type: "adaptive" } }) }) diff --git a/packages/ai/test/provider/xai-responses.test.ts b/packages/ai/test/provider/xai-responses.test.ts index 5b7471fdcb1..17ec620ea1e 100644 --- a/packages/ai/test/provider/xai-responses.test.ts +++ b/packages/ai/test/provider/xai-responses.test.ts @@ -56,7 +56,9 @@ describe("xAI Responses route", () => { expect(XAIResponses.protocol.body).not.toBe(OpenAIResponses.protocol.body) const prepared = yield* compileRequest(LLM.request({ model, prompt: "Hello" })) + expect(prepared.route).toBe("xai-responses") expect(prepared.protocol).toBe("xai-responses") + expect(prepared.model.route.providerMetadataKey).toBe("xai") expect(prepared.body.store).toBe(false) expect(prepared.body.include).toEqual(["reasoning.encrypted_content"]) }), @@ -298,3 +300,14 @@ describe("xAI Responses route", () => { }), ) }) + +it.effect("names the xAI Chat route separately from its OpenAI Chat protocol", () => + Effect.gen(function* () { + const prepared = yield* compileRequest( + LLM.request({ model: XAI.configure({ apiKey: "test" }).chat("grok-4.6"), prompt: "Hello" }), + ) + expect(prepared.route).toBe("xai-chat") + expect(prepared.protocol).toBe("openai-chat") + expect(prepared.model.route.providerMetadataKey).toBe("xai") + }), +) diff --git a/packages/core/test/model-resolver.test.ts b/packages/core/test/model-resolver.test.ts index 3fedbcff941..38044267e44 100644 --- a/packages/core/test/model-resolver.test.ts +++ b/packages/core/test/model-resolver.test.ts @@ -595,7 +595,7 @@ describe("ModelResolver", () => { ) const prepared = yield* compileRequest(LLM.request({ model: resolved, prompt: "Hello" })) - expect(resolved.route.id).toBe("openai-responses") + expect(resolved.route.id).toBe("xai-responses") expect(prepared.body).toMatchObject({ reasoning: { effort: "high", summary: "auto" }, include: ["reasoning.encrypted_content"], @@ -1306,7 +1306,7 @@ describe("ModelResolver", () => { reasoningEffort: "high", }) expect(String(mistral.provider)).toBe("test-provider") - expect(xai.route.id).toBe("openai-responses") + expect(xai.route.id).toBe("xai-responses") expect(xai.route.defaults.providerOptions).toEqual({ reasoningEffort: "high", store: false,