@plurnk/plurnk-providers 1.16.4 → 1.17.0
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 +87 -200
- package/README.md +2 -1
- package/SPEC.md +73 -66
- package/dist/AiSdkProvider.d.ts +0 -1
- package/dist/AiSdkProvider.d.ts.map +1 -1
- package/dist/AiSdkProvider.js +25 -55
- package/dist/AiSdkProvider.js.map +1 -1
- package/dist/AiSdkRequestBody.d.ts +0 -1
- package/dist/AiSdkRequestBody.d.ts.map +1 -1
- package/dist/AiSdkRequestBody.js +1 -17
- package/dist/AiSdkRequestBody.js.map +1 -1
- package/dist/LeadingReasoning.d.ts +16 -0
- package/dist/LeadingReasoning.d.ts.map +1 -0
- package/dist/LeadingReasoning.js +81 -0
- package/dist/LeadingReasoning.js.map +1 -0
- package/dist/aiSdkTransport.d.ts +7 -0
- package/dist/aiSdkTransport.d.ts.map +1 -1
- package/dist/aiSdkTransport.js +19 -5
- package/dist/aiSdkTransport.js.map +1 -1
- package/dist/catalogProvider.d.ts +3 -1
- package/dist/catalogProvider.d.ts.map +1 -1
- package/dist/catalogProvider.js +24 -18
- package/dist/catalogProvider.js.map +1 -1
- package/dist/compatibleProvider.d.ts.map +1 -1
- package/dist/compatibleProvider.js +0 -3
- package/dist/compatibleProvider.js.map +1 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +0 -1
- package/dist/env.js.map +1 -1
- package/dist/errors.d.ts +6 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +46 -15
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/providerError.d.ts +1 -1
- package/dist/providerError.d.ts.map +1 -1
- package/dist/providerError.js +3 -0
- package/dist/providerError.js.map +1 -1
- package/dist/sdkModels.d.ts +1 -13
- package/dist/sdkModels.d.ts.map +1 -1
- package/dist/sdkModels.js +2 -94
- package/dist/sdkModels.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/models.md +90 -0
- package/package.json +6 -6
- package/src/AiSdkProvider.test.ts +99 -38
- package/src/AiSdkProvider.ts +24 -78
- package/src/AiSdkRequestBody.ts +1 -16
- package/src/LeadingReasoning.test.ts +34 -0
- package/src/LeadingReasoning.ts +87 -0
- package/src/ProviderRegistry.test.ts +19 -1
- package/src/aiSdkTransport.test.ts +56 -0
- package/src/aiSdkTransport.ts +31 -5
- package/src/boundaries.test.ts +1 -0
- package/src/catalogProvider.test.ts +110 -20
- package/src/catalogProvider.ts +40 -18
- package/src/compatibleProvider.ts +0 -3
- package/src/env.test.ts +1 -0
- package/src/env.ts +1 -2
- package/src/errors.test.ts +58 -7
- package/src/errors.ts +55 -18
- package/src/index.ts +2 -2
- package/src/providerError.ts +4 -0
- package/src/sdkModels.test.ts +1 -42
- package/src/sdkModels.ts +1 -123
- package/src/types.ts +8 -10
package/src/providerError.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type ProviderErrorKind =
|
|
|
7
7
|
| "network_failure"
|
|
8
8
|
| "deadline_exceeded"
|
|
9
9
|
| "model_refused"
|
|
10
|
+
| "request_rejected"
|
|
10
11
|
| "invalid_response"
|
|
11
12
|
| "unauthorized"
|
|
12
13
|
| "quota_exceeded"
|
|
@@ -16,6 +17,7 @@ export type ProviderErrorKind =
|
|
|
16
17
|
|
|
17
18
|
const defaultStatus = (kind: ProviderErrorKind): number => {
|
|
18
19
|
switch (kind) {
|
|
20
|
+
case "request_rejected": return 400;
|
|
19
21
|
case "unauthorized": return 401;
|
|
20
22
|
case "quota_exceeded": return 402;
|
|
21
23
|
case "capacity_exceeded": return 413;
|
|
@@ -36,6 +38,7 @@ const retryable = (kind: ProviderErrorKind): boolean => {
|
|
|
36
38
|
return true;
|
|
37
39
|
case "deadline_exceeded":
|
|
38
40
|
case "invalid_response":
|
|
41
|
+
case "request_rejected":
|
|
39
42
|
case "grammar_invalid":
|
|
40
43
|
case "capacity_exceeded":
|
|
41
44
|
case "resource_interrupted":
|
|
@@ -60,6 +63,7 @@ const buildProblem = (
|
|
|
60
63
|
deadline_exceeded: "deadline-exceeded",
|
|
61
64
|
model_refused: "model-refused",
|
|
62
65
|
invalid_response: "invalid-response",
|
|
66
|
+
request_rejected: "request-rejected",
|
|
63
67
|
unauthorized: "unauthorized",
|
|
64
68
|
quota_exceeded: "quota-exceeded",
|
|
65
69
|
grammar_invalid: "grammar-invalid",
|
package/src/sdkModels.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import { strict as assert } from "node:assert";
|
|
3
|
-
import { configuredProviderInfo,
|
|
3
|
+
import { configuredProviderInfo, createSdkModel, providerReadiness } from "./sdkModels.ts";
|
|
4
4
|
|
|
5
5
|
test("{§provider-fact-authority} one env declaration holds one credential name", () => {
|
|
6
6
|
assert.deepEqual(configuredProviderInfo("acme-cloud", {
|
|
@@ -301,44 +301,3 @@ test("createSdkModel fails clearly for a declared but unsupported SDK package",
|
|
|
301
301
|
/Models.dev declares unsupported AI SDK package @acme\/ai-sdk/,
|
|
302
302
|
);
|
|
303
303
|
});
|
|
304
|
-
|
|
305
|
-
test("{§provider-embedding-resolution} compatible catalog and declared routes share the standard embedding adapter", () => {
|
|
306
|
-
const cloudflare = createEmbeddingModel("cloudflare-workers-ai", "@cf/qwen/qwen3-embedding-0.6b", {
|
|
307
|
-
CLOUDFLARE_ACCOUNT_ID: "account",
|
|
308
|
-
CLOUDFLARE_API_KEY: "key",
|
|
309
|
-
});
|
|
310
|
-
assert.equal(cloudflare?.providerId, "cloudflare-workers-ai");
|
|
311
|
-
assert.equal(cloudflare?.embeddingModel.provider, "cloudflare-workers-ai.embedding");
|
|
312
|
-
assert.equal(cloudflare?.embeddingModel.modelId, "@cf/qwen/qwen3-embedding-0.6b");
|
|
313
|
-
|
|
314
|
-
const fireworks = createEmbeddingModel("fireworks-ai", "fireworks/qwen3-embedding-8b", {
|
|
315
|
-
FIREWORKS_API_KEY: "key",
|
|
316
|
-
});
|
|
317
|
-
assert.equal(fireworks?.providerId, "fireworks-ai");
|
|
318
|
-
assert.equal(fireworks?.embeddingModel.provider, "fireworks-ai.embedding");
|
|
319
|
-
|
|
320
|
-
const local = createEmbeddingModel("local-embed", "Qwen/Qwen3-Embedding-0.6B", {
|
|
321
|
-
PLURNK_PROVIDERS_PROVIDER_LOCAL_EMBED_NPM: "@ai-sdk/openai-compatible",
|
|
322
|
-
PLURNK_PROVIDERS_PROVIDER_LOCAL_EMBED_BASE_URL: "http://127.0.0.1:8080/v1",
|
|
323
|
-
});
|
|
324
|
-
assert.equal(local?.providerId, "local-embed");
|
|
325
|
-
assert.equal(local?.embeddingModel.provider, "local-embed.embedding");
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test("{§provider-embedding-resolution} OpenRouter uses its official embedding model", () => {
|
|
329
|
-
const resolved = createEmbeddingModel("openrouter", "openai/text-embedding-3-small", {
|
|
330
|
-
OPENROUTER_API_KEY: "key",
|
|
331
|
-
OPENROUTER_HTTP_REFERER: "https://github.com/plurnk/plurnk-service",
|
|
332
|
-
OPENROUTER_APP_TITLE: "Plurnk",
|
|
333
|
-
});
|
|
334
|
-
assert.equal(resolved?.providerId, "openrouter");
|
|
335
|
-
assert.equal(resolved?.embeddingModel.provider, "openrouter");
|
|
336
|
-
assert.equal(resolved?.embeddingModel.modelId, "openai/text-embedding-3-small");
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test("{§provider-embedding-resolution} a generation-only SDK fails before transport", () => {
|
|
340
|
-
assert.throws(
|
|
341
|
-
() => createEmbeddingModel("cerebras", "text-embedding", { CEREBRAS_API_KEY: "key" }),
|
|
342
|
-
/@ai-sdk\/cerebras does not expose an embedding model/,
|
|
343
|
-
);
|
|
344
|
-
});
|
package/src/sdkModels.ts
CHANGED
|
@@ -6,15 +6,12 @@ import { createGoogle } from "@ai-sdk/google";
|
|
|
6
6
|
import { createGroq } from "@ai-sdk/groq";
|
|
7
7
|
import { createMistral } from "@ai-sdk/mistral";
|
|
8
8
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
9
|
-
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
10
9
|
import { createTogetherAI } from "@ai-sdk/togetherai";
|
|
11
10
|
import { createXai } from "@ai-sdk/xai";
|
|
12
|
-
import type { EmbeddingModelV4, EmbeddingModelV4Result } from "@ai-sdk/provider";
|
|
13
11
|
import { createOpenRouter, type OpenRouterChatSettings } from "@openrouter/ai-sdk-provider";
|
|
14
12
|
import {
|
|
15
13
|
resolveModel,
|
|
16
14
|
isProviderCredentialName,
|
|
17
|
-
lookup,
|
|
18
15
|
lookupProvider,
|
|
19
16
|
providerCatalogSnapshot,
|
|
20
17
|
type ProviderInfo,
|
|
@@ -23,13 +20,10 @@ import {
|
|
|
23
20
|
Validator,
|
|
24
21
|
type ModelReadiness,
|
|
25
22
|
type ModelReadinessCause,
|
|
26
|
-
type ProviderRequestAccounting,
|
|
27
|
-
type ProviderUsage,
|
|
28
23
|
type ReasoningPolicy,
|
|
29
24
|
} from "@plurnk/plurnk-contracts";
|
|
30
25
|
import type { LanguageModel } from "ai";
|
|
31
|
-
import { providerCostNormalizer
|
|
32
|
-
import { estimateProviderCost, resolveProviderCost } from "./cost.ts";
|
|
26
|
+
import { providerCostNormalizer } from "./accounting.ts";
|
|
33
27
|
import type { AiSdkProviderOptions, CacheAffinity } from "./AiSdkProvider.ts";
|
|
34
28
|
import type { ProviderCostNormalizer } from "./types.ts";
|
|
35
29
|
|
|
@@ -47,17 +41,6 @@ export type SdkModel = {
|
|
|
47
41
|
readonly catalog: ProviderInfo | null;
|
|
48
42
|
};
|
|
49
43
|
|
|
50
|
-
export type EmbeddingModelResolution = {
|
|
51
|
-
readonly embeddingModel: EmbeddingModelV4;
|
|
52
|
-
readonly providerId: string;
|
|
53
|
-
readonly modelId: string;
|
|
54
|
-
readonly normalizeAccounting: (evidence: {
|
|
55
|
-
readonly outcome: "response" | "error";
|
|
56
|
-
readonly status?: number;
|
|
57
|
-
readonly result?: EmbeddingModelV4Result;
|
|
58
|
-
}) => ProviderRequestAccounting;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
44
|
const cacheControl = { type: "ephemeral" as const };
|
|
62
45
|
// The release generator admits only packages implemented by this package.
|
|
63
46
|
// Derive runtime readiness from that same pinned provider projection instead
|
|
@@ -485,108 +468,3 @@ export const createSdkModel = (
|
|
|
485
468
|
throw new Error(`${provider} provider: Models.dev declares unsupported AI SDK package ${catalog.npm}`);
|
|
486
469
|
}
|
|
487
470
|
};
|
|
488
|
-
|
|
489
|
-
// {§provider-embedding-resolution} Provider construction owns transport and
|
|
490
|
-
// authentication only. Model-space facts stay in the embedding profile.
|
|
491
|
-
export const createEmbeddingModel = (
|
|
492
|
-
provider: string,
|
|
493
|
-
model: string,
|
|
494
|
-
env: NodeJS.ProcessEnv,
|
|
495
|
-
baseUrlOverride?: string,
|
|
496
|
-
): EmbeddingModelResolution | null => {
|
|
497
|
-
const resolved = resolveSdkProvider(provider, env, baseUrlOverride);
|
|
498
|
-
if (resolved === null) return null;
|
|
499
|
-
const { catalog, url } = resolved;
|
|
500
|
-
const directCost = providerCostNormalizer(catalog.npm);
|
|
501
|
-
const modelCost = lookup(provider, model)?.cost ?? lookup(catalog.id, model)?.cost;
|
|
502
|
-
const rates = modelCost === undefined
|
|
503
|
-
? null
|
|
504
|
-
: {
|
|
505
|
-
input: modelCost.inputPer1M,
|
|
506
|
-
output: modelCost.outputPer1M,
|
|
507
|
-
...(modelCost.reasoningPer1M === undefined ? {} : { reasoning: modelCost.reasoningPer1M }),
|
|
508
|
-
...(modelCost.cacheReadPer1M === undefined ? {} : { cacheRead: modelCost.cacheReadPer1M }),
|
|
509
|
-
...(modelCost.cacheWritePer1M === undefined ? {} : { cacheWrite: modelCost.cacheWritePer1M }),
|
|
510
|
-
};
|
|
511
|
-
const normalizeAccounting: EmbeddingModelResolution["normalizeAccounting"] = (evidence) => {
|
|
512
|
-
const tokens = evidence.result?.usage?.tokens;
|
|
513
|
-
const usage: ProviderUsage | undefined = Number.isSafeInteger(tokens) && tokens! >= 0
|
|
514
|
-
? { inputTokens: tokens!, outputTokens: 0, totalTokens: tokens! }
|
|
515
|
-
: undefined;
|
|
516
|
-
const body = evidence.result?.response?.body;
|
|
517
|
-
const rawUsage = typeof body === "object" && body !== null && !Array.isArray(body)
|
|
518
|
-
? (body as Record<string, unknown>).usage
|
|
519
|
-
: undefined;
|
|
520
|
-
const direct = directCost?.({
|
|
521
|
-
providerMetadata: evidence.result?.providerMetadata,
|
|
522
|
-
usage: rawUsage,
|
|
523
|
-
response: { headers: evidence.result?.response?.headers },
|
|
524
|
-
});
|
|
525
|
-
const cost = resolveProviderCost(
|
|
526
|
-
direct,
|
|
527
|
-
estimateProviderCost(
|
|
528
|
-
usage,
|
|
529
|
-
rates,
|
|
530
|
-
`Models.dev ${catalog.id}/${model} rates`,
|
|
531
|
-
),
|
|
532
|
-
);
|
|
533
|
-
return validateProviderRequestAccounting({
|
|
534
|
-
provider: catalog.id,
|
|
535
|
-
model,
|
|
536
|
-
outcome: evidence.outcome,
|
|
537
|
-
...(evidence.status === undefined ? {} : { status: evidence.status }),
|
|
538
|
-
...(usage === undefined ? {} : { usage }),
|
|
539
|
-
cost,
|
|
540
|
-
});
|
|
541
|
-
};
|
|
542
|
-
const result = (embeddingModel: EmbeddingModelV4): EmbeddingModelResolution => ({
|
|
543
|
-
embeddingModel,
|
|
544
|
-
providerId: catalog.id,
|
|
545
|
-
modelId: model,
|
|
546
|
-
normalizeAccounting,
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
switch (catalog.npm) {
|
|
550
|
-
case "@ai-sdk/openai":
|
|
551
|
-
return result(createOpenAI({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).embedding(model));
|
|
552
|
-
case "@ai-sdk/google":
|
|
553
|
-
return result(createGoogle({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).embedding(model));
|
|
554
|
-
case "@ai-sdk/mistral":
|
|
555
|
-
return result(createMistral({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).embedding(model));
|
|
556
|
-
case "@ai-sdk/deepinfra":
|
|
557
|
-
return result(createDeepInfra({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).embeddingModel(model));
|
|
558
|
-
case "@ai-sdk/togetherai":
|
|
559
|
-
return result(createTogetherAI({ apiKey: requireApiKey(provider, env, catalog), baseURL: url }).embeddingModel(model));
|
|
560
|
-
case "@ai-sdk/amazon-bedrock":
|
|
561
|
-
return result(createAmazonBedrock({
|
|
562
|
-
region: env.AWS_REGION ?? env.AWS_DEFAULT_REGION,
|
|
563
|
-
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
564
|
-
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
565
|
-
sessionToken: env.AWS_SESSION_TOKEN,
|
|
566
|
-
apiKey: env.AWS_BEARER_TOKEN_BEDROCK,
|
|
567
|
-
baseURL: url,
|
|
568
|
-
}).embedding(model));
|
|
569
|
-
case "@openrouter/ai-sdk-provider":
|
|
570
|
-
return result(createOpenRouter({
|
|
571
|
-
apiKey: requireApiKey(provider, env, catalog),
|
|
572
|
-
baseURL: url,
|
|
573
|
-
headers: openRouterHeaders(provider, env, catalog),
|
|
574
|
-
}).textEmbeddingModel(model));
|
|
575
|
-
case "@ai-sdk/openai-compatible": {
|
|
576
|
-
if (url === undefined) {
|
|
577
|
-
throw new Error(`${provider} provider: Models.dev supplies no API URL and no base URL was configured`);
|
|
578
|
-
}
|
|
579
|
-
const keyNames = configuredKeyNames(provider, env, catalog);
|
|
580
|
-
const key = keyNames.length === 0 ? undefined : requireApiKey(provider, env, catalog);
|
|
581
|
-
return result(createOpenAICompatible({
|
|
582
|
-
name: provider,
|
|
583
|
-
baseURL: url,
|
|
584
|
-
...(key === undefined ? {} : { apiKey: key }),
|
|
585
|
-
}).embeddingModel(model));
|
|
586
|
-
}
|
|
587
|
-
default:
|
|
588
|
-
throw new Error(
|
|
589
|
-
`${provider} provider: ${catalog.npm} does not expose an embedding model through Plurnk's AI SDK adapter`,
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
};
|
package/src/types.ts
CHANGED
|
@@ -187,10 +187,10 @@ export interface ProviderAssistant<TFinish extends ProviderAttemptFinishReason =
|
|
|
187
187
|
readonly meanLogprob?: number;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// {§provider-grammar-transport} — transport evidence for an operator's grammar: the exact
|
|
191
|
+
// pre-projection response, where the projected content starts within it, and whether the
|
|
192
|
+
// grammar reached the wire. Evidence only; nothing grades it.
|
|
190
193
|
export interface GrammarEvidence {
|
|
191
|
-
// Exact pre-projection response represented by the provider. A generated
|
|
192
|
-
// rail's response root composes any template prefix before @plurnk/gbnf
|
|
193
|
-
// grades it. Offsets are Unicode code points, matching validator verdicts.
|
|
194
194
|
readonly input: string;
|
|
195
195
|
readonly contentStart: number;
|
|
196
196
|
readonly transported: boolean;
|
|
@@ -203,7 +203,7 @@ export interface ProviderResponse<TFinish extends ProviderAttemptFinishReason =
|
|
|
203
203
|
// failover that preceded this response. {§provider-request-accounting}
|
|
204
204
|
readonly accounting: readonly ProviderRequestAccounting[];
|
|
205
205
|
readonly capacity: ProviderRequestCapacity;
|
|
206
|
-
// {§
|
|
206
|
+
// {§provider-grammar-transport} — transport evidence; present only when a grammar was configured.
|
|
207
207
|
readonly grammarEvidence?: GrammarEvidence;
|
|
208
208
|
// Per-turn provider→client metadata bag: the backend's non-standard top-level
|
|
209
209
|
// response fields passed through verbatim. Monetary values carry their own
|
|
@@ -250,12 +250,10 @@ export interface Provider {
|
|
|
250
250
|
// Optional package-authored folksonomy evaluated by the consumer immediately
|
|
251
251
|
// before a provider emission attempt ({§plugin-attribution}).
|
|
252
252
|
attributions?(context: PluginAttributionContext): PluginAttribution;
|
|
253
|
-
// `grammar` is an optional GBNF string
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
// to constrain and which root variant to send is consumer policy
|
|
258
|
-
// ({§gbnf-response-observation}).
|
|
253
|
+
// `grammar` is an optional GBNF string, the operator's own file read by the consumer.
|
|
254
|
+
// Backends that support grammar-constrained sampling attach it verbatim; all others
|
|
255
|
+
// ignore it. The provider never chooses or modifies the grammar
|
|
256
|
+
// ({§provider-grammar-transport}).
|
|
259
257
|
//
|
|
260
258
|
// `maxOutputTokens` may tighten the provider's configured total output
|
|
261
259
|
// budget for this call. It includes visible output and hidden reasoning;
|