@substrat-run/model-providers 0.1.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/LICENSE +661 -0
- package/README.md +31 -0
- package/dist/catalog.d.ts +65 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +64 -0
- package/dist/catalog.js.map +1 -0
- package/dist/host.d.ts +21 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +37 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/list-models.d.ts +12 -0
- package/dist/list-models.d.ts.map +1 -0
- package/dist/list-models.js +57 -0
- package/dist/list-models.js.map +1 -0
- package/dist/model-pairs.d.ts +38 -0
- package/dist/model-pairs.d.ts.map +1 -0
- package/dist/model-pairs.js +56 -0
- package/dist/model-pairs.js.map +1 -0
- package/dist/pricing.d.ts +52 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +100 -0
- package/dist/pricing.js.map +1 -0
- package/dist/provider-errors.d.ts +27 -0
- package/dist/provider-errors.d.ts.map +1 -0
- package/dist/provider-errors.js +74 -0
- package/dist/provider-errors.js.map +1 -0
- package/dist/providers.d.ts +116 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +193 -0
- package/dist/providers.js.map +1 -0
- package/dist/qwen-cache.d.ts +42 -0
- package/dist/qwen-cache.d.ts.map +1 -0
- package/dist/qwen-cache.js +118 -0
- package/dist/qwen-cache.js.map +1 -0
- package/dist/rate-card.generated.d.ts +16 -0
- package/dist/rate-card.generated.d.ts.map +1 -0
- package/dist/rate-card.generated.js +80 -0
- package/dist/rate-card.generated.js.map +1 -0
- package/dist/request.d.ts +10 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +44 -0
- package/dist/request.js.map +1 -0
- package/dist/resolve.d.ts +62 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +121 -0
- package/dist/resolve.js.map +1 -0
- package/dist/spec.d.ts +21 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +27 -0
- package/dist/spec.js.map +1 -0
- package/package.json +42 -0
package/dist/pricing.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List-price math over the generated rate card — QUANTITIES × LIST RATES, and
|
|
3
|
+
* nothing more. Margin is vocabulary: the builder studio charges list × 1.2
|
|
4
|
+
* for its own spend, the platform will charge list × (1 + its margin) for
|
|
5
|
+
* inference it provides (#1054), and the metering engine owns neither (D-E).
|
|
6
|
+
* Each of those is a one-line wrapper over `listCostOfSteps`; the tier and
|
|
7
|
+
* cache semantics below are the part that must not be re-derived twice.
|
|
8
|
+
*
|
|
9
|
+
* The rate card itself is GENERATED (rate-card.generated.ts) from models.dev ×
|
|
10
|
+
* LiteLLM with a failing cross-check, and reviewed as a PR diff — the billing
|
|
11
|
+
* checkpoint of docs/architecture/builder/harness.md §2.2. This file owns the math:
|
|
12
|
+
*
|
|
13
|
+
* - Tier selection is ALL-OR-NOTHING by a request's total input tokens
|
|
14
|
+
* (DashScope/Anthropic threshold semantics): the whole request bills at
|
|
15
|
+
* the tier its input lands in. That makes pricing inherently PER-STEP — a
|
|
16
|
+
* turn is dozens of requests, and summing them before selecting a tier
|
|
17
|
+
* would land the sum in a tier no single request reached. `listCostOfSteps`
|
|
18
|
+
* is the correct path; `listCostOf` over totals is the legacy fallback for
|
|
19
|
+
* entries recorded before per-step usage existed.
|
|
20
|
+
* - Cache reads/writes bill at their own rates when the card has them; a
|
|
21
|
+
* missing cache rate bills that slice at the plain input rate — the
|
|
22
|
+
* provider's own behavior when it publishes no cache pricing. A discount
|
|
23
|
+
* is only ever applied when a catalog actually states one.
|
|
24
|
+
*
|
|
25
|
+
* Decimal strings end to end (K-14). Token counts convert to exact millions
|
|
26
|
+
* (an integer / 1e6 is at most 6 dp), so mulDecimal's 6 dp micro-unit scale
|
|
27
|
+
* never truncates — which is also why rates are multiplied per-MILLION, never
|
|
28
|
+
* pre-divided to a per-token rate ($0.19/1M per-token would be 1.9e-7, below
|
|
29
|
+
* the 6 dp floor).
|
|
30
|
+
*/
|
|
31
|
+
import { addDecimal, mulDecimal } from '@substrat-run/contracts';
|
|
32
|
+
import { RATE_CARD } from './rate-card.generated.js';
|
|
33
|
+
import { parseModelSpec } from './spec.js';
|
|
34
|
+
/** The card entry for a `provider:model`, by longest id prefix; null = unpriced. */
|
|
35
|
+
export function rateFor(model) {
|
|
36
|
+
if (model.indexOf(':') === -1)
|
|
37
|
+
return null;
|
|
38
|
+
const { provider, modelId } = parseModelSpec(model);
|
|
39
|
+
let best = null;
|
|
40
|
+
for (const rate of RATE_CARD) {
|
|
41
|
+
if (rate.provider !== provider || !modelId.startsWith(rate.idPrefix))
|
|
42
|
+
continue;
|
|
43
|
+
if (!best || rate.idPrefix.length > best.idPrefix.length)
|
|
44
|
+
best = rate;
|
|
45
|
+
}
|
|
46
|
+
return best;
|
|
47
|
+
}
|
|
48
|
+
/** An integer token count as an exact millions decimal (12_345 → '0.012345'). */
|
|
49
|
+
function tokensInMillions(tokens) {
|
|
50
|
+
const s = String(Math.max(0, Math.round(tokens))).padStart(7, '0');
|
|
51
|
+
const frac = s.slice(-6).replace(/0+$/, '');
|
|
52
|
+
return `${s.slice(0, -6)}${frac ? `.${frac}` : ''}`;
|
|
53
|
+
}
|
|
54
|
+
/** All-or-nothing: first tier whose bound holds the request's total input. */
|
|
55
|
+
function tierFor(rate, inputTokens) {
|
|
56
|
+
for (const tier of rate.tiers) {
|
|
57
|
+
if (tier.upToInputTokens === null || inputTokens <= tier.upToInputTokens)
|
|
58
|
+
return tier;
|
|
59
|
+
}
|
|
60
|
+
// Unreachable for a well-formed card (last tier is unbounded); the generator
|
|
61
|
+
// guarantees it, but a fallback beats a throw in the billing path.
|
|
62
|
+
return rate.tiers[rate.tiers.length - 1];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Price a turn from its per-request steps — the correct path (see header).
|
|
66
|
+
* USD list price as a decimal string; null when the model has no rate card
|
|
67
|
+
* entry — unpriced, never guessed.
|
|
68
|
+
*/
|
|
69
|
+
export function listCostOfSteps(model, steps) {
|
|
70
|
+
const rate = rateFor(model);
|
|
71
|
+
if (!rate)
|
|
72
|
+
return null;
|
|
73
|
+
let listUsd = '0';
|
|
74
|
+
for (const step of steps) {
|
|
75
|
+
const tier = tierFor(rate, step.inputTokens);
|
|
76
|
+
const cacheRead = Math.min(step.cachedInputTokens ?? 0, step.inputTokens);
|
|
77
|
+
const cacheWrite = Math.min(step.cacheWriteTokens ?? 0, step.inputTokens - cacheRead);
|
|
78
|
+
const plainInput = step.inputTokens - cacheRead - cacheWrite;
|
|
79
|
+
listUsd = addDecimal(listUsd, mulDecimal(tokensInMillions(plainInput), tier.inputPer1M));
|
|
80
|
+
listUsd = addDecimal(listUsd, mulDecimal(tokensInMillions(step.outputTokens), tier.outputPer1M));
|
|
81
|
+
if (cacheRead > 0) {
|
|
82
|
+
listUsd = addDecimal(listUsd, mulDecimal(tokensInMillions(cacheRead), tier.cacheReadPer1M ?? tier.inputPer1M));
|
|
83
|
+
}
|
|
84
|
+
if (cacheWrite > 0) {
|
|
85
|
+
listUsd = addDecimal(listUsd, mulDecimal(tokensInMillions(cacheWrite), tier.cacheWritePer1M ?? tier.inputPer1M));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return listUsd;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Legacy totals path: ledger entries recorded before per-step usage carry only
|
|
92
|
+
* turn totals, with no step split and no cache breakdown. Priced as ONE
|
|
93
|
+
* pseudo-request — for a single-tier card this equals the per-step price
|
|
94
|
+
* exactly; for a tiered card it can only over-select the tier, so a read side
|
|
95
|
+
* uses recorded costs whenever they exist and falls back here only for old rows.
|
|
96
|
+
*/
|
|
97
|
+
export function listCostOf(model, inputTokens, outputTokens) {
|
|
98
|
+
return listCostOfSteps(model, [{ inputTokens, outputTokens }]);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-failure explanations — WORKER-SAFE (no node imports), shared by the
|
|
3
|
+
* hosted DO and the local CLI.
|
|
4
|
+
*
|
|
5
|
+
* Exists because every provider failure this project has actually hit rendered
|
|
6
|
+
* as something misleading before it rendered as itself: a region-scoped key as
|
|
7
|
+
* "invalid", a missing endpoint as a bad token, and an exhausted weekly quota
|
|
8
|
+
* as "API key is invalid" — that last one is the incident that forced this
|
|
9
|
+
* file. The rule: name the REAL failure class first, keep the provider's own
|
|
10
|
+
* message (it often carries the useful detail, like a quota reset time), and
|
|
11
|
+
* always end with what the user can do next.
|
|
12
|
+
*/
|
|
13
|
+
export interface ErrorFacts {
|
|
14
|
+
readonly statusCode?: number;
|
|
15
|
+
readonly code?: string;
|
|
16
|
+
readonly url?: string;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
}
|
|
19
|
+
/** Best-effort extraction from an AI SDK APICallError-shaped object. */
|
|
20
|
+
export declare function errorFacts(err: unknown): ErrorFacts;
|
|
21
|
+
/**
|
|
22
|
+
* Turn a provider error into an actionable message, or null to keep the raw
|
|
23
|
+
* one. `where` distinguishes the credential's home so the "what next" step is
|
|
24
|
+
* real: 'hosted' → worker secrets; 'local' → apps/builder/.env.
|
|
25
|
+
*/
|
|
26
|
+
export declare function explainProviderFailure(provider: string, err: unknown, where: 'hosted' | 'local'): string | null;
|
|
27
|
+
//# sourceMappingURL=provider-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-errors.d.ts","sourceRoot":"","sources":["../src/provider-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,QAAQ,GAAG,OAAO,GACvB,MAAM,GAAG,IAAI,CAqDf"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-failure explanations — WORKER-SAFE (no node imports), shared by the
|
|
3
|
+
* hosted DO and the local CLI.
|
|
4
|
+
*
|
|
5
|
+
* Exists because every provider failure this project has actually hit rendered
|
|
6
|
+
* as something misleading before it rendered as itself: a region-scoped key as
|
|
7
|
+
* "invalid", a missing endpoint as a bad token, and an exhausted weekly quota
|
|
8
|
+
* as "API key is invalid" — that last one is the incident that forced this
|
|
9
|
+
* file. The rule: name the REAL failure class first, keep the provider's own
|
|
10
|
+
* message (it often carries the useful detail, like a quota reset time), and
|
|
11
|
+
* always end with what the user can do next.
|
|
12
|
+
*/
|
|
13
|
+
/** Best-effort extraction from an AI SDK APICallError-shaped object. */
|
|
14
|
+
export function errorFacts(err) {
|
|
15
|
+
const e = err;
|
|
16
|
+
const data = e?.['data']?.['error'];
|
|
17
|
+
return {
|
|
18
|
+
statusCode: typeof e?.['statusCode'] === 'number' ? e['statusCode'] : undefined,
|
|
19
|
+
code: typeof data?.['code'] === 'string' ? data['code'] : undefined,
|
|
20
|
+
url: typeof e?.['url'] === 'string' ? e['url'] : undefined,
|
|
21
|
+
message: err instanceof Error ? err.message : String(err),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Turn a provider error into an actionable message, or null to keep the raw
|
|
26
|
+
* one. `where` distinguishes the credential's home so the "what next" step is
|
|
27
|
+
* real: 'hosted' → worker secrets; 'local' → apps/builder/.env.
|
|
28
|
+
*/
|
|
29
|
+
export function explainProviderFailure(provider, err, where) {
|
|
30
|
+
const f = errorFacts(err);
|
|
31
|
+
const fix = where === 'hosted'
|
|
32
|
+
? `worker secret (secrets/platform.prod.env → BUILDER_* → secrets.mjs push --only builder)`
|
|
33
|
+
: `apps/builder/.env`;
|
|
34
|
+
// Quota exhaustion is NOT an invalid key — the misread that forced this file.
|
|
35
|
+
if (f.code === 'insufficient_quota' || /quota .*(exhaust|exceed)|insufficient_quota/i.test(f.message)) {
|
|
36
|
+
return [
|
|
37
|
+
`${provider}: quota exhausted — the key is fine, the plan's budget is spent.`,
|
|
38
|
+
` Provider says: ${f.message}`,
|
|
39
|
+
` Next: wait for the stated reset, top up the plan, or switch model in the picker`,
|
|
40
|
+
` (another provider keeps working meanwhile).`,
|
|
41
|
+
].join('\n');
|
|
42
|
+
}
|
|
43
|
+
// Rate limit ≠ quota ≠ auth: transient, retry-shaped.
|
|
44
|
+
if (f.statusCode === 429) {
|
|
45
|
+
return [
|
|
46
|
+
`${provider}: rate limited (HTTP 429) — transient, not a key problem.`,
|
|
47
|
+
` Provider says: ${f.message}`,
|
|
48
|
+
` Next: retry the turn in a moment.`,
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
if (f.statusCode === 401 || f.statusCode === 403) {
|
|
52
|
+
const lines = [
|
|
53
|
+
`${provider}: credential rejected (HTTP ${f.statusCode}).`,
|
|
54
|
+
` Provider says: ${f.message}`,
|
|
55
|
+
];
|
|
56
|
+
if (provider === 'qwen') {
|
|
57
|
+
lines.push(` DashScope keys are REGION- and WORKSPACE-scoped — the key is often fine,`, ` just minted for a different endpoint than ${f.url || 'the one configured'}.`, ` Check the key and DASHSCOPE_BASE_URL agree (${fix}).`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
lines.push(` Check the key in the ${fix} — no stray quotes or whitespace.`);
|
|
61
|
+
}
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
if (/model.{0,4}not.{0,4}exist|model_not_found|does not exist/i.test(f.message)) {
|
|
65
|
+
return [
|
|
66
|
+
`${provider}: this endpoint does not serve that model id.`,
|
|
67
|
+
` Provider says: ${f.message}`,
|
|
68
|
+
` Next: pick a different model in the picker — workspace/regional plans`,
|
|
69
|
+
` expose their own catalogs, so an id valid elsewhere can be absent here.`,
|
|
70
|
+
].join('\n');
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=provider-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-errors.js","sourceRoot":"","sources":["../src/provider-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,GAAY;IACtC,MAAM,CAAC,GAAG,GAAqC,CAAC;IAChD,MAAM,IAAI,GAAI,CAAC,EAAE,CAAC,MAAM,CAAyC,EAAE,CAAC,OAAO,CAE/D,CAAC;IACb,OAAO;QACN,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,YAAY,CAAY,CAAC,CAAC,CAAC,SAAS;QAC3F,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,MAAM,CAAY,CAAC,CAAC,CAAC,SAAS;QAC/E,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,KAAK,CAAY,CAAC,CAAC,CAAC,SAAS;QACtE,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;KACzD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACrC,QAAgB,EAChB,GAAY,EACZ,KAAyB;IAEzB,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,GAAG,GACR,KAAK,KAAK,QAAQ;QACjB,CAAC,CAAC,yFAAyF;QAC3F,CAAC,CAAC,mBAAmB,CAAC;IAExB,8EAA8E;IAC9E,IAAI,CAAC,CAAC,IAAI,KAAK,oBAAoB,IAAI,8CAA8C,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACvG,OAAO;YACN,GAAG,QAAQ,kEAAkE;YAC7E,oBAAoB,CAAC,CAAC,OAAO,EAAE;YAC/B,mFAAmF;YACnF,+CAA+C;SAC/C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,sDAAsD;IACtD,IAAI,CAAC,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;QAC1B,OAAO;YACN,GAAG,QAAQ,2DAA2D;YACtE,oBAAoB,CAAC,CAAC,OAAO,EAAE;YAC/B,qCAAqC;SACrC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,IAAI,CAAC,CAAC,UAAU,KAAK,GAAG,IAAI,CAAC,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG;YACb,GAAG,QAAQ,+BAA+B,CAAC,CAAC,UAAU,IAAI;YAC1D,oBAAoB,CAAC,CAAC,OAAO,EAAE;SAC/B,CAAC;QACF,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CACT,4EAA4E,EAC5E,+CAA+C,CAAC,CAAC,GAAG,IAAI,oBAAoB,GAAG,EAC/E,iDAAiD,GAAG,IAAI,CACxD,CAAC;QACH,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,0BAA0B,GAAG,mCAAmC,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,2DAA2D,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACjF,OAAO;YACN,GAAG,QAAQ,+CAA+C;YAC1D,oBAAoB,CAAC,CAAC,OAAO,EAAE;YAC/B,yEAAyE;YACzE,2EAA2E;SAC3E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider table — "any LLM" is expressed here and nowhere else (D-49, D-50,
|
|
3
|
+
* #1054).
|
|
4
|
+
*
|
|
5
|
+
* A `provider:model` string names a row; the row says how to reach the
|
|
6
|
+
* endpoint, which environment keys carry its credential, where inference runs
|
|
7
|
+
* (the D-53/D-54 subprocessor disclosure) and what to suggest in a picker.
|
|
8
|
+
* Adding a provider is one row. Nothing downstream — the builder's generator,
|
|
9
|
+
* a vertical's assistant, the pricing ledger, a settings screen — knows which
|
|
10
|
+
* row ran. If that ever stops being true, the seam has leaked.
|
|
11
|
+
*
|
|
12
|
+
* Two shapes:
|
|
13
|
+
* - `direct` — an AI SDK provider package with a `createX` factory. The
|
|
14
|
+
* package is NOT imported here: a Node host loads it
|
|
15
|
+
* dynamically, a Worker host imports it statically, and both
|
|
16
|
+
* hand the factory to `createModel`. That is what keeps this
|
|
17
|
+
* file loadable everywhere.
|
|
18
|
+
* - `compatible` — an OpenAI-compatible HTTP endpoint, built with
|
|
19
|
+
* `createOpenAICompatible({ baseURL, apiKey })`. One package
|
|
20
|
+
* covers DashScope/Qwen, Cloudflare, Scaleway, Ollama, vLLM,
|
|
21
|
+
* LM Studio, OpenRouter and anything else speaking that
|
|
22
|
+
* dialect, which is why most rows land here.
|
|
23
|
+
*
|
|
24
|
+
* Cloudflare is deliberately an ordinary `compatible` row. Its gateway features
|
|
25
|
+
* (unified billing, per-request metadata, spend limits) are extras a host may
|
|
26
|
+
* layer on THAT row — never a reason for a second code path.
|
|
27
|
+
*/
|
|
28
|
+
export interface HostingSpec {
|
|
29
|
+
/** Who operates the inference endpoint. */
|
|
30
|
+
readonly vendor: string;
|
|
31
|
+
/**
|
|
32
|
+
* Where, as precisely as the endpoint tells us. A function decodes it from the
|
|
33
|
+
* effective host when one vendor serves several regions (DashScope).
|
|
34
|
+
*/
|
|
35
|
+
readonly location: string | ((host: string) => string);
|
|
36
|
+
/**
|
|
37
|
+
* True for inference that never leaves the machine the host runs on. A hosted
|
|
38
|
+
* catalog excludes these; the disclosure sentence changes.
|
|
39
|
+
*/
|
|
40
|
+
readonly local?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface ProviderBase {
|
|
43
|
+
readonly hosting: HostingSpec;
|
|
44
|
+
/** Env var holding the credential. Absent for local runtimes that need none. */
|
|
45
|
+
readonly envVar?: string;
|
|
46
|
+
/** Picker suggestions for a provider with no listable catalog; free text still wins. */
|
|
47
|
+
readonly suggested?: readonly string[];
|
|
48
|
+
/** One line for `--help`-style listings. */
|
|
49
|
+
readonly note?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface DirectProvider extends ProviderBase {
|
|
52
|
+
readonly kind: 'direct';
|
|
53
|
+
/** The AI SDK package a host imports. */
|
|
54
|
+
readonly pkg: string;
|
|
55
|
+
/** Default export, used by a Node host when no base-URL override is set. */
|
|
56
|
+
readonly factory: string;
|
|
57
|
+
/** `createX` form — what `createModel` is handed. */
|
|
58
|
+
readonly createFactory: string;
|
|
59
|
+
/** Env var holding a base-URL override. Absent ⇒ endpoint is not overridable. */
|
|
60
|
+
readonly baseUrlEnv?: string;
|
|
61
|
+
/** The host requests go to when no override is set — for the disclosure. */
|
|
62
|
+
readonly defaultHost: string;
|
|
63
|
+
}
|
|
64
|
+
export interface CompatibleProvider extends ProviderBase {
|
|
65
|
+
readonly kind: 'compatible';
|
|
66
|
+
/** Default endpoint; empty when the endpoint is account-scoped and must be set. */
|
|
67
|
+
readonly baseUrl: string;
|
|
68
|
+
/** Env var overriding the endpoint — self-hosted, regional, or account-scoped. */
|
|
69
|
+
readonly baseUrlEnv: string;
|
|
70
|
+
/**
|
|
71
|
+
* How the endpoint lists its models. `openai` = `GET {base}/models`;
|
|
72
|
+
* `cloudflare-catalog` = the account-level `…/ai/models/search` (Workers AI's
|
|
73
|
+
* compatible surface answers 405 to `/models`).
|
|
74
|
+
*/
|
|
75
|
+
readonly catalog?: 'openai' | 'cloudflare-catalog';
|
|
76
|
+
/**
|
|
77
|
+
* A fetch wrapper the row needs at the wire — DashScope's explicit
|
|
78
|
+
* context-cache markers. Named, not imported, so the table stays data.
|
|
79
|
+
*/
|
|
80
|
+
readonly wire?: 'qwen-cache';
|
|
81
|
+
/**
|
|
82
|
+
* Per-request extras the row's endpoint understands — Cloudflare's AI Gateway
|
|
83
|
+
* headers (attribution metadata, payload retention off, gateway selection). Named
|
|
84
|
+
* here and dispatched in request.ts, never on the provider's name.
|
|
85
|
+
*/
|
|
86
|
+
readonly request?: 'cloudflare-gateway';
|
|
87
|
+
}
|
|
88
|
+
export type ProviderSpec = DirectProvider | CompatibleProvider;
|
|
89
|
+
declare const SENT = "sent to this provider";
|
|
90
|
+
export declare const PROVIDERS: Readonly<Record<string, ProviderSpec>>;
|
|
91
|
+
/**
|
|
92
|
+
* Ollama's endpoint is overridable, so "this machine" is a claim about the ENDPOINT, not
|
|
93
|
+
* about the row. Pointed at a GPU box on the LAN it is somebody else's machine, and the
|
|
94
|
+
* disclosure says so rather than repeating the default.
|
|
95
|
+
*/
|
|
96
|
+
export declare function ollamaLocation(host: string): string;
|
|
97
|
+
/** DashScope endpoints encode region three different ways; decode them all. */
|
|
98
|
+
export declare function qwenLocation(host: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* The row for a provider name, or `undefined` — the ONLY way to look one up.
|
|
101
|
+
*
|
|
102
|
+
* Never `PROVIDERS[name]` directly. The table is an object literal, so a name that
|
|
103
|
+
* happens to be an `Object.prototype` member — `constructor`, `toString`, `valueOf` —
|
|
104
|
+
* resolves to a truthy INHERITED value, sails past an `if (!row)` guard, and reaches
|
|
105
|
+
* `row.hosting.local` as a TypeError instead of the "unknown provider" error the caller
|
|
106
|
+
* is owed. That is reachable from config, not just from a typo: a model spec is a
|
|
107
|
+
* per-install setting (a desk's `TICKET0_MODEL`), so `constructor:x` is a value a
|
|
108
|
+
* settings field can hold.
|
|
109
|
+
*/
|
|
110
|
+
export declare function providerRow(name: string): ProviderSpec | undefined;
|
|
111
|
+
/** One line per provider, for a CLI's "known providers" listing. */
|
|
112
|
+
export declare function knownProviders(): string;
|
|
113
|
+
/** The credential env var of a provider, or null when it needs none / is unknown. */
|
|
114
|
+
export declare function credentialEnvVar(provider: string): string | null;
|
|
115
|
+
export { SENT as DATA_SENT_PHRASE };
|
|
116
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,MAAM,WAAW,WAAW;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IACvD;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,UAAU,YAAY;IACrB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,wFAAwF;IACxF,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAe,SAAQ,YAAY;IACnD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,yCAAyC;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACvD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,oBAAoB,CAAC;IACnD;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CACxC;AAED,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,kBAAkB,CAAC;AAE/D,QAAA,MAAM,IAAI,0BAA0B,CAAC;AAErC,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAyH5D,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAElE;AAED,oEAAoE;AACpE,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEhE;AAED,OAAO,EAAE,IAAI,IAAI,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider table — "any LLM" is expressed here and nowhere else (D-49, D-50,
|
|
3
|
+
* #1054).
|
|
4
|
+
*
|
|
5
|
+
* A `provider:model` string names a row; the row says how to reach the
|
|
6
|
+
* endpoint, which environment keys carry its credential, where inference runs
|
|
7
|
+
* (the D-53/D-54 subprocessor disclosure) and what to suggest in a picker.
|
|
8
|
+
* Adding a provider is one row. Nothing downstream — the builder's generator,
|
|
9
|
+
* a vertical's assistant, the pricing ledger, a settings screen — knows which
|
|
10
|
+
* row ran. If that ever stops being true, the seam has leaked.
|
|
11
|
+
*
|
|
12
|
+
* Two shapes:
|
|
13
|
+
* - `direct` — an AI SDK provider package with a `createX` factory. The
|
|
14
|
+
* package is NOT imported here: a Node host loads it
|
|
15
|
+
* dynamically, a Worker host imports it statically, and both
|
|
16
|
+
* hand the factory to `createModel`. That is what keeps this
|
|
17
|
+
* file loadable everywhere.
|
|
18
|
+
* - `compatible` — an OpenAI-compatible HTTP endpoint, built with
|
|
19
|
+
* `createOpenAICompatible({ baseURL, apiKey })`. One package
|
|
20
|
+
* covers DashScope/Qwen, Cloudflare, Scaleway, Ollama, vLLM,
|
|
21
|
+
* LM Studio, OpenRouter and anything else speaking that
|
|
22
|
+
* dialect, which is why most rows land here.
|
|
23
|
+
*
|
|
24
|
+
* Cloudflare is deliberately an ordinary `compatible` row. Its gateway features
|
|
25
|
+
* (unified billing, per-request metadata, spend limits) are extras a host may
|
|
26
|
+
* layer on THAT row — never a reason for a second code path.
|
|
27
|
+
*/
|
|
28
|
+
import { isLoopbackHost } from './host.js';
|
|
29
|
+
const SENT = 'sent to this provider';
|
|
30
|
+
export const PROVIDERS = {
|
|
31
|
+
anthropic: {
|
|
32
|
+
kind: 'direct',
|
|
33
|
+
pkg: '@ai-sdk/anthropic',
|
|
34
|
+
factory: 'anthropic',
|
|
35
|
+
createFactory: 'createAnthropic',
|
|
36
|
+
baseUrlEnv: 'ANTHROPIC_BASE_URL',
|
|
37
|
+
envVar: 'ANTHROPIC_API_KEY',
|
|
38
|
+
defaultHost: 'api.anthropic.com',
|
|
39
|
+
hosting: { vendor: 'Anthropic', location: 'United States' },
|
|
40
|
+
suggested: ['claude-opus-5', 'claude-sonnet-5', 'claude-haiku-4-5', 'claude-fable-5'],
|
|
41
|
+
note: 'e.g. anthropic:claude-opus-5',
|
|
42
|
+
},
|
|
43
|
+
openai: {
|
|
44
|
+
kind: 'direct',
|
|
45
|
+
pkg: '@ai-sdk/openai',
|
|
46
|
+
factory: 'openai',
|
|
47
|
+
createFactory: 'createOpenAI',
|
|
48
|
+
baseUrlEnv: 'OPENAI_BASE_URL',
|
|
49
|
+
envVar: 'OPENAI_API_KEY',
|
|
50
|
+
defaultHost: 'api.openai.com',
|
|
51
|
+
hosting: { vendor: 'OpenAI', location: 'United States' },
|
|
52
|
+
},
|
|
53
|
+
google: {
|
|
54
|
+
kind: 'direct',
|
|
55
|
+
pkg: '@ai-sdk/google',
|
|
56
|
+
factory: 'google',
|
|
57
|
+
createFactory: 'createGoogleGenerativeAI',
|
|
58
|
+
envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
59
|
+
defaultHost: 'generativelanguage.googleapis.com',
|
|
60
|
+
hosting: { vendor: 'Google', location: 'United States / global' },
|
|
61
|
+
},
|
|
62
|
+
mistral: {
|
|
63
|
+
kind: 'direct',
|
|
64
|
+
pkg: '@ai-sdk/mistral',
|
|
65
|
+
factory: 'mistral',
|
|
66
|
+
createFactory: 'createMistral',
|
|
67
|
+
envVar: 'MISTRAL_API_KEY',
|
|
68
|
+
defaultHost: 'api.mistral.ai',
|
|
69
|
+
hosting: { vendor: 'Mistral', location: 'European Union (France)' },
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* Alibaba Model Studio (DashScope), OpenAI-compatible mode. Defaults to the
|
|
73
|
+
* international endpoint; set DASHSCOPE_BASE_URL for the China-mainland
|
|
74
|
+
* (`https://dashscope.aliyuncs.com/compatible-mode/v1`), US, or a regional
|
|
75
|
+
* workspace endpoint. Keys are region- and workspace-scoped, which is why the
|
|
76
|
+
* location is decoded from the host rather than stated.
|
|
77
|
+
*/
|
|
78
|
+
qwen: {
|
|
79
|
+
kind: 'compatible',
|
|
80
|
+
baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
|
|
81
|
+
baseUrlEnv: 'DASHSCOPE_BASE_URL',
|
|
82
|
+
envVar: 'DASHSCOPE_API_KEY',
|
|
83
|
+
catalog: 'openai',
|
|
84
|
+
wire: 'qwen-cache',
|
|
85
|
+
hosting: { vendor: 'Alibaba Cloud (Model Studio)', location: qwenLocation },
|
|
86
|
+
note: 'e.g. qwen:qwen3-coder-plus · region override via DASHSCOPE_BASE_URL',
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Cloudflare Workers AI + AI Gateway, OpenAI-compatible mode. The endpoint is
|
|
90
|
+
* account-scoped (`…/accounts/<id>/ai/v1`), so there is no static default —
|
|
91
|
+
* set CLOUDFLARE_AI_BASE_URL with the account id. Model ids keep their full
|
|
92
|
+
* catalog prefix: `@cf/…` runs on Cloudflare's own network, a bare
|
|
93
|
+
* `vendor/model` slug (e.g. `openai/gpt-4.1-mini`) is partner-served under
|
|
94
|
+
* unified billing. Deliberately NOT wrangler's CLOUDFLARE_API_TOKEN, so an
|
|
95
|
+
* ambient deploy token is never silently used for inference.
|
|
96
|
+
*/
|
|
97
|
+
cloudflare: {
|
|
98
|
+
kind: 'compatible',
|
|
99
|
+
baseUrl: '',
|
|
100
|
+
baseUrlEnv: 'CLOUDFLARE_AI_BASE_URL',
|
|
101
|
+
envVar: 'CLOUDFLARE_AI_API_TOKEN',
|
|
102
|
+
catalog: 'cloudflare-catalog',
|
|
103
|
+
request: 'cloudflare-gateway',
|
|
104
|
+
hosting: {
|
|
105
|
+
vendor: 'Cloudflare (Workers AI)',
|
|
106
|
+
// D-53 honesty: `@cf/…` ids run on Cloudflare's network; bare
|
|
107
|
+
// `vendor/model` ids are partner-served on that vendor's infrastructure.
|
|
108
|
+
location: 'global (Cloudflare network) · vendor/model ids partner-served',
|
|
109
|
+
},
|
|
110
|
+
suggested: ['@cf/zai-org/glm-5.2', '@cf/moonshotai/kimi-k2.7-code', 'deepseek/deepseek-v4-pro'],
|
|
111
|
+
note: 'e.g. cloudflare:@cf/zai-org/glm-5.2 · set CLOUDFLARE_AI_BASE_URL to https://api.cloudflare.com/client/v4/accounts/<id>/ai/v1',
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* Scaleway Generative APIs — EU-hosted (Paris) open-weight models behind the
|
|
115
|
+
* OpenAI dialect. The row that makes the data-residency answer a picker
|
|
116
|
+
* choice rather than a procurement exception.
|
|
117
|
+
*/
|
|
118
|
+
scaleway: {
|
|
119
|
+
kind: 'compatible',
|
|
120
|
+
baseUrl: 'https://api.scaleway.ai/v1',
|
|
121
|
+
baseUrlEnv: 'SCALEWAY_AI_BASE_URL',
|
|
122
|
+
envVar: 'SCALEWAY_API_KEY',
|
|
123
|
+
catalog: 'openai',
|
|
124
|
+
hosting: { vendor: 'Scaleway (Generative APIs)', location: 'European Union (France, Paris)' },
|
|
125
|
+
note: 'e.g. scaleway:llama-3.3-70b-instruct · EU-resident inference',
|
|
126
|
+
},
|
|
127
|
+
/** Local models. No credential; the endpoint is Ollama's OpenAI-compatible one. */
|
|
128
|
+
ollama: {
|
|
129
|
+
kind: 'compatible',
|
|
130
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
131
|
+
baseUrlEnv: 'OLLAMA_BASE_URL',
|
|
132
|
+
catalog: 'openai',
|
|
133
|
+
hosting: { vendor: 'Ollama (self-hosted)', location: ollamaLocation, local: true },
|
|
134
|
+
note: 'e.g. ollama:qwen3-coder · needs `ollama serve` and the model pulled',
|
|
135
|
+
},
|
|
136
|
+
/** Escape hatch: anything else speaking the OpenAI dialect (vLLM, LM Studio, OpenRouter…). */
|
|
137
|
+
compat: {
|
|
138
|
+
kind: 'compatible',
|
|
139
|
+
baseUrl: '',
|
|
140
|
+
baseUrlEnv: 'OPENAI_COMPATIBLE_BASE_URL',
|
|
141
|
+
envVar: 'OPENAI_COMPATIBLE_API_KEY',
|
|
142
|
+
catalog: 'openai',
|
|
143
|
+
hosting: { vendor: 'Custom endpoint', location: 'operator-defined' },
|
|
144
|
+
note: 'set OPENAI_COMPATIBLE_BASE_URL to the endpoint',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Ollama's endpoint is overridable, so "this machine" is a claim about the ENDPOINT, not
|
|
149
|
+
* about the row. Pointed at a GPU box on the LAN it is somebody else's machine, and the
|
|
150
|
+
* disclosure says so rather than repeating the default.
|
|
151
|
+
*/
|
|
152
|
+
export function ollamaLocation(host) {
|
|
153
|
+
return isLoopbackHost(host) ? 'this machine' : 'a remote host — OLLAMA_BASE_URL points off this machine';
|
|
154
|
+
}
|
|
155
|
+
/** DashScope endpoints encode region three different ways; decode them all. */
|
|
156
|
+
export function qwenLocation(host) {
|
|
157
|
+
if (host.includes('dashscope-intl'))
|
|
158
|
+
return 'Singapore (international endpoint)';
|
|
159
|
+
if (host.includes('dashscope-us'))
|
|
160
|
+
return 'United States';
|
|
161
|
+
if (host === 'dashscope.aliyuncs.com')
|
|
162
|
+
return 'China mainland';
|
|
163
|
+
const regional = host.match(/^([^.]+)\.([a-z]{2}-[a-z]+-\d)\.maas\.aliyuncs\.com$/);
|
|
164
|
+
if (regional)
|
|
165
|
+
return `workspace "${regional[1]}" · region ${regional[2]}`;
|
|
166
|
+
return 'unknown region';
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The row for a provider name, or `undefined` — the ONLY way to look one up.
|
|
170
|
+
*
|
|
171
|
+
* Never `PROVIDERS[name]` directly. The table is an object literal, so a name that
|
|
172
|
+
* happens to be an `Object.prototype` member — `constructor`, `toString`, `valueOf` —
|
|
173
|
+
* resolves to a truthy INHERITED value, sails past an `if (!row)` guard, and reaches
|
|
174
|
+
* `row.hosting.local` as a TypeError instead of the "unknown provider" error the caller
|
|
175
|
+
* is owed. That is reachable from config, not just from a typo: a model spec is a
|
|
176
|
+
* per-install setting (a desk's `TICKET0_MODEL`), so `constructor:x` is a value a
|
|
177
|
+
* settings field can hold.
|
|
178
|
+
*/
|
|
179
|
+
export function providerRow(name) {
|
|
180
|
+
return Object.hasOwn(PROVIDERS, name) ? PROVIDERS[name] : undefined;
|
|
181
|
+
}
|
|
182
|
+
/** One line per provider, for a CLI's "known providers" listing. */
|
|
183
|
+
export function knownProviders() {
|
|
184
|
+
return Object.entries(PROVIDERS)
|
|
185
|
+
.map(([name, spec]) => ` ${name.padEnd(10)} ${spec.note ?? ''}`)
|
|
186
|
+
.join('\n');
|
|
187
|
+
}
|
|
188
|
+
/** The credential env var of a provider, or null when it needs none / is unknown. */
|
|
189
|
+
export function credentialEnvVar(provider) {
|
|
190
|
+
return providerRow(provider)?.envVar ?? null;
|
|
191
|
+
}
|
|
192
|
+
export { SENT as DATA_SENT_PHRASE };
|
|
193
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit context-cache markers for the qwen dialect (H4 follow-up).
|
|
3
|
+
*
|
|
4
|
+
* DashScope's cache is per-model: qwen3.8-max caches implicitly, but the flash
|
|
5
|
+
* tier caches ONLY when the request carries explicit Anthropic-style
|
|
6
|
+
* `cache_control: {type:'ephemeral'}` markers on CONTENT BLOCKS — verified
|
|
7
|
+
* against the token-plan gateway 2026-08-15: block-level markers cache ~99% of
|
|
8
|
+
* the prefix (creation billed at 125%, hits at 10%, 5-minute TTL, ≥1024-token
|
|
9
|
+
* blocks, max 4 markers/request); message-level markers are silently ignored,
|
|
10
|
+
* and `@ai-sdk/openai-compatible` can only emit message-level ones. So the
|
|
11
|
+
* markers are injected here, at the wire: a fetch wrapper rewrites each
|
|
12
|
+
* chat/completions body just-in-time.
|
|
13
|
+
*
|
|
14
|
+
* Placement is the same strategy `withMovingBreakpoint` runs for Claude —
|
|
15
|
+
* system prefix + the request's last message — and because the rewrite is
|
|
16
|
+
* stateless per request, the "moving" part comes free: each step marks its own
|
|
17
|
+
* tail, and the previous step's mark simply never existed on the wire again.
|
|
18
|
+
* Explicit markers are sent to ALL qwen models (max included): explicit read
|
|
19
|
+
* price (10%) undercuts the implicit discount, and the behavior is
|
|
20
|
+
* deterministic instead of best-effort.
|
|
21
|
+
*
|
|
22
|
+
* Worker-safe on purpose: both provider hosts (providers.ts, the node CLI, and
|
|
23
|
+
* providers-worker.ts, the hosted agent) wire this into `createOpenAICompatible`.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* The body transform: marker on each system message (the cross-turn-stable
|
|
27
|
+
* prefix — the only part reusable between turns) and on the last markable
|
|
28
|
+
* message (the within-turn moving breakpoint — each tool-loop step re-reads
|
|
29
|
+
* everything before its own tail from cache). Non-chat bodies pass through
|
|
30
|
+
* untouched.
|
|
31
|
+
*/
|
|
32
|
+
export declare function withQwenCacheMarkers(body: unknown): unknown;
|
|
33
|
+
type FetchLike = typeof globalThis.fetch;
|
|
34
|
+
/**
|
|
35
|
+
* Fetch wrapper for the qwen provider: rewrites POST chat/completions bodies
|
|
36
|
+
* through `withQwenCacheMarkers`. Anything unexpected — non-chat URL, a
|
|
37
|
+
* Request-object body, unparseable JSON — passes through unchanged: a request
|
|
38
|
+
* without markers is merely uncached, never broken.
|
|
39
|
+
*/
|
|
40
|
+
export declare function qwenCacheFetch(inner?: FetchLike): FetchLike;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=qwen-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qwen-cache.d.ts","sourceRoot":"","sources":["../src/qwen-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAsCH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CA+B3D;AAED,KAAK,SAAS,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAEzC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAuB3D"}
|