@plurnk/plurnk-providers 0.1.0 → 0.1.1

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
@@ -55,7 +55,9 @@ interface ProviderUsage {
55
55
 
56
56
  ### Promises
57
57
 
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.
58
+ - `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]
59
+
60
+ [^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.
59
61
  - `assistant.usage` is authoritative. Fill `0`s when the wire response omits a breakdown.
60
62
  - `countTokens` is **synchronous**, returns a non-negative integer, deterministic for the same input.
61
63
  - `costFor` is **pure**, returns pico-USD non-negative integer. Returns `0` for siblings with no known rates (local Ollama, generic OpenAI-compat shims).
@@ -78,7 +80,7 @@ class OpenAI {
78
80
  }
79
81
  ```
80
82
 
81
- The framework's `instantiateProvider` calls `mod.default.fromEnv(env, alias.model)` generically.
83
+ The consumer's instantiation path calls `mod.default.fromEnv(env, alias.model)` generically (§5).
82
84
 
83
85
  `fromEnv` MAY be sync or async; return type `Provider | Promise<Provider>`.
84
86
 
@@ -102,14 +104,20 @@ PLURNK_MODEL_opus=openrouter/anthropic/claude-opus-latest
102
104
  PLURNK_MODEL=gemma
103
105
  ```
104
106
 
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`).
107
+ First path segment names the provider; rest is the model identifier (may contain `/` for tri-level providers like openrouter's `publisher/model`).
106
108
 
107
- Framework helpers (`./ProviderRegistry.ts`):
109
+ This package's exported resolution surface:
108
110
 
109
111
  - `parseAliasesFromEnv(env)` — extracts alias entries.
110
112
  - `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.
113
+ - `standardProviderFromEnv(name, env, model)` / `isStandardProvider(name)` tier-1 instantiation (below).
114
+
115
+ **Two-tier provider resolution.** A provider name resolves in this order:
116
+
117
+ 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`, …).
118
+ 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`).
119
+
120
+ 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
121
 
114
122
  ## §6 Engine → provider guarantees (consumer side)
115
123
 
@@ -124,7 +132,7 @@ Framework helpers (`./ProviderRegistry.ts`):
124
132
  - **No DB access.** Provider never touches `node:sqlite` or storage layers.
125
133
  - **No service access.** No imports from `@plurnk/plurnk-service`.
126
134
  - **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.
135
+ - **Raw `content`.** Returned verbatim. Tools are expressed in-body as plurnk DSL (see §2); providers do not use native tool-calling.
128
136
  - **Atomic.** One `generate` call resolves with one complete `ProviderResponse`. No streaming partial resolves (v0).
129
137
  - **Honors `signal`.** Aborted calls reject; resources free; no orphaned connections.
130
138
  - **Single model.** One provider instance speaks to one model.
@@ -180,3 +188,27 @@ A sibling package satisfies the contract when:
180
188
  12. No runtime import of `@plurnk/plurnk-grammar` parser entry points.
181
189
 
182
190
  Sibling-specific behavioral tests (wire-format compliance, model-family quirks, retry logic) live in each package's own test surface.
191
+
192
+ ## §11 Shared OpenAI-compatible machinery
193
+
194
+ 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.
195
+
196
+ - **`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:
197
+
198
+ ```ts
199
+ new OpenAICompatProvider({
200
+ model, url, // fully-resolved chat-completions URL
201
+ fetchTimeoutMs,
202
+ headers, // fully-resolved request headers (incl. auth)
203
+ contextSize, // number | null
204
+ reasonBudget, reasoningStyle, // "none" | "think" | "include_reasoning" | "effort"
205
+ countTokens, costFor, // strategies; default heuristic / free
206
+ });
207
+ ```
208
+
209
+ - **`chatCompletionStream` / `OpenAiHttpError` / `StreamResponse`** — the SSE client. One shared copy.
210
+ - **`parseRequiredInt` / `parseOptionalInt` / `requireEnv`** — env helpers; each takes a provider `label` for error prefixing.
211
+ - **`tokenizerFor(family)` / `tokenizerByPublisher(model, table, index)` / `parseTokenizerFamily(...)`** — synchronous tokenizer strategies (`heuristic` | `cl100k` | `llama`) and per-publisher dispatch for relay providers.
212
+ - **`effortFromBudget(budget)`** — the shared `PLURNK_REASON` → `low|medium|high` breakpoints.
213
+
214
+ 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
6
  usage?: ProviderUsage;
7
- finishReason?: string | null;
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;IACzB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,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,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,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
@@ -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");
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;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,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,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"}
@@ -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;AAQvG,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;CA4BrH"}
@@ -0,0 +1,84 @@
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
+ // SPEC §2 closed set. Wire values outside it (provider-specific or absent)
12
+ // collapse to null — the consumer treats null as "no signal".
13
+ const FINISH_REASONS = new Set(["stop", "length", "tool_calls", "content_filter"]);
14
+ const normalizeFinishReason = (raw) => raw !== null && FINISH_REASONS.has(raw) ? raw : null;
15
+ // Shared budget→effort breakpoints (xai and google had identical copies).
16
+ export const effortFromBudget = (budget) => {
17
+ if (budget <= 1000)
18
+ return "low";
19
+ if (budget <= 4000)
20
+ return "medium";
21
+ return "high";
22
+ };
23
+ const heuristicTokens = (text) => (text.length === 0 ? 0 : Math.ceil(text.length / 4));
24
+ export default class OpenAICompatProvider {
25
+ #model;
26
+ #url;
27
+ #fetchTimeoutMs;
28
+ #headers;
29
+ #contextSize;
30
+ #reasonBudget;
31
+ #reasoningStyle;
32
+ #countTokens;
33
+ #costFor;
34
+ constructor(config) {
35
+ this.#model = config.model;
36
+ this.#url = config.url;
37
+ this.#fetchTimeoutMs = config.fetchTimeoutMs;
38
+ this.#headers = config.headers ?? {};
39
+ this.#contextSize = config.contextSize ?? null;
40
+ this.#reasonBudget = config.reasonBudget ?? 0;
41
+ this.#reasoningStyle = config.reasoningStyle ?? "none";
42
+ this.#countTokens = config.countTokens ?? heuristicTokens;
43
+ this.#costFor = config.costFor ?? (() => 0);
44
+ }
45
+ get contextSize() { return this.#contextSize; }
46
+ get model() { return this.#model; }
47
+ countTokens(text) { return this.#countTokens(text); }
48
+ costFor(usage) { return this.#costFor(usage); }
49
+ #reasoningBody() {
50
+ if (this.#reasonBudget <= 0)
51
+ return {};
52
+ switch (this.#reasoningStyle) {
53
+ case "think": return { think: true };
54
+ case "include_reasoning": return { include_reasoning: true };
55
+ case "effort": return { reasoning_effort: effortFromBudget(this.#reasonBudget) };
56
+ case "none": return {};
57
+ }
58
+ }
59
+ async generate({ messages, signal }) {
60
+ // Reject before any wire call when already aborted (SPEC §10.8).
61
+ signal?.throwIfAborted();
62
+ const timeoutSignal = AbortSignal.timeout(this.#fetchTimeoutMs);
63
+ const effectiveSignal = signal !== undefined ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
64
+ const body = { model: this.#model, messages, ...this.#reasoningBody() };
65
+ const raw = await chatCompletionStream({ url: this.#url, headers: this.#headers, body, signal: effectiveSignal });
66
+ const usage = {
67
+ prompt: raw.usage?.prompt_tokens ?? 0,
68
+ completion: raw.usage?.completion_tokens ?? 0,
69
+ cached: raw.usage?.cached_tokens ?? 0,
70
+ total: raw.usage?.total_tokens ?? 0,
71
+ };
72
+ return {
73
+ assistant: {
74
+ content: raw.content,
75
+ reasoning: raw.reasoning_content.length > 0 ? raw.reasoning_content : null,
76
+ usage,
77
+ finishReason: normalizeFinishReason(raw.finish_reason),
78
+ model: raw.model ?? this.#model,
79
+ },
80
+ assistantRaw: raw,
81
+ };
82
+ }
83
+ }
84
+ //# 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,13 @@
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 { STANDARD_PROVIDERS, isStandardProvider, standardProviderFromEnv } from "./standardProviders.ts";
3
11
  export { default as Mock } from "./Mock.ts";
4
12
  export type { MockAssistant, MockResponse, MockReturnedAssistant } from "./Mock.ts";
5
13
  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,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,11 @@
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 { STANDARD_PROVIDERS, isStandardProvider, standardProviderFromEnv } from "./standardProviders.js";
2
9
  export { default as Mock } from "./Mock.js";
3
10
  export { mockDefaultUsage } from "./Mock.js";
4
11
  //# 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,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,28 @@
1
+ type StreamRequest = {
2
+ url: string;
3
+ headers: Record<string, string>;
4
+ body: Record<string, unknown>;
5
+ signal: AbortSignal;
6
+ };
7
+ export type StreamResponse = {
8
+ model: string | null;
9
+ content: string;
10
+ reasoning_content: string;
11
+ finish_reason: string | null;
12
+ usage: {
13
+ prompt_tokens?: number;
14
+ completion_tokens?: number;
15
+ total_tokens?: number;
16
+ cached_tokens?: number;
17
+ } | null;
18
+ chunkMetadata: Record<string, unknown>;
19
+ };
20
+ export declare class OpenAiHttpError extends Error {
21
+ readonly status: number;
22
+ readonly body: string;
23
+ readonly retryAfter: number | null;
24
+ constructor(status: number, body: string, retryAfter: number | null);
25
+ }
26
+ export declare const chatCompletionStream: ({ url, headers, body, signal }: StreamRequest) => Promise<StreamResponse>;
27
+ export {};
28
+ //# 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,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;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpH,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
@@ -8,11 +8,12 @@ export interface ProviderUsage {
8
8
  readonly cached: number;
9
9
  readonly total: number;
10
10
  }
11
+ export type FinishReason = "stop" | "length" | "tool_calls" | "content_filter" | null;
11
12
  export interface ProviderAssistant {
12
13
  readonly content: string;
13
14
  readonly reasoning: string | null;
14
15
  readonly usage: ProviderUsage;
15
- readonly finishReason: string | null;
16
+ readonly finishReason: FinishReason;
16
17
  readonly model: string;
17
18
  }
18
19
  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;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;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"}
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.1",
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
  }