framework-agents 0.1.5 → 0.1.6

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.
@@ -100,6 +100,16 @@ var ToolRejection = class extends Error {
100
100
  this.feedback = feedback;
101
101
  }
102
102
  };
103
+ var RETRYABLE = /* @__PURE__ */ Symbol.for("framework.retryable");
104
+ function stampRetryable(err, retryable) {
105
+ err[RETRYABLE] = retryable;
106
+ return err;
107
+ }
108
+ function getRetryable(err) {
109
+ if (err instanceof AgentError) return err.retryable;
110
+ if (err instanceof Error) return err[RETRYABLE];
111
+ return void 0;
112
+ }
103
113
  var HitlInterrupt = class extends Error {
104
114
  interruptId;
105
115
  tool;
@@ -461,6 +471,27 @@ async function runWithBudget(fn, budget = {}) {
461
471
  }
462
472
 
463
473
  // src/agent/index.ts
474
+ var EventQueue = class {
475
+ items = [];
476
+ wakes = [];
477
+ closed = false;
478
+ push(item) {
479
+ this.items.push(item);
480
+ this.wakes.shift()?.();
481
+ }
482
+ close() {
483
+ this.closed = true;
484
+ for (const w of this.wakes.splice(0)) w();
485
+ }
486
+ async next() {
487
+ for (; ; ) {
488
+ const item = this.items.shift();
489
+ if (item !== void 0) return { done: false, value: item };
490
+ if (this.closed) return { done: true };
491
+ await new Promise((resolve2) => this.wakes.push(resolve2));
492
+ }
493
+ }
494
+ };
464
495
  var DEFAULT_SYSTEM = "You are a helpful agent. Use tools when needed. Be concise.";
465
496
  var DEFAULT_MAX_STEPS = 6;
466
497
  var toolJsonSchemas = /* @__PURE__ */ new WeakMap();
@@ -798,27 +829,82 @@ var Agent = class _Agent {
798
829
  ...strategy === "provider" && format ? { responseFormat: { name: responseFormatName(format), schema: responseFormatJsonSchema(format) } } : {}
799
830
  };
800
831
  for (const mw of middleware) req = await mw.beforeModel?.(req, ctxFor(step)) ?? req;
801
- const pending = [];
802
- const callModel = (r) => model.complete({
803
- ...r,
804
- // Propagate the run's abort into the in-flight provider request.
805
- ...signal ? { signal } : {},
806
- onToken: (d) => {
807
- events?.onToken?.(d);
808
- pending.push({ type: "token", delta: d });
809
- },
810
- onThinking: (d) => {
811
- events?.onThinking?.(d);
812
- pending.push({ type: "thinking", delta: d });
832
+ const needsBatching = middleware.some((mw) => mw.wrapModel || mw.afterModel);
833
+ const buffered = [];
834
+ const channel = new EventQueue();
835
+ const emitDelta = (type, d) => {
836
+ const event = { type, delta: d };
837
+ if (needsBatching) buffered.push(event);
838
+ else channel.push(event);
839
+ };
840
+ const callModel = (r) => {
841
+ const start = buffered.length;
842
+ let failed = false;
843
+ try {
844
+ return model.complete({
845
+ ...r,
846
+ // Propagate the run's abort into the in-flight provider request.
847
+ ...signal ? { signal } : {},
848
+ onToken: (d) => {
849
+ events?.onToken?.(d);
850
+ if (failed) return;
851
+ emitDelta("token", d);
852
+ },
853
+ onThinking: (d) => {
854
+ events?.onThinking?.(d);
855
+ if (failed) return;
856
+ emitDelta("thinking", d);
857
+ }
858
+ }).catch((err) => {
859
+ failed = true;
860
+ buffered.splice(start);
861
+ throw err;
862
+ });
863
+ } catch (err) {
864
+ failed = true;
865
+ buffered.splice(start);
866
+ throw err;
813
867
  }
814
- });
868
+ };
815
869
  const modelChain = middleware.filter((mw) => mw.wrapModel).reduceRight(
816
870
  (next, mw) => (r) => mw.wrapModel(r, next, ctxFor(step)),
817
871
  callModel
818
872
  );
819
- let completion = await modelChain(req);
820
- for (const e of pending.splice(0)) {
821
- if (e.delta !== "") yield e;
873
+ let deltasEmittedThisCall = false;
874
+ let completion;
875
+ const pendingCompletion = modelChain(req).then(
876
+ (c) => {
877
+ channel.close();
878
+ return c;
879
+ },
880
+ (e) => {
881
+ channel.close();
882
+ throw e;
883
+ }
884
+ );
885
+ pendingCompletion.catch(() => {
886
+ });
887
+ try {
888
+ if (needsBatching) {
889
+ completion = await pendingCompletion;
890
+ for (const e of buffered.splice(0)) {
891
+ if (e.delta !== "") yield e;
892
+ }
893
+ } else {
894
+ for (; ; ) {
895
+ const next = await channel.next();
896
+ if (next.done) break;
897
+ deltasEmittedThisCall = true;
898
+ if (next.value.delta !== "") yield next.value;
899
+ }
900
+ completion = await pendingCompletion;
901
+ }
902
+ } catch (err) {
903
+ buffered.splice(0);
904
+ if (deltasEmittedThisCall && getRetryable(err) === void 0) {
905
+ stampRetryable(err, false);
906
+ }
907
+ throw err;
822
908
  }
823
909
  for (const mw of [...middleware].reverse()) completion = await mw.afterModel?.(completion, req, ctxFor(step)) ?? completion;
824
910
  usage.inputTokens += completion.usage.inputTokens;
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  imagesOf,
10
10
  todoListMiddleware,
11
11
  wireTextOf
12
- } from "./chunk-G3CMR4MD.js";
12
+ } from "./chunk-JUOOPH4Z.js";
13
13
 
14
14
  // src/serve/cli.ts
15
15
  import { resolve as resolve3 } from "path";
@@ -184,8 +184,8 @@ var OpenAICompatibleProvider = class {
184
184
  }
185
185
  } : {},
186
186
  messages: options.messages.map(toWireMessage),
187
- ...options.tools.length ? {
188
- tools: options.tools.map((t) => ({
187
+ ...options.tools?.length ? {
188
+ tools: (options.tools ?? []).map((t) => ({
189
189
  type: "function",
190
190
  function: {
191
191
  name: t.name,
@@ -252,91 +252,107 @@ var OpenAICompatibleProvider = class {
252
252
  /** SSE: `data: {...}` lines; delta.content -> onToken, delta.reasoning_content -> onThinking. */
253
253
  async readStream(res, options) {
254
254
  if (!res.body) throw new Error("Streaming response has no body");
255
- const reader = res.body.getReader();
256
- const decoder = new TextDecoder();
257
- let text = "";
258
- let thinking = "";
259
- let inputTokens = 0;
260
- let outputTokens = 0;
261
- let reasoningTokens;
262
- let finishReason;
263
- const toolArgBuffers = /* @__PURE__ */ new Map();
264
- let buffer = "";
265
- const flush = (chunk) => {
266
- buffer += chunk;
267
- const lines = buffer.split("\n");
268
- buffer = lines.pop() ?? "";
269
- for (const line of lines) {
270
- const t = line.trim();
271
- if (!t.startsWith("data:")) continue;
272
- const payload = t.slice(5).trim();
273
- if (payload === "[DONE]") continue;
274
- let evt;
275
- try {
276
- evt = JSON.parse(payload);
277
- } catch {
278
- continue;
279
- }
280
- if (evt.choices?.[0]?.finish_reason) finishReason = evt.choices[0].finish_reason ?? void 0;
281
- if (evt.usage) {
282
- if (evt.usage.prompt_tokens != null) inputTokens = evt.usage.prompt_tokens;
283
- if (evt.usage.completion_tokens != null) outputTokens = evt.usage.completion_tokens;
284
- const rt = reasoningOf(evt.usage);
285
- if (rt != null) reasoningTokens = rt;
286
- continue;
287
- }
288
- const delta = evt.choices?.[0]?.delta;
289
- if (!delta) continue;
290
- const think = thinkingOf(delta);
291
- if (think) {
292
- thinking += think;
293
- options.onThinking?.(think);
294
- }
295
- if (delta.content) {
296
- text += delta.content;
297
- options.onToken?.(delta.content);
298
- }
299
- for (const [i, tc] of (delta.tool_calls ?? []).entries()) {
300
- const key = tc.index ?? i;
301
- const slot = toolArgBuffers.get(key) ?? { id: "", name: "", args: "" };
302
- if (tc.id) slot.id = tc.id;
303
- if (tc.function?.name) slot.name = tc.function.name;
304
- if (tc.function?.arguments) slot.args += tc.function.arguments;
305
- toolArgBuffers.set(key, slot);
255
+ return this._readSse(res.body, options).catch((err) => {
256
+ if (err instanceof AgentError || err instanceof Error && err.name === "AbortError") throw err;
257
+ throw new AgentError("MODEL_ERROR", `Stream broke mid-response: ${err}`, false);
258
+ });
259
+ }
260
+ async _readSse(body, options) {
261
+ let delivered = false;
262
+ try {
263
+ const reader = body.getReader();
264
+ const decoder = new TextDecoder();
265
+ let text = "";
266
+ let thinking = "";
267
+ let inputTokens = 0;
268
+ let outputTokens = 0;
269
+ let reasoningTokens;
270
+ let finishReason;
271
+ const toolArgBuffers = /* @__PURE__ */ new Map();
272
+ let buffer = "";
273
+ const flush = (chunk) => {
274
+ buffer += chunk;
275
+ const lines = buffer.split("\n");
276
+ buffer = lines.pop() ?? "";
277
+ for (const line of lines) {
278
+ const t = line.trim();
279
+ if (!t.startsWith("data:")) continue;
280
+ const payload = t.slice(5).trim();
281
+ if (payload === "[DONE]") continue;
282
+ let evt;
283
+ try {
284
+ evt = JSON.parse(payload);
285
+ } catch {
286
+ continue;
287
+ }
288
+ if (evt.choices?.[0]?.finish_reason) finishReason = evt.choices[0].finish_reason ?? void 0;
289
+ if (evt.usage) {
290
+ if (evt.usage.prompt_tokens != null) inputTokens = evt.usage.prompt_tokens;
291
+ if (evt.usage.completion_tokens != null) outputTokens = evt.usage.completion_tokens;
292
+ const rt = reasoningOf(evt.usage);
293
+ if (rt != null) reasoningTokens = rt;
294
+ continue;
295
+ }
296
+ const delta = evt.choices?.[0]?.delta;
297
+ if (!delta) continue;
298
+ const think = thinkingOf(delta);
299
+ if (think) {
300
+ thinking += think;
301
+ delivered = true;
302
+ options.onThinking?.(think);
303
+ }
304
+ if (delta.content) {
305
+ text += delta.content;
306
+ delivered = true;
307
+ options.onToken?.(delta.content);
308
+ }
309
+ for (const [i, tc] of (delta.tool_calls ?? []).entries()) {
310
+ const key = tc.index ?? i;
311
+ delivered = true;
312
+ const slot = toolArgBuffers.get(key) ?? { id: "", name: "", args: "" };
313
+ if (tc.id) slot.id = tc.id;
314
+ if (tc.function?.name) slot.name = tc.function.name;
315
+ if (tc.function?.arguments) slot.args += tc.function.arguments;
316
+ toolArgBuffers.set(key, slot);
317
+ }
306
318
  }
319
+ };
320
+ for (; ; ) {
321
+ const { done, value } = await reader.read();
322
+ if (done) break;
323
+ flush(decoder.decode(value, { stream: true }));
307
324
  }
308
- };
309
- for (; ; ) {
310
- const { done, value } = await reader.read();
311
- if (done) break;
312
- flush(decoder.decode(value, { stream: true }));
313
- }
314
- flush(decoder.decode());
315
- if (!text && !toolArgBuffers.size && thinking && finishReason === "stop") {
316
- throw new AgentError(
317
- "MODEL_ERROR",
318
- "Stream ended at finish_reason=stop having put the entire answer in the reasoning channel (no content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
319
- true
320
- );
321
- }
322
- if (!text && !toolArgBuffers.size && finishReason === "length") {
323
- throw new AgentError(
324
- "MODEL_ERROR",
325
- `Stream ended at finish_reason=length with no content (${outputTokens} tokens burned). Raise maxTokens.`,
326
- false
327
- );
328
- }
329
- return {
330
- text,
331
- ...thinking ? { thinking } : {},
332
- ...finishReason ? { finishReason } : {},
333
- toolCalls: [...toolArgBuffers.values()].filter((t) => t.name).map((t) => ({ id: t.id || `call_${++callSeq}`, name: t.name, args: safeJson(t.args) })),
334
- usage: {
335
- inputTokens,
336
- outputTokens,
337
- ...reasoningTokens != null ? { reasoningTokens } : {}
325
+ flush(decoder.decode());
326
+ if (!text && !toolArgBuffers.size && thinking && finishReason === "stop") {
327
+ throw new AgentError(
328
+ "MODEL_ERROR",
329
+ "Stream ended at finish_reason=stop having put the entire answer in the reasoning channel (no content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
330
+ true
331
+ );
338
332
  }
339
- };
333
+ if (!text && !toolArgBuffers.size && finishReason === "length") {
334
+ throw new AgentError(
335
+ "MODEL_ERROR",
336
+ `Stream ended at finish_reason=length with no content (${outputTokens} tokens burned). Raise maxTokens.`,
337
+ false
338
+ );
339
+ }
340
+ return {
341
+ text,
342
+ ...thinking ? { thinking } : {},
343
+ ...finishReason ? { finishReason } : {},
344
+ toolCalls: [...toolArgBuffers.values()].map((t) => ({ id: t.id || `call_${++callSeq}`, name: t.name, args: safeJson(t.args) })),
345
+ usage: {
346
+ inputTokens,
347
+ outputTokens,
348
+ ...reasoningTokens != null ? { reasoningTokens } : {}
349
+ }
350
+ };
351
+ } catch (err) {
352
+ if (err instanceof Error && err.name === "AbortError") throw err;
353
+ if (err instanceof AgentError) throw err;
354
+ throw new AgentError("MODEL_ERROR", `Stream broke mid-response${delivered ? " (deltas already delivered)" : ""}: ${err}`, !delivered);
355
+ }
340
356
  }
341
357
  };
342
358
  var callSeq = 0;
@@ -431,8 +447,8 @@ var AnthropicProvider = class {
431
447
  ...thinkingBudget ? { thinking: { type: "enabled", budget_tokens: thinkingBudget } } : {},
432
448
  ...stream ? { stream: true } : {},
433
449
  messages,
434
- ...options.tools.length ? {
435
- tools: options.tools.map((t) => ({
450
+ ...options.tools?.length ? {
451
+ tools: (options.tools ?? []).map((t) => ({
436
452
  name: t.name,
437
453
  description: t.description,
438
454
  input_schema: t.parametersSchema ?? { type: "object" }
package/dist/index.d.ts CHANGED
@@ -625,6 +625,7 @@ declare class OpenAICompatibleProvider implements ModelProvider {
625
625
  private readJson;
626
626
  /** SSE: `data: {...}` lines; delta.content -> onToken, delta.reasoning_content -> onThinking. */
627
627
  private readStream;
628
+ private _readSse;
628
629
  }
629
630
  /** Build a provider from env config (TogetherAI preferred when key present). */
630
631
  declare function providerFromEnv(active: {
@@ -768,7 +769,7 @@ interface ResponseFormatWire {
768
769
  interface CompleteOptions {
769
770
  messages: ChatMessage[];
770
771
  /** Tool metadata; `parametersSchema` (JSON Schema) lets models call with real args — omit only for zero-arg tools. */
771
- tools: {
772
+ tools?: {
772
773
  name: string;
773
774
  description: string;
774
775
  parametersSchema?: Record<string, unknown>;
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  usageCost,
72
72
  user,
73
73
  wireTextOf
74
- } from "./chunk-2XXGDP7B.js";
74
+ } from "./chunk-AJI2QSJE.js";
75
75
 
76
76
  // src/config/index.ts
77
77
  import { existsSync } from "fs";
@@ -864,8 +864,8 @@ var OpenAICompatibleProvider = class {
864
864
  }
865
865
  } : {},
866
866
  messages: options.messages.map(toWireMessage),
867
- ...options.tools.length ? {
868
- tools: options.tools.map((t) => ({
867
+ ...options.tools?.length ? {
868
+ tools: (options.tools ?? []).map((t) => ({
869
869
  type: "function",
870
870
  function: {
871
871
  name: t.name,
@@ -932,91 +932,107 @@ var OpenAICompatibleProvider = class {
932
932
  /** SSE: `data: {...}` lines; delta.content -> onToken, delta.reasoning_content -> onThinking. */
933
933
  async readStream(res, options) {
934
934
  if (!res.body) throw new Error("Streaming response has no body");
935
- const reader = res.body.getReader();
936
- const decoder = new TextDecoder();
937
- let text = "";
938
- let thinking = "";
939
- let inputTokens = 0;
940
- let outputTokens = 0;
941
- let reasoningTokens;
942
- let finishReason;
943
- const toolArgBuffers = /* @__PURE__ */ new Map();
944
- let buffer = "";
945
- const flush = (chunk) => {
946
- buffer += chunk;
947
- const lines = buffer.split("\n");
948
- buffer = lines.pop() ?? "";
949
- for (const line of lines) {
950
- const t = line.trim();
951
- if (!t.startsWith("data:")) continue;
952
- const payload = t.slice(5).trim();
953
- if (payload === "[DONE]") continue;
954
- let evt;
955
- try {
956
- evt = JSON.parse(payload);
957
- } catch {
958
- continue;
959
- }
960
- if (evt.choices?.[0]?.finish_reason) finishReason = evt.choices[0].finish_reason ?? void 0;
961
- if (evt.usage) {
962
- if (evt.usage.prompt_tokens != null) inputTokens = evt.usage.prompt_tokens;
963
- if (evt.usage.completion_tokens != null) outputTokens = evt.usage.completion_tokens;
964
- const rt = reasoningOf(evt.usage);
965
- if (rt != null) reasoningTokens = rt;
966
- continue;
967
- }
968
- const delta = evt.choices?.[0]?.delta;
969
- if (!delta) continue;
970
- const think = thinkingOf(delta);
971
- if (think) {
972
- thinking += think;
973
- options.onThinking?.(think);
974
- }
975
- if (delta.content) {
976
- text += delta.content;
977
- options.onToken?.(delta.content);
978
- }
979
- for (const [i, tc] of (delta.tool_calls ?? []).entries()) {
980
- const key = tc.index ?? i;
981
- const slot = toolArgBuffers.get(key) ?? { id: "", name: "", args: "" };
982
- if (tc.id) slot.id = tc.id;
983
- if (tc.function?.name) slot.name = tc.function.name;
984
- if (tc.function?.arguments) slot.args += tc.function.arguments;
985
- toolArgBuffers.set(key, slot);
935
+ return this._readSse(res.body, options).catch((err) => {
936
+ if (err instanceof AgentError || err instanceof Error && err.name === "AbortError") throw err;
937
+ throw new AgentError("MODEL_ERROR", `Stream broke mid-response: ${err}`, false);
938
+ });
939
+ }
940
+ async _readSse(body, options) {
941
+ let delivered = false;
942
+ try {
943
+ const reader = body.getReader();
944
+ const decoder = new TextDecoder();
945
+ let text = "";
946
+ let thinking = "";
947
+ let inputTokens = 0;
948
+ let outputTokens = 0;
949
+ let reasoningTokens;
950
+ let finishReason;
951
+ const toolArgBuffers = /* @__PURE__ */ new Map();
952
+ let buffer = "";
953
+ const flush = (chunk) => {
954
+ buffer += chunk;
955
+ const lines = buffer.split("\n");
956
+ buffer = lines.pop() ?? "";
957
+ for (const line of lines) {
958
+ const t = line.trim();
959
+ if (!t.startsWith("data:")) continue;
960
+ const payload = t.slice(5).trim();
961
+ if (payload === "[DONE]") continue;
962
+ let evt;
963
+ try {
964
+ evt = JSON.parse(payload);
965
+ } catch {
966
+ continue;
967
+ }
968
+ if (evt.choices?.[0]?.finish_reason) finishReason = evt.choices[0].finish_reason ?? void 0;
969
+ if (evt.usage) {
970
+ if (evt.usage.prompt_tokens != null) inputTokens = evt.usage.prompt_tokens;
971
+ if (evt.usage.completion_tokens != null) outputTokens = evt.usage.completion_tokens;
972
+ const rt = reasoningOf(evt.usage);
973
+ if (rt != null) reasoningTokens = rt;
974
+ continue;
975
+ }
976
+ const delta = evt.choices?.[0]?.delta;
977
+ if (!delta) continue;
978
+ const think = thinkingOf(delta);
979
+ if (think) {
980
+ thinking += think;
981
+ delivered = true;
982
+ options.onThinking?.(think);
983
+ }
984
+ if (delta.content) {
985
+ text += delta.content;
986
+ delivered = true;
987
+ options.onToken?.(delta.content);
988
+ }
989
+ for (const [i, tc] of (delta.tool_calls ?? []).entries()) {
990
+ const key = tc.index ?? i;
991
+ delivered = true;
992
+ const slot = toolArgBuffers.get(key) ?? { id: "", name: "", args: "" };
993
+ if (tc.id) slot.id = tc.id;
994
+ if (tc.function?.name) slot.name = tc.function.name;
995
+ if (tc.function?.arguments) slot.args += tc.function.arguments;
996
+ toolArgBuffers.set(key, slot);
997
+ }
986
998
  }
999
+ };
1000
+ for (; ; ) {
1001
+ const { done, value } = await reader.read();
1002
+ if (done) break;
1003
+ flush(decoder.decode(value, { stream: true }));
987
1004
  }
988
- };
989
- for (; ; ) {
990
- const { done, value } = await reader.read();
991
- if (done) break;
992
- flush(decoder.decode(value, { stream: true }));
993
- }
994
- flush(decoder.decode());
995
- if (!text && !toolArgBuffers.size && thinking && finishReason === "stop") {
996
- throw new AgentError(
997
- "MODEL_ERROR",
998
- "Stream ended at finish_reason=stop having put the entire answer in the reasoning channel (no content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
999
- true
1000
- );
1001
- }
1002
- if (!text && !toolArgBuffers.size && finishReason === "length") {
1003
- throw new AgentError(
1004
- "MODEL_ERROR",
1005
- `Stream ended at finish_reason=length with no content (${outputTokens} tokens burned). Raise maxTokens.`,
1006
- false
1007
- );
1008
- }
1009
- return {
1010
- text,
1011
- ...thinking ? { thinking } : {},
1012
- ...finishReason ? { finishReason } : {},
1013
- toolCalls: [...toolArgBuffers.values()].filter((t) => t.name).map((t) => ({ id: t.id || `call_${++callSeq}`, name: t.name, args: safeJson(t.args) })),
1014
- usage: {
1015
- inputTokens,
1016
- outputTokens,
1017
- ...reasoningTokens != null ? { reasoningTokens } : {}
1005
+ flush(decoder.decode());
1006
+ if (!text && !toolArgBuffers.size && thinking && finishReason === "stop") {
1007
+ throw new AgentError(
1008
+ "MODEL_ERROR",
1009
+ "Stream ended at finish_reason=stop having put the entire answer in the reasoning channel (no content, no tool calls, thinking present). The reasoning/content split lost the reply \u2014 retry or raise maxTokens.",
1010
+ true
1011
+ );
1018
1012
  }
1019
- };
1013
+ if (!text && !toolArgBuffers.size && finishReason === "length") {
1014
+ throw new AgentError(
1015
+ "MODEL_ERROR",
1016
+ `Stream ended at finish_reason=length with no content (${outputTokens} tokens burned). Raise maxTokens.`,
1017
+ false
1018
+ );
1019
+ }
1020
+ return {
1021
+ text,
1022
+ ...thinking ? { thinking } : {},
1023
+ ...finishReason ? { finishReason } : {},
1024
+ toolCalls: [...toolArgBuffers.values()].map((t) => ({ id: t.id || `call_${++callSeq}`, name: t.name, args: safeJson(t.args) })),
1025
+ usage: {
1026
+ inputTokens,
1027
+ outputTokens,
1028
+ ...reasoningTokens != null ? { reasoningTokens } : {}
1029
+ }
1030
+ };
1031
+ } catch (err) {
1032
+ if (err instanceof Error && err.name === "AbortError") throw err;
1033
+ if (err instanceof AgentError) throw err;
1034
+ throw new AgentError("MODEL_ERROR", `Stream broke mid-response${delivered ? " (deltas already delivered)" : ""}: ${err}`, !delivered);
1035
+ }
1020
1036
  }
1021
1037
  };
1022
1038
  var callSeq = 0;
@@ -1114,8 +1130,8 @@ var AnthropicProvider = class {
1114
1130
  ...thinkingBudget ? { thinking: { type: "enabled", budget_tokens: thinkingBudget } } : {},
1115
1131
  ...stream ? { stream: true } : {},
1116
1132
  messages,
1117
- ...options.tools.length ? {
1118
- tools: options.tools.map((t) => ({
1133
+ ...options.tools?.length ? {
1134
+ tools: (options.tools ?? []).map((t) => ({
1119
1135
  name: t.name,
1120
1136
  description: t.description,
1121
1137
  input_schema: t.parametersSchema ?? { type: "object" }