@plurnk/plurnk-providers 1.19.0 → 1.19.2
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/SPEC.md +19 -48
- package/dist/AiSdkProvider.d.ts +1 -1
- package/dist/AiSdkProvider.d.ts.map +1 -1
- package/dist/AiSdkProvider.js +0 -3
- package/dist/AiSdkProvider.js.map +1 -1
- package/dist/AiSdkRequestBody.d.ts.map +1 -1
- package/dist/AiSdkRequestBody.js +0 -9
- package/dist/AiSdkRequestBody.js.map +1 -1
- package/dist/Mock.d.ts +1 -2
- package/dist/Mock.d.ts.map +1 -1
- package/dist/Mock.js +0 -1
- package/dist/Mock.js.map +1 -1
- package/dist/accounting.d.ts.map +1 -1
- package/dist/accounting.js +0 -25
- package/dist/accounting.js.map +1 -1
- package/dist/aiSdkTransport.d.ts +0 -8
- package/dist/aiSdkTransport.d.ts.map +1 -1
- package/dist/aiSdkTransport.js +4 -92
- package/dist/aiSdkTransport.js.map +1 -1
- package/dist/catalogProvider.d.ts.map +1 -1
- package/dist/catalogProvider.js +3 -5
- package/dist/catalogProvider.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/sdkModels.d.ts.map +1 -1
- package/dist/sdkModels.js +0 -10
- package/dist/sdkModels.js.map +1 -1
- package/dist/types.d.ts +0 -9
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -6
- package/src/AiSdkProvider.test.ts +0 -120
- package/src/AiSdkProvider.ts +1 -4
- package/src/AiSdkRequestBody.ts +0 -8
- package/src/Mock.ts +1 -3
- package/src/accounting.test.ts +3 -14
- package/src/accounting.ts +0 -26
- package/src/aiSdkTransport.test.ts +0 -61
- package/src/aiSdkTransport.ts +4 -95
- package/src/catalogProvider.test.ts +5 -128
- package/src/catalogProvider.ts +3 -4
- package/src/index.ts +1 -1
- package/src/sdkModels.test.ts +0 -10
- package/src/sdkModels.ts +0 -10
- package/src/types.ts +0 -11
|
@@ -106,12 +106,6 @@ test("provider adapters advertise only reasoning policies they can preserve", ()
|
|
|
106
106
|
}, "mistral-small-latest");
|
|
107
107
|
assert.deepEqual(mistral?.supportedReasoningPolicies, ["off", "adaptive", "high"], "Mistral's low/medium coercion is not advertised as exact support");
|
|
108
108
|
|
|
109
|
-
const grok = catalogProviderFromEnv("xai", {
|
|
110
|
-
...env,
|
|
111
|
-
XAI_API_KEY: "test-key",
|
|
112
|
-
PLURNK_PROVIDERS_REASONING: "adaptive",
|
|
113
|
-
}, "grok-4.6");
|
|
114
|
-
assert.deepEqual(grok?.supportedReasoningPolicies, ["adaptive", "low", "medium", "high", "xhigh"], "Grok 4.6 cannot disable reasoning");
|
|
115
109
|
|
|
116
110
|
const gemini = catalogProviderFromEnv("google", {
|
|
117
111
|
...env,
|
|
@@ -368,79 +362,6 @@ test("official AI SDK provider owns the native request while PLURNK owns call se
|
|
|
368
362
|
assert.equal(calls[0]?.body.prompt_cache_key, "worker", "the official OpenAI SDK projects the documented affinity key");
|
|
369
363
|
});
|
|
370
364
|
|
|
371
|
-
test("xAI's native chat contract requests its strongest cataloged effort and caps the complete reasoning response", async () => {
|
|
372
|
-
let call: { headers: Headers; body: Record<string, unknown> } | undefined;
|
|
373
|
-
mock.method(globalThis, "fetch", async (_input: string | URL | Request, init?: RequestInit) => {
|
|
374
|
-
call = {
|
|
375
|
-
headers: new Headers(init?.headers),
|
|
376
|
-
body: JSON.parse(String(init?.body)) as Record<string, unknown>,
|
|
377
|
-
};
|
|
378
|
-
return new Response([
|
|
379
|
-
`data: ${JSON.stringify({
|
|
380
|
-
id: "response-xai",
|
|
381
|
-
object: "chat.completion.chunk",
|
|
382
|
-
created: 1,
|
|
383
|
-
model: "grok-4.6",
|
|
384
|
-
choices: [{ index: 0, delta: { reasoning_content: "consider" }, finish_reason: null }],
|
|
385
|
-
})}`,
|
|
386
|
-
`data: ${JSON.stringify({
|
|
387
|
-
id: "response-xai",
|
|
388
|
-
object: "chat.completion.chunk",
|
|
389
|
-
created: 2,
|
|
390
|
-
model: "grok-4.6",
|
|
391
|
-
choices: [{ index: 0, delta: { content: "OK" }, finish_reason: "stop" }],
|
|
392
|
-
})}`,
|
|
393
|
-
`data: ${JSON.stringify({
|
|
394
|
-
id: "response-xai",
|
|
395
|
-
object: "chat.completion.chunk",
|
|
396
|
-
created: 3,
|
|
397
|
-
model: "grok-4.6",
|
|
398
|
-
choices: [],
|
|
399
|
-
usage: {
|
|
400
|
-
prompt_tokens: 5,
|
|
401
|
-
completion_tokens: 4,
|
|
402
|
-
total_tokens: 9,
|
|
403
|
-
prompt_tokens_details: { cached_tokens: 2 },
|
|
404
|
-
completion_tokens_details: { reasoning_tokens: 3 },
|
|
405
|
-
cost_in_usd_ticks: 1_230_000,
|
|
406
|
-
},
|
|
407
|
-
})}`,
|
|
408
|
-
"data: [DONE]",
|
|
409
|
-
].join("\n\n"), { headers: { "content-type": "text/event-stream" } });
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
const provider = catalogProviderFromEnv("xai", {
|
|
413
|
-
...env,
|
|
414
|
-
XAI_API_KEY: "test-key",
|
|
415
|
-
PLURNK_PROVIDERS_REASONING: "adaptive",
|
|
416
|
-
}, "grok-4.6");
|
|
417
|
-
const result = await provider?.generate({
|
|
418
|
-
workerId: "xai-worker",
|
|
419
|
-
messages: [{ role: "user", content: "hello" }],
|
|
420
|
-
maxOutputTokens: 16,
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
assert.equal(call?.body.max_completion_tokens, 16);
|
|
424
|
-
assert.equal(call?.body.reasoning_effort, "xhigh", "adaptive uses the strongest route-advertised generic effort");
|
|
425
|
-
assert.equal("max_tokens" in (call?.body ?? {}), false);
|
|
426
|
-
assert.equal(call?.headers.get("x-grok-conv-id"), "xai-worker");
|
|
427
|
-
assert.equal(result?.assistant.reasoning, "consider");
|
|
428
|
-
assert.equal(result?.assistant.content, "OK");
|
|
429
|
-
assert.deepEqual(result?.accounting[0]?.usage, {
|
|
430
|
-
inputTokens: 5,
|
|
431
|
-
outputTokens: 4,
|
|
432
|
-
totalTokens: 9,
|
|
433
|
-
inputTokenDetails: { cacheReadTokens: 2 },
|
|
434
|
-
outputTokenDetails: { textTokens: 1, reasoningTokens: 3 },
|
|
435
|
-
});
|
|
436
|
-
assert.deepEqual(result?.accounting[0]?.cost, {
|
|
437
|
-
kind: "charged",
|
|
438
|
-
amount: { amount: "1230000", currency: "USDTICK" },
|
|
439
|
-
usdEquivalent: "0.000123",
|
|
440
|
-
source: "xAI response usage.cost_in_usd_ticks",
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
|
|
444
365
|
test("Cerebras explicit and adaptive reasoning do not invent a token budget", async () => {
|
|
445
366
|
let body: Record<string, unknown> | undefined;
|
|
446
367
|
mock.method(globalThis, "fetch", async (_input: string | URL | Request, init?: RequestInit) => {
|
|
@@ -852,12 +773,12 @@ test("native provider routes project their documented cache controls through the
|
|
|
852
773
|
|
|
853
774
|
test("cataloged unknown model fails unless its context is explicit", () => {
|
|
854
775
|
assert.throws(
|
|
855
|
-
() => catalogProviderFromEnv("
|
|
776
|
+
() => catalogProviderFromEnv("groq", env, "not-in-the-catalog"),
|
|
856
777
|
/context window unresolved/,
|
|
857
778
|
);
|
|
858
|
-
const provider = catalogProviderFromEnv("
|
|
779
|
+
const provider = catalogProviderFromEnv("groq", {
|
|
859
780
|
...env,
|
|
860
|
-
|
|
781
|
+
GROQ_API_KEY: "test-key",
|
|
861
782
|
PLURNK_PROVIDERS_CONTEXT_WINDOW: "8192",
|
|
862
783
|
PLURNK_PROVIDERS_REASONING: "adaptive",
|
|
863
784
|
}, "not-in-the-catalog");
|
|
@@ -901,9 +822,9 @@ test("{§provider-monetary-evidence} Models.dev is the only fallback rate table"
|
|
|
901
822
|
source: "Models.dev catalog rates",
|
|
902
823
|
});
|
|
903
824
|
|
|
904
|
-
const uncataloged = catalogProviderFromEnv("
|
|
825
|
+
const uncataloged = catalogProviderFromEnv("groq", {
|
|
905
826
|
...env,
|
|
906
|
-
|
|
827
|
+
GROQ_API_KEY: "test-key",
|
|
907
828
|
PLURNK_PROVIDERS_CONTEXT_WINDOW: "8192",
|
|
908
829
|
PLURNK_PROVIDERS_REASONING: "adaptive",
|
|
909
830
|
}, "not-in-the-catalog");
|
|
@@ -964,47 +885,3 @@ test("(#458) declared efforts union into the supported set under the models.dev-
|
|
|
964
885
|
assert.deepEqual(provider?.supportedReasoningPolicies, ["adaptive", "low", "high", "max"]);
|
|
965
886
|
});
|
|
966
887
|
|
|
967
|
-
test("{§provider-reasoning-style} {§google-reasoning-request}: a route-level thinking_config style asks Gemini behind Cloudflare's gateway for readable thoughts", async () => {
|
|
968
|
-
const bodies: Record<string, unknown>[] = [];
|
|
969
|
-
mock.method(globalThis, "fetch", async (_input: string | URL | Request, init?: RequestInit) => {
|
|
970
|
-
bodies.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
|
|
971
|
-
return new Response([
|
|
972
|
-
`data: ${JSON.stringify({
|
|
973
|
-
id: "gateway-gemini",
|
|
974
|
-
object: "chat.completion.chunk",
|
|
975
|
-
created: 1,
|
|
976
|
-
model: "gemini-3.8-flash",
|
|
977
|
-
choices: [{ index: 0, delta: { content: "done" }, finish_reason: "stop" }],
|
|
978
|
-
})}`,
|
|
979
|
-
"data: [DONE]",
|
|
980
|
-
].join("\n\n"), { headers: { "content-type": "text/event-stream" } });
|
|
981
|
-
});
|
|
982
|
-
const gatewayEnv = {
|
|
983
|
-
...env,
|
|
984
|
-
CLOUDFLARE_ACCOUNT_ID: "account",
|
|
985
|
-
CLOUDFLARE_API_KEY: "token",
|
|
986
|
-
PLURNK_PROVIDERS_CONTEXT_WINDOW: "1048576",
|
|
987
|
-
PLURNK_PROVIDERS_PROVIDER_CLOUDFLARE_WORKERS_AI_REASONING_STYLE: "effort_required",
|
|
988
|
-
PLURNK_PROVIDERS_REASONING_STYLE: "thinking_config",
|
|
989
|
-
};
|
|
990
|
-
const model = "google-ai-studio/gemini-3.8-flash";
|
|
991
|
-
const adaptive = catalogProviderFromEnv("cloudflare-workers-ai", { ...gatewayEnv, PLURNK_PROVIDERS_REASONING: "adaptive" }, model);
|
|
992
|
-
assert.deepEqual(adaptive?.supportedReasoningPolicies, ["adaptive", "low", "medium", "high"]);
|
|
993
|
-
await adaptive?.generate({ workerId: "gemini-adaptive", messages: [{ role: "user", content: "hello" }] });
|
|
994
|
-
const high = catalogProviderFromEnv("cloudflare-workers-ai", { ...gatewayEnv, PLURNK_PROVIDERS_REASONING: "high" }, model);
|
|
995
|
-
await high?.generate({ workerId: "gemini-high", messages: [{ role: "user", content: "hello" }] });
|
|
996
|
-
|
|
997
|
-
assert.deepEqual(bodies.map((body) => body.extra_body), [
|
|
998
|
-
{ google: { thinking_config: { include_thoughts: true } } },
|
|
999
|
-
{ google: { thinking_config: { include_thoughts: true, thinking_level: "high" } } },
|
|
1000
|
-
]);
|
|
1001
|
-
assert.ok(bodies.every((body) => !("reasoning_effort" in body)), "Gemini refuses reasoning_effort beside a thinking_config");
|
|
1002
|
-
assert.throws(
|
|
1003
|
-
() => catalogProviderFromEnv("cloudflare-workers-ai", { ...gatewayEnv, PLURNK_PROVIDERS_REASONING: "off" }, model),
|
|
1004
|
-
/reasoning policy 'off' is unsupported; supported policies: adaptive, low, medium, high/,
|
|
1005
|
-
);
|
|
1006
|
-
assert.throws(
|
|
1007
|
-
() => catalogProviderFromEnv("cloudflare-workers-ai", { ...gatewayEnv, PLURNK_PROVIDERS_REASONING_STYLE: "gemini" }, model),
|
|
1008
|
-
/cloudflare-workers-ai provider: PLURNK_PROVIDERS_REASONING_STYLE has invalid value "gemini"/,
|
|
1009
|
-
);
|
|
1010
|
-
});
|
package/src/catalogProvider.ts
CHANGED
|
@@ -53,7 +53,7 @@ const reasoningStyleFromEnv = (
|
|
|
53
53
|
if (value === undefined || value.length === 0) return undefined;
|
|
54
54
|
const styles: readonly ReasoningStyle[] = [
|
|
55
55
|
"none", "think", "include_reasoning", "effort",
|
|
56
|
-
"effort_explicit", "effort_required", "thinking_effort", "
|
|
56
|
+
"effort_explicit", "effort_required", "thinking_effort", "template", "anthropic",
|
|
57
57
|
];
|
|
58
58
|
if (!styles.includes(value as ReasoningStyle)) {
|
|
59
59
|
throw new Error(`${name} provider: ${key} has invalid value "${value}"`);
|
|
@@ -173,9 +173,8 @@ const supportedReasoningPolicies = ({
|
|
|
173
173
|
// not, and a word the template does not know fails loudly on the first request.
|
|
174
174
|
if (style === "template") return REASONING_POLICIES;
|
|
175
175
|
if (info !== undefined && info.reasoning !== true) return activationPolicies;
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
if (style === "thinking_config") return reasoningWithoutOff;
|
|
176
|
+
// The declared wire's whole vocabulary: a route that reasons unconditionally still
|
|
177
|
+
// admits only the levels the catalog advertises for it.
|
|
179
178
|
if (info?.reasoningOptions !== undefined) {
|
|
180
179
|
return catalogSupportedReasoningPolicies({ info, native, style, declared });
|
|
181
180
|
}
|
package/src/index.ts
CHANGED
package/src/sdkModels.test.ts
CHANGED
|
@@ -103,16 +103,6 @@ test("{§model-catalog-readiness}: an authenticated operator declaration reports
|
|
|
103
103
|
);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
test("{§provider-sdk-boundary} createSdkModel uses Models.dev provider facts and operator credentials", () => {
|
|
107
|
-
const sdk = createSdkModel("xai", "grok-build-0.1", { XAI_API_KEY: "test-key" });
|
|
108
|
-
assert.notEqual(sdk, null);
|
|
109
|
-
assert.equal(sdk?.catalog?.npm, "@ai-sdk/xai");
|
|
110
|
-
assert.notEqual(sdk?.languageModel, undefined);
|
|
111
|
-
assert.equal(sdk?.compatible, undefined);
|
|
112
|
-
assert.deepEqual(sdk?.cacheAffinity, { target: "header", name: "x-grok-conv-id" });
|
|
113
|
-
assert.notEqual(sdk?.normalizeCost, undefined);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
106
|
test("createSdkModel constructs Cerebras from Models.dev facts", () => {
|
|
117
107
|
const sdk = createSdkModel("cerebras", "gemma-4-31b", {
|
|
118
108
|
CEREBRAS_API_KEY: "test-key",
|
package/src/sdkModels.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { createGroq } from "@ai-sdk/groq";
|
|
|
7
7
|
import { createMistral } from "@ai-sdk/mistral";
|
|
8
8
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
9
9
|
import { createTogetherAI } from "@ai-sdk/togetherai";
|
|
10
|
-
import { createXai } from "@ai-sdk/xai";
|
|
11
10
|
import { createOpenRouter, type OpenRouterChatSettings } from "@openrouter/ai-sdk-provider";
|
|
12
11
|
import {
|
|
13
12
|
resolveModel,
|
|
@@ -393,15 +392,6 @@ export const createSdkModel = (
|
|
|
393
392
|
},
|
|
394
393
|
catalog,
|
|
395
394
|
};
|
|
396
|
-
case "@ai-sdk/xai":
|
|
397
|
-
return {
|
|
398
|
-
languageModel: createXai({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).chat(model),
|
|
399
|
-
...(catalog.id === "xai"
|
|
400
|
-
? { cacheAffinity: { target: "header" as const, name: "x-grok-conv-id" } }
|
|
401
|
-
: {}),
|
|
402
|
-
...(normalizeCost === undefined ? {} : { normalizeCost }),
|
|
403
|
-
catalog,
|
|
404
|
-
};
|
|
405
395
|
case "@ai-sdk/anthropic":
|
|
406
396
|
return {
|
|
407
397
|
languageModel: createAnthropic({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).languageModel(model),
|
package/src/types.ts
CHANGED
|
@@ -163,20 +163,9 @@ export interface TokenLogprob {
|
|
|
163
163
|
readonly top?: readonly TokenAlternative[];
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
// {§provider-encrypted-reasoning} `id` is provider detail identity; `subtype`
|
|
167
|
-
// is the provider's evidence-backed classification. Neither is a client entity
|
|
168
|
-
// correlation, so consumers must not substitute `id` for a message/tool-call ID.
|
|
169
|
-
export interface ProviderEncryptedReasoningItem {
|
|
170
|
-
readonly id: string | null;
|
|
171
|
-
readonly subtype: string;
|
|
172
|
-
readonly encrypted: ReadonlyArray<{ data: string; format: string | null }>;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
166
|
export interface ProviderAssistant<TFinish extends ProviderAttemptFinishReason = FinishReason> {
|
|
176
167
|
readonly content: string;
|
|
177
168
|
readonly reasoning: string | null;
|
|
178
|
-
// Encrypted reasoning remains distinct from readable `reasoning`.
|
|
179
|
-
readonly reasoningEncrypted?: ReadonlyArray<ProviderEncryptedReasoningItem>;
|
|
180
169
|
readonly finishReason: TFinish;
|
|
181
170
|
readonly model: string;
|
|
182
171
|
// Per-token logprobs, present only when PLURNK_PROVIDERS_TOP_LOGPROBS is set
|