@axlsdk/axl 0.14.0 → 0.15.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 +19 -0
- package/dist/index.cjs +726 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +385 -13
- package/dist/index.d.ts +385 -13
- package/dist/index.js +726 -145
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1965,6 +1965,21 @@ var TimeoutError = class extends AxlError {
|
|
|
1965
1965
|
this.name = "TimeoutError";
|
|
1966
1966
|
}
|
|
1967
1967
|
};
|
|
1968
|
+
function formatBudgetCost(cost) {
|
|
1969
|
+
if (Number.isNaN(cost)) return "$NaN";
|
|
1970
|
+
if (cost === Infinity) return "$Infinity";
|
|
1971
|
+
if (cost === -Infinity) return "-$Infinity";
|
|
1972
|
+
if (cost === 0) return "$0.00";
|
|
1973
|
+
const sign = cost < 0 ? "-" : "";
|
|
1974
|
+
const abs = Math.abs(cost);
|
|
1975
|
+
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
1976
|
+
if (abs < 1e-4) {
|
|
1977
|
+
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
1978
|
+
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
1979
|
+
}
|
|
1980
|
+
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
1981
|
+
return `${sign}$${abs.toFixed(2)}`;
|
|
1982
|
+
}
|
|
1968
1983
|
var BudgetExceededError = class extends AxlError {
|
|
1969
1984
|
limit;
|
|
1970
1985
|
spent;
|
|
@@ -1972,7 +1987,9 @@ var BudgetExceededError = class extends AxlError {
|
|
|
1972
1987
|
constructor(limit, spent, policy) {
|
|
1973
1988
|
super(
|
|
1974
1989
|
"BUDGET_EXCEEDED",
|
|
1975
|
-
`Budget exceeded: spent
|
|
1990
|
+
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
1991
|
+
limit
|
|
1992
|
+
)} limit (policy: ${policy})`
|
|
1976
1993
|
);
|
|
1977
1994
|
this.name = "BudgetExceededError";
|
|
1978
1995
|
this.limit = limit;
|
|
@@ -2160,6 +2177,14 @@ function extractBalanced(str, start, openChar, closeChar) {
|
|
|
2160
2177
|
}
|
|
2161
2178
|
return null;
|
|
2162
2179
|
}
|
|
2180
|
+
function appendRetryMessages(messages, content, feedbackMessage, providerMetadata) {
|
|
2181
|
+
messages.push({
|
|
2182
|
+
role: "assistant",
|
|
2183
|
+
content,
|
|
2184
|
+
...providerMetadata ? { providerMetadata } : {}
|
|
2185
|
+
});
|
|
2186
|
+
messages.push({ role: "system", content: feedbackMessage });
|
|
2187
|
+
}
|
|
2163
2188
|
function estimateMessagesTokens(messages) {
|
|
2164
2189
|
let total = 0;
|
|
2165
2190
|
for (const msg of messages) {
|
|
@@ -2207,6 +2232,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2207
2232
|
awaitHumanHandler;
|
|
2208
2233
|
onAgentStart;
|
|
2209
2234
|
onAgentCallComplete;
|
|
2235
|
+
parentToolCallId;
|
|
2210
2236
|
constructor(init) {
|
|
2211
2237
|
this.input = init.input;
|
|
2212
2238
|
this.executionId = init.executionId;
|
|
@@ -2230,6 +2256,7 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2230
2256
|
this.awaitHumanHandler = init.awaitHumanHandler;
|
|
2231
2257
|
this.onAgentStart = init.onAgentStart;
|
|
2232
2258
|
this.onAgentCallComplete = init.onAgentCallComplete;
|
|
2259
|
+
this.parentToolCallId = init.parentToolCallId;
|
|
2233
2260
|
if (init.metadata?.summaryCache) {
|
|
2234
2261
|
this.summaryCache = init.metadata.summaryCache;
|
|
2235
2262
|
}
|
|
@@ -2240,8 +2267,13 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2240
2267
|
* state store, span manager, memory manager, MCP manager, config,
|
|
2241
2268
|
* awaitHuman handler, pending decisions, tool overrides.
|
|
2242
2269
|
* Isolates: session history, step counter, streaming callbacks (onToken, onAgentStart, onToolCall).
|
|
2270
|
+
*
|
|
2271
|
+
* @param parentToolCallId - The `callId` of the outer `tool_call` that
|
|
2272
|
+
* spawned this child. When set, every trace event emitted by the child
|
|
2273
|
+
* will carry this id as `parentToolCallId`, so consumers can join nested
|
|
2274
|
+
* agent calls back to the outer tool call that invoked them.
|
|
2243
2275
|
*/
|
|
2244
|
-
createChildContext() {
|
|
2276
|
+
createChildContext(parentToolCallId) {
|
|
2245
2277
|
return new _WorkflowContext({
|
|
2246
2278
|
input: this.input,
|
|
2247
2279
|
executionId: this.executionId,
|
|
@@ -2260,7 +2292,11 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2260
2292
|
pendingDecisions: this.pendingDecisions,
|
|
2261
2293
|
toolOverrides: this.toolOverrides,
|
|
2262
2294
|
signal: this.signal,
|
|
2263
|
-
workflowName: this.workflowName
|
|
2295
|
+
workflowName: this.workflowName,
|
|
2296
|
+
// Join key for nested event correlation. Inherit the parent's
|
|
2297
|
+
// `parentToolCallId` when this child is itself nested inside another
|
|
2298
|
+
// child — so grand-children still point to the outermost tool call.
|
|
2299
|
+
parentToolCallId: parentToolCallId ?? this.parentToolCallId
|
|
2264
2300
|
// Isolated: sessionHistory (empty), stepCounter (0),
|
|
2265
2301
|
// onToken (null), onAgentStart (null), onToolCall (null)
|
|
2266
2302
|
});
|
|
@@ -2430,11 +2466,22 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2430
2466
|
this.emitTrace({
|
|
2431
2467
|
type: "guardrail",
|
|
2432
2468
|
agent: agent2._name,
|
|
2433
|
-
data: {
|
|
2469
|
+
data: {
|
|
2470
|
+
guardrailType: "input",
|
|
2471
|
+
blocked: inputResult.block,
|
|
2472
|
+
...inputResult.reason ? { reason: inputResult.reason } : {},
|
|
2473
|
+
// Input guardrails can't retry (prompt is user-supplied), so attempt
|
|
2474
|
+
// and maxAttempts are always 1 — emit them for shape consistency with
|
|
2475
|
+
// output guardrails so consumers don't need two narrowers.
|
|
2476
|
+
attempt: 1,
|
|
2477
|
+
maxAttempts: 1
|
|
2478
|
+
}
|
|
2434
2479
|
});
|
|
2435
2480
|
this.spanManager?.addEventToActiveSpan("axl.guardrail.check", {
|
|
2436
2481
|
"axl.guardrail.type": "input",
|
|
2437
2482
|
"axl.guardrail.blocked": inputResult.block,
|
|
2483
|
+
"axl.guardrail.attempt": 1,
|
|
2484
|
+
"axl.guardrail.maxAttempts": 1,
|
|
2438
2485
|
...inputResult.reason ? { "axl.guardrail.reason": inputResult.reason } : {}
|
|
2439
2486
|
});
|
|
2440
2487
|
if (inputResult.block) {
|
|
@@ -2461,11 +2508,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2461
2508
|
let guardrailOutputRetries = 0;
|
|
2462
2509
|
let schemaRetries = 0;
|
|
2463
2510
|
let validateRetries = 0;
|
|
2511
|
+
const maxGuardrailRetries = guardrails?.maxRetries ?? 2;
|
|
2512
|
+
let pendingRetryReason;
|
|
2513
|
+
const verboseTrace = this.config.trace?.level === "full";
|
|
2464
2514
|
while (turns < maxTurns) {
|
|
2465
2515
|
if (Date.now() - startTime > timeoutMs) {
|
|
2466
2516
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
2467
2517
|
}
|
|
2468
2518
|
turns++;
|
|
2519
|
+
const turnStart = Date.now();
|
|
2469
2520
|
const chatOptions = {
|
|
2470
2521
|
model,
|
|
2471
2522
|
temperature: options?.temperature ?? agent2._config.temperature,
|
|
@@ -2546,16 +2597,22 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2546
2597
|
usageCapture.value = response.usage;
|
|
2547
2598
|
}
|
|
2548
2599
|
if (response.cost) {
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2600
|
+
this._accumulateBudgetCost(response.cost);
|
|
2601
|
+
}
|
|
2602
|
+
let messagesSnapshot;
|
|
2603
|
+
if (verboseTrace) {
|
|
2604
|
+
try {
|
|
2605
|
+
messagesSnapshot = structuredClone(currentMessages);
|
|
2606
|
+
} catch (err) {
|
|
2607
|
+
console.warn(
|
|
2608
|
+
"[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
|
|
2609
|
+
err instanceof Error ? err.message : String(err)
|
|
2610
|
+
);
|
|
2611
|
+
messagesSnapshot = [...currentMessages];
|
|
2557
2612
|
}
|
|
2558
2613
|
}
|
|
2614
|
+
const retryReason = pendingRetryReason;
|
|
2615
|
+
pendingRetryReason = void 0;
|
|
2559
2616
|
this.emitTrace({
|
|
2560
2617
|
type: "agent_call",
|
|
2561
2618
|
agent: agent2._name,
|
|
@@ -2567,8 +2624,25 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2567
2624
|
output: response.usage.completion_tokens,
|
|
2568
2625
|
reasoning: response.usage.reasoning_tokens
|
|
2569
2626
|
} : void 0,
|
|
2570
|
-
duration: Date.now() -
|
|
2571
|
-
data: {
|
|
2627
|
+
duration: Date.now() - turnStart,
|
|
2628
|
+
data: {
|
|
2629
|
+
prompt,
|
|
2630
|
+
response: response.content,
|
|
2631
|
+
...systemPrompt ? { system: systemPrompt } : {},
|
|
2632
|
+
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
2633
|
+
params: {
|
|
2634
|
+
...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
|
|
2635
|
+
maxTokens: chatOptions.maxTokens,
|
|
2636
|
+
...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
|
|
2637
|
+
...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
|
|
2638
|
+
...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
|
|
2639
|
+
...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
|
|
2640
|
+
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
2641
|
+
},
|
|
2642
|
+
turn: turns,
|
|
2643
|
+
...retryReason ? { retryReason } : {},
|
|
2644
|
+
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
2645
|
+
}
|
|
2572
2646
|
});
|
|
2573
2647
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2574
2648
|
currentMessages.push({
|
|
@@ -2633,7 +2707,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2633
2707
|
this.emitTrace({
|
|
2634
2708
|
type: "handoff",
|
|
2635
2709
|
agent: agent2._name,
|
|
2636
|
-
data: {
|
|
2710
|
+
data: {
|
|
2711
|
+
target: targetName,
|
|
2712
|
+
mode,
|
|
2713
|
+
duration,
|
|
2714
|
+
source: agent2._name,
|
|
2715
|
+
...handoffPrompt !== prompt ? { message: handoffPrompt } : {}
|
|
2716
|
+
}
|
|
2637
2717
|
});
|
|
2638
2718
|
return result;
|
|
2639
2719
|
}
|
|
@@ -2643,7 +2723,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2643
2723
|
this.emitTrace({
|
|
2644
2724
|
type: "handoff",
|
|
2645
2725
|
agent: agent2._name,
|
|
2646
|
-
data: {
|
|
2726
|
+
data: {
|
|
2727
|
+
target: targetName,
|
|
2728
|
+
mode,
|
|
2729
|
+
duration: Date.now() - handoffStart,
|
|
2730
|
+
source: agent2._name,
|
|
2731
|
+
...handoffPrompt !== prompt ? { message: handoffPrompt } : {}
|
|
2732
|
+
}
|
|
2647
2733
|
});
|
|
2648
2734
|
}
|
|
2649
2735
|
continue;
|
|
@@ -2663,7 +2749,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2663
2749
|
this.emitTrace({
|
|
2664
2750
|
type: "handoff",
|
|
2665
2751
|
agent: agent2._name,
|
|
2666
|
-
data: { target: targetName, mode, duration }
|
|
2752
|
+
data: { target: targetName, mode, duration, source: agent2._name }
|
|
2667
2753
|
});
|
|
2668
2754
|
return result;
|
|
2669
2755
|
}
|
|
@@ -2673,7 +2759,12 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2673
2759
|
this.emitTrace({
|
|
2674
2760
|
type: "handoff",
|
|
2675
2761
|
agent: agent2._name,
|
|
2676
|
-
data: {
|
|
2762
|
+
data: {
|
|
2763
|
+
target: targetName,
|
|
2764
|
+
mode,
|
|
2765
|
+
duration: Date.now() - handoffStart,
|
|
2766
|
+
source: agent2._name
|
|
2767
|
+
}
|
|
2677
2768
|
});
|
|
2678
2769
|
return onewayResult;
|
|
2679
2770
|
}
|
|
@@ -2766,24 +2857,18 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2766
2857
|
});
|
|
2767
2858
|
if (!decision.approved) {
|
|
2768
2859
|
const reason = decision.reason ?? "Denied by human";
|
|
2769
|
-
this.emitTrace({
|
|
2770
|
-
type: "tool_denied",
|
|
2771
|
-
agent: agent2._name,
|
|
2772
|
-
tool: toolName,
|
|
2773
|
-
data: { denied: true, reason, args: toolArgs }
|
|
2774
|
-
});
|
|
2775
2860
|
currentMessages.push({
|
|
2776
2861
|
role: "tool",
|
|
2777
2862
|
content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
|
|
2778
2863
|
tool_call_id: toolCall.id
|
|
2779
2864
|
});
|
|
2780
|
-
return false;
|
|
2865
|
+
return { approved: false, reason };
|
|
2781
2866
|
}
|
|
2782
|
-
return true;
|
|
2867
|
+
return { approved: true };
|
|
2783
2868
|
};
|
|
2784
|
-
let
|
|
2869
|
+
let approvalOutcome;
|
|
2785
2870
|
if (this.spanManager) {
|
|
2786
|
-
|
|
2871
|
+
approvalOutcome = await this.spanManager.withSpanAsync(
|
|
2787
2872
|
"axl.tool.approval",
|
|
2788
2873
|
{
|
|
2789
2874
|
"axl.tool.name": toolName,
|
|
@@ -2791,20 +2876,24 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2791
2876
|
},
|
|
2792
2877
|
async (span) => {
|
|
2793
2878
|
const result = await approvalFn();
|
|
2794
|
-
span.setAttribute("axl.tool.approval.approved", result);
|
|
2879
|
+
span.setAttribute("axl.tool.approval.approved", result.approved);
|
|
2795
2880
|
return result;
|
|
2796
2881
|
}
|
|
2797
2882
|
);
|
|
2798
2883
|
} else {
|
|
2799
|
-
|
|
2884
|
+
approvalOutcome = await approvalFn();
|
|
2800
2885
|
}
|
|
2801
|
-
if (!approved) continue;
|
|
2802
2886
|
this.emitTrace({
|
|
2803
|
-
type: "
|
|
2887
|
+
type: "tool_approval",
|
|
2804
2888
|
agent: agent2._name,
|
|
2805
2889
|
tool: toolName,
|
|
2806
|
-
data: {
|
|
2890
|
+
data: {
|
|
2891
|
+
approved: approvalOutcome.approved,
|
|
2892
|
+
args: toolArgs,
|
|
2893
|
+
...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
|
|
2894
|
+
}
|
|
2807
2895
|
});
|
|
2896
|
+
if (!approvalOutcome.approved) continue;
|
|
2808
2897
|
}
|
|
2809
2898
|
if (tool2 && tool2.hooks?.before) {
|
|
2810
2899
|
try {
|
|
@@ -2837,7 +2926,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2837
2926
|
resultContent2 = JSON.stringify(toolResult2);
|
|
2838
2927
|
}
|
|
2839
2928
|
} else if (tool2) {
|
|
2840
|
-
const childCtx = this.createChildContext();
|
|
2929
|
+
const childCtx = this.createChildContext(toolCall.id);
|
|
2841
2930
|
try {
|
|
2842
2931
|
toolResult2 = await tool2._execute(toolArgs, childCtx);
|
|
2843
2932
|
} catch (err) {
|
|
@@ -2897,37 +2986,43 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2897
2986
|
const content = response.content;
|
|
2898
2987
|
if (guardrails?.output) {
|
|
2899
2988
|
const outputResult = await guardrails.output(content, { metadata: this.metadata });
|
|
2989
|
+
const attempt = guardrailOutputRetries + 1;
|
|
2990
|
+
const maxAttempts = maxGuardrailRetries + 1;
|
|
2991
|
+
const onBlock = guardrails.onBlock ?? "throw";
|
|
2992
|
+
let feedbackMessage;
|
|
2993
|
+
if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
|
|
2994
|
+
feedbackMessage = `Your previous response was blocked by a safety guardrail: ${outputResult.reason ?? "Output blocked"}. Please provide a different response that complies with the guidelines.`;
|
|
2995
|
+
}
|
|
2900
2996
|
this.emitTrace({
|
|
2901
2997
|
type: "guardrail",
|
|
2902
2998
|
agent: agent2._name,
|
|
2903
2999
|
data: {
|
|
2904
3000
|
guardrailType: "output",
|
|
2905
3001
|
blocked: outputResult.block,
|
|
2906
|
-
reason: outputResult.reason
|
|
3002
|
+
...outputResult.reason ? { reason: outputResult.reason } : {},
|
|
3003
|
+
attempt,
|
|
3004
|
+
maxAttempts,
|
|
3005
|
+
...feedbackMessage ? { feedbackMessage } : {}
|
|
2907
3006
|
}
|
|
2908
3007
|
});
|
|
2909
3008
|
this.spanManager?.addEventToActiveSpan("axl.guardrail.check", {
|
|
2910
3009
|
"axl.guardrail.type": "output",
|
|
2911
3010
|
"axl.guardrail.blocked": outputResult.block,
|
|
3011
|
+
"axl.guardrail.attempt": attempt,
|
|
3012
|
+
"axl.guardrail.maxAttempts": maxAttempts,
|
|
2912
3013
|
...outputResult.reason ? { "axl.guardrail.reason": outputResult.reason } : {}
|
|
2913
3014
|
});
|
|
2914
3015
|
if (outputResult.block) {
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
currentMessages.push({
|
|
2926
|
-
role: "system",
|
|
2927
|
-
content: `Your previous response was blocked by a safety guardrail: ${outputResult.reason ?? "Output blocked"}. Please provide a different response that complies with the guidelines.`
|
|
2928
|
-
});
|
|
2929
|
-
continue;
|
|
2930
|
-
}
|
|
3016
|
+
if (feedbackMessage) {
|
|
3017
|
+
guardrailOutputRetries++;
|
|
3018
|
+
appendRetryMessages(
|
|
3019
|
+
currentMessages,
|
|
3020
|
+
content,
|
|
3021
|
+
feedbackMessage,
|
|
3022
|
+
response.providerMetadata
|
|
3023
|
+
);
|
|
3024
|
+
pendingRetryReason = "guardrail";
|
|
3025
|
+
continue;
|
|
2931
3026
|
}
|
|
2932
3027
|
if (typeof onBlock === "function") {
|
|
2933
3028
|
return onBlock(outputResult.reason ?? "Output blocked by guardrail", {
|
|
@@ -2939,30 +3034,58 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2939
3034
|
}
|
|
2940
3035
|
let validated = void 0;
|
|
2941
3036
|
if (options?.schema) {
|
|
3037
|
+
const maxSchemaRetries = options.retries ?? 3;
|
|
3038
|
+
const schemaAttempt = schemaRetries + 1;
|
|
3039
|
+
const schemaMaxAttempts = maxSchemaRetries + 1;
|
|
3040
|
+
let schemaValid = false;
|
|
3041
|
+
let schemaReason;
|
|
3042
|
+
let schemaFeedback;
|
|
3043
|
+
let schemaErr;
|
|
2942
3044
|
try {
|
|
2943
3045
|
const parsed = JSON.parse(extractJson(content));
|
|
2944
3046
|
validated = options.schema.parse(parsed);
|
|
3047
|
+
schemaValid = true;
|
|
2945
3048
|
} catch (err) {
|
|
2946
|
-
|
|
3049
|
+
schemaErr = err;
|
|
3050
|
+
schemaReason = err instanceof Error ? err.message : String(err);
|
|
2947
3051
|
if (schemaRetries < maxSchemaRetries) {
|
|
3052
|
+
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
this.emitTrace({
|
|
3056
|
+
type: "schema_check",
|
|
3057
|
+
agent: agent2._name,
|
|
3058
|
+
data: {
|
|
3059
|
+
valid: schemaValid,
|
|
3060
|
+
...schemaReason ? { reason: schemaReason } : {},
|
|
3061
|
+
attempt: schemaAttempt,
|
|
3062
|
+
maxAttempts: schemaMaxAttempts,
|
|
3063
|
+
...schemaFeedback ? { feedbackMessage: schemaFeedback } : {}
|
|
3064
|
+
}
|
|
3065
|
+
});
|
|
3066
|
+
this.spanManager?.addEventToActiveSpan("axl.schema.check", {
|
|
3067
|
+
"axl.schema.valid": schemaValid,
|
|
3068
|
+
"axl.schema.attempt": schemaAttempt,
|
|
3069
|
+
"axl.schema.maxAttempts": schemaMaxAttempts,
|
|
3070
|
+
...schemaReason ? { "axl.schema.reason": schemaReason } : {}
|
|
3071
|
+
});
|
|
3072
|
+
if (!schemaValid) {
|
|
3073
|
+
if (schemaFeedback) {
|
|
2948
3074
|
schemaRetries++;
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
role: "assistant",
|
|
3075
|
+
appendRetryMessages(
|
|
3076
|
+
currentMessages,
|
|
2952
3077
|
content,
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
content: `Your response was not valid JSON or did not match the required schema: ${errorMsg}. Please fix and try again.`
|
|
2958
|
-
});
|
|
3078
|
+
schemaFeedback,
|
|
3079
|
+
response.providerMetadata
|
|
3080
|
+
);
|
|
3081
|
+
pendingRetryReason = "schema";
|
|
2959
3082
|
continue;
|
|
2960
3083
|
}
|
|
2961
|
-
const zodErr =
|
|
3084
|
+
const zodErr = schemaErr instanceof ZodError ? schemaErr : new ZodError([
|
|
2962
3085
|
{
|
|
2963
3086
|
code: "custom",
|
|
2964
3087
|
path: [],
|
|
2965
|
-
message:
|
|
3088
|
+
message: schemaReason ?? "Schema parse failed"
|
|
2966
3089
|
}
|
|
2967
3090
|
]);
|
|
2968
3091
|
throw new VerifyError(content, zodErr, maxSchemaRetries);
|
|
@@ -2978,31 +3101,40 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2978
3101
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2979
3102
|
validateResult = { valid: false, reason: `Validator error: ${reason}` };
|
|
2980
3103
|
}
|
|
3104
|
+
const maxValidateRetries = options.validateRetries ?? 2;
|
|
3105
|
+
const validateAttempt = validateRetries + 1;
|
|
3106
|
+
const validateMaxAttempts = maxValidateRetries + 1;
|
|
3107
|
+
let validateFeedback;
|
|
3108
|
+
if (!validateResult.valid && validateRetries < maxValidateRetries) {
|
|
3109
|
+
validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
|
|
3110
|
+
}
|
|
2981
3111
|
this.emitTrace({
|
|
2982
3112
|
type: "validate",
|
|
2983
3113
|
agent: agent2._name,
|
|
2984
3114
|
data: {
|
|
2985
3115
|
valid: validateResult.valid,
|
|
2986
|
-
...validateResult.reason ? { reason: validateResult.reason } : {}
|
|
3116
|
+
...validateResult.reason ? { reason: validateResult.reason } : {},
|
|
3117
|
+
attempt: validateAttempt,
|
|
3118
|
+
maxAttempts: validateMaxAttempts,
|
|
3119
|
+
...validateFeedback ? { feedbackMessage: validateFeedback } : {}
|
|
2987
3120
|
}
|
|
2988
3121
|
});
|
|
2989
3122
|
this.spanManager?.addEventToActiveSpan("axl.validate.check", {
|
|
2990
3123
|
"axl.validate.valid": validateResult.valid,
|
|
3124
|
+
"axl.validate.attempt": validateAttempt,
|
|
3125
|
+
"axl.validate.maxAttempts": validateMaxAttempts,
|
|
2991
3126
|
...validateResult.reason ? { "axl.validate.reason": validateResult.reason } : {}
|
|
2992
3127
|
});
|
|
2993
3128
|
if (!validateResult.valid) {
|
|
2994
|
-
|
|
2995
|
-
if (validateRetries < maxValidateRetries) {
|
|
3129
|
+
if (validateFeedback) {
|
|
2996
3130
|
validateRetries++;
|
|
2997
|
-
|
|
2998
|
-
|
|
3131
|
+
appendRetryMessages(
|
|
3132
|
+
currentMessages,
|
|
2999
3133
|
content,
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
content: `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`
|
|
3005
|
-
});
|
|
3134
|
+
validateFeedback,
|
|
3135
|
+
response.providerMetadata
|
|
3136
|
+
);
|
|
3137
|
+
pendingRetryReason = "validate";
|
|
3006
3138
|
continue;
|
|
3007
3139
|
}
|
|
3008
3140
|
throw new ValidationError(
|
|
@@ -3371,6 +3503,17 @@ ${summaryResponse.content}`
|
|
|
3371
3503
|
async verify(fn, schema, options) {
|
|
3372
3504
|
const maxRetries = options?.retries ?? 3;
|
|
3373
3505
|
let lastRetry = void 0;
|
|
3506
|
+
const emitVerifyOutcome = (passed, attempts, lastError) => {
|
|
3507
|
+
this.emitTrace({
|
|
3508
|
+
type: "verify",
|
|
3509
|
+
agent: void 0,
|
|
3510
|
+
data: {
|
|
3511
|
+
passed,
|
|
3512
|
+
attempts,
|
|
3513
|
+
...lastError ? { lastError } : {}
|
|
3514
|
+
}
|
|
3515
|
+
});
|
|
3516
|
+
};
|
|
3374
3517
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3375
3518
|
let rawOutput;
|
|
3376
3519
|
try {
|
|
@@ -3389,12 +3532,14 @@ ${summaryResponse.content}`
|
|
|
3389
3532
|
const errorMsg = validateResult.reason ?? "Validation failed";
|
|
3390
3533
|
lastRetry = { error: errorMsg, output: rawOutput, parsed };
|
|
3391
3534
|
if (attempt === maxRetries) {
|
|
3535
|
+
emitVerifyOutcome(false, attempt + 1, errorMsg);
|
|
3392
3536
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3393
3537
|
throw new ValidationError(parsed, errorMsg, maxRetries);
|
|
3394
3538
|
}
|
|
3395
3539
|
continue;
|
|
3396
3540
|
}
|
|
3397
3541
|
}
|
|
3542
|
+
emitVerifyOutcome(true, attempt + 1);
|
|
3398
3543
|
return parsed;
|
|
3399
3544
|
} catch (err) {
|
|
3400
3545
|
if (err instanceof ValidationError) {
|
|
@@ -3404,6 +3549,7 @@ ${summaryResponse.content}`
|
|
|
3404
3549
|
parsed: err.lastOutput
|
|
3405
3550
|
};
|
|
3406
3551
|
if (attempt === maxRetries) {
|
|
3552
|
+
emitVerifyOutcome(false, attempt + 1, err.reason);
|
|
3407
3553
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3408
3554
|
throw err;
|
|
3409
3555
|
}
|
|
@@ -3412,6 +3558,7 @@ ${summaryResponse.content}`
|
|
|
3412
3558
|
if (err instanceof VerifyError) {
|
|
3413
3559
|
lastRetry = { error: err.message, output: rawOutput ?? err.lastOutput };
|
|
3414
3560
|
if (attempt === maxRetries) {
|
|
3561
|
+
emitVerifyOutcome(false, attempt + 1, err.message);
|
|
3415
3562
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3416
3563
|
throw err;
|
|
3417
3564
|
}
|
|
@@ -3420,12 +3567,14 @@ ${summaryResponse.content}`
|
|
|
3420
3567
|
const errorMsg = err instanceof ZodError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3421
3568
|
lastRetry = { error: errorMsg, output: rawOutput };
|
|
3422
3569
|
if (attempt === maxRetries) {
|
|
3570
|
+
emitVerifyOutcome(false, attempt + 1, errorMsg);
|
|
3423
3571
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3424
3572
|
const zodErr = err instanceof ZodError ? err : new ZodError([{ code: "custom", path: [], message: errorMsg }]);
|
|
3425
3573
|
throw new VerifyError(rawOutput, zodErr, maxRetries);
|
|
3426
3574
|
}
|
|
3427
3575
|
}
|
|
3428
3576
|
}
|
|
3577
|
+
emitVerifyOutcome(false, maxRetries + 1, lastRetry?.error ?? "Verify failed");
|
|
3429
3578
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3430
3579
|
throw new VerifyError(
|
|
3431
3580
|
lastRetry?.output,
|
|
@@ -3731,6 +3880,76 @@ ${summaryResponse.content}`
|
|
|
3731
3880
|
}
|
|
3732
3881
|
return decision;
|
|
3733
3882
|
}
|
|
3883
|
+
/**
|
|
3884
|
+
* Compose an `AbortSignal` for a memory operation's underlying embedder
|
|
3885
|
+
* fetch. Combines the parent context signal (user cancellation,
|
|
3886
|
+
* `runtime.execute({ signal })`) with the budget hard_stop abort so
|
|
3887
|
+
* that either cause correctly aborts an in-flight embed call.
|
|
3888
|
+
*
|
|
3889
|
+
* Returns `undefined` if there's nothing to cancel on — the embedder
|
|
3890
|
+
* runs without a signal in that case, matching its prior behavior.
|
|
3891
|
+
*
|
|
3892
|
+
* @internal
|
|
3893
|
+
*/
|
|
3894
|
+
_composeMemorySignal() {
|
|
3895
|
+
const budgetSignal = this.budgetContext?.abortController?.signal;
|
|
3896
|
+
if (this.signal && budgetSignal) return AbortSignal.any([this.signal, budgetSignal]);
|
|
3897
|
+
return this.signal ?? budgetSignal;
|
|
3898
|
+
}
|
|
3899
|
+
/**
|
|
3900
|
+
* Accumulate a cost amount into the active `budgetContext` and trip the
|
|
3901
|
+
* `exceeded` flag if we've crossed the limit. On `hard_stop` policy,
|
|
3902
|
+
* also fires the abort controller so in-flight operations cancel.
|
|
3903
|
+
*
|
|
3904
|
+
* Called from every code path that spends money: the `agent_call` loop,
|
|
3905
|
+
* semantic memory operations (`ctx.remember({embed:true})`, `ctx.recall({query})`),
|
|
3906
|
+
* and any future cost-emitting primitive. Centralizing the logic here
|
|
3907
|
+
* means `ctx.budget({ limit, policy })` accurately enforces the limit
|
|
3908
|
+
* across ALL cost sources — not just agent calls.
|
|
3909
|
+
*
|
|
3910
|
+
* @internal
|
|
3911
|
+
*/
|
|
3912
|
+
_accumulateBudgetCost(amount) {
|
|
3913
|
+
if (!this.budgetContext) return;
|
|
3914
|
+
if (!Number.isFinite(amount) || amount <= 0) return;
|
|
3915
|
+
this.budgetContext.totalCost += amount;
|
|
3916
|
+
if (this.budgetContext.totalCost >= this.budgetContext.limit) {
|
|
3917
|
+
this.budgetContext.exceeded = true;
|
|
3918
|
+
if (this.budgetContext.policy === "hard_stop" && this.budgetContext.abortController) {
|
|
3919
|
+
this.budgetContext.abortController.abort();
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
// ── workflow lifecycle trace emission ──────────────────────────────────
|
|
3924
|
+
//
|
|
3925
|
+
// These are called by the runtime at execution boundaries. They emit
|
|
3926
|
+
// first-class `workflow_start` / `workflow_end` trace events instead of
|
|
3927
|
+
// the previous `ctx.log('workflow_start', ...)` indirection — so consumers
|
|
3928
|
+
// that narrow via `event.type === 'workflow_start'` actually see them.
|
|
3929
|
+
// Internal: the runtime is the only caller, user workflows never call these.
|
|
3930
|
+
/** @internal */
|
|
3931
|
+
_emitWorkflowStart(input) {
|
|
3932
|
+
this.emitTrace({
|
|
3933
|
+
type: "workflow_start",
|
|
3934
|
+
workflow: this.workflowName,
|
|
3935
|
+
data: { input }
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
/** @internal */
|
|
3939
|
+
_emitWorkflowEnd(info) {
|
|
3940
|
+
this.emitTrace({
|
|
3941
|
+
type: "workflow_end",
|
|
3942
|
+
workflow: this.workflowName,
|
|
3943
|
+
duration: info.duration,
|
|
3944
|
+
data: {
|
|
3945
|
+
status: info.status,
|
|
3946
|
+
duration: info.duration,
|
|
3947
|
+
...info.result !== void 0 ? { result: info.result } : {},
|
|
3948
|
+
...info.error !== void 0 ? { error: info.error } : {},
|
|
3949
|
+
...info.aborted ? { aborted: true } : {}
|
|
3950
|
+
}
|
|
3951
|
+
});
|
|
3952
|
+
}
|
|
3734
3953
|
// ── ctx.log() ─────────────────────────────────────────────────────────
|
|
3735
3954
|
log(event, data) {
|
|
3736
3955
|
this.emitTrace({
|
|
@@ -3766,8 +3985,59 @@ ${summaryResponse.content}`
|
|
|
3766
3985
|
if (!this.stateStore) {
|
|
3767
3986
|
throw new Error("A state store is required for memory operations.");
|
|
3768
3987
|
}
|
|
3988
|
+
if (this.budgetContext?.exceeded) {
|
|
3989
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
3990
|
+
if (policy !== "warn") {
|
|
3991
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3769
3994
|
const sessionId = this.metadata?.sessionId;
|
|
3770
|
-
|
|
3995
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
3996
|
+
try {
|
|
3997
|
+
const memorySignal = this._composeMemorySignal();
|
|
3998
|
+
const { usage } = await this.memoryManager.remember(
|
|
3999
|
+
key,
|
|
4000
|
+
value,
|
|
4001
|
+
this.stateStore,
|
|
4002
|
+
sessionId,
|
|
4003
|
+
options,
|
|
4004
|
+
memorySignal
|
|
4005
|
+
);
|
|
4006
|
+
if (usage?.cost != null) {
|
|
4007
|
+
this._accumulateBudgetCost(usage.cost);
|
|
4008
|
+
}
|
|
4009
|
+
this.emitTrace({
|
|
4010
|
+
type: "log",
|
|
4011
|
+
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4012
|
+
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4013
|
+
data: {
|
|
4014
|
+
event: "memory_remember",
|
|
4015
|
+
key,
|
|
4016
|
+
scope,
|
|
4017
|
+
embed: options?.embed === true,
|
|
4018
|
+
...usage ? { usage } : {}
|
|
4019
|
+
}
|
|
4020
|
+
});
|
|
4021
|
+
} catch (err) {
|
|
4022
|
+
const partialUsage = err.axlEmbedUsage;
|
|
4023
|
+
if (partialUsage?.cost != null) {
|
|
4024
|
+
this._accumulateBudgetCost(partialUsage.cost);
|
|
4025
|
+
}
|
|
4026
|
+
this.emitTrace({
|
|
4027
|
+
type: "log",
|
|
4028
|
+
...partialUsage?.cost != null ? { cost: partialUsage.cost } : {},
|
|
4029
|
+
...partialUsage?.tokens != null ? { tokens: { input: partialUsage.tokens } } : {},
|
|
4030
|
+
data: {
|
|
4031
|
+
event: "memory_remember",
|
|
4032
|
+
key,
|
|
4033
|
+
scope,
|
|
4034
|
+
embed: options?.embed === true,
|
|
4035
|
+
...partialUsage ? { usage: partialUsage } : {},
|
|
4036
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4037
|
+
}
|
|
4038
|
+
});
|
|
4039
|
+
throw err;
|
|
4040
|
+
}
|
|
3771
4041
|
}
|
|
3772
4042
|
/**
|
|
3773
4043
|
* Recall a value from memory by key, or perform semantic search if query option is provided.
|
|
@@ -3781,8 +4051,64 @@ ${summaryResponse.content}`
|
|
|
3781
4051
|
if (!this.stateStore) {
|
|
3782
4052
|
throw new Error("A state store is required for memory operations.");
|
|
3783
4053
|
}
|
|
4054
|
+
if (this.budgetContext?.exceeded) {
|
|
4055
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
4056
|
+
if (policy !== "warn") {
|
|
4057
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
3784
4060
|
const sessionId = this.metadata?.sessionId;
|
|
3785
|
-
|
|
4061
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4062
|
+
const semantic = options?.query !== void 0;
|
|
4063
|
+
try {
|
|
4064
|
+
const memorySignal = this._composeMemorySignal();
|
|
4065
|
+
const { data, usage } = await this.memoryManager.recall(
|
|
4066
|
+
key,
|
|
4067
|
+
this.stateStore,
|
|
4068
|
+
sessionId,
|
|
4069
|
+
options,
|
|
4070
|
+
memorySignal
|
|
4071
|
+
);
|
|
4072
|
+
let hit;
|
|
4073
|
+
let resultCount;
|
|
4074
|
+
if (semantic) {
|
|
4075
|
+
resultCount = Array.isArray(data) ? data.length : 0;
|
|
4076
|
+
hit = resultCount > 0;
|
|
4077
|
+
} else {
|
|
4078
|
+
hit = data !== null && data !== void 0;
|
|
4079
|
+
}
|
|
4080
|
+
if (usage?.cost != null) {
|
|
4081
|
+
this._accumulateBudgetCost(usage.cost);
|
|
4082
|
+
}
|
|
4083
|
+
this.emitTrace({
|
|
4084
|
+
type: "log",
|
|
4085
|
+
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4086
|
+
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4087
|
+
data: {
|
|
4088
|
+
event: "memory_recall",
|
|
4089
|
+
key,
|
|
4090
|
+
scope,
|
|
4091
|
+
semantic,
|
|
4092
|
+
hit,
|
|
4093
|
+
...resultCount !== void 0 ? { resultCount } : {},
|
|
4094
|
+
...usage ? { usage } : {}
|
|
4095
|
+
}
|
|
4096
|
+
});
|
|
4097
|
+
return data;
|
|
4098
|
+
} catch (err) {
|
|
4099
|
+
this.emitTrace({
|
|
4100
|
+
type: "log",
|
|
4101
|
+
data: {
|
|
4102
|
+
event: "memory_recall",
|
|
4103
|
+
key,
|
|
4104
|
+
scope,
|
|
4105
|
+
semantic,
|
|
4106
|
+
hit: false,
|
|
4107
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4108
|
+
}
|
|
4109
|
+
});
|
|
4110
|
+
throw err;
|
|
4111
|
+
}
|
|
3786
4112
|
}
|
|
3787
4113
|
/** Delete a memory entry by key. */
|
|
3788
4114
|
async forget(key, options) {
|
|
@@ -3795,7 +4121,25 @@ ${summaryResponse.content}`
|
|
|
3795
4121
|
throw new Error("A state store is required for memory operations.");
|
|
3796
4122
|
}
|
|
3797
4123
|
const sessionId = this.metadata?.sessionId;
|
|
3798
|
-
|
|
4124
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4125
|
+
try {
|
|
4126
|
+
await this.memoryManager.forget(key, this.stateStore, sessionId, options);
|
|
4127
|
+
this.emitTrace({
|
|
4128
|
+
type: "log",
|
|
4129
|
+
data: { event: "memory_forget", key, scope }
|
|
4130
|
+
});
|
|
4131
|
+
} catch (err) {
|
|
4132
|
+
this.emitTrace({
|
|
4133
|
+
type: "log",
|
|
4134
|
+
data: {
|
|
4135
|
+
event: "memory_forget",
|
|
4136
|
+
key,
|
|
4137
|
+
scope,
|
|
4138
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4139
|
+
}
|
|
4140
|
+
});
|
|
4141
|
+
throw err;
|
|
4142
|
+
}
|
|
3799
4143
|
}
|
|
3800
4144
|
// ── ctx.delegate() ──────────────────────────────────────────────────
|
|
3801
4145
|
/**
|
|
@@ -3823,6 +4167,15 @@ ${summaryResponse.content}`
|
|
|
3823
4167
|
names.add(a._name);
|
|
3824
4168
|
}
|
|
3825
4169
|
if (agents.length === 1) {
|
|
4170
|
+
this.emitTrace({
|
|
4171
|
+
type: "delegate",
|
|
4172
|
+
agent: agents[0]._name,
|
|
4173
|
+
data: {
|
|
4174
|
+
candidates: [agents[0]._name],
|
|
4175
|
+
selected: agents[0]._name,
|
|
4176
|
+
reason: "single_candidate"
|
|
4177
|
+
}
|
|
4178
|
+
});
|
|
3826
4179
|
return this.ask(agents[0], prompt, {
|
|
3827
4180
|
schema: options?.schema,
|
|
3828
4181
|
retries: options?.retries,
|
|
@@ -3863,7 +4216,8 @@ ${summaryResponse.content}`
|
|
|
3863
4216
|
agent: "_delegate_router",
|
|
3864
4217
|
data: {
|
|
3865
4218
|
candidates: agents.map((a) => a._name),
|
|
3866
|
-
routerModel: routerModelUri
|
|
4219
|
+
routerModel: routerModelUri,
|
|
4220
|
+
reason: "routed"
|
|
3867
4221
|
}
|
|
3868
4222
|
});
|
|
3869
4223
|
return this.ask(routerAgent, prompt, {
|
|
@@ -3875,19 +4229,117 @@ ${summaryResponse.content}`
|
|
|
3875
4229
|
});
|
|
3876
4230
|
}
|
|
3877
4231
|
// ── Private ───────────────────────────────────────────────────────────
|
|
4232
|
+
/**
|
|
4233
|
+
* Internal emitter input — intentionally loose so call sites don't need to
|
|
4234
|
+
* build a perfectly-narrowed discriminated-union member. The resulting
|
|
4235
|
+
* `TraceEvent` (exported type) remains strict, and TypeScript narrows it at
|
|
4236
|
+
* consumer sites via the `type` discriminator.
|
|
4237
|
+
*/
|
|
3878
4238
|
emitTrace(partial) {
|
|
3879
4239
|
let data = partial.data;
|
|
3880
|
-
if (this.config.trace?.redact &&
|
|
3881
|
-
|
|
4240
|
+
if (this.config.trace?.redact && data) {
|
|
4241
|
+
if (partial.type === "agent_call") {
|
|
4242
|
+
const d = data;
|
|
4243
|
+
const redacted = {
|
|
4244
|
+
...d,
|
|
4245
|
+
prompt: "[redacted]",
|
|
4246
|
+
response: "[redacted]"
|
|
4247
|
+
};
|
|
4248
|
+
if (d.system !== void 0) redacted.system = "[redacted]";
|
|
4249
|
+
if (d.thinking !== void 0) redacted.thinking = "[redacted]";
|
|
4250
|
+
if (Array.isArray(d.messages)) {
|
|
4251
|
+
redacted.messages = [
|
|
4252
|
+
{ role: "system", content: `[${d.messages.length} messages redacted]` }
|
|
4253
|
+
];
|
|
4254
|
+
}
|
|
4255
|
+
data = redacted;
|
|
4256
|
+
} else if (partial.type === "guardrail" || partial.type === "schema_check" || partial.type === "validate") {
|
|
4257
|
+
const d = data;
|
|
4258
|
+
const needsRedact = d.feedbackMessage !== void 0 || d.reason !== void 0;
|
|
4259
|
+
if (needsRedact) {
|
|
4260
|
+
data = {
|
|
4261
|
+
...d,
|
|
4262
|
+
...d.reason !== void 0 ? { reason: "[redacted]" } : {},
|
|
4263
|
+
...d.feedbackMessage !== void 0 ? { feedbackMessage: "[redacted]" } : {}
|
|
4264
|
+
};
|
|
4265
|
+
}
|
|
4266
|
+
} else if (partial.type === "tool_call") {
|
|
4267
|
+
const d = data;
|
|
4268
|
+
data = {
|
|
4269
|
+
...d,
|
|
4270
|
+
args: "[redacted]",
|
|
4271
|
+
result: "[redacted]"
|
|
4272
|
+
};
|
|
4273
|
+
} else if (partial.type === "tool_approval") {
|
|
4274
|
+
const d = data;
|
|
4275
|
+
data = {
|
|
4276
|
+
...d,
|
|
4277
|
+
args: "[redacted]",
|
|
4278
|
+
...d.reason !== void 0 ? { reason: "[redacted]" } : {}
|
|
4279
|
+
};
|
|
4280
|
+
} else if (partial.type === "handoff") {
|
|
4281
|
+
const d = data;
|
|
4282
|
+
if (d.message !== void 0) {
|
|
4283
|
+
data = { ...d, message: "[redacted]" };
|
|
4284
|
+
}
|
|
4285
|
+
} else if (partial.type === "workflow_start") {
|
|
4286
|
+
const d = data;
|
|
4287
|
+
if (d.input !== void 0) {
|
|
4288
|
+
data = { ...d, input: "[redacted]" };
|
|
4289
|
+
}
|
|
4290
|
+
} else if (partial.type === "workflow_end") {
|
|
4291
|
+
const d = data;
|
|
4292
|
+
const redacted = { ...d };
|
|
4293
|
+
if (d.result !== void 0) redacted.result = "[redacted]";
|
|
4294
|
+
if (d.error !== void 0) redacted.error = "[redacted]";
|
|
4295
|
+
data = redacted;
|
|
4296
|
+
} else if (partial.type === "log") {
|
|
4297
|
+
const d = data;
|
|
4298
|
+
const redacted = {};
|
|
4299
|
+
for (const [k, v] of Object.entries(d)) {
|
|
4300
|
+
if (k === "event" || typeof v === "number" || typeof v === "boolean") {
|
|
4301
|
+
redacted[k] = v;
|
|
4302
|
+
} else if (typeof v === "string") {
|
|
4303
|
+
redacted[k] = "[redacted]";
|
|
4304
|
+
} else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
4305
|
+
const inner = v;
|
|
4306
|
+
const innerRedacted = {};
|
|
4307
|
+
for (const [ik, iv] of Object.entries(inner)) {
|
|
4308
|
+
if (typeof iv === "number" || typeof iv === "boolean") {
|
|
4309
|
+
innerRedacted[ik] = iv;
|
|
4310
|
+
} else if (typeof iv === "string") {
|
|
4311
|
+
innerRedacted[ik] = "[redacted]";
|
|
4312
|
+
} else {
|
|
4313
|
+
innerRedacted[ik] = "[redacted]";
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
redacted[k] = innerRedacted;
|
|
4317
|
+
} else {
|
|
4318
|
+
redacted[k] = "[redacted]";
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
data = redacted;
|
|
4322
|
+
}
|
|
3882
4323
|
}
|
|
3883
4324
|
const event = {
|
|
3884
4325
|
executionId: this.executionId,
|
|
3885
4326
|
step: this.stepCounter++,
|
|
3886
4327
|
timestamp: Date.now(),
|
|
4328
|
+
...this.workflowName ? { workflow: this.workflowName } : {},
|
|
4329
|
+
...this.parentToolCallId ? { parentToolCallId: this.parentToolCallId } : {},
|
|
3887
4330
|
...partial,
|
|
3888
4331
|
data
|
|
3889
4332
|
};
|
|
3890
|
-
this.onTrace
|
|
4333
|
+
if (this.onTrace) {
|
|
4334
|
+
try {
|
|
4335
|
+
this.onTrace(event);
|
|
4336
|
+
} catch (err) {
|
|
4337
|
+
console.error(
|
|
4338
|
+
"[axl] onTrace handler threw; trace event dropped:",
|
|
4339
|
+
err instanceof Error ? err.message : String(err)
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
3891
4343
|
}
|
|
3892
4344
|
};
|
|
3893
4345
|
|
|
@@ -5129,6 +5581,18 @@ var McpManager = class {
|
|
|
5129
5581
|
};
|
|
5130
5582
|
|
|
5131
5583
|
// src/memory/manager.ts
|
|
5584
|
+
function assertEmbedResult(result) {
|
|
5585
|
+
if (Array.isArray(result)) {
|
|
5586
|
+
throw new Error(
|
|
5587
|
+
"Embedder.embed() returned a bare number[][] (legacy shape). As of @axlsdk/axl 0.15, Embedder.embed() must return { vectors: number[][]; usage?: { tokens?, cost?, model? } }. Wrap your existing return value: `return { vectors: await old() }`."
|
|
5588
|
+
);
|
|
5589
|
+
}
|
|
5590
|
+
if (!result || typeof result !== "object" || !Array.isArray(result.vectors)) {
|
|
5591
|
+
throw new Error(
|
|
5592
|
+
`Embedder.embed() must return { vectors: number[][]; usage?: EmbedUsage }. Got ${result === null ? "null" : typeof result}.`
|
|
5593
|
+
);
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5132
5596
|
var MemoryManager = class {
|
|
5133
5597
|
vectorStore;
|
|
5134
5598
|
embedder;
|
|
@@ -5143,8 +5607,11 @@ var MemoryManager = class {
|
|
|
5143
5607
|
* Store a key-value pair in memory.
|
|
5144
5608
|
* If a vector store and embedder are configured and embed is true,
|
|
5145
5609
|
* also embeds the value for semantic search.
|
|
5610
|
+
*
|
|
5611
|
+
* Returns `{ usage }` when embedding happened, carrying the embedder's
|
|
5612
|
+
* cost/token reporting out to the caller for cost attribution.
|
|
5146
5613
|
*/
|
|
5147
|
-
async remember(key, value, stateStore, sessionId, options) {
|
|
5614
|
+
async remember(key, value, stateStore, sessionId, options, signal) {
|
|
5148
5615
|
const scope = options?.scope ?? "session";
|
|
5149
5616
|
if (scope === "session" && !sessionId) {
|
|
5150
5617
|
throw new Error(
|
|
@@ -5163,23 +5630,44 @@ var MemoryManager = class {
|
|
|
5163
5630
|
}
|
|
5164
5631
|
if (options?.embed === true && this.vectorStore && this.embedder) {
|
|
5165
5632
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
5166
|
-
const
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5633
|
+
const raw = await this.embedder.embed([text], signal);
|
|
5634
|
+
assertEmbedResult(raw);
|
|
5635
|
+
const { vectors, usage } = raw;
|
|
5636
|
+
try {
|
|
5637
|
+
await this.vectorStore.upsert([
|
|
5638
|
+
{
|
|
5639
|
+
id: `${storeScope}:${key}`,
|
|
5640
|
+
content: text,
|
|
5641
|
+
embedding: vectors[0],
|
|
5642
|
+
metadata: { key, scope, ...options?.metadata }
|
|
5643
|
+
}
|
|
5644
|
+
]);
|
|
5645
|
+
} catch (err) {
|
|
5646
|
+
if (usage && typeof err === "object" && err !== null) {
|
|
5647
|
+
Object.defineProperty(err, "axlEmbedUsage", {
|
|
5648
|
+
value: usage,
|
|
5649
|
+
enumerable: false,
|
|
5650
|
+
writable: true,
|
|
5651
|
+
configurable: true
|
|
5652
|
+
});
|
|
5173
5653
|
}
|
|
5174
|
-
|
|
5654
|
+
throw err;
|
|
5655
|
+
}
|
|
5656
|
+
return usage ? { usage } : {};
|
|
5175
5657
|
}
|
|
5658
|
+
return {};
|
|
5176
5659
|
}
|
|
5177
5660
|
/**
|
|
5178
5661
|
* Recall a value from memory by key, or perform semantic search if query is provided.
|
|
5662
|
+
*
|
|
5663
|
+
* Always returns `{ data }`; semantic recalls additionally include
|
|
5664
|
+
* `usage` from the embedder (when the embedder reported it).
|
|
5179
5665
|
*/
|
|
5180
|
-
async recall(key, stateStore, sessionId, options) {
|
|
5666
|
+
async recall(key, stateStore, sessionId, options, signal) {
|
|
5181
5667
|
if (options?.query && this.vectorStore && this.embedder) {
|
|
5182
|
-
const
|
|
5668
|
+
const raw = await this.embedder.embed([options.query], signal);
|
|
5669
|
+
assertEmbedResult(raw);
|
|
5670
|
+
const { vectors, usage } = raw;
|
|
5183
5671
|
const topK = options?.topK ?? 5;
|
|
5184
5672
|
const searchScope = options?.scope ?? "session";
|
|
5185
5673
|
if (searchScope === "session" && !sessionId) {
|
|
@@ -5187,13 +5675,14 @@ var MemoryManager = class {
|
|
|
5187
5675
|
'sessionId is required for session-scoped memory. Use { scope: "global" } or provide a sessionId in metadata.'
|
|
5188
5676
|
);
|
|
5189
5677
|
}
|
|
5190
|
-
const rawResults = await this.vectorStore.search(
|
|
5678
|
+
const rawResults = await this.vectorStore.search(vectors[0], topK * 3);
|
|
5191
5679
|
const filtered = rawResults.filter((r) => {
|
|
5192
5680
|
if (r.id.startsWith("global:")) return true;
|
|
5193
5681
|
if (searchScope === "session") return r.id.startsWith(`session:${sessionId}:`);
|
|
5194
5682
|
return true;
|
|
5195
5683
|
});
|
|
5196
|
-
|
|
5684
|
+
const data = filtered.slice(0, topK);
|
|
5685
|
+
return usage ? { data, usage } : { data };
|
|
5197
5686
|
}
|
|
5198
5687
|
const scope = options?.scope ?? "session";
|
|
5199
5688
|
if (scope === "session" && !sessionId) {
|
|
@@ -5203,10 +5692,10 @@ var MemoryManager = class {
|
|
|
5203
5692
|
}
|
|
5204
5693
|
const storeScope = scope === "session" ? `session:${sessionId}` : "global";
|
|
5205
5694
|
if (stateStore.getMemory) {
|
|
5206
|
-
return stateStore.getMemory(storeScope, key);
|
|
5695
|
+
return { data: await stateStore.getMemory(storeScope, key) };
|
|
5207
5696
|
}
|
|
5208
5697
|
const storeKey = `memory:${storeScope}:${key}`;
|
|
5209
|
-
return stateStore.getSessionMeta(storeKey, "value");
|
|
5698
|
+
return { data: await stateStore.getSessionMeta(storeKey, "value") };
|
|
5210
5699
|
}
|
|
5211
5700
|
/** Delete a memory entry. If a vector store is configured, also removes the embedding. */
|
|
5212
5701
|
async forget(key, stateStore, sessionId, options) {
|
|
@@ -5306,6 +5795,22 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5306
5795
|
});
|
|
5307
5796
|
}
|
|
5308
5797
|
}
|
|
5798
|
+
/**
|
|
5799
|
+
* Whether `config.trace.redact` is enabled on this runtime. A narrow
|
|
5800
|
+
* boolean getter is preferred over exposing the full config because:
|
|
5801
|
+
* (a) `Readonly<AxlConfig>` is shallow so the config would be mutable
|
|
5802
|
+
* at runtime through sub-objects, subverting any compliance guarantee;
|
|
5803
|
+
* (b) observability consumers like Studio only need the boolean, not
|
|
5804
|
+
* the whole config tree; (c) if future observability config needs to
|
|
5805
|
+
* expand, each new flag gets its own narrow getter.
|
|
5806
|
+
*
|
|
5807
|
+
* Studio's server-side redaction helpers (executions, memory, sessions,
|
|
5808
|
+
* evals, decisions, tools, playground, workflows) all route through
|
|
5809
|
+
* this accessor to decide whether to scrub response payloads.
|
|
5810
|
+
*/
|
|
5811
|
+
isRedactEnabled() {
|
|
5812
|
+
return this.config.trace?.redact === true;
|
|
5813
|
+
}
|
|
5309
5814
|
/**
|
|
5310
5815
|
* Initialize MCP servers configured in the config.
|
|
5311
5816
|
* Call this before executing workflows that use MCP tools.
|
|
@@ -5455,10 +5960,19 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5455
5960
|
metadata: result2.metadata ?? metadata
|
|
5456
5961
|
};
|
|
5457
5962
|
};
|
|
5458
|
-
result = await runEvalFn(entry.config, wrappedExecuteFn, this
|
|
5963
|
+
result = await runEvalFn(entry.config, wrappedExecuteFn, this, {
|
|
5964
|
+
onProgress: options?.onProgress,
|
|
5965
|
+
signal: options?.signal,
|
|
5966
|
+
captureTraces: options?.captureTraces
|
|
5967
|
+
});
|
|
5459
5968
|
} else {
|
|
5460
5969
|
result = await this.eval(
|
|
5461
|
-
entry.config
|
|
5970
|
+
entry.config,
|
|
5971
|
+
{
|
|
5972
|
+
onProgress: options?.onProgress,
|
|
5973
|
+
signal: options?.signal,
|
|
5974
|
+
captureTraces: options?.captureTraces
|
|
5975
|
+
}
|
|
5462
5976
|
);
|
|
5463
5977
|
}
|
|
5464
5978
|
const resultObj = result;
|
|
@@ -5596,6 +6110,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5596
6110
|
}
|
|
5597
6111
|
},
|
|
5598
6112
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
6113
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5599
6114
|
stateStore: this.stateStore,
|
|
5600
6115
|
workflowName: name,
|
|
5601
6116
|
mcpManager: this.mcpManager,
|
|
@@ -5609,7 +6124,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5609
6124
|
policy: "finish_and_stop"
|
|
5610
6125
|
}
|
|
5611
6126
|
});
|
|
5612
|
-
ctx.log("workflow_start", { workflow: name, executionId });
|
|
5613
6127
|
return this.spanManager.withSpanAsync(
|
|
5614
6128
|
"axl.workflow.execute",
|
|
5615
6129
|
{
|
|
@@ -5619,17 +6133,17 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5619
6133
|
},
|
|
5620
6134
|
async (span) => {
|
|
5621
6135
|
try {
|
|
6136
|
+
ctx._emitWorkflowStart(validated);
|
|
5622
6137
|
const result = await workflow2.handler(ctx);
|
|
5623
6138
|
const output = workflow2.outputSchema ? workflow2.outputSchema.parse(result) : result;
|
|
5624
6139
|
execInfo.status = "completed";
|
|
5625
6140
|
execInfo.completedAt = Date.now();
|
|
5626
6141
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5627
6142
|
execInfo.result = output;
|
|
5628
|
-
ctx.
|
|
5629
|
-
workflow: name,
|
|
6143
|
+
ctx._emitWorkflowEnd({
|
|
5630
6144
|
status: "completed",
|
|
5631
6145
|
duration: execInfo.duration,
|
|
5632
|
-
|
|
6146
|
+
result: output
|
|
5633
6147
|
});
|
|
5634
6148
|
if (this.stateStore.deleteCheckpoints) {
|
|
5635
6149
|
await this.stateStore.deleteCheckpoints(executionId);
|
|
@@ -5639,11 +6153,17 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5639
6153
|
this.persistExecution(execInfo);
|
|
5640
6154
|
return output;
|
|
5641
6155
|
} catch (err) {
|
|
6156
|
+
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
5642
6157
|
execInfo.status = "failed";
|
|
5643
6158
|
execInfo.completedAt = Date.now();
|
|
5644
6159
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5645
6160
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5646
|
-
ctx.
|
|
6161
|
+
ctx._emitWorkflowEnd({
|
|
6162
|
+
status: "failed",
|
|
6163
|
+
duration: execInfo.duration,
|
|
6164
|
+
error: execInfo.error,
|
|
6165
|
+
...aborted ? { aborted: true } : {}
|
|
6166
|
+
});
|
|
5647
6167
|
this.persistExecution(execInfo);
|
|
5648
6168
|
throw err;
|
|
5649
6169
|
} finally {
|
|
@@ -5708,24 +6228,16 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5708
6228
|
duration: data?.duration ?? void 0
|
|
5709
6229
|
});
|
|
5710
6230
|
}
|
|
5711
|
-
} else if (event.type === "
|
|
6231
|
+
} else if (event.type === "tool_approval") {
|
|
5712
6232
|
const data = event.data;
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
axlStream._push({
|
|
5722
|
-
type: "tool_approval",
|
|
5723
|
-
name: event.tool ?? "",
|
|
5724
|
-
args: data?.args,
|
|
5725
|
-
approved: false,
|
|
5726
|
-
reason: data?.reason ?? void 0
|
|
5727
|
-
});
|
|
5728
|
-
}
|
|
6233
|
+
axlStream._push({
|
|
6234
|
+
type: "tool_approval",
|
|
6235
|
+
name: event.tool ?? "",
|
|
6236
|
+
args: data?.args,
|
|
6237
|
+
approved: data?.approved === true,
|
|
6238
|
+
...data?.reason ? { reason: data.reason } : {}
|
|
6239
|
+
});
|
|
6240
|
+
} else if (event.type === "tool_denied") {
|
|
5729
6241
|
} else if (event.type === "agent_call") {
|
|
5730
6242
|
axlStream._push({
|
|
5731
6243
|
type: "agent_end",
|
|
@@ -5758,6 +6270,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5758
6270
|
axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
|
|
5759
6271
|
},
|
|
5760
6272
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
6273
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5761
6274
|
stateStore: this.stateStore,
|
|
5762
6275
|
workflowName: name,
|
|
5763
6276
|
mcpManager: this.mcpManager,
|
|
@@ -5781,17 +6294,17 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5781
6294
|
},
|
|
5782
6295
|
async (span) => {
|
|
5783
6296
|
try {
|
|
6297
|
+
wfCtx._emitWorkflowStart(validated);
|
|
5784
6298
|
const rawResult = await workflow2.handler(wfCtx);
|
|
5785
6299
|
const result = workflow2.outputSchema ? workflow2.outputSchema.parse(rawResult) : rawResult;
|
|
5786
6300
|
execInfo.status = "completed";
|
|
5787
6301
|
execInfo.completedAt = Date.now();
|
|
5788
6302
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5789
6303
|
execInfo.result = result;
|
|
5790
|
-
wfCtx.
|
|
5791
|
-
workflow: name,
|
|
6304
|
+
wfCtx._emitWorkflowEnd({
|
|
5792
6305
|
status: "completed",
|
|
5793
6306
|
duration: execInfo.duration,
|
|
5794
|
-
|
|
6307
|
+
result
|
|
5795
6308
|
});
|
|
5796
6309
|
if (this.stateStore.deleteCheckpoints) {
|
|
5797
6310
|
await this.stateStore.deleteCheckpoints(executionId);
|
|
@@ -5808,14 +6321,16 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5808
6321
|
};
|
|
5809
6322
|
run().then((result) => axlStream._done(result)).catch((err) => {
|
|
5810
6323
|
if (execInfo) {
|
|
6324
|
+
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
5811
6325
|
execInfo.status = "failed";
|
|
5812
6326
|
execInfo.completedAt = Date.now();
|
|
5813
6327
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5814
6328
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5815
|
-
ctx?.
|
|
5816
|
-
workflow: name,
|
|
6329
|
+
ctx?._emitWorkflowEnd({
|
|
5817
6330
|
status: "failed",
|
|
5818
|
-
|
|
6331
|
+
duration: execInfo.duration,
|
|
6332
|
+
error: execInfo.error,
|
|
6333
|
+
...aborted ? { aborted: true } : {}
|
|
5819
6334
|
});
|
|
5820
6335
|
this.persistExecution(execInfo);
|
|
5821
6336
|
}
|
|
@@ -5880,6 +6395,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5880
6395
|
if (this.stateStore.saveEvalResult) {
|
|
5881
6396
|
await this.stateStore.saveEvalResult(entry);
|
|
5882
6397
|
}
|
|
6398
|
+
this.emit("eval_result", entry);
|
|
5883
6399
|
}
|
|
5884
6400
|
/**
|
|
5885
6401
|
* Delete an eval history entry by id. Removes from in-memory cache and
|
|
@@ -6011,7 +6527,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6011
6527
|
*
|
|
6012
6528
|
* @see Spec Section 13.5
|
|
6013
6529
|
*/
|
|
6014
|
-
async eval(config) {
|
|
6530
|
+
async eval(config, options) {
|
|
6015
6531
|
let runEvalFn;
|
|
6016
6532
|
try {
|
|
6017
6533
|
({ runEval: runEvalFn } = await import("@axlsdk/eval"));
|
|
@@ -6026,7 +6542,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6026
6542
|
});
|
|
6027
6543
|
return { output: result, cost, metadata };
|
|
6028
6544
|
};
|
|
6029
|
-
return runEvalFn(config, executeWorkflow, this);
|
|
6545
|
+
return runEvalFn(config, executeWorkflow, this, options);
|
|
6030
6546
|
}
|
|
6031
6547
|
/**
|
|
6032
6548
|
* Compare two evaluation results to detect regressions and improvements.
|
|
@@ -6064,9 +6580,31 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6064
6580
|
* Returns cost (same as `trackCost`) plus metadata extracted from trace events:
|
|
6065
6581
|
* models (unique URIs), tokens (input/output/reasoning sums), and agent call count.
|
|
6066
6582
|
*
|
|
6583
|
+
* ## Cost vs tokens semantics
|
|
6584
|
+
*
|
|
6585
|
+
* - `cost` is the full aggregate across EVERY event with a top-level
|
|
6586
|
+
* `event.cost` set: agent calls, tool calls, semantic memory ops, etc.
|
|
6587
|
+
* This is the number to reconcile against your provider bill.
|
|
6588
|
+
*
|
|
6589
|
+
* - `metadata.tokens` is narrowly scoped to **agent** prompt/completion/
|
|
6590
|
+
* reasoning tokens. Embedder tokens from semantic `ctx.remember({embed:true})`
|
|
6591
|
+
* / `ctx.recall({query})` are deliberately NOT summed here — they're a
|
|
6592
|
+
* different category (input-only, different pricing, different model).
|
|
6593
|
+
* Conflating them would make "prompt tokens" misleading in the UI. If you
|
|
6594
|
+
* need embedder token counts, subscribe to `runtime.on('trace', ...)` and
|
|
6595
|
+
* read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
|
|
6596
|
+
*
|
|
6597
|
+
* Pass `{ captureTraces: true }` to also collect the raw `TraceEvent[]` observed
|
|
6598
|
+
* during `fn()`. This is opt-in because it keeps every event in memory for the
|
|
6599
|
+
* duration of the call — useful for eval per-item capture, debugging, and test
|
|
6600
|
+
* assertions, but overhead grows with trace volume. When enabled, verbose-mode
|
|
6601
|
+
* `agent_call.data.messages` snapshots are omitted from captured events (still
|
|
6602
|
+
* broadcast via onTrace) to keep memory bounded — callers who need the full
|
|
6603
|
+
* verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
|
|
6604
|
+
*
|
|
6067
6605
|
* Works with both `createContext()` and `execute()` calls inside `fn`.
|
|
6068
6606
|
*/
|
|
6069
|
-
async trackExecution(fn) {
|
|
6607
|
+
async trackExecution(fn, options) {
|
|
6070
6608
|
const parentScope = costScopeStorage.getStore();
|
|
6071
6609
|
const scope = {
|
|
6072
6610
|
totalCost: 0,
|
|
@@ -6077,6 +6615,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6077
6615
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
6078
6616
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
6079
6617
|
let agentCalls = 0;
|
|
6618
|
+
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
6080
6619
|
const listener = (event) => {
|
|
6081
6620
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
6082
6621
|
if (event.cost) scope.totalCost += event.cost;
|
|
@@ -6089,17 +6628,22 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6089
6628
|
tokens.reasoning += event.tokens.reasoning ?? 0;
|
|
6090
6629
|
}
|
|
6091
6630
|
}
|
|
6092
|
-
let workflowName;
|
|
6093
6631
|
if (event.type === "workflow_start" && event.workflow) {
|
|
6094
|
-
|
|
6095
|
-
} else if (event.type === "log" && event.data && typeof event.data === "object") {
|
|
6096
|
-
const d = event.data;
|
|
6097
|
-
if (d.event === "workflow_start" && typeof d.workflow === "string") {
|
|
6098
|
-
workflowName = d.workflow;
|
|
6099
|
-
}
|
|
6632
|
+
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
6100
6633
|
}
|
|
6101
|
-
if (
|
|
6102
|
-
|
|
6634
|
+
if (capturedTraces) {
|
|
6635
|
+
if (event.type === "agent_call" && event.data) {
|
|
6636
|
+
const d = event.data;
|
|
6637
|
+
if ("messages" in d) {
|
|
6638
|
+
const rest = {};
|
|
6639
|
+
for (const k of Object.keys(d)) {
|
|
6640
|
+
if (k !== "messages") rest[k] = d[k];
|
|
6641
|
+
}
|
|
6642
|
+
capturedTraces.push({ ...event, data: rest });
|
|
6643
|
+
return;
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
capturedTraces.push(event);
|
|
6103
6647
|
}
|
|
6104
6648
|
};
|
|
6105
6649
|
this.setMaxListeners(this.getMaxListeners() + 1);
|
|
@@ -6109,6 +6653,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6109
6653
|
return {
|
|
6110
6654
|
result,
|
|
6111
6655
|
cost: scope.totalCost,
|
|
6656
|
+
...capturedTraces ? { traces: capturedTraces } : {},
|
|
6112
6657
|
metadata: {
|
|
6113
6658
|
models: [...modelCalls.keys()],
|
|
6114
6659
|
modelCallCounts: modelCalls.size > 0 ? Object.fromEntries(modelCalls) : void 0,
|
|
@@ -6118,6 +6663,16 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6118
6663
|
workflowCallCounts: workflowCalls.size > 0 ? Object.fromEntries(workflowCalls) : void 0
|
|
6119
6664
|
}
|
|
6120
6665
|
};
|
|
6666
|
+
} catch (err) {
|
|
6667
|
+
if (capturedTraces && typeof err === "object" && err !== null) {
|
|
6668
|
+
Object.defineProperty(err, "axlCapturedTraces", {
|
|
6669
|
+
value: capturedTraces,
|
|
6670
|
+
enumerable: false,
|
|
6671
|
+
writable: true,
|
|
6672
|
+
configurable: true
|
|
6673
|
+
});
|
|
6674
|
+
}
|
|
6675
|
+
throw err;
|
|
6121
6676
|
} finally {
|
|
6122
6677
|
this.off("trace", listener);
|
|
6123
6678
|
this.setMaxListeners(this.getMaxListeners() - 1);
|
|
@@ -6172,11 +6727,13 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6172
6727
|
const level = this.config.trace?.level ?? "steps";
|
|
6173
6728
|
const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
|
|
6174
6729
|
const parts = [`[axl] execution:${event.executionId}`];
|
|
6175
|
-
const data = event.data;
|
|
6176
6730
|
if (event.type === "workflow_start") {
|
|
6177
|
-
parts.push(`${workflowPrefix}
|
|
6731
|
+
parts.push(`${workflowPrefix}started`);
|
|
6178
6732
|
} else if (event.type === "workflow_end") {
|
|
6179
|
-
|
|
6733
|
+
const d = event.data;
|
|
6734
|
+
const status = d?.aborted ? "aborted" : d?.status ?? "completed";
|
|
6735
|
+
parts.push(`${workflowPrefix}${status}`);
|
|
6736
|
+
if (event.duration != null) parts.push(`${(event.duration / 1e3).toFixed(1)}s`);
|
|
6180
6737
|
} else if (event.type === "agent_call") {
|
|
6181
6738
|
parts.push(`${workflowPrefix}step:${event.step} agent_call`);
|
|
6182
6739
|
if (event.agent) parts.push(`agent:${event.agent}`);
|
|
@@ -6412,6 +6969,16 @@ var RedisStore = class _RedisStore {
|
|
|
6412
6969
|
};
|
|
6413
6970
|
|
|
6414
6971
|
// src/memory/embedder-openai.ts
|
|
6972
|
+
var EMBEDDING_PRICE_PER_1M_TOKENS = {
|
|
6973
|
+
"text-embedding-3-small": 0.02,
|
|
6974
|
+
"text-embedding-3-large": 0.13,
|
|
6975
|
+
"text-embedding-ada-002": 0.1
|
|
6976
|
+
};
|
|
6977
|
+
function computeCost(model, tokens) {
|
|
6978
|
+
const pricePerMillion = EMBEDDING_PRICE_PER_1M_TOKENS[model];
|
|
6979
|
+
if (pricePerMillion == null) return void 0;
|
|
6980
|
+
return tokens / 1e6 * pricePerMillion;
|
|
6981
|
+
}
|
|
6415
6982
|
var OpenAIEmbedder = class {
|
|
6416
6983
|
apiKey;
|
|
6417
6984
|
model;
|
|
@@ -6426,8 +6993,8 @@ var OpenAIEmbedder = class {
|
|
|
6426
6993
|
this.baseUrl = options.baseUrl ?? "https://api.openai.com";
|
|
6427
6994
|
this.dimensions = options.dimensions ?? 1536;
|
|
6428
6995
|
}
|
|
6429
|
-
async embed(texts) {
|
|
6430
|
-
const response = await
|
|
6996
|
+
async embed(texts, signal) {
|
|
6997
|
+
const response = await fetchWithRetry(`${this.baseUrl}/v1/embeddings`, {
|
|
6431
6998
|
method: "POST",
|
|
6432
6999
|
headers: {
|
|
6433
7000
|
"Content-Type": "application/json",
|
|
@@ -6437,14 +7004,28 @@ var OpenAIEmbedder = class {
|
|
|
6437
7004
|
model: this.model,
|
|
6438
7005
|
input: texts,
|
|
6439
7006
|
dimensions: this.dimensions
|
|
6440
|
-
})
|
|
7007
|
+
}),
|
|
7008
|
+
signal
|
|
6441
7009
|
});
|
|
6442
7010
|
if (!response.ok) {
|
|
6443
7011
|
const body = await response.text();
|
|
6444
7012
|
throw new Error(`OpenAI embeddings API error (${response.status}): ${body}`);
|
|
6445
7013
|
}
|
|
6446
7014
|
const json = await response.json();
|
|
6447
|
-
|
|
7015
|
+
const vectors = json.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
7016
|
+
const tokens = json.usage?.prompt_tokens ?? json.usage?.total_tokens;
|
|
7017
|
+
const cost = tokens != null ? computeCost(this.model, tokens) : void 0;
|
|
7018
|
+
if (tokens == null && cost == null) {
|
|
7019
|
+
return { vectors };
|
|
7020
|
+
}
|
|
7021
|
+
return {
|
|
7022
|
+
vectors,
|
|
7023
|
+
usage: {
|
|
7024
|
+
...tokens != null ? { tokens } : {},
|
|
7025
|
+
...cost != null ? { cost } : {},
|
|
7026
|
+
model: this.model
|
|
7027
|
+
}
|
|
7028
|
+
};
|
|
6448
7029
|
}
|
|
6449
7030
|
};
|
|
6450
7031
|
|