@axlsdk/axl 0.7.5 → 0.8.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.
package/dist/index.js CHANGED
@@ -165,29 +165,32 @@ async function fetchWithRetry(input, init, maxRetries = MAX_RETRIES) {
165
165
 
166
166
  // src/providers/openai.ts
167
167
  var OPENAI_PRICING = {
168
- "gpt-4o": [25e-7, 1e-5],
169
- "gpt-4o-mini": [15e-8, 6e-7],
170
- "gpt-4-turbo": [1e-5, 3e-5],
171
- "gpt-4": [3e-5, 6e-5],
172
- "gpt-3.5-turbo": [5e-7, 15e-7],
173
- "gpt-5": [125e-8, 1e-5],
174
- "gpt-5-mini": [25e-8, 2e-6],
175
- "gpt-5-nano": [5e-8, 4e-7],
176
- "gpt-5.1": [125e-8, 1e-5],
177
- "gpt-5.2": [175e-8, 14e-6],
178
- "gpt-5.3": [175e-8, 14e-6],
179
- "gpt-5.4": [25e-7, 15e-6],
180
- "gpt-5.4-pro": [3e-5, 18e-5],
181
- o1: [15e-6, 6e-5],
182
- "o1-mini": [3e-6, 12e-6],
183
- "o1-pro": [15e-5, 6e-4],
184
- o3: [1e-5, 4e-5],
185
- "o3-mini": [11e-7, 44e-7],
186
- "o3-pro": [2e-5, 8e-5],
187
- "o4-mini": [11e-7, 44e-7],
188
- "gpt-4.1": [2e-6, 8e-6],
189
- "gpt-4.1-mini": [4e-7, 16e-7],
190
- "gpt-4.1-nano": [1e-7, 4e-7]
168
+ // gpt-4o era — cache reads at 50% of input rate
169
+ "gpt-4o": [25e-7, 1e-5, 0.5],
170
+ "gpt-4o-mini": [15e-8, 6e-7, 0.5],
171
+ "gpt-4-turbo": [1e-5, 3e-5, 0.5],
172
+ "gpt-4": [3e-5, 6e-5, 0.5],
173
+ "gpt-3.5-turbo": [5e-7, 15e-7, 0.5],
174
+ o1: [15e-6, 6e-5, 0.5],
175
+ "o1-mini": [3e-6, 12e-6, 0.5],
176
+ "o1-pro": [15e-5, 6e-4, 0.5],
177
+ // gpt-4.1 / o3 / o4 era — cache reads at 25% of input rate
178
+ "gpt-4.1": [2e-6, 8e-6, 0.25],
179
+ "gpt-4.1-mini": [4e-7, 16e-7, 0.25],
180
+ "gpt-4.1-nano": [1e-7, 4e-7, 0.25],
181
+ o3: [1e-5, 4e-5, 0.25],
182
+ "o3-mini": [11e-7, 44e-7, 0.25],
183
+ "o3-pro": [2e-5, 8e-5, 0.25],
184
+ "o4-mini": [11e-7, 44e-7, 0.25],
185
+ // gpt-5 era — cache reads at 10% of input rate
186
+ "gpt-5": [125e-8, 1e-5, 0.1],
187
+ "gpt-5-mini": [25e-8, 2e-6, 0.1],
188
+ "gpt-5-nano": [5e-8, 4e-7, 0.1],
189
+ "gpt-5.1": [125e-8, 1e-5, 0.1],
190
+ "gpt-5.2": [175e-8, 14e-6, 0.1],
191
+ "gpt-5.3": [175e-8, 14e-6, 0.1],
192
+ "gpt-5.4": [25e-7, 15e-6, 0.1],
193
+ "gpt-5.4-pro": [3e-5, 18e-5, 0.1]
191
194
  };
192
195
  var PRICING_KEYS_BY_LENGTH = Object.keys(OPENAI_PRICING).sort((a, b) => b.length - a.length);
193
196
  function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens) {
@@ -201,9 +204,9 @@ function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens)
201
204
  }
202
205
  }
203
206
  if (!pricing) return 0;
204
- const [inputRate, outputRate] = pricing;
207
+ const [inputRate, outputRate, cacheMultiplier] = pricing;
205
208
  const cached = cachedTokens ?? 0;
206
- const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * 0.5;
209
+ const inputCost = (promptTokens - cached) * inputRate + cached * inputRate * cacheMultiplier;
207
210
  return inputCost + completionTokens * outputRate;
208
211
  }
209
212
  function isOSeriesModel(model) {
@@ -313,7 +316,7 @@ var OpenAIProvider = class {
313
316
  if (!res.body) {
314
317
  throw new Error("OpenAI stream response has no body");
315
318
  }
316
- yield* this.parseSSEStream(res.body);
319
+ yield* this.parseSSEStream(res.body, options.model);
317
320
  }
318
321
  // ---------------------------------------------------------------------------
319
322
  // Internal helpers
@@ -387,7 +390,7 @@ var OpenAIProvider = class {
387
390
  if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
388
391
  return out;
389
392
  }
390
- async *parseSSEStream(body) {
393
+ async *parseSSEStream(body, model) {
391
394
  const reader = body.getReader();
392
395
  const decoder = new TextDecoder();
393
396
  let buffer = "";
@@ -404,7 +407,16 @@ var OpenAIProvider = class {
404
407
  const trimmed = line.trim();
405
408
  if (!trimmed || trimmed.startsWith(":")) continue;
406
409
  if (trimmed === "data: [DONE]") {
407
- yield { type: "done", usage: usageData };
410
+ yield {
411
+ type: "done",
412
+ usage: usageData,
413
+ cost: usageData ? estimateOpenAICost(
414
+ model,
415
+ usageData.prompt_tokens,
416
+ usageData.completion_tokens,
417
+ usageData.cached_tokens
418
+ ) : void 0
419
+ };
408
420
  return;
409
421
  }
410
422
  if (trimmed.startsWith("data: ")) {
@@ -447,7 +459,16 @@ var OpenAIProvider = class {
447
459
  }
448
460
  }
449
461
  }
450
- yield { type: "done", usage: usageData };
462
+ yield {
463
+ type: "done",
464
+ usage: usageData,
465
+ cost: usageData ? estimateOpenAICost(
466
+ model,
467
+ usageData.prompt_tokens,
468
+ usageData.completion_tokens,
469
+ usageData.cached_tokens
470
+ ) : void 0
471
+ };
451
472
  } finally {
452
473
  reader.releaseLock();
453
474
  }
@@ -705,6 +726,7 @@ var OpenAIResponsesProvider = class {
705
726
  const decoder = new TextDecoder();
706
727
  let buffer = "";
707
728
  const callIdMap = /* @__PURE__ */ new Map();
729
+ let eventType = "";
708
730
  try {
709
731
  while (true) {
710
732
  const { done, value } = await reader.read();
@@ -712,7 +734,6 @@ var OpenAIResponsesProvider = class {
712
734
  buffer += decoder.decode(value, { stream: true });
713
735
  const lines = buffer.split("\n");
714
736
  buffer = lines.pop() ?? "";
715
- let eventType = "";
716
737
  for (const line of lines) {
717
738
  const trimmed = line.trim();
718
739
  if (!trimmed || trimmed.startsWith(":")) continue;
@@ -780,7 +801,17 @@ var OpenAIResponsesProvider = class {
780
801
  } : void 0;
781
802
  const reasoningItems = response?.output?.filter((item) => item.type === "reasoning") ?? [];
782
803
  const providerMetadata = reasoningItems.length > 0 ? { openaiReasoningItems: reasoningItems } : void 0;
783
- return { type: "done", usage, providerMetadata };
804
+ return {
805
+ type: "done",
806
+ usage,
807
+ cost: usage ? estimateOpenAICost(
808
+ model,
809
+ usage.prompt_tokens,
810
+ usage.completion_tokens,
811
+ usage.cached_tokens
812
+ ) : void 0,
813
+ providerMetadata
814
+ };
784
815
  }
785
816
  case "response.failed": {
786
817
  const errorMsg = data.response?.error?.message ?? data.response?.status_details?.error?.message ?? "Unknown error";
@@ -913,7 +944,7 @@ var AnthropicProvider = class {
913
944
  if (!res.body) {
914
945
  throw new Error("Anthropic stream response has no body");
915
946
  }
916
- yield* this.parseSSEStream(res.body);
947
+ yield* this.parseSSEStream(res.body, options.model);
917
948
  }
918
949
  // ---------------------------------------------------------------------------
919
950
  // Internal: request building
@@ -1156,13 +1187,14 @@ ${jsonInstruction}` : jsonInstruction;
1156
1187
  // ---------------------------------------------------------------------------
1157
1188
  // Internal: SSE stream parsing
1158
1189
  // ---------------------------------------------------------------------------
1159
- async *parseSSEStream(body) {
1190
+ async *parseSSEStream(body, model) {
1160
1191
  const reader = body.getReader();
1161
1192
  const decoder = new TextDecoder();
1162
1193
  let buffer = "";
1163
1194
  let currentToolId = "";
1164
1195
  let currentToolName = "";
1165
1196
  let usage;
1197
+ let cacheWrite = 0;
1166
1198
  try {
1167
1199
  while (true) {
1168
1200
  const { done, value } = await reader.read();
@@ -1219,7 +1251,7 @@ ${jsonInstruction}` : jsonInstruction;
1219
1251
  case "message_start": {
1220
1252
  if (event.message?.usage) {
1221
1253
  const cacheRead = event.message.usage.cache_read_input_tokens ?? 0;
1222
- const cacheWrite = event.message.usage.cache_creation_input_tokens ?? 0;
1254
+ cacheWrite = event.message.usage.cache_creation_input_tokens ?? 0;
1223
1255
  const inputTokens = (event.message.usage.input_tokens ?? 0) + cacheRead + cacheWrite;
1224
1256
  usage = {
1225
1257
  prompt_tokens: inputTokens,
@@ -1250,13 +1282,33 @@ ${jsonInstruction}` : jsonInstruction;
1250
1282
  if (usage) {
1251
1283
  usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
1252
1284
  }
1253
- yield { type: "done", usage };
1285
+ yield {
1286
+ type: "done",
1287
+ usage,
1288
+ cost: usage ? estimateAnthropicCost(
1289
+ model,
1290
+ usage.prompt_tokens,
1291
+ usage.completion_tokens,
1292
+ usage.cached_tokens,
1293
+ cacheWrite
1294
+ ) : void 0
1295
+ };
1254
1296
  return;
1255
1297
  }
1256
1298
  }
1257
1299
  }
1258
1300
  }
1259
- yield { type: "done", usage };
1301
+ yield {
1302
+ type: "done",
1303
+ usage,
1304
+ cost: usage ? estimateAnthropicCost(
1305
+ model,
1306
+ usage.prompt_tokens,
1307
+ usage.completion_tokens,
1308
+ usage.cached_tokens,
1309
+ cacheWrite
1310
+ ) : void 0
1311
+ };
1260
1312
  } finally {
1261
1313
  reader.releaseLock();
1262
1314
  }
@@ -1383,7 +1435,7 @@ var GeminiProvider = class {
1383
1435
  if (!res.body) {
1384
1436
  throw new Error("Gemini stream response has no body");
1385
1437
  }
1386
- yield* this.parseSSEStream(res.body);
1438
+ yield* this.parseSSEStream(res.body, options.model);
1387
1439
  }
1388
1440
  // ---------------------------------------------------------------------------
1389
1441
  // Internal: request building
@@ -1664,7 +1716,7 @@ var GeminiProvider = class {
1664
1716
  // ---------------------------------------------------------------------------
1665
1717
  // Internal: SSE stream parsing
1666
1718
  // ---------------------------------------------------------------------------
1667
- async *parseSSEStream(body) {
1719
+ async *parseSSEStream(body, model) {
1668
1720
  const reader = body.getReader();
1669
1721
  const decoder = new TextDecoder();
1670
1722
  let buffer = "";
@@ -1720,7 +1772,17 @@ var GeminiProvider = class {
1720
1772
  }
1721
1773
  }
1722
1774
  const providerMetadata = accumulatedParts.length > 0 ? { geminiParts: accumulatedParts } : void 0;
1723
- yield { type: "done", usage, providerMetadata };
1775
+ yield {
1776
+ type: "done",
1777
+ usage,
1778
+ cost: usage ? estimateGeminiCost(
1779
+ model,
1780
+ usage.prompt_tokens,
1781
+ usage.completion_tokens,
1782
+ usage.cached_tokens
1783
+ ) : void 0,
1784
+ providerMetadata
1785
+ };
1724
1786
  } finally {
1725
1787
  reader.releaseLock();
1726
1788
  }
@@ -1936,6 +1998,18 @@ var GuardrailError = class extends AxlError {
1936
1998
  this.reason = reason;
1937
1999
  }
1938
2000
  };
2001
+ var ValidationError = class extends AxlError {
2002
+ lastOutput;
2003
+ reason;
2004
+ retries;
2005
+ constructor(lastOutput, reason, retries) {
2006
+ super("VALIDATION_ERROR", `Validation failed after ${retries} retries: ${reason}`);
2007
+ this.name = "ValidationError";
2008
+ this.lastOutput = lastOutput;
2009
+ this.reason = reason;
2010
+ this.retries = retries;
2011
+ }
2012
+ };
1939
2013
  var ToolDenied = class extends AxlError {
1940
2014
  toolName;
1941
2015
  agentName;
@@ -2200,9 +2274,6 @@ var WorkflowContext = class _WorkflowContext {
2200
2274
  agent2,
2201
2275
  prompt,
2202
2276
  options,
2203
- 0,
2204
- void 0,
2205
- void 0,
2206
2277
  void 0,
2207
2278
  usageCapture
2208
2279
  );
@@ -2252,7 +2323,7 @@ var WorkflowContext = class _WorkflowContext {
2252
2323
  return result;
2253
2324
  });
2254
2325
  }
2255
- async executeAgentCall(agent2, prompt, options, retryCount = 0, previousOutput, previousError, handoffMessages, usageCapture) {
2326
+ async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
2256
2327
  if (this.budgetContext?.exceeded) {
2257
2328
  const { limit, totalCost: spent, policy } = this.budgetContext;
2258
2329
  if (policy === "warn") {
@@ -2324,16 +2395,6 @@ var WorkflowContext = class _WorkflowContext {
2324
2395
 
2325
2396
  Respond with valid JSON matching this schema:
2326
2397
  ${JSON.stringify(jsonSchema, null, 2)}`;
2327
- }
2328
- if (previousOutput && previousError) {
2329
- userContent += `
2330
-
2331
- Your previous response was invalid:
2332
- ${previousOutput}
2333
-
2334
- Error: ${previousError}
2335
-
2336
- Please fix and try again.`;
2337
2398
  }
2338
2399
  messages.push({ role: "user", content: userContent });
2339
2400
  if (handoffMessages && handoffMessages.length > 0) {
@@ -2378,9 +2439,17 @@ Please fix and try again.`;
2378
2439
  const maxTurns = agent2._config.maxTurns ?? 25;
2379
2440
  const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
2380
2441
  const startTime = Date.now();
2442
+ if (this.onToken && options?.validate) {
2443
+ throw new AxlError(
2444
+ "INVALID_CONFIG",
2445
+ "Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
2446
+ );
2447
+ }
2381
2448
  const currentMessages = [...messages];
2382
2449
  let turns = 0;
2383
2450
  let guardrailOutputRetries = 0;
2451
+ let schemaRetries = 0;
2452
+ let validateRetries = 0;
2384
2453
  while (turns < maxTurns) {
2385
2454
  if (Date.now() - startTime > timeoutMs) {
2386
2455
  throw new TimeoutError("ctx.ask()", timeoutMs);
@@ -2430,7 +2499,8 @@ Please fix and try again.`;
2430
2499
  response = {
2431
2500
  content: content2,
2432
2501
  tool_calls: void 0,
2433
- usage: chunk.usage
2502
+ usage: chunk.usage,
2503
+ cost: chunk.cost
2434
2504
  };
2435
2505
  }
2436
2506
  }
@@ -2507,14 +2577,17 @@ Please fix and try again.`;
2507
2577
  }
2508
2578
  }
2509
2579
  const handoffStart = Date.now();
2510
- const handoffOptions = options ? { schema: options.schema, retries: options.retries, metadata: options.metadata } : void 0;
2580
+ const handoffOptions = options ? {
2581
+ schema: options.schema,
2582
+ retries: options.retries,
2583
+ metadata: options.metadata,
2584
+ validate: options.validate,
2585
+ validateRetries: options.validateRetries
2586
+ } : void 0;
2511
2587
  const handoffFn = () => this.executeAgentCall(
2512
2588
  descriptor.agent,
2513
2589
  handoffPrompt,
2514
2590
  handoffOptions,
2515
- 0,
2516
- void 0,
2517
- void 0,
2518
2591
  currentMessages,
2519
2592
  usageCapture
2520
2593
  );
@@ -2848,26 +2921,26 @@ Please fix and try again.`;
2848
2921
  throw new GuardrailError("output", outputResult.reason ?? "Output blocked by guardrail");
2849
2922
  }
2850
2923
  }
2924
+ let validated = void 0;
2851
2925
  if (options?.schema) {
2852
2926
  try {
2853
2927
  const parsed = JSON.parse(stripMarkdownFences(content));
2854
- const validated = options.schema.parse(parsed);
2855
- this.pushAssistantToSessionHistory(content, response.providerMetadata);
2856
- return validated;
2928
+ validated = options.schema.parse(parsed);
2857
2929
  } catch (err) {
2858
- const maxRetries = options.retries ?? 3;
2859
- if (retryCount < maxRetries) {
2930
+ const maxSchemaRetries = options.retries ?? 3;
2931
+ if (schemaRetries < maxSchemaRetries) {
2932
+ schemaRetries++;
2860
2933
  const errorMsg = err instanceof Error ? err.message : String(err);
2861
- return this.executeAgentCall(
2862
- agent2,
2863
- prompt,
2864
- options,
2865
- retryCount + 1,
2934
+ currentMessages.push({
2935
+ role: "assistant",
2866
2936
  content,
2867
- errorMsg,
2868
- void 0,
2869
- usageCapture
2870
- );
2937
+ ...response.providerMetadata ? { providerMetadata: response.providerMetadata } : {}
2938
+ });
2939
+ currentMessages.push({
2940
+ role: "system",
2941
+ content: `Your response was not valid JSON or did not match the required schema: ${errorMsg}. Please fix and try again.`
2942
+ });
2943
+ continue;
2871
2944
  }
2872
2945
  const zodErr = err instanceof ZodError ? err : new ZodError([
2873
2946
  {
@@ -2876,11 +2949,55 @@ Please fix and try again.`;
2876
2949
  message: err instanceof Error ? err.message : String(err)
2877
2950
  }
2878
2951
  ]);
2879
- throw new VerifyError(content, zodErr, maxRetries);
2952
+ throw new VerifyError(content, zodErr, maxSchemaRetries);
2953
+ }
2954
+ }
2955
+ if (options?.schema && options.validate) {
2956
+ let validateResult;
2957
+ try {
2958
+ validateResult = await options.validate(validated, {
2959
+ metadata: this.metadata
2960
+ });
2961
+ } catch (err) {
2962
+ const reason = err instanceof Error ? err.message : String(err);
2963
+ validateResult = { valid: false, reason: `Validator error: ${reason}` };
2964
+ }
2965
+ this.emitTrace({
2966
+ type: "validate",
2967
+ agent: agent2._name,
2968
+ data: {
2969
+ valid: validateResult.valid,
2970
+ ...validateResult.reason ? { reason: validateResult.reason } : {}
2971
+ }
2972
+ });
2973
+ this.spanManager?.addEventToActiveSpan("axl.validate.check", {
2974
+ "axl.validate.valid": validateResult.valid,
2975
+ ...validateResult.reason ? { "axl.validate.reason": validateResult.reason } : {}
2976
+ });
2977
+ if (!validateResult.valid) {
2978
+ const maxValidateRetries = options.validateRetries ?? 2;
2979
+ if (validateRetries < maxValidateRetries) {
2980
+ validateRetries++;
2981
+ currentMessages.push({
2982
+ role: "assistant",
2983
+ content,
2984
+ ...response.providerMetadata ? { providerMetadata: response.providerMetadata } : {}
2985
+ });
2986
+ currentMessages.push({
2987
+ role: "system",
2988
+ content: `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`
2989
+ });
2990
+ continue;
2991
+ }
2992
+ throw new ValidationError(
2993
+ validated,
2994
+ validateResult.reason ?? "Validation failed",
2995
+ maxValidateRetries
2996
+ );
2880
2997
  }
2881
2998
  }
2882
2999
  this.pushAssistantToSessionHistory(content, response.providerMetadata);
2883
- return content;
3000
+ return validated ?? content;
2884
3001
  }
2885
3002
  throw new MaxTurnsError("ctx.ask()", maxTurns);
2886
3003
  }
@@ -3237,32 +3354,57 @@ ${summaryResponse.content}`
3237
3354
  // ── ctx.verify() ──────────────────────────────────────────────────────
3238
3355
  async verify(fn, schema, options) {
3239
3356
  const maxRetries = options?.retries ?? 3;
3240
- let lastOutput = void 0;
3241
- let lastErrorMessage = void 0;
3357
+ let lastRetry = void 0;
3242
3358
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
3243
- let result;
3359
+ let rawOutput;
3244
3360
  try {
3245
- result = await fn(lastOutput, lastErrorMessage);
3246
- lastOutput = result;
3247
- return schema.parse(result);
3361
+ const result = await fn(lastRetry);
3362
+ rawOutput = result;
3363
+ const parsed = schema.parse(result);
3364
+ if (options?.validate) {
3365
+ let validateResult;
3366
+ try {
3367
+ validateResult = await options.validate(parsed, { metadata: this.metadata });
3368
+ } catch (err) {
3369
+ const reason = err instanceof Error ? err.message : String(err);
3370
+ validateResult = { valid: false, reason: `Validator error: ${reason}` };
3371
+ }
3372
+ if (!validateResult.valid) {
3373
+ const errorMsg = validateResult.reason ?? "Validation failed";
3374
+ lastRetry = { error: errorMsg, output: rawOutput, parsed };
3375
+ if (attempt === maxRetries) {
3376
+ if (options?.fallback !== void 0) return options.fallback;
3377
+ throw new ValidationError(parsed, errorMsg, maxRetries);
3378
+ }
3379
+ continue;
3380
+ }
3381
+ }
3382
+ return parsed;
3248
3383
  } catch (err) {
3249
- if (err instanceof ZodError) {
3250
- lastErrorMessage = err.message;
3251
- } else if (err instanceof Error) {
3252
- lastErrorMessage = err.message;
3253
- } else {
3254
- lastErrorMessage = String(err);
3384
+ if (err instanceof ValidationError) {
3385
+ lastRetry = {
3386
+ error: err.reason,
3387
+ output: rawOutput,
3388
+ parsed: err.lastOutput
3389
+ };
3390
+ if (attempt === maxRetries) {
3391
+ if (options?.fallback !== void 0) return options.fallback;
3392
+ throw err;
3393
+ }
3394
+ continue;
3255
3395
  }
3396
+ const errorMsg = err instanceof ZodError ? err.message : err instanceof Error ? err.message : String(err);
3397
+ lastRetry = { error: errorMsg, output: rawOutput };
3256
3398
  if (attempt === maxRetries) {
3257
3399
  if (options?.fallback !== void 0) return options.fallback;
3258
- const zodErr = err instanceof ZodError ? err : new ZodError([{ code: "custom", path: [], message: lastErrorMessage }]);
3259
- throw new VerifyError(lastOutput, zodErr, maxRetries);
3400
+ const zodErr = err instanceof ZodError ? err : new ZodError([{ code: "custom", path: [], message: errorMsg }]);
3401
+ throw new VerifyError(rawOutput, zodErr, maxRetries);
3260
3402
  }
3261
3403
  }
3262
3404
  }
3263
3405
  if (options?.fallback !== void 0) return options.fallback;
3264
3406
  throw new VerifyError(
3265
- lastOutput,
3407
+ lastRetry?.output,
3266
3408
  new ZodError([{ code: "custom", path: [], message: "Verify failed" }]),
3267
3409
  maxRetries
3268
3410
  );
@@ -3364,7 +3506,7 @@ ${summaryResponse.content}`
3364
3506
  let remaining = fns.length;
3365
3507
  for (const fn of fns) {
3366
3508
  const p = signalStorage.run(composedSignal, fn);
3367
- p.then((value) => {
3509
+ p.then(async (value) => {
3368
3510
  if (settled) return;
3369
3511
  if (schema) {
3370
3512
  const parsed = schema.safeParse(value);
@@ -3377,6 +3519,33 @@ ${summaryResponse.content}`
3377
3519
  }
3378
3520
  return;
3379
3521
  }
3522
+ if (options?.validate) {
3523
+ try {
3524
+ const validateResult = await options.validate(parsed.data, {
3525
+ metadata: this.metadata
3526
+ });
3527
+ if (!validateResult.valid) {
3528
+ remaining--;
3529
+ lastError = new Error(
3530
+ `Validation failed: ${validateResult.reason ?? "Validation failed"}`
3531
+ );
3532
+ if (remaining === 0 && !settled) {
3533
+ settled = true;
3534
+ reject(lastError);
3535
+ }
3536
+ return;
3537
+ }
3538
+ } catch (err) {
3539
+ remaining--;
3540
+ lastError = err instanceof Error ? err : new Error(`Validator error: ${String(err)}`);
3541
+ if (remaining === 0 && !settled) {
3542
+ settled = true;
3543
+ reject(lastError);
3544
+ }
3545
+ return;
3546
+ }
3547
+ }
3548
+ if (settled) return;
3380
3549
  settled = true;
3381
3550
  controller.abort();
3382
3551
  resolve(parsed.data);
@@ -3628,7 +3797,9 @@ ${summaryResponse.content}`
3628
3797
  return this.ask(agents[0], prompt, {
3629
3798
  schema: options?.schema,
3630
3799
  retries: options?.retries,
3631
- metadata: options?.metadata
3800
+ metadata: options?.metadata,
3801
+ validate: options?.validate,
3802
+ validateRetries: options?.validateRetries
3632
3803
  });
3633
3804
  }
3634
3805
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
@@ -3669,7 +3840,9 @@ ${summaryResponse.content}`
3669
3840
  return this.ask(routerAgent, prompt, {
3670
3841
  schema: options?.schema,
3671
3842
  retries: options?.retries,
3672
- metadata: options?.metadata
3843
+ metadata: options?.metadata,
3844
+ validate: options?.validate,
3845
+ validateRetries: options?.validateRetries
3673
3846
  });
3674
3847
  }
3675
3848
  // ── Private ───────────────────────────────────────────────────────────
@@ -5949,6 +6122,7 @@ export {
5949
6122
  SqliteVectorStore,
5950
6123
  TimeoutError,
5951
6124
  ToolDenied,
6125
+ ValidationError,
5952
6126
  VerifyError,
5953
6127
  WorkflowContext,
5954
6128
  agent,