@providerkit/core 0.21.1 → 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.
Files changed (92) hide show
  1. package/README.md +7 -7
  2. package/dist/attribution.d.ts +2 -2
  3. package/dist/capability.d.ts +9 -9
  4. package/dist/capability.js +9 -9
  5. package/dist/capability.js.map +1 -1
  6. package/dist/context.d.ts +4 -4
  7. package/dist/context.js +4 -4
  8. package/dist/errors.d.ts +15 -15
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/errors.js +14 -14
  11. package/dist/image.d.ts +1 -1
  12. package/dist/image.d.ts.map +1 -1
  13. package/dist/jev.d.ts +9 -9
  14. package/dist/jev.d.ts.map +1 -1
  15. package/dist/jev.js +1 -1
  16. package/dist/key-pool.d.ts +4 -4
  17. package/dist/key-pool.js +6 -6
  18. package/dist/key-pool.js.map +1 -1
  19. package/dist/presets.d.ts +26 -22
  20. package/dist/presets.d.ts.map +1 -1
  21. package/dist/presets.js +13 -12
  22. package/dist/presets.js.map +1 -1
  23. package/dist/providers/anthropic.d.ts +5 -5
  24. package/dist/providers/anthropic.d.ts.map +1 -1
  25. package/dist/providers/anthropic.js +9 -9
  26. package/dist/providers/factory.d.ts +2 -2
  27. package/dist/providers/factory.d.ts.map +1 -1
  28. package/dist/providers/factory.js +2 -1
  29. package/dist/providers/factory.js.map +1 -1
  30. package/dist/providers/gemini.d.ts +2 -2
  31. package/dist/providers/gemini.d.ts.map +1 -1
  32. package/dist/providers/gemini.js +15 -14
  33. package/dist/providers/gemini.js.map +1 -1
  34. package/dist/providers/openai.d.ts +12 -9
  35. package/dist/providers/openai.d.ts.map +1 -1
  36. package/dist/providers/openai.js +9 -7
  37. package/dist/providers/openai.js.map +1 -1
  38. package/dist/providers/responses.d.ts +3 -3
  39. package/dist/providers/responses.js +5 -5
  40. package/dist/providers/responses.js.map +1 -1
  41. package/dist/providers/zai.d.ts +2 -2
  42. package/dist/providers/zai.js +2 -2
  43. package/dist/rate-limit.d.ts +3 -3
  44. package/dist/rate-limit.js +3 -3
  45. package/dist/retry.d.ts +5 -5
  46. package/dist/retry.js +4 -4
  47. package/dist/schema.d.ts +6 -6
  48. package/dist/schema.js +8 -8
  49. package/dist/tool-args.d.ts +1 -1
  50. package/dist/tool-args.js +2 -2
  51. package/dist/tools.d.ts +3 -3
  52. package/dist/tools.js +2 -2
  53. package/dist/tools.js.map +1 -1
  54. package/dist/transport.d.ts +7 -7
  55. package/dist/transport.js +6 -6
  56. package/dist/types.d.ts +24 -23
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/types.js +3 -3
  59. package/dist/types.js.map +1 -1
  60. package/dist/usage.d.ts +4 -4
  61. package/dist/usage.d.ts.map +1 -1
  62. package/dist/usage.js +3 -3
  63. package/dist/usage.js.map +1 -1
  64. package/dist/watchdog.d.ts +12 -12
  65. package/dist/watchdog.js +10 -10
  66. package/dist/zod.d.ts +4 -4
  67. package/dist/zod.js +3 -3
  68. package/package.json +1 -1
  69. package/src/attribution.ts +2 -2
  70. package/src/capability.ts +12 -12
  71. package/src/context.ts +4 -4
  72. package/src/errors.ts +18 -18
  73. package/src/image.ts +1 -1
  74. package/src/jev.ts +11 -11
  75. package/src/key-pool.ts +7 -7
  76. package/src/presets.ts +26 -22
  77. package/src/providers/anthropic.ts +14 -14
  78. package/src/providers/factory.ts +4 -3
  79. package/src/providers/gemini.ts +19 -16
  80. package/src/providers/openai.ts +17 -12
  81. package/src/providers/responses.ts +11 -11
  82. package/src/providers/zai.ts +2 -2
  83. package/src/rate-limit.ts +4 -4
  84. package/src/retry.ts +5 -5
  85. package/src/schema.ts +8 -8
  86. package/src/tool-args.ts +2 -2
  87. package/src/tools.ts +4 -4
  88. package/src/transport.ts +7 -7
  89. package/src/types.ts +24 -23
  90. package/src/usage.ts +5 -5
  91. package/src/watchdog.ts +13 -13
  92. package/src/zod.ts +4 -4
@@ -39,7 +39,7 @@ export interface ResponsesConfig extends ProviderFallbackConfig {
39
39
  effort?: Effort;
40
40
  maxTokens?: number;
41
41
  fetchImpl?: typeof fetch;
42
- /** Extra request headers — where a subscription backend's account id goes
42
+ /** Extra request headers, where a subscription backend's account id goes
43
43
  * (`ChatGPT-Account-Id`), which those backends reject the request without. */
44
44
  headers?: Record<string, string>;
45
45
  /** Header that carries `StreamOptions.sessionId`. When set and a call has no
@@ -50,7 +50,7 @@ export interface ResponsesConfig extends ProviderFallbackConfig {
50
50
  * The ChatGPT subscription surface serves it at `/backend-api/codex/responses`
51
51
  * with no version segment, so that backend needs
52
52
  * `{ baseUrl: "https://chatgpt.com/backend-api/codex", path: "/responses" }`.
53
- * Without the override the POST 404s, and a 404 classifies as "model" — the
53
+ * Without the override the POST 404s, and a 404 classifies as "model". The
54
54
  * user is told the model id does not exist when the path was the problem.
55
55
  */
56
56
  path?: string;
@@ -141,8 +141,8 @@ function partsToResponses(
141
141
  });
142
142
  }
143
143
 
144
- /** A tool result is a bare string unless it carried images — then the content-
145
- * array form, the only way to hand this shape a screenshot back. */
144
+ /** A tool result is a bare string unless it carried images. Then it uses the
145
+ * content-array form, the only way to hand this shape a screenshot back. */
146
146
  function toolOutput(
147
147
  content: string,
148
148
  images: readonly ImagePart[],
@@ -157,7 +157,7 @@ function toolOutput(
157
157
  /**
158
158
  * Flatten a history into `instructions` plus the input item list.
159
159
  *
160
- * This shape has no system ROLE — the system prompt is a top-level
160
+ * This shape has no system role. The system prompt is a top-level
161
161
  * `instructions` string, and everything else is items. One assistant turn can
162
162
  * become several items (its text, then one `function_call` per tool it asked
163
163
  * for), which is why a message maps to a list rather than to one item.
@@ -244,7 +244,7 @@ interface ResponsesUsage {
244
244
 
245
245
  interface ResponsesItem {
246
246
  type?: string;
247
- /** The output item's own id (`fc_…`) — what the argument deltas reference. */
247
+ /** The output item's own id (`fc_…`), which the argument deltas reference. */
248
248
  id?: string;
249
249
  /** The id a `function_call_output` must quote on the next turn. */
250
250
  call_id?: string;
@@ -263,7 +263,7 @@ interface ResponsesEvent {
263
263
  arguments?: string;
264
264
  item?: ResponsesItem;
265
265
  response?: {
266
- /** `completed`, `incomplete` or `failed` — stated on the terminal event,
266
+ /** `completed`, `incomplete` or `failed`, stated on the terminal event,
267
267
  * and not always the one its `type` implies. */
268
268
  status?: string;
269
269
  usage?: ResponsesUsage;
@@ -297,8 +297,8 @@ interface CodexRateLimits {
297
297
  /**
298
298
  * The quota wall a `codex.rate_limits` snapshot reports, if it is one.
299
299
  *
300
- * The snapshot rides ahead of the first output on every turn and is telemetry
301
- * — a full window the account's credits cover, or that the backend still
300
+ * The snapshot rides ahead of the first output on every turn and is telemetry.
301
+ * A full window the account's credits cover, or that the backend still
302
302
  * `allowed`, lets the turn proceed. It is only the reason when the stream then
303
303
  * closes without a response: that close is a spent window, not a dropped
304
304
  * socket, and retrying it walks every backup into the same wall. The fuller
@@ -336,8 +336,8 @@ function usageChunk(usage: ResponsesUsage): ProviderChunk {
336
336
  };
337
337
  }
338
338
 
339
- /** What has already gone out for one in-flight function call — identity and
340
- * arguments both — so the authoritative snapshot on `.done` can be diffed
339
+ /** What has already gone out for one in-flight function call (identity and
340
+ * arguments), so the authoritative snapshot on `.done` can be diffed
341
341
  * against it instead of duplicated. */
342
342
  interface PendingCall {
343
343
  index: number;
@@ -13,8 +13,8 @@ export type ZaiCodingConfig = Omit<AnthropicConfig, "baseUrl" | "id" | "bearer">
13
13
  * adapter.
14
14
  *
15
15
  * Dialect notes, measured 2026-09-13 against the live endpoint on
16
- * `glm-5.3-flash`: an absent thinking field means the MODEL's default —
17
- * thinking ON — so `effort: "none"` is sent as an explicit
16
+ * `glm-5.3-flash`: an absent thinking field means the model's default
17
+ * (thinking on), so `effort: "none"` is sent as an explicit
18
18
  * `thinking: { type: "disabled" }`; `low`–`max` ride the standard thinking
19
19
  * budgets, which the endpoint accepts. Images force a thinking block even
20
20
  * under `disabled` (the model's choice, billed as output). */
package/src/rate-limit.ts CHANGED
@@ -32,7 +32,7 @@ export type RateLimitWindow = "5h" | "weekly" | "monthly";
32
32
  export interface RateLimitReset {
33
33
  /** Absolute time the binding window resets, when any header disclosed it. */
34
34
  resetAtMs?: number;
35
- /** Server-requested wait (`retry-after`) — what a retry policy honours. */
35
+ /** Server-requested wait (`retry-after`), which a retry policy honours. */
36
36
  retryAfterMs?: number;
37
37
  /** Which subscription window bound, when it could be named at all. */
38
38
  window?: RateLimitWindow;
@@ -58,7 +58,7 @@ const RESET_HEADERS = [
58
58
  ];
59
59
 
60
60
  /**
61
- * The ChatGPT backend's two windows. Both ride every response — the slots are
61
+ * The ChatGPT backend's two windows. Both ride every response. The slots are
62
62
  * "primary" and "secondary", NOT "5h" and "weekly", so the window is named by
63
63
  * its own `-window-minutes` (300 and 10080 in every capture), and its
64
64
  * `-used-percent` says which one is full. Recorded by cc-proxy off a live turn
@@ -154,7 +154,7 @@ function parseRetryAfter(value: string | null, now: number): number | undefined
154
154
  * Anthropic's `anthropic-ratelimit-unified-*` pair rides on every OAuth
155
155
  * response, so both windows are always reported and only one of them is the
156
156
  * reason for this 429. API-key accounts get the `anthropic-ratelimit-*-reset`
157
- * RFC 3339 timestamps instead. Everything is optional — an unrecognized shape
157
+ * RFC 3339 timestamps instead. Everything is optional: an unrecognized shape
158
158
  * yields an empty result, and the caller falls back to its generic message.
159
159
  */
160
160
  export function parseRateLimitReset(headers: Headers, now = Date.now()): RateLimitReset {
@@ -270,7 +270,7 @@ function parseProseRetryMs(text: string): number | undefined {
270
270
  * Other providers and gateways (LiteLLM, Cloudflare, Ollama) report countdowns in prose:
271
271
  * `Rate limit exceeded. Try again in 20s.`
272
272
  * The window name is inferred from the wait itself, and only past the floor
273
- * above — a sub-minute retry is a throttle, not a subscription window.
273
+ * above. A sub-minute retry is a throttle, not a subscription window.
274
274
  */
275
275
  export function parseUsageLimitBody(bodyText: string, now = Date.now()): RateLimitReset {
276
276
  let body: unknown;
package/src/retry.ts CHANGED
@@ -93,8 +93,8 @@ function delayFor(err: unknown, attempt: number, opts: RetryOptions): number | n
93
93
  const defaultShouldRetry = (err: unknown, _attempt: number): boolean => isRetryable(err);
94
94
 
95
95
  /**
96
- * Run `fn`, retrying transient failures with backoff. For one-shot calls —
97
- * a title, a summary, a compaction pass.
96
+ * Run `fn`, retrying transient failures with backoff. For one-shot calls such
97
+ * as a title, a summary or a compaction pass.
98
98
  */
99
99
  export async function withRetry<T>(
100
100
  fn: (attempt: number) => Promise<T>,
@@ -124,7 +124,7 @@ export async function withRetry<T>(
124
124
  }
125
125
 
126
126
  /**
127
- * The streaming twin — with the rule that makes it safe: a retry happens only
127
+ * The streaming twin of `withRetry`. A retry happens only
128
128
  * while NOTHING has been yielded yet.
129
129
  *
130
130
  * `factory` is re-invoked per attempt and gets a fresh signal, so an abandoned
@@ -182,7 +182,7 @@ export interface BackupModelOptions {
182
182
  models: string[];
183
183
  /**
184
184
  * Whether a failure may fall through to the remaining models. Default:
185
- * overload and rate limits only — those are per-model-endpoint, and nothing
185
+ * overload and rate limits only. Those are per-model-endpoint, and nothing
186
186
  * else on the list is. An auth failure or an invalid request would land
187
187
  * identically on every backup.
188
188
  */
@@ -226,7 +226,7 @@ export async function withBackupModels<T>(
226
226
  }
227
227
 
228
228
  /**
229
- * The streaming twin — carrying the same commitment rule as `withStreamRetry`.
229
+ * The streaming twin of `withBackupModels`, with the same commitment rule as `withStreamRetry`.
230
230
  *
231
231
  * This is the correction worth naming: walking to a backup model AFTER chunks
232
232
  * have already reached the consumer replays the answer from the top, in a
package/src/schema.ts CHANGED
@@ -70,19 +70,19 @@ export function clampToSchema(value: unknown, node: unknown): unknown {
70
70
  * Whether OpenAI's `strict` schema mode will accept this schema.
71
71
  *
72
72
  * Strict is the only JSON mode that actually guarantees the shape, so it is
73
- * worth having — but it demands more than JSON Schema does: every property an
73
+ * worth having, but it demands more than JSON Schema does: every property an
74
74
  * object lists must ALSO be required, and every object must close itself with
75
75
  * `additionalProperties: false`, all the way down. A schema with one optional
76
76
  * field is not "mostly strict"; it is a flat 400 naming a nested path rather
77
77
  * than the rule it broke.
78
78
  *
79
79
  * That trap is the reason this exists. An agent's response schema grows
80
- * optional fields naturally — a `data` block only some flows fill, the fields
81
- * one step collects — and a caller who adds one wants their answer, not a
80
+ * optional fields naturally (a `data` block only some flows fill, the fields
81
+ * one step collects). A caller who adds one wants their answer, not a
82
82
  * lecture about a mode they never asked for. So the OpenAI-shape adapters ask
83
83
  * this and drop to plain (unenforced) schema mode instead of failing the turn.
84
84
  *
85
- * Anything it cannot verify — a `$ref`, a composed `allOf` — answers false:
85
+ * Anything it cannot verify (a `$ref`, a composed `allOf`) answers false:
86
86
  * the cost of guessing wrong that way is unenforced output, and the cost of
87
87
  * guessing wrong the other way is a request that cannot succeed at all.
88
88
  */
@@ -97,7 +97,7 @@ export function clampToSchema(value: unknown, node: unknown): unknown {
97
97
  *
98
98
  * Two shapes need it, which is why the wording lives here rather than in one of
99
99
  * them. Anthropic has never had a schema mode. The OpenAI dialect falls back to
100
- * plain JSON mode on every gateway — `json_object` asks for valid JSON and says
100
+ * plain JSON mode on every gateway: `json_object` asks for valid JSON and says
101
101
  * nothing whatsoever about its shape.
102
102
  */
103
103
  export function schemaPrompt(schema: unknown): string {
@@ -134,8 +134,8 @@ export function isStrictSchema(node: unknown): boolean {
134
134
  );
135
135
  }
136
136
 
137
- /** Keywords whose value is a schema, a list of schemas, or a map of them —
138
- * the only places a `pattern` can mean a regex. Everything else (`default`,
137
+ /** Keywords whose value is a schema, a list of schemas, or a map of them.
138
+ * These are the only places a `pattern` can mean a regex. Everything else (`default`,
139
139
  * `examples`, `enum`, property NAMES) is data and is left alone. */
140
140
  const SCHEMA_SLOTS = [
141
141
  "items",
@@ -156,7 +156,7 @@ const SCHEMA_MAPS = ["properties", "patternProperties", "$defs", "definitions",
156
156
  * A tool schema with every `pattern` constraint removed.
157
157
  *
158
158
  * The ChatGPT backend compiles tool schemas with OpenAI's own regex dialect and
159
- * answers a pattern it cannot read with a 400 for the WHOLE request — strict
159
+ * answers a pattern it cannot read with a 400 for the whole request, strict
160
160
  * or not. Claude Code's Artifact tool carries one, and every request that
161
161
  * offered it failed (cc-proxy #141/#142). Losing the constraint costs a
162
162
  * validation the caller's tool runner repeats anyway; the 400 costs the turn.
package/src/tool-args.ts CHANGED
@@ -58,7 +58,7 @@ function unescapeJson(text: string): string {
58
58
  /**
59
59
  * Best-effort recovery of `"key": "value"` string fields from truncated JSON.
60
60
  *
61
- * Only strings: they are what a summary field — the one worth rescuing — is
61
+ * Only strings: they are what a summary field (the one worth rescuing) is
62
62
  * made of, and a closing quote is the single reliable boundary in a partial
63
63
  * stream. Numbers, booleans and objects are dropped, because a salvaged
64
64
  * half-value is worse than none.
@@ -151,7 +151,7 @@ export function parseToolArgs(raw: string): Record<string, unknown> {
151
151
  return healArgs(isRecord(parsed) ? parsed : {});
152
152
  }
153
153
 
154
- /** Whether `raw` parses at all — how a caller tells a truncated tool call from
154
+ /** Whether `raw` parses at all. Lets a caller tell a truncated tool call from
155
155
  * an intact one, since `parseToolArgs` deliberately never throws. */
156
156
  export function isCompleteJson(raw: string): boolean {
157
157
  if (!raw.trim()) return false;
package/src/tools.ts CHANGED
@@ -13,7 +13,7 @@ import type { JsonObjectSchema, ToolDefinition } from "./types.ts";
13
13
  import { messageOf } from "./errors.ts";
14
14
 
15
15
  export interface ToolContext {
16
- /** The model's own call id when there is one — the event, any approval row
16
+ /** The model's own call id when there is one. The event, any approval row
17
17
  * and the tool message must all key on what the provider expects back. */
18
18
  callId?: string;
19
19
  signal?: AbortSignal;
@@ -29,7 +29,7 @@ export type ToolOutcome<O> =
29
29
  ok: false;
30
30
  callId: string;
31
31
  kind: ToolFailure;
32
- /** Fed back to the model verbatim — so it must read as an instruction to
32
+ /** Fed back to the model verbatim, so it must read as an instruction to
33
33
  * a reader who cannot see our stack trace. */
34
34
  error: string;
35
35
  durationMs: number;
@@ -83,7 +83,7 @@ export class ToolTimeoutError extends Error {
83
83
  }
84
84
  }
85
85
 
86
- /** Rejects when `signal` aborts — races a `run` that ignores its own signal. */
86
+ /** Rejects when `signal` aborts. Races a `run` that ignores its own signal. */
87
87
  function abortion(signal: AbortSignal): Promise<never> {
88
88
  return new Promise((_resolve, reject) => {
89
89
  if (signal.aborted) reject(signal.reason);
@@ -220,7 +220,7 @@ export class ToolRegistry {
220
220
  }
221
221
 
222
222
  /**
223
- * Definitions for an allow-list, in the order given — which is the order the
223
+ * Definitions for an allow-list, in the order given. This is the order the
224
224
  * model reads them in, and it is part of the cached prompt prefix. Reordering
225
225
  * or appending mid-conversation invalidates that prefix, so a caller that
226
226
  * cares should freeze the list when the session opens.
package/src/transport.ts CHANGED
@@ -16,7 +16,7 @@ export interface RequestInit_ {
16
16
  signal?: AbortSignal;
17
17
  /** Swapped in tests, or to route through a proxy. Defaults to global fetch. */
18
18
  fetchImpl?: typeof fetch;
19
- /** Called when the response headers arrive and on every body read after —
19
+ /** Called when the response headers arrive and on every body read after,
20
20
  * keep-alives included. What the watchdog's idle clock runs on. */
21
21
  onActivity?: () => void;
22
22
  /** Called when the body states `[DONE]`. See `SseHooks.onDone`. */
@@ -41,8 +41,8 @@ export interface SseHooks {
41
41
 
42
42
  /**
43
43
  * One frame larger than this is not a frame. Without a cap a malformed or
44
- * runaway upstream grows the buffer until the runtime dies — an MV3 worker
45
- * first. cc-proxy caps the ChatGPT backend at the same 8 MiB.
44
+ * runaway upstream grows the buffer until the runtime dies, starting with an
45
+ * MV3 worker. cc-proxy caps the ChatGPT backend at the same 8 MiB.
46
46
  */
47
47
  export const MAX_SSE_FRAME_CHARS = 8 * 1024 * 1024;
48
48
 
@@ -53,7 +53,7 @@ export function apiUrl(baseUrl: string, path: string): string {
53
53
 
54
54
  /**
55
55
  * How long the provider says to wait, read from the RESPONSE rather than from
56
- * a thrown error. Headers are authoritative — `Retry-After` first, then the
56
+ * a thrown error. Headers are authoritative: `Retry-After` first, then the
57
57
  * vendor reset headers that name a subscription window rather than a
58
58
  * per-minute throttle, because "try again in a moment" is a lie for those.
59
59
  */
@@ -168,21 +168,21 @@ function payloadOf(frame: string): string | null {
168
168
  }
169
169
 
170
170
  /**
171
- * Yield each `data:` payload of an SSE body, trimmed — plus the bare JSON
171
+ * Yield each `data:` payload of an SSE body, trimmed, plus the bare JSON
172
172
  * object a vendor appends outside the framing, which is only ever an error
173
173
  * (see `payloadOf`).
174
174
  *
175
175
  * Frames are split on the blank line the spec requires, so a payload containing
176
176
  * a bare newline survives; `[DONE]` is swallowed here rather than in every
177
177
  * adapter, and reported through `hooks.onDone`. CRLF and bare CR are
178
- * normalized — some gateways send them, and a `\r` left on the end of a JSON
178
+ * normalized: some gateways send them, and a `\r` left on the end of a JSON
179
179
  * payload is a parse error nobody enjoys debugging.
180
180
  *
181
181
  * Exported apart from `streamSse` because the envelope above it is the half an
182
182
  * adopting app most often cannot take: an app with its own translated error
183
183
  * copy, its own log levels, or its own auth refresh has to keep building the
184
184
  * request and reading the failure itself. Framing is the half nobody should
185
- * write twice — hand it a `res.body` and keep your own envelope.
185
+ * write twice. Hand it a `res.body` and keep your own envelope.
186
186
  */
187
187
  export async function* parseSseStream(
188
188
  body: ReadableStream<Uint8Array>,
package/src/types.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * `output_config.effort` for current Claude models, where `none` is the lowest
15
15
  * effort on a model that cannot stop thinking; and to thinking budgets for
16
16
  * Claude 4.5 and older and for every other vendor on that wire.
17
- * Support varies per model — an unsupported level comes back as a clean 400.
17
+ * Support varies per model. An unsupported level comes back as a clean 400.
18
18
  *
19
19
  * Ordered least → most; the type derives from the array so the runtime guard
20
20
  * and the union can never drift apart.
@@ -34,7 +34,7 @@ export interface TextPart {
34
34
  text: string;
35
35
  }
36
36
 
37
- /** An image the model looks at (vision) — bytes as base64, never a URL the
37
+ /** An image the model looks at (vision), with bytes as base64, never a URL the
38
38
  * provider would have to fetch on our behalf. */
39
39
  export interface ImagePart {
40
40
  type: "image";
@@ -48,7 +48,7 @@ export type FileMimeType =
48
48
  `application/${string}` | `audio/${string}` | `video/${string}` | `text/${string}`;
49
49
 
50
50
  /**
51
- * A file the model reads — a PDF, a recording, a video — as base64 bytes,
51
+ * A file the model reads (a PDF, a recording, a video) as base64 bytes,
52
52
  * never a URL. Only the Gemini adapter sends one, as `inlineData`, the way it
53
53
  * sends an image. Every other adapter refuses it with an `invalid`
54
54
  * ProviderError before any request goes out, rather than drop it: a dropped
@@ -94,7 +94,7 @@ export type ChatMessage =
94
94
  * OpenAI-shape serializes it as `reasoning_content`; Anthropic-shape
95
95
  * drops it, since its thinking blocks carry signatures we never capture.
96
96
  *
97
- * A turn that DISABLES thinking must not carry it — mixing the two is
97
+ * A turn that disables thinking must not carry it. Mixing the two is
98
98
  * unsupported. `stripReasoning` below is that rule, once.
99
99
  */
100
100
  reasoning?: string;
@@ -102,7 +102,7 @@ export type ChatMessage =
102
102
  * OpenRouter's normalized reasoning payload, arriving on the stream and
103
103
  * riding back UNMODIFIED on the next turn's assistant message.
104
104
  *
105
- * Opaque on purpose — the same contract as Gemini's `thoughtSignature`,
105
+ * Opaque on purpose, with the same contract as Gemini's `thoughtSignature`,
106
106
  * and for the same reason: it is the provider's own record of how it got
107
107
  * here, and reading, reshaping or dropping it costs the model its
108
108
  * continuity across a tool round. Absent on every other dialect.
@@ -133,7 +133,7 @@ export interface ReasoningItems {
133
133
  items: unknown[];
134
134
  }
135
135
 
136
- /** JSON Schema for an object — what every provider's tool contract wants. */
136
+ /** JSON Schema for an object, as every provider's tool contract requires. */
137
137
  export interface JsonObjectSchema {
138
138
  type: "object";
139
139
  properties?: Record<string, unknown>;
@@ -161,19 +161,19 @@ export interface ToolCallDelta {
161
161
 
162
162
  export interface TokenUsage {
163
163
  inputTokens: number;
164
- /** Cache-HIT subset of `inputTokens` — providers auto-cache repeated
164
+ /** Cache-hit subset of `inputTokens`. Providers auto-cache repeated
165
165
  * prefixes and bill the hit portion far cheaper, so it must be tracked
166
166
  * separately to cost a turn correctly. 0 when the provider reports none. */
167
167
  cachedInputTokens: number;
168
- /** Tokens WRITTEN to cache — also a subset of `inputTokens`, so the total
168
+ /** Tokens written to cache, also a subset of `inputTokens`, so the total
169
169
  * stays the whole prompt the window has to hold. Anthropic bills these
170
170
  * above the input rate; the OpenAI-shape auto-cachers bill them at it.
171
171
  * 0 when not reported. */
172
172
  cacheWriteTokens?: number;
173
173
  outputTokens: number;
174
174
  /**
175
- * The part of `outputTokens` spent thinking, when the provider reports it —
176
- * already INSIDE `outputTokens`, so never add it again. It is the number
175
+ * The part of `outputTokens` spent thinking, when the provider reports it.
176
+ * Already inside `outputTokens`, so never add it again. It is the number
177
177
  * that explains a turn that came back empty with a length finish: the
178
178
  * reasoning ate the budget. Anthropic does not report it; absent there.
179
179
  */
@@ -195,7 +195,7 @@ export const EMPTY_USAGE: TokenUsage = {
195
195
  outputTokens: 0,
196
196
  };
197
197
 
198
- /** Normalized streaming chunk — every provider's shape collapses into this. */
198
+ /** Normalized streaming chunk. Every provider's shape maps to this. */
199
199
  export interface ProviderChunk {
200
200
  type: "delta" | "usage" | "finish";
201
201
  /** The selected endpoint and model when a fallback provider emits this chunk.
@@ -203,7 +203,7 @@ export interface ProviderChunk {
203
203
  source?: { provider: string; model: string };
204
204
  content?: string;
205
205
  reasoning?: string;
206
- /** OpenRouter's normalized reasoning payload — hand it back on the next
206
+ /** OpenRouter's normalized reasoning payload. Hand it back on the next
207
207
  * turn's assistant message verbatim. See ChatMessage.reasoningDetails. */
208
208
  reasoningDetails?: unknown[];
209
209
  /** Once per turn, where a provider replays its reasoning. See ChatMessage.reasoningItems. */
@@ -213,22 +213,22 @@ export interface ProviderChunk {
213
213
  finishReason?: FinishReason;
214
214
  }
215
215
 
216
- /** Pin or deny tool use. `{ name }` forces one specific tool — how a run is
216
+ /** Pin or deny tool use. `{ name }` forces one specific tool, so a run is
217
217
  * made to commit an answer at its step budget's edge. */
218
218
  export type ToolChoice = "auto" | "none" | "required" | { name: string };
219
219
 
220
220
  /**
221
221
  * Ask for a JSON object matching `schema`. Providers that enforce schemas get
222
222
  * it verbatim; the rest get JSON mode plus the schema in the prompt. Either
223
- * way the CALLER validates — a provider's "guaranteed" JSON is not one.
223
+ * way the caller validates: a provider's "guaranteed" JSON is not one.
224
224
  */
225
225
  export interface JsonOutput {
226
226
  name: string;
227
227
  schema: JsonObjectSchema;
228
228
  /**
229
229
  * Force OpenAI's strict schema mode on or off. Left unset, the adapters ask
230
- * `isStrictSchema` and enforce whenever the schema actually qualifies —
231
- * which is what keeps an optional field from turning a working call into a
230
+ * `isStrictSchema` and enforce whenever the schema actually qualifies.
231
+ * This keeps an optional field from turning a working call into a
232
232
  * 400. Set it only to overrule that reading.
233
233
  */
234
234
  strict?: boolean;
@@ -339,13 +339,13 @@ function bracketed(text: string): string {
339
339
  /**
340
340
  * How the schema rides on a call that ALSO carries tools.
341
341
  *
342
- * `"response_format"` sends both — what the shapes document, what most models
342
+ * `"response_format"` sends both, as the shapes document and most models
343
343
  * honour. `"prompt"` leaves the format off that call and sends the schema as
344
344
  * prompt instead, which is what the Anthropic shape has always done.
345
345
  *
346
346
  * It is a setting because a model that cannot serve both never says so: its
347
347
  * decoder is pinned to the schema, the tool call has nowhere to go, and it
348
- * writes the announcement instead — "let me look that up" — and stops. Nothing
348
+ * writes the announcement instead ("let me look that up") and stops. Nothing
349
349
  * is logged, because nothing failed. `probeJsonWithTools` answers it in one
350
350
  * call; there is no default that is right for every model.
351
351
  */
@@ -359,9 +359,10 @@ export interface StreamOptions {
359
359
  * task emitting a large artifact must raise it or the tool-call JSON is
360
360
  * silently truncated mid-argument. */
361
361
  maxTokens?: number;
362
+ /** Not sent to Gemini, which fixes its own sampling. Same for `topP`. */
362
363
  temperature?: number;
363
364
  /**
364
- * Nucleus sampling. Set this OR `temperature`, not both — the vendors all
365
+ * Nucleus sampling. Set this or `temperature`, not both. The vendors all
365
366
  * document them as alternatives and some reject the pair outright.
366
367
  */
367
368
  topP?: number;
@@ -378,7 +379,7 @@ export interface StreamOptions {
378
379
  toolChoice?: ToolChoice;
379
380
  json?: JsonOutput;
380
381
  /**
381
- * Overrides the provider's own setting for this call — how `probeJsonWithTools`
382
+ * Overrides the provider's own setting for this call, so `probeJsonWithTools`
382
383
  * asks the same model both ways. See {@link JsonWithTools}.
383
384
  */
384
385
  jsonWithTools?: JsonWithTools;
@@ -397,7 +398,7 @@ export interface StreamOptions {
397
398
  */
398
399
  onActivity?: () => void;
399
400
  /**
400
- * `false` asks for at most one tool call per turn — for tools that must run
401
+ * `false` asks for at most one tool call per turn, for tools that must run
401
402
  * in order, or a backend lane that refuses parallel calls. Absent or `true`
402
403
  * is every vendor's default and sends nothing. Gemini has no such switch,
403
404
  * so it refuses `false` rather than silently ignore it.
@@ -488,7 +489,7 @@ export async function drainStream(
488
489
  *
489
490
  * The chain-of-thought belongs only to thinking turns. A provider that
490
491
  * requires it replayed while thinking is ON (DeepSeek) rejects it when
491
- * thinking is OFF — which is exactly the shape of a forced-submit salvage
492
+ * thinking is off. This is exactly the shape of a forced-submit salvage
492
493
  * turn, where a run reasons through its whole investigation and then drops
493
494
  * thinking to serialize what it already found.
494
495
  *
@@ -518,7 +519,7 @@ export function stripReasoning(messages: readonly ChatMessage[]): ChatMessage[]
518
519
  });
519
520
  }
520
521
 
521
- /** `data:` URI for an image part — what the OpenAI dialect wants inline. */
522
+ /** `data:` URI for an image part, as the OpenAI dialect requires inline. */
522
523
  export function toDataUri(part: ImagePart): string {
523
524
  return `data:${part.mimeType};base64,${part.data}`;
524
525
  }
package/src/usage.ts CHANGED
@@ -15,14 +15,14 @@
15
15
  import type { TokenUsage } from "./types.ts";
16
16
  import { EMPTY_USAGE } from "./types.ts";
17
17
 
18
- /** USD per MILLION tokens — how every vendor prints its price sheet, so a row
18
+ /** USD per million tokens, as every vendor prints its price sheet, so a row
19
19
  * can be checked against one without arithmetic. */
20
20
  export interface ModelRate {
21
21
  /** Cache-MISS input: fresh tokens the provider had to read. */
22
22
  input: number;
23
23
  output: number;
24
24
  /** Cache-HIT input. Providers auto-cache repeated prefixes and bill the hit
25
- * portion far cheaper — 0.1× on Anthropic, ~0.1× on Gemini, 0.5× on some
25
+ * portion far cheaper: 0.1× on Anthropic, ~0.1× on Gemini, 0.5× on some
26
26
  * OpenAI models. In an agent loop the re-sent context is overwhelmingly
27
27
  * hits, so billing it at the miss rate overcounts by up to 10×. */
28
28
  cacheRead: number;
@@ -95,14 +95,14 @@ export function costUsd(usage: TokenUsage, rate: ModelRate): number {
95
95
  }
96
96
 
97
97
  /** What one call adds to a run: its reported cost, else its rate, else
98
- * nothing — an unpriced call still counts its tokens. */
98
+ * nothing. An unpriced call still counts its tokens. */
99
99
  function callCost(usage: TokenUsage, rate: ModelRate | undefined): number {
100
100
  return rate ? costUsd(usage, rate) : (usage.reportedCostUsd ?? 0);
101
101
  }
102
102
 
103
103
  /**
104
104
  * Accumulates usage and cost across the calls of one run, pricing each with
105
- * the rate in effect for the model that served it — so a run that switches to
105
+ * the rate in effect for the model that served it, so a run that switches to
106
106
  * a backup model, or spans a time-of-day price boundary, still bills correctly.
107
107
  * A call that carries a reported cost is billed that, with or without a rate.
108
108
  */
@@ -110,7 +110,7 @@ export class UsageTracker {
110
110
  private usage: TokenUsage = { ...EMPTY_USAGE, cacheWriteTokens: 0 };
111
111
  private cost = 0;
112
112
  /** USD the cache hits saved, versus billing them all as misses.
113
- * Observability only — never billed. */
113
+ * Observability only. Never billed. */
114
114
  private saved = 0;
115
115
 
116
116
  add(usage: TokenUsage, rate?: ModelRate): void {
package/src/watchdog.ts CHANGED
@@ -42,7 +42,7 @@ import type {
42
42
  * stream is dead. */
43
43
  export const STREAM_IDLE_MS = 60_000;
44
44
 
45
- /** No chunk for this long — the wait for the response to start included —
45
+ /** No chunk for this long, including the wait for the response to start,
46
46
  * and the stream is going nowhere, keep-alives or not. */
47
47
  export const STREAM_PROGRESS_MS = 300_000;
48
48
 
@@ -51,17 +51,17 @@ export interface StreamWatch {
51
51
  readonly signal: AbortSignal;
52
52
  /** A chunk arrived: re-arm both deadlines, and mark TTFT if it was the first. */
53
53
  sawByte(): void;
54
- /** The response showed life without a chunk — its headers, a keep-alive, a
54
+ /** The response showed life without a chunk: its headers, a keep-alive, a
55
55
  * partial frame. Starts or re-arms the idle deadline only. */
56
56
  sawActivity(): void;
57
57
  /**
58
- * Milliseconds from the call opening to its first chunk of any kind — the
58
+ * Milliseconds from the call opening to its first chunk of any kind. The
59
59
  * wait a person actually experiences, and the number a prompt-cache pin
60
60
  * exists to shrink. Null until something arrives.
61
61
  */
62
62
  firstChunkMs(): number | null;
63
63
  /**
64
- * Re-issue a provider failure as the timeout when — and only when — it was
64
+ * Re-issue a provider failure as the timeout only when it was
65
65
  * one of our deadlines that aborted. A caller's Stop passes through untouched.
66
66
  */
67
67
  classify(err: unknown): unknown;
@@ -148,7 +148,7 @@ export function streamWatch(opts: StreamWatchOptions = {}): StreamWatch {
148
148
 
149
149
  /**
150
150
  * 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
151
+ * through it. Disposes on any exit: completion, throw, or the consumer
152
152
  * breaking out of the loop.
153
153
  */
154
154
  export async function* watchChunks<T>(
@@ -173,7 +173,7 @@ export async function* watchChunks<T>(
173
173
  * A stream that ends with no text and no tool call is a failure wearing a
174
174
  * success's clothes: `stop_reason: end_turn` with zero content blocks, which
175
175
  * 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
176
+ * `max_tokens`. Nothing throws, so nothing retries. The caller simply shows a
177
177
  * person an empty answer, and the only trace is a bill.
178
178
  *
179
179
  * Reasoning is not an answer. It still streams the moment it arrives, so a
@@ -185,12 +185,12 @@ export async function* watchChunks<T>(
185
185
  *
186
186
  * Classified by what fixes it. A `length` finish is the caller's own cap:
187
187
  * `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
188
+ * cooled down for it. The fix is a larger `maxTokens` or a lower effort. Any
189
189
  * other empty turn is `overload`, because that is both true and useful: it is
190
190
  * theirs and temporary, so it is transient (the same model, retried, usually
191
191
  * answers) and backup-eligible (a model that keeps doing it should be walked
192
192
  * 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.
193
+ * retry rule still holds: retry only while nothing was emitted.
194
194
  */
195
195
  export async function* requireContent<T extends ProviderChunk>(
196
196
  provider: string,
@@ -240,7 +240,7 @@ export async function* requireContent<T extends ProviderChunk>(
240
240
  throw new ProviderError(
241
241
  provider,
242
242
  "invalid",
243
- `${provider}: the output cap ran out before any answer${spent} — raise maxTokens or lower effort`,
243
+ `${provider}: the output cap ran out before any answer${spent}. Raise maxTokens or lower effort.`,
244
244
  );
245
245
  }
246
246
  throw new ProviderError(provider, "overload", `${provider}: completed with no content`);
@@ -267,8 +267,8 @@ export interface WatchdogOptions {
267
267
  * A provider with both silent failures already handled.
268
268
  *
269
269
  * 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
270
+ * `createStream`: build a watch, hand the provider the watch's signal, wrap
271
+ * the chunks. The middle one is the trap. Pass the caller's signal
272
272
  * instead and everything still compiles, still streams, still passes the
273
273
  * tests: the watchdog simply never aborts anything, because the request it was
274
274
  * meant to cancel was never told about it. The failure has no symptom until
@@ -276,8 +276,8 @@ export interface WatchdogOptions {
276
276
  *
277
277
  * So the composition belongs here rather than in a docs snippet each app
278
278
  * 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.
279
+ * `withStreamRetry` and `streamWithBackupModels`. Timeouts and empty turns
280
+ * without a `length` finish are transient, so wrapping it in a retry is safe.
281
281
  */
282
282
  export function withWatchdog(provider: Provider, opts: WatchdogOptions = {}): Provider {
283
283
  return {