@providerkit/core 0.21.1 → 0.23.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/auth/flows.d.ts.map +1 -1
- package/dist/auth/flows.js +5 -5
- package/dist/auth/flows.js.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth.d.ts +12 -0
- package/dist/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js +19 -1
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/token-source.d.ts +9 -3
- package/dist/auth/token-source.d.ts.map +1 -1
- package/dist/auth/token-source.js +24 -9
- package/dist/auth/token-source.js.map +1 -1
- package/dist/auth/with-token.d.ts +29 -0
- package/dist/auth/with-token.d.ts.map +1 -0
- package/dist/auth/with-token.js +85 -0
- package/dist/auth/with-token.js.map +1 -0
- 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 +43 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +123 -4
- package/dist/context.js.map +1 -1
- package/dist/errors.d.ts +26 -17
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +57 -18
- package/dist/errors.js.map +1 -1
- package/dist/fallback.d.ts +45 -1
- package/dist/fallback.d.ts.map +1 -1
- package/dist/fallback.js +138 -75
- package/dist/fallback.js.map +1 -1
- 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 +14 -8
- package/dist/key-pool.d.ts.map +1 -1
- package/dist/key-pool.js +35 -18
- package/dist/key-pool.js.map +1 -1
- package/dist/presets.d.ts +33 -24
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +25 -14
- 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 +89 -34
- package/dist/providers/anthropic.js.map +1 -1
- 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 +8 -2
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +117 -41
- 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 +72 -21
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/responses.d.ts +3 -3
- package/dist/providers/responses.d.ts.map +1 -1
- package/dist/providers/responses.js +103 -57
- 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 +13 -3
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +10 -4
- package/dist/rate-limit.js.map +1 -1
- package/dist/retry.d.ts +16 -5
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +29 -16
- package/dist/retry.js.map +1 -1
- package/dist/schema.d.ts +6 -6
- package/dist/schema.js +8 -8
- package/dist/tool-args.d.ts +23 -1
- package/dist/tool-args.d.ts.map +1 -1
- package/dist/tool-args.js +30 -2
- package/dist/tool-args.js.map +1 -1
- 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 +40 -10
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +82 -21
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +37 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -11
- 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 +14 -12
- package/dist/watchdog.d.ts.map +1 -1
- package/dist/watchdog.js +35 -16
- package/dist/watchdog.js.map +1 -1
- 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/auth/flows.ts +5 -4
- package/src/auth/index.ts +1 -0
- package/src/auth/oauth.ts +23 -1
- package/src/auth/token-source.ts +30 -11
- package/src/auth/with-token.ts +97 -0
- package/src/capability.ts +12 -12
- package/src/context.ts +141 -6
- package/src/errors.ts +63 -23
- package/src/fallback.ts +173 -75
- package/src/image.ts +1 -1
- package/src/jev.ts +11 -11
- package/src/key-pool.ts +37 -20
- package/src/presets.ts +38 -24
- package/src/providers/anthropic.ts +97 -38
- package/src/providers/factory.ts +4 -3
- package/src/providers/gemini.ts +121 -44
- package/src/providers/openai.ts +78 -25
- package/src/providers/responses.ts +113 -61
- package/src/providers/zai.ts +2 -2
- package/src/rate-limit.ts +35 -7
- package/src/retry.ts +46 -15
- package/src/schema.ts +8 -8
- package/src/tool-args.ts +46 -2
- package/src/tools.ts +4 -4
- package/src/transport.ts +103 -23
- package/src/types.ts +37 -23
- package/src/usage.ts +5 -5
- package/src/watchdog.ts +38 -19
- package/src/zod.ts +4 -4
package/src/watchdog.ts
CHANGED
|
@@ -24,10 +24,14 @@
|
|
|
24
24
|
// scratch, which re-ran the reasoning into the same deadline.
|
|
25
25
|
//
|
|
26
26
|
// When either fires, the watchdog aborts ITS OWN controller and the caller's
|
|
27
|
-
// signal is only bridged in
|
|
28
|
-
// distinguishable from our timeout. One is their cancel and is never retried
|
|
29
|
-
//
|
|
30
|
-
//
|
|
27
|
+
// signal is only bridged in, which is what keeps a person's Stop
|
|
28
|
+
// distinguishable from our timeout. One is their cancel and is never retried.
|
|
29
|
+
// The other is ours and transient, with one exception: a progress timeout
|
|
30
|
+
// before the response started carries `shouldRetry: false`. That wait was a
|
|
31
|
+
// backend thinking with its headers held back, and a replay sends the same
|
|
32
|
+
// prompt into the same 300s and pays for the reasoning again, up to three
|
|
33
|
+
// times (cc-proxy never replays slow headers). Its kind stays `timeout`, so a
|
|
34
|
+
// fallback pool still moves on to its next candidate.
|
|
31
35
|
import { ProviderError } from "./errors.ts";
|
|
32
36
|
import type {
|
|
33
37
|
ChatMessage,
|
|
@@ -42,7 +46,7 @@ import type {
|
|
|
42
46
|
* stream is dead. */
|
|
43
47
|
export const STREAM_IDLE_MS = 60_000;
|
|
44
48
|
|
|
45
|
-
/** No chunk for this long
|
|
49
|
+
/** No chunk for this long, including the wait for the response to start,
|
|
46
50
|
* and the stream is going nowhere, keep-alives or not. */
|
|
47
51
|
export const STREAM_PROGRESS_MS = 300_000;
|
|
48
52
|
|
|
@@ -51,17 +55,17 @@ export interface StreamWatch {
|
|
|
51
55
|
readonly signal: AbortSignal;
|
|
52
56
|
/** A chunk arrived: re-arm both deadlines, and mark TTFT if it was the first. */
|
|
53
57
|
sawByte(): void;
|
|
54
|
-
/** The response showed life without a chunk
|
|
58
|
+
/** The response showed life without a chunk: its headers, a keep-alive, a
|
|
55
59
|
* partial frame. Starts or re-arms the idle deadline only. */
|
|
56
60
|
sawActivity(): void;
|
|
57
61
|
/**
|
|
58
|
-
* Milliseconds from the call opening to its first chunk of any kind
|
|
62
|
+
* Milliseconds from the call opening to its first chunk of any kind. The
|
|
59
63
|
* wait a person actually experiences, and the number a prompt-cache pin
|
|
60
64
|
* exists to shrink. Null until something arrives.
|
|
61
65
|
*/
|
|
62
66
|
firstChunkMs(): number | null;
|
|
63
67
|
/**
|
|
64
|
-
* Re-issue a provider failure as the timeout
|
|
68
|
+
* Re-issue a provider failure as the timeout only when it was
|
|
65
69
|
* one of our deadlines that aborted. A caller's Stop passes through untouched.
|
|
66
70
|
*/
|
|
67
71
|
classify(err: unknown): unknown;
|
|
@@ -91,6 +95,9 @@ export function streamWatch(opts: StreamWatchOptions = {}): StreamWatch {
|
|
|
91
95
|
let firstChunk: number | null = null;
|
|
92
96
|
let fired: ProviderError | null = null;
|
|
93
97
|
let disposed = false;
|
|
98
|
+
// The first sign of life (the headers, a keep-alive, a chunk). The idle
|
|
99
|
+
// clock waits for it, and a timeout that fires before it is not replayed.
|
|
100
|
+
let responded = false;
|
|
94
101
|
|
|
95
102
|
// The bridge is structural rather than an event listener: AbortSignal.any
|
|
96
103
|
// aborts synchronously when an input is ALREADY aborted, which is the race
|
|
@@ -101,7 +108,13 @@ export function streamWatch(opts: StreamWatchOptions = {}): StreamWatch {
|
|
|
101
108
|
|
|
102
109
|
function deadline(ms: number, what: string): ReturnType<typeof setTimeout> {
|
|
103
110
|
const timer = setTimeout(() => {
|
|
104
|
-
|
|
111
|
+
// Only the progress clock runs before the response starts, so this is
|
|
112
|
+
// the backend that thinks with its headers held back (see the header).
|
|
113
|
+
fired = responded
|
|
114
|
+
? new ProviderError(provider, "timeout", `stream went ${ms / 1000}s without ${what}`)
|
|
115
|
+
: new ProviderError(provider, "timeout", `no response in ${ms / 1000}s`, {
|
|
116
|
+
shouldRetry: false,
|
|
117
|
+
});
|
|
105
118
|
timeout.abort(fired);
|
|
106
119
|
}, ms);
|
|
107
120
|
// An orphaned watch — its consumer gone, dispose never called — must not
|
|
@@ -117,6 +130,7 @@ export function streamWatch(opts: StreamWatchOptions = {}): StreamWatch {
|
|
|
117
130
|
const live = () => !disposed && !signal.aborted;
|
|
118
131
|
|
|
119
132
|
function sawActivity() {
|
|
133
|
+
responded = true;
|
|
120
134
|
clearTimeout(idle);
|
|
121
135
|
if (live()) idle = deadline(idleMs, "a byte");
|
|
122
136
|
}
|
|
@@ -134,7 +148,10 @@ export function streamWatch(opts: StreamWatchOptions = {}): StreamWatch {
|
|
|
134
148
|
classify(err: unknown) {
|
|
135
149
|
// Our deadline, not theirs — and not the caller's Stop.
|
|
136
150
|
if (fired && !(callerSignal?.aborted ?? false)) {
|
|
137
|
-
return new ProviderError(fired.provider, "timeout", fired.message, {
|
|
151
|
+
return new ProviderError(fired.provider, "timeout", fired.message, {
|
|
152
|
+
shouldRetry: fired.shouldRetry,
|
|
153
|
+
cause: err,
|
|
154
|
+
});
|
|
138
155
|
}
|
|
139
156
|
return err;
|
|
140
157
|
},
|
|
@@ -148,7 +165,7 @@ export function streamWatch(opts: StreamWatchOptions = {}): StreamWatch {
|
|
|
148
165
|
|
|
149
166
|
/**
|
|
150
167
|
* Wrap a stream so every chunk re-arms `watch`, and a failure is re-classified
|
|
151
|
-
* through it. Disposes on any exit
|
|
168
|
+
* through it. Disposes on any exit: completion, throw, or the consumer
|
|
152
169
|
* breaking out of the loop.
|
|
153
170
|
*/
|
|
154
171
|
export async function* watchChunks<T>(
|
|
@@ -173,7 +190,7 @@ export async function* watchChunks<T>(
|
|
|
173
190
|
* A stream that ends with no text and no tool call is a failure wearing a
|
|
174
191
|
* success's clothes: `stop_reason: end_turn` with zero content blocks, which
|
|
175
192
|
* the vendors emit under load, or a thinking block that ate the whole
|
|
176
|
-
* `max_tokens`. Nothing throws, so nothing retries
|
|
193
|
+
* `max_tokens`. Nothing throws, so nothing retries. The caller simply shows a
|
|
177
194
|
* person an empty answer, and the only trace is a bill.
|
|
178
195
|
*
|
|
179
196
|
* Reasoning is not an answer. It still streams the moment it arrives, so a
|
|
@@ -185,12 +202,12 @@ export async function* watchChunks<T>(
|
|
|
185
202
|
*
|
|
186
203
|
* Classified by what fixes it. A `length` finish is the caller's own cap:
|
|
187
204
|
* `invalid`, so nothing retries it into the same cap and no backup model is
|
|
188
|
-
* cooled down for it
|
|
205
|
+
* cooled down for it. The fix is a larger `maxTokens` or a lower effort. Any
|
|
189
206
|
* other empty turn is `overload`, because that is both true and useful: it is
|
|
190
207
|
* theirs and temporary, so it is transient (the same model, retried, usually
|
|
191
208
|
* answers) and backup-eligible (a model that keeps doing it should be walked
|
|
192
209
|
* away from). Until something streams, the empty frames are held back, so the
|
|
193
|
-
* retry rule
|
|
210
|
+
* retry rule still holds: retry only while nothing was emitted.
|
|
194
211
|
*/
|
|
195
212
|
export async function* requireContent<T extends ProviderChunk>(
|
|
196
213
|
provider: string,
|
|
@@ -240,7 +257,7 @@ export async function* requireContent<T extends ProviderChunk>(
|
|
|
240
257
|
throw new ProviderError(
|
|
241
258
|
provider,
|
|
242
259
|
"invalid",
|
|
243
|
-
`${provider}: the output cap ran out before any answer${spent}
|
|
260
|
+
`${provider}: the output cap ran out before any answer${spent}. Raise maxTokens or lower effort.`,
|
|
244
261
|
);
|
|
245
262
|
}
|
|
246
263
|
throw new ProviderError(provider, "overload", `${provider}: completed with no content`);
|
|
@@ -267,8 +284,8 @@ export interface WatchdogOptions {
|
|
|
267
284
|
* A provider with both silent failures already handled.
|
|
268
285
|
*
|
|
269
286
|
* Every consumer of this package wrote the same three lines around every
|
|
270
|
-
* `createStream
|
|
271
|
-
* the chunks
|
|
287
|
+
* `createStream`: build a watch, hand the provider the watch's signal, wrap
|
|
288
|
+
* the chunks. The middle one is the trap. Pass the caller's signal
|
|
272
289
|
* instead and everything still compiles, still streams, still passes the
|
|
273
290
|
* tests: the watchdog simply never aborts anything, because the request it was
|
|
274
291
|
* meant to cancel was never told about it. The failure has no symptom until
|
|
@@ -276,8 +293,10 @@ export interface WatchdogOptions {
|
|
|
276
293
|
*
|
|
277
294
|
* So the composition belongs here rather than in a docs snippet each app
|
|
278
295
|
* copies. The result is still a `Provider`, so it composes unchanged with
|
|
279
|
-
* `withStreamRetry` and `streamWithBackupModels
|
|
280
|
-
*
|
|
296
|
+
* `withStreamRetry` and `streamWithBackupModels`. Timeouts and empty turns
|
|
297
|
+
* without a `length` finish are transient, so wrapping it in a retry is safe.
|
|
298
|
+
* The one timeout a replay would make worse, no response at all within the
|
|
299
|
+
* progress window, says so itself with `shouldRetry: false`.
|
|
281
300
|
*/
|
|
282
301
|
export function withWatchdog(provider: Provider, opts: WatchdogOptions = {}): Provider {
|
|
283
302
|
return {
|
package/src/zod.ts
CHANGED
|
@@ -12,8 +12,8 @@ import type { JsonObjectSchema } from "./types.ts";
|
|
|
12
12
|
* A zod schema as the JSON Schema every provider's tool contract wants.
|
|
13
13
|
*
|
|
14
14
|
* `$schema` is dropped. zod emits the dialect URI at the root, and a provider
|
|
15
|
-
* validating a tool's `parameters` against its own supported subset
|
|
16
|
-
* under `strict: true`, Gemini's `parametersJsonSchema`
|
|
15
|
+
* validating a tool's `parameters` against its own supported subset (OpenAI
|
|
16
|
+
* under `strict: true`, Gemini's `parametersJsonSchema`) rejects the whole
|
|
17
17
|
* tool over that one key, with a message that names neither zod nor the field.
|
|
18
18
|
*/
|
|
19
19
|
export function toJsonObjectSchema(schema: z.ZodType, label = "schema"): JsonObjectSchema {
|
|
@@ -46,14 +46,14 @@ export interface ZodToolSpec<I, O> {
|
|
|
46
46
|
* rejecting the call. Worth it for a TERMINAL tool, which gets no second
|
|
47
47
|
* chance: a forced-submit salvage turn runs exactly once, and discarding an
|
|
48
48
|
* otherwise-valid answer over a few extra characters loses the whole run.
|
|
49
|
-
* Off by default
|
|
49
|
+
* Off by default: an ordinary tool can simply be called again.
|
|
50
50
|
*/
|
|
51
51
|
clampOverflow?: boolean;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* A tool whose arguments are validated by zod, with the failure reported to
|
|
56
|
-
* the
|
|
56
|
+
* the model in words it can act on. `topic: expected string, received number`
|
|
57
57
|
* beats a stack trace it cannot read.
|
|
58
58
|
*/
|
|
59
59
|
export function zodTool<I, O>(spec: ZodToolSpec<I, O>): Tool<I, O> {
|