@providerkit/core 0.3.0 → 0.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -30,6 +30,11 @@ export const EMPTY_USAGE = {
30
30
  export async function drainStream(stream, model) {
31
31
  let text = "";
32
32
  let reasoning = "";
33
+ // Not concatenated: this half of the record is a payload the provider owns,
34
+ // and it arrives whole on one delta rather than in fragments. Dropped here,
35
+ // a drained turn replays only half its own reasoning on the next round —
36
+ // which is the failure `reasoningDetails` exists to prevent.
37
+ let reasoningDetails;
33
38
  let usage = EMPTY_USAGE;
34
39
  let finishReason = null;
35
40
  for await (const chunk of stream) {
@@ -38,6 +43,8 @@ export async function drainStream(stream, model) {
38
43
  text += chunk.content;
39
44
  if (chunk.reasoning)
40
45
  reasoning += chunk.reasoning;
46
+ if (chunk.reasoningDetails?.length)
47
+ reasoningDetails = chunk.reasoningDetails;
41
48
  }
42
49
  else if (chunk.type === "usage" && chunk.usage) {
43
50
  usage = chunk.usage;
@@ -46,7 +53,14 @@ export async function drainStream(stream, model) {
46
53
  finishReason = chunk.finishReason;
47
54
  }
48
55
  }
49
- return { text, reasoning, usage, finishReason, model };
56
+ return {
57
+ text,
58
+ reasoning,
59
+ ...(reasoningDetails ? { reasoningDetails } : {}),
60
+ usage,
61
+ finishReason,
62
+ model,
63
+ };
50
64
  }
51
65
  /**
52
66
  * Prepare a history for a thinking-DISABLED turn: strip `reasoning` from every
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,mEAAmE;AACnE,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,0BAA0B;AAE1B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AAGzE,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AACpD,CAAC;AA4GD,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC,WAAW,EAAE,CAAC;IACd,iBAAiB,EAAE,CAAC;IACpB,YAAY,EAAE,CAAC;CAChB,CAAC;AA6DF;kEACkE;AAClE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAoC,EACpC,KAAa;IAEb,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,IAAI,KAAK,GAAe,WAAW,CAAC;IACpC,IAAI,YAAY,GAAwB,IAAI,CAAC;IAC7C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,IAAI,KAAK,CAAC,OAAO;gBAAE,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC;YACzC,IAAI,KAAK,CAAC,SAAS;gBAAE,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;QACpD,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACjD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACtB,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACzD,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO,OAAO,CAAC;QACjD,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC;QAC9F,yEAAyE;QACzE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,QAAQ,IAAI,CAAC,QAAQ,WAAW,IAAI,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,mEAAmE;AACnE,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,0BAA0B;AAE1B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AAGzE,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;AACpD,CAAC;AA4GD,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC,WAAW,EAAE,CAAC;IACd,iBAAiB,EAAE,CAAC;IACpB,YAAY,EAAE,CAAC;CAChB,CAAC;AAoFF;kEACkE;AAClE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAoC,EACpC,KAAa;IAEb,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,4EAA4E;IAC5E,4EAA4E;IAC5E,yEAAyE;IACzE,6DAA6D;IAC7D,IAAI,gBAAuC,CAAC;IAC5C,IAAI,KAAK,GAAe,WAAW,CAAC;IACpC,IAAI,YAAY,GAAwB,IAAI,CAAC;IAC7C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,IAAI,KAAK,CAAC,OAAO;gBAAE,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC;YACzC,IAAI,KAAK,CAAC,SAAS;gBAAE,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;YAClD,IAAI,KAAK,CAAC,gBAAgB,EAAE,MAAM;gBAAE,gBAAgB,GAAG,KAAK,CAAC,gBAAgB,CAAC;QAChF,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YACjD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACtB,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACzD,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI;QACJ,SAAS;QACT,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,KAAK;QACL,YAAY;QACZ,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO,OAAO,CAAC;QACjD,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC;QAC9F,yEAAyE;QACzE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;QAC1E,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,QAAQ,IAAI,CAAC,QAAQ,WAAW,IAAI,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC"}
@@ -1,3 +1,4 @@
1
+ import type { Provider, ProviderChunk } from "./types.ts";
1
2
  /** No byte at all for this long and the stream is considered wedged. */
2
3
  export declare const STREAM_IDLE_MS = 60000;
3
4
  export interface StreamWatch {
@@ -31,4 +32,49 @@ export declare function streamWatch(opts?: StreamWatchOptions): StreamWatch;
31
32
  * breaking out of the loop.
32
33
  */
33
34
  export declare function watchChunks<T>(watch: StreamWatch, chunks: AsyncIterable<T>): AsyncGenerator<T>;
35
+ /**
36
+ * Reject a turn that completed but produced nothing usable.
37
+ *
38
+ * A stream that ends with no text, no reasoning and no tool call is a failure
39
+ * wearing a success's clothes: `stop_reason: end_turn` with zero content
40
+ * blocks, which the vendors emit under load and after a thinking block eats
41
+ * the whole `max_tokens`. Nothing throws, so nothing retries — the caller
42
+ * simply shows a person an empty answer, and the only trace is a bill.
43
+ *
44
+ * Classified `overload` because that is both true and useful: it is theirs and
45
+ * temporary, so it is transient (the same model, retried, usually answers) and
46
+ * backup-eligible (a model that keeps doing it should be walked away from).
47
+ * The throw lands before any chunk is yielded downstream, so the retry rule
48
+ * that matters — retry only while nothing was emitted — still holds.
49
+ */
50
+ export declare function requireContent<T extends ProviderChunk>(provider: string, chunks: AsyncIterable<T>): AsyncGenerator<T>;
51
+ export interface WatchdogOptions {
52
+ /** Silence this long and the stream is wedged. Defaults to `STREAM_IDLE_MS`. */
53
+ idleMs?: number;
54
+ /**
55
+ * Reject a turn that ends having said nothing, as `requireContent` does. On
56
+ * by default: an empty completion is a failure in every loop, and the one
57
+ * shaped like a success is the one nobody catches.
58
+ */
59
+ requireContent?: boolean;
60
+ /** Time to first byte for this call, reported once the byte arrives. */
61
+ onFirstChunk?: (ms: number) => void;
62
+ }
63
+ /**
64
+ * A provider with both silent failures already handled.
65
+ *
66
+ * Every consumer of this package wrote the same three lines around every
67
+ * `createStream` — build a watch, hand the provider the WATCH's signal, wrap
68
+ * the chunks — and the middle one is the trap. Pass the caller's signal
69
+ * instead and everything still compiles, still streams, still passes the
70
+ * tests: the watchdog simply never aborts anything, because the request it was
71
+ * meant to cancel was never told about it. The failure has no symptom until
72
+ * production, where it is the exact hang the watchdog was added to end.
73
+ *
74
+ * So the composition belongs here rather than in a docs snippet each app
75
+ * copies. The result is still a `Provider`, so it composes unchanged with
76
+ * `withStreamRetry` and `streamWithBackupModels` — and both of the failures it
77
+ * catches are transient, which is what makes wrapping it in a retry correct.
78
+ */
79
+ export declare function withWatchdog(provider: Provider, opts?: WatchdogOptions): Provider;
34
80
  //# sourceMappingURL=watchdog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"watchdog.d.ts","sourceRoot":"","sources":["../src/watchdog.ts"],"names":[],"mappings":"AAgBA,wEAAwE;AACxE,eAAO,MAAM,cAAc,QAAS,CAAC;AAErC,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,8EAA8E;IAC9E,OAAO,IAAI,IAAI,CAAC;IAChB;;;;OAIG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,6DAA6D;IAC7D,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAoDtE;AAED;;;;GAIG;AACH,wBAAuB,WAAW,CAAC,CAAC,EAClC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GACvB,cAAc,CAAC,CAAC,CAAC,CAWnB"}
1
+ {"version":3,"file":"watchdog.d.ts","sourceRoot":"","sources":["../src/watchdog.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAEV,QAAQ,EACR,aAAa,EAGd,MAAM,YAAY,CAAC;AAEpB,wEAAwE;AACxE,eAAO,MAAM,cAAc,QAAS,CAAC;AAErC,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,8EAA8E;IAC9E,OAAO,IAAI,IAAI,CAAC;IAChB;;;;OAIG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,6DAA6D;IAC7D,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAsDtE;AAED;;;;GAIG;AACH,wBAAuB,WAAW,CAAC,CAAC,EAClC,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GACvB,cAAc,CAAC,CAAC,CAAC,CAWnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAuB,cAAc,CAAC,CAAC,SAAS,aAAa,EAC3D,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GACvB,cAAc,CAAC,CAAC,CAAC,CAwBnB;AAED,MAAM,WAAW,eAAe;IAC9B,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,GAAE,eAAoB,GAAG,QAAQ,CAoCrF"}
package/dist/watchdog.js CHANGED
@@ -1,3 +1,6 @@
1
+ // The two ways a stream fails without failing: it goes silent, or it ends
2
+ // having said nothing at all.
3
+ //
1
4
  // The stream-idle watchdog.
2
5
  //
3
6
  // A provider that stops sending bytes is indistinguishable from a long prefill
@@ -26,7 +29,9 @@ export function streamWatch(opts = {}) {
26
29
  let disposed = false;
27
30
  // The bridge is structural rather than an event listener: AbortSignal.any
28
31
  // aborts synchronously when an input is ALREADY aborted, which is the race
29
- // no listener can catch (the event fired before we subscribed).
32
+ // no listener can catch (the event fired before we subscribed). It is also
33
+ // the package's runtime floor — see `engines` — rather than a polyfilled
34
+ // nicety: every runtime this package targets has had it for years.
30
35
  const signal = callerSignal ? AbortSignal.any([callerSignal, timeout.signal]) : timeout.signal;
31
36
  const idleError = (cause) => new ProviderError(provider, "timeout", `stream went ${idleMs / 1000}s without a byte`, {
32
37
  cause,
@@ -82,4 +87,90 @@ export async function* watchChunks(watch, chunks) {
82
87
  watch.dispose();
83
88
  }
84
89
  }
90
+ /**
91
+ * Reject a turn that completed but produced nothing usable.
92
+ *
93
+ * A stream that ends with no text, no reasoning and no tool call is a failure
94
+ * wearing a success's clothes: `stop_reason: end_turn` with zero content
95
+ * blocks, which the vendors emit under load and after a thinking block eats
96
+ * the whole `max_tokens`. Nothing throws, so nothing retries — the caller
97
+ * simply shows a person an empty answer, and the only trace is a bill.
98
+ *
99
+ * Classified `overload` because that is both true and useful: it is theirs and
100
+ * temporary, so it is transient (the same model, retried, usually answers) and
101
+ * backup-eligible (a model that keeps doing it should be walked away from).
102
+ * The throw lands before any chunk is yielded downstream, so the retry rule
103
+ * that matters — retry only while nothing was emitted — still holds.
104
+ */
105
+ export async function* requireContent(provider, chunks) {
106
+ const held = [];
107
+ let usable = false;
108
+ for await (const chunk of chunks) {
109
+ if (!usable) {
110
+ usable = Boolean(chunk.content || chunk.reasoning || chunk.toolCalls?.length);
111
+ // Held rather than forwarded: once a chunk is out, the stream is
112
+ // committed and the retry this guard exists to trigger can no longer
113
+ // fire. Nothing content-bearing has arrived yet, so there is nothing to
114
+ // hold back but the empty frames.
115
+ if (!usable) {
116
+ held.push(chunk);
117
+ continue;
118
+ }
119
+ yield* held;
120
+ held.length = 0;
121
+ }
122
+ yield chunk;
123
+ }
124
+ if (!usable) {
125
+ throw new ProviderError(provider, "overload", `${provider}: completed with no content`);
126
+ }
127
+ }
128
+ /**
129
+ * A provider with both silent failures already handled.
130
+ *
131
+ * Every consumer of this package wrote the same three lines around every
132
+ * `createStream` — build a watch, hand the provider the WATCH's signal, wrap
133
+ * the chunks — and the middle one is the trap. Pass the caller's signal
134
+ * instead and everything still compiles, still streams, still passes the
135
+ * tests: the watchdog simply never aborts anything, because the request it was
136
+ * meant to cancel was never told about it. The failure has no symptom until
137
+ * production, where it is the exact hang the watchdog was added to end.
138
+ *
139
+ * So the composition belongs here rather than in a docs snippet each app
140
+ * copies. The result is still a `Provider`, so it composes unchanged with
141
+ * `withStreamRetry` and `streamWithBackupModels` — and both of the failures it
142
+ * catches are transient, which is what makes wrapping it in a retry correct.
143
+ */
144
+ export function withWatchdog(provider, opts = {}) {
145
+ return {
146
+ ...provider,
147
+ createStream(messages, tools, streamOpts = {}) {
148
+ // Armed on first read, not here: a stream built now and iterated later
149
+ // must not spend its deadline sitting in a variable.
150
+ async function* watched() {
151
+ // idleMs and signal both default inside streamWatch.
152
+ const watch = streamWatch({
153
+ provider: provider.id,
154
+ idleMs: opts.idleMs,
155
+ signal: streamOpts.signal,
156
+ });
157
+ const source = provider.createStream(messages, tools, {
158
+ ...streamOpts,
159
+ signal: watch.signal,
160
+ });
161
+ let reported = false;
162
+ for await (const chunk of watchChunks(watch, source)) {
163
+ // Before `requireContent` holds anything back — TTFT is the first
164
+ // byte of any kind, not the first byte worth showing.
165
+ if (!reported) {
166
+ reported = true;
167
+ opts.onFirstChunk?.(watch.firstChunkMs() ?? 0);
168
+ }
169
+ yield chunk;
170
+ }
171
+ }
172
+ return opts.requireContent === false ? watched() : requireContent(provider.id, watched());
173
+ },
174
+ };
175
+ }
85
176
  //# sourceMappingURL=watchdog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"watchdog.js","sourceRoot":"","sources":["../src/watchdog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"watchdog.js","sourceRoot":"","sources":["../src/watchdog.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@providerkit/core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "The layer under your agent loop: one seam for every LLM provider, plus the failure handling you only learn in production.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,6 +23,9 @@
23
23
  "LICENSE"
24
24
  ],
25
25
  "sideEffects": false,
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
26
29
  "publishConfig": {
27
30
  "access": "public"
28
31
  },
package/src/errors.ts CHANGED
@@ -325,7 +325,10 @@ const RATE_PATTERNS: readonly RegExp[] = [
325
325
  const OVERLOAD_PATTERNS: readonly RegExp[] = [
326
326
  /overloaded|overloaded_error/i,
327
327
  /\bunavailable\b|UNAVAILABLE/,
328
- /internal error|INTERNAL/,
328
+ /internal error/i,
329
+ // Google's status enum, which is upper-case by contract — kept exact so it
330
+ // does not swallow the word in ordinary prose.
331
+ /\bINTERNAL\b/,
329
332
  /\bcapacity\b/i,
330
333
  /"code"\s*:\s*5\d\d/,
331
334
  ];
@@ -356,6 +359,15 @@ export function classifyHttp(status: number | undefined, body: string): ErrorKin
356
359
  }
357
360
 
358
361
  export function classify(err: unknown, status?: number, body?: string): ErrorKind {
362
+ // An error this package already classified knows its own kind, and nothing is
363
+ // learned by deriving it a second time from a status and a body it never had.
364
+ //
365
+ // This is not a shortcut, it is a correctness fix. The watchdog's own idle
366
+ // timeout is a ProviderError carrying kind "timeout" and no HTTP status, so
367
+ // re-deriving it landed on "unknown" — not transient, therefore never
368
+ // retried, which is the exact opposite of the reason the watchdog exists
369
+ // (invariant 2). A wedged stream aborted at 60s and then failed for good.
370
+ if (err instanceof ProviderError) return err.kind;
359
371
  if (isAbort(err)) return "aborted";
360
372
  if (isTransportFailure(err)) return "network";
361
373
 
@@ -3,6 +3,7 @@ import { streamError } from "../errors.ts";
3
3
  import { parseToolArgs } from "../tool-args.ts";
4
4
  import { streamSse, apiUrl } from "../transport.ts";
5
5
  import type {
6
+ JsonOutput,
6
7
  ChatMessage,
7
8
  ContentPart,
8
9
  Effort,
@@ -104,6 +105,28 @@ function systemBlocks(text: string): unknown[] | undefined {
104
105
  return text ? [{ type: "text", text, cache_control: { type: "ephemeral" } }] : undefined;
105
106
  }
106
107
 
108
+ /**
109
+ * The schema, as an extra system block — Anthropic has no native schema mode,
110
+ * and the seam promises that a provider without one gets the schema in the
111
+ * prompt instead. Without this an `opts.json` request went out carrying
112
+ * nothing at all: the model answered in prose, the caller's `JSON.parse` threw,
113
+ * and the turn failed on the happy path where no retry looks.
114
+ *
115
+ * It rides AFTER the cached block, and that order is load-bearing. A cache
116
+ * breakpoint caches everything before it, so folding a per-call schema into the
117
+ * cached block would change the cached prefix on every turn whose schema
118
+ * differs and throw the whole system prompt's cache away — paying for the
119
+ * schema with the most expensive thing in an agent loop.
120
+ */
121
+ function jsonBlock(json: JsonOutput): unknown {
122
+ return {
123
+ type: "text",
124
+ text:
125
+ "Respond with a single JSON object matching this schema. No prose, no code fence:\n" +
126
+ JSON.stringify(json.schema),
127
+ };
128
+ }
129
+
107
130
  export function toAnthropicMessages(messages: readonly ChatMessage[]): {
108
131
  system?: unknown[];
109
132
  messages: unknown[];
@@ -209,8 +232,11 @@ export function createAnthropicProvider(config: AnthropicConfig): Provider {
209
232
  messages: body,
210
233
  stream: true,
211
234
  };
212
- if (system) request.system = system;
235
+ const systemBody = opts.json ? [...(system ?? []), jsonBlock(opts.json)] : system;
236
+ if (systemBody?.length) request.system = systemBody;
213
237
  if (opts.temperature !== undefined) request.temperature = opts.temperature;
238
+ if (opts.topP !== undefined) request.top_p = opts.topP;
239
+ if (opts.stopSequences?.length) request.stop_sequences = opts.stopSequences;
214
240
  if (tools.length > 0) {
215
241
  request.tools = tools.map((tool) => ({
216
242
  name: tool.name,
@@ -231,6 +257,7 @@ export function createAnthropicProvider(config: AnthropicConfig): Provider {
231
257
  request.thinking = { type: "enabled", budget_tokens: budget };
232
258
  // Thinking and sampling are mutually exclusive on this shape.
233
259
  delete request.temperature;
260
+ delete request.top_p;
234
261
  }
235
262
 
236
263
  // Anthropic reports cache reads and writes as fields of their OWN,
@@ -252,6 +252,8 @@ export function createGeminiProvider(config: GeminiConfig): Provider {
252
252
  const generationConfig: Record<string, unknown> = {};
253
253
  if (maxTokens !== undefined) generationConfig.maxOutputTokens = maxTokens;
254
254
  if (opts.temperature !== undefined) generationConfig.temperature = opts.temperature;
255
+ if (opts.topP !== undefined) generationConfig.topP = opts.topP;
256
+ if (opts.stopSequences?.length) generationConfig.stopSequences = opts.stopSequences;
255
257
  // No effort means the model's own dynamic thinking. Sending MINIMAL here
256
258
  // would switch that off for a caller who never asked, which is the whole
257
259
  // reason the seam treats an absent effort as "never sent".
@@ -16,6 +16,7 @@ import type {
16
16
  ToolDefinition,
17
17
  } from "../types.ts";
18
18
  import { toDataUri } from "../types.ts";
19
+ import { isStrictSchema } from "../schema.ts";
19
20
 
20
21
  export interface OpenAIConfig {
21
22
  apiKey: string;
@@ -256,6 +257,8 @@ export function createOpenAIProvider(config: OpenAIConfig): Provider {
256
257
  const maxTokens = opts.maxTokens ?? config.maxTokens;
257
258
  if (maxTokens !== undefined) request.max_tokens = maxTokens;
258
259
  if (opts.temperature !== undefined) request.temperature = opts.temperature;
260
+ if (opts.topP !== undefined) request.top_p = opts.topP;
261
+ if (opts.stopSequences?.length) request.stop = opts.stopSequences;
259
262
  Object.assign(request, effortParams(config.effortDialect ?? dialectFor(id), effort));
260
263
  if (tools.length > 0) {
261
264
  request.tools = tools.map((tool) => ({
@@ -278,7 +281,11 @@ export function createOpenAIProvider(config: OpenAIConfig): Provider {
278
281
  (config.jsonMode ?? (id === "openai" ? "schema" : "object")) === "schema"
279
282
  ? {
280
283
  type: "json_schema",
281
- json_schema: { name: opts.json.name, schema: opts.json.schema, strict: true },
284
+ json_schema: {
285
+ name: opts.json.name,
286
+ schema: opts.json.schema,
287
+ strict: opts.json.strict ?? isStrictSchema(opts.json.schema),
288
+ },
282
289
  }
283
290
  : // Everything else gets plain JSON mode. Schema ENFORCEMENT is
284
291
  // OpenAI's; the gateways and the vendors behind them offer JSON
@@ -24,6 +24,7 @@ import type {
24
24
  ToolDefinition,
25
25
  } from "../types.ts";
26
26
  import { toDataUri } from "../types.ts";
27
+ import { isStrictSchema } from "../schema.ts";
27
28
 
28
29
  export interface ResponsesConfig {
29
30
  apiKey: string;
@@ -250,6 +251,9 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
250
251
  const maxTokens = opts.maxTokens ?? config.maxTokens;
251
252
  if (maxTokens !== undefined) request.max_output_tokens = maxTokens;
252
253
  if (opts.temperature !== undefined) request.temperature = opts.temperature;
254
+ if (opts.topP !== undefined) request.top_p = opts.topP;
255
+ // No stop sequences on this shape — it has no equivalent field, and
256
+ // inventing one would 400 the request rather than shorten the answer.
253
257
  if (effort && effort !== "none") {
254
258
  // `summary` is what switches the reasoning stream ON. Without it this
255
259
  // shape emits no reasoning_summary_text events at all, and a caller
@@ -279,7 +283,7 @@ export function createResponsesProvider(config: ResponsesConfig): Provider {
279
283
  type: "json_schema",
280
284
  name: opts.json.name,
281
285
  schema: opts.json.schema,
282
- strict: true,
286
+ strict: opts.json.strict ?? isStrictSchema(opts.json.schema),
283
287
  },
284
288
  };
285
289
  }
package/src/schema.ts CHANGED
@@ -65,3 +65,44 @@ export function clampToSchema(value: unknown, node: unknown): unknown {
65
65
 
66
66
  return value;
67
67
  }
68
+
69
+ /**
70
+ * Whether OpenAI's `strict` schema mode will accept this schema.
71
+ *
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
74
+ * object lists must ALSO be required, and every object must close itself with
75
+ * `additionalProperties: false`, all the way down. A schema with one optional
76
+ * field is not "mostly strict"; it is a flat 400 naming a nested path rather
77
+ * than the rule it broke.
78
+ *
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
82
+ * lecture about a mode they never asked for. So the OpenAI-shape adapters ask
83
+ * this and drop to plain (unenforced) schema mode instead of failing the turn.
84
+ *
85
+ * Anything it cannot verify — a `$ref`, a composed `allOf` — answers false:
86
+ * the cost of guessing wrong that way is unenforced output, and the cost of
87
+ * guessing wrong the other way is a request that cannot succeed at all.
88
+ */
89
+ export function isStrictSchema(node: unknown): boolean {
90
+ if (!node || typeof node !== "object") return false;
91
+ const schema = node as SchemaNode;
92
+
93
+ if (schema.$ref !== undefined || schema.allOf !== undefined) return false;
94
+
95
+ const union = schema.anyOf ?? schema.oneOf;
96
+ if (Array.isArray(union)) return union.every(isStrictSchema);
97
+
98
+ // A leaf (string, number, boolean, enum) carries no strict obligations.
99
+ if (schema.type === "array") return schema.items === undefined || isStrictSchema(schema.items);
100
+ if (schema.properties === undefined) return true;
101
+
102
+ if (schema.additionalProperties !== false) return false;
103
+ const properties = schema.properties as Record<string, unknown>;
104
+ const required = new Set(Array.isArray(schema.required) ? (schema.required as string[]) : []);
105
+ return Object.entries(properties).every(
106
+ ([key, value]) => required.has(key) && isStrictSchema(value),
107
+ );
108
+ }
package/src/types.ts CHANGED
@@ -161,6 +161,13 @@ export type ToolChoice = "auto" | "none" | "required" | { name: string };
161
161
  export interface JsonOutput {
162
162
  name: string;
163
163
  schema: JsonObjectSchema;
164
+ /**
165
+ * Force OpenAI's strict schema mode on or off. Left unset, the adapters ask
166
+ * `isStrictSchema` and enforce whenever the schema actually qualifies —
167
+ * which is what keeps an optional field from turning a working call into a
168
+ * 400. Set it only to overrule that reading.
169
+ */
170
+ strict?: boolean;
164
171
  }
165
172
 
166
173
  export interface StreamOptions {
@@ -172,6 +179,20 @@ export interface StreamOptions {
172
179
  * silently truncated mid-argument. */
173
180
  maxTokens?: number;
174
181
  temperature?: number;
182
+ /**
183
+ * Nucleus sampling. Set this OR `temperature`, not both — the vendors all
184
+ * document them as alternatives and some reject the pair outright.
185
+ */
186
+ topP?: number;
187
+ /**
188
+ * Strings that end the turn when generated. The only four-shape sampling
189
+ * field beyond these two; `top_k`, `metadata` and the rest are one vendor's
190
+ * each and stay off the seam, where a caller reaching for them is asking for
191
+ * that vendor rather than for a provider.
192
+ *
193
+ * Not sent on the Responses shape, which has no equivalent.
194
+ */
195
+ stopSequences?: string[];
175
196
  signal?: AbortSignal;
176
197
  toolChoice?: ToolChoice;
177
198
  json?: JsonOutput;
@@ -190,6 +211,8 @@ export interface Provider {
190
211
  export interface Completion {
191
212
  text: string;
192
213
  reasoning: string;
214
+ /** Present only where the provider sent one. See ChatMessage.reasoningDetails. */
215
+ reasoningDetails?: unknown[];
193
216
  usage: TokenUsage;
194
217
  finishReason: FinishReason | null;
195
218
  model: string;
@@ -203,19 +226,32 @@ export async function drainStream(
203
226
  ): Promise<Completion> {
204
227
  let text = "";
205
228
  let reasoning = "";
229
+ // Not concatenated: this half of the record is a payload the provider owns,
230
+ // and it arrives whole on one delta rather than in fragments. Dropped here,
231
+ // a drained turn replays only half its own reasoning on the next round —
232
+ // which is the failure `reasoningDetails` exists to prevent.
233
+ let reasoningDetails: unknown[] | undefined;
206
234
  let usage: TokenUsage = EMPTY_USAGE;
207
235
  let finishReason: FinishReason | null = null;
208
236
  for await (const chunk of stream) {
209
237
  if (chunk.type === "delta") {
210
238
  if (chunk.content) text += chunk.content;
211
239
  if (chunk.reasoning) reasoning += chunk.reasoning;
240
+ if (chunk.reasoningDetails?.length) reasoningDetails = chunk.reasoningDetails;
212
241
  } else if (chunk.type === "usage" && chunk.usage) {
213
242
  usage = chunk.usage;
214
243
  } else if (chunk.type === "finish" && chunk.finishReason) {
215
244
  finishReason = chunk.finishReason;
216
245
  }
217
246
  }
218
- return { text, reasoning, usage, finishReason, model };
247
+ return {
248
+ text,
249
+ reasoning,
250
+ ...(reasoningDetails ? { reasoningDetails } : {}),
251
+ usage,
252
+ finishReason,
253
+ model,
254
+ };
219
255
  }
220
256
 
221
257
  /**