fix(provider): select prompt cache keys by SDK (#38424)
This commit is contained in:
@@ -58,6 +58,28 @@ function sdkKey(npm: string): string | undefined {
|
|||||||
return "vertex"
|
return "vertex"
|
||||||
case "@ai-sdk/google":
|
case "@ai-sdk/google":
|
||||||
return "google"
|
return "google"
|
||||||
|
case "@ai-sdk/alibaba":
|
||||||
|
return "alibaba"
|
||||||
|
case "@ai-sdk/cerebras":
|
||||||
|
return "cerebras"
|
||||||
|
case "@ai-sdk/cohere":
|
||||||
|
return "cohere"
|
||||||
|
case "@ai-sdk/deepinfra":
|
||||||
|
return "deepinfra"
|
||||||
|
case "@ai-sdk/groq":
|
||||||
|
return "groq"
|
||||||
|
case "@ai-sdk/mistral":
|
||||||
|
return "mistral"
|
||||||
|
case "@ai-sdk/perplexity":
|
||||||
|
return "perplexity"
|
||||||
|
case "@ai-sdk/togetherai":
|
||||||
|
return "togetherai"
|
||||||
|
case "@ai-sdk/vercel":
|
||||||
|
return "vercel"
|
||||||
|
case "@ai-sdk/xai":
|
||||||
|
return "xai"
|
||||||
|
case "venice-ai-sdk-provider":
|
||||||
|
return "venice"
|
||||||
case "@ai-sdk/gateway":
|
case "@ai-sdk/gateway":
|
||||||
return "gateway"
|
return "gateway"
|
||||||
case "@openrouter/ai-sdk-provider":
|
case "@openrouter/ai-sdk-provider":
|
||||||
@@ -442,6 +464,9 @@ function mapProviderOptions(
|
|||||||
export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
|
export function message(msgs: ModelMessage[], model: Provider.Model, options: Record<string, unknown>) {
|
||||||
msgs = unsupportedParts(msgs, model)
|
msgs = unsupportedParts(msgs, model)
|
||||||
msgs = normalizeMessages(msgs, model, options)
|
msgs = normalizeMessages(msgs, model, options)
|
||||||
|
const usesAnthropicAutomaticCaching =
|
||||||
|
options.cacheControl !== undefined &&
|
||||||
|
(model.api.npm === "@ai-sdk/anthropic" || model.api.npm === "@ai-sdk/google-vertex/anthropic")
|
||||||
if (
|
if (
|
||||||
(model.providerID === "anthropic" ||
|
(model.providerID === "anthropic" ||
|
||||||
model.providerID === "google-vertex-anthropic" ||
|
model.providerID === "google-vertex-anthropic" ||
|
||||||
@@ -451,7 +476,8 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re
|
|||||||
model.id.includes("claude") ||
|
model.id.includes("claude") ||
|
||||||
model.api.npm === "@ai-sdk/anthropic" ||
|
model.api.npm === "@ai-sdk/anthropic" ||
|
||||||
model.api.npm === "@ai-sdk/alibaba") &&
|
model.api.npm === "@ai-sdk/alibaba") &&
|
||||||
model.api.npm !== "@ai-sdk/gateway"
|
model.api.npm !== "@ai-sdk/gateway" &&
|
||||||
|
!usesAnthropicAutomaticCaching
|
||||||
) {
|
) {
|
||||||
msgs = applyCaching(msgs, model)
|
msgs = applyCaching(msgs, model)
|
||||||
}
|
}
|
||||||
@@ -1137,7 +1163,6 @@ export function options(input: {
|
|||||||
|
|
||||||
if (input.model.api.npm === "@ai-sdk/azure") {
|
if (input.model.api.npm === "@ai-sdk/azure") {
|
||||||
result["store"] = false
|
result["store"] = false
|
||||||
result["promptCacheKey"] = input.sessionID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.model.api.npm === "@openrouter/ai-sdk-provider" || input.model.api.npm === "@llmgateway/ai-sdk-provider") {
|
if (input.model.api.npm === "@openrouter/ai-sdk-provider" || input.model.api.npm === "@llmgateway/ai-sdk-provider") {
|
||||||
@@ -1166,16 +1191,6 @@ export function options(input: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
input.providerOptions?.setCacheKey !== false &&
|
|
||||||
(input.model.providerID === "openai" ||
|
|
||||||
input.model.api.npm === "@ai-sdk/openai" ||
|
|
||||||
input.model.api.npm === "@ai-sdk/xai" ||
|
|
||||||
input.providerOptions?.setCacheKey)
|
|
||||||
) {
|
|
||||||
result["promptCacheKey"] = input.sessionID
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.model.providerID === "meta" && input.model.api.npm === "@ai-sdk/openai") {
|
if (input.model.providerID === "meta" && input.model.api.npm === "@ai-sdk/openai") {
|
||||||
result["reasoningSummary"] = "auto"
|
result["reasoningSummary"] = "auto"
|
||||||
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
||||||
@@ -1224,6 +1239,25 @@ export function options(input: {
|
|||||||
result["enable_thinking"] = true
|
result["enable_thinking"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.providerOptions?.setCacheKey !== false) {
|
||||||
|
if (input.model.api.npm === "@ai-sdk/deepinfra" || input.model.api.npm === "@ai-sdk/cerebras") {
|
||||||
|
result["prompt_cache_key"] = input.sessionID
|
||||||
|
} else if (
|
||||||
|
input.model.api.npm === "@ai-sdk/openai" ||
|
||||||
|
input.model.api.npm === "@ai-sdk/azure" ||
|
||||||
|
input.model.api.npm === "@ai-sdk/xai" ||
|
||||||
|
input.model.api.npm === "@ai-sdk/mistral" ||
|
||||||
|
input.model.api.npm === "venice-ai-sdk-provider" ||
|
||||||
|
input.providerOptions?.setCacheKey === true
|
||||||
|
) {
|
||||||
|
result["promptCacheKey"] = input.sessionID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.model.api.npm === "@ai-sdk/gateway") {
|
||||||
|
result["gateway"] = { caching: "auto" }
|
||||||
|
}
|
||||||
|
|
||||||
if (input.model.api.npm === "@ai-sdk/azure" && input.model.api.id.includes("gpt-5.5")) {
|
if (input.model.api.npm === "@ai-sdk/azure" && input.model.api.id.includes("gpt-5.5")) {
|
||||||
result["reasoningSummary"] = "auto"
|
result["reasoningSummary"] = "auto"
|
||||||
return result
|
return result
|
||||||
@@ -1256,26 +1290,13 @@ export function options(input: {
|
|||||||
result["textVerbosity"] = "low"
|
result["textVerbosity"] = "low"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.model.providerID.startsWith("opencode")) {
|
if (input.model.providerID.startsWith("opencode") && input.providerOptions?.setCacheKey !== false) {
|
||||||
result["promptCacheKey"] = input.sessionID
|
result["promptCacheKey"] = input.sessionID
|
||||||
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
result["include"] = INCLUDE_ENCRYPTED_REASONING
|
||||||
result["reasoningSummary"] = "auto"
|
result["reasoningSummary"] = "auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.model.providerID === "venice") {
|
|
||||||
result["promptCacheKey"] = input.sessionID
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.model.providerID === "openrouter") {
|
|
||||||
result["prompt_cache_key"] = input.sessionID
|
|
||||||
}
|
|
||||||
if (input.model.api.npm === "@ai-sdk/gateway") {
|
|
||||||
result["gateway"] = {
|
|
||||||
caching: "auto",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,32 @@ describe("ProviderTransform.options - setCacheKey", () => {
|
|||||||
expect(result.promptCacheKey).toBe(sessionID)
|
expect(result.promptCacheKey).toBe(sessionID)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should set promptCacheKey for the OpenAI SDK regardless of provider ID", () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: {
|
||||||
|
...mockModel,
|
||||||
|
providerID: "custom-openai",
|
||||||
|
api: { id: "gpt-5", url: "https://example.com", npm: "@ai-sdk/openai" },
|
||||||
|
},
|
||||||
|
sessionID,
|
||||||
|
providerOptions: {},
|
||||||
|
})
|
||||||
|
expect(result.promptCacheKey).toBe(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should not set promptCacheKey for the OpenAI-compatible SDK by provider name", () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: {
|
||||||
|
...mockModel,
|
||||||
|
providerID: "openai",
|
||||||
|
api: { id: "gpt-5", url: "https://example.com", npm: "@ai-sdk/openai-compatible" },
|
||||||
|
},
|
||||||
|
sessionID,
|
||||||
|
providerOptions: {},
|
||||||
|
})
|
||||||
|
expect(result.promptCacheKey).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test("should not set promptCacheKey for openai when explicitly disabled", () => {
|
test("should not set promptCacheKey for openai when explicitly disabled", () => {
|
||||||
const openaiModel = {
|
const openaiModel = {
|
||||||
...mockModel,
|
...mockModel,
|
||||||
@@ -209,6 +235,70 @@ describe("ProviderTransform.options - setCacheKey", () => {
|
|||||||
providerOptions: {},
|
providerOptions: {},
|
||||||
})
|
})
|
||||||
expect(result.store).toBe(false)
|
expect(result.store).toBe(false)
|
||||||
|
expect(result.promptCacheKey).toBe(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should disable the Azure cache key without disabling store=false", () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: {
|
||||||
|
...mockModel,
|
||||||
|
providerID: "azure",
|
||||||
|
api: { id: "gpt-5", url: "https://azure.com", npm: "@ai-sdk/azure" },
|
||||||
|
},
|
||||||
|
sessionID,
|
||||||
|
providerOptions: { setCacheKey: false },
|
||||||
|
})
|
||||||
|
expect(result.store).toBe(false)
|
||||||
|
expect(result.promptCacheKey).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should keep the Azure cache key for gpt-5.5 early return", () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: {
|
||||||
|
...mockModel,
|
||||||
|
providerID: "azure",
|
||||||
|
api: { id: "gpt-5.5", url: "https://azure.com", npm: "@ai-sdk/azure" },
|
||||||
|
},
|
||||||
|
sessionID,
|
||||||
|
providerOptions: {},
|
||||||
|
})
|
||||||
|
expect(result.store).toBe(false)
|
||||||
|
expect(result.reasoningSummary).toBe("auto")
|
||||||
|
expect(result.promptCacheKey).toBe(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const npm of ["@ai-sdk/deepinfra", "@ai-sdk/cerebras"]) {
|
||||||
|
test(`should set the snake-case cache key for ${npm}`, () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: { ...mockModel, providerID: "custom", api: { ...mockModel.api, npm } },
|
||||||
|
sessionID,
|
||||||
|
providerOptions: {},
|
||||||
|
})
|
||||||
|
expect(result.prompt_cache_key).toBe(sessionID)
|
||||||
|
expect(result.promptCacheKey).toBeUndefined()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should set promptCacheKey for the Mistral SDK", () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: { ...mockModel, providerID: "custom", api: { ...mockModel.api, npm: "@ai-sdk/mistral" } },
|
||||||
|
sessionID,
|
||||||
|
providerOptions: {},
|
||||||
|
})
|
||||||
|
expect(result.promptCacheKey).toBe(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should not send an undocumented OpenRouter prompt_cache_key", () => {
|
||||||
|
const result = ProviderTransform.options({
|
||||||
|
model: {
|
||||||
|
...mockModel,
|
||||||
|
providerID: "openrouter",
|
||||||
|
api: { ...mockModel.api, npm: "@openrouter/ai-sdk-provider" },
|
||||||
|
},
|
||||||
|
sessionID,
|
||||||
|
providerOptions: {},
|
||||||
|
})
|
||||||
|
expect(result.prompt_cache_key).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -722,6 +812,17 @@ describe("ProviderTransform.providerOptions", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("uses canonical sdk key for custom xAI models", () => {
|
||||||
|
const model = createModel({
|
||||||
|
providerID: "my-xai",
|
||||||
|
api: { id: "grok-4", url: "https://api.x.ai", npm: "@ai-sdk/xai" },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(ProviderTransform.providerOptions(model, { promptCacheKey: "session" })).toEqual({
|
||||||
|
xai: { promptCacheKey: "session" },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("forces reasoning for explicit effort even when model is not marked reasoning-capable", () => {
|
test("forces reasoning for explicit effort even when model is not marked reasoning-capable", () => {
|
||||||
const model = createModel({
|
const model = createModel({
|
||||||
capabilities: {
|
capabilities: {
|
||||||
@@ -3011,6 +3112,20 @@ describe("ProviderTransform.message - cache control on gateway", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("does not add explicit breakpoints when Anthropic automatic caching is enabled", () => {
|
||||||
|
const model = createModel({
|
||||||
|
providerID: "anthropic",
|
||||||
|
api: { id: "claude-sonnet-4", url: "https://api.anthropic.com", npm: "@ai-sdk/anthropic" },
|
||||||
|
})
|
||||||
|
const msgs = [
|
||||||
|
{ role: "system", content: "You are a helpful assistant" },
|
||||||
|
{ role: "user", content: "Hello" },
|
||||||
|
] as any[]
|
||||||
|
|
||||||
|
const result = ProviderTransform.message(msgs, model, { cacheControl: { type: "ephemeral" } }) as any[]
|
||||||
|
expect(result.every((message) => message.providerOptions === undefined)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test("google-vertex-anthropic applies cache control", () => {
|
test("google-vertex-anthropic applies cache control", () => {
|
||||||
const model = createModel({
|
const model = createModel({
|
||||||
providerID: "google-vertex-anthropic",
|
providerID: "google-vertex-anthropic",
|
||||||
|
|||||||
Reference in New Issue
Block a user