@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.
Files changed (137) hide show
  1. package/README.md +7 -7
  2. package/dist/attribution.d.ts +2 -2
  3. package/dist/auth/flows.d.ts.map +1 -1
  4. package/dist/auth/flows.js +5 -5
  5. package/dist/auth/flows.js.map +1 -1
  6. package/dist/auth/index.d.ts +1 -0
  7. package/dist/auth/index.d.ts.map +1 -1
  8. package/dist/auth/index.js +1 -0
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/auth/oauth.d.ts +12 -0
  11. package/dist/auth/oauth.d.ts.map +1 -1
  12. package/dist/auth/oauth.js +19 -1
  13. package/dist/auth/oauth.js.map +1 -1
  14. package/dist/auth/token-source.d.ts +9 -3
  15. package/dist/auth/token-source.d.ts.map +1 -1
  16. package/dist/auth/token-source.js +24 -9
  17. package/dist/auth/token-source.js.map +1 -1
  18. package/dist/auth/with-token.d.ts +29 -0
  19. package/dist/auth/with-token.d.ts.map +1 -0
  20. package/dist/auth/with-token.js +85 -0
  21. package/dist/auth/with-token.js.map +1 -0
  22. package/dist/capability.d.ts +9 -9
  23. package/dist/capability.js +9 -9
  24. package/dist/capability.js.map +1 -1
  25. package/dist/context.d.ts +43 -5
  26. package/dist/context.d.ts.map +1 -1
  27. package/dist/context.js +123 -4
  28. package/dist/context.js.map +1 -1
  29. package/dist/errors.d.ts +26 -17
  30. package/dist/errors.d.ts.map +1 -1
  31. package/dist/errors.js +57 -18
  32. package/dist/errors.js.map +1 -1
  33. package/dist/fallback.d.ts +45 -1
  34. package/dist/fallback.d.ts.map +1 -1
  35. package/dist/fallback.js +138 -75
  36. package/dist/fallback.js.map +1 -1
  37. package/dist/image.d.ts +1 -1
  38. package/dist/image.d.ts.map +1 -1
  39. package/dist/jev.d.ts +9 -9
  40. package/dist/jev.d.ts.map +1 -1
  41. package/dist/jev.js +1 -1
  42. package/dist/key-pool.d.ts +14 -8
  43. package/dist/key-pool.d.ts.map +1 -1
  44. package/dist/key-pool.js +35 -18
  45. package/dist/key-pool.js.map +1 -1
  46. package/dist/presets.d.ts +33 -24
  47. package/dist/presets.d.ts.map +1 -1
  48. package/dist/presets.js +25 -14
  49. package/dist/presets.js.map +1 -1
  50. package/dist/providers/anthropic.d.ts +5 -5
  51. package/dist/providers/anthropic.d.ts.map +1 -1
  52. package/dist/providers/anthropic.js +89 -34
  53. package/dist/providers/anthropic.js.map +1 -1
  54. package/dist/providers/factory.d.ts +2 -2
  55. package/dist/providers/factory.d.ts.map +1 -1
  56. package/dist/providers/factory.js +2 -1
  57. package/dist/providers/factory.js.map +1 -1
  58. package/dist/providers/gemini.d.ts +8 -2
  59. package/dist/providers/gemini.d.ts.map +1 -1
  60. package/dist/providers/gemini.js +117 -41
  61. package/dist/providers/gemini.js.map +1 -1
  62. package/dist/providers/openai.d.ts +12 -9
  63. package/dist/providers/openai.d.ts.map +1 -1
  64. package/dist/providers/openai.js +72 -21
  65. package/dist/providers/openai.js.map +1 -1
  66. package/dist/providers/responses.d.ts +3 -3
  67. package/dist/providers/responses.d.ts.map +1 -1
  68. package/dist/providers/responses.js +103 -57
  69. package/dist/providers/responses.js.map +1 -1
  70. package/dist/providers/zai.d.ts +2 -2
  71. package/dist/providers/zai.js +2 -2
  72. package/dist/rate-limit.d.ts +13 -3
  73. package/dist/rate-limit.d.ts.map +1 -1
  74. package/dist/rate-limit.js +10 -4
  75. package/dist/rate-limit.js.map +1 -1
  76. package/dist/retry.d.ts +16 -5
  77. package/dist/retry.d.ts.map +1 -1
  78. package/dist/retry.js +29 -16
  79. package/dist/retry.js.map +1 -1
  80. package/dist/schema.d.ts +6 -6
  81. package/dist/schema.js +8 -8
  82. package/dist/tool-args.d.ts +23 -1
  83. package/dist/tool-args.d.ts.map +1 -1
  84. package/dist/tool-args.js +30 -2
  85. package/dist/tool-args.js.map +1 -1
  86. package/dist/tools.d.ts +3 -3
  87. package/dist/tools.js +2 -2
  88. package/dist/tools.js.map +1 -1
  89. package/dist/transport.d.ts +40 -10
  90. package/dist/transport.d.ts.map +1 -1
  91. package/dist/transport.js +82 -21
  92. package/dist/transport.js.map +1 -1
  93. package/dist/types.d.ts +37 -23
  94. package/dist/types.d.ts.map +1 -1
  95. package/dist/types.js +3 -11
  96. package/dist/types.js.map +1 -1
  97. package/dist/usage.d.ts +4 -4
  98. package/dist/usage.d.ts.map +1 -1
  99. package/dist/usage.js +3 -3
  100. package/dist/usage.js.map +1 -1
  101. package/dist/watchdog.d.ts +14 -12
  102. package/dist/watchdog.d.ts.map +1 -1
  103. package/dist/watchdog.js +35 -16
  104. package/dist/watchdog.js.map +1 -1
  105. package/dist/zod.d.ts +4 -4
  106. package/dist/zod.js +3 -3
  107. package/package.json +1 -1
  108. package/src/attribution.ts +2 -2
  109. package/src/auth/flows.ts +5 -4
  110. package/src/auth/index.ts +1 -0
  111. package/src/auth/oauth.ts +23 -1
  112. package/src/auth/token-source.ts +30 -11
  113. package/src/auth/with-token.ts +97 -0
  114. package/src/capability.ts +12 -12
  115. package/src/context.ts +141 -6
  116. package/src/errors.ts +63 -23
  117. package/src/fallback.ts +173 -75
  118. package/src/image.ts +1 -1
  119. package/src/jev.ts +11 -11
  120. package/src/key-pool.ts +37 -20
  121. package/src/presets.ts +38 -24
  122. package/src/providers/anthropic.ts +97 -38
  123. package/src/providers/factory.ts +4 -3
  124. package/src/providers/gemini.ts +121 -44
  125. package/src/providers/openai.ts +78 -25
  126. package/src/providers/responses.ts +113 -61
  127. package/src/providers/zai.ts +2 -2
  128. package/src/rate-limit.ts +35 -7
  129. package/src/retry.ts +46 -15
  130. package/src/schema.ts +8 -8
  131. package/src/tool-args.ts +46 -2
  132. package/src/tools.ts +4 -4
  133. package/src/transport.ts +103 -23
  134. package/src/types.ts +37 -23
  135. package/src/usage.ts +5 -5
  136. package/src/watchdog.ts +38 -19
  137. 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 — 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, is transient, and fires while nothing has streamed yet,
30
- // so the retry is always safe.
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 — the wait for the response to start included —
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 — its headers, a keep-alive, a
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 — the
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 when — and only when — it was
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
- fired = new ProviderError(provider, "timeout", `stream went ${ms / 1000}s without ${what}`);
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, { cause: err });
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 — completion, throw, or the consumer
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 — the caller simply shows a
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 — the fix is a larger `maxTokens` or a lower effort. Any
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 that matters — retry only while nothing was emitted — still holds.
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} — raise maxTokens or lower effort`,
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` — build a watch, hand the provider the WATCH's signal, wrap
271
- * the chunks — and the middle one is the trap. Pass the caller's signal
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` — and both of the failures it
280
- * catches are transient, which is what makes wrapping it in a retry correct.
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 — OpenAI
16
- * under `strict: true`, Gemini's `parametersJsonSchema` — rejects the whole
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 — an ordinary tool can simply be called again.
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 MODEL in words it can act on — `topic: expected string, received number`
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> {