@plurnk/plurnk-providers 0.1.0 → 0.1.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/README.md CHANGED
@@ -9,8 +9,13 @@ Framework + contract for `@plurnk/plurnk-providers-*` sibling packages (LLM tran
9
9
 
10
10
  ## Exports
11
11
 
12
- - `Provider`, `ChatMessage`, `ProviderResponse`, `ProviderAssistant`, `ProviderUsage`, `ProviderFactory` — types.
13
- - `parseAliasesFromEnv`, `resolveActiveAlias`, `instantiateProvider`, `loadActiveProvider` alias-cascade resolution and provider instantiation.
12
+ - `Provider`, `ChatMessage`, `ProviderResponse`, `ProviderAssistant`, `ProviderUsage`, `FinishReason`, `ProviderFactory` — types.
13
+ - `parseAliasesFromEnv`, `resolveActiveAlias` — alias-cascade resolution (pure env-parsing). Provider instantiation (`instantiateProvider`, `loadActiveProvider`) is consumer-side; see SPEC §5.
14
+ - `OpenAICompatProvider` (+ `OpenAICompatConfig`, `ReasoningStyle`, `effortFromBudget`) — shared OpenAI-compatible transport spine; siblings extend it (SPEC §11).
15
+ - `chatCompletionStream`, `OpenAiHttpError`, `StreamResponse` — the shared SSE client.
16
+ - `parseRequiredInt`, `parseOptionalInt`, `requireEnv` — env helpers.
17
+ - `tokenizerFor`, `tokenizerByPublisher`, `parseTokenizerFamily` (+ `TokenizerFamily`, `CountTokens`) — synchronous tokenizer strategies.
18
+ - `STANDARD_PROVIDERS`, `isStandardProvider`, `standardProviderFromEnv` — pure-config OpenAI-compatible providers (no sibling package needed).
14
19
  - `Mock` — reference implementation + test fixture (dual-purpose).
15
20
 
16
21
  ## Tests
package/SPEC.md CHANGED
@@ -46,17 +46,22 @@ interface ProviderResponse {
46
46
  }
47
47
 
48
48
  interface ProviderUsage {
49
- prompt: number;
50
- completion: number;
51
- cached: number;
52
- total: number;
49
+ prompt: number; // input tokens (cached ones included)
50
+ completion: number; // visible output tokens, EXCLUDING reasoning
51
+ reasoning: number; // reasoning/thinking tokens, billed as output
52
+ cached: number; // subset of prompt served from cache
53
+ total: number; // prompt + completion + reasoning
53
54
  }
54
55
  ```
55
56
 
57
+ Usage invariant: `total = prompt + completion + reasoning`; `cached ⊆ prompt`; `completion` excludes reasoning; **billable output = `completion + reasoning`**. Providers report reasoning two ways — inside `completion_tokens` (OpenAI, via `completion_tokens_details.reasoning_tokens`) or only as the `total − prompt − completion` gap (Gemini). The framework's `normalizeUsage` (§11) collapses both to this invariant, so siblings on `OpenAICompatProvider` get it for free.
58
+
56
59
  ### Promises
57
60
 
58
- - `assistant.content` is the **verbatim** model emission. Consumer parses via `@plurnk/plurnk-grammar` — providers MUST NOT parse. Native tool-call outputs (OpenAI function calls, Anthropic tool_use) MUST be normalized back to plurnk DSL string at the provider boundary.
59
- - `assistant.usage` is authoritative. Fill `0`s when the wire response omits a breakdown.
61
+ - `assistant.content` is the **verbatim** model emission. Consumer parses via `@plurnk/plurnk-grammar` — providers MUST NOT parse. plurnk uses a **tools-in-body** design: tool invocations are expressed as plurnk DSL *inside the message content*, never via a provider's native tool-calling API, so `content` is always raw text and providers never request, parse, or translate native tool calls.[^tools]
62
+
63
+ [^tools]: A provider that wants to drive native tool-calling (OpenAI `tool_calls`, Anthropic `tool_use`) would have to normalize those emissions back into a plurnk DSL string at the boundary. No provider does this and it is out of scope for v0 — tools-in-body sidesteps the whole problem. The clause is recorded only so a future native-tools mode has a defined contract.
64
+ - `assistant.usage` is authoritative and follows the invariant above. Fill `0`s when the wire response omits a breakdown.
60
65
  - `countTokens` is **synchronous**, returns a non-negative integer, deterministic for the same input.
61
66
  - `costFor` is **pure**, returns pico-USD non-negative integer. Returns `0` for siblings with no known rates (local Ollama, generic OpenAI-compat shims).
62
67
  - `contextSize` resolves to `null` when provider can't determine the model's context window. Consumer treats null as "no budget info available."
@@ -78,7 +83,7 @@ class OpenAI {
78
83
  }
79
84
  ```
80
85
 
81
- The framework's `instantiateProvider` calls `mod.default.fromEnv(env, alias.model)` generically.
86
+ The consumer's instantiation path calls `mod.default.fromEnv(env, alias.model)` generically (§5).
82
87
 
83
88
  `fromEnv` MAY be sync or async; return type `Provider | Promise<Provider>`.
84
89
 
@@ -102,14 +107,20 @@ PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
102
107
  PLURNK_MODEL=gemma
103
108
  ```
104
109
 
105
- First path segment names the provider plugin (`@plurnk/plurnk-providers-<provider>`); rest is the model identifier (may contain `/` for tri-level providers like openrouter's `publisher/model`).
110
+ First path segment names the provider; rest is the model identifier (may contain `/` for tri-level providers like openrouter's `publisher/model`).
106
111
 
107
- Framework helpers (`./ProviderRegistry.ts`):
112
+ This package's exported resolution surface:
108
113
 
109
114
  - `parseAliasesFromEnv(env)` — extracts alias entries.
110
115
  - `resolveActiveAlias(env)` — `{ alias, provider, model } | null`.
111
- - `instantiateProvider(alias, env)` — dynamic-imports `@plurnk/plurnk-providers-<provider>` and calls `fromEnv(env, model)`.
112
- - `loadActiveProvider(env)` — resolve + instantiate in one call.
116
+ - `standardProviderFromEnv(name, env, model)` / `isStandardProvider(name)` tier-1 instantiation (below).
117
+
118
+ **Two-tier provider resolution.** A provider name resolves in this order:
119
+
120
+ 1. **Standard provider** (`§11`) — if `isStandardProvider(name)`, the framework instantiates it directly via `standardProviderFromEnv(name, env, model)`. No sibling package exists or is imported. Covers every plain OpenAI-compatible endpoint (`openai`, `groq`, `deepseek`, `mistral`, `together`, `fireworks`, `deepinfra`, …).
121
+ 2. **Bespoke sibling** — otherwise, dynamic-import `@plurnk/plurnk-providers-<provider>` and call `fromEnv(env, model)`. Reserved for providers with real runtime surface: a catalog/pricing probe or a non-OpenAI wire shape (`openrouter`, `ollama`, `google`, `xai`, `cloudflare`, the planned `anthropic`/`bedrock`/`vertex`/`cohere`).
122
+
123
+ Tier 1 lives here because it needs no package resolution. **Tier 2 is not shipped by this package.** Node's `import()` resolves specifiers relative to the calling module, so the dynamic-import step has to run where the sibling packages are actually installed — the consumer's `node_modules`, not this package's. The consumer owns it: in plurnk-service it is `src/core/ProviderInstantiate.ts` (`instantiateProvider` / `loadActiveProvider`). That code should try `standardProviderFromEnv` first and dynamic-import `@plurnk/plurnk-providers-<provider>` only when it returns `null`.
113
124
 
114
125
  ## §6 Engine → provider guarantees (consumer side)
115
126
 
@@ -124,7 +135,7 @@ Framework helpers (`./ProviderRegistry.ts`):
124
135
  - **No DB access.** Provider never touches `node:sqlite` or storage layers.
125
136
  - **No service access.** No imports from `@plurnk/plurnk-service`.
126
137
  - **No grammar runtime dep.** Type-imports from `@plurnk/plurnk-grammar` are fine; invoking `PlurnkParser.parse` is consumer-side.
127
- - **Raw `content`.** Native tool-call outputs MUST be normalized back to plurnk DSL string.
138
+ - **Raw `content`.** Returned verbatim. Tools are expressed in-body as plurnk DSL (see §2); providers do not use native tool-calling.
128
139
  - **Atomic.** One `generate` call resolves with one complete `ProviderResponse`. No streaming partial resolves (v0).
129
140
  - **Honors `signal`.** Aborted calls reject; resources free; no orphaned connections.
130
141
  - **Single model.** One provider instance speaks to one model.
@@ -180,3 +191,28 @@ A sibling package satisfies the contract when:
180
191
  12. No runtime import of `@plurnk/plurnk-grammar` parser entry points.
181
192
 
182
193
  Sibling-specific behavioral tests (wire-format compliance, model-family quirks, retry logic) live in each package's own test surface.
194
+
195
+ ## §11 Shared OpenAI-compatible machinery
196
+
197
+ The framework ships the transport spine every OpenAI-compatible provider had been duplicating. Build a sibling *on top of these* — don't re-implement them.
198
+
199
+ - **`OpenAICompatProvider`** — a `Provider` implementation built by composition. Its `generate` does the universal work (merge `signal` with a `PLURNK_FETCH_TIMEOUT` deadline, stream the completion, map `usage`, normalize `finishReason` to the §2 set, assemble the response). Per-provider deltas arrive as config:
200
+
201
+ ```ts
202
+ new OpenAICompatProvider({
203
+ model, url, // fully-resolved chat-completions URL
204
+ fetchTimeoutMs,
205
+ headers, // fully-resolved request headers (incl. auth)
206
+ contextSize, // number | null
207
+ reasonBudget, reasoningStyle, // "none" | "think" | "include_reasoning" | "effort"
208
+ countTokens, costFor, // strategies; default heuristic / free
209
+ });
210
+ ```
211
+
212
+ - **`chatCompletionStream` / `OpenAiHttpError` / `StreamResponse`** — the SSE client. One shared copy.
213
+ - **`normalizeUsage(raw)` / `computeCost(usage, {input, output, cached})`** — usage normalization to the §2 invariant (handles both reasoning-reporting conventions) and the single cost formula (bills `completion + reasoning` at the output rate). `OpenAICompatProvider` applies `normalizeUsage` automatically; siblings pass their per-token rates to `computeCost` in their `costFor`.
214
+ - **`parseRequiredInt` / `parseOptionalInt` / `requireEnv`** — env helpers; each takes a provider `label` for error prefixing.
215
+ - **`tokenizerFor(family)` / `tokenizerByPublisher(model, table, index)` / `parseTokenizerFamily(...)`** — synchronous tokenizer strategies (`heuristic` | `cl100k` | `llama`) and per-publisher dispatch for relay providers.
216
+ - **`effortFromBudget(budget)`** — the shared `PLURNK_REASON` → `low|medium|high` breakpoints.
217
+
218
+ A **bespoke sibling** therefore reduces to a thin class whose `fromEnv` probes whatever it needs (model catalog, pricing, context window), builds the config, and returns `new OpenAICompatProvider(config)`. A **standard provider** (§5 tier 1) needs no sibling at all — it's a frozen entry in `STANDARD_PROVIDERS` describing its key var, base URL, reasoning style, and tokenizer; `standardProviderFromEnv` does the rest.
package/dist/Mock.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { PlurnkStatement } from "@plurnk/plurnk-grammar";
2
- import type { ChatMessage, Provider, ProviderAssistant, ProviderUsage } from "./types.ts";
2
+ import type { ChatMessage, FinishReason, Provider, ProviderAssistant, ProviderUsage } from "./types.ts";
3
3
  export type MockAssistant = {
4
4
  content: string;
5
5
  reasoning: string | null;
6
- usage?: ProviderUsage;
7
- finishReason?: string | null;
6
+ usage?: Partial<ProviderUsage>;
7
+ finishReason?: FinishReason;
8
8
  model?: string;
9
9
  ops?: PlurnkStatement[];
10
10
  };
@@ -26,7 +26,7 @@ export default class Mock implements Provider {
26
26
  get model(): string;
27
27
  countTokens(text: string): number;
28
28
  costFor(_usage: ProviderUsage): number;
29
- generate(_: {
29
+ generate({ signal }: {
30
30
  messages: ChatMessage[];
31
31
  signal?: AbortSignal;
32
32
  }): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"Mock.d.ts","sourceRoot":"","sources":["../src/Mock.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1F,MAAM,MAAM,aAAa,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACvB,SAAS,EAAE,aAAa,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,GAAG;IAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAEpF,QAAA,MAAM,aAAa,EAAE,aAAiE,CAAC;AAEvF,MAAM,CAAC,OAAO,OAAO,IAAK,YAAW,QAAQ;;gBAI7B,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE;QAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,YAAY,EAAE,CAAA;KAAE;IAKjG,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAA8B;IAC9D,IAAI,KAAK,IAAI,MAAM,CAAmB;IAItC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKjC,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM;IAEhC,QAAQ,CAAC,CAAC,EAAE;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,qBAAqB,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAe1I,IAAI,SAAS,IAAI,MAAM,CAA+B;CACzD;AAED,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"Mock.d.ts","sourceRoot":"","sources":["../src/Mock.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExG,MAAM,MAAM,aAAa,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,KAAK,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACvB,SAAS,EAAE,aAAa,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,GAAG;IAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAEpF,QAAA,MAAM,aAAa,EAAE,aAA+E,CAAC;AAErG,MAAM,CAAC,OAAO,OAAO,IAAK,YAAW,QAAQ;;gBAI7B,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE;QAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,YAAY,EAAE,CAAA;KAAE;IAKjG,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAA8B;IAC9D,IAAI,KAAK,IAAI,MAAM,CAAmB;IAItC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAKjC,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM;IAEhC,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,qBAAqB,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAkBnJ,IAAI,SAAS,IAAI,MAAM,CAA+B;CACzD;AAED,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
package/dist/Mock.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // engine tests; (b) worked example for sibling authors implementing the
5
5
  // Provider contract. Production providers don't expose the `ops` escape
6
6
  // hatch — that's an intg-only convenience.
7
- const DEFAULT_USAGE = { prompt: 0, completion: 0, cached: 0, total: 0 };
7
+ const DEFAULT_USAGE = { prompt: 0, completion: 0, reasoning: 0, cached: 0, total: 0 };
8
8
  export default class Mock {
9
9
  #contextSize;
10
10
  #queue;
@@ -21,7 +21,10 @@ export default class Mock {
21
21
  }
22
22
  // Mock is free.
23
23
  costFor(_usage) { return 0; }
24
- async generate(_) {
24
+ async generate({ signal }) {
25
+ // Honor abort before consuming the queue — an aborted call makes no
26
+ // "wire call" and must not exhaust a queued response (SPEC §10.8).
27
+ signal?.throwIfAborted();
25
28
  const next = this.#queue.shift();
26
29
  if (next === undefined)
27
30
  throw new Error("Mock provider exhausted: no more queued responses");
@@ -29,7 +32,7 @@ export default class Mock {
29
32
  const assistant = {
30
33
  content: a.content,
31
34
  reasoning: a.reasoning,
32
- usage: a.usage ?? DEFAULT_USAGE,
35
+ usage: { ...DEFAULT_USAGE, ...a.usage },
33
36
  finishReason: a.finishReason ?? "stop",
34
37
  model: a.model ?? "mock",
35
38
  ...(a.ops !== undefined ? { ops: a.ops } : {}),
package/dist/Mock.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Mock.js","sourceRoot":"","sources":["../src/Mock.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AACxE,2CAA2C;AAwB3C,MAAM,aAAa,GAAkB,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAEvF,MAAM,CAAC,OAAO,OAAO,IAAI;IACrB,YAAY,CAAgB;IAC5B,MAAM,CAAiB;IAEvB,YAAY,EAAE,WAAW,EAAE,SAAS,EAA6D;QAC7F,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,WAAW,KAAoB,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9D,IAAI,KAAK,KAAa,OAAO,MAAM,CAAC,CAAC,CAAC;IAEtC,sEAAsE;IACtE,8BAA8B;IAC9B,WAAW,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,gBAAgB;IAChB,OAAO,CAAC,MAAqB,IAAY,OAAO,CAAC,CAAC,CAAC,CAAC;IAEpD,KAAK,CAAC,QAAQ,CAAC,CAAoD;QAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACjC,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC7F,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,MAAM,SAAS,GAA0B;YACrC,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,aAAa;YAC/B,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,MAAM;YACtC,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,MAAM;YACxB,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjD,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;IAClE,CAAC;IAED,IAAI,SAAS,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;CACzD;AAED,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"Mock.js","sourceRoot":"","sources":["../src/Mock.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AACxE,2CAA2C;AAyB3C,MAAM,aAAa,GAAkB,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAErG,MAAM,CAAC,OAAO,OAAO,IAAI;IACrB,YAAY,CAAgB;IAC5B,MAAM,CAAiB;IAEvB,YAAY,EAAE,WAAW,EAAE,SAAS,EAA6D;QAC7F,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,IAAI,WAAW,KAAoB,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9D,IAAI,KAAK,KAAa,OAAO,MAAM,CAAC,CAAC,CAAC;IAEtC,sEAAsE;IACtE,8BAA8B;IAC9B,WAAW,CAAC,IAAY;QACpB,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,gBAAgB;IAChB,OAAO,CAAC,MAAqB,IAAY,OAAO,CAAC,CAAC,CAAC,CAAC;IAEpD,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAqD;QACxE,oEAAoE;QACpE,mEAAmE;QACnE,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACjC,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC7F,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,MAAM,SAAS,GAA0B;YACrC,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,KAAK,EAAE,EAAE,GAAG,aAAa,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE;YACvC,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,MAAM;YACtC,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,MAAM;YACxB,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjD,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;IAClE,CAAC;IAED,IAAI,SAAS,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;CACzD;AAED,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { ChatMessage, Provider, ProviderResponse, ProviderUsage } from "./types.ts";
2
+ export type ReasoningStyle = "none" | "think" | "include_reasoning" | "effort";
3
+ export type OpenAICompatConfig = {
4
+ model: string;
5
+ url: string;
6
+ fetchTimeoutMs: number;
7
+ headers?: Record<string, string>;
8
+ contextSize?: number | null;
9
+ reasonBudget?: number;
10
+ reasoningStyle?: ReasoningStyle;
11
+ countTokens?: (text: string) => number;
12
+ costFor?: (usage: ProviderUsage) => number;
13
+ };
14
+ export declare const effortFromBudget: (budget: number) => "low" | "medium" | "high";
15
+ export default class OpenAICompatProvider implements Provider {
16
+ #private;
17
+ constructor(config: OpenAICompatConfig);
18
+ get contextSize(): number | null;
19
+ get model(): string;
20
+ countTokens(text: string): number;
21
+ costFor(usage: ProviderUsage): number;
22
+ generate({ messages, signal }: {
23
+ messages: ChatMessage[];
24
+ signal?: AbortSignal;
25
+ }): Promise<ProviderResponse>;
26
+ }
27
+ //# sourceMappingURL=OpenAICompat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenAICompat.d.ts","sourceRoot":"","sources":["../src/OpenAICompat.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAgB,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AASvG,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,mBAAmB,GAAG,QAAQ,CAAC;AAE/E,MAAM,MAAM,kBAAkB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,CAAC;CAC9C,CAAC;AASF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,MAAM,KAAG,KAAK,GAAG,QAAQ,GAAG,MAIpE,CAAC;AAIF,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,QAAQ;;gBAW7C,MAAM,EAAE,kBAAkB;IAYtC,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAA8B;IAC9D,IAAI,KAAK,IAAI,MAAM,CAAwB;IAE3C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IACjC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM;IAY/B,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAqBrH"}
@@ -0,0 +1,79 @@
1
+ // Shared OpenAI-compatible provider. Implements the universal generate()
2
+ // spine — signal merging, the SSE call, usage mapping, finishReason
3
+ // normalization, response assembly — that every sibling had duplicated.
4
+ //
5
+ // Composition, not inheritance: the per-provider deltas (resolved URL, auth
6
+ // headers, reasoning translation style, tokenizer, cost) arrive as config.
7
+ // A sibling's fromEnv probes whatever it needs (catalog, pricing, context
8
+ // window), builds the config, and returns `new OpenAICompatProvider(config)`.
9
+ // Pure-config providers come from ./standardProviders.ts with no sibling at all.
10
+ import { chatCompletionStream } from "./openaiStream.js";
11
+ import { normalizeUsage } from "./usage.js";
12
+ // SPEC §2 closed set. Wire values outside it (provider-specific or absent)
13
+ // collapse to null — the consumer treats null as "no signal".
14
+ const FINISH_REASONS = new Set(["stop", "length", "tool_calls", "content_filter"]);
15
+ const normalizeFinishReason = (raw) => raw !== null && FINISH_REASONS.has(raw) ? raw : null;
16
+ // Shared budget→effort breakpoints (xai and google had identical copies).
17
+ export const effortFromBudget = (budget) => {
18
+ if (budget <= 1000)
19
+ return "low";
20
+ if (budget <= 4000)
21
+ return "medium";
22
+ return "high";
23
+ };
24
+ const heuristicTokens = (text) => (text.length === 0 ? 0 : Math.ceil(text.length / 4));
25
+ export default class OpenAICompatProvider {
26
+ #model;
27
+ #url;
28
+ #fetchTimeoutMs;
29
+ #headers;
30
+ #contextSize;
31
+ #reasonBudget;
32
+ #reasoningStyle;
33
+ #countTokens;
34
+ #costFor;
35
+ constructor(config) {
36
+ this.#model = config.model;
37
+ this.#url = config.url;
38
+ this.#fetchTimeoutMs = config.fetchTimeoutMs;
39
+ this.#headers = config.headers ?? {};
40
+ this.#contextSize = config.contextSize ?? null;
41
+ this.#reasonBudget = config.reasonBudget ?? 0;
42
+ this.#reasoningStyle = config.reasoningStyle ?? "none";
43
+ this.#countTokens = config.countTokens ?? heuristicTokens;
44
+ this.#costFor = config.costFor ?? (() => 0);
45
+ }
46
+ get contextSize() { return this.#contextSize; }
47
+ get model() { return this.#model; }
48
+ countTokens(text) { return this.#countTokens(text); }
49
+ costFor(usage) { return this.#costFor(usage); }
50
+ #reasoningBody() {
51
+ if (this.#reasonBudget <= 0)
52
+ return {};
53
+ switch (this.#reasoningStyle) {
54
+ case "think": return { think: true };
55
+ case "include_reasoning": return { include_reasoning: true };
56
+ case "effort": return { reasoning_effort: effortFromBudget(this.#reasonBudget) };
57
+ case "none": return {};
58
+ }
59
+ }
60
+ async generate({ messages, signal }) {
61
+ // Reject before any wire call when already aborted (SPEC §10.8).
62
+ signal?.throwIfAborted();
63
+ const timeoutSignal = AbortSignal.timeout(this.#fetchTimeoutMs);
64
+ const effectiveSignal = signal !== undefined ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
65
+ const body = { model: this.#model, messages, ...this.#reasoningBody() };
66
+ const raw = await chatCompletionStream({ url: this.#url, headers: this.#headers, body, signal: effectiveSignal });
67
+ return {
68
+ assistant: {
69
+ content: raw.content,
70
+ reasoning: raw.reasoning_content.length > 0 ? raw.reasoning_content : null,
71
+ usage: normalizeUsage(raw.usage),
72
+ finishReason: normalizeFinishReason(raw.finish_reason),
73
+ model: raw.model ?? this.#model,
74
+ },
75
+ assistantRaw: raw,
76
+ };
77
+ }
78
+ }
79
+ //# sourceMappingURL=OpenAICompat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OpenAICompat.js","sourceRoot":"","sources":["../src/OpenAICompat.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../src/ProviderRegistry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAO,MAAM,mBAAmB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,aAAa,EAgBvF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,aAAa,GAAG,IAKzF,CAAC"}
1
+ {"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../src/ProviderRegistry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAO,MAAM,mBAAmB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,aAAa,EAkBvF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,aAAa,GAAG,IAKzF,CAAC"}
@@ -9,6 +9,7 @@
9
9
  // env-parsing helpers.
10
10
  export const parseAliasesFromEnv = (env = process.env) => {
11
11
  const out = [];
12
+ const seen = new Set();
12
13
  for (const [key, value] of Object.entries(env)) {
13
14
  if (value === undefined || value.length === 0)
14
15
  continue;
@@ -20,11 +21,13 @@ export const parseAliasesFromEnv = (env = process.env) => {
20
21
  const slash = value.indexOf("/");
21
22
  if (slash <= 0)
22
23
  continue;
23
- out.push({
24
- alias: aliasRaw.toLowerCase(),
25
- provider: value.slice(0, slash),
26
- model: value.slice(slash + 1),
27
- });
24
+ const alias = aliasRaw.toLowerCase();
25
+ // Aliases are case-folded, so PLURNK_MODEL_opus and PLURNK_MODEL_OPUS
26
+ // collide. Surface the ambiguity rather than silently picking one.
27
+ if (seen.has(alias))
28
+ throw new Error(`Duplicate provider alias "${alias}": multiple PLURNK_MODEL_* keys case-fold to the same alias. Rename one.`);
29
+ seen.add(alias);
30
+ out.push({ alias, provider: value.slice(0, slash), model: value.slice(slash + 1) });
28
31
  }
29
32
  return out;
30
33
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderRegistry.js","sourceRoot":"","sources":["../src/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,kEAAkE;AAClE,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,oEAAoE;AACpE,6EAA6E;AAC7E,yEAAyE;AACzE,uBAAuB;AAIvB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAmB,EAAE;IACzF,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACxD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC;YAAE,SAAS;QAC/C,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,IAAI,CAAC;YAAE,SAAS;QACzB,GAAG,CAAC,IAAI,CAAC;YACL,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE;YAC7B,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;YAC/B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;SAChC,CAAC,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAwB,EAAE;IAC7F,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC;IAClC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAC;AAC3E,CAAC,CAAC"}
1
+ {"version":3,"file":"ProviderRegistry.js","sourceRoot":"","sources":["../src/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,kEAAkE;AAClE,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,oEAAoE;AACpE,6EAA6E;AAC7E,yEAAyE;AACzE,uBAAuB;AAIvB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAmB,EAAE;IACzF,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACxD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC;YAAE,SAAS;QAC/C,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAK,IAAI,CAAC;YAAE,SAAS;QACzB,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QACrC,sEAAsE;QACtE,mEAAmE;QACnE,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,0EAA0E,CAAC,CAAC;QACnJ,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAyB,OAAO,CAAC,GAAG,EAAwB,EAAE;IAC7F,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC;IAClC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,OAAO,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAC;AAC3E,CAAC,CAAC"}
package/dist/env.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export declare const parseRequiredInt: (raw: string | undefined, name: string, label: string) => number;
2
+ export declare const parseOptionalInt: (raw: string | undefined, name: string, label: string) => number | null;
3
+ export declare const requireEnv: (raw: string | undefined, name: string, label: string) => string;
4
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,GAAG,SAAS,EAAE,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MAKvF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,GAAG,SAAS,EAAE,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MAAM,GAAG,IAKhG,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,GAAG,SAAS,EAAE,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,MAGjF,CAAC"}
package/dist/env.js ADDED
@@ -0,0 +1,25 @@
1
+ // Env-parsing helpers shared by every provider's fromEnv factory. Each was
2
+ // copy-pasted per sibling with only the error-message prefix differing; the
3
+ // `label` parameter (the provider name) restores that prefix from one source.
4
+ export const parseRequiredInt = (raw, name, label) => {
5
+ if (raw === undefined || raw.length === 0)
6
+ throw new Error(`${label} provider: ${name} must be set`);
7
+ const n = Number(raw);
8
+ if (!Number.isFinite(n))
9
+ throw new Error(`${label} provider: ${name} must be a number (got "${raw}")`);
10
+ return n;
11
+ };
12
+ export const parseOptionalInt = (raw, name, label) => {
13
+ if (raw === undefined || raw.length === 0)
14
+ return null;
15
+ const n = Number(raw);
16
+ if (!Number.isFinite(n))
17
+ throw new Error(`${label} provider: ${name} must be a number (got "${raw}")`);
18
+ return n;
19
+ };
20
+ export const requireEnv = (raw, name, label) => {
21
+ if (raw === undefined || raw.length === 0)
22
+ throw new Error(`${label} provider: ${name} must be set`);
23
+ return raw;
24
+ };
25
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAE9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAuB,EAAE,IAAY,EAAE,KAAa,EAAU,EAAE;IAC7F,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,cAAc,IAAI,cAAc,CAAC,CAAC;IACrG,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,cAAc,IAAI,2BAA2B,GAAG,IAAI,CAAC,CAAC;IACvG,OAAO,CAAC,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,GAAuB,EAAE,IAAY,EAAE,KAAa,EAAiB,EAAE;IACpG,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,cAAc,IAAI,2BAA2B,GAAG,IAAI,CAAC,CAAC;IACvG,OAAO,CAAC,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAuB,EAAE,IAAY,EAAE,KAAa,EAAU,EAAE;IACvF,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,cAAc,IAAI,cAAc,CAAC,CAAC;IACrG,OAAO,GAAG,CAAC;AACf,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,15 @@
1
- export type { ChatMessage, Provider, ProviderAlias, ProviderAssistant, ProviderFactory, ProviderResponse, ProviderUsage, } from "./types.ts";
1
+ export type { ChatMessage, FinishReason, Provider, ProviderAlias, ProviderAssistant, ProviderFactory, ProviderResponse, ProviderUsage, } from "./types.ts";
2
2
  export { parseAliasesFromEnv, resolveActiveAlias, } from "./ProviderRegistry.ts";
3
+ export { default as OpenAICompatProvider, effortFromBudget } from "./OpenAICompat.ts";
4
+ export type { OpenAICompatConfig, ReasoningStyle } from "./OpenAICompat.ts";
5
+ export { chatCompletionStream, OpenAiHttpError } from "./openaiStream.ts";
6
+ export type { StreamResponse } from "./openaiStream.ts";
7
+ export { parseRequiredInt, parseOptionalInt, requireEnv } from "./env.ts";
8
+ export { tokenizerFor, tokenizerByPublisher, parseTokenizerFamily } from "./tokenizers.ts";
9
+ export type { TokenizerFamily, CountTokens } from "./tokenizers.ts";
10
+ export { normalizeUsage, computeCost } from "./usage.ts";
11
+ export type { RawUsage, TokenRates } from "./usage.ts";
12
+ export { STANDARD_PROVIDERS, isStandardProvider, standardProviderFromEnv } from "./standardProviders.ts";
3
13
  export { default as Mock } from "./Mock.ts";
4
14
  export type { MockAssistant, MockResponse, MockReturnedAssistant } from "./Mock.ts";
5
15
  export { mockDefaultUsage } from "./Mock.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,WAAW,EACX,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,aAAa,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACH,mBAAmB,EACnB,kBAAkB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,aAAa,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACH,mBAAmB,EACnB,kBAAkB,GACrB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACtF,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3F,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzG,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,12 @@
1
1
  export { parseAliasesFromEnv, resolveActiveAlias, } from "./ProviderRegistry.js";
2
+ // Shared OpenAI-compatible transport machinery — the spine every sibling
3
+ // extends and the basis for ./standardProviders.ts.
4
+ export { default as OpenAICompatProvider, effortFromBudget } from "./OpenAICompat.js";
5
+ export { chatCompletionStream, OpenAiHttpError } from "./openaiStream.js";
6
+ export { parseRequiredInt, parseOptionalInt, requireEnv } from "./env.js";
7
+ export { tokenizerFor, tokenizerByPublisher, parseTokenizerFamily } from "./tokenizers.js";
8
+ export { normalizeUsage, computeCost } from "./usage.js";
9
+ export { STANDARD_PROVIDERS, isStandardProvider, standardProviderFromEnv } from "./standardProviders.js";
2
10
  export { default as Mock } from "./Mock.js";
3
11
  export { mockDefaultUsage } from "./Mock.js";
4
12
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EACH,mBAAmB,EACnB,kBAAkB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACH,mBAAmB,EACnB,kBAAkB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,yEAAyE;AACzE,oDAAoD;AACpD,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEtF,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE1E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE3F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzG,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,24 @@
1
+ type StreamRequest = {
2
+ url: string;
3
+ headers: Record<string, string>;
4
+ body: Record<string, unknown>;
5
+ signal: AbortSignal;
6
+ };
7
+ import type { RawUsage } from "./usage.ts";
8
+ export type StreamResponse = {
9
+ model: string | null;
10
+ content: string;
11
+ reasoning_content: string;
12
+ finish_reason: string | null;
13
+ usage: RawUsage | null;
14
+ chunkMetadata: Record<string, unknown>;
15
+ };
16
+ export declare class OpenAiHttpError extends Error {
17
+ readonly status: number;
18
+ readonly body: string;
19
+ readonly retryAfter: number | null;
20
+ constructor(status: number, body: string, retryAfter: number | null);
21
+ }
22
+ export declare const chatCompletionStream: ({ url, headers, body, signal }: StreamRequest) => Promise<StreamResponse>;
23
+ export {};
24
+ //# sourceMappingURL=openaiStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiStream.d.ts","sourceRoot":"","sources":["../src/openaiStream.ts"],"names":[],"mappings":"AAMA,KAAK,aAAa,GAAG;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,WAAW,CAAC;CACvB,CAAC;AAEF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,cAAc,GAAG;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C,CAAC;AAEF,qBAAa,eAAgB,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;gBACvB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;CAMtE;AAWD,eAAO,MAAM,oBAAoB,GAAU,gCAAgC,aAAa,KAAG,OAAO,CAAC,cAAc,CAmEhH,CAAC"}
@@ -0,0 +1,103 @@
1
+ // SSE client for OpenAI-compatible /chat/completions. Streaming keeps long
2
+ // completions alive through CDN proxies; the aggregated result is returned as
3
+ // one StreamResponse (the Provider contract is atomic — no partial resolves).
4
+ // Adapted from rummy's proven implementation; previously copy-pasted byte-for-
5
+ // byte into every @plurnk/plurnk-providers-* sibling, now shared from here.
6
+ export class OpenAiHttpError extends Error {
7
+ status;
8
+ body;
9
+ retryAfter;
10
+ constructor(status, body, retryAfter) {
11
+ super(`OpenAI ${status} - ${body}`);
12
+ this.status = status;
13
+ this.body = body;
14
+ this.retryAfter = retryAfter;
15
+ }
16
+ }
17
+ const parseRetryAfter = (header) => {
18
+ if (header === null)
19
+ return null;
20
+ const asInt = Number.parseInt(header, 10);
21
+ if (Number.isFinite(asInt))
22
+ return asInt * 1000;
23
+ const asDate = Date.parse(header);
24
+ if (Number.isFinite(asDate))
25
+ return Math.max(0, asDate - Date.now());
26
+ return null;
27
+ };
28
+ export const chatCompletionStream = async ({ url, headers, body, signal }) => {
29
+ const requestBody = { ...body, stream: true, stream_options: { include_usage: true } };
30
+ const response = await fetch(url, {
31
+ method: "POST",
32
+ headers: { "Content-Type": "application/json", ...headers },
33
+ body: JSON.stringify(requestBody),
34
+ signal,
35
+ });
36
+ if (!response.ok) {
37
+ const errorBody = await response.text();
38
+ throw new OpenAiHttpError(response.status, errorBody, parseRetryAfter(response.headers.get("retry-after")));
39
+ }
40
+ if (response.body === null)
41
+ throw new Error("OpenAI response body is null");
42
+ const reader = response.body.getReader();
43
+ const decoder = new TextDecoder();
44
+ let buffer = "";
45
+ let content = "";
46
+ let reasoning_content = "";
47
+ let usage = null;
48
+ let model = null;
49
+ let finish_reason = null;
50
+ const chunkMetadata = {};
51
+ while (true) {
52
+ const { done, value } = await reader.read();
53
+ if (done)
54
+ break;
55
+ buffer += decoder.decode(value, { stream: true });
56
+ const lines = buffer.split("\n");
57
+ buffer = lines.pop() ?? "";
58
+ for (const rawLine of lines) {
59
+ const line = rawLine.trim();
60
+ if (!line.startsWith("data:"))
61
+ continue;
62
+ const payload = line.slice(5).trimStart();
63
+ if (payload === "[DONE]" || payload === "")
64
+ continue;
65
+ let chunk;
66
+ try {
67
+ chunk = JSON.parse(payload);
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ if (typeof chunk.model === "string")
73
+ model = chunk.model;
74
+ if (chunk.usage !== undefined && chunk.usage !== null)
75
+ usage = chunk.usage;
76
+ for (const [k, v] of Object.entries(chunk)) {
77
+ if (k === "choices" || k === "usage")
78
+ continue;
79
+ chunkMetadata[k] = v;
80
+ }
81
+ const choices = chunk.choices;
82
+ const choice = choices?.[0];
83
+ if (choice === undefined)
84
+ continue;
85
+ if (typeof choice.finish_reason === "string")
86
+ finish_reason = choice.finish_reason;
87
+ const delta = choice.delta;
88
+ if (delta === undefined)
89
+ continue;
90
+ if (typeof delta.content === "string")
91
+ content += delta.content;
92
+ // Reasoning surfaces under different field names per provider.
93
+ if (typeof delta.reasoning_content === "string")
94
+ reasoning_content += delta.reasoning_content;
95
+ if (typeof delta.reasoning === "string")
96
+ reasoning_content += delta.reasoning;
97
+ if (typeof delta.thinking === "string")
98
+ reasoning_content += delta.thinking;
99
+ }
100
+ }
101
+ return { model, content, reasoning_content, finish_reason, usage, chunkMetadata };
102
+ };
103
+ //# sourceMappingURL=openaiStream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiStream.js","sourceRoot":"","sources":["../src/openaiStream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Provider } from "./types.ts";
2
+ import { type ReasoningStyle } from "./OpenAICompat.ts";
3
+ import { type TokenizerFamily } from "./tokenizers.ts";
4
+ type StandardProviderSpec = {
5
+ apiKeyVar: string;
6
+ apiKeyRequired: boolean;
7
+ baseUrl?: string;
8
+ baseUrlVar?: string;
9
+ chatPath: string;
10
+ flexBaseStrip?: boolean;
11
+ reasoningStyle: ReasoningStyle;
12
+ tokenizerDefault: TokenizerFamily;
13
+ tokenizerEnvVar: string;
14
+ };
15
+ export declare const STANDARD_PROVIDERS: Readonly<Record<string, StandardProviderSpec>>;
16
+ export declare const isStandardProvider: (name: string) => boolean;
17
+ export declare const standardProviderFromEnv: (name: string, env: NodeJS.ProcessEnv, model: string) => Provider | null;
18
+ export {};
19
+ //# sourceMappingURL=standardProviders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standardProviders.d.ts","sourceRoot":"","sources":["../src/standardProviders.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAA6B,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE9E,OAAO,EAAsC,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE3F,KAAK,oBAAoB,GAAG;IAGxB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;IAGxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,QAAQ,EAAE,MAAM,CAAC;IAIjB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,MAAM,CAAC;CAC3B,CAAC;AAGF,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAuC5E,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,OAAqC,CAAC;AAcxF,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,CAAC,UAAU,EAAE,OAAO,MAAM,KAAG,QAAQ,GAAG,IAuBxG,CAAC"}
@@ -0,0 +1,88 @@
1
+ // Pure-config OpenAI-compatible providers. A provider qualifies as "standard"
2
+ // when it has no unique runtime surface — no catalog probe, no pricing fetch,
3
+ // no bespoke wire shape — so it reduces to: an env var for the key, a base
4
+ // URL, a reasoning-translation style, and a tokenizer. Such providers need NO
5
+ // sibling package; the framework instantiates them directly.
6
+ //
7
+ // Two-tier resolution (SPEC §5): the consumer tries standardProviderFromEnv
8
+ // first, then falls back to dynamic-importing @plurnk/plurnk-providers-<name>
9
+ // for the bespoke ones (openrouter, ollama, google, xai, cloudflare, ...).
10
+ import OpenAICompatProvider from "./OpenAICompat.js";
11
+ import { parseRequiredInt, parseOptionalInt, requireEnv } from "./env.js";
12
+ import { parseTokenizerFamily, tokenizerFor } from "./tokenizers.js";
13
+ // Frozen so a downstream can't mutate the shared table.
14
+ export const STANDARD_PROVIDERS = Object.freeze({
15
+ // Generic OpenAI-compatible endpoint (OpenAI proper, llama-server, vLLM,
16
+ // LM Studio, or any chat-completions shim). Operator supplies the base.
17
+ // Replaces the former @plurnk/plurnk-providers-openai sibling verbatim.
18
+ openai: {
19
+ apiKeyVar: "OPENAI_API_KEY", apiKeyRequired: false,
20
+ baseUrlVar: "OPENAI_BASE_URL", chatPath: "/v1/chat/completions", flexBaseStrip: true,
21
+ reasoningStyle: "think", tokenizerDefault: "heuristic", tokenizerEnvVar: "OPENAI_TOKENIZER",
22
+ },
23
+ groq: {
24
+ apiKeyVar: "GROQ_API_KEY", apiKeyRequired: true,
25
+ baseUrl: "https://api.groq.com/openai/v1", baseUrlVar: "GROQ_BASE_URL", chatPath: "/chat/completions",
26
+ reasoningStyle: "effort", tokenizerDefault: "heuristic", tokenizerEnvVar: "GROQ_TOKENIZER",
27
+ },
28
+ deepseek: {
29
+ apiKeyVar: "DEEPSEEK_API_KEY", apiKeyRequired: true,
30
+ baseUrl: "https://api.deepseek.com/v1", baseUrlVar: "DEEPSEEK_BASE_URL", chatPath: "/chat/completions",
31
+ reasoningStyle: "none", tokenizerDefault: "heuristic", tokenizerEnvVar: "DEEPSEEK_TOKENIZER",
32
+ },
33
+ mistral: {
34
+ apiKeyVar: "MISTRAL_API_KEY", apiKeyRequired: true,
35
+ baseUrl: "https://api.mistral.ai/v1", baseUrlVar: "MISTRAL_BASE_URL", chatPath: "/chat/completions",
36
+ reasoningStyle: "none", tokenizerDefault: "heuristic", tokenizerEnvVar: "MISTRAL_TOKENIZER",
37
+ },
38
+ together: {
39
+ apiKeyVar: "TOGETHER_API_KEY", apiKeyRequired: true,
40
+ baseUrl: "https://api.together.xyz/v1", baseUrlVar: "TOGETHER_BASE_URL", chatPath: "/chat/completions",
41
+ reasoningStyle: "none", tokenizerDefault: "heuristic", tokenizerEnvVar: "TOGETHER_TOKENIZER",
42
+ },
43
+ fireworks: {
44
+ apiKeyVar: "FIREWORKS_API_KEY", apiKeyRequired: true,
45
+ baseUrl: "https://api.fireworks.ai/inference/v1", baseUrlVar: "FIREWORKS_BASE_URL", chatPath: "/chat/completions",
46
+ reasoningStyle: "none", tokenizerDefault: "heuristic", tokenizerEnvVar: "FIREWORKS_TOKENIZER",
47
+ },
48
+ deepinfra: {
49
+ apiKeyVar: "DEEPINFRA_API_KEY", apiKeyRequired: true,
50
+ baseUrl: "https://api.deepinfra.com/v1/openai", baseUrlVar: "DEEPINFRA_BASE_URL", chatPath: "/chat/completions",
51
+ reasoningStyle: "none", tokenizerDefault: "heuristic", tokenizerEnvVar: "DEEPINFRA_TOKENIZER",
52
+ },
53
+ });
54
+ export const isStandardProvider = (name) => name in STANDARD_PROVIDERS;
55
+ const resolveUrl = (spec, env, label) => {
56
+ const override = spec.baseUrlVar !== undefined ? env[spec.baseUrlVar] : undefined;
57
+ const base = override !== undefined && override.length > 0 ? override : spec.baseUrl;
58
+ if (base === undefined || base.length === 0) {
59
+ throw new Error(`${label} provider: ${spec.baseUrlVar ?? "base URL"} must be set`);
60
+ }
61
+ const trimmed = spec.flexBaseStrip === true ? base.replace(/\/v1\/?$/, "") : base.replace(/\/$/, "");
62
+ return `${trimmed}${spec.chatPath}`;
63
+ };
64
+ // Returns a configured Provider, or null when `name` is not a standard
65
+ // provider (so the consumer falls through to dynamic import).
66
+ export const standardProviderFromEnv = (name, env, model) => {
67
+ const spec = STANDARD_PROVIDERS[name];
68
+ if (spec === undefined)
69
+ return null;
70
+ const apiKey = spec.apiKeyRequired
71
+ ? requireEnv(env[spec.apiKeyVar], spec.apiKeyVar, name)
72
+ : env[spec.apiKeyVar] ?? "";
73
+ const headers = {};
74
+ if (apiKey.length > 0)
75
+ headers.Authorization = `Bearer ${apiKey}`;
76
+ const family = parseTokenizerFamily(env[spec.tokenizerEnvVar], spec.tokenizerDefault, spec.tokenizerEnvVar, name);
77
+ return new OpenAICompatProvider({
78
+ model,
79
+ url: resolveUrl(spec, env, name),
80
+ headers,
81
+ contextSize: parseOptionalInt(env.PLURNK_PROVIDER_CONTEXT_SIZE, "PLURNK_PROVIDER_CONTEXT_SIZE", name),
82
+ fetchTimeoutMs: parseRequiredInt(env.PLURNK_FETCH_TIMEOUT, "PLURNK_FETCH_TIMEOUT", name),
83
+ reasonBudget: parseRequiredInt(env.PLURNK_REASON, "PLURNK_REASON", name),
84
+ reasoningStyle: spec.reasoningStyle,
85
+ countTokens: tokenizerFor(family),
86
+ });
87
+ };
88
+ //# sourceMappingURL=standardProviders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standardProviders.js","sourceRoot":"","sources":["../src/standardProviders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export type TokenizerFamily = "heuristic" | "cl100k" | "llama";
2
+ export type CountTokens = (text: string) => number;
3
+ export declare const tokenizerFor: (family: TokenizerFamily) => CountTokens;
4
+ export declare const parseTokenizerFamily: (raw: string | undefined, fallback: TokenizerFamily, envName: string, label: string) => TokenizerFamily;
5
+ export declare const tokenizerByPublisher: (model: string, table: ReadonlyMap<string, TokenizerFamily>, index?: number) => TokenizerFamily;
6
+ //# sourceMappingURL=tokenizers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizers.d.ts","sourceRoot":"","sources":["../src/tokenizers.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/D,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAQnD,eAAO,MAAM,YAAY,GAAI,QAAQ,eAAe,KAAG,WAAiC,CAAC;AAKzF,eAAO,MAAM,oBAAoB,GAAI,KAAK,MAAM,GAAG,SAAS,EAAE,UAAU,eAAe,EAAE,SAAS,MAAM,EAAE,OAAO,MAAM,KAAG,eAKzH,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC7B,OAAO,MAAM,EACb,OAAO,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,EAC3C,cAAS,KACV,eAGF,CAAC"}
@@ -0,0 +1,36 @@
1
+ // Tokenizer strategies shared by every provider's countTokens(). Centralizes
2
+ // the gpt-tokenizer / llama-tokenizer-js deps (each sibling pulled them in
3
+ // separately) and the per-publisher dispatch maps that openrouter and
4
+ // cloudflare had independently re-invented.
5
+ //
6
+ // countTokens is synchronous by contract (SPEC §2), so only client-side
7
+ // tokenizers qualify. "heuristic" (chars/4) is the safe default for any
8
+ // upstream whose tokenizer we can't run locally.
9
+ import { encode as encodeCl100k } from "gpt-tokenizer/encoding/cl100k_base";
10
+ import llamaTokenizer from "llama-tokenizer-js";
11
+ const heuristic = (text) => (text.length === 0 ? 0 : Math.ceil(text.length / 4));
12
+ const cl100k = (text) => (text.length === 0 ? 0 : encodeCl100k(text).length);
13
+ const llama = (text) => (text.length === 0 ? 0 : llamaTokenizer.encode(text).length);
14
+ const STRATEGIES = Object.freeze({ heuristic, cl100k, llama });
15
+ export const tokenizerFor = (family) => STRATEGIES[family];
16
+ // Parse an operator-declared tokenizer override (e.g. OPENAI_TOKENIZER).
17
+ // Accepts a legacy "cl100k_base" spelling for back-compat with the openai
18
+ // sibling's existing env contract.
19
+ export const parseTokenizerFamily = (raw, fallback, envName, label) => {
20
+ if (raw === undefined || raw.length === 0)
21
+ return fallback;
22
+ if (raw === "cl100k_base")
23
+ return "cl100k";
24
+ if (raw === "heuristic" || raw === "cl100k" || raw === "llama")
25
+ return raw;
26
+ throw new Error(`${label} provider: ${envName} must be one of "heuristic", "cl100k", "llama" (got "${raw}")`);
27
+ };
28
+ // Dispatch a tokenizer family from a model id's publisher segment. Relay
29
+ // providers (openrouter, cloudflare) route across many upstream families, so
30
+ // the family is read from the id prefix. `index` selects which "/"-segment
31
+ // holds the publisher (0 for "anthropic/claude…", 1 for "@cf/meta/llama…").
32
+ export const tokenizerByPublisher = (model, table, index = 0) => {
33
+ const publisher = model.split("/")[index];
34
+ return publisher !== undefined ? table.get(publisher) ?? "heuristic" : "heuristic";
35
+ };
36
+ //# sourceMappingURL=tokenizers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizers.js","sourceRoot":"","sources":["../src/tokenizers.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,2EAA2E;AAC3E,sEAAsE;AACtE,4CAA4C;AAC5C,EAAE;AACF,wEAAwE;AACxE,wEAAwE;AACxE,iDAAiD;AAEjD,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,cAAc,MAAM,oBAAoB,CAAC;AAMhD,MAAM,SAAS,GAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAC9F,MAAM,MAAM,GAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1F,MAAM,KAAK,GAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;AAElG,MAAM,UAAU,GAAmD,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAE/G,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAuB,EAAe,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAEzF,yEAAyE;AACzE,0EAA0E;AAC1E,mCAAmC;AACnC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAuB,EAAE,QAAyB,EAAE,OAAe,EAAE,KAAa,EAAmB,EAAE;IACxI,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC3D,IAAI,GAAG,KAAK,aAAa;QAAE,OAAO,QAAQ,CAAC;IAC3C,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC;IAC3E,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,cAAc,OAAO,wDAAwD,GAAG,IAAI,CAAC,CAAC;AAClH,CAAC,CAAC;AAEF,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAChC,KAAa,EACb,KAA2C,EAC3C,KAAK,GAAG,CAAC,EACM,EAAE;IACjB,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;AACvF,CAAC,CAAC"}
package/dist/types.d.ts CHANGED
@@ -5,14 +5,16 @@ export interface ChatMessage {
5
5
  export interface ProviderUsage {
6
6
  readonly prompt: number;
7
7
  readonly completion: number;
8
+ readonly reasoning: number;
8
9
  readonly cached: number;
9
10
  readonly total: number;
10
11
  }
12
+ export type FinishReason = "stop" | "length" | "tool_calls" | "content_filter" | null;
11
13
  export interface ProviderAssistant {
12
14
  readonly content: string;
13
15
  readonly reasoning: string | null;
14
16
  readonly usage: ProviderUsage;
15
- readonly finishReason: string | null;
17
+ readonly finishReason: FinishReason;
16
18
  readonly model: string;
17
19
  }
18
20
  export interface ProviderResponse {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAI7F,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAGlC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAMD,MAAM,WAAW,eAAe;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAID,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,gBAAgB,GAAG,IAAI,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAI7F,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAGlC,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAMD,MAAM,WAAW,eAAe;IAC5B,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAChF"}
@@ -0,0 +1,21 @@
1
+ import type { ProviderUsage } from "./types.ts";
2
+ export type RawUsage = {
3
+ prompt_tokens?: number;
4
+ completion_tokens?: number;
5
+ total_tokens?: number;
6
+ cached_tokens?: number;
7
+ prompt_tokens_details?: {
8
+ cached_tokens?: number;
9
+ };
10
+ completion_tokens_details?: {
11
+ reasoning_tokens?: number;
12
+ };
13
+ };
14
+ export declare const normalizeUsage: (raw: RawUsage | null | undefined) => ProviderUsage;
15
+ export type TokenRates = {
16
+ input: number;
17
+ output: number;
18
+ cached: number;
19
+ };
20
+ export declare const computeCost: (usage: ProviderUsage, rates: TokenRates) => number;
21
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,MAAM,MAAM,QAAQ,GAAG;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,yBAAyB,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,QAAQ,GAAG,IAAI,GAAG,SAAS,KAAG,aAsBjE,CAAC;AAGF,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAK3E,eAAO,MAAM,WAAW,GAAI,OAAO,aAAa,EAAE,OAAO,UAAU,KAAG,MAIrE,CAAC"}
package/dist/usage.js ADDED
@@ -0,0 +1,42 @@
1
+ // Usage normalization + cost — the shared token-accounting model.
2
+ //
3
+ // Providers report token usage in two incompatible ways:
4
+ // - OpenAI-style: reasoning is a SUBSET of completion_tokens, surfaced via
5
+ // completion_tokens_details.reasoning_tokens; total = prompt + completion.
6
+ // - Gemini-style: reasoning is OMITTED from completion_tokens and only
7
+ // recoverable as total - prompt - completion (no details field at all).
8
+ // normalizeUsage collapses both into one invariant (see ProviderUsage):
9
+ // total = prompt + completion + reasoning; cached ⊆ prompt;
10
+ // completion EXCLUDES reasoning; billable output = completion + reasoning.
11
+ export const normalizeUsage = (raw) => {
12
+ const prompt = raw?.prompt_tokens ?? 0;
13
+ const completionRaw = raw?.completion_tokens ?? 0;
14
+ const reportedTotal = raw?.total_tokens ?? 0;
15
+ // OpenAI nests cached under prompt_tokens_details; others put it top-level.
16
+ const cached = raw?.prompt_tokens_details?.cached_tokens ?? raw?.cached_tokens ?? 0;
17
+ const reasoningDetail = raw?.completion_tokens_details?.reasoning_tokens;
18
+ let completion;
19
+ let reasoning;
20
+ if (reasoningDetail !== undefined) {
21
+ // OpenAI-style: reasoning is part of completion_tokens — split it out.
22
+ reasoning = reasoningDetail;
23
+ completion = Math.max(0, completionRaw - reasoningDetail);
24
+ }
25
+ else {
26
+ // Gemini-style (or no reasoning): tokens beyond prompt+completion are
27
+ // reasoning. Only trust the gap when a total was actually reported.
28
+ reasoning = reportedTotal > 0 ? Math.max(0, reportedTotal - prompt - completionRaw) : 0;
29
+ completion = completionRaw;
30
+ }
31
+ const total = reportedTotal > 0 ? reportedTotal : prompt + completion + reasoning;
32
+ return { prompt, completion, reasoning, cached, total };
33
+ };
34
+ // The one cost formula every provider uses: non-cached prompt at the input
35
+ // rate, cached prompt at the cache rate, and billable output (completion +
36
+ // reasoning) at the output rate.
37
+ export const computeCost = (usage, rates) => {
38
+ const nonCachedPrompt = Math.max(0, usage.prompt - usage.cached);
39
+ const output = usage.completion + usage.reasoning;
40
+ return Math.round(nonCachedPrompt * rates.input + usage.cached * rates.cached + output * rates.output);
41
+ };
42
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,yDAAyD;AACzD,6EAA6E;AAC7E,+EAA+E;AAC/E,yEAAyE;AACzE,4EAA4E;AAC5E,wEAAwE;AACxE,+DAA+D;AAC/D,8EAA8E;AAc9E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAgC,EAAiB,EAAE;IAC9E,MAAM,MAAM,GAAG,GAAG,EAAE,aAAa,IAAI,CAAC,CAAC;IACvC,MAAM,aAAa,GAAG,GAAG,EAAE,iBAAiB,IAAI,CAAC,CAAC;IAClD,MAAM,aAAa,GAAG,GAAG,EAAE,YAAY,IAAI,CAAC,CAAC;IAC7C,4EAA4E;IAC5E,MAAM,MAAM,GAAG,GAAG,EAAE,qBAAqB,EAAE,aAAa,IAAI,GAAG,EAAE,aAAa,IAAI,CAAC,CAAC;IACpF,MAAM,eAAe,GAAG,GAAG,EAAE,yBAAyB,EAAE,gBAAgB,CAAC;IAEzE,IAAI,UAAkB,CAAC;IACvB,IAAI,SAAiB,CAAC;IACtB,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAChC,uEAAuE;QACvE,SAAS,GAAG,eAAe,CAAC;QAC5B,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,eAAe,CAAC,CAAC;IAC9D,CAAC;SAAM,CAAC;QACJ,sEAAsE;QACtE,oEAAoE;QACpE,SAAS,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxF,UAAU,GAAG,aAAa,CAAC;IAC/B,CAAC;IACD,MAAM,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAClF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC5D,CAAC,CAAC;AAKF,2EAA2E;AAC3E,2EAA2E;AAC3E,iCAAiC;AACjC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAoB,EAAE,KAAiB,EAAU,EAAE;IAC3E,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;AAC3G,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@plurnk/plurnk-providers",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Framework + contract for the @plurnk/plurnk-providers-* LLM transport family.",
5
- "keywords": ["plurnk", "llm", "provider", "tokenizer"],
5
+ "keywords": [
6
+ "plurnk",
7
+ "llm",
8
+ "provider",
9
+ "tokenizer"
10
+ ],
6
11
  "homepage": "https://github.com/plurnk/plurnk-providers#readme",
7
12
  "bugs": {
8
13
  "url": "https://github.com/plurnk/plurnk-providers/issues"
@@ -41,11 +46,15 @@
41
46
  "prepare": "npm run build"
42
47
  },
43
48
  "peerDependencies": {
44
- "@plurnk/plurnk-grammar": "^0.15.0"
49
+ "@plurnk/plurnk-grammar": "0.17.0"
45
50
  },
46
51
  "devDependencies": {
47
- "@plurnk/plurnk-grammar": "^0.15.0",
52
+ "@plurnk/plurnk-grammar": "0.17.0",
48
53
  "@types/node": "^25.8.0",
49
54
  "typescript": "^6.0.3"
55
+ },
56
+ "dependencies": {
57
+ "gpt-tokenizer": "^3.4.0",
58
+ "llama-tokenizer-js": "^1.2.2"
50
59
  }
51
60
  }