@plurnk/plurnk-providers 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.env.defaults +41 -34
  2. package/README.md +15 -0
  3. package/SPEC.md +242 -89
  4. package/dist/AiSdkProvider.d.ts +33 -33
  5. package/dist/AiSdkProvider.d.ts.map +1 -1
  6. package/dist/AiSdkProvider.js +442 -133
  7. package/dist/AiSdkProvider.js.map +1 -1
  8. package/dist/Mock.d.ts +10 -11
  9. package/dist/Mock.d.ts.map +1 -1
  10. package/dist/Mock.js +87 -25
  11. package/dist/Mock.js.map +1 -1
  12. package/dist/Pool.d.ts +9 -24
  13. package/dist/Pool.d.ts.map +1 -1
  14. package/dist/Pool.js +86 -25
  15. package/dist/Pool.js.map +1 -1
  16. package/dist/accounting.d.ts +5 -2
  17. package/dist/accounting.d.ts.map +1 -1
  18. package/dist/accounting.js +100 -16
  19. package/dist/accounting.js.map +1 -1
  20. package/dist/accountingPublic.d.ts +5 -0
  21. package/dist/accountingPublic.d.ts.map +1 -0
  22. package/dist/accountingPublic.js +3 -0
  23. package/dist/accountingPublic.js.map +1 -0
  24. package/dist/aiSdkTransport.d.ts +9 -2
  25. package/dist/aiSdkTransport.d.ts.map +1 -1
  26. package/dist/aiSdkTransport.js +160 -62
  27. package/dist/aiSdkTransport.js.map +1 -1
  28. package/dist/capacity.d.ts +26 -0
  29. package/dist/capacity.d.ts.map +1 -0
  30. package/dist/capacity.js +90 -0
  31. package/dist/capacity.js.map +1 -0
  32. package/dist/catalogProvider.d.ts +8 -3
  33. package/dist/catalogProvider.d.ts.map +1 -1
  34. package/dist/catalogProvider.js +45 -41
  35. package/dist/catalogProvider.js.map +1 -1
  36. package/dist/compatibleProvider.d.ts.map +1 -1
  37. package/dist/compatibleProvider.js +26 -12
  38. package/dist/compatibleProvider.js.map +1 -1
  39. package/dist/cost.d.ts +10 -10
  40. package/dist/cost.d.ts.map +1 -1
  41. package/dist/cost.js +90 -42
  42. package/dist/cost.js.map +1 -1
  43. package/dist/env.d.ts +13 -11
  44. package/dist/env.d.ts.map +1 -1
  45. package/dist/env.js +83 -46
  46. package/dist/env.js.map +1 -1
  47. package/dist/errors.d.ts +17 -3
  48. package/dist/errors.d.ts.map +1 -1
  49. package/dist/errors.js +91 -8
  50. package/dist/errors.js.map +1 -1
  51. package/dist/index.d.ts +7 -6
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +5 -3
  54. package/dist/index.js.map +1 -1
  55. package/dist/ollama.js +3 -3
  56. package/dist/ollama.js.map +1 -1
  57. package/dist/promptTokens.d.ts.map +1 -1
  58. package/dist/promptTokens.js +7 -4
  59. package/dist/promptTokens.js.map +1 -1
  60. package/dist/sdkModels.d.ts +7 -2
  61. package/dist/sdkModels.d.ts.map +1 -1
  62. package/dist/sdkModels.js +43 -13
  63. package/dist/sdkModels.js.map +1 -1
  64. package/dist/types.d.ts +55 -33
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/usage.d.ts +22 -5
  67. package/dist/usage.d.ts.map +1 -1
  68. package/dist/usage.js +169 -83
  69. package/dist/usage.js.map +1 -1
  70. package/package.json +18 -7
  71. package/src/AiSdkProvider.test.ts +964 -206
  72. package/src/AiSdkProvider.ts +545 -155
  73. package/src/Mock.test.ts +69 -30
  74. package/src/Mock.ts +99 -29
  75. package/src/Pool.test.ts +90 -19
  76. package/src/Pool.ts +96 -27
  77. package/src/ProviderRegistry.test.ts +16 -11
  78. package/src/accounting.test.ts +58 -22
  79. package/src/accounting.ts +119 -18
  80. package/src/accountingPublic.ts +9 -0
  81. package/src/aiSdkTransport.test.ts +42 -49
  82. package/src/aiSdkTransport.ts +174 -62
  83. package/src/boundaries.test.ts +2 -0
  84. package/src/capacity.test.ts +92 -0
  85. package/src/capacity.ts +140 -0
  86. package/src/catalogProvider.test.ts +339 -30
  87. package/src/catalogProvider.ts +65 -47
  88. package/src/compatibleProvider.test.ts +7 -5
  89. package/src/compatibleProvider.ts +29 -13
  90. package/src/cost.test.ts +86 -36
  91. package/src/cost.ts +111 -50
  92. package/src/defaults.test.ts +13 -3
  93. package/src/env.test.ts +103 -25
  94. package/src/env.ts +153 -65
  95. package/src/errors.test.ts +80 -2
  96. package/src/errors.ts +107 -8
  97. package/src/index.ts +26 -7
  98. package/src/ollama.test.ts +5 -3
  99. package/src/ollama.ts +3 -3
  100. package/src/promptTokens.ts +8 -5
  101. package/src/sdkModels.test.ts +77 -8
  102. package/src/sdkModels.ts +51 -15
  103. package/src/types.ts +112 -51
  104. package/src/usage.test.ts +112 -116
  105. package/src/usage.ts +214 -93
package/src/index.ts CHANGED
@@ -10,14 +10,24 @@ export type {
10
10
  ProviderOptions,
11
11
  ProviderResponse,
12
12
  ProviderEncryptedReasoningItem,
13
+ ProviderAccounting,
14
+ ProviderCost,
15
+ ProviderCostNormalizer,
16
+ ProviderCallKind,
17
+ ProviderGenerateArgs,
18
+ ProviderRequestAccounting,
19
+ ProviderRequestCapacity,
20
+ ProviderRequestCapacityDecision,
21
+ ProviderRequestIdentity,
22
+ ProviderRequestObserver,
23
+ ProviderRequestSettlement,
13
24
  ProviderUsage,
14
25
  PromptTokenMeasurement,
15
26
  TokenLogprob,
16
27
  TokenAlternative,
17
- AuthoritativeCharge,
18
28
  } from "./types.ts";
19
- export type { ProviderCost } from "@plurnk/plurnk-contracts";
20
29
  export { assertPromptTokenMeasurement } from "./promptTokens.ts";
30
+ export { assessRequestCapacity, effectiveInputCapacity, effectiveOutputBudget, requestCapacityDecision } from "./capacity.ts";
21
31
 
22
32
  // Alias cascade — re-exported from the zero-dep @plurnk/plurnk-aliases, so
23
33
  // the "." surface is unchanged for existing importers and there's one source of
@@ -43,16 +53,25 @@ export type { AiSdkProviderConfig, ReasoningStyle, GrammarStyle } from "./AiSdkP
43
53
  // DECISION stays the consumer's, by choosing which pool to call.
44
54
  export { default as Pool } from "./Pool.ts";
45
55
  export type { ProviderFetch } from "./AiSdkProvider.ts";
46
- export { parseRequiredInt, parseOptionalInt, parseRequiredFloat, parseOptionalFloat, requireEnv, reasoningFromEnv, reasoningResponseStyleFromEnv, scopeEnvToAlias, dataCaptureFromEnv, contextWindowFromEnv, effectiveContextWindow, envelopeFromEnv, resolveReserve, PROVIDERS_KNOBS } from "./env.ts";
47
- export type { Reasoning, ReasoningMode, ReasoningResponseStyle, ReserveSpec } from "./env.ts";
48
- export { normalizeUsage, calculateCostUsd, calculateCostUsdDecimal } from "./usage.ts";
56
+ export { parseRequiredInt, parseOptionalInt, parseRequiredFloat, parseOptionalFloat, requireEnv, reasoningFromEnv, reasoningResponseStyleFromEnv, scopeEnvToAlias, dataCaptureFromEnv, contextWindowFromEnv, effectiveContextWindow, generationEnvelopeFromEnv, resolveGenerationEnvelopeFromEnv, resolveTokenBudget, PROVIDERS_KNOBS } from "./env.ts";
57
+ export type { GenerationEnvelope, Reasoning, ReasoningMode, ReasoningResponseStyle, TokenBudgetSpec } from "./env.ts";
58
+ export { normalizeUsage, calculateCostUsdDecimal, validateProviderUsage } from "./usage.ts";
49
59
  export {
50
- providerCostFor,
60
+ addDecimals,
61
+ estimateProviderCost,
51
62
  providerCostUsd,
52
63
  resolveProviderCost,
53
- validateAuthoritativeCharge,
64
+ sumProviderCostsUsd,
65
+ validateChargedCost,
66
+ validateDecimal,
54
67
  validateProviderCost,
55
68
  } from "./cost.ts";
69
+ export {
70
+ aggregateProviderAccounting,
71
+ plurnkCostNormalizer,
72
+ providerCostNormalizer,
73
+ validateProviderRequestAccounting,
74
+ } from "./accounting.ts";
56
75
  export type { RawUsage, TokenRates } from "./usage.ts";
57
76
  export { ProviderError, classifyProviderError, toProviderError } from "./errors.ts";
58
77
  export { providerSource } from "./notices.ts";
@@ -4,16 +4,18 @@ import { ollamaProviderFromEnv } from "./ollama.ts";
4
4
 
5
5
  const env = Object.freeze({
6
6
  PLURNK_PROVIDERS_FETCH_TIMEOUT: "1000",
7
+ PLURNK_PROVIDERS_OPERATION_TIMEOUT: "3000",
8
+ PLURNK_PROVIDERS_FIRST_CONTENT_TIMEOUT: "1000",
7
9
  PLURNK_PROVIDERS_STREAM_IDLE_TIMEOUT: "0",
8
10
  PLURNK_PROVIDERS_REASONING: "off",
9
11
  PLURNK_PROVIDERS_TEMPERATURE: "0.2",
10
12
  PLURNK_PROVIDERS_REPEAT_PENALTY: "1.15",
11
13
  PLURNK_PROVIDERS_FREQUENCY_PENALTY: "0",
12
- PLURNK_PROVIDERS_REASONING_RESERVE: "10%",
13
- PLURNK_PROVIDERS_COMPLETION_RESERVE: "25%",
14
+ PLURNK_PROVIDERS_OUTPUT_BUDGET: "35%",
14
15
  PLURNK_PROVIDERS_RETRY_ATTEMPTS: "0",
15
16
  PLURNK_PROVIDERS_ERROR_DETAIL_LIMIT: "512",
16
- PLURNK_PROVIDERS_PROMPT_CACHE_KEY: "1",
17
+ PLURNK_PROVIDERS_CACHE_AFFINITY: "1",
18
+ PLURNK_PROVIDERS_CACHE_WRITE_POLICY: "stable-system",
17
19
  });
18
20
 
19
21
  test.afterEach(() => mock.restoreAll());
package/src/ollama.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2
- import { contextWindowFromEnv, effectiveContextWindow, parseRequiredInt, requireEnv } from "./env.ts";
2
+ import { contextWindowFromEnv, effectiveContextWindow, parseTimeoutMs, requireEnv } from "./env.ts";
3
3
  import { providerFromSdkModel } from "./catalogProvider.ts";
4
4
  import type { Provider, ProviderOptions } from "./types.ts";
5
5
 
@@ -20,7 +20,7 @@ const fetchContextWindow = async ({
20
20
  method: "POST",
21
21
  headers: { "Content-Type": "application/json" },
22
22
  body: JSON.stringify({ model }),
23
- signal: AbortSignal.timeout(timeout),
23
+ ...(timeout > 0 ? { signal: AbortSignal.timeout(timeout) } : {}),
24
24
  });
25
25
  if (!response.ok) throw new Error(`ollama provider: /api/show returned ${response.status}`);
26
26
  const data = await response.json() as ShowResponse;
@@ -41,7 +41,7 @@ export const ollamaProviderFromEnv = async (
41
41
  "OLLAMA_BASE_URL or OLLAMA_HOST (or a PLURNK_BASEURL_<alias> override)",
42
42
  "ollama",
43
43
  ).replace(/\/+$/, "").replace(/\/v1$/, "");
44
- const timeout = parseRequiredInt(
44
+ const timeout = parseTimeoutMs(
45
45
  env.PLURNK_PROVIDERS_FETCH_TIMEOUT,
46
46
  "PLURNK_PROVIDERS_FETCH_TIMEOUT",
47
47
  "ollama",
@@ -4,6 +4,7 @@ const KINDS = new Set<PromptTokenMeasurement["kind"]>([
4
4
  "exact",
5
5
  "upper_bound",
6
6
  "estimate",
7
+ "unavailable",
7
8
  ]);
8
9
 
9
10
  export const assertPromptTokenMeasurement = (
@@ -13,19 +14,21 @@ export const assertPromptTokenMeasurement = (
13
14
  if (typeof value !== "object" || value === null) {
14
15
  throw new TypeError(`${owner}: prompt token measurement must be an object`);
15
16
  }
16
- const candidate = value as Partial<PromptTokenMeasurement>;
17
- if (!KINDS.has(candidate.kind as PromptTokenMeasurement["kind"])) {
17
+ const candidate = value as Record<string, unknown>;
18
+ const kind = candidate.kind as PromptTokenMeasurement["kind"];
19
+ if (!KINDS.has(kind)) {
18
20
  throw new TypeError(`${owner}: prompt token measurement has invalid kind ${JSON.stringify(candidate.kind)}`);
19
21
  }
20
- if (!Number.isInteger(candidate.tokens) || candidate.tokens! < 0) {
22
+ if (kind !== "unavailable"
23
+ && (!Number.isInteger(candidate.tokens) || (candidate.tokens as number) < 0)) {
21
24
  throw new TypeError(`${owner}: prompt token measurement tokens must be a non-negative integer`);
22
25
  }
23
26
  if (typeof candidate.source !== "string" || candidate.source.length === 0) {
24
27
  throw new TypeError(`${owner}: prompt token measurement source must be a non-empty string`);
25
28
  }
26
- if (candidate.kind === "estimate"
29
+ if ((kind === "estimate" || kind === "unavailable")
27
30
  && (typeof candidate.detail !== "string" || candidate.detail.length === 0)) {
28
- throw new TypeError(`${owner}: estimated prompt token measurement requires detail`);
31
+ throw new TypeError(`${owner}: ${kind} prompt token measurement requires detail`);
29
32
  }
30
33
  return value as PromptTokenMeasurement;
31
34
  };
@@ -19,12 +19,31 @@ test("createSdkModel uses Models.dev provider facts and operator credentials", (
19
19
  const sdk = createSdkModel("xai", "grok-build-0.1", { XAI_API_KEY: "test-key" });
20
20
  assert.notEqual(sdk, null);
21
21
  assert.equal(sdk?.catalog?.npm, "@ai-sdk/xai");
22
- assert.equal(sdk?.languageModel, undefined);
23
- assert.deepEqual(sdk?.compatible, {
24
- url: "https://api.x.ai/v1/chat/completions",
25
- headers: { Authorization: "Bearer test-key" },
22
+ assert.notEqual(sdk?.languageModel, undefined);
23
+ assert.equal(sdk?.compatible, undefined);
24
+ assert.deepEqual(sdk?.cacheAffinity, { target: "header", name: "x-grok-conv-id" });
25
+ assert.notEqual(sdk?.normalizeCost, undefined);
26
+ });
27
+
28
+ test("createSdkModel constructs Cerebras from Models.dev facts", () => {
29
+ const sdk = createSdkModel("cerebras", "gemma-4-31b", {
30
+ CEREBRAS_API_KEY: "test-key",
26
31
  });
27
- assert.notEqual(sdk?.normalizeCharge, undefined);
32
+ assert.notEqual(sdk, null);
33
+ assert.equal(sdk?.catalog?.npm, "@ai-sdk/cerebras");
34
+ assert.notEqual(sdk?.languageModel, undefined);
35
+ assert.equal(sdk?.compatible, undefined);
36
+ });
37
+
38
+ test("the Google SDK adapter owns its readable-reasoning response projection", () => {
39
+ assert.deepEqual(
40
+ createSdkModel("google", "gemini-3.7-flash", { GEMINI_API_KEY: "test-key" })?.reasoningResponseProviderOptions,
41
+ { google: { thinkingConfig: { includeThoughts: true } } },
42
+ );
43
+ assert.equal(
44
+ createSdkModel("cerebras", "gemma-4-31b", { CEREBRAS_API_KEY: "test-key" })?.reasoningResponseProviderOptions,
45
+ undefined,
46
+ );
28
47
  });
29
48
 
30
49
  test("createSdkModel attaches DeepInfra's documented response-cost normalizer", () => {
@@ -32,13 +51,12 @@ test("createSdkModel attaches DeepInfra's documented response-cost normalizer",
32
51
  DEEPINFRA_API_KEY: "test-key",
33
52
  });
34
53
  assert.notEqual(sdk?.languageModel, undefined);
35
- assert.deepEqual(sdk?.normalizeCharge?.({
54
+ assert.deepEqual(sdk?.normalizeCost?.({
36
55
  usage: { estimated_cost: 5.04e-5 },
37
56
  response: { id: "response-1" },
38
57
  }), {
39
- kind: "authoritative",
58
+ kind: "estimated",
40
59
  amount: { amount: "0.0000504", currency: "USD" },
41
- usdEquivalent: "0.0000504",
42
60
  source: "DeepInfra response usage.estimated_cost",
43
61
  });
44
62
  });
@@ -54,6 +72,57 @@ test("createSdkModel expands catalog endpoint variables without treating them as
54
72
  url: "https://api.cloudflare.com/client/v4/accounts/account/ai/v1/chat/completions",
55
73
  headers: { Authorization: "Bearer token" },
56
74
  });
75
+ assert.deepEqual(sdk?.cacheAffinity, { target: "header", name: "x-session-affinity" });
76
+ });
77
+
78
+ test("catalog routes own their documented cache-affinity request projection", () => {
79
+ assert.deepEqual(
80
+ createSdkModel("openai", "gpt-4.1-mini", { OPENAI_API_KEY: "key" })?.cacheAffinity,
81
+ { target: "provider-option", provider: "openai", name: "promptCacheKey" },
82
+ );
83
+ assert.deepEqual(
84
+ createSdkModel("deepinfra", "zai-org/GLM-5.2", { DEEPINFRA_API_KEY: "key" })?.cacheAffinity,
85
+ { target: "provider-option", provider: "deepinfra", name: "prompt_cache_key" },
86
+ );
87
+ assert.deepEqual(
88
+ createSdkModel("openrouter", "openai/gpt-5", { OPENROUTER_API_KEY: "key" })?.cacheAffinity,
89
+ { target: "header", name: "x-session-id" },
90
+ );
91
+ assert.deepEqual(
92
+ createSdkModel("fireworks", "accounts/fireworks/models/test", { FIREWORKS_API_KEY: "key" })?.cacheAffinity,
93
+ { target: "body", name: "prompt_cache_key" },
94
+ );
95
+ });
96
+
97
+ test("explicit stable-system cache breakpoints exist only on supported Claude routes", () => {
98
+ const cacheControl = { type: "ephemeral" };
99
+ assert.deepEqual(
100
+ createSdkModel("anthropic", "claude-sonnet-4-6", { ANTHROPIC_API_KEY: "key" })?.systemCacheProviderOptions,
101
+ { anthropic: { cacheControl } },
102
+ );
103
+ assert.deepEqual(
104
+ createSdkModel("openrouter", "anthropic/claude-sonnet-4.6", { OPENROUTER_API_KEY: "key" })?.systemCacheProviderOptions,
105
+ { openrouter: { cacheControl } },
106
+ );
107
+ assert.equal(
108
+ createSdkModel("openrouter", "openai/gpt-5", { OPENROUTER_API_KEY: "key" })?.systemCacheProviderOptions,
109
+ undefined,
110
+ );
111
+ assert.equal(
112
+ createSdkModel("deepseek", "deepseek-v4-flash", { DEEPSEEK_API_KEY: "key" })?.systemCacheProviderOptions,
113
+ undefined,
114
+ );
115
+ });
116
+
117
+ test("an operator-declared compatible provider receives no guessed cache extension", () => {
118
+ const sdk = createSdkModel("acme", "model", {
119
+ ACME_API_KEY: "key",
120
+ PLURNK_PROVIDERS_PROVIDER_ACME_NPM: "@ai-sdk/openai-compatible",
121
+ PLURNK_PROVIDERS_PROVIDER_ACME_BASE_URL: "https://api.acme.test/v1",
122
+ PLURNK_PROVIDERS_PROVIDER_ACME_API_KEY_ENV: "ACME_API_KEY",
123
+ });
124
+ assert.equal(sdk?.cacheAffinity, undefined);
125
+ assert.equal(sdk?.systemCacheProviderOptions, undefined);
57
126
  });
58
127
 
59
128
  test("#157: a cataloged compatible provider fails before transport when its declared credential is absent", () => {
package/src/sdkModels.ts CHANGED
@@ -1,27 +1,36 @@
1
1
  import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
2
2
  import { createAnthropic } from "@ai-sdk/anthropic";
3
+ import { createCerebras } from "@ai-sdk/cerebras";
3
4
  import { createDeepInfra } from "@ai-sdk/deepinfra";
4
5
  import { createGoogle } from "@ai-sdk/google";
5
6
  import { createGroq } from "@ai-sdk/groq";
6
7
  import { createMistral } from "@ai-sdk/mistral";
7
8
  import { createOpenAI } from "@ai-sdk/openai";
8
9
  import { createTogetherAI } from "@ai-sdk/togetherai";
10
+ import { createXai } from "@ai-sdk/xai";
9
11
  import { createOpenRouter } from "@openrouter/ai-sdk-provider";
10
12
  import { lookupProvider, type ProviderInfo } from "@plurnk/plurnk-models";
11
13
  import type { LanguageModel } from "ai";
12
- import { authoritativeChargeNormalizer } from "./accounting.ts";
13
- import type { AuthoritativeChargeNormalizer } from "./types.ts";
14
+ import { providerCostNormalizer } from "./accounting.ts";
15
+ import type { AiSdkProviderOptions, CacheAffinity } from "./AiSdkProvider.ts";
16
+ import type { ProviderCostNormalizer } from "./types.ts";
14
17
 
15
18
  export type SdkModel = {
16
19
  readonly languageModel?: LanguageModel;
17
- readonly normalizeCharge?: AuthoritativeChargeNormalizer;
20
+ readonly normalizeCost?: ProviderCostNormalizer;
18
21
  readonly compatible?: {
19
22
  readonly url: string;
20
23
  readonly headers: Readonly<Record<string, string>>;
21
24
  };
25
+ readonly cacheAffinity?: CacheAffinity;
26
+ readonly systemCacheProviderOptions?: AiSdkProviderOptions;
27
+ readonly reasoningResponseProviderOptions?: AiSdkProviderOptions;
28
+ readonly additiveReasoningProvider?: "anthropic" | "bedrock";
22
29
  readonly catalog: ProviderInfo | null;
23
30
  };
24
31
 
32
+ const cacheControl = { type: "ephemeral" as const };
33
+
25
34
  const envPrefix = (provider: string): string =>
26
35
  provider.replaceAll(/[^a-zA-Z0-9]/g, "_").toUpperCase();
27
36
 
@@ -107,12 +116,15 @@ export const createSdkModel = (
107
116
  const catalog = lookupProvider(provider) ?? configuredProviderInfo(provider, env);
108
117
  if (catalog === null) return null;
109
118
  const url = baseUrl(provider, env, catalog, baseUrlOverride);
110
- const normalizeCharge = authoritativeChargeNormalizer(catalog.npm);
119
+ const normalizeCost = providerCostNormalizer(catalog.npm);
111
120
 
112
121
  switch (catalog.npm) {
113
122
  case "@ai-sdk/openai":
114
123
  return {
115
124
  languageModel: createOpenAI({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).chat(model),
125
+ ...(catalog.id === "openai"
126
+ ? { cacheAffinity: { target: "provider-option" as const, provider: "openai", name: "promptCacheKey" } }
127
+ : {}),
116
128
  catalog,
117
129
  };
118
130
  case "@ai-sdk/groq":
@@ -120,6 +132,11 @@ export const createSdkModel = (
120
132
  languageModel: createGroq({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
121
133
  catalog,
122
134
  };
135
+ case "@ai-sdk/cerebras":
136
+ return {
137
+ languageModel: createCerebras({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
138
+ catalog,
139
+ };
123
140
  case "@ai-sdk/mistral":
124
141
  return {
125
142
  languageModel: createMistral({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
@@ -133,29 +150,36 @@ export const createSdkModel = (
133
150
  case "@ai-sdk/deepinfra":
134
151
  return {
135
152
  languageModel: createDeepInfra({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
136
- ...(normalizeCharge === undefined ? {} : { normalizeCharge }),
153
+ ...(normalizeCost === undefined ? {} : { normalizeCost }),
154
+ ...(catalog.id === "deepinfra"
155
+ ? { cacheAffinity: { target: "provider-option" as const, provider: "deepinfra", name: "prompt_cache_key" } }
156
+ : {}),
137
157
  catalog,
138
158
  };
139
159
  case "@ai-sdk/google":
140
160
  return {
141
161
  languageModel: createGoogle({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
162
+ reasoningResponseProviderOptions: {
163
+ google: { thinkingConfig: { includeThoughts: true } },
164
+ },
142
165
  catalog,
143
166
  };
144
- case "@ai-sdk/xai": {
145
- const key = requireApiKey(provider, env, catalog);
146
- const compatibleBase = url ?? "https://api.x.ai/v1";
167
+ case "@ai-sdk/xai":
147
168
  return {
148
- compatible: {
149
- url: `${compatibleBase}/chat/completions`,
150
- headers: { Authorization: `Bearer ${key}` },
151
- },
152
- ...(normalizeCharge === undefined ? {} : { normalizeCharge }),
169
+ languageModel: createXai({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).chat(model),
170
+ ...(catalog.id === "xai"
171
+ ? { cacheAffinity: { target: "header" as const, name: "x-grok-conv-id" } }
172
+ : {}),
173
+ ...(normalizeCost === undefined ? {} : { normalizeCost }),
153
174
  catalog,
154
175
  };
155
- }
156
176
  case "@ai-sdk/anthropic":
157
177
  return {
158
178
  languageModel: createAnthropic({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
179
+ additiveReasoningProvider: "anthropic",
180
+ ...(catalog.id === "anthropic"
181
+ ? { systemCacheProviderOptions: { anthropic: { cacheControl } } }
182
+ : {}),
159
183
  catalog,
160
184
  };
161
185
  case "@ai-sdk/amazon-bedrock":
@@ -168,6 +192,7 @@ export const createSdkModel = (
168
192
  apiKey: env.AWS_BEARER_TOKEN_BEDROCK,
169
193
  baseURL: url,
170
194
  }).languageModel(model),
195
+ additiveReasoningProvider: "bedrock",
171
196
  catalog,
172
197
  };
173
198
  case "@openrouter/ai-sdk-provider":
@@ -180,7 +205,13 @@ export const createSdkModel = (
180
205
  ...(env.OPENROUTER_X_TITLE === undefined ? {} : { "X-Title": env.OPENROUTER_X_TITLE }),
181
206
  },
182
207
  }).languageModel(model),
183
- ...(normalizeCharge === undefined ? {} : { normalizeCharge }),
208
+ ...(catalog.id === "openrouter"
209
+ ? { cacheAffinity: { target: "header" as const, name: "x-session-id" } }
210
+ : {}),
211
+ ...(catalog.id === "openrouter" && model.replace(/^~/, "").startsWith("anthropic/")
212
+ ? { systemCacheProviderOptions: { openrouter: { cacheControl } } }
213
+ : {}),
214
+ ...(normalizeCost === undefined ? {} : { normalizeCost }),
184
215
  catalog,
185
216
  };
186
217
  case "@ai-sdk/openai-compatible":
@@ -194,6 +225,11 @@ export const createSdkModel = (
194
225
  ? {}
195
226
  : { Authorization: `Bearer ${key}` },
196
227
  },
228
+ ...(catalog.id === "cloudflare-workers-ai"
229
+ ? { cacheAffinity: { target: "header" as const, name: "x-session-affinity" } }
230
+ : catalog.id === "fireworks-ai"
231
+ ? { cacheAffinity: { target: "body" as const, name: "prompt_cache_key" } }
232
+ : {}),
197
233
  catalog,
198
234
  };
199
235
  default: