@providerkit/core 0.21.0 → 0.22.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/README.md +7 -7
- package/dist/attribution.d.ts +2 -2
- package/dist/capability.d.ts +9 -9
- package/dist/capability.js +9 -9
- package/dist/capability.js.map +1 -1
- package/dist/context.d.ts +4 -4
- package/dist/context.js +4 -4
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +14 -14
- package/dist/image.d.ts +1 -1
- package/dist/image.d.ts.map +1 -1
- package/dist/jev.d.ts +9 -9
- package/dist/jev.d.ts.map +1 -1
- package/dist/jev.js +1 -1
- package/dist/key-pool.d.ts +4 -4
- package/dist/key-pool.js +6 -6
- package/dist/key-pool.js.map +1 -1
- package/dist/presets.d.ts +26 -22
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +13 -12
- package/dist/presets.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -5
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +9 -9
- package/dist/providers/factory.d.ts +2 -2
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +2 -1
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/gemini.d.ts +2 -2
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +15 -14
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts +12 -9
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +14 -13
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/responses.d.ts +3 -3
- package/dist/providers/responses.js +5 -5
- package/dist/providers/responses.js.map +1 -1
- package/dist/providers/zai.d.ts +2 -2
- package/dist/providers/zai.js +2 -2
- package/dist/rate-limit.d.ts +3 -3
- package/dist/rate-limit.js +3 -3
- package/dist/retry.d.ts +5 -5
- package/dist/retry.js +4 -4
- package/dist/schema.d.ts +6 -6
- package/dist/schema.js +8 -8
- package/dist/tool-args.d.ts +1 -1
- package/dist/tool-args.js +2 -2
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/dist/transport.d.ts +7 -7
- package/dist/transport.js +6 -6
- package/dist/types.d.ts +24 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -3
- package/dist/types.js.map +1 -1
- package/dist/usage.d.ts +4 -4
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +3 -3
- package/dist/usage.js.map +1 -1
- package/dist/watchdog.d.ts +12 -12
- package/dist/watchdog.js +10 -10
- package/dist/zod.d.ts +4 -4
- package/dist/zod.js +3 -3
- package/package.json +1 -1
- package/src/attribution.ts +2 -2
- package/src/capability.ts +12 -12
- package/src/context.ts +4 -4
- package/src/errors.ts +18 -18
- package/src/image.ts +1 -1
- package/src/jev.ts +11 -11
- package/src/key-pool.ts +7 -7
- package/src/presets.ts +26 -22
- package/src/providers/anthropic.ts +14 -14
- package/src/providers/factory.ts +4 -3
- package/src/providers/gemini.ts +19 -16
- package/src/providers/openai.ts +22 -17
- package/src/providers/responses.ts +11 -11
- package/src/providers/zai.ts +2 -2
- package/src/rate-limit.ts +4 -4
- package/src/retry.ts +5 -5
- package/src/schema.ts +8 -8
- package/src/tool-args.ts +2 -2
- package/src/tools.ts +4 -4
- package/src/transport.ts +7 -7
- package/src/types.ts +24 -23
- package/src/usage.ts +5 -5
- package/src/watchdog.ts +13 -13
- package/src/zod.ts +4 -4
package/dist/usage.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { TokenUsage } from "./types.ts";
|
|
2
|
-
/** USD per
|
|
2
|
+
/** USD per million tokens, as every vendor prints its price sheet, so a row
|
|
3
3
|
* can be checked against one without arithmetic. */
|
|
4
4
|
export interface ModelRate {
|
|
5
5
|
/** Cache-MISS input: fresh tokens the provider had to read. */
|
|
6
6
|
input: number;
|
|
7
7
|
output: number;
|
|
8
8
|
/** Cache-HIT input. Providers auto-cache repeated prefixes and bill the hit
|
|
9
|
-
* portion far cheaper
|
|
9
|
+
* portion far cheaper: 0.1× on Anthropic, ~0.1× on Gemini, 0.5× on some
|
|
10
10
|
* OpenAI models. In an agent loop the re-sent context is overwhelmingly
|
|
11
11
|
* hits, so billing it at the miss rate overcounts by up to 10×. */
|
|
12
12
|
cacheRead: number;
|
|
@@ -40,7 +40,7 @@ export declare function subtractUsage(a: TokenUsage, b: TokenUsage): TokenUsage;
|
|
|
40
40
|
export declare function costUsd(usage: TokenUsage, rate: ModelRate): number;
|
|
41
41
|
/**
|
|
42
42
|
* Accumulates usage and cost across the calls of one run, pricing each with
|
|
43
|
-
* the rate in effect for the model that served it
|
|
43
|
+
* the rate in effect for the model that served it, so a run that switches to
|
|
44
44
|
* a backup model, or spans a time-of-day price boundary, still bills correctly.
|
|
45
45
|
* A call that carries a reported cost is billed that, with or without a rate.
|
|
46
46
|
*/
|
|
@@ -48,7 +48,7 @@ export declare class UsageTracker {
|
|
|
48
48
|
private usage;
|
|
49
49
|
private cost;
|
|
50
50
|
/** USD the cache hits saved, versus billing them all as misses.
|
|
51
|
-
* Observability only
|
|
51
|
+
* Observability only. Never billed. */
|
|
52
52
|
private saved;
|
|
53
53
|
add(usage: TokenUsage, rate?: ModelRate): void;
|
|
54
54
|
subtract(usage: TokenUsage, rate?: ModelRate): void;
|
package/dist/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C;qDACqD;AACrD,MAAM,WAAW,SAAS;IACxB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf;;;wEAGoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB;;0EAEsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;8DAG8D;AAC9D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAUjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAUtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAgBlE;AAQD;;;;;GAKG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAuD;IACpE,OAAO,CAAC,IAAI,CAAK;IACjB;
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C;qDACqD;AACrD,MAAM,WAAW,SAAS;IACxB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf;;;wEAGoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB;;0EAEsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;8DAG8D;AAC9D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAUjE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAUtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAgBlE;AAQD;;;;;GAKG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAuD;IACpE,OAAO,CAAC,IAAI,CAAK;IACjB;4CACwC;IACxC,OAAO,CAAC,KAAK,CAAK;IAElB,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI;IAQ9C,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI;IAQnD,IAAI,MAAM,IAAI,UAAU,CAEvB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED;;yDAEqD;IACrD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIrC,KAAK,IAAI,IAAI;CAKd"}
|
package/dist/usage.js
CHANGED
|
@@ -58,13 +58,13 @@ export function costUsd(usage, rate) {
|
|
|
58
58
|
1_000_000);
|
|
59
59
|
}
|
|
60
60
|
/** What one call adds to a run: its reported cost, else its rate, else
|
|
61
|
-
* nothing
|
|
61
|
+
* nothing. An unpriced call still counts its tokens. */
|
|
62
62
|
function callCost(usage, rate) {
|
|
63
63
|
return rate ? costUsd(usage, rate) : (usage.reportedCostUsd ?? 0);
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* Accumulates usage and cost across the calls of one run, pricing each with
|
|
67
|
-
* the rate in effect for the model that served it
|
|
67
|
+
* the rate in effect for the model that served it, so a run that switches to
|
|
68
68
|
* a backup model, or spans a time-of-day price boundary, still bills correctly.
|
|
69
69
|
* A call that carries a reported cost is billed that, with or without a rate.
|
|
70
70
|
*/
|
|
@@ -72,7 +72,7 @@ export class UsageTracker {
|
|
|
72
72
|
usage = { ...EMPTY_USAGE, cacheWriteTokens: 0 };
|
|
73
73
|
cost = 0;
|
|
74
74
|
/** USD the cache hits saved, versus billing them all as misses.
|
|
75
|
-
* Observability only
|
|
75
|
+
* Observability only. Never billed. */
|
|
76
76
|
saved = 0;
|
|
77
77
|
add(usage, rate) {
|
|
78
78
|
this.usage = addUsage(this.usage, usage);
|
package/dist/usage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
|
package/dist/watchdog.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Provider, ProviderChunk } from "./types.ts";
|
|
|
2
2
|
/** No byte at all for this long, once the response has started, and the
|
|
3
3
|
* stream is dead. */
|
|
4
4
|
export declare const STREAM_IDLE_MS = 60000;
|
|
5
|
-
/** No chunk for this long
|
|
5
|
+
/** No chunk for this long, including the wait for the response to start,
|
|
6
6
|
* and the stream is going nowhere, keep-alives or not. */
|
|
7
7
|
export declare const STREAM_PROGRESS_MS = 300000;
|
|
8
8
|
export interface StreamWatch {
|
|
@@ -10,17 +10,17 @@ export interface StreamWatch {
|
|
|
10
10
|
readonly signal: AbortSignal;
|
|
11
11
|
/** A chunk arrived: re-arm both deadlines, and mark TTFT if it was the first. */
|
|
12
12
|
sawByte(): void;
|
|
13
|
-
/** The response showed life without a chunk
|
|
13
|
+
/** The response showed life without a chunk: its headers, a keep-alive, a
|
|
14
14
|
* partial frame. Starts or re-arms the idle deadline only. */
|
|
15
15
|
sawActivity(): void;
|
|
16
16
|
/**
|
|
17
|
-
* Milliseconds from the call opening to its first chunk of any kind
|
|
17
|
+
* Milliseconds from the call opening to its first chunk of any kind. The
|
|
18
18
|
* wait a person actually experiences, and the number a prompt-cache pin
|
|
19
19
|
* exists to shrink. Null until something arrives.
|
|
20
20
|
*/
|
|
21
21
|
firstChunkMs(): number | null;
|
|
22
22
|
/**
|
|
23
|
-
* Re-issue a provider failure as the timeout
|
|
23
|
+
* Re-issue a provider failure as the timeout only when it was
|
|
24
24
|
* one of our deadlines that aborted. A caller's Stop passes through untouched.
|
|
25
25
|
*/
|
|
26
26
|
classify(err: unknown): unknown;
|
|
@@ -36,7 +36,7 @@ export interface StreamWatchOptions {
|
|
|
36
36
|
export declare function streamWatch(opts?: StreamWatchOptions): StreamWatch;
|
|
37
37
|
/**
|
|
38
38
|
* Wrap a stream so every chunk re-arms `watch`, and a failure is re-classified
|
|
39
|
-
* through it. Disposes on any exit
|
|
39
|
+
* through it. Disposes on any exit: completion, throw, or the consumer
|
|
40
40
|
* breaking out of the loop.
|
|
41
41
|
*/
|
|
42
42
|
export declare function watchChunks<T>(watch: StreamWatch, chunks: AsyncIterable<T>): AsyncGenerator<T>;
|
|
@@ -46,7 +46,7 @@ export declare function watchChunks<T>(watch: StreamWatch, chunks: AsyncIterable
|
|
|
46
46
|
* A stream that ends with no text and no tool call is a failure wearing a
|
|
47
47
|
* success's clothes: `stop_reason: end_turn` with zero content blocks, which
|
|
48
48
|
* the vendors emit under load, or a thinking block that ate the whole
|
|
49
|
-
* `max_tokens`. Nothing throws, so nothing retries
|
|
49
|
+
* `max_tokens`. Nothing throws, so nothing retries. The caller simply shows a
|
|
50
50
|
* person an empty answer, and the only trace is a bill.
|
|
51
51
|
*
|
|
52
52
|
* Reasoning is not an answer. It still streams the moment it arrives, so a
|
|
@@ -58,12 +58,12 @@ export declare function watchChunks<T>(watch: StreamWatch, chunks: AsyncIterable
|
|
|
58
58
|
*
|
|
59
59
|
* Classified by what fixes it. A `length` finish is the caller's own cap:
|
|
60
60
|
* `invalid`, so nothing retries it into the same cap and no backup model is
|
|
61
|
-
* cooled down for it
|
|
61
|
+
* cooled down for it. The fix is a larger `maxTokens` or a lower effort. Any
|
|
62
62
|
* other empty turn is `overload`, because that is both true and useful: it is
|
|
63
63
|
* theirs and temporary, so it is transient (the same model, retried, usually
|
|
64
64
|
* answers) and backup-eligible (a model that keeps doing it should be walked
|
|
65
65
|
* away from). Until something streams, the empty frames are held back, so the
|
|
66
|
-
* retry rule
|
|
66
|
+
* retry rule still holds: retry only while nothing was emitted.
|
|
67
67
|
*/
|
|
68
68
|
export declare function requireContent<T extends ProviderChunk>(provider: string, chunks: AsyncIterable<T>, opts?: {
|
|
69
69
|
afterToolResult?: boolean;
|
|
@@ -88,8 +88,8 @@ export interface WatchdogOptions {
|
|
|
88
88
|
* A provider with both silent failures already handled.
|
|
89
89
|
*
|
|
90
90
|
* Every consumer of this package wrote the same three lines around every
|
|
91
|
-
* `createStream
|
|
92
|
-
* the chunks
|
|
91
|
+
* `createStream`: build a watch, hand the provider the watch's signal, wrap
|
|
92
|
+
* the chunks. The middle one is the trap. Pass the caller's signal
|
|
93
93
|
* instead and everything still compiles, still streams, still passes the
|
|
94
94
|
* tests: the watchdog simply never aborts anything, because the request it was
|
|
95
95
|
* meant to cancel was never told about it. The failure has no symptom until
|
|
@@ -97,8 +97,8 @@ export interface WatchdogOptions {
|
|
|
97
97
|
*
|
|
98
98
|
* So the composition belongs here rather than in a docs snippet each app
|
|
99
99
|
* copies. The result is still a `Provider`, so it composes unchanged with
|
|
100
|
-
* `withStreamRetry` and `streamWithBackupModels
|
|
101
|
-
*
|
|
100
|
+
* `withStreamRetry` and `streamWithBackupModels`. Timeouts and empty turns
|
|
101
|
+
* without a `length` finish are transient, so wrapping it in a retry is safe.
|
|
102
102
|
*/
|
|
103
103
|
export declare function withWatchdog(provider: Provider, opts?: WatchdogOptions): Provider;
|
|
104
104
|
//# sourceMappingURL=watchdog.d.ts.map
|
package/dist/watchdog.js
CHANGED
|
@@ -32,7 +32,7 @@ import { ProviderError } from "./errors.js";
|
|
|
32
32
|
/** No byte at all for this long, once the response has started, and the
|
|
33
33
|
* stream is dead. */
|
|
34
34
|
export const STREAM_IDLE_MS = 60_000;
|
|
35
|
-
/** No chunk for this long
|
|
35
|
+
/** No chunk for this long, including the wait for the response to start,
|
|
36
36
|
* and the stream is going nowhere, keep-alives or not. */
|
|
37
37
|
export const STREAM_PROGRESS_MS = 300_000;
|
|
38
38
|
export function streamWatch(opts = {}) {
|
|
@@ -102,7 +102,7 @@ export function streamWatch(opts = {}) {
|
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* Wrap a stream so every chunk re-arms `watch`, and a failure is re-classified
|
|
105
|
-
* through it. Disposes on any exit
|
|
105
|
+
* through it. Disposes on any exit: completion, throw, or the consumer
|
|
106
106
|
* breaking out of the loop.
|
|
107
107
|
*/
|
|
108
108
|
export async function* watchChunks(watch, chunks) {
|
|
@@ -125,7 +125,7 @@ export async function* watchChunks(watch, chunks) {
|
|
|
125
125
|
* A stream that ends with no text and no tool call is a failure wearing a
|
|
126
126
|
* success's clothes: `stop_reason: end_turn` with zero content blocks, which
|
|
127
127
|
* the vendors emit under load, or a thinking block that ate the whole
|
|
128
|
-
* `max_tokens`. Nothing throws, so nothing retries
|
|
128
|
+
* `max_tokens`. Nothing throws, so nothing retries. The caller simply shows a
|
|
129
129
|
* person an empty answer, and the only trace is a bill.
|
|
130
130
|
*
|
|
131
131
|
* Reasoning is not an answer. It still streams the moment it arrives, so a
|
|
@@ -137,12 +137,12 @@ export async function* watchChunks(watch, chunks) {
|
|
|
137
137
|
*
|
|
138
138
|
* Classified by what fixes it. A `length` finish is the caller's own cap:
|
|
139
139
|
* `invalid`, so nothing retries it into the same cap and no backup model is
|
|
140
|
-
* cooled down for it
|
|
140
|
+
* cooled down for it. The fix is a larger `maxTokens` or a lower effort. Any
|
|
141
141
|
* other empty turn is `overload`, because that is both true and useful: it is
|
|
142
142
|
* theirs and temporary, so it is transient (the same model, retried, usually
|
|
143
143
|
* answers) and backup-eligible (a model that keeps doing it should be walked
|
|
144
144
|
* away from). Until something streams, the empty frames are held back, so the
|
|
145
|
-
* retry rule
|
|
145
|
+
* retry rule still holds: retry only while nothing was emitted.
|
|
146
146
|
*/
|
|
147
147
|
export async function* requireContent(provider, chunks, opts = {}) {
|
|
148
148
|
const held = [];
|
|
@@ -186,7 +186,7 @@ export async function* requireContent(provider, chunks, opts = {}) {
|
|
|
186
186
|
const spent = usage?.reasoningTokens
|
|
187
187
|
? ` (${usage.reasoningTokens} of ${usage.outputTokens} output tokens went to reasoning)`
|
|
188
188
|
: "";
|
|
189
|
-
throw new ProviderError(provider, "invalid", `${provider}: the output cap ran out before any answer${spent}
|
|
189
|
+
throw new ProviderError(provider, "invalid", `${provider}: the output cap ran out before any answer${spent}. Raise maxTokens or lower effort.`);
|
|
190
190
|
}
|
|
191
191
|
throw new ProviderError(provider, "overload", `${provider}: completed with no content`);
|
|
192
192
|
}
|
|
@@ -194,8 +194,8 @@ export async function* requireContent(provider, chunks, opts = {}) {
|
|
|
194
194
|
* A provider with both silent failures already handled.
|
|
195
195
|
*
|
|
196
196
|
* Every consumer of this package wrote the same three lines around every
|
|
197
|
-
* `createStream
|
|
198
|
-
* the chunks
|
|
197
|
+
* `createStream`: build a watch, hand the provider the watch's signal, wrap
|
|
198
|
+
* the chunks. The middle one is the trap. Pass the caller's signal
|
|
199
199
|
* instead and everything still compiles, still streams, still passes the
|
|
200
200
|
* tests: the watchdog simply never aborts anything, because the request it was
|
|
201
201
|
* meant to cancel was never told about it. The failure has no symptom until
|
|
@@ -203,8 +203,8 @@ export async function* requireContent(provider, chunks, opts = {}) {
|
|
|
203
203
|
*
|
|
204
204
|
* So the composition belongs here rather than in a docs snippet each app
|
|
205
205
|
* copies. The result is still a `Provider`, so it composes unchanged with
|
|
206
|
-
* `withStreamRetry` and `streamWithBackupModels
|
|
207
|
-
*
|
|
206
|
+
* `withStreamRetry` and `streamWithBackupModels`. Timeouts and empty turns
|
|
207
|
+
* without a `length` finish are transient, so wrapping it in a retry is safe.
|
|
208
208
|
*/
|
|
209
209
|
export function withWatchdog(provider, opts = {}) {
|
|
210
210
|
return {
|
package/dist/zod.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import type { JsonObjectSchema } from "./types.ts";
|
|
|
5
5
|
* A zod schema as the JSON Schema every provider's tool contract wants.
|
|
6
6
|
*
|
|
7
7
|
* `$schema` is dropped. zod emits the dialect URI at the root, and a provider
|
|
8
|
-
* validating a tool's `parameters` against its own supported subset
|
|
9
|
-
* under `strict: true`, Gemini's `parametersJsonSchema`
|
|
8
|
+
* validating a tool's `parameters` against its own supported subset (OpenAI
|
|
9
|
+
* under `strict: true`, Gemini's `parametersJsonSchema`) rejects the whole
|
|
10
10
|
* tool over that one key, with a message that names neither zod nor the field.
|
|
11
11
|
*/
|
|
12
12
|
export declare function toJsonObjectSchema(schema: z.ZodType, label?: string): JsonObjectSchema;
|
|
@@ -26,13 +26,13 @@ export interface ZodToolSpec<I, O> {
|
|
|
26
26
|
* rejecting the call. Worth it for a TERMINAL tool, which gets no second
|
|
27
27
|
* chance: a forced-submit salvage turn runs exactly once, and discarding an
|
|
28
28
|
* otherwise-valid answer over a few extra characters loses the whole run.
|
|
29
|
-
* Off by default
|
|
29
|
+
* Off by default: an ordinary tool can simply be called again.
|
|
30
30
|
*/
|
|
31
31
|
clampOverflow?: boolean;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* A tool whose arguments are validated by zod, with the failure reported to
|
|
35
|
-
* the
|
|
35
|
+
* the model in words it can act on. `topic: expected string, received number`
|
|
36
36
|
* beats a stack trace it cannot read.
|
|
37
37
|
*/
|
|
38
38
|
export declare function zodTool<I, O>(spec: ZodToolSpec<I, O>): Tool<I, O>;
|
package/dist/zod.js
CHANGED
|
@@ -10,8 +10,8 @@ import { clampToSchema } from "./schema.js";
|
|
|
10
10
|
* A zod schema as the JSON Schema every provider's tool contract wants.
|
|
11
11
|
*
|
|
12
12
|
* `$schema` is dropped. zod emits the dialect URI at the root, and a provider
|
|
13
|
-
* validating a tool's `parameters` against its own supported subset
|
|
14
|
-
* under `strict: true`, Gemini's `parametersJsonSchema`
|
|
13
|
+
* validating a tool's `parameters` against its own supported subset (OpenAI
|
|
14
|
+
* under `strict: true`, Gemini's `parametersJsonSchema`) rejects the whole
|
|
15
15
|
* tool over that one key, with a message that names neither zod nor the field.
|
|
16
16
|
*/
|
|
17
17
|
export function toJsonObjectSchema(schema, label = "schema") {
|
|
@@ -26,7 +26,7 @@ export function toJsonObjectSchema(schema, label = "schema") {
|
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* A tool whose arguments are validated by zod, with the failure reported to
|
|
29
|
-
* the
|
|
29
|
+
* the model in words it can act on. `topic: expected string, received number`
|
|
30
30
|
* beats a stack trace it cannot read.
|
|
31
31
|
*/
|
|
32
32
|
export function zodTool(spec) {
|
package/package.json
CHANGED
package/src/attribution.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// HTTP header names are case-insensitive but this seam carries them as a
|
|
10
10
|
// plain record, so `http-referer` and `HTTP-Referer` must mean the same opt-out.
|
|
11
11
|
export interface Attribution {
|
|
12
|
-
/** Site URL, sent as `HTTP-Referer`
|
|
12
|
+
/** Site URL, sent as `HTTP-Referer` (e.g. "https://your.app"). */
|
|
13
13
|
siteUrl?: string;
|
|
14
|
-
/** App name, sent as `X-Title`
|
|
14
|
+
/** App name, sent as `X-Title` (e.g. "Your App"). */
|
|
15
15
|
siteName?: string;
|
|
16
16
|
}
|
|
17
17
|
|
package/src/capability.ts
CHANGED
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
|
|
19
19
|
import type { JsonWithTools, Provider } from "./types.ts";
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* turn
|
|
21
|
+
/** A question the model cannot answer from memory, so a tool call is the only
|
|
22
|
+
* correct turn. Not making one is a real signal rather than a judgement call. */
|
|
23
23
|
const PROBE_TOOL = {
|
|
24
24
|
name: "get_current_time",
|
|
25
25
|
description:
|
|
26
|
-
"The only source of the current time. You cannot know the time without calling this
|
|
26
|
+
"The only source of the current time. You cannot know the time without calling this. Never answer a time question from memory or guess.",
|
|
27
27
|
inputSchema: {
|
|
28
28
|
type: "object" as const,
|
|
29
29
|
properties: { timezone: { type: "string", description: "IANA timezone, e.g. Asia/Tokyo" } },
|
|
@@ -46,7 +46,7 @@ const SHAPES: JsonWithTools[] = ["response_format", "prompt"];
|
|
|
46
46
|
export interface JsonWithToolsProbe {
|
|
47
47
|
/**
|
|
48
48
|
* The shape to configure, or `null` when neither called the tool on every
|
|
49
|
-
* sample
|
|
49
|
+
* sample. That model cannot be trusted with tools and a schema together,
|
|
50
50
|
* whichever way the schema rides, and the honest fix is a different model.
|
|
51
51
|
*/
|
|
52
52
|
use: JsonWithTools | null;
|
|
@@ -58,8 +58,8 @@ export interface JsonWithToolsProbe {
|
|
|
58
58
|
|
|
59
59
|
export interface ProbeOptions {
|
|
60
60
|
/**
|
|
61
|
-
* Samples per shape. Above one because the interesting failures are partial
|
|
62
|
-
* `gemini-3.8-flash` called its tool 3/10
|
|
61
|
+
* Samples per shape. Above one because the interesting failures are partial:
|
|
62
|
+
* `gemini-3.8-flash` called its tool 3/10. A single sample reports a
|
|
63
63
|
* coin flip as a capability. A shape passes only when EVERY sample calls.
|
|
64
64
|
*/
|
|
65
65
|
samples?: number;
|
|
@@ -95,12 +95,12 @@ async function callsTool(
|
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* Ask a model, on the wire, which {@link JsonWithTools} shape it actually
|
|
98
|
-
* serves
|
|
98
|
+
* serves. Then configure the provider with `probe.use`.
|
|
99
99
|
*
|
|
100
100
|
* Both shapes are always tried, because a result you can read beats a result
|
|
101
101
|
* you have to trust: `calls` is the table to log, and "0/3 and 3/3" is what
|
|
102
102
|
* makes a later regression obvious. It costs `samples × 2` short calls, made
|
|
103
|
-
* one after another, once, at whatever moment the app decides to ask
|
|
103
|
+
* one after another, once, at whatever moment the app decides to ask. Boot is
|
|
104
104
|
* the usual one.
|
|
105
105
|
*
|
|
106
106
|
* Probe each provider on its own, never a fallback chain: the shape is a fact
|
|
@@ -117,8 +117,8 @@ async function callsTool(
|
|
|
117
117
|
* report "this model cannot call tools", which is a much worse thing to
|
|
118
118
|
* believe than "the call failed".
|
|
119
119
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
120
|
+
* It catches a structural failure: a model that cannot emit the call, every
|
|
121
|
+
* time, on the easiest question there is. A model that is merely
|
|
122
122
|
* unreliable will pass: `gemini-3.8-flash` answers this probe 3/3 and a real
|
|
123
123
|
* sales turn 3/10. That is the right line to draw. A harder probe would start
|
|
124
124
|
* failing good models for being terse, and "sometimes forgets its tools" is a
|
|
@@ -226,8 +226,8 @@ function normalizeId(id: string): string {
|
|
|
226
226
|
* - `deepseek/deepseek-v4.1-flash` matches `deepseek-v4.1-flash`
|
|
227
227
|
* - `accounts/fireworks/models/glm-5p3-flash` matches `glm-5.3-flash`
|
|
228
228
|
*
|
|
229
|
-
* Never throws
|
|
230
|
-
* returns `undefined` so capability lookups never break caller execution.
|
|
229
|
+
* Never throws. If the catalog cannot be retrieved or the model is unknown,
|
|
230
|
+
* it returns `undefined` so capability lookups never break caller execution.
|
|
231
231
|
*/
|
|
232
232
|
export async function resolveModelCapabilities(
|
|
233
233
|
modelId: string,
|
package/src/context.ts
CHANGED
|
@@ -9,13 +9,13 @@ import type { ChatMessage } from "./types.ts";
|
|
|
9
9
|
* What the answer needs after the prompt: an output budget, plus the tools'
|
|
10
10
|
* own schemas, plus the slack no provider documents.
|
|
11
11
|
*
|
|
12
|
-
* Absolute rather than a percentage on purpose
|
|
12
|
+
* Absolute rather than a percentage on purpose: a 1M window does not need a
|
|
13
13
|
* 100k cushion, and a 128k window needs more than 12k.
|
|
14
14
|
*/
|
|
15
15
|
export const CONTEXT_RESERVE_TOKENS = 32_000;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Four characters per token
|
|
18
|
+
* Four characters per token is the rule of thumb every provider's own
|
|
19
19
|
* calculator agrees with to within a fifth, which is all the precision this
|
|
20
20
|
* needs. It decides WHEN to fold: folding one turn early costs a cheap model
|
|
21
21
|
* call, folding one turn late costs the whole turn.
|
|
@@ -64,7 +64,7 @@ export function conversationTokens(messages: readonly ChatMessage[]): number {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* The provider's own input count says the
|
|
67
|
+
* The provider's own input count says the limit is close. Fold before the next
|
|
68
68
|
* step rather than after the 400.
|
|
69
69
|
*
|
|
70
70
|
* Takes the REPORTED count, not an estimate, because it is the only figure
|
|
@@ -99,7 +99,7 @@ export function historyBudgetTokens(contextWindow: number): number {
|
|
|
99
99
|
*
|
|
100
100
|
* 1. Never split a tool call from its result. Every provider rejects a tool
|
|
101
101
|
* result whose call is missing, so a cut landing between them produces a
|
|
102
|
-
* 400 on the very next turn
|
|
102
|
+
* 400 on the very next turn, the failure compaction was called to avoid.
|
|
103
103
|
* 2. Never cut into the system prompt. It is not history.
|
|
104
104
|
*
|
|
105
105
|
* Returns the index the kept tail starts at, or 0 when everything already fits.
|
package/src/errors.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type { FilePart } from "./types.ts";
|
|
|
15
15
|
|
|
16
16
|
/** What kind of failure this is, named by what actually fixes it. */
|
|
17
17
|
export type ErrorKind =
|
|
18
|
-
/** Our own cancel
|
|
18
|
+
/** Our own cancel: the caller pressed Stop. Never retried. */
|
|
19
19
|
| "aborted"
|
|
20
20
|
/** Our own deadline, or the provider's 408. Retry. */
|
|
21
21
|
| "timeout"
|
|
@@ -24,13 +24,13 @@ export type ErrorKind =
|
|
|
24
24
|
/** Theirs, and temporary: 5xx, Anthropic's 529, "overloaded". Retry, and
|
|
25
25
|
* worth trying a different model. */
|
|
26
26
|
| "overload"
|
|
27
|
-
/** 429 per-minute throttle. Wait
|
|
27
|
+
/** 429 per-minute throttle. Wait, or rotate to another key or model. */
|
|
28
28
|
| "rate"
|
|
29
29
|
/** Balance or usage window exhausted. Waiting minutes will not fix it. */
|
|
30
30
|
| "quota"
|
|
31
31
|
/** The plan never included this API. Neither a new key nor a top-up fixes it. */
|
|
32
32
|
| "entitlement"
|
|
33
|
-
/** 401/403
|
|
33
|
+
/** 401/403: the key is wrong, not the request. */
|
|
34
34
|
| "auth"
|
|
35
35
|
/** The model id does not exist or is not served here. */
|
|
36
36
|
| "model"
|
|
@@ -38,7 +38,7 @@ export type ErrorKind =
|
|
|
38
38
|
| "context"
|
|
39
39
|
/** Safety filter or refusal. */
|
|
40
40
|
| "content"
|
|
41
|
-
/** Any other 4xx
|
|
41
|
+
/** Any other 4xx: a bug in what we sent. */
|
|
42
42
|
| "invalid"
|
|
43
43
|
| "unknown";
|
|
44
44
|
|
|
@@ -81,7 +81,7 @@ export function isBackupEligible(kind: ErrorKind): boolean {
|
|
|
81
81
|
* 1. Caller aborts are never retryable.
|
|
82
82
|
* 2. An explicit `shouldRetry` directive on ProviderError (e.g. from server `x-should-retry`)
|
|
83
83
|
* 3. Non-retryable kinds (quota, context, model, entitlement, content, invalid) return false.
|
|
84
|
-
* 4. Excessive retry-after waits (exceeding `maxWaitMs`) return false
|
|
84
|
+
* 4. Excessive retry-after waits (exceeding `maxWaitMs`) return false. Long waits belong to fallbacks.
|
|
85
85
|
* 5. Transient kinds (rate, overload, network, timeout) return true.
|
|
86
86
|
* 6. Transport-level network faults (fetch failed, connection lost, SSL blips) return true.
|
|
87
87
|
*/
|
|
@@ -115,7 +115,7 @@ export class ProviderError extends Error {
|
|
|
115
115
|
readonly window?: RateLimitWindow;
|
|
116
116
|
/** Explicit server directive (`x-should-retry`), overriding default classification. */
|
|
117
117
|
readonly shouldRetry?: boolean;
|
|
118
|
-
/** The provider's own response body, truncated
|
|
118
|
+
/** The provider's own response body, truncated. It carries the actual reason,
|
|
119
119
|
* otherwise lost behind "400 status code (no body)". */
|
|
120
120
|
readonly body?: string;
|
|
121
121
|
|
|
@@ -213,7 +213,7 @@ export function messageOf(err: unknown): string {
|
|
|
213
213
|
* Everything readable about the failure as one searchable string: the message
|
|
214
214
|
* plus the parsed provider body. SDKs park the parsed body on `.error`, and
|
|
215
215
|
* that is where the real reason lives ("max_tokens too large", the quotaId,
|
|
216
|
-
* `"type": "billing_error"`). Scanned as
|
|
216
|
+
* `"type": "billing_error"`). Scanned as text: the error-type strings
|
|
217
217
|
* serialize into it, so no JSON walking is needed.
|
|
218
218
|
*/
|
|
219
219
|
function bodyTextOf(err: unknown): string {
|
|
@@ -293,7 +293,7 @@ function isAbort(err: unknown): boolean {
|
|
|
293
293
|
* A transport fault: the socket died before or during the response, so there
|
|
294
294
|
* is NO status and no body for the patterns below to read.
|
|
295
295
|
*
|
|
296
|
-
* The chain is walked because the useful code is rarely on the thrown error
|
|
296
|
+
* The chain is walked because the useful code is rarely on the thrown error:
|
|
297
297
|
* it sits on `cause`, sometimes several wrappers deep. Without this walk every
|
|
298
298
|
* network blip classifies as permanent, and a long run dies on its first
|
|
299
299
|
* hiccup, which is the single likeliest way to lose a minutes-long job.
|
|
@@ -455,7 +455,7 @@ const CONTENT_PATTERNS: readonly RegExp[] = [
|
|
|
455
455
|
* A throttle said in words rather than in a 429.
|
|
456
456
|
*
|
|
457
457
|
* Every other kind has body evidence; rate had only the status, which is
|
|
458
|
-
* exactly the evidence an in-band failure lacks
|
|
458
|
+
* exactly the evidence an in-band failure lacks. An SSE response is already
|
|
459
459
|
* 200 when the throttle lands, so the reason arrives as a payload with no
|
|
460
460
|
* status line at all. Checked below the status branches, so it only ever
|
|
461
461
|
* catches what would otherwise fall through to "unknown".
|
|
@@ -501,18 +501,18 @@ const matches = (patterns: readonly RegExp[], text: string): boolean =>
|
|
|
501
501
|
* The kind of failure, from whatever was thrown.
|
|
502
502
|
*
|
|
503
503
|
* Body patterns outrank status for the 4xx family; within them, context beats
|
|
504
|
-
* entitlement beats quota beats auth
|
|
504
|
+
* entitlement beats quota beats auth. Each earlier category's fix is useless
|
|
505
505
|
* for the later ones.
|
|
506
506
|
*/
|
|
507
507
|
/**
|
|
508
|
-
* The kind of a failure that arrived as an HTTP response
|
|
508
|
+
* The kind of a failure that arrived as an HTTP response: a status and a body,
|
|
509
509
|
* with nothing thrown.
|
|
510
510
|
*
|
|
511
511
|
* `classify` below is for a caught error, and it reads `status` and the body
|
|
512
512
|
* text off that error when they are not passed separately. A response has no
|
|
513
513
|
* error object, so callers were inventing one to fill the slot: a bare body
|
|
514
514
|
* string, the same text twice, a `{ status, error }` literal. All three are
|
|
515
|
-
* inert
|
|
515
|
+
* inert (nothing on them can satisfy `isAbort` or `isTransportFailure`), so
|
|
516
516
|
* they were three spellings of `undefined`.
|
|
517
517
|
*/
|
|
518
518
|
export function classifyHttp(status: number | undefined, body: string): ErrorKind {
|
|
@@ -568,7 +568,7 @@ export function classify(err: unknown, status?: number, body?: string): ErrorKin
|
|
|
568
568
|
/**
|
|
569
569
|
* How long the provider asked us to wait, in ms. Two dialects: Gemini's
|
|
570
570
|
* RetryInfo (`"retryDelay": "52s"`, inside the body) and the `Retry-After`
|
|
571
|
-
* header, which SDKs keep on the error. Honouring it beats guessing
|
|
571
|
+
* header, which SDKs keep on the error. Honouring it beats guessing: a
|
|
572
572
|
* backoff shorter than the window just burns an attempt.
|
|
573
573
|
*/
|
|
574
574
|
export function parseRetryAfterMs(err: unknown, body?: string): number | undefined {
|
|
@@ -664,14 +664,14 @@ export function parseContextOverflow(err: unknown, body?: string): ContextOverfl
|
|
|
664
664
|
* completion: retry sees nothing to retry, and a key pool never rotates off an
|
|
665
665
|
* exhausted key.
|
|
666
666
|
*
|
|
667
|
-
* `error` is the vendor's own payload, whatever shape it came in
|
|
667
|
+
* `error` is the vendor's own payload, whatever shape it came in: the numeric
|
|
668
668
|
* HTTP `code` Gemini and OpenRouter use, the slug OpenAI and Anthropic put in
|
|
669
669
|
* `code`/`type`, the canonical `status` name. The whole thing serializes into
|
|
670
670
|
* the searchable body, so RetryInfo's `retryDelay` is honoured wherever it sits.
|
|
671
671
|
*
|
|
672
672
|
* `unknown` is floored to "overload" rather than kept: a stream that dies after
|
|
673
673
|
* its headers is by construction a transient upstream fault, and "unknown" is
|
|
674
|
-
* never retried. Everything the body
|
|
674
|
+
* never retried. Everything the body does name keeps its own kind, which is
|
|
675
675
|
* what tells the caller to wait, rotate a key, or compact instead of retrying
|
|
676
676
|
* a failure that will repeat.
|
|
677
677
|
*/
|
|
@@ -697,7 +697,7 @@ export function streamError(provider: string, error: unknown): ProviderError {
|
|
|
697
697
|
}
|
|
698
698
|
|
|
699
699
|
/** The reset a stream error carries: the body's clock, plus the rate-limit
|
|
700
|
-
* headers the ChatGPT backend mirrors
|
|
700
|
+
* headers the ChatGPT backend mirrors into its error frame, the only place a
|
|
701
701
|
* failure after the 200 can say which window ran out. */
|
|
702
702
|
function readReset(error: unknown, body: string) {
|
|
703
703
|
const raw =
|
|
@@ -720,7 +720,7 @@ function readReset(error: unknown, body: string) {
|
|
|
720
720
|
* read it: a caller that never checks the finish keeps half an answer as if it
|
|
721
721
|
* were the whole one, the ledger records a billed turn as free, and no pool
|
|
722
722
|
* learns the endpoint is dropping streams. cc-proxy shipped the same fix
|
|
723
|
-
* (0.1.39) for the same reason
|
|
723
|
+
* (0.1.39) for the same reason: "instead of marking affected streamed
|
|
724
724
|
* responses as successful".
|
|
725
725
|
*
|
|
726
726
|
* `network`, because that is what it is: the same failure as a socket that
|
|
@@ -736,7 +736,7 @@ export function streamCut(provider: string): ProviderError {
|
|
|
736
736
|
);
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
-
/** The
|
|
739
|
+
/** The failure details you can log, so a dead run never reads
|
|
740
740
|
* "400 status code (no body)". */
|
|
741
741
|
export function describeProviderError(err: unknown): Record<string, unknown> {
|
|
742
742
|
if (err instanceof ProviderError) {
|
package/src/image.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface ImageLimits {
|
|
|
16
16
|
minSide: number;
|
|
17
17
|
/** Smallest area, in square pixels. */
|
|
18
18
|
minArea: number;
|
|
19
|
-
/** Largest decoded size
|
|
19
|
+
/** Largest decoded size in bytes (width × height × bytes per pixel). */
|
|
20
20
|
maxDecodedBytes: number;
|
|
21
21
|
/** Most images in one request. The newest win; older ones become notes. */
|
|
22
22
|
maxImages: number;
|