@providerkit/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/dist/context.d.ts +69 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +132 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +86 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +356 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/anthropic.d.ts +26 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +245 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/openai.d.ts +30 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +185 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/retry.d.ts +79 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +200 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +48 -0
- package/dist/schema.js.map +1 -0
- package/dist/tool-args.d.ts +12 -0
- package/dist/tool-args.d.ts.map +1 -0
- package/dist/tool-args.js +113 -0
- package/dist/tool-args.js.map +1 -0
- package/dist/tools.d.ts +82 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +155 -0
- package/dist/tools.js.map +1 -0
- package/dist/transport.d.ts +31 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +157 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +75 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +50 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +71 -0
- package/dist/usage.js.map +1 -0
- package/dist/watchdog.d.ts +34 -0
- package/dist/watchdog.d.ts.map +1 -0
- package/dist/watchdog.js +85 -0
- package/dist/watchdog.js.map +1 -0
- package/dist/zod.d.ts +32 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +49 -0
- package/dist/zod.js.map +1 -0
- package/package.json +76 -0
- package/src/context.ts +150 -0
- package/src/errors.ts +398 -0
- package/src/index.ts +12 -0
- package/src/providers/anthropic.ts +315 -0
- package/src/providers/openai.ts +246 -0
- package/src/retry.ts +246 -0
- package/src/schema.ts +67 -0
- package/src/tool-args.ts +117 -0
- package/src/tools.ts +237 -0
- package/src/transport.ts +162 -0
- package/src/types.ts +231 -0
- package/src/usage.ts +106 -0
- package/src/watchdog.ts +119 -0
- package/src/zod.ts +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gustavo Salomé
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
<img src="brand/providerkit-mark.svg" alt="" width="76" align="left" hspace="4" vspace="2">
|
|
2
|
+
|
|
3
|
+
# providerkit
|
|
4
|
+
|
|
5
|
+
**The layer under your agent loop.** One seam for every LLM provider, plus the failure
|
|
6
|
+
handling you only learn in production.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun add @providerkit/core # npm / pnpm / yarn all fine
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Zero runtime dependencies. `fetch` only — no vendor SDKs, no Node built-ins — so the same
|
|
13
|
+
build runs in Bun, Node, Cloudflare Workers, Deno and a Chrome MV3 service worker.
|
|
14
|
+
|
|
15
|
+
## What this is
|
|
16
|
+
|
|
17
|
+
Most "unified LLM interface" libraries stop at the interface. That part is easy, and it is
|
|
18
|
+
not where the time goes. The time goes here:
|
|
19
|
+
|
|
20
|
+
- a stream that opens, sends nothing, and never ends
|
|
21
|
+
- a socket that dies four `cause` levels down, with no HTTP status to read
|
|
22
|
+
- quota exhaustion arriving as `429` from one vendor, `402` from another, `403` from a
|
|
23
|
+
third and `400` from a fourth — with "retry" being the wrong advice for all of them
|
|
24
|
+
- a `429` that is really a context overflow, where waiting fixes nothing and compaction
|
|
25
|
+
fixes everything
|
|
26
|
+
- a plan that never included the API, which neither a new key nor a top-up will fix
|
|
27
|
+
- Anthropic reporting cache tokens _outside_ the input count while OpenAI reports them
|
|
28
|
+
_inside_ it — so the same conversation costs two different things
|
|
29
|
+
- `reasoning_content` that must be replayed on tool-call turns, and must _not_ be sent
|
|
30
|
+
when thinking is off
|
|
31
|
+
- a tool call's JSON truncated mid-argument, throwing away an answer that was right there
|
|
32
|
+
|
|
33
|
+
providerkit is that knowledge, as a library.
|
|
34
|
+
|
|
35
|
+
## What this is not
|
|
36
|
+
|
|
37
|
+
**Not an agent framework.** There is no loop here, no prompt, no memory, no graph. Your
|
|
38
|
+
loop is where your product lives; it should stay yours. This is everything underneath it.
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createAnthropicProvider, drainStream } from "@providerkit/core";
|
|
44
|
+
|
|
45
|
+
const provider = createAnthropicProvider({
|
|
46
|
+
apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
47
|
+
model: "claude-sonnet-5",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
for await (const chunk of provider.createStream(messages, tools, { effort: "medium" })) {
|
|
51
|
+
if (chunk.content) process.stdout.write(chunk.content);
|
|
52
|
+
if (chunk.usage) console.log(chunk.usage); // one meaning across every provider
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`createOpenAIProvider` speaks the dialect most gateways do, so it serves OpenAI, OpenRouter,
|
|
57
|
+
DeepSeek, GLM, Kimi, Groq, Together, vLLM, Ollama and LM Studio:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const provider = createOpenAIProvider({
|
|
61
|
+
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
62
|
+
baseUrl: "https://openrouter.ai/api",
|
|
63
|
+
id: "openrouter",
|
|
64
|
+
model: "deepseek/deepseek-v4-pro",
|
|
65
|
+
// Keep the prompt cache warm: it lives on the upstream host's account, and
|
|
66
|
+
// OpenRouter's default routing hops hosts between rounds — every hop is a
|
|
67
|
+
// cold cache, in both latency and effective input cost.
|
|
68
|
+
providerOrder: ["deepinfra", "fireworks"],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Failures, named by what fixes them
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { ProviderError, isTransient, isBackupEligible } from "@providerkit/core";
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// …
|
|
79
|
+
} catch (raw) {
|
|
80
|
+
const err = ProviderError.from("anthropic", raw);
|
|
81
|
+
|
|
82
|
+
err.kind; // "overload" | "rate" | "quota" | "entitlement" | "auth" | "context" | …
|
|
83
|
+
err.retryAfterMs; // honoured from Retry-After, vendor reset headers, or Gemini's RetryInfo
|
|
84
|
+
err.body; // the provider's actual words, never "400 status code (no body)"
|
|
85
|
+
|
|
86
|
+
if (err.kind === "context") compactAndRetry();
|
|
87
|
+
else if (isBackupEligible(err.kind)) tryAnotherModel();
|
|
88
|
+
else if (isTransient(err.kind)) retry();
|
|
89
|
+
else surface(err);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Retrying, safely
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { withStreamRetry, streamWithBackupModels } from "@providerkit/core";
|
|
97
|
+
|
|
98
|
+
// Retries only while NOTHING has been emitted. Past the first chunk the stream
|
|
99
|
+
// is committed — a retry would replay tokens already on the reader's screen.
|
|
100
|
+
const stream = withStreamRetry((signal) => provider.createStream(messages, tools, { signal }));
|
|
101
|
+
|
|
102
|
+
// Walks [primary, ...backups] on overload and rate limits only; an auth failure
|
|
103
|
+
// or an invalid request would land identically on every backup.
|
|
104
|
+
const withFallback = streamWithBackupModels((model) => run(model), {
|
|
105
|
+
models: ["claude-opus-5", "claude-sonnet-5"],
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### The watchdog
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { streamWatch, watchChunks } from "@providerkit/core";
|
|
113
|
+
|
|
114
|
+
const watch = streamWatch({ provider: "openai", signal: userSignal });
|
|
115
|
+
for await (const chunk of watchChunks(
|
|
116
|
+
watch,
|
|
117
|
+
provider.createStream(messages, tools, {
|
|
118
|
+
signal: watch.signal,
|
|
119
|
+
}),
|
|
120
|
+
)) {
|
|
121
|
+
// any byte re-arms the deadline
|
|
122
|
+
}
|
|
123
|
+
watch.firstChunkMs(); // TTFT — the number a prompt-cache pin exists to shrink
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A stream that goes 60 seconds without a byte is aborted and surfaced as a transient
|
|
127
|
+
`timeout`. The watchdog aborts its _own_ controller and only bridges the caller's, so your
|
|
128
|
+
user's Stop stays distinguishable from our deadline: one is never retried, the other always
|
|
129
|
+
is.
|
|
130
|
+
|
|
131
|
+
### Tools
|
|
132
|
+
|
|
133
|
+
JSON Schema by default, so the core needs no zod:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { defineTool, ToolRegistry } from "@providerkit/core";
|
|
137
|
+
|
|
138
|
+
const search = defineTool({
|
|
139
|
+
name: "search",
|
|
140
|
+
description: "Search the corpus",
|
|
141
|
+
inputSchema: { type: "object", properties: { q: { type: "string" } }, required: ["q"] },
|
|
142
|
+
run: async ({ q }, ctx) => findAll(q, { signal: ctx.signal }),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const outcome = await search.invoke(rawArgsFromModel);
|
|
146
|
+
// { ok: false, kind: "invalid_input" | "timeout" | "aborted" | "failed", error }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A failing tool is **data**, not an exception — the loop feeds `error` back so the model can
|
|
150
|
+
correct itself. Only a caller's abort escapes.
|
|
151
|
+
|
|
152
|
+
With zod, from the optional entry point:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { zodTool } from "@providerkit/core/zod";
|
|
156
|
+
|
|
157
|
+
const submit = zodTool({
|
|
158
|
+
name: "submit",
|
|
159
|
+
description: "Submit the final answer",
|
|
160
|
+
isTerminal: true,
|
|
161
|
+
clampOverflow: true, // a terminal tool gets no second chance
|
|
162
|
+
input: z.object({ summary: z.string().max(2000) }),
|
|
163
|
+
run: async (input) => input,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Truncated tool calls
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { parseToolArgs, isCompleteJson } from "@providerkit/core";
|
|
171
|
+
|
|
172
|
+
// Never throws. A turn cut off mid-argument keeps every field that closed
|
|
173
|
+
// before the cut, plus the half-written one the cut landed in.
|
|
174
|
+
const args = parseToolArgs(rawArgumentString);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Compaction
|
|
178
|
+
|
|
179
|
+
The decisions, not the prompt — the summary is yours to write:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { needsCompaction, pickCut, applyCompaction, historyBudgetTokens } from "@providerkit/core";
|
|
183
|
+
|
|
184
|
+
if (needsCompaction(lastInputTokens, contextWindow)) {
|
|
185
|
+
const cut = pickCut(messages, historyBudgetTokens(contextWindow));
|
|
186
|
+
messages = applyCompaction(messages, cut, await summarize(messages.slice(0, cut)));
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`pickCut` never splits a tool call from its result (every provider rejects the orphan with a
|
|
191
|
+
400 — the failure compaction was called to avoid), never cuts into the system prompt, and
|
|
192
|
+
always keeps the newest message.
|
|
193
|
+
|
|
194
|
+
### Cost
|
|
195
|
+
|
|
196
|
+
The arithmetic, not the rates. A price table is volatile data about a catalogue that differs
|
|
197
|
+
per application, and a wrong number shipped in a library is a wrong number in everyone's
|
|
198
|
+
ledger — so you keep the numbers, verifiable line by line against a vendor's price sheet:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { UsageTracker } from "@providerkit/core";
|
|
202
|
+
|
|
203
|
+
const tracker = new UsageTracker();
|
|
204
|
+
tracker.add(usage, { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 }); // USD per Mtok
|
|
205
|
+
tracker.costUsd;
|
|
206
|
+
tracker.cacheSavingsUsd; // what the cache saved, for the "is caching even working" question
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Origin
|
|
210
|
+
|
|
211
|
+
This was extracted from five production codebases that had each independently grown the same
|
|
212
|
+
layer — about 9,100 lines solving one ~2,000-line problem. They had three separate
|
|
213
|
+
60-second idle watchdogs, identical down to the constant. On one day in September 2026, two
|
|
214
|
+
of them shipped the same five fixes independently.
|
|
215
|
+
|
|
216
|
+
They had also each learned a _different_ part of the problem. One walked the `cause` chain
|
|
217
|
+
for dead sockets; one parsed Gemini's `RetryInfo`; one read the body before the status and
|
|
218
|
+
knew the quota wordings in five languages; one knew Anthropic's `529` and when a failure is
|
|
219
|
+
worth a different model; one could rescue an answer from a tool call the model truncated.
|
|
220
|
+
|
|
221
|
+
The classifier here is the union of all five, and the suite is every failure any of them
|
|
222
|
+
ever saw. That is the part worth having.
|
|
223
|
+
|
|
224
|
+
## Repo
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
core/ the npm package `providerkit`
|
|
228
|
+
site/ providerkit.dev — also open source
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`bun install`, then `cd core && bun run test`. See [AGENTS.md](./AGENTS.md) for the layout,
|
|
232
|
+
the invariants worth not regressing, and what is left to build.
|
|
233
|
+
|
|
234
|
+
## Status
|
|
235
|
+
|
|
236
|
+
Working and tested (218 tests): the seam, the error classifier, retry and backup-model
|
|
237
|
+
fallback, the idle watchdog, cost math, the fetch/SSE transport, tool-argument salvage, the
|
|
238
|
+
Anthropic and OpenAI-shape adapters, the tool kernel, schema clamping, and the compaction
|
|
239
|
+
decisions.
|
|
240
|
+
|
|
241
|
+
Not yet: the Gemini and Responses adapters, and the multi-key rotation pool.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ChatMessage } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* What the answer needs after the prompt: an output budget, plus the tools'
|
|
4
|
+
* own schemas, plus the slack no provider documents.
|
|
5
|
+
*
|
|
6
|
+
* Absolute rather than a percentage on purpose — a 1M window does not need a
|
|
7
|
+
* 100k cushion, and a 128k window needs more than 12k.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CONTEXT_RESERVE_TOKENS = 32000;
|
|
10
|
+
/**
|
|
11
|
+
* Four characters per token — the rule of thumb every provider's own
|
|
12
|
+
* calculator agrees with to within a fifth, which is all the precision this
|
|
13
|
+
* needs. It decides WHEN to fold: folding one turn early costs a cheap model
|
|
14
|
+
* call, folding one turn late costs the whole turn.
|
|
15
|
+
*/
|
|
16
|
+
export declare function estimateTokens(text: string): number;
|
|
17
|
+
/** What one message costs to re-send: its words, its reasoning, and the
|
|
18
|
+
* arguments of any calls it made. */
|
|
19
|
+
export declare function messageTokens(message: ChatMessage): number;
|
|
20
|
+
export declare function conversationTokens(messages: readonly ChatMessage[]): number;
|
|
21
|
+
/**
|
|
22
|
+
* The provider's own input count says the wall is close — fold before the next
|
|
23
|
+
* step rather than after the 400.
|
|
24
|
+
*
|
|
25
|
+
* Takes the REPORTED count, not an estimate, because it is the only figure
|
|
26
|
+
* that is not a guess. Estimate only when there is no reported count yet.
|
|
27
|
+
*/
|
|
28
|
+
export declare function needsCompaction(inputTokens: number, contextWindow: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* How much of the window the history BEHIND the current turn may spend: a
|
|
31
|
+
* tenth, floored so a small window still gets a usable memory. The tail stays
|
|
32
|
+
* verbatim and the summary is short, so the rest is what folding buys back.
|
|
33
|
+
*/
|
|
34
|
+
export declare function historyBudgetTokens(contextWindow: number): number;
|
|
35
|
+
/**
|
|
36
|
+
* Where to cut so the messages AFTER the cut fit `budget`, walking backwards
|
|
37
|
+
* from the newest.
|
|
38
|
+
*
|
|
39
|
+
* Two invariants the cut must respect, and both are correctness rather than
|
|
40
|
+
* taste:
|
|
41
|
+
*
|
|
42
|
+
* 1. Never split a tool call from its result. Every provider rejects a tool
|
|
43
|
+
* result whose call is missing, so a cut landing between them produces a
|
|
44
|
+
* 400 on the very next turn — the failure compaction was called to avoid.
|
|
45
|
+
* 2. Never cut into the system prompt. It is not history.
|
|
46
|
+
*
|
|
47
|
+
* Returns the index the kept tail starts at, or 0 when everything already fits.
|
|
48
|
+
*/
|
|
49
|
+
export declare function pickCut(messages: readonly ChatMessage[], budget: number): number;
|
|
50
|
+
/**
|
|
51
|
+
* Fold `messages` into `[…system, summary, …tail]`.
|
|
52
|
+
*
|
|
53
|
+
* The summary arrives as a user turn rather than a system one: a system
|
|
54
|
+
* message added mid-conversation reads to the model as a new instruction, and
|
|
55
|
+
* several providers require the system block to be first and singular anyway.
|
|
56
|
+
*/
|
|
57
|
+
export declare function applyCompaction(messages: readonly ChatMessage[], cut: number, summary: string): ChatMessage[];
|
|
58
|
+
/**
|
|
59
|
+
* The context window for a model, when nothing volunteered one.
|
|
60
|
+
*
|
|
61
|
+
* The ladder exists because the endpoints disagree about whether to tell you:
|
|
62
|
+
* OpenRouter, LM Studio and Ollama publish `context_length`; Anthropic and
|
|
63
|
+
* OpenAI do not. A reported number always wins; this is the floor under it.
|
|
64
|
+
*
|
|
65
|
+
* A conservative default is the right failure: too small folds one turn early
|
|
66
|
+
* and costs a cheap call, too large hits a hard 400 mid-run.
|
|
67
|
+
*/
|
|
68
|
+
export declare function guessContextWindow(model: string): number;
|
|
69
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAWD;sCACsC;AACtC,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAO1D;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAEnF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA0BhF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,CAQf;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMxD"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the answer needs after the prompt: an output budget, plus the tools'
|
|
3
|
+
* own schemas, plus the slack no provider documents.
|
|
4
|
+
*
|
|
5
|
+
* Absolute rather than a percentage on purpose — a 1M window does not need a
|
|
6
|
+
* 100k cushion, and a 128k window needs more than 12k.
|
|
7
|
+
*/
|
|
8
|
+
export const CONTEXT_RESERVE_TOKENS = 32_000;
|
|
9
|
+
/**
|
|
10
|
+
* Four characters per token — the rule of thumb every provider's own
|
|
11
|
+
* calculator agrees with to within a fifth, which is all the precision this
|
|
12
|
+
* needs. It decides WHEN to fold: folding one turn early costs a cheap model
|
|
13
|
+
* call, folding one turn late costs the whole turn.
|
|
14
|
+
*/
|
|
15
|
+
export function estimateTokens(text) {
|
|
16
|
+
return Math.ceil(text.length / 4);
|
|
17
|
+
}
|
|
18
|
+
function textOf(message) {
|
|
19
|
+
if (message.role === "user" && typeof message.content !== "string") {
|
|
20
|
+
// Only the words are countable; an image's cost is the provider's own
|
|
21
|
+
// arithmetic and no character count approximates it.
|
|
22
|
+
return message.content.map((part) => (part.type === "text" ? part.text : "")).join("");
|
|
23
|
+
}
|
|
24
|
+
return typeof message.content === "string" ? message.content : "";
|
|
25
|
+
}
|
|
26
|
+
/** What one message costs to re-send: its words, its reasoning, and the
|
|
27
|
+
* arguments of any calls it made. */
|
|
28
|
+
export function messageTokens(message) {
|
|
29
|
+
const extra = message.role === "assistant"
|
|
30
|
+
? (message.reasoning ?? "") +
|
|
31
|
+
(message.toolCalls?.map((call) => call.name + call.arguments).join("") ?? "")
|
|
32
|
+
: "";
|
|
33
|
+
return estimateTokens(textOf(message) + extra);
|
|
34
|
+
}
|
|
35
|
+
export function conversationTokens(messages) {
|
|
36
|
+
return messages.reduce((total, message) => total + messageTokens(message), 0);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The provider's own input count says the wall is close — fold before the next
|
|
40
|
+
* step rather than after the 400.
|
|
41
|
+
*
|
|
42
|
+
* Takes the REPORTED count, not an estimate, because it is the only figure
|
|
43
|
+
* that is not a guess. Estimate only when there is no reported count yet.
|
|
44
|
+
*/
|
|
45
|
+
export function needsCompaction(inputTokens, contextWindow) {
|
|
46
|
+
return inputTokens >= contextWindow - CONTEXT_RESERVE_TOKENS;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* How much of the window the history BEHIND the current turn may spend: a
|
|
50
|
+
* tenth, floored so a small window still gets a usable memory. The tail stays
|
|
51
|
+
* verbatim and the summary is short, so the rest is what folding buys back.
|
|
52
|
+
*/
|
|
53
|
+
export function historyBudgetTokens(contextWindow) {
|
|
54
|
+
return Math.max(6_000, Math.floor(contextWindow * 0.1));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Where to cut so the messages AFTER the cut fit `budget`, walking backwards
|
|
58
|
+
* from the newest.
|
|
59
|
+
*
|
|
60
|
+
* Two invariants the cut must respect, and both are correctness rather than
|
|
61
|
+
* taste:
|
|
62
|
+
*
|
|
63
|
+
* 1. Never split a tool call from its result. Every provider rejects a tool
|
|
64
|
+
* result whose call is missing, so a cut landing between them produces a
|
|
65
|
+
* 400 on the very next turn — the failure compaction was called to avoid.
|
|
66
|
+
* 2. Never cut into the system prompt. It is not history.
|
|
67
|
+
*
|
|
68
|
+
* Returns the index the kept tail starts at, or 0 when everything already fits.
|
|
69
|
+
*/
|
|
70
|
+
export function pickCut(messages, budget) {
|
|
71
|
+
const firstNonSystem = messages.findIndex((message) => message.role !== "system");
|
|
72
|
+
// Nothing but a system prompt: there is no history to fold.
|
|
73
|
+
if (firstNonSystem === -1)
|
|
74
|
+
return messages.length;
|
|
75
|
+
// The newest message is always kept, budget or not. A conversation whose
|
|
76
|
+
// latest turn alone overruns the budget is not fixable by cutting history,
|
|
77
|
+
// and answering a summary instead of the question the person just asked is
|
|
78
|
+
// never the right failure.
|
|
79
|
+
const latest = messages.length - 1;
|
|
80
|
+
let cut = latest;
|
|
81
|
+
let total = messageTokens(messages[latest]);
|
|
82
|
+
for (let i = latest - 1; i >= firstNonSystem; i--) {
|
|
83
|
+
total += messageTokens(messages[i]);
|
|
84
|
+
if (total > budget)
|
|
85
|
+
break;
|
|
86
|
+
cut = i;
|
|
87
|
+
}
|
|
88
|
+
// Walk back off any tool result whose assistant turn would be left behind
|
|
89
|
+
// it. Bounded by the first non-system message, so it can never run off the
|
|
90
|
+
// front. This can exceed the budget by a message or two, which is the right
|
|
91
|
+
// trade: an orphaned tool result is a hard 400, not an overrun.
|
|
92
|
+
while (cut > firstNonSystem && messages[cut].role === "tool")
|
|
93
|
+
cut--;
|
|
94
|
+
return cut;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Fold `messages` into `[…system, summary, …tail]`.
|
|
98
|
+
*
|
|
99
|
+
* The summary arrives as a user turn rather than a system one: a system
|
|
100
|
+
* message added mid-conversation reads to the model as a new instruction, and
|
|
101
|
+
* several providers require the system block to be first and singular anyway.
|
|
102
|
+
*/
|
|
103
|
+
export function applyCompaction(messages, cut, summary) {
|
|
104
|
+
const system = messages.filter((message) => message.role === "system");
|
|
105
|
+
const tail = messages.slice(cut).filter((message) => message.role !== "system");
|
|
106
|
+
return [
|
|
107
|
+
...system,
|
|
108
|
+
{ role: "user", content: `[Earlier conversation, summarized]\n\n${summary}` },
|
|
109
|
+
...tail,
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The context window for a model, when nothing volunteered one.
|
|
114
|
+
*
|
|
115
|
+
* The ladder exists because the endpoints disagree about whether to tell you:
|
|
116
|
+
* OpenRouter, LM Studio and Ollama publish `context_length`; Anthropic and
|
|
117
|
+
* OpenAI do not. A reported number always wins; this is the floor under it.
|
|
118
|
+
*
|
|
119
|
+
* A conservative default is the right failure: too small folds one turn early
|
|
120
|
+
* and costs a cheap call, too large hits a hard 400 mid-run.
|
|
121
|
+
*/
|
|
122
|
+
export function guessContextWindow(model) {
|
|
123
|
+
const id = model.toLowerCase();
|
|
124
|
+
if (/gemini|gpt-4\.1|grok-4|llama-4/.test(id))
|
|
125
|
+
return 1_000_000;
|
|
126
|
+
if (/claude|gpt-5|o[34]|deepseek|kimi|glm|qwen/.test(id))
|
|
127
|
+
return 200_000;
|
|
128
|
+
if (/gpt-4o|mistral|command-r/.test(id))
|
|
129
|
+
return 128_000;
|
|
130
|
+
return 128_000;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** What kind of failure this is, named by what actually fixes it. */
|
|
2
|
+
export type ErrorKind =
|
|
3
|
+
/** Our own cancel — the caller pressed Stop. Never retried. */
|
|
4
|
+
"aborted"
|
|
5
|
+
/** Our own deadline, or the provider's 408. Retry. */
|
|
6
|
+
| "timeout"
|
|
7
|
+
/** The request never reached the provider: socket died, DNS, proxy. Retry. */
|
|
8
|
+
| "network"
|
|
9
|
+
/** Theirs, and temporary: 5xx, Anthropic's 529, "overloaded". Retry, and
|
|
10
|
+
* worth trying a different model. */
|
|
11
|
+
| "overload"
|
|
12
|
+
/** 429 per-minute throttle. Wait — or rotate to another key or model. */
|
|
13
|
+
| "rate"
|
|
14
|
+
/** Balance or usage window exhausted. Waiting minutes will not fix it. */
|
|
15
|
+
| "quota"
|
|
16
|
+
/** The plan never included this API. Neither a new key nor a top-up fixes it. */
|
|
17
|
+
| "entitlement"
|
|
18
|
+
/** 401/403 — the key is wrong, not the request. */
|
|
19
|
+
| "auth"
|
|
20
|
+
/** The model id does not exist or is not served here. */
|
|
21
|
+
| "model"
|
|
22
|
+
/** The prompt outgrew the context window. The fix is to send less. */
|
|
23
|
+
| "context"
|
|
24
|
+
/** Safety filter or refusal. */
|
|
25
|
+
| "content"
|
|
26
|
+
/** Any other 4xx — a bug in what we sent. */
|
|
27
|
+
| "invalid" | "unknown";
|
|
28
|
+
export declare function isTransient(kind: ErrorKind): boolean;
|
|
29
|
+
export declare function isBackupEligible(kind: ErrorKind): boolean;
|
|
30
|
+
export declare class ProviderError extends Error {
|
|
31
|
+
readonly provider: string;
|
|
32
|
+
readonly kind: ErrorKind;
|
|
33
|
+
readonly status?: number;
|
|
34
|
+
readonly code?: string;
|
|
35
|
+
/** Honoured when the provider said how long to wait (Retry-After, or
|
|
36
|
+
* Gemini's RetryInfo.retryDelay). */
|
|
37
|
+
readonly retryAfterMs?: number;
|
|
38
|
+
/** The provider's own response body, truncated — the actual reason, which is
|
|
39
|
+
* otherwise lost behind "400 status code (no body)". */
|
|
40
|
+
readonly body?: string;
|
|
41
|
+
constructor(provider: string, kind: ErrorKind, message: string, opts?: {
|
|
42
|
+
status?: number;
|
|
43
|
+
code?: string;
|
|
44
|
+
retryAfterMs?: number;
|
|
45
|
+
body?: string;
|
|
46
|
+
cause?: unknown;
|
|
47
|
+
});
|
|
48
|
+
get isTransient(): boolean;
|
|
49
|
+
get isBackupEligible(): boolean;
|
|
50
|
+
/** Wrap anything thrown into a classified ProviderError. Already-wrapped
|
|
51
|
+
* errors pass through untouched, so classification happens exactly once. */
|
|
52
|
+
static from(provider: string, err: unknown): ProviderError;
|
|
53
|
+
}
|
|
54
|
+
export declare function messageOf(err: unknown): string;
|
|
55
|
+
/**
|
|
56
|
+
* A transport fault: the socket died before or during the response, so there
|
|
57
|
+
* is NO status and no body for the patterns below to read.
|
|
58
|
+
*
|
|
59
|
+
* The chain is walked because the useful code is rarely on the thrown error —
|
|
60
|
+
* it sits on `cause`, sometimes several wrappers deep. Without this walk every
|
|
61
|
+
* network blip classifies as permanent, and a long run dies on its first
|
|
62
|
+
* hiccup, which is the single likeliest way to lose a minutes-long job.
|
|
63
|
+
*
|
|
64
|
+
* A deliberate abort short-circuits to false: the caller pressed Stop, and
|
|
65
|
+
* retrying that just re-fails against the same dead signal.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isTransportFailure(err: unknown): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The kind of failure, from whatever was thrown.
|
|
70
|
+
*
|
|
71
|
+
* Body patterns outrank status for the 4xx family; within them, context beats
|
|
72
|
+
* entitlement beats quota beats auth — each earlier category's fix is useless
|
|
73
|
+
* for the later ones.
|
|
74
|
+
*/
|
|
75
|
+
export declare function classify(err: unknown, status?: number, body?: string): ErrorKind;
|
|
76
|
+
/**
|
|
77
|
+
* How long the provider asked us to wait, in ms. Two dialects: Gemini's
|
|
78
|
+
* RetryInfo (`"retryDelay": "52s"`, inside the body) and the `Retry-After`
|
|
79
|
+
* header, which SDKs keep on the error. Honouring it beats guessing — a
|
|
80
|
+
* backoff shorter than the window just burns an attempt.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseRetryAfterMs(err: unknown, body?: string): number | undefined;
|
|
83
|
+
/** The loggable surface of a failure — so a dead run never reads
|
|
84
|
+
* "400 status code (no body)". */
|
|
85
|
+
export declare function describeProviderError(err: unknown): Record<string, unknown>;
|
|
86
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAYA,qEAAqE;AACrE,MAAM,MAAM,SAAS;AACnB,+DAA+D;AAC7D,SAAS;AACX,sDAAsD;GACpD,SAAS;AACX,8EAA8E;GAC5E,SAAS;AACX;sCACsC;GACpC,UAAU;AACZ,yEAAyE;GACvE,MAAM;AACR,0EAA0E;GACxE,OAAO;AACT,iFAAiF;GAC/E,aAAa;AACf,mDAAmD;GACjD,MAAM;AACR,yDAAyD;GACvD,OAAO;AACT,sEAAsE;GACpE,SAAS;AACX,gCAAgC;GAC9B,SAAS;AACX,6CAA6C;GAC3C,SAAS,GACT,SAAS,CAAC;AAcd,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAEzD;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;0CACsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;6DACyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAGrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,OAAO,CAAC;KACZ;IAYR,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IAED;iFAC6E;IAC7E,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,aAAa;CAc3D;AAgBD,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAU9C;AAwDD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAiBxD;AAqFD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAgChF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkBjF;AAED;mCACmC;AACnC,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAa3E"}
|