@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.
Files changed (55) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +31 -0
  3. package/dist/catalog.d.ts +65 -0
  4. package/dist/catalog.d.ts.map +1 -0
  5. package/dist/catalog.js +64 -0
  6. package/dist/catalog.js.map +1 -0
  7. package/dist/host.d.ts +21 -0
  8. package/dist/host.d.ts.map +1 -0
  9. package/dist/host.js +37 -0
  10. package/dist/host.js.map +1 -0
  11. package/dist/index.d.ts +21 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +21 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/list-models.d.ts +12 -0
  16. package/dist/list-models.d.ts.map +1 -0
  17. package/dist/list-models.js +57 -0
  18. package/dist/list-models.js.map +1 -0
  19. package/dist/model-pairs.d.ts +38 -0
  20. package/dist/model-pairs.d.ts.map +1 -0
  21. package/dist/model-pairs.js +56 -0
  22. package/dist/model-pairs.js.map +1 -0
  23. package/dist/pricing.d.ts +52 -0
  24. package/dist/pricing.d.ts.map +1 -0
  25. package/dist/pricing.js +100 -0
  26. package/dist/pricing.js.map +1 -0
  27. package/dist/provider-errors.d.ts +27 -0
  28. package/dist/provider-errors.d.ts.map +1 -0
  29. package/dist/provider-errors.js +74 -0
  30. package/dist/provider-errors.js.map +1 -0
  31. package/dist/providers.d.ts +116 -0
  32. package/dist/providers.d.ts.map +1 -0
  33. package/dist/providers.js +193 -0
  34. package/dist/providers.js.map +1 -0
  35. package/dist/qwen-cache.d.ts +42 -0
  36. package/dist/qwen-cache.d.ts.map +1 -0
  37. package/dist/qwen-cache.js +118 -0
  38. package/dist/qwen-cache.js.map +1 -0
  39. package/dist/rate-card.generated.d.ts +16 -0
  40. package/dist/rate-card.generated.d.ts.map +1 -0
  41. package/dist/rate-card.generated.js +80 -0
  42. package/dist/rate-card.generated.js.map +1 -0
  43. package/dist/request.d.ts +10 -0
  44. package/dist/request.d.ts.map +1 -0
  45. package/dist/request.js +44 -0
  46. package/dist/request.js.map +1 -0
  47. package/dist/resolve.d.ts +62 -0
  48. package/dist/resolve.d.ts.map +1 -0
  49. package/dist/resolve.js +121 -0
  50. package/dist/resolve.js.map +1 -0
  51. package/dist/spec.d.ts +21 -0
  52. package/dist/spec.d.ts.map +1 -0
  53. package/dist/spec.js +27 -0
  54. package/dist/spec.js.map +1 -0
  55. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @substrat-run/model-providers
2
+
3
+ The model-provider seam: one `provider:model` grammar, a table of providers, the
4
+ hosting disclosure per provider, the generated rate card and the list-price math over
5
+ it. Hosts differ only in *where credentials come from* and *how provider packages get
6
+ loaded* — both are parameters. Cloudflare is one row, not the design (issue #1054).
7
+
8
+ **Full documentation: https://substrat.net/reference/model-providers**
9
+
10
+ ## Usage
11
+
12
+ ```ts
13
+ import { createAnthropic } from '@ai-sdk/anthropic';
14
+ import { createModel, providerCatalog, listCostOfSteps } from '@substrat-run/model-providers';
15
+
16
+ // A Worker: bindings in, statically imported factories in.
17
+ const { model, label } = createModel('cloudflare:@cf/zai-org/glm-5.2', env, {
18
+ factories: { anthropic: createAnthropic },
19
+ hosted: true,
20
+ describeMissing: (v) => `${v} is not set as a worker secret`,
21
+ });
22
+
23
+ // What a picker shows — vendor, location, host, and whether this environment can run it.
24
+ providerCatalog(env, { hosted: true, sent: 'Conversation text' });
25
+
26
+ // List price from the AI SDK usage shape; margin is the caller's vocabulary.
27
+ listCostOfSteps('anthropic:claude-opus-5', [{ inputTokens: 100_000, outputTokens: 10_000 }]);
28
+ ```
29
+
30
+ The rate card is generated (`pnpm --filter @substrat-run/model-providers update-rate-card`)
31
+ from models.dev × LiteLLM with a failing cross-check, and reviewed as a PR diff.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Where does this model actually run? — the disclosure, as data.
3
+ *
4
+ * D-53/D-54: the model provider is a subprocessor of whatever is sent to it, so
5
+ * a picker must say WHERE inference happens, not just which model. This turns a
6
+ * provider row + its effective endpoint into a human-readable hosting statement.
7
+ * Deliberately factual — vendor, place, host — not marketing.
8
+ */
9
+ import { type ModelPair } from './model-pairs.js';
10
+ import { type CredentialEnv } from './resolve.js';
11
+ export interface HostingInfo {
12
+ /** Who operates the inference endpoint. */
13
+ readonly vendor: string;
14
+ /** Where, as precisely as the endpoint tells us. */
15
+ readonly location: string;
16
+ /** The hostname requests actually go to. */
17
+ readonly host: string;
18
+ /**
19
+ * True only when inference runs on this machine IN FACT — the row is declared local
20
+ * and its effective endpoint is loopback. Never inferred the other way.
21
+ */
22
+ readonly local: boolean;
23
+ /** The one sentence about what is sent — the host's own words. */
24
+ readonly dataNote: string;
25
+ }
26
+ export interface CatalogOptions {
27
+ /**
28
+ * What this host sends — 'Session code and chat', 'Conversation text and the
29
+ * knowledge base'. Rendered as "<sent> — sent to this provider." (number-neutral).
30
+ */
31
+ readonly sent?: string;
32
+ /** Exclude rows marked `local` (a hosted runtime cannot dial localhost). */
33
+ readonly hosted?: boolean;
34
+ /** Restrict to these providers — e.g. the direct rows a Worker statically wired. */
35
+ readonly only?: readonly string[];
36
+ }
37
+ export declare function hostingInfo(provider: string, env: CredentialEnv, options?: CatalogOptions): HostingInfo;
38
+ export interface ProviderCatalogEntry {
39
+ readonly name: string;
40
+ readonly kind: 'direct' | 'compatible';
41
+ readonly hosting: HostingInfo;
42
+ /**
43
+ * `set` means THIS ENVIRONMENT CAN RUN THE ROW — the credential and, for an
44
+ * account-scoped endpoint such as Cloudflare's, the base URL too. A picker gates
45
+ * listing and selection on it, and a token with no endpoint can do neither, so
46
+ * "credential present but endpoint absent" must read as not set. `missing` names
47
+ * which variables, so the UI can say which one rather than blaming the key.
48
+ */
49
+ readonly credential: {
50
+ readonly envVar: string | null;
51
+ readonly set: boolean;
52
+ readonly missing: readonly string[];
53
+ };
54
+ /** True when models can be listed live from the endpoint. */
55
+ readonly listable: boolean;
56
+ readonly suggested: readonly string[];
57
+ /** The `<provider>:auto` pair, when one is declared (model-pairs.ts). */
58
+ readonly pair?: ModelPair;
59
+ }
60
+ /**
61
+ * The picker catalog: one entry per provider this host can run, with the
62
+ * disclosure intact and `credential.set` reflecting THIS environment.
63
+ */
64
+ export declare function providerCatalog(env: CredentialEnv, options?: CatalogOptions): ProviderCatalogEntry[];
65
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG/D,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAEnE,MAAM,WAAW,WAAW;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAQD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,OAAO,GAAE,cAAmB,GAAG,WAAW,CAoB3G;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IACpH,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,GAAE,cAAmB,GAAG,oBAAoB,EAAE,CAkBxG"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Where does this model actually run? — the disclosure, as data.
3
+ *
4
+ * D-53/D-54: the model provider is a subprocessor of whatever is sent to it, so
5
+ * a picker must say WHERE inference happens, not just which model. This turns a
6
+ * provider row + its effective endpoint into a human-readable hosting statement.
7
+ * Deliberately factual — vendor, place, host — not marketing.
8
+ */
9
+ import { MODEL_PAIRS } from './model-pairs.js';
10
+ import { PROVIDERS, providerRow } from './providers.js';
11
+ import { hostOf, isLoopbackHost } from './host.js';
12
+ import { credentialsFrom } from './resolve.js';
13
+ function effectiveHost(name, row, env) {
14
+ const creds = credentialsFrom(name, env);
15
+ if (row.kind === 'direct')
16
+ return creds.baseUrl ? hostOf(creds.baseUrl) : row.defaultHost;
17
+ return creds.baseUrl ? hostOf(creds.baseUrl) : `(${row.baseUrlEnv} not set)`;
18
+ }
19
+ export function hostingInfo(provider, env, options = {}) {
20
+ const row = providerRow(provider);
21
+ const sent = `${options.sent ?? 'Inputs'} — sent to this provider.`;
22
+ if (!row)
23
+ return { vendor: provider, location: 'unknown', host: 'unknown', local: false, dataNote: sent };
24
+ const host = effectiveHost(provider, row, env);
25
+ const location = typeof row.hosting.location === 'function' ? row.hosting.location(host) : row.hosting.location;
26
+ // `hosting.local` says the row is local BY DEFAULT; the effective endpoint says
27
+ // whether it still is. Ollama's endpoint is overridable, so a row declared local can
28
+ // be pointed at a remote host — and the disclosure must follow the endpoint, not the
29
+ // declaration. The effective endpoint only ever TAKES the local claim away; it never
30
+ // grants one, so a `compat` row aimed at localhost still reads as sent (see
31
+ // `isLoopbackHost`).
32
+ const local = row.hosting.local === true && isLoopbackHost(host);
33
+ return {
34
+ vendor: row.hosting.vendor,
35
+ location,
36
+ host,
37
+ local,
38
+ dataNote: local ? `${options.sent ?? 'Inputs'} — never leaves this machine.` : sent,
39
+ };
40
+ }
41
+ /**
42
+ * The picker catalog: one entry per provider this host can run, with the
43
+ * disclosure intact and `credential.set` reflecting THIS environment.
44
+ */
45
+ export function providerCatalog(env, options = {}) {
46
+ return Object.entries(PROVIDERS)
47
+ .filter(([name, row]) => !(options.hosted && row.hosting.local))
48
+ .filter(([name]) => !options.only || options.only.includes(name))
49
+ .map(([name, row]) => {
50
+ const envVar = row.envVar ?? null;
51
+ const creds = credentialsFrom(name, env);
52
+ const pair = MODEL_PAIRS[name];
53
+ return {
54
+ name,
55
+ kind: row.kind,
56
+ hosting: hostingInfo(name, env, options),
57
+ credential: { envVar, set: creds.missing.length === 0, missing: creds.missing },
58
+ listable: row.kind === 'compatible',
59
+ suggested: row.suggested ?? [],
60
+ ...(pair ? { pair } : {}),
61
+ };
62
+ });
63
+ }
64
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
package/dist/host.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Host helpers — the two questions the disclosure asks of an endpoint.
3
+ *
4
+ * Their own module because BOTH the provider table (a row whose location depends on
5
+ * where its endpoint points) and the catalog (which builds the disclosure) need them,
6
+ * and the table must not import the catalog that reads it.
7
+ */
8
+ /** The host of a URL, or the string itself when it will not parse. */
9
+ export declare function hostOf(url: string): string;
10
+ /**
11
+ * Is a host loopback — this machine, and no other?
12
+ *
13
+ * Answers `true` only for a host it RECOGNISES as loopback. An unfamiliar name, an
14
+ * unparseable URL, `0.0.0.0`, a LAN address: all read as remote. That asymmetry is the
15
+ * point, and it is the direction a privacy claim must be wrong in — telling somebody
16
+ * their prompts stayed on their laptop when they went to a GPU box is a lie, while
17
+ * telling them a local endpoint might have been sent to overstates the exposure and
18
+ * costs them nothing.
19
+ */
20
+ export declare function isLoopbackHost(host: string): boolean;
21
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,sEAAsE;AACtE,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOpD"}
package/dist/host.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Host helpers — the two questions the disclosure asks of an endpoint.
3
+ *
4
+ * Their own module because BOTH the provider table (a row whose location depends on
5
+ * where its endpoint points) and the catalog (which builds the disclosure) need them,
6
+ * and the table must not import the catalog that reads it.
7
+ */
8
+ /** The host of a URL, or the string itself when it will not parse. */
9
+ export function hostOf(url) {
10
+ try {
11
+ return new URL(url).host;
12
+ }
13
+ catch {
14
+ return url;
15
+ }
16
+ }
17
+ /**
18
+ * Is a host loopback — this machine, and no other?
19
+ *
20
+ * Answers `true` only for a host it RECOGNISES as loopback. An unfamiliar name, an
21
+ * unparseable URL, `0.0.0.0`, a LAN address: all read as remote. That asymmetry is the
22
+ * point, and it is the direction a privacy claim must be wrong in — telling somebody
23
+ * their prompts stayed on their laptop when they went to a GPU box is a lie, while
24
+ * telling them a local endpoint might have been sent to overstates the exposure and
25
+ * costs them nothing.
26
+ */
27
+ export function isLoopbackHost(host) {
28
+ // `new URL(...).host` carries the port; the name alone decides. IPv6 arrives
29
+ // bracketed (`[::1]:11434`), which the same strip handles.
30
+ const name = host.replace(/:\d+$/, '').toLowerCase();
31
+ if (name === 'localhost' || name.endsWith('.localhost'))
32
+ return true; // RFC 6761
33
+ if (name === '[::1]' || name === '::1')
34
+ return true;
35
+ return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(name);
36
+ }
37
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,sEAAsE;AACtE,MAAM,UAAU,MAAM,CAAC,GAAW;IACjC,IAAI,CAAC;QACJ,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,GAAG,CAAC;IACZ,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,6EAA6E;IAC7E,2DAA2D;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACrD,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,WAAW;IACjF,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACpD,OAAO,kCAAkC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @substrat-run/model-providers — the model-provider seam (#1054).
3
+ *
4
+ * One `provider:model` grammar, one table of providers, one disclosure per row,
5
+ * one generated rate card and the list-price math over it. Hosts differ only in
6
+ * where credentials come from and how direct-provider packages get loaded, and
7
+ * both of those are parameters. Cloudflare is one row.
8
+ */
9
+ export { DEFAULT_PROVIDER, normalizeModelSpec, parseModelSpec, providerOf, type ModelSpec } from './spec.js';
10
+ export { PROVIDERS, credentialEnvVar, knownProviders, providerRow, ollamaLocation, qwenLocation, type CompatibleProvider, type DirectProvider, type HostingSpec, type ProviderSpec, } from './providers.js';
11
+ export { ProviderError, createModel, credentialsFrom, providerSpec, type CreateModelOptions, type CredentialEnv, type DirectFactories, type DirectFactory, type ProviderCredentials, type ResolvedModel, } from './resolve.js';
12
+ export { listModels } from './list-models.js';
13
+ export { hostOf, isLoopbackHost } from './host.js';
14
+ export { requestHeadersFor, type RequestExtrasInput, type WireAttribution } from './request.js';
15
+ export { hostingInfo, providerCatalog, type CatalogOptions, type HostingInfo, type ProviderCatalogEntry, } from './catalog.js';
16
+ export { MODEL_PAIRS, pairFor, resolveAutoSpec, samplingFor, type ModelPair, type PairTier } from './model-pairs.js';
17
+ export { listCostOf, listCostOfSteps, rateFor, type ModelRate, type RateTier, type StepTokens, } from './pricing.js';
18
+ export { RATE_CARD, RATE_CARD_GENERATED_AT } from './rate-card.generated.js';
19
+ export { errorFacts, explainProviderFailure, type ErrorFacts } from './provider-errors.js';
20
+ export { qwenCacheFetch, withQwenCacheMarkers } from './qwen-cache.js';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAC7G,OAAO,EACN,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,EACd,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,YAAY,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,aAAa,EACb,WAAW,EACX,eAAe,EACf,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,aAAa,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,EACN,WAAW,EACX,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,oBAAoB,GACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrH,OAAO,EACN,UAAU,EACV,eAAe,EACf,OAAO,EACP,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @substrat-run/model-providers — the model-provider seam (#1054).
3
+ *
4
+ * One `provider:model` grammar, one table of providers, one disclosure per row,
5
+ * one generated rate card and the list-price math over it. Hosts differ only in
6
+ * where credentials come from and how direct-provider packages get loaded, and
7
+ * both of those are parameters. Cloudflare is one row.
8
+ */
9
+ export { DEFAULT_PROVIDER, normalizeModelSpec, parseModelSpec, providerOf } from './spec.js';
10
+ export { PROVIDERS, credentialEnvVar, knownProviders, providerRow, ollamaLocation, qwenLocation, } from './providers.js';
11
+ export { ProviderError, createModel, credentialsFrom, providerSpec, } from './resolve.js';
12
+ export { listModels } from './list-models.js';
13
+ export { hostOf, isLoopbackHost } from './host.js';
14
+ export { requestHeadersFor } from './request.js';
15
+ export { hostingInfo, providerCatalog, } from './catalog.js';
16
+ export { MODEL_PAIRS, pairFor, resolveAutoSpec, samplingFor } from './model-pairs.js';
17
+ export { listCostOf, listCostOfSteps, rateFor, } from './pricing.js';
18
+ export { RATE_CARD, RATE_CARD_GENERATED_AT } from './rate-card.generated.js';
19
+ export { errorFacts, explainProviderFailure } from './provider-errors.js';
20
+ export { qwenCacheFetch, withQwenCacheMarkers } from './qwen-cache.js';
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAkB,MAAM,WAAW,CAAC;AAC7G,OAAO,EACN,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,cAAc,EACd,YAAY,GAKZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACN,aAAa,EACb,WAAW,EACX,eAAe,EACf,YAAY,GAOZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAiD,MAAM,cAAc,CAAC;AAChG,OAAO,EACN,WAAW,EACX,eAAe,GAIf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAiC,MAAM,kBAAkB,CAAC;AACrH,OAAO,EACN,UAAU,EACV,eAAe,EACf,OAAO,GAIP,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Ask an OpenAI-compatible endpoint which models it actually serves.
3
+ *
4
+ * Worth having as a first-class affordance: a workspace or regional plan exposes
5
+ * its own model list, so a model id that is valid on one endpoint returns a bare
6
+ * "Model not exist." on another — with no hint of what would work instead.
7
+ *
8
+ * Direct providers are not listable here — see their own documentation.
9
+ */
10
+ import { type CredentialEnv } from './resolve.js';
11
+ export declare function listModels(provider: string, env: CredentialEnv): Promise<string[]>;
12
+ //# sourceMappingURL=list-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-models.d.ts","sourceRoot":"","sources":["../src/list-models.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAgD,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhG,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBxF"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Ask an OpenAI-compatible endpoint which models it actually serves.
3
+ *
4
+ * Worth having as a first-class affordance: a workspace or regional plan exposes
5
+ * its own model list, so a model id that is valid on one endpoint returns a bare
6
+ * "Model not exist." on another — with no hint of what would work instead.
7
+ *
8
+ * Direct providers are not listable here — see their own documentation.
9
+ */
10
+ import { credentialsFrom, ProviderError, providerSpec } from './resolve.js';
11
+ export async function listModels(provider, env) {
12
+ const row = providerSpec(provider);
13
+ if (row.kind !== 'compatible') {
14
+ throw new ProviderError(`listing models is only supported for OpenAI-compatible providers; ` +
15
+ `${provider} is a direct provider — see its own documentation.`);
16
+ }
17
+ const creds = credentialsFrom(provider, env);
18
+ if (!creds.baseUrl)
19
+ throw new ProviderError(`provider ${provider} needs ${row.baseUrlEnv} set.`);
20
+ const headers = creds.apiKey ? { Authorization: `Bearer ${creds.apiKey}` } : {};
21
+ if (row.catalog === 'cloudflare-catalog')
22
+ return listCloudflareCatalog(creds.baseUrl, headers);
23
+ const res = await fetch(`${creds.baseUrl.replace(/\/$/, '')}/models`, { headers });
24
+ if (!res.ok)
25
+ throw new ProviderError(`${creds.baseUrl}/models returned HTTP ${res.status}`);
26
+ const body = (await res.json());
27
+ return (body.data ?? [])
28
+ .map((m) => (typeof m.id === 'string' ? m.id : null))
29
+ .filter((id) => id !== null)
30
+ .sort();
31
+ }
32
+ /**
33
+ * Workers AI's OpenAI-compatible surface serves chat/completions and embeddings
34
+ * but NOT `GET /models` (405) — the catalog lives one level up, on the account
35
+ * API (`…/ai/v1` → `…/ai/models/search`), task-filtered server-side so the
36
+ * picker offers models that can run a text turn, not embeddings or speech. It
37
+ * lists Cloudflare's own `@cf/…` models only; partner-served `vendor/model` ids
38
+ * stay free-text. Paged defensively; the filtered catalog fits one page today.
39
+ */
40
+ async function listCloudflareCatalog(baseUrl, headers) {
41
+ const root = baseUrl.replace(/\/$/, '').replace(/\/v1$/, '');
42
+ const names = [];
43
+ for (let page = 1; page <= 5; page++) {
44
+ const url = `${root}/models/search?task=${encodeURIComponent('Text Generation')}&per_page=100&page=${page}`;
45
+ const res = await fetch(url, { headers });
46
+ if (!res.ok)
47
+ throw new ProviderError(`${root}/models/search returned HTTP ${res.status}`);
48
+ const body = (await res.json());
49
+ const rows = body.result ?? [];
50
+ names.push(...rows.map((m) => (typeof m.name === 'string' ? m.name : null)).filter((n) => n !== null));
51
+ // The page boundary is the RAW page length: a filtered-out entry must not end the walk early.
52
+ if (rows.length < 100)
53
+ break;
54
+ }
55
+ return names.sort();
56
+ }
57
+ //# sourceMappingURL=list-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-models.js","sourceRoot":"","sources":["../src/list-models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export interface ModelPair {
2
+ /** Conversational turns — cheap. */
3
+ readonly fast: string;
4
+ /** The hard turns — the tier where models actually diverge. */
5
+ readonly strong: string;
6
+ }
7
+ export type PairTier = keyof ModelPair;
8
+ /**
9
+ * Model ids are config, not architecture: the qwen ids were verified against
10
+ * the DashScope /models endpoint (2026-08-15) — update them here when the
11
+ * catalog moves, nothing else needs to change.
12
+ */
13
+ export declare const MODEL_PAIRS: Readonly<Record<string, ModelPair>>;
14
+ /**
15
+ * Sampling defaults per provider (builder-harness.md H4). Qwen's own
16
+ * recommendation — and opencode's shipped per-family table — is 0.55 for the
17
+ * qwen family; the SDK default (1.0) is measurably chattier and loopier on
18
+ * agentic runs. topP 0.8 is Qwen's published qwen3-coder setting; without
19
+ * nucleus truncation the family degenerates into single-token repetition loops
20
+ * mid-turn. Others: undefined — adaptive thinking dislikes a pinned
21
+ * temperature, and the SDK default is the provider's own.
22
+ */
23
+ export declare function samplingFor(spec: string): {
24
+ temperature?: number;
25
+ topP?: number;
26
+ };
27
+ /** The `<provider>:auto` pair a spec names, or null for a concrete spec. */
28
+ export declare function pairFor(spec: string): {
29
+ provider: string;
30
+ pair: ModelPair;
31
+ } | null;
32
+ /**
33
+ * `qwen:auto` + tier → a concrete spec; concrete specs pass through untouched.
34
+ * `<provider>:auto` for a provider with no declared pair also passes through —
35
+ * the provider's own "model not exist" error names the fix better than we can.
36
+ */
37
+ export declare function resolveAutoSpec(spec: string, tier: PairTier): string;
38
+ //# sourceMappingURL=model-pairs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-pairs.d.ts","sourceRoot":"","sources":["../src/model-pairs.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,SAAS;IACzB,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;AAEvC;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAG3D,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAEjF;AAED,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAKlF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAIpE"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Auto model pairs — a provider-scoped {fast, strong} pair behind the spec
3
+ * `<provider>:auto`, resolved per tier at call time: conversational turns run
4
+ * the fast model, the hard agentic turns run the strong one.
5
+ *
6
+ * Pairs never cross a provider: the provider choice is a consent decision (the
7
+ * picker's D-53 disclosure — WHERE the data goes), so auto-routing must stay
8
+ * inside the provider the user picked. A mixed pair is a deliberate future
9
+ * feature gated on both credentials being configured — never an implicit
10
+ * fallback.
11
+ *
12
+ * Worker-safe on purpose: every host and every catalog import this, so the
13
+ * pair the picker SHOWS is the pair the turn loop RUNS.
14
+ */
15
+ import { parseModelSpec } from './spec.js';
16
+ /**
17
+ * Model ids are config, not architecture: the qwen ids were verified against
18
+ * the DashScope /models endpoint (2026-08-15) — update them here when the
19
+ * catalog moves, nothing else needs to change.
20
+ */
21
+ export const MODEL_PAIRS = {
22
+ qwen: { fast: 'qwen3.6-flash', strong: 'qwen3.8-max' },
23
+ anthropic: { fast: 'claude-sonnet-5', strong: 'claude-opus-5' },
24
+ };
25
+ /**
26
+ * Sampling defaults per provider (builder-harness.md H4). Qwen's own
27
+ * recommendation — and opencode's shipped per-family table — is 0.55 for the
28
+ * qwen family; the SDK default (1.0) is measurably chattier and loopier on
29
+ * agentic runs. topP 0.8 is Qwen's published qwen3-coder setting; without
30
+ * nucleus truncation the family degenerates into single-token repetition loops
31
+ * mid-turn. Others: undefined — adaptive thinking dislikes a pinned
32
+ * temperature, and the SDK default is the provider's own.
33
+ */
34
+ export function samplingFor(spec) {
35
+ return parseModelSpec(spec).provider === 'qwen' ? { temperature: 0.55, topP: 0.8 } : {};
36
+ }
37
+ /** The `<provider>:auto` pair a spec names, or null for a concrete spec. */
38
+ export function pairFor(spec) {
39
+ const { provider, modelId } = parseModelSpec(spec);
40
+ if (modelId !== 'auto')
41
+ return null;
42
+ const pair = MODEL_PAIRS[provider];
43
+ return pair ? { provider, pair } : null;
44
+ }
45
+ /**
46
+ * `qwen:auto` + tier → a concrete spec; concrete specs pass through untouched.
47
+ * `<provider>:auto` for a provider with no declared pair also passes through —
48
+ * the provider's own "model not exist" error names the fix better than we can.
49
+ */
50
+ export function resolveAutoSpec(spec, tier) {
51
+ const hit = pairFor(spec);
52
+ if (!hit)
53
+ return spec;
54
+ return `${hit.provider}:${hit.pair[tier]}`;
55
+ }
56
+ //# sourceMappingURL=model-pairs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-pairs.js","sourceRoot":"","sources":["../src/model-pairs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAW3C;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAwC;IAC/D,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE;IACtD,SAAS,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,eAAe,EAAE;CAC/D,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACzF,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACnD,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAAc;IAC3D,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,52 @@
1
+ export interface RateTier {
2
+ /**
3
+ * Inclusive upper bound on the request's total input tokens for this tier;
4
+ * null = top (or only) tier. Tiers are ordered ascending.
5
+ */
6
+ readonly upToInputTokens: number | null;
7
+ /** Provider list price in USD per 1M tokens, decimal strings. */
8
+ readonly inputPer1M: string;
9
+ readonly outputPer1M: string;
10
+ /** null = provider publishes no cache pricing; that slice bills at input rate. */
11
+ readonly cacheReadPer1M: string | null;
12
+ readonly cacheWritePer1M: string | null;
13
+ }
14
+ export interface ModelRate {
15
+ readonly provider: string;
16
+ /** Longest-prefix match on the model id, so dated snapshots (`qwen3.8-max-2026-01-01`) price as their base model. */
17
+ readonly idPrefix: string;
18
+ readonly label: string;
19
+ /** Smallest context both catalogs agree on; null when neither reports one. */
20
+ readonly contextTokens: number | null;
21
+ readonly tiers: readonly RateTier[];
22
+ /** Where the numbers came from, for the audit trip back to the price page. */
23
+ readonly sources: readonly string[];
24
+ }
25
+ /**
26
+ * One provider request's token counts. `inputTokens` is the TOTAL input the
27
+ * provider reported, INCLUDING the cached slices — the AI SDK's usage shape —
28
+ * so the non-cached remainder is computed here, in one place.
29
+ */
30
+ export interface StepTokens {
31
+ readonly inputTokens: number;
32
+ readonly outputTokens: number;
33
+ readonly cachedInputTokens?: number;
34
+ readonly cacheWriteTokens?: number;
35
+ }
36
+ /** The card entry for a `provider:model`, by longest id prefix; null = unpriced. */
37
+ export declare function rateFor(model: string): ModelRate | null;
38
+ /**
39
+ * Price a turn from its per-request steps — the correct path (see header).
40
+ * USD list price as a decimal string; null when the model has no rate card
41
+ * entry — unpriced, never guessed.
42
+ */
43
+ export declare function listCostOfSteps(model: string, steps: readonly StepTokens[]): string | null;
44
+ /**
45
+ * Legacy totals path: ledger entries recorded before per-step usage carry only
46
+ * turn totals, with no step split and no cache breakdown. Priced as ONE
47
+ * pseudo-request — for a single-tier card this equals the per-step price
48
+ * exactly; for a tiered card it can only over-select the tier, so a read side
49
+ * uses recorded costs whenever they exist and falls back here only for old rows.
50
+ */
51
+ export declare function listCostOf(model: string, inputTokens: number, outputTokens: number): string | null;
52
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAkCA,MAAM,WAAW,QAAQ;IACxB;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,iEAAiE;IACjE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qHAAqH;IACrH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IACpC,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,oFAAoF;AACpF,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CASvD;AAmBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,GAAG,IAAI,CAsB1F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElG"}