@axlsdk/axl 0.13.8 → 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 +763 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +407 -13
- package/dist/index.d.ts +407 -13
- package/dist/index.js +763 -137
- 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
|
|
|
@@ -4082,6 +4534,9 @@ var MemoryStore = class {
|
|
|
4082
4534
|
const result = limit ? sorted.slice(0, limit) : sorted;
|
|
4083
4535
|
return result.map((e) => structuredClone(e));
|
|
4084
4536
|
}
|
|
4537
|
+
async deleteEvalResult(id) {
|
|
4538
|
+
return this.evalHistory.delete(id);
|
|
4539
|
+
}
|
|
4085
4540
|
// ── Sessions (Studio introspection) ─────────────────────────────────
|
|
4086
4541
|
async listSessions() {
|
|
4087
4542
|
return [...this.sessions.keys()];
|
|
@@ -4390,6 +4845,10 @@ var SQLiteStore = class {
|
|
|
4390
4845
|
data: safeJsonParse(r.data)
|
|
4391
4846
|
}));
|
|
4392
4847
|
}
|
|
4848
|
+
async deleteEvalResult(id) {
|
|
4849
|
+
const result = this.db.prepare("DELETE FROM eval_history WHERE id = ?").run(id);
|
|
4850
|
+
return result.changes > 0;
|
|
4851
|
+
}
|
|
4393
4852
|
// ── Sessions (Studio introspection) ────────────────────────────────────
|
|
4394
4853
|
async listSessions() {
|
|
4395
4854
|
const rows = this.db.prepare("SELECT session_id FROM sessions").all();
|
|
@@ -5122,6 +5581,18 @@ var McpManager = class {
|
|
|
5122
5581
|
};
|
|
5123
5582
|
|
|
5124
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
|
+
}
|
|
5125
5596
|
var MemoryManager = class {
|
|
5126
5597
|
vectorStore;
|
|
5127
5598
|
embedder;
|
|
@@ -5136,8 +5607,11 @@ var MemoryManager = class {
|
|
|
5136
5607
|
* Store a key-value pair in memory.
|
|
5137
5608
|
* If a vector store and embedder are configured and embed is true,
|
|
5138
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.
|
|
5139
5613
|
*/
|
|
5140
|
-
async remember(key, value, stateStore, sessionId, options) {
|
|
5614
|
+
async remember(key, value, stateStore, sessionId, options, signal) {
|
|
5141
5615
|
const scope = options?.scope ?? "session";
|
|
5142
5616
|
if (scope === "session" && !sessionId) {
|
|
5143
5617
|
throw new Error(
|
|
@@ -5156,23 +5630,44 @@ var MemoryManager = class {
|
|
|
5156
5630
|
}
|
|
5157
5631
|
if (options?.embed === true && this.vectorStore && this.embedder) {
|
|
5158
5632
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
5159
|
-
const
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
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
|
+
});
|
|
5166
5653
|
}
|
|
5167
|
-
|
|
5654
|
+
throw err;
|
|
5655
|
+
}
|
|
5656
|
+
return usage ? { usage } : {};
|
|
5168
5657
|
}
|
|
5658
|
+
return {};
|
|
5169
5659
|
}
|
|
5170
5660
|
/**
|
|
5171
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).
|
|
5172
5665
|
*/
|
|
5173
|
-
async recall(key, stateStore, sessionId, options) {
|
|
5666
|
+
async recall(key, stateStore, sessionId, options, signal) {
|
|
5174
5667
|
if (options?.query && this.vectorStore && this.embedder) {
|
|
5175
|
-
const
|
|
5668
|
+
const raw = await this.embedder.embed([options.query], signal);
|
|
5669
|
+
assertEmbedResult(raw);
|
|
5670
|
+
const { vectors, usage } = raw;
|
|
5176
5671
|
const topK = options?.topK ?? 5;
|
|
5177
5672
|
const searchScope = options?.scope ?? "session";
|
|
5178
5673
|
if (searchScope === "session" && !sessionId) {
|
|
@@ -5180,13 +5675,14 @@ var MemoryManager = class {
|
|
|
5180
5675
|
'sessionId is required for session-scoped memory. Use { scope: "global" } or provide a sessionId in metadata.'
|
|
5181
5676
|
);
|
|
5182
5677
|
}
|
|
5183
|
-
const rawResults = await this.vectorStore.search(
|
|
5678
|
+
const rawResults = await this.vectorStore.search(vectors[0], topK * 3);
|
|
5184
5679
|
const filtered = rawResults.filter((r) => {
|
|
5185
5680
|
if (r.id.startsWith("global:")) return true;
|
|
5186
5681
|
if (searchScope === "session") return r.id.startsWith(`session:${sessionId}:`);
|
|
5187
5682
|
return true;
|
|
5188
5683
|
});
|
|
5189
|
-
|
|
5684
|
+
const data = filtered.slice(0, topK);
|
|
5685
|
+
return usage ? { data, usage } : { data };
|
|
5190
5686
|
}
|
|
5191
5687
|
const scope = options?.scope ?? "session";
|
|
5192
5688
|
if (scope === "session" && !sessionId) {
|
|
@@ -5196,10 +5692,10 @@ var MemoryManager = class {
|
|
|
5196
5692
|
}
|
|
5197
5693
|
const storeScope = scope === "session" ? `session:${sessionId}` : "global";
|
|
5198
5694
|
if (stateStore.getMemory) {
|
|
5199
|
-
return stateStore.getMemory(storeScope, key);
|
|
5695
|
+
return { data: await stateStore.getMemory(storeScope, key) };
|
|
5200
5696
|
}
|
|
5201
5697
|
const storeKey = `memory:${storeScope}:${key}`;
|
|
5202
|
-
return stateStore.getSessionMeta(storeKey, "value");
|
|
5698
|
+
return { data: await stateStore.getSessionMeta(storeKey, "value") };
|
|
5203
5699
|
}
|
|
5204
5700
|
/** Delete a memory entry. If a vector store is configured, also removes the embedding. */
|
|
5205
5701
|
async forget(key, stateStore, sessionId, options) {
|
|
@@ -5299,6 +5795,22 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5299
5795
|
});
|
|
5300
5796
|
}
|
|
5301
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
|
+
}
|
|
5302
5814
|
/**
|
|
5303
5815
|
* Initialize MCP servers configured in the config.
|
|
5304
5816
|
* Call this before executing workflows that use MCP tools.
|
|
@@ -5448,10 +5960,19 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5448
5960
|
metadata: result2.metadata ?? metadata
|
|
5449
5961
|
};
|
|
5450
5962
|
};
|
|
5451
|
-
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
|
+
});
|
|
5452
5968
|
} else {
|
|
5453
5969
|
result = await this.eval(
|
|
5454
|
-
entry.config
|
|
5970
|
+
entry.config,
|
|
5971
|
+
{
|
|
5972
|
+
onProgress: options?.onProgress,
|
|
5973
|
+
signal: options?.signal,
|
|
5974
|
+
captureTraces: options?.captureTraces
|
|
5975
|
+
}
|
|
5455
5976
|
);
|
|
5456
5977
|
}
|
|
5457
5978
|
const resultObj = result;
|
|
@@ -5589,6 +6110,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5589
6110
|
}
|
|
5590
6111
|
},
|
|
5591
6112
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
6113
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5592
6114
|
stateStore: this.stateStore,
|
|
5593
6115
|
workflowName: name,
|
|
5594
6116
|
mcpManager: this.mcpManager,
|
|
@@ -5602,7 +6124,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5602
6124
|
policy: "finish_and_stop"
|
|
5603
6125
|
}
|
|
5604
6126
|
});
|
|
5605
|
-
ctx.log("workflow_start", { workflow: name, executionId });
|
|
5606
6127
|
return this.spanManager.withSpanAsync(
|
|
5607
6128
|
"axl.workflow.execute",
|
|
5608
6129
|
{
|
|
@@ -5612,17 +6133,17 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5612
6133
|
},
|
|
5613
6134
|
async (span) => {
|
|
5614
6135
|
try {
|
|
6136
|
+
ctx._emitWorkflowStart(validated);
|
|
5615
6137
|
const result = await workflow2.handler(ctx);
|
|
5616
6138
|
const output = workflow2.outputSchema ? workflow2.outputSchema.parse(result) : result;
|
|
5617
6139
|
execInfo.status = "completed";
|
|
5618
6140
|
execInfo.completedAt = Date.now();
|
|
5619
6141
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5620
6142
|
execInfo.result = output;
|
|
5621
|
-
ctx.
|
|
5622
|
-
workflow: name,
|
|
6143
|
+
ctx._emitWorkflowEnd({
|
|
5623
6144
|
status: "completed",
|
|
5624
6145
|
duration: execInfo.duration,
|
|
5625
|
-
|
|
6146
|
+
result: output
|
|
5626
6147
|
});
|
|
5627
6148
|
if (this.stateStore.deleteCheckpoints) {
|
|
5628
6149
|
await this.stateStore.deleteCheckpoints(executionId);
|
|
@@ -5632,11 +6153,17 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5632
6153
|
this.persistExecution(execInfo);
|
|
5633
6154
|
return output;
|
|
5634
6155
|
} catch (err) {
|
|
6156
|
+
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
5635
6157
|
execInfo.status = "failed";
|
|
5636
6158
|
execInfo.completedAt = Date.now();
|
|
5637
6159
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5638
6160
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5639
|
-
ctx.
|
|
6161
|
+
ctx._emitWorkflowEnd({
|
|
6162
|
+
status: "failed",
|
|
6163
|
+
duration: execInfo.duration,
|
|
6164
|
+
error: execInfo.error,
|
|
6165
|
+
...aborted ? { aborted: true } : {}
|
|
6166
|
+
});
|
|
5640
6167
|
this.persistExecution(execInfo);
|
|
5641
6168
|
throw err;
|
|
5642
6169
|
} finally {
|
|
@@ -5701,24 +6228,16 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5701
6228
|
duration: data?.duration ?? void 0
|
|
5702
6229
|
});
|
|
5703
6230
|
}
|
|
5704
|
-
} else if (event.type === "
|
|
6231
|
+
} else if (event.type === "tool_approval") {
|
|
5705
6232
|
const data = event.data;
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
axlStream._push({
|
|
5715
|
-
type: "tool_approval",
|
|
5716
|
-
name: event.tool ?? "",
|
|
5717
|
-
args: data?.args,
|
|
5718
|
-
approved: false,
|
|
5719
|
-
reason: data?.reason ?? void 0
|
|
5720
|
-
});
|
|
5721
|
-
}
|
|
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") {
|
|
5722
6241
|
} else if (event.type === "agent_call") {
|
|
5723
6242
|
axlStream._push({
|
|
5724
6243
|
type: "agent_end",
|
|
@@ -5751,6 +6270,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5751
6270
|
axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
|
|
5752
6271
|
},
|
|
5753
6272
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
6273
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5754
6274
|
stateStore: this.stateStore,
|
|
5755
6275
|
workflowName: name,
|
|
5756
6276
|
mcpManager: this.mcpManager,
|
|
@@ -5774,17 +6294,17 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5774
6294
|
},
|
|
5775
6295
|
async (span) => {
|
|
5776
6296
|
try {
|
|
6297
|
+
wfCtx._emitWorkflowStart(validated);
|
|
5777
6298
|
const rawResult = await workflow2.handler(wfCtx);
|
|
5778
6299
|
const result = workflow2.outputSchema ? workflow2.outputSchema.parse(rawResult) : rawResult;
|
|
5779
6300
|
execInfo.status = "completed";
|
|
5780
6301
|
execInfo.completedAt = Date.now();
|
|
5781
6302
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5782
6303
|
execInfo.result = result;
|
|
5783
|
-
wfCtx.
|
|
5784
|
-
workflow: name,
|
|
6304
|
+
wfCtx._emitWorkflowEnd({
|
|
5785
6305
|
status: "completed",
|
|
5786
6306
|
duration: execInfo.duration,
|
|
5787
|
-
|
|
6307
|
+
result
|
|
5788
6308
|
});
|
|
5789
6309
|
if (this.stateStore.deleteCheckpoints) {
|
|
5790
6310
|
await this.stateStore.deleteCheckpoints(executionId);
|
|
@@ -5801,14 +6321,16 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5801
6321
|
};
|
|
5802
6322
|
run().then((result) => axlStream._done(result)).catch((err) => {
|
|
5803
6323
|
if (execInfo) {
|
|
6324
|
+
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
5804
6325
|
execInfo.status = "failed";
|
|
5805
6326
|
execInfo.completedAt = Date.now();
|
|
5806
6327
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5807
6328
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5808
|
-
ctx?.
|
|
5809
|
-
workflow: name,
|
|
6329
|
+
ctx?._emitWorkflowEnd({
|
|
5810
6330
|
status: "failed",
|
|
5811
|
-
|
|
6331
|
+
duration: execInfo.duration,
|
|
6332
|
+
error: execInfo.error,
|
|
6333
|
+
...aborted ? { aborted: true } : {}
|
|
5812
6334
|
});
|
|
5813
6335
|
this.persistExecution(execInfo);
|
|
5814
6336
|
}
|
|
@@ -5873,6 +6395,25 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5873
6395
|
if (this.stateStore.saveEvalResult) {
|
|
5874
6396
|
await this.stateStore.saveEvalResult(entry);
|
|
5875
6397
|
}
|
|
6398
|
+
this.emit("eval_result", entry);
|
|
6399
|
+
}
|
|
6400
|
+
/**
|
|
6401
|
+
* Delete an eval history entry by id. Removes from in-memory cache and
|
|
6402
|
+
* the configured StateStore. Returns true if an entry was actually removed.
|
|
6403
|
+
*
|
|
6404
|
+
* Ensures lazy-loaded history is loaded first so the in-memory cache and
|
|
6405
|
+
* the store can't drift apart on the deletion path.
|
|
6406
|
+
*/
|
|
6407
|
+
async deleteEvalResult(id) {
|
|
6408
|
+
await this.getEvalHistory();
|
|
6409
|
+
const beforeLength = this.evalHistory.length;
|
|
6410
|
+
this.evalHistory = this.evalHistory.filter((e) => e.id !== id);
|
|
6411
|
+
const removedFromMemory = this.evalHistory.length < beforeLength;
|
|
6412
|
+
let removedFromStore = false;
|
|
6413
|
+
if (this.stateStore.deleteEvalResult) {
|
|
6414
|
+
removedFromStore = await this.stateStore.deleteEvalResult(id);
|
|
6415
|
+
}
|
|
6416
|
+
return removedFromMemory || removedFromStore;
|
|
5876
6417
|
}
|
|
5877
6418
|
/** Get eval result history (most recent first). */
|
|
5878
6419
|
async getEvalHistory() {
|
|
@@ -5986,7 +6527,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5986
6527
|
*
|
|
5987
6528
|
* @see Spec Section 13.5
|
|
5988
6529
|
*/
|
|
5989
|
-
async eval(config) {
|
|
6530
|
+
async eval(config, options) {
|
|
5990
6531
|
let runEvalFn;
|
|
5991
6532
|
try {
|
|
5992
6533
|
({ runEval: runEvalFn } = await import("@axlsdk/eval"));
|
|
@@ -6001,7 +6542,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6001
6542
|
});
|
|
6002
6543
|
return { output: result, cost, metadata };
|
|
6003
6544
|
};
|
|
6004
|
-
return runEvalFn(config, executeWorkflow, this);
|
|
6545
|
+
return runEvalFn(config, executeWorkflow, this, options);
|
|
6005
6546
|
}
|
|
6006
6547
|
/**
|
|
6007
6548
|
* Compare two evaluation results to detect regressions and improvements.
|
|
@@ -6039,9 +6580,31 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6039
6580
|
* Returns cost (same as `trackCost`) plus metadata extracted from trace events:
|
|
6040
6581
|
* models (unique URIs), tokens (input/output/reasoning sums), and agent call count.
|
|
6041
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
|
+
*
|
|
6042
6605
|
* Works with both `createContext()` and `execute()` calls inside `fn`.
|
|
6043
6606
|
*/
|
|
6044
|
-
async trackExecution(fn) {
|
|
6607
|
+
async trackExecution(fn, options) {
|
|
6045
6608
|
const parentScope = costScopeStorage.getStore();
|
|
6046
6609
|
const scope = {
|
|
6047
6610
|
totalCost: 0,
|
|
@@ -6049,8 +6612,10 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6049
6612
|
parent: parentScope
|
|
6050
6613
|
};
|
|
6051
6614
|
const modelCalls = /* @__PURE__ */ new Map();
|
|
6615
|
+
const workflowCalls = /* @__PURE__ */ new Map();
|
|
6052
6616
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
6053
6617
|
let agentCalls = 0;
|
|
6618
|
+
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
6054
6619
|
const listener = (event) => {
|
|
6055
6620
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
6056
6621
|
if (event.cost) scope.totalCost += event.cost;
|
|
@@ -6063,6 +6628,23 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6063
6628
|
tokens.reasoning += event.tokens.reasoning ?? 0;
|
|
6064
6629
|
}
|
|
6065
6630
|
}
|
|
6631
|
+
if (event.type === "workflow_start" && event.workflow) {
|
|
6632
|
+
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
6633
|
+
}
|
|
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);
|
|
6647
|
+
}
|
|
6066
6648
|
};
|
|
6067
6649
|
this.setMaxListeners(this.getMaxListeners() + 1);
|
|
6068
6650
|
this.on("trace", listener);
|
|
@@ -6071,13 +6653,26 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6071
6653
|
return {
|
|
6072
6654
|
result,
|
|
6073
6655
|
cost: scope.totalCost,
|
|
6656
|
+
...capturedTraces ? { traces: capturedTraces } : {},
|
|
6074
6657
|
metadata: {
|
|
6075
6658
|
models: [...modelCalls.keys()],
|
|
6076
6659
|
modelCallCounts: modelCalls.size > 0 ? Object.fromEntries(modelCalls) : void 0,
|
|
6077
6660
|
tokens,
|
|
6078
|
-
agentCalls
|
|
6661
|
+
agentCalls,
|
|
6662
|
+
workflows: [...workflowCalls.keys()],
|
|
6663
|
+
workflowCallCounts: workflowCalls.size > 0 ? Object.fromEntries(workflowCalls) : void 0
|
|
6079
6664
|
}
|
|
6080
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;
|
|
6081
6676
|
} finally {
|
|
6082
6677
|
this.off("trace", listener);
|
|
6083
6678
|
this.setMaxListeners(this.getMaxListeners() - 1);
|
|
@@ -6132,11 +6727,13 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6132
6727
|
const level = this.config.trace?.level ?? "steps";
|
|
6133
6728
|
const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
|
|
6134
6729
|
const parts = [`[axl] execution:${event.executionId}`];
|
|
6135
|
-
const data = event.data;
|
|
6136
6730
|
if (event.type === "workflow_start") {
|
|
6137
|
-
parts.push(`${workflowPrefix}
|
|
6731
|
+
parts.push(`${workflowPrefix}started`);
|
|
6138
6732
|
} else if (event.type === "workflow_end") {
|
|
6139
|
-
|
|
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`);
|
|
6140
6737
|
} else if (event.type === "agent_call") {
|
|
6141
6738
|
parts.push(`${workflowPrefix}step:${event.step} agent_call`);
|
|
6142
6739
|
if (event.agent) parts.push(`agent:${event.agent}`);
|
|
@@ -6353,6 +6950,11 @@ var RedisStore = class _RedisStore {
|
|
|
6353
6950
|
entries.sort((a, b) => b.timestamp - a.timestamp);
|
|
6354
6951
|
return limit ? entries.slice(0, limit) : entries;
|
|
6355
6952
|
}
|
|
6953
|
+
async deleteEvalResult(id) {
|
|
6954
|
+
await this.client.sRem(this.evalHistorySetKey(), id);
|
|
6955
|
+
const deleted = await this.client.del(this.evalHistoryKey(id));
|
|
6956
|
+
return deleted > 0;
|
|
6957
|
+
}
|
|
6356
6958
|
// ── Sessions (Studio introspection) ────────────────────────────────────
|
|
6357
6959
|
async listSessions() {
|
|
6358
6960
|
return this.client.sMembers("axl:session-ids");
|
|
@@ -6367,6 +6969,16 @@ var RedisStore = class _RedisStore {
|
|
|
6367
6969
|
};
|
|
6368
6970
|
|
|
6369
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
|
+
}
|
|
6370
6982
|
var OpenAIEmbedder = class {
|
|
6371
6983
|
apiKey;
|
|
6372
6984
|
model;
|
|
@@ -6381,8 +6993,8 @@ var OpenAIEmbedder = class {
|
|
|
6381
6993
|
this.baseUrl = options.baseUrl ?? "https://api.openai.com";
|
|
6382
6994
|
this.dimensions = options.dimensions ?? 1536;
|
|
6383
6995
|
}
|
|
6384
|
-
async embed(texts) {
|
|
6385
|
-
const response = await
|
|
6996
|
+
async embed(texts, signal) {
|
|
6997
|
+
const response = await fetchWithRetry(`${this.baseUrl}/v1/embeddings`, {
|
|
6386
6998
|
method: "POST",
|
|
6387
6999
|
headers: {
|
|
6388
7000
|
"Content-Type": "application/json",
|
|
@@ -6392,14 +7004,28 @@ var OpenAIEmbedder = class {
|
|
|
6392
7004
|
model: this.model,
|
|
6393
7005
|
input: texts,
|
|
6394
7006
|
dimensions: this.dimensions
|
|
6395
|
-
})
|
|
7007
|
+
}),
|
|
7008
|
+
signal
|
|
6396
7009
|
});
|
|
6397
7010
|
if (!response.ok) {
|
|
6398
7011
|
const body = await response.text();
|
|
6399
7012
|
throw new Error(`OpenAI embeddings API error (${response.status}): ${body}`);
|
|
6400
7013
|
}
|
|
6401
7014
|
const json = await response.json();
|
|
6402
|
-
|
|
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
|
+
};
|
|
6403
7029
|
}
|
|
6404
7030
|
};
|
|
6405
7031
|
|