@axlsdk/testing 0.19.1 → 0.20.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/README.md CHANGED
@@ -116,9 +116,22 @@ const runtime = new AxlTestRuntime();
116
116
  runtime.register(myWorkflow);
117
117
 
118
118
  // Mock the LLM
119
- runtime.mockProvider('openai', MockProvider.sequence([
120
- { content: '42' },
121
- ]));
119
+ runtime.mockProvider(
120
+ 'openai',
121
+ MockProvider.sequence([
122
+ {
123
+ content: '',
124
+ tool_calls: [
125
+ {
126
+ id: 'call-calculator',
127
+ type: 'function',
128
+ function: { name: 'calculator', arguments: '{"expression":"2+2"}' },
129
+ },
130
+ ],
131
+ },
132
+ { content: '42' },
133
+ ]),
134
+ );
122
135
 
123
136
  // Mock tools
124
137
  runtime.mockTool('calculator', async ({ expression }) => ({ result: 4 }));
@@ -126,13 +139,23 @@ runtime.mockTool('calculator', async ({ expression }) => ({ result: 4 }));
126
139
  // Execute
127
140
  const result = await runtime.execute('my-workflow', { question: 'What is 2+2?' });
128
141
 
129
- // Inspect recorded calls
130
- expect(runtime.agentCalls()).toHaveLength(1);
131
- expect(runtime.toolCalls()).toHaveLength(1);
142
+ // Inspect recorded calls. Every accepted call has a terminal v2 outcome.
143
+ expect(runtime.agentCalls()).toHaveLength(2); // tool request + final provider turn
144
+ const [call] = runtime.toolCalls('calculator');
145
+ if (call.outcome.status !== 'succeeded') {
146
+ throw new Error(`Expected calculator to succeed, got ${call.outcome.status}`);
147
+ }
148
+ expect(call.outcome.result).toEqual({ result: 4 });
132
149
  expect(runtime.totalCost()).toBe(0);
133
150
  expect(runtime.unpriced()).toBe(false); // true if any call used an unpriced model → totalCost() is a lower bound
134
151
  ```
135
152
 
153
+ Recorded tool calls also carry `executionId`, `askId`, and `callId` for exact
154
+ correlation. Failed, denied, and pre-result cancelled outcomes intentionally do
155
+ not expose a top-level `result`; narrow `outcome.status` first. Configured mocks
156
+ bypass the local schema, approval, retry, hooks, and real handler, while still
157
+ using the configured tool's `sensitive` and `toModelOutput` policies.
158
+
136
159
  For testing human-in-the-loop flows:
137
160
 
138
161
  ```typescript
package/dist/index.cjs CHANGED
@@ -235,6 +235,15 @@ var import_axl = require("@axlsdk/axl");
235
235
  function generateExecutionId() {
236
236
  return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
237
237
  }
238
+ function preserveDrainCause(error, cause) {
239
+ if (cause === void 0 || cause === error || "cause" in error) return error;
240
+ Object.defineProperty(error, "cause", {
241
+ configurable: true,
242
+ enumerable: false,
243
+ value: cause
244
+ });
245
+ return error;
246
+ }
238
247
  var AxlTestRuntime = class {
239
248
  workflows = /* @__PURE__ */ new Map();
240
249
  mockProviders = /* @__PURE__ */ new Map();
@@ -306,6 +315,7 @@ var AxlTestRuntime = class {
306
315
  // Thread `events` config so tests can exercise the queue cap and
307
316
  // overflow policy on `ctx.events`. Mirrors the production runtime.
308
317
  eventStreamOptions: options?.events,
318
+ branchDrainTimeoutMs: options?.branchDrainTimeoutMs,
309
319
  // Production runtime threads this (see `runtime.ts:594` in
310
320
  // `execute()` and `:765` in `stream()`). `emitEvent` auto-stamps
311
321
  // `event.workflow` from it, so consumers grouping by workflow
@@ -324,9 +334,17 @@ var AxlTestRuntime = class {
324
334
  type: event.type,
325
335
  data: event.data
326
336
  });
327
- if (event.type === "tool_call_end" && event.data) {
328
- const { args, result } = event.data;
329
- this._toolCalls.push({ name: event.tool, args, result });
337
+ if (event.type === "tool_call_end") {
338
+ const { args, requestedTool, outcome } = event.data;
339
+ this._toolCalls.push({
340
+ name: event.tool,
341
+ executionId: event.executionId,
342
+ askId: event.askId,
343
+ callId: event.callId,
344
+ args,
345
+ ...requestedTool !== void 0 ? { requestedTool } : {},
346
+ outcome
347
+ });
330
348
  }
331
349
  this._totalCost += (0, import_axl.eventCostContribution)(event);
332
350
  if ((0, import_axl.isUnpricedLeaf)(event)) this._unpriced = true;
@@ -355,23 +373,33 @@ var AxlTestRuntime = class {
355
373
  ctx._emitWorkflowStart(validated);
356
374
  try {
357
375
  const result = await workflow.handler(ctx);
376
+ const observation = await ctx._drainBranchWork();
358
377
  if (workflow.outputSchema) workflow.outputSchema.parse(result);
359
378
  ctx._emitWorkflowEnd({
360
379
  status: "completed",
361
380
  duration: Date.now() - startedAt,
362
- result
381
+ result,
382
+ observation
363
383
  });
364
384
  if (this.recordPath) await this._writeRecording();
365
385
  return result;
366
386
  } catch (err) {
367
- const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
387
+ let workflowError = err;
388
+ let observation = { complete: true };
389
+ try {
390
+ observation = await ctx._drainBranchWork();
391
+ } catch (drainError) {
392
+ workflowError = drainError instanceof Error ? preserveDrainCause(drainError, workflowError) : drainError;
393
+ }
394
+ const aborted = typeof workflowError === "object" && workflowError !== null && workflowError.name === "AbortError";
368
395
  ctx._emitWorkflowEnd({
369
396
  status: "failed",
370
397
  duration: Date.now() - startedAt,
371
- error: err instanceof Error ? err.message : String(err),
372
- ...aborted ? { aborted: true } : {}
398
+ error: workflowError instanceof Error ? workflowError.message : String(workflowError),
399
+ ...aborted ? { aborted: true } : {},
400
+ observation
373
401
  });
374
- throw err;
402
+ throw workflowError;
375
403
  }
376
404
  }
377
405
  toolCalls(name) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/mock-provider.ts","../src/mock-tool.ts","../src/test-runtime.ts"],"sourcesContent":["export { AxlTestRuntime } from './test-runtime.js';\nexport type { RecordedToolCall, RecordedAgentCall, RecordedStep } from './test-runtime.js';\nexport { MockProvider } from './mock-provider.js';\nexport { MockTool } from './mock-tool.js';\n","import { readFileSync } from 'node:fs';\nimport { z } from 'zod';\nimport type {\n ChatMessage,\n ChatOptions,\n ToolCallMessage,\n ProviderResponse,\n StreamChunk,\n Provider,\n} from '@axlsdk/axl';\n\nfunction randomAlphanumeric(length: number): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n result += chars[Math.floor(Math.random() * chars.length)];\n }\n return result;\n}\n\nfunction generateFromSchema(schema: unknown): unknown {\n if (schema instanceof z.ZodString) return randomAlphanumeric(8 + Math.floor(Math.random() * 13));\n if (schema instanceof z.ZodNumber) {\n const min = Number.isFinite(schema.minValue) ? schema.minValue! : 0;\n const max = Number.isFinite(schema.maxValue) ? schema.maxValue! : 100;\n return min + Math.random() * (max - min);\n }\n if (schema instanceof z.ZodBoolean) return Math.random() < 0.5;\n if (schema instanceof z.ZodArray) {\n const count = 1 + Math.floor(Math.random() * 3);\n return Array.from({ length: count }, () => generateFromSchema(schema.element));\n }\n if (schema instanceof z.ZodObject) {\n const obj: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema.shape)) obj[key] = generateFromSchema(value);\n return obj;\n }\n if (schema instanceof z.ZodOptional) {\n if (Math.random() < 0.5) return undefined;\n return generateFromSchema(schema.unwrap());\n }\n if (schema instanceof z.ZodDefault) return schema.parse(undefined);\n if (schema instanceof z.ZodEnum) {\n const values = schema.options;\n return values.length > 0 ? values[Math.floor(Math.random() * values.length)] : '';\n }\n if (schema instanceof z.ZodLiteral) return [...schema.values][0];\n if (schema instanceof z.ZodNullable) return null;\n if (schema instanceof z.ZodUnion) {\n const options = schema.options;\n return generateFromSchema(options[Math.floor(Math.random() * options.length)]);\n }\n return {};\n}\n\nexport class MockProvider implements Provider {\n readonly name = 'mock';\n private _calls: { messages: ChatMessage[]; options: ChatOptions }[] = [];\n /** Per-call optional chunk arrays, set by `sequence()` / `chunked()` so\n * `stream()` can yield one `text_delta` per chunk. Per-call indexed\n * alongside the response sequence. */\n private chunkSequence?: Array<string[] | undefined>;\n /** Optional ms delay between successive chunks during `stream()`. Useful\n * for dev fixtures that need to demonstrate streaming UX visually\n * (without a delay, mock chunks fire synchronously and complete in\n * microseconds — invisible to a human watching). Default 0 keeps\n * every existing test fast.\n *\n * Set via `provider.chunkDelayMs = 50` after construction. Not part of\n * any factory's surface to keep the test API minimal. */\n chunkDelayMs = 0;\n\n private constructor(\n private responseFn: (\n messages: ChatMessage[],\n callIndex: number,\n ) => ProviderResponse | Promise<ProviderResponse>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse> {\n this._calls.push({ messages, options });\n return await this.responseFn(messages, this._calls.length - 1);\n }\n\n async *stream(messages: ChatMessage[], options: ChatOptions): AsyncGenerator<StreamChunk> {\n const callIndex = this._calls.length;\n const response = await this.chat(messages, options);\n const chunks = this.chunkSequence?.[callIndex];\n if (chunks && chunks.length > 0) {\n // Sanity guard — if a caller passes chunks AND content, they MUST\n // match. Otherwise tests pass while the real prod content silently\n // diverges from what the streaming path observes.\n const joined = chunks.join('');\n if (joined !== response.content) {\n throw new Error(\n `MockProvider.stream: chunks.join('') !== content. ` +\n `chunks=\"${joined}\" content=\"${response.content}\"`,\n );\n }\n for (let i = 0; i < chunks.length; i++) {\n yield { type: 'text_delta', content: chunks[i] };\n if (this.chunkDelayMs > 0 && i < chunks.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, this.chunkDelayMs));\n }\n }\n } else if (response.content) {\n yield { type: 'text_delta', content: response.content };\n }\n if (response.tool_calls) {\n for (const tc of response.tool_calls) {\n yield {\n type: 'tool_call_delta',\n id: tc.id,\n name: tc.function.name,\n arguments: tc.function.arguments,\n };\n }\n }\n yield { type: 'done', usage: response.usage, providerMetadata: response.providerMetadata };\n }\n\n static sequence(\n responses: Array<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n /** When set, `stream()` yields one `text_delta` per entry instead\n * of one big delta with the full content. Use to exercise\n * partial-JSON parsing, structural-boundary throttling, and\n * cross-attempt token retention in tests. Must satisfy\n * `chunks.join('') === content`. */\n chunks?: string[];\n }>,\n ): MockProvider {\n const provider = new MockProvider((_messages, callIndex) => {\n if (callIndex >= responses.length) {\n throw new Error(\n `MockProvider.sequence: no response for call index ${callIndex}. Only ${responses.length} responses defined.`,\n );\n }\n const resp = responses[callIndex];\n return {\n content: resp.content,\n tool_calls: resp.tool_calls,\n providerMetadata: resp.providerMetadata,\n usage: resp.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: resp.cost ?? 0,\n };\n });\n provider.chunkSequence = responses.map((r) => r.chunks);\n return provider;\n }\n\n /**\n * Convenience: build a `sequence()` from plain content strings, splitting\n * each one into fixed-size chunks for the streaming path. Default\n * `chunkSize` is 4 chars (≈1 token).\n */\n static chunked(contents: string[], chunkSize = 4): MockProvider {\n const responses = contents.map((content) => {\n const chunks: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunks.push(content.slice(i, i + chunkSize));\n }\n return { content, chunks };\n });\n return MockProvider.sequence(responses);\n }\n\n static echo(): MockProvider {\n return new MockProvider((messages) => {\n const lastUser = [...messages].reverse().find((m) => m.role === 'user');\n return {\n content: lastUser?.content ?? '',\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n };\n });\n }\n\n static json(schema: unknown): MockProvider {\n return new MockProvider(() => ({\n content: JSON.stringify(generateFromSchema(schema)),\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n }));\n }\n\n static replay(source: string | ProviderResponse[]): MockProvider {\n const data =\n typeof source === 'string'\n ? (JSON.parse(readFileSync(source, 'utf-8')) as ProviderResponse[])\n : source;\n return new MockProvider((_messages, callIndex) => {\n if (callIndex >= data.length) {\n throw new Error(`MockProvider.replay: no recorded response for call index ${callIndex}`);\n }\n return data[callIndex];\n });\n }\n\n static fn(\n handler: (\n messages: ChatMessage[],\n callIndex: number,\n ) =>\n | {\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }\n | Promise<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }>,\n ): MockProvider {\n return new MockProvider(async (messages, callIndex) => {\n const result = await handler(messages, callIndex);\n return {\n ...result,\n usage: result.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: result.cost ?? 0,\n };\n });\n }\n}\n","export class MockTool {\n private _calls: { input: unknown }[] = [];\n\n constructor(\n readonly name: string,\n private handler: (input: unknown) => unknown | Promise<unknown>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async execute(input: unknown): Promise<unknown> {\n this._calls.push({ input });\n return this.handler(input);\n }\n\n static create(name: string, handler: (input: unknown) => unknown | Promise<unknown>): MockTool {\n return new MockTool(name, handler);\n }\n}\n","import { MockProvider } from './mock-provider.js';\nimport { MockTool } from './mock-tool.js';\nimport type {\n AxlEvent,\n AwaitHumanOptions,\n HumanDecision,\n ProviderResponse,\n AgentCallInfo,\n AxlConfig,\n AnyWorkflow,\n} from '@axlsdk/axl';\nimport {\n WorkflowContext,\n MemoryStore,\n ProviderRegistry,\n eventCostContribution,\n isUnpricedLeaf,\n} from '@axlsdk/axl';\nimport type { EventStreamOptions, WorkflowContextInit } from '@axlsdk/axl';\n\n// `AnyWorkflow` is a re-exported alias for `Workflow<any, any>` — see\n// the JSDoc on `AnyWorkflow` in `@axlsdk/axl/workflow.ts`. The bivariant\n// `any` is load-bearing for satisfying narrowly-typed handlers (e.g., a\n// `Workflow<TInput=MsgType>` whose handler takes `WorkflowContext<MsgType>`).\n\nexport type RecordedToolCall = { name: string; args: unknown; result: unknown };\nexport type RecordedAgentCall = Partial<AgentCallInfo> &\n Pick<AgentCallInfo, 'agent' | 'prompt' | 'response'>;\nexport type RecordedStep = { step: number; type: string; data: unknown };\n\nfunction generateExecutionId(): string {\n return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport type AxlTestRuntimeOptions = {\n record?: string;\n humanDecisions?: (options: AwaitHumanOptions) => HumanDecision;\n /** AxlConfig for the test runtime. Controls trace level/redact, context management, etc.\n * Defaults to `{}`. Set `{ trace: { level: 'full' } }` to verify verbose-mode snapshots\n * in tests, or `{ trace: { redact: true } }` to verify redaction policy in tests. */\n config?: AxlConfig;\n};\n\nexport class AxlTestRuntime {\n private workflows = new Map<string, AnyWorkflow>();\n private mockProviders = new Map<string, MockProvider>();\n private mockToolMap = new Map<string, MockTool>();\n private _toolCalls: RecordedToolCall[] = [];\n private _agentCalls: RecordedAgentCall[] = [];\n private _steps: RecordedStep[] = [];\n private _traceLog: AxlEvent[] = [];\n private _totalCost = 0;\n private _unpriced = false;\n private _stepCounter = 0;\n private recordPath?: string;\n private recorded: ProviderResponse[] = [];\n private _executionId: string = generateExecutionId();\n private _humanDecisionHandler?: (options: AwaitHumanOptions) => HumanDecision;\n private _config: AxlConfig;\n\n constructor(options?: AxlTestRuntimeOptions) {\n this.recordPath = options?.record;\n this._humanDecisionHandler = options?.humanDecisions;\n this._config = options?.config ?? {};\n }\n\n register(workflow: AnyWorkflow): void {\n this.workflows.set(workflow.name, workflow);\n }\n\n mockProvider(name: string, provider: MockProvider): void {\n this.mockProviders.set(name, provider);\n }\n\n mockTool<TInput = unknown>(\n name: string,\n handler: (input: TInput) => unknown | Promise<unknown>,\n ): void {\n // The MockTool registry is type-erased (`(input: unknown) => unknown`)\n // because the runtime can't statically know each tool's input shape; tests\n // opt in to a typed input via the generic, and we cast on storage.\n this.mockToolMap.set(name, MockTool.create(name, handler as (input: unknown) => unknown));\n }\n\n async execute(\n workflowName: string,\n input: unknown,\n options?: { metadata?: Record<string, unknown>; events?: EventStreamOptions },\n ): Promise<unknown> {\n const workflow = this.workflows.get(workflowName);\n if (!workflow) throw new Error(`Workflow \"${workflowName}\" not registered`);\n\n // Reset state between executions\n this._toolCalls = [];\n this._agentCalls = [];\n this._steps = [];\n this._traceLog = [];\n this._totalCost = 0;\n this._unpriced = false;\n this._stepCounter = 0;\n this.recorded = [];\n this._executionId = generateExecutionId();\n\n const validated = workflow.inputSchema.parse(input);\n\n // Build ProviderRegistry from registered mock providers\n const registry = new ProviderRegistry();\n registry.clearFactories(); // Remove built-in factories; only use explicit mocks\n for (const [name, provider] of this.mockProviders) {\n registry.registerInstance(name, provider);\n }\n // Fallback resolution: 'default' key or single-provider\n const defaultProvider = this.mockProviders.get('default');\n if (defaultProvider) {\n registry.setFallback(defaultProvider);\n } else if (this.mockProviders.size === 1) {\n const onlyProvider = this.mockProviders.values().next().value!;\n registry.setFallback(onlyProvider);\n }\n\n // Build toolOverrides from mockToolMap\n const toolOverrides = new Map<string, (args: unknown) => Promise<unknown>>();\n for (const [name, mockTool] of this.mockToolMap) {\n toolOverrides.set(name, async (args: unknown) => {\n return mockTool.execute(args);\n });\n }\n\n // Previously `_recordStep('workflow_start', ...)` was called here with\n // raw `{ workflow, input }` data, AND `ctx._emitWorkflowStart` below\n // pushed through the onTrace handler (which also bumps `_stepCounter`\n // and appends to `_steps`). Result: two entries in `steps()` per\n // workflow_start, with the first bypassing `config.trace.redact`.\n // The `_recordStep` call is removed — onTrace is the single source\n // of truth for `_steps`, matching the behavior of every other event\n // type in the test runtime. Reviewer architecture §5.\n\n const init: WorkflowContextInit = {\n input: validated,\n executionId: this._executionId,\n metadata: options?.metadata,\n // Thread the constructor-provided config so tests can exercise\n // trace.level === 'full' and trace.redact behavior end-to-end.\n config: this._config,\n // Thread `events` config so tests can exercise the queue cap and\n // overflow policy on `ctx.events`. Mirrors the production runtime.\n eventStreamOptions: options?.events,\n // Production runtime threads this (see `runtime.ts:594` in\n // `execute()` and `:765` in `stream()`). `emitEvent` auto-stamps\n // `event.workflow` from it, so consumers grouping by workflow\n // (Cost Dashboard `byWorkflow`, `trackExecution.metadata.workflows`,\n // any eval runner) see the same attribution in tests and prod.\n workflowName,\n providerRegistry: registry,\n stateStore: new MemoryStore(),\n toolOverrides: toolOverrides.size > 0 ? toolOverrides : undefined,\n awaitHumanHandler: this._humanDecisionHandler\n ? (opts: AwaitHumanOptions) => this._humanDecisionHandler!(opts)\n : undefined,\n onTrace: (event: AxlEvent) => {\n this._traceLog.push(event);\n this._stepCounter++;\n this._steps.push({\n step: this._stepCounter,\n type: event.type,\n data: (event as { data?: unknown }).data,\n });\n\n if (event.type === 'tool_call_end' && event.data) {\n const { args, result } = event.data as { args: unknown; result: unknown };\n // `tool: string` is required on `tool_call_end`; narrowing makes it non-null.\n this._toolCalls.push({ name: event.tool, args, result });\n }\n\n // Single-source-of-truth cost accumulator. Skips ask_end\n // rollups (decision 10) and guards NaN/Infinity.\n this._totalCost += eventCostContribution(event);\n // Honest aggregate: one unpriced leaf makes `totalCost()` a lower bound.\n if (isUnpricedLeaf(event)) this._unpriced = true;\n\n // Track provider responses for recording\n if (event.type === 'agent_call_end' && event.data) {\n const { response } = event.data as { prompt?: string; response?: string };\n if (response !== undefined) {\n this.recorded.push({\n content: response,\n usage: event.tokens\n ? {\n prompt_tokens: event.tokens.input ?? 0,\n completion_tokens: event.tokens.output ?? 0,\n total_tokens:\n (event.tokens.input ?? 0) +\n (event.tokens.output ?? 0) +\n (event.tokens.reasoning ?? 0),\n ...(event.tokens.reasoning !== undefined\n ? { reasoning_tokens: event.tokens.reasoning }\n : {}),\n }\n : { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n ...(event.cost !== undefined ? { cost: event.cost } : {}),\n });\n }\n }\n },\n onAgentCallComplete: (call: AgentCallInfo) => {\n this._agentCalls.push(call);\n },\n };\n\n const ctx = new WorkflowContext(init);\n\n // Emit workflow_start through the same pipeline production uses,\n // AFTER ctx exists so `emitEvent`'s redact/step/timestamp handling\n // applies. Previously emitted via `_pushTrace` which bypassed\n // redaction — `config: { trace: { redact: true } }` on the test\n // runtime would scrub intermediate events but leak `input`/`result`\n // through the synthesized workflow_start/end.\n const startedAt = Date.now();\n ctx._emitWorkflowStart(validated);\n try {\n const result = await workflow.handler(ctx);\n\n // Validate output schema if defined\n if (workflow.outputSchema) workflow.outputSchema.parse(result);\n\n ctx._emitWorkflowEnd({\n status: 'completed',\n duration: Date.now() - startedAt,\n result,\n });\n // `_steps` is populated by onTrace for every event (including\n // workflow_end); no separate `_recordStep` call needed — that\n // would double-count.\n\n if (this.recordPath) await this._writeRecording();\n return result;\n } catch (err) {\n // Parity with production: failed workflows get a terminal\n // `workflow_end(status: 'failed')` event so consumers counting\n // workflow_start ↔ workflow_end pairs never see an unclosed one.\n // `aborted` flag mirrors the runtime.ts AbortError detection.\n const aborted =\n typeof err === 'object' &&\n err !== null &&\n (err as { name?: unknown }).name === 'AbortError';\n ctx._emitWorkflowEnd({\n status: 'failed',\n duration: Date.now() - startedAt,\n error: err instanceof Error ? err.message : String(err),\n ...(aborted ? { aborted: true } : {}),\n });\n // `_steps` populated by onTrace (see comment on the success path).\n throw err;\n }\n }\n\n toolCalls(name?: string): RecordedToolCall[] {\n return name ? this._toolCalls.filter((c) => c.name === name) : this._toolCalls;\n }\n\n agentCalls(name?: string): RecordedAgentCall[] {\n return name ? this._agentCalls.filter((c) => c.agent === name) : this._agentCalls;\n }\n\n totalCost(): number {\n return this._totalCost;\n }\n /** True when any cost-bearing call was unpriced — `totalCost()` is then a\n * LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */\n unpriced(): boolean {\n return this._unpriced;\n }\n steps(): RecordedStep[] {\n return this._steps;\n }\n traceLog(): AxlEvent[] {\n return this._traceLog;\n }\n\n private async _writeRecording(): Promise<void> {\n if (!this.recordPath) return;\n const { writeFile, mkdir } = await import('node:fs/promises');\n const { dirname } = await import('node:path');\n await mkdir(dirname(this.recordPath), { recursive: true });\n await writeFile(this.recordPath, JSON.stringify(this.recorded, null, 2), 'utf8');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/mock-provider.ts","../src/mock-tool.ts","../src/test-runtime.ts"],"sourcesContent":["export { AxlTestRuntime } from './test-runtime.js';\nexport type { RecordedToolCall, RecordedAgentCall, RecordedStep } from './test-runtime.js';\nexport { MockProvider } from './mock-provider.js';\nexport { MockTool } from './mock-tool.js';\n","import { readFileSync } from 'node:fs';\nimport { z } from 'zod';\nimport type {\n ChatMessage,\n ChatOptions,\n ToolCallMessage,\n ProviderResponse,\n StreamChunk,\n Provider,\n} from '@axlsdk/axl';\n\nfunction randomAlphanumeric(length: number): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n result += chars[Math.floor(Math.random() * chars.length)];\n }\n return result;\n}\n\nfunction generateFromSchema(schema: unknown): unknown {\n if (schema instanceof z.ZodString) return randomAlphanumeric(8 + Math.floor(Math.random() * 13));\n if (schema instanceof z.ZodNumber) {\n const min = Number.isFinite(schema.minValue) ? schema.minValue! : 0;\n const max = Number.isFinite(schema.maxValue) ? schema.maxValue! : 100;\n return min + Math.random() * (max - min);\n }\n if (schema instanceof z.ZodBoolean) return Math.random() < 0.5;\n if (schema instanceof z.ZodArray) {\n const count = 1 + Math.floor(Math.random() * 3);\n return Array.from({ length: count }, () => generateFromSchema(schema.element));\n }\n if (schema instanceof z.ZodObject) {\n const obj: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema.shape)) obj[key] = generateFromSchema(value);\n return obj;\n }\n if (schema instanceof z.ZodOptional) {\n if (Math.random() < 0.5) return undefined;\n return generateFromSchema(schema.unwrap());\n }\n if (schema instanceof z.ZodDefault) return schema.parse(undefined);\n if (schema instanceof z.ZodEnum) {\n const values = schema.options;\n return values.length > 0 ? values[Math.floor(Math.random() * values.length)] : '';\n }\n if (schema instanceof z.ZodLiteral) return [...schema.values][0];\n if (schema instanceof z.ZodNullable) return null;\n if (schema instanceof z.ZodUnion) {\n const options = schema.options;\n return generateFromSchema(options[Math.floor(Math.random() * options.length)]);\n }\n return {};\n}\n\nexport class MockProvider implements Provider {\n readonly name = 'mock';\n private _calls: { messages: ChatMessage[]; options: ChatOptions }[] = [];\n /** Per-call optional chunk arrays, set by `sequence()` / `chunked()` so\n * `stream()` can yield one `text_delta` per chunk. Per-call indexed\n * alongside the response sequence. */\n private chunkSequence?: Array<string[] | undefined>;\n /** Optional ms delay between successive chunks during `stream()`. Useful\n * for dev fixtures that need to demonstrate streaming UX visually\n * (without a delay, mock chunks fire synchronously and complete in\n * microseconds — invisible to a human watching). Default 0 keeps\n * every existing test fast.\n *\n * Set via `provider.chunkDelayMs = 50` after construction. Not part of\n * any factory's surface to keep the test API minimal. */\n chunkDelayMs = 0;\n\n private constructor(\n private responseFn: (\n messages: ChatMessage[],\n callIndex: number,\n ) => ProviderResponse | Promise<ProviderResponse>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse> {\n this._calls.push({ messages, options });\n return await this.responseFn(messages, this._calls.length - 1);\n }\n\n async *stream(messages: ChatMessage[], options: ChatOptions): AsyncGenerator<StreamChunk> {\n const callIndex = this._calls.length;\n const response = await this.chat(messages, options);\n const chunks = this.chunkSequence?.[callIndex];\n if (chunks && chunks.length > 0) {\n // Sanity guard — if a caller passes chunks AND content, they MUST\n // match. Otherwise tests pass while the real prod content silently\n // diverges from what the streaming path observes.\n const joined = chunks.join('');\n if (joined !== response.content) {\n throw new Error(\n `MockProvider.stream: chunks.join('') !== content. ` +\n `chunks=\"${joined}\" content=\"${response.content}\"`,\n );\n }\n for (let i = 0; i < chunks.length; i++) {\n yield { type: 'text_delta', content: chunks[i] };\n if (this.chunkDelayMs > 0 && i < chunks.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, this.chunkDelayMs));\n }\n }\n } else if (response.content) {\n yield { type: 'text_delta', content: response.content };\n }\n if (response.tool_calls) {\n for (const tc of response.tool_calls) {\n yield {\n type: 'tool_call_delta',\n id: tc.id,\n name: tc.function.name,\n arguments: tc.function.arguments,\n };\n }\n }\n yield { type: 'done', usage: response.usage, providerMetadata: response.providerMetadata };\n }\n\n static sequence(\n responses: Array<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n /** When set, `stream()` yields one `text_delta` per entry instead\n * of one big delta with the full content. Use to exercise\n * partial-JSON parsing, structural-boundary throttling, and\n * cross-attempt token retention in tests. Must satisfy\n * `chunks.join('') === content`. */\n chunks?: string[];\n }>,\n ): MockProvider {\n const provider = new MockProvider((_messages, callIndex) => {\n if (callIndex >= responses.length) {\n throw new Error(\n `MockProvider.sequence: no response for call index ${callIndex}. Only ${responses.length} responses defined.`,\n );\n }\n const resp = responses[callIndex];\n return {\n content: resp.content,\n tool_calls: resp.tool_calls,\n providerMetadata: resp.providerMetadata,\n usage: resp.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: resp.cost ?? 0,\n };\n });\n provider.chunkSequence = responses.map((r) => r.chunks);\n return provider;\n }\n\n /**\n * Convenience: build a `sequence()` from plain content strings, splitting\n * each one into fixed-size chunks for the streaming path. Default\n * `chunkSize` is 4 chars (≈1 token).\n */\n static chunked(contents: string[], chunkSize = 4): MockProvider {\n const responses = contents.map((content) => {\n const chunks: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunks.push(content.slice(i, i + chunkSize));\n }\n return { content, chunks };\n });\n return MockProvider.sequence(responses);\n }\n\n static echo(): MockProvider {\n return new MockProvider((messages) => {\n const lastUser = [...messages].reverse().find((m) => m.role === 'user');\n return {\n content: lastUser?.content ?? '',\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n };\n });\n }\n\n static json(schema: unknown): MockProvider {\n return new MockProvider(() => ({\n content: JSON.stringify(generateFromSchema(schema)),\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n }));\n }\n\n static replay(source: string | ProviderResponse[]): MockProvider {\n const data =\n typeof source === 'string'\n ? (JSON.parse(readFileSync(source, 'utf-8')) as ProviderResponse[])\n : source;\n return new MockProvider((_messages, callIndex) => {\n if (callIndex >= data.length) {\n throw new Error(`MockProvider.replay: no recorded response for call index ${callIndex}`);\n }\n return data[callIndex];\n });\n }\n\n static fn(\n handler: (\n messages: ChatMessage[],\n callIndex: number,\n ) =>\n | {\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }\n | Promise<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }>,\n ): MockProvider {\n return new MockProvider(async (messages, callIndex) => {\n const result = await handler(messages, callIndex);\n return {\n ...result,\n usage: result.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: result.cost ?? 0,\n };\n });\n }\n}\n","export class MockTool {\n private _calls: { input: unknown }[] = [];\n\n constructor(\n readonly name: string,\n private handler: (input: unknown) => unknown | Promise<unknown>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async execute(input: unknown): Promise<unknown> {\n this._calls.push({ input });\n return this.handler(input);\n }\n\n static create(name: string, handler: (input: unknown) => unknown | Promise<unknown>): MockTool {\n return new MockTool(name, handler);\n }\n}\n","import { MockProvider } from './mock-provider.js';\nimport { MockTool } from './mock-tool.js';\nimport type {\n AxlEvent,\n AwaitHumanOptions,\n HumanDecision,\n ProviderResponse,\n AgentCallInfo,\n AxlConfig,\n AnyWorkflow,\n ToolCallOutcome,\n ObservationStatus,\n} from '@axlsdk/axl';\nimport {\n WorkflowContext,\n MemoryStore,\n ProviderRegistry,\n eventCostContribution,\n isUnpricedLeaf,\n} from '@axlsdk/axl';\nimport type { EventStreamOptions, WorkflowContextInit } from '@axlsdk/axl';\n\n// `AnyWorkflow` is a re-exported alias for `Workflow<any, any>` — see\n// the JSDoc on `AnyWorkflow` in `@axlsdk/axl/workflow.ts`. The bivariant\n// `any` is load-bearing for satisfying narrowly-typed handlers (e.g., a\n// `Workflow<TInput=MsgType>` whose handler takes `WorkflowContext<MsgType>`).\n\n/** One accepted provider-issued tool invocation, recorded from its terminal v2 event.\n *\n * Narrow `outcome.status` before reading status-specific fields. Correlation uses\n * `(executionId, askId, callId)`; `name` is the canonical tool name.\n */\nexport type RecordedToolCall = {\n name: string;\n executionId: string;\n askId: string;\n callId: string;\n args: unknown;\n requestedTool?: string;\n outcome: ToolCallOutcome;\n};\nexport type RecordedAgentCall = Partial<AgentCallInfo> &\n Pick<AgentCallInfo, 'agent' | 'prompt' | 'response'>;\nexport type RecordedStep = { step: number; type: string; data: unknown };\n\nfunction generateExecutionId(): string {\n return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction preserveDrainCause(error: Error, cause: unknown): Error {\n if (cause === undefined || cause === error || 'cause' in error) return error;\n Object.defineProperty(error, 'cause', {\n configurable: true,\n enumerable: false,\n value: cause,\n });\n return error;\n}\n\nexport type AxlTestRuntimeOptions = {\n record?: string;\n humanDecisions?: (options: AwaitHumanOptions) => HumanDecision;\n /** AxlConfig for the test runtime. Controls trace level/redact, context management, etc.\n * Defaults to `{}`. Set `{ trace: { level: 'full' } }` to verify verbose-mode snapshots\n * in tests, or `{ trace: { redact: true } }` to verify redaction policy in tests. */\n config?: AxlConfig;\n};\n\nexport class AxlTestRuntime {\n private workflows = new Map<string, AnyWorkflow>();\n private mockProviders = new Map<string, MockProvider>();\n private mockToolMap = new Map<string, MockTool>();\n private _toolCalls: RecordedToolCall[] = [];\n private _agentCalls: RecordedAgentCall[] = [];\n private _steps: RecordedStep[] = [];\n private _traceLog: AxlEvent[] = [];\n private _totalCost = 0;\n private _unpriced = false;\n private _stepCounter = 0;\n private recordPath?: string;\n private recorded: ProviderResponse[] = [];\n private _executionId: string = generateExecutionId();\n private _humanDecisionHandler?: (options: AwaitHumanOptions) => HumanDecision;\n private _config: AxlConfig;\n\n constructor(options?: AxlTestRuntimeOptions) {\n this.recordPath = options?.record;\n this._humanDecisionHandler = options?.humanDecisions;\n this._config = options?.config ?? {};\n }\n\n register(workflow: AnyWorkflow): void {\n this.workflows.set(workflow.name, workflow);\n }\n\n mockProvider(name: string, provider: MockProvider): void {\n this.mockProviders.set(name, provider);\n }\n\n mockTool<TInput = unknown>(\n name: string,\n handler: (input: TInput) => unknown | Promise<unknown>,\n ): void {\n // The MockTool registry is type-erased (`(input: unknown) => unknown`)\n // because the runtime can't statically know each tool's input shape; tests\n // opt in to a typed input via the generic, and we cast on storage.\n this.mockToolMap.set(name, MockTool.create(name, handler as (input: unknown) => unknown));\n }\n\n async execute(\n workflowName: string,\n input: unknown,\n options?: {\n metadata?: Record<string, unknown>;\n events?: EventStreamOptions;\n branchDrainTimeoutMs?: number;\n },\n ): Promise<unknown> {\n const workflow = this.workflows.get(workflowName);\n if (!workflow) throw new Error(`Workflow \"${workflowName}\" not registered`);\n\n // Reset state between executions\n this._toolCalls = [];\n this._agentCalls = [];\n this._steps = [];\n this._traceLog = [];\n this._totalCost = 0;\n this._unpriced = false;\n this._stepCounter = 0;\n this.recorded = [];\n this._executionId = generateExecutionId();\n\n const validated = workflow.inputSchema.parse(input);\n\n // Build ProviderRegistry from registered mock providers\n const registry = new ProviderRegistry();\n registry.clearFactories(); // Remove built-in factories; only use explicit mocks\n for (const [name, provider] of this.mockProviders) {\n registry.registerInstance(name, provider);\n }\n // Fallback resolution: 'default' key or single-provider\n const defaultProvider = this.mockProviders.get('default');\n if (defaultProvider) {\n registry.setFallback(defaultProvider);\n } else if (this.mockProviders.size === 1) {\n const onlyProvider = this.mockProviders.values().next().value!;\n registry.setFallback(onlyProvider);\n }\n\n // Build toolOverrides from mockToolMap\n const toolOverrides = new Map<string, (args: unknown) => Promise<unknown>>();\n for (const [name, mockTool] of this.mockToolMap) {\n toolOverrides.set(name, async (args: unknown) => {\n return mockTool.execute(args);\n });\n }\n\n // Previously `_recordStep('workflow_start', ...)` was called here with\n // raw `{ workflow, input }` data, AND `ctx._emitWorkflowStart` below\n // pushed through the onTrace handler (which also bumps `_stepCounter`\n // and appends to `_steps`). Result: two entries in `steps()` per\n // workflow_start, with the first bypassing `config.trace.redact`.\n // The `_recordStep` call is removed — onTrace is the single source\n // of truth for `_steps`, matching the behavior of every other event\n // type in the test runtime. Reviewer architecture §5.\n\n const init: WorkflowContextInit = {\n input: validated,\n executionId: this._executionId,\n metadata: options?.metadata,\n // Thread the constructor-provided config so tests can exercise\n // trace.level === 'full' and trace.redact behavior end-to-end.\n config: this._config,\n // Thread `events` config so tests can exercise the queue cap and\n // overflow policy on `ctx.events`. Mirrors the production runtime.\n eventStreamOptions: options?.events,\n branchDrainTimeoutMs: options?.branchDrainTimeoutMs,\n // Production runtime threads this (see `runtime.ts:594` in\n // `execute()` and `:765` in `stream()`). `emitEvent` auto-stamps\n // `event.workflow` from it, so consumers grouping by workflow\n // (Cost Dashboard `byWorkflow`, `trackExecution.metadata.workflows`,\n // any eval runner) see the same attribution in tests and prod.\n workflowName,\n providerRegistry: registry,\n stateStore: new MemoryStore(),\n toolOverrides: toolOverrides.size > 0 ? toolOverrides : undefined,\n awaitHumanHandler: this._humanDecisionHandler\n ? (opts: AwaitHumanOptions) => this._humanDecisionHandler!(opts)\n : undefined,\n onTrace: (event: AxlEvent) => {\n this._traceLog.push(event);\n this._stepCounter++;\n this._steps.push({\n step: this._stepCounter,\n type: event.type,\n data: (event as { data?: unknown }).data,\n });\n\n if (event.type === 'tool_call_end') {\n const { args, requestedTool, outcome } = event.data;\n this._toolCalls.push({\n name: event.tool,\n executionId: event.executionId,\n askId: event.askId,\n callId: event.callId,\n args,\n ...(requestedTool !== undefined ? { requestedTool } : {}),\n outcome,\n });\n }\n\n // Single-source-of-truth cost accumulator. Skips ask_end\n // rollups (decision 10) and guards NaN/Infinity.\n this._totalCost += eventCostContribution(event);\n // Honest aggregate: one unpriced leaf makes `totalCost()` a lower bound.\n if (isUnpricedLeaf(event)) this._unpriced = true;\n\n // Track provider responses for recording\n if (event.type === 'agent_call_end' && event.data) {\n const { response } = event.data as { prompt?: string; response?: string };\n if (response !== undefined) {\n this.recorded.push({\n content: response,\n usage: event.tokens\n ? {\n prompt_tokens: event.tokens.input ?? 0,\n completion_tokens: event.tokens.output ?? 0,\n total_tokens:\n (event.tokens.input ?? 0) +\n (event.tokens.output ?? 0) +\n (event.tokens.reasoning ?? 0),\n ...(event.tokens.reasoning !== undefined\n ? { reasoning_tokens: event.tokens.reasoning }\n : {}),\n }\n : { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n ...(event.cost !== undefined ? { cost: event.cost } : {}),\n });\n }\n }\n },\n onAgentCallComplete: (call: AgentCallInfo) => {\n this._agentCalls.push(call);\n },\n };\n\n const ctx = new WorkflowContext(init);\n\n // Emit workflow_start through the same pipeline production uses,\n // AFTER ctx exists so `emitEvent`'s redact/step/timestamp handling\n // applies. Previously emitted via `_pushTrace` which bypassed\n // redaction — `config: { trace: { redact: true } }` on the test\n // runtime would scrub intermediate events but leak `input`/`result`\n // through the synthesized workflow_start/end.\n const startedAt = Date.now();\n ctx._emitWorkflowStart(validated);\n try {\n const result = await workflow.handler(ctx);\n const observation = await ctx._drainBranchWork();\n\n // Validate output schema if defined\n if (workflow.outputSchema) workflow.outputSchema.parse(result);\n\n ctx._emitWorkflowEnd({\n status: 'completed',\n duration: Date.now() - startedAt,\n result,\n observation,\n });\n // `_steps` is populated by onTrace for every event (including\n // workflow_end); no separate `_recordStep` call needed — that\n // would double-count.\n\n if (this.recordPath) await this._writeRecording();\n return result;\n } catch (err) {\n let workflowError = err;\n let observation: ObservationStatus = { complete: true };\n try {\n observation = await ctx._drainBranchWork();\n } catch (drainError) {\n workflowError =\n drainError instanceof Error ? preserveDrainCause(drainError, workflowError) : drainError;\n }\n // Parity with production: failed workflows get a terminal\n // `workflow_end(status: 'failed')` event so consumers counting\n // workflow_start ↔ workflow_end pairs never see an unclosed one.\n // `aborted` flag mirrors the runtime.ts AbortError detection.\n const aborted =\n typeof workflowError === 'object' &&\n workflowError !== null &&\n (workflowError as { name?: unknown }).name === 'AbortError';\n ctx._emitWorkflowEnd({\n status: 'failed',\n duration: Date.now() - startedAt,\n error: workflowError instanceof Error ? workflowError.message : String(workflowError),\n ...(aborted ? { aborted: true } : {}),\n observation,\n });\n // `_steps` populated by onTrace (see comment on the success path).\n throw workflowError;\n }\n }\n\n toolCalls(name?: string): RecordedToolCall[] {\n return name ? this._toolCalls.filter((c) => c.name === name) : this._toolCalls;\n }\n\n agentCalls(name?: string): RecordedAgentCall[] {\n return name ? this._agentCalls.filter((c) => c.agent === name) : this._agentCalls;\n }\n\n totalCost(): number {\n return this._totalCost;\n }\n /** True when any cost-bearing call was unpriced — `totalCost()` is then a\n * LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */\n unpriced(): boolean {\n return this._unpriced;\n }\n steps(): RecordedStep[] {\n return this._steps;\n }\n traceLog(): AxlEvent[] {\n return this._traceLog;\n }\n\n private async _writeRecording(): Promise<void> {\n if (!this.recordPath) return;\n const { writeFile, mkdir } = await import('node:fs/promises');\n const { dirname } = await import('node:path');\n await mkdir(dirname(this.recordPath), { recursive: true });\n await writeFile(this.recordPath, JSON.stringify(this.recorded, null, 2), 'utf8');\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAA6B;AAC7B,iBAAkB;AAUlB,SAAS,mBAAmB,QAAwB;AAClD,QAAM,QAAQ;AACd,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,cAAU,MAAM,KAAK,MAAM,KAAK,OAAO,IAAI,MAAM,MAAM,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,QAA0B;AACpD,MAAI,kBAAkB,aAAE,UAAW,QAAO,mBAAmB,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,CAAC;AAC/F,MAAI,kBAAkB,aAAE,WAAW;AACjC,UAAM,MAAM,OAAO,SAAS,OAAO,QAAQ,IAAI,OAAO,WAAY;AAClE,UAAM,MAAM,OAAO,SAAS,OAAO,QAAQ,IAAI,OAAO,WAAY;AAClE,WAAO,MAAM,KAAK,OAAO,KAAK,MAAM;AAAA,EACtC;AACA,MAAI,kBAAkB,aAAE,WAAY,QAAO,KAAK,OAAO,IAAI;AAC3D,MAAI,kBAAkB,aAAE,UAAU;AAChC,UAAM,QAAQ,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,CAAC;AAC9C,WAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,MAAM,mBAAmB,OAAO,OAAO,CAAC;AAAA,EAC/E;AACA,MAAI,kBAAkB,aAAE,WAAW;AACjC,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,KAAK,EAAG,KAAI,GAAG,IAAI,mBAAmB,KAAK;AAC5F,WAAO;AAAA,EACT;AACA,MAAI,kBAAkB,aAAE,aAAa;AACnC,QAAI,KAAK,OAAO,IAAI,IAAK,QAAO;AAChC,WAAO,mBAAmB,OAAO,OAAO,CAAC;AAAA,EAC3C;AACA,MAAI,kBAAkB,aAAE,WAAY,QAAO,OAAO,MAAM,MAAS;AACjE,MAAI,kBAAkB,aAAE,SAAS;AAC/B,UAAM,SAAS,OAAO;AACtB,WAAO,OAAO,SAAS,IAAI,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,OAAO,MAAM,CAAC,IAAI;AAAA,EACjF;AACA,MAAI,kBAAkB,aAAE,WAAY,QAAO,CAAC,GAAG,OAAO,MAAM,EAAE,CAAC;AAC/D,MAAI,kBAAkB,aAAE,YAAa,QAAO;AAC5C,MAAI,kBAAkB,aAAE,UAAU;AAChC,UAAM,UAAU,OAAO;AACvB,WAAO,mBAAmB,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC/E;AACA,SAAO,CAAC;AACV;AAEO,IAAM,eAAN,MAAM,cAAiC;AAAA,EAiBpC,YACE,YAIR;AAJQ;AAAA,EAIP;AAAA,EArBM,OAAO;AAAA,EACR,SAA8D,CAAC;AAAA;AAAA;AAAA;AAAA,EAI/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASR,eAAe;AAAA,EASf,IAAI,QAAQ;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,KAAK,UAAyB,SAAiD;AACnF,SAAK,OAAO,KAAK,EAAE,UAAU,QAAQ,CAAC;AACtC,WAAO,MAAM,KAAK,WAAW,UAAU,KAAK,OAAO,SAAS,CAAC;AAAA,EAC/D;AAAA,EAEA,OAAO,OAAO,UAAyB,SAAmD;AACxF,UAAM,YAAY,KAAK,OAAO;AAC9B,UAAM,WAAW,MAAM,KAAK,KAAK,UAAU,OAAO;AAClD,UAAM,SAAS,KAAK,gBAAgB,SAAS;AAC7C,QAAI,UAAU,OAAO,SAAS,GAAG;AAI/B,YAAM,SAAS,OAAO,KAAK,EAAE;AAC7B,UAAI,WAAW,SAAS,SAAS;AAC/B,cAAM,IAAI;AAAA,UACR,6DACa,MAAM,cAAc,SAAS,OAAO;AAAA,QACnD;AAAA,MACF;AACA,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,cAAM,EAAE,MAAM,cAAc,SAAS,OAAO,CAAC,EAAE;AAC/C,YAAI,KAAK,eAAe,KAAK,IAAI,OAAO,SAAS,GAAG;AAClD,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,YAAY,CAAC;AAAA,QACvE;AAAA,MACF;AAAA,IACF,WAAW,SAAS,SAAS;AAC3B,YAAM,EAAE,MAAM,cAAc,SAAS,SAAS,QAAQ;AAAA,IACxD;AACA,QAAI,SAAS,YAAY;AACvB,iBAAW,MAAM,SAAS,YAAY;AACpC,cAAM;AAAA,UACJ,MAAM;AAAA,UACN,IAAI,GAAG;AAAA,UACP,MAAM,GAAG,SAAS;AAAA,UAClB,WAAW,GAAG,SAAS;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AACA,UAAM,EAAE,MAAM,QAAQ,OAAO,SAAS,OAAO,kBAAkB,SAAS,iBAAiB;AAAA,EAC3F;AAAA,EAEA,OAAO,SACL,WAac;AACd,UAAM,WAAW,IAAI,cAAa,CAAC,WAAW,cAAc;AAC1D,UAAI,aAAa,UAAU,QAAQ;AACjC,cAAM,IAAI;AAAA,UACR,qDAAqD,SAAS,UAAU,UAAU,MAAM;AAAA,QAC1F;AAAA,MACF;AACA,YAAM,OAAO,UAAU,SAAS;AAChC,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,QACjB,kBAAkB,KAAK;AAAA,QACvB,OAAO,KAAK,SAAS,EAAE,eAAe,IAAI,mBAAmB,IAAI,cAAc,GAAG;AAAA,QAClF,MAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF,CAAC;AACD,aAAS,gBAAgB,UAAU,IAAI,CAAC,MAAM,EAAE,MAAM;AACtD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,QAAQ,UAAoB,YAAY,GAAiB;AAC9D,UAAM,YAAY,SAAS,IAAI,CAAC,YAAY;AAC1C,YAAM,SAAmB,CAAC;AAC1B,eAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;AAClD,eAAO,KAAK,QAAQ,MAAM,GAAG,IAAI,SAAS,CAAC;AAAA,MAC7C;AACA,aAAO,EAAE,SAAS,OAAO;AAAA,IAC3B,CAAC;AACD,WAAO,cAAa,SAAS,SAAS;AAAA,EACxC;AAAA,EAEA,OAAO,OAAqB;AAC1B,WAAO,IAAI,cAAa,CAAC,aAAa;AACpC,YAAM,WAAW,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACtE,aAAO;AAAA,QACL,SAAS,UAAU,WAAW;AAAA,QAC9B,OAAO,EAAE,eAAe,IAAI,mBAAmB,IAAI,cAAc,GAAG;AAAA,QACpE,MAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAO,KAAK,QAA+B;AACzC,WAAO,IAAI,cAAa,OAAO;AAAA,MAC7B,SAAS,KAAK,UAAU,mBAAmB,MAAM,CAAC;AAAA,MAClD,OAAO,EAAE,eAAe,IAAI,mBAAmB,IAAI,cAAc,GAAG;AAAA,MACpE,MAAM;AAAA,IACR,EAAE;AAAA,EACJ;AAAA,EAEA,OAAO,OAAO,QAAmD;AAC/D,UAAM,OACJ,OAAO,WAAW,WACb,KAAK,UAAM,6BAAa,QAAQ,OAAO,CAAC,IACzC;AACN,WAAO,IAAI,cAAa,CAAC,WAAW,cAAc;AAChD,UAAI,aAAa,KAAK,QAAQ;AAC5B,cAAM,IAAI,MAAM,4DAA4D,SAAS,EAAE;AAAA,MACzF;AACA,aAAO,KAAK,SAAS;AAAA,IACvB,CAAC;AAAA,EACH;AAAA,EAEA,OAAO,GACL,SAkBc;AACd,WAAO,IAAI,cAAa,OAAO,UAAU,cAAc;AACrD,YAAM,SAAS,MAAM,QAAQ,UAAU,SAAS;AAChD,aAAO;AAAA,QACL,GAAG;AAAA,QACH,OAAO,OAAO,SAAS,EAAE,eAAe,IAAI,mBAAmB,IAAI,cAAc,GAAG;AAAA,QACpF,MAAM,OAAO,QAAQ;AAAA,MACvB;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AC5OO,IAAM,WAAN,MAAM,UAAS;AAAA,EAGpB,YACW,MACD,SACR;AAFS;AACD;AAAA,EACP;AAAA,EALK,SAA+B,CAAC;AAAA,EAOxC,IAAI,QAAQ;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,QAAQ,OAAkC;AAC9C,SAAK,OAAO,KAAK,EAAE,MAAM,CAAC;AAC1B,WAAO,KAAK,QAAQ,KAAK;AAAA,EAC3B;AAAA,EAEA,OAAO,OAAO,MAAc,SAAmE;AAC7F,WAAO,IAAI,UAAS,MAAM,OAAO;AAAA,EACnC;AACF;;;ACPA,iBAMO;AA0BP,SAAS,sBAA8B;AACrC,SAAO,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACtE;AAEA,SAAS,mBAAmB,OAAc,OAAuB;AAC/D,MAAI,UAAU,UAAa,UAAU,SAAS,WAAW,MAAO,QAAO;AACvE,SAAO,eAAe,OAAO,SAAS;AAAA,IACpC,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,OAAO;AAAA,EACT,CAAC;AACD,SAAO;AACT;AAWO,IAAM,iBAAN,MAAqB;AAAA,EAClB,YAAY,oBAAI,IAAyB;AAAA,EACzC,gBAAgB,oBAAI,IAA0B;AAAA,EAC9C,cAAc,oBAAI,IAAsB;AAAA,EACxC,aAAiC,CAAC;AAAA,EAClC,cAAmC,CAAC;AAAA,EACpC,SAAyB,CAAC;AAAA,EAC1B,YAAwB,CAAC;AAAA,EACzB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,eAAe;AAAA,EACf;AAAA,EACA,WAA+B,CAAC;AAAA,EAChC,eAAuB,oBAAoB;AAAA,EAC3C;AAAA,EACA;AAAA,EAER,YAAY,SAAiC;AAC3C,SAAK,aAAa,SAAS;AAC3B,SAAK,wBAAwB,SAAS;AACtC,SAAK,UAAU,SAAS,UAAU,CAAC;AAAA,EACrC;AAAA,EAEA,SAAS,UAA6B;AACpC,SAAK,UAAU,IAAI,SAAS,MAAM,QAAQ;AAAA,EAC5C;AAAA,EAEA,aAAa,MAAc,UAA8B;AACvD,SAAK,cAAc,IAAI,MAAM,QAAQ;AAAA,EACvC;AAAA,EAEA,SACE,MACA,SACM;AAIN,SAAK,YAAY,IAAI,MAAM,SAAS,OAAO,MAAM,OAAsC,CAAC;AAAA,EAC1F;AAAA,EAEA,MAAM,QACJ,cACA,OACA,SAKkB;AAClB,UAAM,WAAW,KAAK,UAAU,IAAI,YAAY;AAChD,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,aAAa,YAAY,kBAAkB;AAG1E,SAAK,aAAa,CAAC;AACnB,SAAK,cAAc,CAAC;AACpB,SAAK,SAAS,CAAC;AACf,SAAK,YAAY,CAAC;AAClB,SAAK,aAAa;AAClB,SAAK,YAAY;AACjB,SAAK,eAAe;AACpB,SAAK,WAAW,CAAC;AACjB,SAAK,eAAe,oBAAoB;AAExC,UAAM,YAAY,SAAS,YAAY,MAAM,KAAK;AAGlD,UAAM,WAAW,IAAI,4BAAiB;AACtC,aAAS,eAAe;AACxB,eAAW,CAAC,MAAM,QAAQ,KAAK,KAAK,eAAe;AACjD,eAAS,iBAAiB,MAAM,QAAQ;AAAA,IAC1C;AAEA,UAAM,kBAAkB,KAAK,cAAc,IAAI,SAAS;AACxD,QAAI,iBAAiB;AACnB,eAAS,YAAY,eAAe;AAAA,IACtC,WAAW,KAAK,cAAc,SAAS,GAAG;AACxC,YAAM,eAAe,KAAK,cAAc,OAAO,EAAE,KAAK,EAAE;AACxD,eAAS,YAAY,YAAY;AAAA,IACnC;AAGA,UAAM,gBAAgB,oBAAI,IAAiD;AAC3E,eAAW,CAAC,MAAM,QAAQ,KAAK,KAAK,aAAa;AAC/C,oBAAc,IAAI,MAAM,OAAO,SAAkB;AAC/C,eAAO,SAAS,QAAQ,IAAI;AAAA,MAC9B,CAAC;AAAA,IACH;AAWA,UAAM,OAA4B;AAAA,MAChC,OAAO;AAAA,MACP,aAAa,KAAK;AAAA,MAClB,UAAU,SAAS;AAAA;AAAA;AAAA,MAGnB,QAAQ,KAAK;AAAA;AAAA;AAAA,MAGb,oBAAoB,SAAS;AAAA,MAC7B,sBAAsB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM/B;AAAA,MACA,kBAAkB;AAAA,MAClB,YAAY,IAAI,uBAAY;AAAA,MAC5B,eAAe,cAAc,OAAO,IAAI,gBAAgB;AAAA,MACxD,mBAAmB,KAAK,wBACpB,CAAC,SAA4B,KAAK,sBAAuB,IAAI,IAC7D;AAAA,MACJ,SAAS,CAAC,UAAoB;AAC5B,aAAK,UAAU,KAAK,KAAK;AACzB,aAAK;AACL,aAAK,OAAO,KAAK;AAAA,UACf,MAAM,KAAK;AAAA,UACX,MAAM,MAAM;AAAA,UACZ,MAAO,MAA6B;AAAA,QACtC,CAAC;AAED,YAAI,MAAM,SAAS,iBAAiB;AAClC,gBAAM,EAAE,MAAM,eAAe,QAAQ,IAAI,MAAM;AAC/C,eAAK,WAAW,KAAK;AAAA,YACnB,MAAM,MAAM;AAAA,YACZ,aAAa,MAAM;AAAA,YACnB,OAAO,MAAM;AAAA,YACb,QAAQ,MAAM;AAAA,YACd;AAAA,YACA,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,YACvD;AAAA,UACF,CAAC;AAAA,QACH;AAIA,aAAK,kBAAc,kCAAsB,KAAK;AAE9C,gBAAI,2BAAe,KAAK,EAAG,MAAK,YAAY;AAG5C,YAAI,MAAM,SAAS,oBAAoB,MAAM,MAAM;AACjD,gBAAM,EAAE,SAAS,IAAI,MAAM;AAC3B,cAAI,aAAa,QAAW;AAC1B,iBAAK,SAAS,KAAK;AAAA,cACjB,SAAS;AAAA,cACT,OAAO,MAAM,SACT;AAAA,gBACE,eAAe,MAAM,OAAO,SAAS;AAAA,gBACrC,mBAAmB,MAAM,OAAO,UAAU;AAAA,gBAC1C,eACG,MAAM,OAAO,SAAS,MACtB,MAAM,OAAO,UAAU,MACvB,MAAM,OAAO,aAAa;AAAA,gBAC7B,GAAI,MAAM,OAAO,cAAc,SAC3B,EAAE,kBAAkB,MAAM,OAAO,UAAU,IAC3C,CAAC;AAAA,cACP,IACA,EAAE,eAAe,GAAG,mBAAmB,GAAG,cAAc,EAAE;AAAA,cAC9D,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,YACzD,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,MACA,qBAAqB,CAAC,SAAwB;AAC5C,aAAK,YAAY,KAAK,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,2BAAgB,IAAI;AAQpC,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,mBAAmB,SAAS;AAChC,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,QAAQ,GAAG;AACzC,YAAM,cAAc,MAAM,IAAI,iBAAiB;AAG/C,UAAI,SAAS,aAAc,UAAS,aAAa,MAAM,MAAM;AAE7D,UAAI,iBAAiB;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,KAAK,IAAI,IAAI;AAAA,QACvB;AAAA,QACA;AAAA,MACF,CAAC;AAKD,UAAI,KAAK,WAAY,OAAM,KAAK,gBAAgB;AAChD,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,UAAI,gBAAgB;AACpB,UAAI,cAAiC,EAAE,UAAU,KAAK;AACtD,UAAI;AACF,sBAAc,MAAM,IAAI,iBAAiB;AAAA,MAC3C,SAAS,YAAY;AACnB,wBACE,sBAAsB,QAAQ,mBAAmB,YAAY,aAAa,IAAI;AAAA,MAClF;AAKA,YAAM,UACJ,OAAO,kBAAkB,YACzB,kBAAkB,QACjB,cAAqC,SAAS;AACjD,UAAI,iBAAiB;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,KAAK,IAAI,IAAI;AAAA,QACvB,OAAO,yBAAyB,QAAQ,cAAc,UAAU,OAAO,aAAa;AAAA,QACpF,GAAI,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,QACnC;AAAA,MACF,CAAC;AAED,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,UAAU,MAAmC;AAC3C,WAAO,OAAO,KAAK,WAAW,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI,IAAI,KAAK;AAAA,EACtE;AAAA,EAEA,WAAW,MAAoC;AAC7C,WAAO,OAAO,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI,KAAK;AAAA,EACxE;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA,EAGA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EACA,QAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EACA,WAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,kBAAiC;AAC7C,QAAI,CAAC,KAAK,WAAY;AACtB,UAAM,EAAE,WAAW,MAAM,IAAI,MAAM,OAAO,aAAkB;AAC5D,UAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,MAAW;AAC5C,UAAM,MAAM,QAAQ,KAAK,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AACzD,UAAM,UAAU,KAAK,YAAY,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,GAAG,MAAM;AAAA,EACjF;AACF;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Provider, ChatMessage, ChatOptions, ProviderResponse, StreamChunk, ToolCallMessage, AwaitHumanOptions, HumanDecision, AxlConfig, AnyWorkflow, EventStreamOptions, AgentCallInfo, AxlEvent } from '@axlsdk/axl';
1
+ import { Provider, ChatMessage, ChatOptions, ProviderResponse, StreamChunk, ToolCallMessage, AwaitHumanOptions, HumanDecision, AxlConfig, AnyWorkflow, EventStreamOptions, ToolCallOutcome, AgentCallInfo, AxlEvent } from '@axlsdk/axl';
2
2
 
3
3
  declare class MockProvider implements Provider {
4
4
  private responseFn;
@@ -73,10 +73,19 @@ declare class MockProvider implements Provider {
73
73
  }>): MockProvider;
74
74
  }
75
75
 
76
+ /** One accepted provider-issued tool invocation, recorded from its terminal v2 event.
77
+ *
78
+ * Narrow `outcome.status` before reading status-specific fields. Correlation uses
79
+ * `(executionId, askId, callId)`; `name` is the canonical tool name.
80
+ */
76
81
  type RecordedToolCall = {
77
82
  name: string;
83
+ executionId: string;
84
+ askId: string;
85
+ callId: string;
78
86
  args: unknown;
79
- result: unknown;
87
+ requestedTool?: string;
88
+ outcome: ToolCallOutcome;
80
89
  };
81
90
  type RecordedAgentCall = Partial<AgentCallInfo> & Pick<AgentCallInfo, 'agent' | 'prompt' | 'response'>;
82
91
  type RecordedStep = {
@@ -115,6 +124,7 @@ declare class AxlTestRuntime {
115
124
  execute(workflowName: string, input: unknown, options?: {
116
125
  metadata?: Record<string, unknown>;
117
126
  events?: EventStreamOptions;
127
+ branchDrainTimeoutMs?: number;
118
128
  }): Promise<unknown>;
119
129
  toolCalls(name?: string): RecordedToolCall[];
120
130
  agentCalls(name?: string): RecordedAgentCall[];
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Provider, ChatMessage, ChatOptions, ProviderResponse, StreamChunk, ToolCallMessage, AwaitHumanOptions, HumanDecision, AxlConfig, AnyWorkflow, EventStreamOptions, AgentCallInfo, AxlEvent } from '@axlsdk/axl';
1
+ import { Provider, ChatMessage, ChatOptions, ProviderResponse, StreamChunk, ToolCallMessage, AwaitHumanOptions, HumanDecision, AxlConfig, AnyWorkflow, EventStreamOptions, ToolCallOutcome, AgentCallInfo, AxlEvent } from '@axlsdk/axl';
2
2
 
3
3
  declare class MockProvider implements Provider {
4
4
  private responseFn;
@@ -73,10 +73,19 @@ declare class MockProvider implements Provider {
73
73
  }>): MockProvider;
74
74
  }
75
75
 
76
+ /** One accepted provider-issued tool invocation, recorded from its terminal v2 event.
77
+ *
78
+ * Narrow `outcome.status` before reading status-specific fields. Correlation uses
79
+ * `(executionId, askId, callId)`; `name` is the canonical tool name.
80
+ */
76
81
  type RecordedToolCall = {
77
82
  name: string;
83
+ executionId: string;
84
+ askId: string;
85
+ callId: string;
78
86
  args: unknown;
79
- result: unknown;
87
+ requestedTool?: string;
88
+ outcome: ToolCallOutcome;
80
89
  };
81
90
  type RecordedAgentCall = Partial<AgentCallInfo> & Pick<AgentCallInfo, 'agent' | 'prompt' | 'response'>;
82
91
  type RecordedStep = {
@@ -115,6 +124,7 @@ declare class AxlTestRuntime {
115
124
  execute(workflowName: string, input: unknown, options?: {
116
125
  metadata?: Record<string, unknown>;
117
126
  events?: EventStreamOptions;
127
+ branchDrainTimeoutMs?: number;
118
128
  }): Promise<unknown>;
119
129
  toolCalls(name?: string): RecordedToolCall[];
120
130
  agentCalls(name?: string): RecordedAgentCall[];
package/dist/index.js CHANGED
@@ -203,6 +203,15 @@ import {
203
203
  function generateExecutionId() {
204
204
  return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
205
205
  }
206
+ function preserveDrainCause(error, cause) {
207
+ if (cause === void 0 || cause === error || "cause" in error) return error;
208
+ Object.defineProperty(error, "cause", {
209
+ configurable: true,
210
+ enumerable: false,
211
+ value: cause
212
+ });
213
+ return error;
214
+ }
206
215
  var AxlTestRuntime = class {
207
216
  workflows = /* @__PURE__ */ new Map();
208
217
  mockProviders = /* @__PURE__ */ new Map();
@@ -274,6 +283,7 @@ var AxlTestRuntime = class {
274
283
  // Thread `events` config so tests can exercise the queue cap and
275
284
  // overflow policy on `ctx.events`. Mirrors the production runtime.
276
285
  eventStreamOptions: options?.events,
286
+ branchDrainTimeoutMs: options?.branchDrainTimeoutMs,
277
287
  // Production runtime threads this (see `runtime.ts:594` in
278
288
  // `execute()` and `:765` in `stream()`). `emitEvent` auto-stamps
279
289
  // `event.workflow` from it, so consumers grouping by workflow
@@ -292,9 +302,17 @@ var AxlTestRuntime = class {
292
302
  type: event.type,
293
303
  data: event.data
294
304
  });
295
- if (event.type === "tool_call_end" && event.data) {
296
- const { args, result } = event.data;
297
- this._toolCalls.push({ name: event.tool, args, result });
305
+ if (event.type === "tool_call_end") {
306
+ const { args, requestedTool, outcome } = event.data;
307
+ this._toolCalls.push({
308
+ name: event.tool,
309
+ executionId: event.executionId,
310
+ askId: event.askId,
311
+ callId: event.callId,
312
+ args,
313
+ ...requestedTool !== void 0 ? { requestedTool } : {},
314
+ outcome
315
+ });
298
316
  }
299
317
  this._totalCost += eventCostContribution(event);
300
318
  if (isUnpricedLeaf(event)) this._unpriced = true;
@@ -323,23 +341,33 @@ var AxlTestRuntime = class {
323
341
  ctx._emitWorkflowStart(validated);
324
342
  try {
325
343
  const result = await workflow.handler(ctx);
344
+ const observation = await ctx._drainBranchWork();
326
345
  if (workflow.outputSchema) workflow.outputSchema.parse(result);
327
346
  ctx._emitWorkflowEnd({
328
347
  status: "completed",
329
348
  duration: Date.now() - startedAt,
330
- result
349
+ result,
350
+ observation
331
351
  });
332
352
  if (this.recordPath) await this._writeRecording();
333
353
  return result;
334
354
  } catch (err) {
335
- const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
355
+ let workflowError = err;
356
+ let observation = { complete: true };
357
+ try {
358
+ observation = await ctx._drainBranchWork();
359
+ } catch (drainError) {
360
+ workflowError = drainError instanceof Error ? preserveDrainCause(drainError, workflowError) : drainError;
361
+ }
362
+ const aborted = typeof workflowError === "object" && workflowError !== null && workflowError.name === "AbortError";
336
363
  ctx._emitWorkflowEnd({
337
364
  status: "failed",
338
365
  duration: Date.now() - startedAt,
339
- error: err instanceof Error ? err.message : String(err),
340
- ...aborted ? { aborted: true } : {}
366
+ error: workflowError instanceof Error ? workflowError.message : String(workflowError),
367
+ ...aborted ? { aborted: true } : {},
368
+ observation
341
369
  });
342
- throw err;
370
+ throw workflowError;
343
371
  }
344
372
  }
345
373
  toolCalls(name) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mock-provider.ts","../src/mock-tool.ts","../src/test-runtime.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { z } from 'zod';\nimport type {\n ChatMessage,\n ChatOptions,\n ToolCallMessage,\n ProviderResponse,\n StreamChunk,\n Provider,\n} from '@axlsdk/axl';\n\nfunction randomAlphanumeric(length: number): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n result += chars[Math.floor(Math.random() * chars.length)];\n }\n return result;\n}\n\nfunction generateFromSchema(schema: unknown): unknown {\n if (schema instanceof z.ZodString) return randomAlphanumeric(8 + Math.floor(Math.random() * 13));\n if (schema instanceof z.ZodNumber) {\n const min = Number.isFinite(schema.minValue) ? schema.minValue! : 0;\n const max = Number.isFinite(schema.maxValue) ? schema.maxValue! : 100;\n return min + Math.random() * (max - min);\n }\n if (schema instanceof z.ZodBoolean) return Math.random() < 0.5;\n if (schema instanceof z.ZodArray) {\n const count = 1 + Math.floor(Math.random() * 3);\n return Array.from({ length: count }, () => generateFromSchema(schema.element));\n }\n if (schema instanceof z.ZodObject) {\n const obj: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema.shape)) obj[key] = generateFromSchema(value);\n return obj;\n }\n if (schema instanceof z.ZodOptional) {\n if (Math.random() < 0.5) return undefined;\n return generateFromSchema(schema.unwrap());\n }\n if (schema instanceof z.ZodDefault) return schema.parse(undefined);\n if (schema instanceof z.ZodEnum) {\n const values = schema.options;\n return values.length > 0 ? values[Math.floor(Math.random() * values.length)] : '';\n }\n if (schema instanceof z.ZodLiteral) return [...schema.values][0];\n if (schema instanceof z.ZodNullable) return null;\n if (schema instanceof z.ZodUnion) {\n const options = schema.options;\n return generateFromSchema(options[Math.floor(Math.random() * options.length)]);\n }\n return {};\n}\n\nexport class MockProvider implements Provider {\n readonly name = 'mock';\n private _calls: { messages: ChatMessage[]; options: ChatOptions }[] = [];\n /** Per-call optional chunk arrays, set by `sequence()` / `chunked()` so\n * `stream()` can yield one `text_delta` per chunk. Per-call indexed\n * alongside the response sequence. */\n private chunkSequence?: Array<string[] | undefined>;\n /** Optional ms delay between successive chunks during `stream()`. Useful\n * for dev fixtures that need to demonstrate streaming UX visually\n * (without a delay, mock chunks fire synchronously and complete in\n * microseconds — invisible to a human watching). Default 0 keeps\n * every existing test fast.\n *\n * Set via `provider.chunkDelayMs = 50` after construction. Not part of\n * any factory's surface to keep the test API minimal. */\n chunkDelayMs = 0;\n\n private constructor(\n private responseFn: (\n messages: ChatMessage[],\n callIndex: number,\n ) => ProviderResponse | Promise<ProviderResponse>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse> {\n this._calls.push({ messages, options });\n return await this.responseFn(messages, this._calls.length - 1);\n }\n\n async *stream(messages: ChatMessage[], options: ChatOptions): AsyncGenerator<StreamChunk> {\n const callIndex = this._calls.length;\n const response = await this.chat(messages, options);\n const chunks = this.chunkSequence?.[callIndex];\n if (chunks && chunks.length > 0) {\n // Sanity guard — if a caller passes chunks AND content, they MUST\n // match. Otherwise tests pass while the real prod content silently\n // diverges from what the streaming path observes.\n const joined = chunks.join('');\n if (joined !== response.content) {\n throw new Error(\n `MockProvider.stream: chunks.join('') !== content. ` +\n `chunks=\"${joined}\" content=\"${response.content}\"`,\n );\n }\n for (let i = 0; i < chunks.length; i++) {\n yield { type: 'text_delta', content: chunks[i] };\n if (this.chunkDelayMs > 0 && i < chunks.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, this.chunkDelayMs));\n }\n }\n } else if (response.content) {\n yield { type: 'text_delta', content: response.content };\n }\n if (response.tool_calls) {\n for (const tc of response.tool_calls) {\n yield {\n type: 'tool_call_delta',\n id: tc.id,\n name: tc.function.name,\n arguments: tc.function.arguments,\n };\n }\n }\n yield { type: 'done', usage: response.usage, providerMetadata: response.providerMetadata };\n }\n\n static sequence(\n responses: Array<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n /** When set, `stream()` yields one `text_delta` per entry instead\n * of one big delta with the full content. Use to exercise\n * partial-JSON parsing, structural-boundary throttling, and\n * cross-attempt token retention in tests. Must satisfy\n * `chunks.join('') === content`. */\n chunks?: string[];\n }>,\n ): MockProvider {\n const provider = new MockProvider((_messages, callIndex) => {\n if (callIndex >= responses.length) {\n throw new Error(\n `MockProvider.sequence: no response for call index ${callIndex}. Only ${responses.length} responses defined.`,\n );\n }\n const resp = responses[callIndex];\n return {\n content: resp.content,\n tool_calls: resp.tool_calls,\n providerMetadata: resp.providerMetadata,\n usage: resp.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: resp.cost ?? 0,\n };\n });\n provider.chunkSequence = responses.map((r) => r.chunks);\n return provider;\n }\n\n /**\n * Convenience: build a `sequence()` from plain content strings, splitting\n * each one into fixed-size chunks for the streaming path. Default\n * `chunkSize` is 4 chars (≈1 token).\n */\n static chunked(contents: string[], chunkSize = 4): MockProvider {\n const responses = contents.map((content) => {\n const chunks: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunks.push(content.slice(i, i + chunkSize));\n }\n return { content, chunks };\n });\n return MockProvider.sequence(responses);\n }\n\n static echo(): MockProvider {\n return new MockProvider((messages) => {\n const lastUser = [...messages].reverse().find((m) => m.role === 'user');\n return {\n content: lastUser?.content ?? '',\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n };\n });\n }\n\n static json(schema: unknown): MockProvider {\n return new MockProvider(() => ({\n content: JSON.stringify(generateFromSchema(schema)),\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n }));\n }\n\n static replay(source: string | ProviderResponse[]): MockProvider {\n const data =\n typeof source === 'string'\n ? (JSON.parse(readFileSync(source, 'utf-8')) as ProviderResponse[])\n : source;\n return new MockProvider((_messages, callIndex) => {\n if (callIndex >= data.length) {\n throw new Error(`MockProvider.replay: no recorded response for call index ${callIndex}`);\n }\n return data[callIndex];\n });\n }\n\n static fn(\n handler: (\n messages: ChatMessage[],\n callIndex: number,\n ) =>\n | {\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }\n | Promise<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }>,\n ): MockProvider {\n return new MockProvider(async (messages, callIndex) => {\n const result = await handler(messages, callIndex);\n return {\n ...result,\n usage: result.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: result.cost ?? 0,\n };\n });\n }\n}\n","export class MockTool {\n private _calls: { input: unknown }[] = [];\n\n constructor(\n readonly name: string,\n private handler: (input: unknown) => unknown | Promise<unknown>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async execute(input: unknown): Promise<unknown> {\n this._calls.push({ input });\n return this.handler(input);\n }\n\n static create(name: string, handler: (input: unknown) => unknown | Promise<unknown>): MockTool {\n return new MockTool(name, handler);\n }\n}\n","import { MockProvider } from './mock-provider.js';\nimport { MockTool } from './mock-tool.js';\nimport type {\n AxlEvent,\n AwaitHumanOptions,\n HumanDecision,\n ProviderResponse,\n AgentCallInfo,\n AxlConfig,\n AnyWorkflow,\n} from '@axlsdk/axl';\nimport {\n WorkflowContext,\n MemoryStore,\n ProviderRegistry,\n eventCostContribution,\n isUnpricedLeaf,\n} from '@axlsdk/axl';\nimport type { EventStreamOptions, WorkflowContextInit } from '@axlsdk/axl';\n\n// `AnyWorkflow` is a re-exported alias for `Workflow<any, any>` — see\n// the JSDoc on `AnyWorkflow` in `@axlsdk/axl/workflow.ts`. The bivariant\n// `any` is load-bearing for satisfying narrowly-typed handlers (e.g., a\n// `Workflow<TInput=MsgType>` whose handler takes `WorkflowContext<MsgType>`).\n\nexport type RecordedToolCall = { name: string; args: unknown; result: unknown };\nexport type RecordedAgentCall = Partial<AgentCallInfo> &\n Pick<AgentCallInfo, 'agent' | 'prompt' | 'response'>;\nexport type RecordedStep = { step: number; type: string; data: unknown };\n\nfunction generateExecutionId(): string {\n return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport type AxlTestRuntimeOptions = {\n record?: string;\n humanDecisions?: (options: AwaitHumanOptions) => HumanDecision;\n /** AxlConfig for the test runtime. Controls trace level/redact, context management, etc.\n * Defaults to `{}`. Set `{ trace: { level: 'full' } }` to verify verbose-mode snapshots\n * in tests, or `{ trace: { redact: true } }` to verify redaction policy in tests. */\n config?: AxlConfig;\n};\n\nexport class AxlTestRuntime {\n private workflows = new Map<string, AnyWorkflow>();\n private mockProviders = new Map<string, MockProvider>();\n private mockToolMap = new Map<string, MockTool>();\n private _toolCalls: RecordedToolCall[] = [];\n private _agentCalls: RecordedAgentCall[] = [];\n private _steps: RecordedStep[] = [];\n private _traceLog: AxlEvent[] = [];\n private _totalCost = 0;\n private _unpriced = false;\n private _stepCounter = 0;\n private recordPath?: string;\n private recorded: ProviderResponse[] = [];\n private _executionId: string = generateExecutionId();\n private _humanDecisionHandler?: (options: AwaitHumanOptions) => HumanDecision;\n private _config: AxlConfig;\n\n constructor(options?: AxlTestRuntimeOptions) {\n this.recordPath = options?.record;\n this._humanDecisionHandler = options?.humanDecisions;\n this._config = options?.config ?? {};\n }\n\n register(workflow: AnyWorkflow): void {\n this.workflows.set(workflow.name, workflow);\n }\n\n mockProvider(name: string, provider: MockProvider): void {\n this.mockProviders.set(name, provider);\n }\n\n mockTool<TInput = unknown>(\n name: string,\n handler: (input: TInput) => unknown | Promise<unknown>,\n ): void {\n // The MockTool registry is type-erased (`(input: unknown) => unknown`)\n // because the runtime can't statically know each tool's input shape; tests\n // opt in to a typed input via the generic, and we cast on storage.\n this.mockToolMap.set(name, MockTool.create(name, handler as (input: unknown) => unknown));\n }\n\n async execute(\n workflowName: string,\n input: unknown,\n options?: { metadata?: Record<string, unknown>; events?: EventStreamOptions },\n ): Promise<unknown> {\n const workflow = this.workflows.get(workflowName);\n if (!workflow) throw new Error(`Workflow \"${workflowName}\" not registered`);\n\n // Reset state between executions\n this._toolCalls = [];\n this._agentCalls = [];\n this._steps = [];\n this._traceLog = [];\n this._totalCost = 0;\n this._unpriced = false;\n this._stepCounter = 0;\n this.recorded = [];\n this._executionId = generateExecutionId();\n\n const validated = workflow.inputSchema.parse(input);\n\n // Build ProviderRegistry from registered mock providers\n const registry = new ProviderRegistry();\n registry.clearFactories(); // Remove built-in factories; only use explicit mocks\n for (const [name, provider] of this.mockProviders) {\n registry.registerInstance(name, provider);\n }\n // Fallback resolution: 'default' key or single-provider\n const defaultProvider = this.mockProviders.get('default');\n if (defaultProvider) {\n registry.setFallback(defaultProvider);\n } else if (this.mockProviders.size === 1) {\n const onlyProvider = this.mockProviders.values().next().value!;\n registry.setFallback(onlyProvider);\n }\n\n // Build toolOverrides from mockToolMap\n const toolOverrides = new Map<string, (args: unknown) => Promise<unknown>>();\n for (const [name, mockTool] of this.mockToolMap) {\n toolOverrides.set(name, async (args: unknown) => {\n return mockTool.execute(args);\n });\n }\n\n // Previously `_recordStep('workflow_start', ...)` was called here with\n // raw `{ workflow, input }` data, AND `ctx._emitWorkflowStart` below\n // pushed through the onTrace handler (which also bumps `_stepCounter`\n // and appends to `_steps`). Result: two entries in `steps()` per\n // workflow_start, with the first bypassing `config.trace.redact`.\n // The `_recordStep` call is removed — onTrace is the single source\n // of truth for `_steps`, matching the behavior of every other event\n // type in the test runtime. Reviewer architecture §5.\n\n const init: WorkflowContextInit = {\n input: validated,\n executionId: this._executionId,\n metadata: options?.metadata,\n // Thread the constructor-provided config so tests can exercise\n // trace.level === 'full' and trace.redact behavior end-to-end.\n config: this._config,\n // Thread `events` config so tests can exercise the queue cap and\n // overflow policy on `ctx.events`. Mirrors the production runtime.\n eventStreamOptions: options?.events,\n // Production runtime threads this (see `runtime.ts:594` in\n // `execute()` and `:765` in `stream()`). `emitEvent` auto-stamps\n // `event.workflow` from it, so consumers grouping by workflow\n // (Cost Dashboard `byWorkflow`, `trackExecution.metadata.workflows`,\n // any eval runner) see the same attribution in tests and prod.\n workflowName,\n providerRegistry: registry,\n stateStore: new MemoryStore(),\n toolOverrides: toolOverrides.size > 0 ? toolOverrides : undefined,\n awaitHumanHandler: this._humanDecisionHandler\n ? (opts: AwaitHumanOptions) => this._humanDecisionHandler!(opts)\n : undefined,\n onTrace: (event: AxlEvent) => {\n this._traceLog.push(event);\n this._stepCounter++;\n this._steps.push({\n step: this._stepCounter,\n type: event.type,\n data: (event as { data?: unknown }).data,\n });\n\n if (event.type === 'tool_call_end' && event.data) {\n const { args, result } = event.data as { args: unknown; result: unknown };\n // `tool: string` is required on `tool_call_end`; narrowing makes it non-null.\n this._toolCalls.push({ name: event.tool, args, result });\n }\n\n // Single-source-of-truth cost accumulator. Skips ask_end\n // rollups (decision 10) and guards NaN/Infinity.\n this._totalCost += eventCostContribution(event);\n // Honest aggregate: one unpriced leaf makes `totalCost()` a lower bound.\n if (isUnpricedLeaf(event)) this._unpriced = true;\n\n // Track provider responses for recording\n if (event.type === 'agent_call_end' && event.data) {\n const { response } = event.data as { prompt?: string; response?: string };\n if (response !== undefined) {\n this.recorded.push({\n content: response,\n usage: event.tokens\n ? {\n prompt_tokens: event.tokens.input ?? 0,\n completion_tokens: event.tokens.output ?? 0,\n total_tokens:\n (event.tokens.input ?? 0) +\n (event.tokens.output ?? 0) +\n (event.tokens.reasoning ?? 0),\n ...(event.tokens.reasoning !== undefined\n ? { reasoning_tokens: event.tokens.reasoning }\n : {}),\n }\n : { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n ...(event.cost !== undefined ? { cost: event.cost } : {}),\n });\n }\n }\n },\n onAgentCallComplete: (call: AgentCallInfo) => {\n this._agentCalls.push(call);\n },\n };\n\n const ctx = new WorkflowContext(init);\n\n // Emit workflow_start through the same pipeline production uses,\n // AFTER ctx exists so `emitEvent`'s redact/step/timestamp handling\n // applies. Previously emitted via `_pushTrace` which bypassed\n // redaction — `config: { trace: { redact: true } }` on the test\n // runtime would scrub intermediate events but leak `input`/`result`\n // through the synthesized workflow_start/end.\n const startedAt = Date.now();\n ctx._emitWorkflowStart(validated);\n try {\n const result = await workflow.handler(ctx);\n\n // Validate output schema if defined\n if (workflow.outputSchema) workflow.outputSchema.parse(result);\n\n ctx._emitWorkflowEnd({\n status: 'completed',\n duration: Date.now() - startedAt,\n result,\n });\n // `_steps` is populated by onTrace for every event (including\n // workflow_end); no separate `_recordStep` call needed — that\n // would double-count.\n\n if (this.recordPath) await this._writeRecording();\n return result;\n } catch (err) {\n // Parity with production: failed workflows get a terminal\n // `workflow_end(status: 'failed')` event so consumers counting\n // workflow_start ↔ workflow_end pairs never see an unclosed one.\n // `aborted` flag mirrors the runtime.ts AbortError detection.\n const aborted =\n typeof err === 'object' &&\n err !== null &&\n (err as { name?: unknown }).name === 'AbortError';\n ctx._emitWorkflowEnd({\n status: 'failed',\n duration: Date.now() - startedAt,\n error: err instanceof Error ? err.message : String(err),\n ...(aborted ? { aborted: true } : {}),\n });\n // `_steps` populated by onTrace (see comment on the success path).\n throw err;\n }\n }\n\n toolCalls(name?: string): RecordedToolCall[] {\n return name ? this._toolCalls.filter((c) => c.name === name) : this._toolCalls;\n }\n\n agentCalls(name?: string): RecordedAgentCall[] {\n return name ? this._agentCalls.filter((c) => c.agent === name) : this._agentCalls;\n }\n\n totalCost(): number {\n return this._totalCost;\n }\n /** True when any cost-bearing call was unpriced — `totalCost()` is then a\n * LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */\n unpriced(): boolean {\n return this._unpriced;\n }\n steps(): RecordedStep[] {\n return this._steps;\n }\n traceLog(): AxlEvent[] {\n return this._traceLog;\n }\n\n private async _writeRecording(): Promise<void> {\n if (!this.recordPath) return;\n const { writeFile, mkdir } = await import('node:fs/promises');\n const { dirname } = await import('node:path');\n await mkdir(dirname(this.recordPath), { recursive: true });\n await writeFile(this.recordPath, JSON.stringify(this.recorded, null, 2), 'utf8');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mock-provider.ts","../src/mock-tool.ts","../src/test-runtime.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { z } from 'zod';\nimport type {\n ChatMessage,\n ChatOptions,\n ToolCallMessage,\n ProviderResponse,\n StreamChunk,\n Provider,\n} from '@axlsdk/axl';\n\nfunction randomAlphanumeric(length: number): string {\n const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n result += chars[Math.floor(Math.random() * chars.length)];\n }\n return result;\n}\n\nfunction generateFromSchema(schema: unknown): unknown {\n if (schema instanceof z.ZodString) return randomAlphanumeric(8 + Math.floor(Math.random() * 13));\n if (schema instanceof z.ZodNumber) {\n const min = Number.isFinite(schema.minValue) ? schema.minValue! : 0;\n const max = Number.isFinite(schema.maxValue) ? schema.maxValue! : 100;\n return min + Math.random() * (max - min);\n }\n if (schema instanceof z.ZodBoolean) return Math.random() < 0.5;\n if (schema instanceof z.ZodArray) {\n const count = 1 + Math.floor(Math.random() * 3);\n return Array.from({ length: count }, () => generateFromSchema(schema.element));\n }\n if (schema instanceof z.ZodObject) {\n const obj: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema.shape)) obj[key] = generateFromSchema(value);\n return obj;\n }\n if (schema instanceof z.ZodOptional) {\n if (Math.random() < 0.5) return undefined;\n return generateFromSchema(schema.unwrap());\n }\n if (schema instanceof z.ZodDefault) return schema.parse(undefined);\n if (schema instanceof z.ZodEnum) {\n const values = schema.options;\n return values.length > 0 ? values[Math.floor(Math.random() * values.length)] : '';\n }\n if (schema instanceof z.ZodLiteral) return [...schema.values][0];\n if (schema instanceof z.ZodNullable) return null;\n if (schema instanceof z.ZodUnion) {\n const options = schema.options;\n return generateFromSchema(options[Math.floor(Math.random() * options.length)]);\n }\n return {};\n}\n\nexport class MockProvider implements Provider {\n readonly name = 'mock';\n private _calls: { messages: ChatMessage[]; options: ChatOptions }[] = [];\n /** Per-call optional chunk arrays, set by `sequence()` / `chunked()` so\n * `stream()` can yield one `text_delta` per chunk. Per-call indexed\n * alongside the response sequence. */\n private chunkSequence?: Array<string[] | undefined>;\n /** Optional ms delay between successive chunks during `stream()`. Useful\n * for dev fixtures that need to demonstrate streaming UX visually\n * (without a delay, mock chunks fire synchronously and complete in\n * microseconds — invisible to a human watching). Default 0 keeps\n * every existing test fast.\n *\n * Set via `provider.chunkDelayMs = 50` after construction. Not part of\n * any factory's surface to keep the test API minimal. */\n chunkDelayMs = 0;\n\n private constructor(\n private responseFn: (\n messages: ChatMessage[],\n callIndex: number,\n ) => ProviderResponse | Promise<ProviderResponse>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async chat(messages: ChatMessage[], options: ChatOptions): Promise<ProviderResponse> {\n this._calls.push({ messages, options });\n return await this.responseFn(messages, this._calls.length - 1);\n }\n\n async *stream(messages: ChatMessage[], options: ChatOptions): AsyncGenerator<StreamChunk> {\n const callIndex = this._calls.length;\n const response = await this.chat(messages, options);\n const chunks = this.chunkSequence?.[callIndex];\n if (chunks && chunks.length > 0) {\n // Sanity guard — if a caller passes chunks AND content, they MUST\n // match. Otherwise tests pass while the real prod content silently\n // diverges from what the streaming path observes.\n const joined = chunks.join('');\n if (joined !== response.content) {\n throw new Error(\n `MockProvider.stream: chunks.join('') !== content. ` +\n `chunks=\"${joined}\" content=\"${response.content}\"`,\n );\n }\n for (let i = 0; i < chunks.length; i++) {\n yield { type: 'text_delta', content: chunks[i] };\n if (this.chunkDelayMs > 0 && i < chunks.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, this.chunkDelayMs));\n }\n }\n } else if (response.content) {\n yield { type: 'text_delta', content: response.content };\n }\n if (response.tool_calls) {\n for (const tc of response.tool_calls) {\n yield {\n type: 'tool_call_delta',\n id: tc.id,\n name: tc.function.name,\n arguments: tc.function.arguments,\n };\n }\n }\n yield { type: 'done', usage: response.usage, providerMetadata: response.providerMetadata };\n }\n\n static sequence(\n responses: Array<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n /** When set, `stream()` yields one `text_delta` per entry instead\n * of one big delta with the full content. Use to exercise\n * partial-JSON parsing, structural-boundary throttling, and\n * cross-attempt token retention in tests. Must satisfy\n * `chunks.join('') === content`. */\n chunks?: string[];\n }>,\n ): MockProvider {\n const provider = new MockProvider((_messages, callIndex) => {\n if (callIndex >= responses.length) {\n throw new Error(\n `MockProvider.sequence: no response for call index ${callIndex}. Only ${responses.length} responses defined.`,\n );\n }\n const resp = responses[callIndex];\n return {\n content: resp.content,\n tool_calls: resp.tool_calls,\n providerMetadata: resp.providerMetadata,\n usage: resp.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: resp.cost ?? 0,\n };\n });\n provider.chunkSequence = responses.map((r) => r.chunks);\n return provider;\n }\n\n /**\n * Convenience: build a `sequence()` from plain content strings, splitting\n * each one into fixed-size chunks for the streaming path. Default\n * `chunkSize` is 4 chars (≈1 token).\n */\n static chunked(contents: string[], chunkSize = 4): MockProvider {\n const responses = contents.map((content) => {\n const chunks: string[] = [];\n for (let i = 0; i < content.length; i += chunkSize) {\n chunks.push(content.slice(i, i + chunkSize));\n }\n return { content, chunks };\n });\n return MockProvider.sequence(responses);\n }\n\n static echo(): MockProvider {\n return new MockProvider((messages) => {\n const lastUser = [...messages].reverse().find((m) => m.role === 'user');\n return {\n content: lastUser?.content ?? '',\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n };\n });\n }\n\n static json(schema: unknown): MockProvider {\n return new MockProvider(() => ({\n content: JSON.stringify(generateFromSchema(schema)),\n usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: 0,\n }));\n }\n\n static replay(source: string | ProviderResponse[]): MockProvider {\n const data =\n typeof source === 'string'\n ? (JSON.parse(readFileSync(source, 'utf-8')) as ProviderResponse[])\n : source;\n return new MockProvider((_messages, callIndex) => {\n if (callIndex >= data.length) {\n throw new Error(`MockProvider.replay: no recorded response for call index ${callIndex}`);\n }\n return data[callIndex];\n });\n }\n\n static fn(\n handler: (\n messages: ChatMessage[],\n callIndex: number,\n ) =>\n | {\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }\n | Promise<{\n content: string;\n tool_calls?: ToolCallMessage[];\n providerMetadata?: Record<string, unknown>;\n usage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number };\n cost?: number;\n }>,\n ): MockProvider {\n return new MockProvider(async (messages, callIndex) => {\n const result = await handler(messages, callIndex);\n return {\n ...result,\n usage: result.usage ?? { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 },\n cost: result.cost ?? 0,\n };\n });\n }\n}\n","export class MockTool {\n private _calls: { input: unknown }[] = [];\n\n constructor(\n readonly name: string,\n private handler: (input: unknown) => unknown | Promise<unknown>,\n ) {}\n\n get calls() {\n return this._calls;\n }\n\n async execute(input: unknown): Promise<unknown> {\n this._calls.push({ input });\n return this.handler(input);\n }\n\n static create(name: string, handler: (input: unknown) => unknown | Promise<unknown>): MockTool {\n return new MockTool(name, handler);\n }\n}\n","import { MockProvider } from './mock-provider.js';\nimport { MockTool } from './mock-tool.js';\nimport type {\n AxlEvent,\n AwaitHumanOptions,\n HumanDecision,\n ProviderResponse,\n AgentCallInfo,\n AxlConfig,\n AnyWorkflow,\n ToolCallOutcome,\n ObservationStatus,\n} from '@axlsdk/axl';\nimport {\n WorkflowContext,\n MemoryStore,\n ProviderRegistry,\n eventCostContribution,\n isUnpricedLeaf,\n} from '@axlsdk/axl';\nimport type { EventStreamOptions, WorkflowContextInit } from '@axlsdk/axl';\n\n// `AnyWorkflow` is a re-exported alias for `Workflow<any, any>` — see\n// the JSDoc on `AnyWorkflow` in `@axlsdk/axl/workflow.ts`. The bivariant\n// `any` is load-bearing for satisfying narrowly-typed handlers (e.g., a\n// `Workflow<TInput=MsgType>` whose handler takes `WorkflowContext<MsgType>`).\n\n/** One accepted provider-issued tool invocation, recorded from its terminal v2 event.\n *\n * Narrow `outcome.status` before reading status-specific fields. Correlation uses\n * `(executionId, askId, callId)`; `name` is the canonical tool name.\n */\nexport type RecordedToolCall = {\n name: string;\n executionId: string;\n askId: string;\n callId: string;\n args: unknown;\n requestedTool?: string;\n outcome: ToolCallOutcome;\n};\nexport type RecordedAgentCall = Partial<AgentCallInfo> &\n Pick<AgentCallInfo, 'agent' | 'prompt' | 'response'>;\nexport type RecordedStep = { step: number; type: string; data: unknown };\n\nfunction generateExecutionId(): string {\n return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction preserveDrainCause(error: Error, cause: unknown): Error {\n if (cause === undefined || cause === error || 'cause' in error) return error;\n Object.defineProperty(error, 'cause', {\n configurable: true,\n enumerable: false,\n value: cause,\n });\n return error;\n}\n\nexport type AxlTestRuntimeOptions = {\n record?: string;\n humanDecisions?: (options: AwaitHumanOptions) => HumanDecision;\n /** AxlConfig for the test runtime. Controls trace level/redact, context management, etc.\n * Defaults to `{}`. Set `{ trace: { level: 'full' } }` to verify verbose-mode snapshots\n * in tests, or `{ trace: { redact: true } }` to verify redaction policy in tests. */\n config?: AxlConfig;\n};\n\nexport class AxlTestRuntime {\n private workflows = new Map<string, AnyWorkflow>();\n private mockProviders = new Map<string, MockProvider>();\n private mockToolMap = new Map<string, MockTool>();\n private _toolCalls: RecordedToolCall[] = [];\n private _agentCalls: RecordedAgentCall[] = [];\n private _steps: RecordedStep[] = [];\n private _traceLog: AxlEvent[] = [];\n private _totalCost = 0;\n private _unpriced = false;\n private _stepCounter = 0;\n private recordPath?: string;\n private recorded: ProviderResponse[] = [];\n private _executionId: string = generateExecutionId();\n private _humanDecisionHandler?: (options: AwaitHumanOptions) => HumanDecision;\n private _config: AxlConfig;\n\n constructor(options?: AxlTestRuntimeOptions) {\n this.recordPath = options?.record;\n this._humanDecisionHandler = options?.humanDecisions;\n this._config = options?.config ?? {};\n }\n\n register(workflow: AnyWorkflow): void {\n this.workflows.set(workflow.name, workflow);\n }\n\n mockProvider(name: string, provider: MockProvider): void {\n this.mockProviders.set(name, provider);\n }\n\n mockTool<TInput = unknown>(\n name: string,\n handler: (input: TInput) => unknown | Promise<unknown>,\n ): void {\n // The MockTool registry is type-erased (`(input: unknown) => unknown`)\n // because the runtime can't statically know each tool's input shape; tests\n // opt in to a typed input via the generic, and we cast on storage.\n this.mockToolMap.set(name, MockTool.create(name, handler as (input: unknown) => unknown));\n }\n\n async execute(\n workflowName: string,\n input: unknown,\n options?: {\n metadata?: Record<string, unknown>;\n events?: EventStreamOptions;\n branchDrainTimeoutMs?: number;\n },\n ): Promise<unknown> {\n const workflow = this.workflows.get(workflowName);\n if (!workflow) throw new Error(`Workflow \"${workflowName}\" not registered`);\n\n // Reset state between executions\n this._toolCalls = [];\n this._agentCalls = [];\n this._steps = [];\n this._traceLog = [];\n this._totalCost = 0;\n this._unpriced = false;\n this._stepCounter = 0;\n this.recorded = [];\n this._executionId = generateExecutionId();\n\n const validated = workflow.inputSchema.parse(input);\n\n // Build ProviderRegistry from registered mock providers\n const registry = new ProviderRegistry();\n registry.clearFactories(); // Remove built-in factories; only use explicit mocks\n for (const [name, provider] of this.mockProviders) {\n registry.registerInstance(name, provider);\n }\n // Fallback resolution: 'default' key or single-provider\n const defaultProvider = this.mockProviders.get('default');\n if (defaultProvider) {\n registry.setFallback(defaultProvider);\n } else if (this.mockProviders.size === 1) {\n const onlyProvider = this.mockProviders.values().next().value!;\n registry.setFallback(onlyProvider);\n }\n\n // Build toolOverrides from mockToolMap\n const toolOverrides = new Map<string, (args: unknown) => Promise<unknown>>();\n for (const [name, mockTool] of this.mockToolMap) {\n toolOverrides.set(name, async (args: unknown) => {\n return mockTool.execute(args);\n });\n }\n\n // Previously `_recordStep('workflow_start', ...)` was called here with\n // raw `{ workflow, input }` data, AND `ctx._emitWorkflowStart` below\n // pushed through the onTrace handler (which also bumps `_stepCounter`\n // and appends to `_steps`). Result: two entries in `steps()` per\n // workflow_start, with the first bypassing `config.trace.redact`.\n // The `_recordStep` call is removed — onTrace is the single source\n // of truth for `_steps`, matching the behavior of every other event\n // type in the test runtime. Reviewer architecture §5.\n\n const init: WorkflowContextInit = {\n input: validated,\n executionId: this._executionId,\n metadata: options?.metadata,\n // Thread the constructor-provided config so tests can exercise\n // trace.level === 'full' and trace.redact behavior end-to-end.\n config: this._config,\n // Thread `events` config so tests can exercise the queue cap and\n // overflow policy on `ctx.events`. Mirrors the production runtime.\n eventStreamOptions: options?.events,\n branchDrainTimeoutMs: options?.branchDrainTimeoutMs,\n // Production runtime threads this (see `runtime.ts:594` in\n // `execute()` and `:765` in `stream()`). `emitEvent` auto-stamps\n // `event.workflow` from it, so consumers grouping by workflow\n // (Cost Dashboard `byWorkflow`, `trackExecution.metadata.workflows`,\n // any eval runner) see the same attribution in tests and prod.\n workflowName,\n providerRegistry: registry,\n stateStore: new MemoryStore(),\n toolOverrides: toolOverrides.size > 0 ? toolOverrides : undefined,\n awaitHumanHandler: this._humanDecisionHandler\n ? (opts: AwaitHumanOptions) => this._humanDecisionHandler!(opts)\n : undefined,\n onTrace: (event: AxlEvent) => {\n this._traceLog.push(event);\n this._stepCounter++;\n this._steps.push({\n step: this._stepCounter,\n type: event.type,\n data: (event as { data?: unknown }).data,\n });\n\n if (event.type === 'tool_call_end') {\n const { args, requestedTool, outcome } = event.data;\n this._toolCalls.push({\n name: event.tool,\n executionId: event.executionId,\n askId: event.askId,\n callId: event.callId,\n args,\n ...(requestedTool !== undefined ? { requestedTool } : {}),\n outcome,\n });\n }\n\n // Single-source-of-truth cost accumulator. Skips ask_end\n // rollups (decision 10) and guards NaN/Infinity.\n this._totalCost += eventCostContribution(event);\n // Honest aggregate: one unpriced leaf makes `totalCost()` a lower bound.\n if (isUnpricedLeaf(event)) this._unpriced = true;\n\n // Track provider responses for recording\n if (event.type === 'agent_call_end' && event.data) {\n const { response } = event.data as { prompt?: string; response?: string };\n if (response !== undefined) {\n this.recorded.push({\n content: response,\n usage: event.tokens\n ? {\n prompt_tokens: event.tokens.input ?? 0,\n completion_tokens: event.tokens.output ?? 0,\n total_tokens:\n (event.tokens.input ?? 0) +\n (event.tokens.output ?? 0) +\n (event.tokens.reasoning ?? 0),\n ...(event.tokens.reasoning !== undefined\n ? { reasoning_tokens: event.tokens.reasoning }\n : {}),\n }\n : { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n ...(event.cost !== undefined ? { cost: event.cost } : {}),\n });\n }\n }\n },\n onAgentCallComplete: (call: AgentCallInfo) => {\n this._agentCalls.push(call);\n },\n };\n\n const ctx = new WorkflowContext(init);\n\n // Emit workflow_start through the same pipeline production uses,\n // AFTER ctx exists so `emitEvent`'s redact/step/timestamp handling\n // applies. Previously emitted via `_pushTrace` which bypassed\n // redaction — `config: { trace: { redact: true } }` on the test\n // runtime would scrub intermediate events but leak `input`/`result`\n // through the synthesized workflow_start/end.\n const startedAt = Date.now();\n ctx._emitWorkflowStart(validated);\n try {\n const result = await workflow.handler(ctx);\n const observation = await ctx._drainBranchWork();\n\n // Validate output schema if defined\n if (workflow.outputSchema) workflow.outputSchema.parse(result);\n\n ctx._emitWorkflowEnd({\n status: 'completed',\n duration: Date.now() - startedAt,\n result,\n observation,\n });\n // `_steps` is populated by onTrace for every event (including\n // workflow_end); no separate `_recordStep` call needed — that\n // would double-count.\n\n if (this.recordPath) await this._writeRecording();\n return result;\n } catch (err) {\n let workflowError = err;\n let observation: ObservationStatus = { complete: true };\n try {\n observation = await ctx._drainBranchWork();\n } catch (drainError) {\n workflowError =\n drainError instanceof Error ? preserveDrainCause(drainError, workflowError) : drainError;\n }\n // Parity with production: failed workflows get a terminal\n // `workflow_end(status: 'failed')` event so consumers counting\n // workflow_start ↔ workflow_end pairs never see an unclosed one.\n // `aborted` flag mirrors the runtime.ts AbortError detection.\n const aborted =\n typeof workflowError === 'object' &&\n workflowError !== null &&\n (workflowError as { name?: unknown }).name === 'AbortError';\n ctx._emitWorkflowEnd({\n status: 'failed',\n duration: Date.now() - startedAt,\n error: workflowError instanceof Error ? workflowError.message : String(workflowError),\n ...(aborted ? { aborted: true } : {}),\n observation,\n });\n // `_steps` populated by onTrace (see comment on the success path).\n throw workflowError;\n }\n }\n\n toolCalls(name?: string): RecordedToolCall[] {\n return name ? this._toolCalls.filter((c) => c.name === name) : this._toolCalls;\n }\n\n agentCalls(name?: string): RecordedAgentCall[] {\n return name ? this._agentCalls.filter((c) => c.agent === name) : this._agentCalls;\n }\n\n totalCost(): number {\n return this._totalCost;\n }\n /** True when any cost-bearing call was unpriced — `totalCost()` is then a\n * LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */\n unpriced(): boolean {\n return this._unpriced;\n }\n steps(): RecordedStep[] {\n return this._steps;\n }\n traceLog(): AxlEvent[] {\n return this._traceLog;\n }\n\n private async _writeRecording(): Promise<void> {\n if (!this.recordPath) return;\n const { writeFile, mkdir } = await import('node:fs/promises');\n const { dirname } = await import('node:path');\n await mkdir(dirname(this.recordPath), { recursive: true });\n await writeFile(this.recordPath, JSON.stringify(this.recorded, null, 2), 'utf8');\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axlsdk/testing",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "Testing utilities for Axl agentic workflows",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@axlsdk/axl": "0.19.1"
25
+ "@axlsdk/axl": "0.20.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "zod": "^4.0.0"