@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.
- package/.env.defaults +41 -34
- package/README.md +15 -0
- package/SPEC.md +242 -89
- package/dist/AiSdkProvider.d.ts +33 -33
- package/dist/AiSdkProvider.d.ts.map +1 -1
- package/dist/AiSdkProvider.js +442 -133
- package/dist/AiSdkProvider.js.map +1 -1
- package/dist/Mock.d.ts +10 -11
- package/dist/Mock.d.ts.map +1 -1
- package/dist/Mock.js +87 -25
- package/dist/Mock.js.map +1 -1
- package/dist/Pool.d.ts +9 -24
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +86 -25
- package/dist/Pool.js.map +1 -1
- package/dist/accounting.d.ts +5 -2
- package/dist/accounting.d.ts.map +1 -1
- package/dist/accounting.js +100 -16
- package/dist/accounting.js.map +1 -1
- package/dist/accountingPublic.d.ts +5 -0
- package/dist/accountingPublic.d.ts.map +1 -0
- package/dist/accountingPublic.js +3 -0
- package/dist/accountingPublic.js.map +1 -0
- package/dist/aiSdkTransport.d.ts +9 -2
- package/dist/aiSdkTransport.d.ts.map +1 -1
- package/dist/aiSdkTransport.js +160 -62
- package/dist/aiSdkTransport.js.map +1 -1
- package/dist/capacity.d.ts +26 -0
- package/dist/capacity.d.ts.map +1 -0
- package/dist/capacity.js +90 -0
- package/dist/capacity.js.map +1 -0
- package/dist/catalogProvider.d.ts +8 -3
- package/dist/catalogProvider.d.ts.map +1 -1
- package/dist/catalogProvider.js +45 -41
- package/dist/catalogProvider.js.map +1 -1
- package/dist/compatibleProvider.d.ts.map +1 -1
- package/dist/compatibleProvider.js +26 -12
- package/dist/compatibleProvider.js.map +1 -1
- package/dist/cost.d.ts +10 -10
- package/dist/cost.d.ts.map +1 -1
- package/dist/cost.js +90 -42
- package/dist/cost.js.map +1 -1
- package/dist/env.d.ts +13 -11
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +83 -46
- package/dist/env.js.map +1 -1
- package/dist/errors.d.ts +17 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +91 -8
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/ollama.js +3 -3
- package/dist/ollama.js.map +1 -1
- package/dist/promptTokens.d.ts.map +1 -1
- package/dist/promptTokens.js +7 -4
- package/dist/promptTokens.js.map +1 -1
- package/dist/sdkModels.d.ts +7 -2
- package/dist/sdkModels.d.ts.map +1 -1
- package/dist/sdkModels.js +43 -13
- package/dist/sdkModels.js.map +1 -1
- package/dist/types.d.ts +55 -33
- package/dist/types.d.ts.map +1 -1
- package/dist/usage.d.ts +22 -5
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +169 -83
- package/dist/usage.js.map +1 -1
- package/package.json +18 -7
- package/src/AiSdkProvider.test.ts +964 -206
- package/src/AiSdkProvider.ts +545 -155
- package/src/Mock.test.ts +69 -30
- package/src/Mock.ts +99 -29
- package/src/Pool.test.ts +90 -19
- package/src/Pool.ts +96 -27
- package/src/ProviderRegistry.test.ts +16 -11
- package/src/accounting.test.ts +58 -22
- package/src/accounting.ts +119 -18
- package/src/accountingPublic.ts +9 -0
- package/src/aiSdkTransport.test.ts +42 -49
- package/src/aiSdkTransport.ts +174 -62
- package/src/boundaries.test.ts +2 -0
- package/src/capacity.test.ts +92 -0
- package/src/capacity.ts +140 -0
- package/src/catalogProvider.test.ts +339 -30
- package/src/catalogProvider.ts +65 -47
- package/src/compatibleProvider.test.ts +7 -5
- package/src/compatibleProvider.ts +29 -13
- package/src/cost.test.ts +86 -36
- package/src/cost.ts +111 -50
- package/src/defaults.test.ts +13 -3
- package/src/env.test.ts +103 -25
- package/src/env.ts +153 -65
- package/src/errors.test.ts +80 -2
- package/src/errors.ts +107 -8
- package/src/index.ts +26 -7
- package/src/ollama.test.ts +5 -3
- package/src/ollama.ts +3 -3
- package/src/promptTokens.ts +8 -5
- package/src/sdkModels.test.ts +77 -8
- package/src/sdkModels.ts +51 -15
- package/src/types.ts +112 -51
- package/src/usage.test.ts +112 -116
- 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,
|
|
47
|
-
export type { Reasoning, ReasoningMode, ReasoningResponseStyle,
|
|
48
|
-
export { normalizeUsage,
|
|
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
|
-
|
|
60
|
+
addDecimals,
|
|
61
|
+
estimateProviderCost,
|
|
51
62
|
providerCostUsd,
|
|
52
63
|
resolveProviderCost,
|
|
53
|
-
|
|
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";
|
package/src/ollama.test.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
44
|
+
const timeout = parseTimeoutMs(
|
|
45
45
|
env.PLURNK_PROVIDERS_FETCH_TIMEOUT,
|
|
46
46
|
"PLURNK_PROVIDERS_FETCH_TIMEOUT",
|
|
47
47
|
"ollama",
|
package/src/promptTokens.ts
CHANGED
|
@@ -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
|
|
17
|
-
|
|
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 (
|
|
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 (
|
|
29
|
+
if ((kind === "estimate" || kind === "unavailable")
|
|
27
30
|
&& (typeof candidate.detail !== "string" || candidate.detail.length === 0)) {
|
|
28
|
-
throw new TypeError(`${owner}:
|
|
31
|
+
throw new TypeError(`${owner}: ${kind} prompt token measurement requires detail`);
|
|
29
32
|
}
|
|
30
33
|
return value as PromptTokenMeasurement;
|
|
31
34
|
};
|
package/src/sdkModels.test.ts
CHANGED
|
@@ -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.
|
|
23
|
-
assert.
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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?.
|
|
54
|
+
assert.deepEqual(sdk?.normalizeCost?.({
|
|
36
55
|
usage: { estimated_cost: 5.04e-5 },
|
|
37
56
|
response: { id: "response-1" },
|
|
38
57
|
}), {
|
|
39
|
-
kind: "
|
|
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 {
|
|
13
|
-
import type {
|
|
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
|
|
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
|
|
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
|
-
...(
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
...(
|
|
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
|
-
...(
|
|
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:
|