@axlsdk/testing 0.18.2 → 0.19.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 +2 -0
- package/dist/index.cjs +15 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +22 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -130,6 +130,7 @@ const result = await runtime.execute('my-workflow', { question: 'What is 2+2?' }
|
|
|
130
130
|
expect(runtime.agentCalls()).toHaveLength(1);
|
|
131
131
|
expect(runtime.toolCalls()).toHaveLength(1);
|
|
132
132
|
expect(runtime.totalCost()).toBe(0);
|
|
133
|
+
expect(runtime.unpriced()).toBe(false); // true if any call used an unpriced model → totalCost() is a lower bound
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
For testing human-in-the-loop flows:
|
|
@@ -188,6 +189,7 @@ runtime.mockProvider('openai', provider);
|
|
|
188
189
|
runtime.agentCalls(); // All recorded agent invocations
|
|
189
190
|
runtime.toolCalls(); // All recorded tool invocations
|
|
190
191
|
runtime.totalCost(); // Cumulative cost
|
|
192
|
+
runtime.unpriced(); // true if any call was unpriced (totalCost() is then a lower bound)
|
|
191
193
|
runtime.steps(); // All recorded steps (agents + tools)
|
|
192
194
|
runtime.traceLog(); // All trace events
|
|
193
195
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -244,6 +244,7 @@ var AxlTestRuntime = class {
|
|
|
244
244
|
_steps = [];
|
|
245
245
|
_traceLog = [];
|
|
246
246
|
_totalCost = 0;
|
|
247
|
+
_unpriced = false;
|
|
247
248
|
_stepCounter = 0;
|
|
248
249
|
recordPath;
|
|
249
250
|
recorded = [];
|
|
@@ -272,6 +273,7 @@ var AxlTestRuntime = class {
|
|
|
272
273
|
this._steps = [];
|
|
273
274
|
this._traceLog = [];
|
|
274
275
|
this._totalCost = 0;
|
|
276
|
+
this._unpriced = false;
|
|
275
277
|
this._stepCounter = 0;
|
|
276
278
|
this.recorded = [];
|
|
277
279
|
this._executionId = generateExecutionId();
|
|
@@ -327,13 +329,19 @@ var AxlTestRuntime = class {
|
|
|
327
329
|
this._toolCalls.push({ name: event.tool, args, result });
|
|
328
330
|
}
|
|
329
331
|
this._totalCost += (0, import_axl.eventCostContribution)(event);
|
|
332
|
+
if ((0, import_axl.isUnpricedLeaf)(event)) this._unpriced = true;
|
|
330
333
|
if (event.type === "agent_call_end" && event.data) {
|
|
331
334
|
const { response } = event.data;
|
|
332
335
|
if (response !== void 0) {
|
|
333
336
|
this.recorded.push({
|
|
334
337
|
content: response,
|
|
335
|
-
usage:
|
|
336
|
-
|
|
338
|
+
usage: event.tokens ? {
|
|
339
|
+
prompt_tokens: event.tokens.input ?? 0,
|
|
340
|
+
completion_tokens: event.tokens.output ?? 0,
|
|
341
|
+
total_tokens: (event.tokens.input ?? 0) + (event.tokens.output ?? 0) + (event.tokens.reasoning ?? 0),
|
|
342
|
+
...event.tokens.reasoning !== void 0 ? { reasoning_tokens: event.tokens.reasoning } : {}
|
|
343
|
+
} : { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
344
|
+
...event.cost !== void 0 ? { cost: event.cost } : {}
|
|
337
345
|
});
|
|
338
346
|
}
|
|
339
347
|
}
|
|
@@ -375,6 +383,11 @@ var AxlTestRuntime = class {
|
|
|
375
383
|
totalCost() {
|
|
376
384
|
return this._totalCost;
|
|
377
385
|
}
|
|
386
|
+
/** True when any cost-bearing call was unpriced — `totalCost()` is then a
|
|
387
|
+
* LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */
|
|
388
|
+
unpriced() {
|
|
389
|
+
return this._unpriced;
|
|
390
|
+
}
|
|
378
391
|
steps() {
|
|
379
392
|
return this._steps;
|
|
380
393
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 { WorkflowContext, MemoryStore, ProviderRegistry, eventCostContribution } 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 _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._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\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: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n cost: event.cost ?? 0,\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 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} 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":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -101,6 +101,7 @@ declare class AxlTestRuntime {
|
|
|
101
101
|
private _steps;
|
|
102
102
|
private _traceLog;
|
|
103
103
|
private _totalCost;
|
|
104
|
+
private _unpriced;
|
|
104
105
|
private _stepCounter;
|
|
105
106
|
private recordPath?;
|
|
106
107
|
private recorded;
|
|
@@ -118,6 +119,9 @@ declare class AxlTestRuntime {
|
|
|
118
119
|
toolCalls(name?: string): RecordedToolCall[];
|
|
119
120
|
agentCalls(name?: string): RecordedAgentCall[];
|
|
120
121
|
totalCost(): number;
|
|
122
|
+
/** True when any cost-bearing call was unpriced — `totalCost()` is then a
|
|
123
|
+
* LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */
|
|
124
|
+
unpriced(): boolean;
|
|
121
125
|
steps(): RecordedStep[];
|
|
122
126
|
traceLog(): AxlEvent[];
|
|
123
127
|
private _writeRecording;
|
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,7 @@ declare class AxlTestRuntime {
|
|
|
101
101
|
private _steps;
|
|
102
102
|
private _traceLog;
|
|
103
103
|
private _totalCost;
|
|
104
|
+
private _unpriced;
|
|
104
105
|
private _stepCounter;
|
|
105
106
|
private recordPath?;
|
|
106
107
|
private recorded;
|
|
@@ -118,6 +119,9 @@ declare class AxlTestRuntime {
|
|
|
118
119
|
toolCalls(name?: string): RecordedToolCall[];
|
|
119
120
|
agentCalls(name?: string): RecordedAgentCall[];
|
|
120
121
|
totalCost(): number;
|
|
122
|
+
/** True when any cost-bearing call was unpriced — `totalCost()` is then a
|
|
123
|
+
* LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */
|
|
124
|
+
unpriced(): boolean;
|
|
121
125
|
steps(): RecordedStep[];
|
|
122
126
|
traceLog(): AxlEvent[];
|
|
123
127
|
private _writeRecording;
|
package/dist/index.js
CHANGED
|
@@ -193,7 +193,13 @@ var MockTool = class _MockTool {
|
|
|
193
193
|
};
|
|
194
194
|
|
|
195
195
|
// src/test-runtime.ts
|
|
196
|
-
import {
|
|
196
|
+
import {
|
|
197
|
+
WorkflowContext,
|
|
198
|
+
MemoryStore,
|
|
199
|
+
ProviderRegistry,
|
|
200
|
+
eventCostContribution,
|
|
201
|
+
isUnpricedLeaf
|
|
202
|
+
} from "@axlsdk/axl";
|
|
197
203
|
function generateExecutionId() {
|
|
198
204
|
return `test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
199
205
|
}
|
|
@@ -206,6 +212,7 @@ var AxlTestRuntime = class {
|
|
|
206
212
|
_steps = [];
|
|
207
213
|
_traceLog = [];
|
|
208
214
|
_totalCost = 0;
|
|
215
|
+
_unpriced = false;
|
|
209
216
|
_stepCounter = 0;
|
|
210
217
|
recordPath;
|
|
211
218
|
recorded = [];
|
|
@@ -234,6 +241,7 @@ var AxlTestRuntime = class {
|
|
|
234
241
|
this._steps = [];
|
|
235
242
|
this._traceLog = [];
|
|
236
243
|
this._totalCost = 0;
|
|
244
|
+
this._unpriced = false;
|
|
237
245
|
this._stepCounter = 0;
|
|
238
246
|
this.recorded = [];
|
|
239
247
|
this._executionId = generateExecutionId();
|
|
@@ -289,13 +297,19 @@ var AxlTestRuntime = class {
|
|
|
289
297
|
this._toolCalls.push({ name: event.tool, args, result });
|
|
290
298
|
}
|
|
291
299
|
this._totalCost += eventCostContribution(event);
|
|
300
|
+
if (isUnpricedLeaf(event)) this._unpriced = true;
|
|
292
301
|
if (event.type === "agent_call_end" && event.data) {
|
|
293
302
|
const { response } = event.data;
|
|
294
303
|
if (response !== void 0) {
|
|
295
304
|
this.recorded.push({
|
|
296
305
|
content: response,
|
|
297
|
-
usage:
|
|
298
|
-
|
|
306
|
+
usage: event.tokens ? {
|
|
307
|
+
prompt_tokens: event.tokens.input ?? 0,
|
|
308
|
+
completion_tokens: event.tokens.output ?? 0,
|
|
309
|
+
total_tokens: (event.tokens.input ?? 0) + (event.tokens.output ?? 0) + (event.tokens.reasoning ?? 0),
|
|
310
|
+
...event.tokens.reasoning !== void 0 ? { reasoning_tokens: event.tokens.reasoning } : {}
|
|
311
|
+
} : { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
312
|
+
...event.cost !== void 0 ? { cost: event.cost } : {}
|
|
299
313
|
});
|
|
300
314
|
}
|
|
301
315
|
}
|
|
@@ -337,6 +351,11 @@ var AxlTestRuntime = class {
|
|
|
337
351
|
totalCost() {
|
|
338
352
|
return this._totalCost;
|
|
339
353
|
}
|
|
354
|
+
/** True when any cost-bearing call was unpriced — `totalCost()` is then a
|
|
355
|
+
* LOWER BOUND. Mirrors `ExecutionInfo.unpriced` / `BudgetResult.unpriced`. */
|
|
356
|
+
unpriced() {
|
|
357
|
+
return this._unpriced;
|
|
358
|
+
}
|
|
340
359
|
steps() {
|
|
341
360
|
return this._steps;
|
|
342
361
|
}
|
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 { WorkflowContext, MemoryStore, ProviderRegistry, eventCostContribution } 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 _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._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\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: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n cost: event.cost ?? 0,\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 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} 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":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;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,EAAE,UAAW,QAAO,mBAAmB,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE,CAAC;AAC/F,MAAI,kBAAkB,EAAE,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,EAAE,WAAY,QAAO,KAAK,OAAO,IAAI;AAC3D,MAAI,kBAAkB,EAAE,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,EAAE,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,EAAE,aAAa;AACnC,QAAI,KAAK,OAAO,IAAI,IAAK,QAAO;AAChC,WAAO,mBAAmB,OAAO,OAAO,CAAC;AAAA,EAC3C;AACA,MAAI,kBAAkB,EAAE,WAAY,QAAO,OAAO,MAAM,MAAS;AACjE,MAAI,kBAAkB,EAAE,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,EAAE,WAAY,QAAO,CAAC,GAAG,OAAO,MAAM,EAAE,CAAC;AAC/D,MAAI,kBAAkB,EAAE,YAAa,QAAO;AAC5C,MAAI,kBAAkB,EAAE,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,MAAM,aAAa,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;;;ACTA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAaP,SAAS,sBAA8B;AACrC,SAAO,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACtE;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,SACkB;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,iBAAiB;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;AAAA;AAAA;AAAA;AAAA;AAAA,MAM7B;AAAA,MACA,kBAAkB;AAAA,MAClB,YAAY,IAAI,YAAY;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,mBAAmB,MAAM,MAAM;AAChD,gBAAM,EAAE,MAAM,OAAO,IAAI,MAAM;AAE/B,eAAK,WAAW,KAAK,EAAE,MAAM,MAAM,MAAM,MAAM,OAAO,CAAC;AAAA,QACzD;AAIA,aAAK,cAAc,sBAAsB,KAAK;AAE9C,YAAI,eAAe,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,gBAAgB,IAAI;AAQpC,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,mBAAmB,SAAS;AAChC,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,QAAQ,GAAG;AAGzC,UAAI,SAAS,aAAc,UAAS,aAAa,MAAM,MAAM;AAE7D,UAAI,iBAAiB;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,KAAK,IAAI,IAAI;AAAA,QACvB;AAAA,MACF,CAAC;AAKD,UAAI,KAAK,WAAY,OAAM,KAAK,gBAAgB;AAChD,aAAO;AAAA,IACT,SAAS,KAAK;AAKZ,YAAM,UACJ,OAAO,QAAQ,YACf,QAAQ,QACP,IAA2B,SAAS;AACvC,UAAI,iBAAiB;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,KAAK,IAAI,IAAI;AAAA,QACvB,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACtD,GAAI,UAAU,EAAE,SAAS,KAAK,IAAI,CAAC;AAAA,MACrC,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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axlsdk/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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.
|
|
25
|
+
"@axlsdk/axl": "0.19.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"zod": "^4.0.0"
|