@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.
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 +14 -13
  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 +22 -17
  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
@@ -31,7 +31,7 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
31
31
  * /chat/completions when the base already includes the API version, such as
32
32
  * a gateway using /v4 rather than /v1. */
33
33
  path?: string;
34
- /** Names the provider in errors and logs — "openrouter", "deepseek", … It
34
+ /** Names the provider in errors and logs ("openrouter", "deepseek", …). It
35
35
  * also picks the effort dialect below, unless `effortDialect` overrides. */
36
36
  id?: string;
37
37
  effort?: Effort;
@@ -50,7 +50,7 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
50
50
  /**
51
51
  * How a json request rides when the SAME call also carries tools.
52
52
  *
53
- * `response_format` — the default — sends both, which is what the shape
53
+ * `response_format` (the default) sends both, which is what the shape
54
54
  * documents and what most models honour. `prompt` drops the response format
55
55
  * from those calls only and sends the schema as prompt instead: the shape the
56
56
  * Anthropic adapter has always used, and the reason this failure cannot
@@ -58,7 +58,7 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
58
58
  *
59
59
  * It needs a knob because a model that cannot serve both does not say so. Its
60
60
  * decoder is pinned to the schema, the tool call has nowhere to go, and the
61
- * model writes the announcement instead — "let me look that up for you" — and
61
+ * model writes the announcement instead ("let me look that up for you") and
62
62
  * the turn ends looking like a model with no initiative rather than a request
63
63
  * that made the call impossible. Nothing is logged, because nothing failed.
64
64
  *
@@ -70,7 +70,7 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
70
70
  * No default is right for everyone, which is why this stays a setting rather
71
71
  * than becoming a rule: the same run put `qwen3.8-flash` at 1/6 with the
72
72
  * schema in the prompt against 6/6 with the response format. Don't guess it
73
- * from the model card — `probeJsonWithTools` asks the model itself, in one
73
+ * from the model card. `probeJsonWithTools` asks the model itself, in one
74
74
  * call. `StreamOptions.jsonWithTools` overrides this per call.
75
75
  */
76
76
  jsonWithTools?: JsonWithTools;
@@ -78,7 +78,7 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
78
78
  fetchImpl?: typeof fetch;
79
79
  headers?: Record<string, string>;
80
80
  /**
81
- * App attribution for OpenRouter's rankings — the site URL rides as
81
+ * App attribution for OpenRouter's rankings. The site URL rides as
82
82
  * `HTTP-Referer`, the app name as `X-Title`. Public, not secret. Sent
83
83
  * whenever set (other vendors ignore unknown headers); an explicit entry
84
84
  * in `headers` always wins.
@@ -91,7 +91,7 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
91
91
  /**
92
92
  * Pin OpenRouter to preferred upstream hosts so the PROMPT CACHE stays warm
93
93
  * across rounds. The cache lives on the upstream host's account and default
94
- * routing hops between them, and every hop is a cold cache — worse latency
94
+ * routing hops between them. Every hop is a cold cache, with worse latency
95
95
  * and higher effective input cost. Fallbacks stay on: this is a preference,
96
96
  * not a lock.
97
97
  */
@@ -109,6 +109,9 @@ export interface OpenAIConfig extends ProviderFallbackConfig {
109
109
  * `agent`. GitHub Copilot bills a premium request per `user` turn and lets
110
110
  * the agent's own follow-ups (tool results) ride free. */
111
111
  initiatorHeader?: boolean;
112
+ /** Never send `temperature` or `top_p`. For an endpoint that fixes its own
113
+ * sampling and rejects the fields (Gemini's OpenAI-compatible route). */
114
+ noSampling?: boolean;
112
115
  /** Default `service_tier` for every call; `StreamOptions.serviceTier` wins. */
113
116
  serviceTier?: ServiceTier;
114
117
  }
@@ -215,14 +218,14 @@ const OPENROUTER_NONE: ReadonlyMap<string, "low" | "minimal" | "none"> = new Map
215
218
  * MiMo v2.6 Flash takes one, and DeepSeek V4 Flash thinks at `low` and not
216
219
  * with the field omitted. So `none` is said per model, from
217
220
  * `OPENROUTER_NONE`, which also says why an unlisted model gets nothing.
218
- * Naming a level raises it — the accepted values are `low`, `high` and
221
+ * Naming a level raises it. The accepted values are `low`, `high` and
219
222
  * `max` (`medium` and `xhigh` are accepted too, but our vocabulary has no
220
223
  * use for them).
221
224
  * - **DeepSeek V4 defaults thinking ON** on its own API, so `none` has to be an
222
225
  * explicit refusal there. The same word is a different request on each
223
226
  * dialect, and on OpenRouter on each model: the spelling belongs to the
224
227
  * endpoint, never to the caller.
225
- * - **OpenAI** takes `reasoning_effort`, and `none` is one of its values — not
228
+ * - **OpenAI** takes `reasoning_effort`, and `none` is one of its values, not
226
229
  * the absence of the field. GPT-5.1 both accepted `none` and made it the
227
230
  * default; everything from GPT-5 back still defaults to `medium`. So sending
228
231
  * nothing is NOT a way to say "do not think": on every model released before
@@ -286,11 +289,12 @@ export function effortParams(
286
289
  /**
287
290
  * OpenCode Go's chat models each take a different set of `reasoning_effort`
288
291
  * values (cc-proxy's table, measured on live traffic). A level a model can't
289
- * take is refused here, rather than sent as a 400 or quietly changed into a
290
- * level the caller didn't ask for.
292
+ * take is raised to the least it offers (GLM, DeepSeek) or refused (MiMo
293
+ * `max`), rather than sent as a 400. A refusal is `invalid`, which stops the
294
+ * whole fallback chain, so refuse only where there is nothing sensible to send.
291
295
  *
292
296
  * - GLM 5.2 and 5.3 take only `high` and `max`, with no way to turn thinking
293
- * off. `none` gets `high`, the least they offer.
297
+ * off. `none`, `low` and `medium` get `high`, the least they offer.
294
298
  * - DeepSeek V4 takes `low` to `max`. `none` gets `low`, its least.
295
299
  * - MiMo takes `low`, `medium` and `high`, and `none` (measured 2026-09-27 on
296
300
  * `mimo-v2.6-flash`; cc-proxy maps it to `low` instead).
@@ -307,8 +311,7 @@ function goEffortParams(effort: Effort, model = ""): Record<string, unknown> {
307
311
  };
308
312
  const level = (value: string) => ({ reasoning_effort: value });
309
313
  if (/glm-5[-.p]?[23]/.test(id)) {
310
- if (effort === "none" || effort === "high") return level("high");
311
- return effort === "max" ? level("max") : refuse("high or max");
314
+ return level(effort === "max" ? "max" : "high");
312
315
  }
313
316
  if (id.includes("deepseek-v4")) return level(effort === "none" ? "low" : effort);
314
317
  if (id.includes("mimo"))
@@ -351,7 +354,7 @@ function partsToOpenAI(content: string | ContentPart[], provider: string): unkno
351
354
  }
352
355
 
353
356
  /**
354
- * Assistant turns carry `reasoning_content` when the history has it — thinking
357
+ * Assistant turns carry `reasoning_content` when the history has it. Thinking
355
358
  * providers require the prior turn's chain-of-thought replayed on a turn that
356
359
  * made a tool call. A caller running a turn with thinking OFF must strip it
357
360
  * first (`stripReasoning`); the two cannot be mixed.
@@ -454,7 +457,7 @@ interface OpenAIChunk {
454
457
  is_byok?: boolean;
455
458
  cost_details?: { upstream_inference_cost?: unknown } | null;
456
459
  } | null;
457
- /** Present only on the in-band failure below — never beside a choice.
460
+ /** Present only on the in-band failure below. Never beside a choice.
458
461
  * `code` is the numeric HTTP status on the gateways, a slug on OpenAI. */
459
462
  error?: { message?: string; code?: string | number; type?: string };
460
463
  }
@@ -527,8 +530,10 @@ export function createOpenAIProvider(config: OpenAIConfig): Provider {
527
530
  };
528
531
  const maxTokens = opts.maxTokens ?? config.maxTokens;
529
532
  if (maxTokens !== undefined) request.max_tokens = maxTokens;
530
- if (opts.temperature !== undefined) request.temperature = opts.temperature;
531
- if (opts.topP !== undefined) request.top_p = opts.topP;
533
+ if (!config.noSampling) {
534
+ if (opts.temperature !== undefined) request.temperature = opts.temperature;
535
+ if (opts.topP !== undefined) request.top_p = opts.topP;
536
+ }
532
537
  if (opts.stopSequences?.length) request.stop = opts.stopSequences;
533
538
  const serviceTier = opts.serviceTier ?? config.serviceTier;
534
539
  if (serviceTier) request.service_tier = serviceTier;
@@ -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
  }