@axlsdk/axl 0.15.0 → 0.16.1
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 +11 -7
- package/dist/index.cjs +1734 -627
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +941 -236
- package/dist/index.d.ts +941 -236
- package/dist/index.js +1725 -627
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -110,11 +110,13 @@ var init_span_manager = __esm({
|
|
|
110
110
|
// src/index.ts
|
|
111
111
|
var index_exports = {};
|
|
112
112
|
__export(index_exports, {
|
|
113
|
+
AXL_EVENT_TYPES: () => AXL_EVENT_TYPES,
|
|
113
114
|
AnthropicProvider: () => AnthropicProvider,
|
|
114
115
|
AxlError: () => AxlError,
|
|
115
116
|
AxlRuntime: () => AxlRuntime,
|
|
116
117
|
AxlStream: () => AxlStream,
|
|
117
118
|
BudgetExceededError: () => BudgetExceededError,
|
|
119
|
+
COST_BEARING_LEAF_TYPES: () => COST_BEARING_LEAF_TYPES,
|
|
118
120
|
GeminiProvider: () => GeminiProvider,
|
|
119
121
|
GuardrailError: () => GuardrailError,
|
|
120
122
|
InMemoryVectorStore: () => InMemoryVectorStore,
|
|
@@ -129,6 +131,8 @@ __export(index_exports, {
|
|
|
129
131
|
OpenAIResponsesProvider: () => OpenAIResponsesProvider,
|
|
130
132
|
ProviderRegistry: () => ProviderRegistry,
|
|
131
133
|
QuorumNotMet: () => QuorumNotMet,
|
|
134
|
+
REDACTED: () => REDACTED,
|
|
135
|
+
REDACTION_RULES: () => REDACTION_RULES,
|
|
132
136
|
RedisStore: () => RedisStore,
|
|
133
137
|
SQLiteStore: () => SQLiteStore,
|
|
134
138
|
Session: () => Session,
|
|
@@ -141,7 +145,12 @@ __export(index_exports, {
|
|
|
141
145
|
agent: () => agent,
|
|
142
146
|
createSpanManager: () => createSpanManager,
|
|
143
147
|
defineConfig: () => defineConfig,
|
|
148
|
+
eventCostContribution: () => eventCostContribution,
|
|
144
149
|
extractJson: () => extractJson,
|
|
150
|
+
isCostBearingLeaf: () => isCostBearingLeaf,
|
|
151
|
+
isRootLevel: () => isRootLevel,
|
|
152
|
+
parsePartialJson: () => parsePartialJson,
|
|
153
|
+
redactEvent: () => redactEvent,
|
|
145
154
|
resolveThinkingOptions: () => resolveThinkingOptions,
|
|
146
155
|
tool: () => tool,
|
|
147
156
|
workflow: () => workflow,
|
|
@@ -1463,6 +1472,41 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1463
1472
|
};
|
|
1464
1473
|
|
|
1465
1474
|
// src/providers/gemini.ts
|
|
1475
|
+
var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
1476
|
+
"additionalProperties",
|
|
1477
|
+
"$schema",
|
|
1478
|
+
"$ref",
|
|
1479
|
+
"$defs",
|
|
1480
|
+
"definitions",
|
|
1481
|
+
"not",
|
|
1482
|
+
"allOf",
|
|
1483
|
+
"patternProperties",
|
|
1484
|
+
"unevaluatedProperties",
|
|
1485
|
+
"unevaluatedItems"
|
|
1486
|
+
]);
|
|
1487
|
+
function sanitizeSchemaForGemini(schema) {
|
|
1488
|
+
if (Array.isArray(schema)) {
|
|
1489
|
+
return schema.map((item) => sanitizeSchemaForGemini(item));
|
|
1490
|
+
}
|
|
1491
|
+
if (schema === null || typeof schema !== "object") {
|
|
1492
|
+
return schema;
|
|
1493
|
+
}
|
|
1494
|
+
const src = schema;
|
|
1495
|
+
const out = {};
|
|
1496
|
+
for (const [key, value] of Object.entries(src)) {
|
|
1497
|
+
if (GEMINI_DISALLOWED_SCHEMA_KEYS.has(key)) continue;
|
|
1498
|
+
if (key === "oneOf") {
|
|
1499
|
+
out.anyOf = sanitizeSchemaForGemini(value);
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
if (key === "const") {
|
|
1503
|
+
if (!("enum" in src)) out.enum = [value];
|
|
1504
|
+
continue;
|
|
1505
|
+
}
|
|
1506
|
+
out[key] = sanitizeSchemaForGemini(value);
|
|
1507
|
+
}
|
|
1508
|
+
return out;
|
|
1509
|
+
}
|
|
1466
1510
|
var GEMINI_PRICING = {
|
|
1467
1511
|
"gemini-2.5-pro": [125e-8, 1e-5],
|
|
1468
1512
|
"gemini-2.5-flash": [3e-7, 25e-7],
|
|
@@ -1633,7 +1677,9 @@ var GeminiProvider = class {
|
|
|
1633
1677
|
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
1634
1678
|
generationConfig.responseMimeType = "application/json";
|
|
1635
1679
|
if (options.responseFormat.type === "json_schema" && options.responseFormat.json_schema?.schema) {
|
|
1636
|
-
generationConfig.responseSchema =
|
|
1680
|
+
generationConfig.responseSchema = sanitizeSchemaForGemini(
|
|
1681
|
+
options.responseFormat.json_schema.schema
|
|
1682
|
+
);
|
|
1637
1683
|
}
|
|
1638
1684
|
}
|
|
1639
1685
|
if (Object.keys(generationConfig).length > 0) {
|
|
@@ -1810,7 +1856,7 @@ var GeminiProvider = class {
|
|
|
1810
1856
|
return {
|
|
1811
1857
|
name: tool2.function.name,
|
|
1812
1858
|
description: tool2.function.description,
|
|
1813
|
-
parameters: tool2.function.parameters
|
|
1859
|
+
parameters: sanitizeSchemaForGemini(tool2.function.parameters)
|
|
1814
1860
|
};
|
|
1815
1861
|
}
|
|
1816
1862
|
// ---------------------------------------------------------------------------
|
|
@@ -2069,6 +2115,7 @@ var defaultRegistry = new ProviderRegistry();
|
|
|
2069
2115
|
|
|
2070
2116
|
// src/context.ts
|
|
2071
2117
|
var import_node_async_hooks = require("async_hooks");
|
|
2118
|
+
var import_node_crypto = require("crypto");
|
|
2072
2119
|
var import_zod = require("zod");
|
|
2073
2120
|
|
|
2074
2121
|
// src/errors.ts
|
|
@@ -2188,6 +2235,432 @@ var ToolDenied = class extends AxlError {
|
|
|
2188
2235
|
}
|
|
2189
2236
|
};
|
|
2190
2237
|
|
|
2238
|
+
// src/partial-json.ts
|
|
2239
|
+
var MAX_DEPTH = 256;
|
|
2240
|
+
function parsePartialJson(input) {
|
|
2241
|
+
const parser = new PartialParser(input);
|
|
2242
|
+
parser.skipWhitespace();
|
|
2243
|
+
if (parser.eof()) return void 0;
|
|
2244
|
+
const result = parser.parseValue();
|
|
2245
|
+
return result.value;
|
|
2246
|
+
}
|
|
2247
|
+
var PartialParser = class {
|
|
2248
|
+
constructor(src) {
|
|
2249
|
+
this.src = src;
|
|
2250
|
+
}
|
|
2251
|
+
pos = 0;
|
|
2252
|
+
depth = 0;
|
|
2253
|
+
eof() {
|
|
2254
|
+
return this.pos >= this.src.length;
|
|
2255
|
+
}
|
|
2256
|
+
peek() {
|
|
2257
|
+
return this.src[this.pos];
|
|
2258
|
+
}
|
|
2259
|
+
advance() {
|
|
2260
|
+
return this.src[this.pos++];
|
|
2261
|
+
}
|
|
2262
|
+
skipWhitespace() {
|
|
2263
|
+
while (!this.eof()) {
|
|
2264
|
+
const c = this.peek();
|
|
2265
|
+
if (c === " " || c === "\n" || c === " " || c === "\r") {
|
|
2266
|
+
this.pos++;
|
|
2267
|
+
} else {
|
|
2268
|
+
break;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
parseValue() {
|
|
2273
|
+
this.skipWhitespace();
|
|
2274
|
+
if (this.eof()) return { value: void 0, complete: false };
|
|
2275
|
+
const c = this.peek();
|
|
2276
|
+
if (c === "{" || c === "[") {
|
|
2277
|
+
if (++this.depth > MAX_DEPTH) {
|
|
2278
|
+
throw new SyntaxError(
|
|
2279
|
+
`Maximum nesting depth exceeded (${MAX_DEPTH}) at position ${this.pos}`
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
try {
|
|
2283
|
+
return c === "{" ? this.parseObject() : this.parseArray();
|
|
2284
|
+
} finally {
|
|
2285
|
+
this.depth--;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
if (c === '"') return this.parseString();
|
|
2289
|
+
if (c === "t" || c === "f") return this.parseBool();
|
|
2290
|
+
if (c === "n") return this.parseNull();
|
|
2291
|
+
if (c === "-" || c >= "0" && c <= "9") return this.parseNumber();
|
|
2292
|
+
throw new SyntaxError(`Unexpected character '${c}' at position ${this.pos}`);
|
|
2293
|
+
}
|
|
2294
|
+
parseObject() {
|
|
2295
|
+
this.advance();
|
|
2296
|
+
const obj = {};
|
|
2297
|
+
this.skipWhitespace();
|
|
2298
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2299
|
+
if (this.peek() === "}") {
|
|
2300
|
+
this.advance();
|
|
2301
|
+
return { value: obj, complete: true };
|
|
2302
|
+
}
|
|
2303
|
+
while (!this.eof()) {
|
|
2304
|
+
this.skipWhitespace();
|
|
2305
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2306
|
+
if (this.peek() === "}") {
|
|
2307
|
+
this.advance();
|
|
2308
|
+
return { value: obj, complete: true };
|
|
2309
|
+
}
|
|
2310
|
+
if (this.peek() !== '"') {
|
|
2311
|
+
throw new SyntaxError(`Expected '"' at position ${this.pos}, got '${this.peek()}'`);
|
|
2312
|
+
}
|
|
2313
|
+
const keyResult = this.parseString();
|
|
2314
|
+
if (!keyResult.complete) {
|
|
2315
|
+
return { value: obj, complete: false };
|
|
2316
|
+
}
|
|
2317
|
+
const key = keyResult.value;
|
|
2318
|
+
this.skipWhitespace();
|
|
2319
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2320
|
+
if (this.peek() !== ":") {
|
|
2321
|
+
return { value: obj, complete: false };
|
|
2322
|
+
}
|
|
2323
|
+
this.advance();
|
|
2324
|
+
this.skipWhitespace();
|
|
2325
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2326
|
+
const valueResult = this.parseValue();
|
|
2327
|
+
if (valueResult.value !== void 0 || valueResult.complete) {
|
|
2328
|
+
obj[key] = valueResult.value;
|
|
2329
|
+
}
|
|
2330
|
+
if (!valueResult.complete) {
|
|
2331
|
+
return { value: obj, complete: false };
|
|
2332
|
+
}
|
|
2333
|
+
this.skipWhitespace();
|
|
2334
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2335
|
+
const next = this.peek();
|
|
2336
|
+
if (next === ",") {
|
|
2337
|
+
this.advance();
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
if (next === "}") {
|
|
2341
|
+
this.advance();
|
|
2342
|
+
return { value: obj, complete: true };
|
|
2343
|
+
}
|
|
2344
|
+
throw new SyntaxError(`Expected ',' or '}' at position ${this.pos}, got '${next}'`);
|
|
2345
|
+
}
|
|
2346
|
+
return { value: obj, complete: false };
|
|
2347
|
+
}
|
|
2348
|
+
parseArray() {
|
|
2349
|
+
this.advance();
|
|
2350
|
+
const arr = [];
|
|
2351
|
+
this.skipWhitespace();
|
|
2352
|
+
if (this.eof()) return { value: arr, complete: false };
|
|
2353
|
+
if (this.peek() === "]") {
|
|
2354
|
+
this.advance();
|
|
2355
|
+
return { value: arr, complete: true };
|
|
2356
|
+
}
|
|
2357
|
+
while (!this.eof()) {
|
|
2358
|
+
this.skipWhitespace();
|
|
2359
|
+
if (this.eof()) return { value: arr, complete: false };
|
|
2360
|
+
if (this.peek() === "]") {
|
|
2361
|
+
this.advance();
|
|
2362
|
+
return { value: arr, complete: true };
|
|
2363
|
+
}
|
|
2364
|
+
const elemResult = this.parseValue();
|
|
2365
|
+
if (elemResult.value !== void 0 || elemResult.complete) {
|
|
2366
|
+
arr.push(elemResult.value);
|
|
2367
|
+
}
|
|
2368
|
+
if (!elemResult.complete) {
|
|
2369
|
+
return { value: arr, complete: false };
|
|
2370
|
+
}
|
|
2371
|
+
this.skipWhitespace();
|
|
2372
|
+
if (this.eof()) return { value: arr, complete: false };
|
|
2373
|
+
const next = this.peek();
|
|
2374
|
+
if (next === ",") {
|
|
2375
|
+
this.advance();
|
|
2376
|
+
continue;
|
|
2377
|
+
}
|
|
2378
|
+
if (next === "]") {
|
|
2379
|
+
this.advance();
|
|
2380
|
+
return { value: arr, complete: true };
|
|
2381
|
+
}
|
|
2382
|
+
throw new SyntaxError(`Expected ',' or ']' at position ${this.pos}, got '${next}'`);
|
|
2383
|
+
}
|
|
2384
|
+
return { value: arr, complete: false };
|
|
2385
|
+
}
|
|
2386
|
+
parseString() {
|
|
2387
|
+
this.advance();
|
|
2388
|
+
let result = "";
|
|
2389
|
+
while (!this.eof()) {
|
|
2390
|
+
const c = this.advance();
|
|
2391
|
+
if (c === '"') return { value: result, complete: true };
|
|
2392
|
+
if (c === "\\") {
|
|
2393
|
+
if (this.eof()) {
|
|
2394
|
+
return { value: result, complete: false };
|
|
2395
|
+
}
|
|
2396
|
+
const esc = this.advance();
|
|
2397
|
+
switch (esc) {
|
|
2398
|
+
case '"':
|
|
2399
|
+
result += '"';
|
|
2400
|
+
break;
|
|
2401
|
+
case "\\":
|
|
2402
|
+
result += "\\";
|
|
2403
|
+
break;
|
|
2404
|
+
case "/":
|
|
2405
|
+
result += "/";
|
|
2406
|
+
break;
|
|
2407
|
+
case "b":
|
|
2408
|
+
result += "\b";
|
|
2409
|
+
break;
|
|
2410
|
+
case "f":
|
|
2411
|
+
result += "\f";
|
|
2412
|
+
break;
|
|
2413
|
+
case "n":
|
|
2414
|
+
result += "\n";
|
|
2415
|
+
break;
|
|
2416
|
+
case "r":
|
|
2417
|
+
result += "\r";
|
|
2418
|
+
break;
|
|
2419
|
+
case "t":
|
|
2420
|
+
result += " ";
|
|
2421
|
+
break;
|
|
2422
|
+
case "u": {
|
|
2423
|
+
if (this.pos + 4 > this.src.length) {
|
|
2424
|
+
return { value: result, complete: false };
|
|
2425
|
+
}
|
|
2426
|
+
const hex = this.src.slice(this.pos, this.pos + 4);
|
|
2427
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
2428
|
+
throw new SyntaxError(`Invalid \\u escape '${hex}' at position ${this.pos}`);
|
|
2429
|
+
}
|
|
2430
|
+
this.pos += 4;
|
|
2431
|
+
result += String.fromCharCode(parseInt(hex, 16));
|
|
2432
|
+
break;
|
|
2433
|
+
}
|
|
2434
|
+
default:
|
|
2435
|
+
throw new SyntaxError(`Invalid escape '\\${esc}' at position ${this.pos - 1}`);
|
|
2436
|
+
}
|
|
2437
|
+
} else {
|
|
2438
|
+
result += c;
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
return { value: result, complete: false };
|
|
2442
|
+
}
|
|
2443
|
+
parseNumber() {
|
|
2444
|
+
const start = this.pos;
|
|
2445
|
+
if (this.peek() === "-") this.advance();
|
|
2446
|
+
while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
|
|
2447
|
+
if (!this.eof() && this.peek() === ".") {
|
|
2448
|
+
this.advance();
|
|
2449
|
+
while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
|
|
2450
|
+
}
|
|
2451
|
+
if (!this.eof() && (this.peek() === "e" || this.peek() === "E")) {
|
|
2452
|
+
this.advance();
|
|
2453
|
+
if (!this.eof() && (this.peek() === "+" || this.peek() === "-")) this.advance();
|
|
2454
|
+
while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
|
|
2455
|
+
}
|
|
2456
|
+
const text = this.src.slice(start, this.pos);
|
|
2457
|
+
if (text === "" || text === "-" || /\.$/.test(text) || /[eE][+-]?$/.test(text)) {
|
|
2458
|
+
return { value: void 0, complete: false };
|
|
2459
|
+
}
|
|
2460
|
+
const n = Number(text);
|
|
2461
|
+
if (Number.isNaN(n)) {
|
|
2462
|
+
return { value: void 0, complete: false };
|
|
2463
|
+
}
|
|
2464
|
+
return { value: n, complete: true };
|
|
2465
|
+
}
|
|
2466
|
+
parseBool() {
|
|
2467
|
+
const remaining = this.src.slice(this.pos);
|
|
2468
|
+
if (remaining.startsWith("true")) {
|
|
2469
|
+
this.pos += 4;
|
|
2470
|
+
return { value: true, complete: true };
|
|
2471
|
+
}
|
|
2472
|
+
if (remaining.startsWith("false")) {
|
|
2473
|
+
this.pos += 5;
|
|
2474
|
+
return { value: false, complete: true };
|
|
2475
|
+
}
|
|
2476
|
+
if ("true".startsWith(remaining) || "false".startsWith(remaining)) {
|
|
2477
|
+
this.pos = this.src.length;
|
|
2478
|
+
return { value: void 0, complete: false };
|
|
2479
|
+
}
|
|
2480
|
+
throw new SyntaxError(`Expected 'true' or 'false' at position ${this.pos}`);
|
|
2481
|
+
}
|
|
2482
|
+
parseNull() {
|
|
2483
|
+
const remaining = this.src.slice(this.pos);
|
|
2484
|
+
if (remaining.startsWith("null")) {
|
|
2485
|
+
this.pos += 4;
|
|
2486
|
+
return { value: null, complete: true };
|
|
2487
|
+
}
|
|
2488
|
+
if ("null".startsWith(remaining)) {
|
|
2489
|
+
this.pos = this.src.length;
|
|
2490
|
+
return { value: void 0, complete: false };
|
|
2491
|
+
}
|
|
2492
|
+
throw new SyntaxError(`Expected 'null' at position ${this.pos}`);
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
|
|
2496
|
+
// src/event-utils.ts
|
|
2497
|
+
var COST_BEARING_LEAF_TYPES = [
|
|
2498
|
+
"agent_call_end",
|
|
2499
|
+
"tool_call_end",
|
|
2500
|
+
"memory_remember",
|
|
2501
|
+
"memory_recall"
|
|
2502
|
+
];
|
|
2503
|
+
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
2504
|
+
function eventCostContribution(event) {
|
|
2505
|
+
if (event.type === "ask_end") return 0;
|
|
2506
|
+
const c = event.cost;
|
|
2507
|
+
return typeof c === "number" && Number.isFinite(c) && c >= 0 ? c : 0;
|
|
2508
|
+
}
|
|
2509
|
+
function isCostBearingLeaf(event) {
|
|
2510
|
+
return COST_LEAF_SET.has(event.type);
|
|
2511
|
+
}
|
|
2512
|
+
function isRootLevel(event) {
|
|
2513
|
+
const d = event.depth;
|
|
2514
|
+
return (d ?? 0) === 0;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// src/redaction.ts
|
|
2518
|
+
var REDACTED = "[redacted]";
|
|
2519
|
+
function walkObjectOneLevel(obj, preserveKeys) {
|
|
2520
|
+
const out = {};
|
|
2521
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2522
|
+
if (preserveKeys.has(k) || typeof v === "number" || typeof v === "boolean") {
|
|
2523
|
+
out[k] = v;
|
|
2524
|
+
} else if (typeof v === "string") {
|
|
2525
|
+
out[k] = REDACTED;
|
|
2526
|
+
} else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
2527
|
+
const inner = v;
|
|
2528
|
+
const innerOut = {};
|
|
2529
|
+
for (const [ik, iv] of Object.entries(inner)) {
|
|
2530
|
+
if (typeof iv === "number" || typeof iv === "boolean") {
|
|
2531
|
+
innerOut[ik] = iv;
|
|
2532
|
+
} else {
|
|
2533
|
+
innerOut[ik] = REDACTED;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
out[k] = innerOut;
|
|
2537
|
+
} else {
|
|
2538
|
+
out[k] = REDACTED;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return out;
|
|
2542
|
+
}
|
|
2543
|
+
var LOG_PRESERVE_KEYS = /* @__PURE__ */ new Set(["event"]);
|
|
2544
|
+
var MEMORY_PRESERVE_KEYS = /* @__PURE__ */ new Set(["scope"]);
|
|
2545
|
+
function redactGate(event) {
|
|
2546
|
+
const d = event.data;
|
|
2547
|
+
if (!d) return event;
|
|
2548
|
+
if (d.reason === void 0 && d.feedbackMessage === void 0) return event;
|
|
2549
|
+
return {
|
|
2550
|
+
...event,
|
|
2551
|
+
data: {
|
|
2552
|
+
...d,
|
|
2553
|
+
...d.reason !== void 0 ? { reason: REDACTED } : {},
|
|
2554
|
+
...d.feedbackMessage !== void 0 ? { feedbackMessage: REDACTED } : {}
|
|
2555
|
+
}
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
function redactMemory(event) {
|
|
2559
|
+
const d = event.data;
|
|
2560
|
+
if (!d || typeof d !== "object") return event;
|
|
2561
|
+
return {
|
|
2562
|
+
...event,
|
|
2563
|
+
data: walkObjectOneLevel(d, MEMORY_PRESERVE_KEYS)
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
var passthrough = (e) => e;
|
|
2567
|
+
var REDACTION_RULES = {
|
|
2568
|
+
workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
|
|
2569
|
+
workflow_end: (e) => {
|
|
2570
|
+
const d = e.data;
|
|
2571
|
+
if (d.result === void 0 && d.error === void 0) return e;
|
|
2572
|
+
return {
|
|
2573
|
+
...e,
|
|
2574
|
+
data: {
|
|
2575
|
+
...d,
|
|
2576
|
+
...d.result !== void 0 ? { result: REDACTED } : {},
|
|
2577
|
+
...d.error !== void 0 ? { error: REDACTED } : {}
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
},
|
|
2581
|
+
ask_start: (e) => ({ ...e, prompt: REDACTED }),
|
|
2582
|
+
ask_end: (e) => ({
|
|
2583
|
+
...e,
|
|
2584
|
+
outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
|
|
2585
|
+
}),
|
|
2586
|
+
agent_call_start: (e) => {
|
|
2587
|
+
const d = e.data;
|
|
2588
|
+
const out = { ...d, prompt: REDACTED };
|
|
2589
|
+
if (d.system !== void 0) out.system = REDACTED;
|
|
2590
|
+
if (Array.isArray(d.messages)) {
|
|
2591
|
+
out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
|
|
2592
|
+
}
|
|
2593
|
+
return { ...e, data: out };
|
|
2594
|
+
},
|
|
2595
|
+
agent_call_end: (e) => {
|
|
2596
|
+
const d = e.data;
|
|
2597
|
+
const out = { ...d, response: REDACTED };
|
|
2598
|
+
if (d.thinking !== void 0) out.thinking = REDACTED;
|
|
2599
|
+
if (d.error !== void 0) out.error = REDACTED;
|
|
2600
|
+
return { ...e, data: out };
|
|
2601
|
+
},
|
|
2602
|
+
token: (e) => ({ ...e, data: REDACTED }),
|
|
2603
|
+
tool_call_start: (e) => ({ ...e, data: { ...e.data, args: REDACTED } }),
|
|
2604
|
+
tool_call_end: (e) => ({ ...e, data: { ...e.data, args: REDACTED, result: REDACTED } }),
|
|
2605
|
+
tool_approval: (e) => ({
|
|
2606
|
+
...e,
|
|
2607
|
+
data: {
|
|
2608
|
+
...e.data,
|
|
2609
|
+
args: REDACTED,
|
|
2610
|
+
...e.data.reason !== void 0 ? { reason: REDACTED } : {}
|
|
2611
|
+
}
|
|
2612
|
+
}),
|
|
2613
|
+
tool_denied: (e) => {
|
|
2614
|
+
if (!e.data) return e;
|
|
2615
|
+
const d = e.data;
|
|
2616
|
+
if (d.args === void 0 && d.reason === void 0) return e;
|
|
2617
|
+
return {
|
|
2618
|
+
...e,
|
|
2619
|
+
data: {
|
|
2620
|
+
...d,
|
|
2621
|
+
...d.args !== void 0 ? { args: REDACTED } : {},
|
|
2622
|
+
...d.reason !== void 0 ? { reason: REDACTED } : {}
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
},
|
|
2626
|
+
delegate: passthrough,
|
|
2627
|
+
handoff_start: (e) => e.data.message !== void 0 ? { ...e, data: { ...e.data, message: REDACTED } } : e,
|
|
2628
|
+
handoff_return: passthrough,
|
|
2629
|
+
pipeline: (e) => e.status === "failed" ? { ...e, reason: REDACTED } : e,
|
|
2630
|
+
partial_object: (e) => ({ ...e, data: { ...e.data, object: REDACTED } }),
|
|
2631
|
+
verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
|
|
2632
|
+
log: (e) => {
|
|
2633
|
+
if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
|
|
2634
|
+
return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
|
|
2635
|
+
},
|
|
2636
|
+
memory_remember: redactMemory,
|
|
2637
|
+
memory_recall: redactMemory,
|
|
2638
|
+
memory_forget: redactMemory,
|
|
2639
|
+
checkpoint_save: passthrough,
|
|
2640
|
+
checkpoint_replay: passthrough,
|
|
2641
|
+
await_human: (e) => e.data.prompt !== void 0 ? { ...e, data: { ...e.data, prompt: REDACTED } } : e,
|
|
2642
|
+
await_human_resolved: (e) => {
|
|
2643
|
+
const dec = e.data.decision;
|
|
2644
|
+
if (!dec) return e;
|
|
2645
|
+
const hasData = "data" in dec && dec.data !== void 0;
|
|
2646
|
+
const hasReason = "reason" in dec && dec.reason !== void 0;
|
|
2647
|
+
if (!hasData && !hasReason) return e;
|
|
2648
|
+
const scrubbed = { ...dec };
|
|
2649
|
+
if (hasData) scrubbed.data = REDACTED;
|
|
2650
|
+
if (hasReason) scrubbed.reason = REDACTED;
|
|
2651
|
+
return { ...e, data: { ...e.data, decision: scrubbed } };
|
|
2652
|
+
},
|
|
2653
|
+
guardrail: redactGate,
|
|
2654
|
+
schema_check: redactGate,
|
|
2655
|
+
validate: redactGate,
|
|
2656
|
+
done: (e) => ({ ...e, data: { result: REDACTED } }),
|
|
2657
|
+
error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
|
|
2658
|
+
};
|
|
2659
|
+
function redactEvent(event) {
|
|
2660
|
+
const rule = REDACTION_RULES[event.type];
|
|
2661
|
+
return rule(event);
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2191
2664
|
// src/config.ts
|
|
2192
2665
|
function defineConfig(config) {
|
|
2193
2666
|
return config;
|
|
@@ -2265,6 +2738,7 @@ function resolveConfig(config) {
|
|
|
2265
2738
|
|
|
2266
2739
|
// src/context.ts
|
|
2267
2740
|
var signalStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
2741
|
+
var askStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
2268
2742
|
function zodToJsonSchema(schema) {
|
|
2269
2743
|
const result = import_zod.z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
2270
2744
|
delete result.$schema;
|
|
@@ -2366,8 +2840,34 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2366
2840
|
pendingDecisions;
|
|
2367
2841
|
budgetContext;
|
|
2368
2842
|
stateStore;
|
|
2369
|
-
|
|
2370
|
-
|
|
2843
|
+
/** Root step counter for this execution. Inherited by every ctx.ask()
|
|
2844
|
+
* frame so all events from this WorkflowContext (root + nested) share
|
|
2845
|
+
* a single monotonic counter, even when concurrent branch primitives
|
|
2846
|
+
* fire asks before any single parent ask exists. Per-instance so
|
|
2847
|
+
* separate executions don't cross-talk. */
|
|
2848
|
+
stepRefRoot = { value: 0 };
|
|
2849
|
+
/**
|
|
2850
|
+
* Auto-checkpoint counters for internal `_checkpoint` callers. Held by
|
|
2851
|
+
* reference so child contexts created via `createChildContext` share
|
|
2852
|
+
* the same number space — otherwise a tool handler's nested `ctx.ask()`
|
|
2853
|
+
* (which auto-checkpoints) would collide with the parent's
|
|
2854
|
+
* auto-checkpoints in the state store. See `WorkflowContextInit
|
|
2855
|
+
* .autoCheckpointCounters` for the per-agent vs root split.
|
|
2856
|
+
* User-facing `ctx.checkpoint(name, fn)` requires an explicit name and
|
|
2857
|
+
* does NOT touch these counters — it goes straight to the store under
|
|
2858
|
+
* the caller-supplied key.
|
|
2859
|
+
*/
|
|
2860
|
+
autoCheckpointCounters;
|
|
2861
|
+
/** Idempotency guards for `workflow_start` / `workflow_end` emission.
|
|
2862
|
+
* Both `runtime.execute()` and `runtime.stream()` have paths where a
|
|
2863
|
+
* post-emit side-effect (checkpoint deletion, state-store persistence)
|
|
2864
|
+
* can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
|
|
2865
|
+
* fired — the outer catch would then fire `_emitWorkflowEnd({status:
|
|
2866
|
+
* 'failed'})` for a second time. These flags make both emitters
|
|
2867
|
+
* single-fire so consumers never see paired-then-conflicting terminal
|
|
2868
|
+
* events for one execution. */
|
|
2869
|
+
_workflowStartEmitted = false;
|
|
2870
|
+
_workflowEndEmitted = false;
|
|
2371
2871
|
signal;
|
|
2372
2872
|
summaryCache;
|
|
2373
2873
|
workflowName;
|
|
@@ -2379,7 +2879,6 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2379
2879
|
awaitHumanHandler;
|
|
2380
2880
|
onAgentStart;
|
|
2381
2881
|
onAgentCallComplete;
|
|
2382
|
-
parentToolCallId;
|
|
2383
2882
|
constructor(init) {
|
|
2384
2883
|
this.input = init.input;
|
|
2385
2884
|
this.executionId = init.executionId;
|
|
@@ -2403,7 +2902,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2403
2902
|
this.awaitHumanHandler = init.awaitHumanHandler;
|
|
2404
2903
|
this.onAgentStart = init.onAgentStart;
|
|
2405
2904
|
this.onAgentCallComplete = init.onAgentCallComplete;
|
|
2406
|
-
this.
|
|
2905
|
+
this.autoCheckpointCounters = init.autoCheckpointCounters ?? {
|
|
2906
|
+
byAgent: /* @__PURE__ */ new Map(),
|
|
2907
|
+
root: 0
|
|
2908
|
+
};
|
|
2407
2909
|
if (init.metadata?.summaryCache) {
|
|
2408
2910
|
this.summaryCache = init.metadata.summaryCache;
|
|
2409
2911
|
}
|
|
@@ -2412,15 +2914,23 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2412
2914
|
* Create a child context for nested agent invocations (e.g., agent-as-tool).
|
|
2413
2915
|
* Shares: budget tracking, abort signals, trace emission, provider registry,
|
|
2414
2916
|
* state store, span manager, memory manager, MCP manager, config,
|
|
2415
|
-
* awaitHuman handler, pending decisions, tool overrides
|
|
2416
|
-
*
|
|
2917
|
+
* awaitHuman handler, pending decisions, tool overrides, AND the
|
|
2918
|
+
* streaming callbacks (onToken / onAgentStart / onToolCall).
|
|
2919
|
+
* Isolates: session history.
|
|
2920
|
+
*
|
|
2921
|
+
* Streaming callbacks now propagate into nested asks because every callback
|
|
2922
|
+
* invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
|
|
2923
|
+
* consumers that want root-only behavior can filter on `meta.depth === 0`
|
|
2924
|
+
* instead of relying on the runtime to drop nested events. This is the
|
|
2925
|
+
* "nested ask visibility" fix from spec §3.2.
|
|
2417
2926
|
*
|
|
2418
|
-
*
|
|
2419
|
-
*
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2927
|
+
* The shared step counter lives in `askStorage` (ALS), so there is no
|
|
2928
|
+
* per-context counter to pass through anymore. Nested events are
|
|
2929
|
+
* correlated to their parent via `parentAskId` (set on every nested ask
|
|
2930
|
+
* by ALS frame allocation) — the deprecated `parentToolCallId` field
|
|
2931
|
+
* was removed in 0.16.0.
|
|
2422
2932
|
*/
|
|
2423
|
-
createChildContext(
|
|
2933
|
+
createChildContext() {
|
|
2424
2934
|
return new _WorkflowContext({
|
|
2425
2935
|
input: this.input,
|
|
2426
2936
|
executionId: this.executionId,
|
|
@@ -2434,18 +2944,20 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2434
2944
|
spanManager: this.spanManager,
|
|
2435
2945
|
memoryManager: this.memoryManager,
|
|
2436
2946
|
onTrace: this.onTrace,
|
|
2947
|
+
onToken: this.onToken,
|
|
2948
|
+
onToolCall: this.onToolCall,
|
|
2949
|
+
onAgentStart: this.onAgentStart,
|
|
2437
2950
|
onAgentCallComplete: this.onAgentCallComplete,
|
|
2438
2951
|
awaitHumanHandler: this.awaitHumanHandler,
|
|
2439
2952
|
pendingDecisions: this.pendingDecisions,
|
|
2440
2953
|
toolOverrides: this.toolOverrides,
|
|
2441
2954
|
signal: this.signal,
|
|
2442
2955
|
workflowName: this.workflowName,
|
|
2443
|
-
//
|
|
2444
|
-
// `
|
|
2445
|
-
// child
|
|
2446
|
-
|
|
2447
|
-
// Isolated: sessionHistory (empty)
|
|
2448
|
-
// onToken (null), onAgentStart (null), onToolCall (null)
|
|
2956
|
+
// Share the parent's auto-checkpoint counters so internal checkpoints
|
|
2957
|
+
// emitted from `ctx.ask` / spawn / race / parallel / map in this
|
|
2958
|
+
// child context don't collide with the parent's in the state store.
|
|
2959
|
+
autoCheckpointCounters: this.autoCheckpointCounters
|
|
2960
|
+
// Isolated: sessionHistory (empty)
|
|
2449
2961
|
});
|
|
2450
2962
|
}
|
|
2451
2963
|
/**
|
|
@@ -2456,72 +2968,150 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2456
2968
|
get currentSignal() {
|
|
2457
2969
|
return signalStorage.getStore() ?? this.signal;
|
|
2458
2970
|
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Build a `CallbackMeta` for the current ask frame. Used at every
|
|
2973
|
+
* `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
|
|
2974
|
+
* route or filter by ask correlation (`meta.depth === 0` for root
|
|
2975
|
+
* chat UIs, etc.).
|
|
2976
|
+
*
|
|
2977
|
+
* If the call is somehow outside an ask frame (e.g., a programmatic
|
|
2978
|
+
* `tool.run()` test harness), falls back to a synthetic meta keyed off
|
|
2979
|
+
* `executionId` so the type contract holds — consumers see `depth: 0`
|
|
2980
|
+
* and can ignore those events.
|
|
2981
|
+
*/
|
|
2982
|
+
currentCallbackMeta(agentName) {
|
|
2983
|
+
const frame = askStorage.getStore();
|
|
2984
|
+
if (frame) {
|
|
2985
|
+
return {
|
|
2986
|
+
askId: frame.askId,
|
|
2987
|
+
...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
|
|
2988
|
+
depth: frame.depth,
|
|
2989
|
+
agent: agentName
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
return { askId: this.executionId, depth: 0, agent: agentName };
|
|
2993
|
+
}
|
|
2459
2994
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
2460
2995
|
async ask(agent2, prompt, options) {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
const
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
"axl.agent.completion_tokens",
|
|
2491
|
-
usageCapture.value.completion_tokens
|
|
2996
|
+
const agentName = agent2._name;
|
|
2997
|
+
return this._checkpoint(
|
|
2998
|
+
this._autoCheckpointName("ask", agentName),
|
|
2999
|
+
async () => {
|
|
3000
|
+
const parentFrame = askStorage.getStore();
|
|
3001
|
+
const askId = (0, import_node_crypto.randomUUID)();
|
|
3002
|
+
const depth = (parentFrame?.depth ?? -1) + 1;
|
|
3003
|
+
const stepRef = parentFrame?.stepRef ?? this.stepRefRoot;
|
|
3004
|
+
const frame = {
|
|
3005
|
+
askId,
|
|
3006
|
+
parentAskId: parentFrame?.askId,
|
|
3007
|
+
depth,
|
|
3008
|
+
agent: agent2._name,
|
|
3009
|
+
stepRef,
|
|
3010
|
+
askCost: { value: 0 }
|
|
3011
|
+
};
|
|
3012
|
+
return askStorage.run(frame, async () => {
|
|
3013
|
+
const askStart = Date.now();
|
|
3014
|
+
this.emitEvent({ type: "ask_start", prompt });
|
|
3015
|
+
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
3016
|
+
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
3017
|
+
const usageCapture = {};
|
|
3018
|
+
const doCall = async () => {
|
|
3019
|
+
const result = await this.executeAgentCall(
|
|
3020
|
+
agent2,
|
|
3021
|
+
prompt,
|
|
3022
|
+
options,
|
|
3023
|
+
void 0,
|
|
3024
|
+
usageCapture
|
|
2492
3025
|
);
|
|
2493
|
-
|
|
2494
|
-
|
|
3026
|
+
return result;
|
|
3027
|
+
};
|
|
3028
|
+
let outcome;
|
|
3029
|
+
try {
|
|
3030
|
+
const result = this.spanManager ? await this.spanManager.withSpanAsync(
|
|
3031
|
+
"axl.agent.ask",
|
|
3032
|
+
{
|
|
3033
|
+
"axl.agent.name": agent2._name,
|
|
3034
|
+
"axl.agent.model": agent2.resolveModel(resolveCtx)
|
|
3035
|
+
},
|
|
3036
|
+
async (span) => {
|
|
3037
|
+
const r = await doCall();
|
|
3038
|
+
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
3039
|
+
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
3040
|
+
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
3041
|
+
if (usageCapture.value) {
|
|
3042
|
+
span.setAttribute(
|
|
3043
|
+
"axl.agent.prompt_tokens",
|
|
3044
|
+
usageCapture.value.prompt_tokens
|
|
3045
|
+
);
|
|
3046
|
+
span.setAttribute(
|
|
3047
|
+
"axl.agent.completion_tokens",
|
|
3048
|
+
usageCapture.value.completion_tokens
|
|
3049
|
+
);
|
|
3050
|
+
if (usageCapture.value.cached_tokens)
|
|
3051
|
+
span.setAttribute(
|
|
3052
|
+
"axl.agent.cached_tokens",
|
|
3053
|
+
usageCapture.value.cached_tokens
|
|
3054
|
+
);
|
|
3055
|
+
}
|
|
3056
|
+
return r;
|
|
3057
|
+
}
|
|
3058
|
+
) : await doCall();
|
|
3059
|
+
outcome = { ok: true, result };
|
|
3060
|
+
const costAfter = this.budgetContext?.totalCost ?? 0;
|
|
3061
|
+
if (this.onAgentCallComplete) {
|
|
3062
|
+
try {
|
|
3063
|
+
this.onAgentCallComplete({
|
|
3064
|
+
agent: agent2._name,
|
|
3065
|
+
prompt,
|
|
3066
|
+
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
3067
|
+
model: agent2.resolveModel(resolveCtx),
|
|
3068
|
+
cost: costAfter - costBefore,
|
|
3069
|
+
duration: Date.now() - askStart,
|
|
3070
|
+
promptVersion: agent2._config.version,
|
|
3071
|
+
temperature: options?.temperature ?? agent2._config.temperature,
|
|
3072
|
+
maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
|
|
3073
|
+
effort: options?.effort ?? agent2._config.effort,
|
|
3074
|
+
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
3075
|
+
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
3076
|
+
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
3077
|
+
stop: options?.stop ?? agent2._config.stop,
|
|
3078
|
+
providerOptions: options?.providerOptions ?? agent2._config.providerOptions
|
|
3079
|
+
});
|
|
3080
|
+
} catch (hookErr) {
|
|
3081
|
+
console.error(
|
|
3082
|
+
"[axl] onAgentCallComplete hook threw; ask outcome unchanged:",
|
|
3083
|
+
hookErr instanceof Error ? hookErr.message : String(hookErr)
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return result;
|
|
3088
|
+
} catch (err) {
|
|
3089
|
+
outcome = {
|
|
3090
|
+
ok: false,
|
|
3091
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3092
|
+
};
|
|
3093
|
+
throw err;
|
|
3094
|
+
} finally {
|
|
3095
|
+
this.emitEvent({
|
|
3096
|
+
type: "ask_end",
|
|
3097
|
+
outcome: outcome ?? {
|
|
3098
|
+
ok: false,
|
|
3099
|
+
error: "ask_end emitted without outcome \u2014 internal bug"
|
|
3100
|
+
},
|
|
3101
|
+
cost: frame.askCost.value,
|
|
3102
|
+
duration: Date.now() - askStart
|
|
3103
|
+
});
|
|
2495
3104
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
this.onAgentCallComplete?.({
|
|
2501
|
-
agent: agent2._name,
|
|
2502
|
-
prompt,
|
|
2503
|
-
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
2504
|
-
model: agent2.resolveModel(resolveCtx),
|
|
2505
|
-
cost: costAfter - costBefore,
|
|
2506
|
-
duration: Date.now() - startTime,
|
|
2507
|
-
promptVersion: agent2._config.version,
|
|
2508
|
-
temperature: options?.temperature ?? agent2._config.temperature,
|
|
2509
|
-
maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
|
|
2510
|
-
effort: options?.effort ?? agent2._config.effort,
|
|
2511
|
-
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
2512
|
-
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
2513
|
-
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
2514
|
-
stop: options?.stop ?? agent2._config.stop,
|
|
2515
|
-
providerOptions: options?.providerOptions ?? agent2._config.providerOptions
|
|
2516
|
-
});
|
|
2517
|
-
return result;
|
|
2518
|
-
});
|
|
3105
|
+
});
|
|
3106
|
+
},
|
|
3107
|
+
{ agent: agentName }
|
|
3108
|
+
);
|
|
2519
3109
|
}
|
|
2520
3110
|
async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
|
|
2521
3111
|
if (this.budgetContext?.exceeded) {
|
|
2522
3112
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
2523
3113
|
if (policy === "warn") {
|
|
2524
|
-
this.
|
|
3114
|
+
this.emitEvent({
|
|
2525
3115
|
type: "log",
|
|
2526
3116
|
data: { warning: "Budget exceeded", limit, spent, policy }
|
|
2527
3117
|
});
|
|
@@ -2610,7 +3200,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2610
3200
|
const guardrails = agent2._config.guardrails;
|
|
2611
3201
|
if (guardrails?.input) {
|
|
2612
3202
|
const inputResult = await guardrails.input(prompt, { metadata: this.metadata });
|
|
2613
|
-
this.
|
|
3203
|
+
this.emitEvent({
|
|
2614
3204
|
type: "guardrail",
|
|
2615
3205
|
agent: agent2._name,
|
|
2616
3206
|
data: {
|
|
@@ -2644,12 +3234,6 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2644
3234
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
2645
3235
|
const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
|
|
2646
3236
|
const startTime = Date.now();
|
|
2647
|
-
if (this.onToken && options?.validate) {
|
|
2648
|
-
throw new AxlError(
|
|
2649
|
-
"INVALID_CONFIG",
|
|
2650
|
-
"Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
|
|
2651
|
-
);
|
|
2652
|
-
}
|
|
2653
3237
|
const currentMessages = [...messages];
|
|
2654
3238
|
let turns = 0;
|
|
2655
3239
|
let guardrailOutputRetries = 0;
|
|
@@ -2658,11 +3242,42 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2658
3242
|
const maxGuardrailRetries = guardrails?.maxRetries ?? 2;
|
|
2659
3243
|
let pendingRetryReason;
|
|
2660
3244
|
const verboseTrace = this.config.trace?.level === "full";
|
|
3245
|
+
let lastStartStage = "initial";
|
|
3246
|
+
let lastStartAttempt = 1;
|
|
3247
|
+
let lastStartMaxAttempts = 1;
|
|
2661
3248
|
while (turns < maxTurns) {
|
|
2662
3249
|
if (Date.now() - startTime > timeoutMs) {
|
|
2663
3250
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
2664
3251
|
}
|
|
2665
3252
|
turns++;
|
|
3253
|
+
const isFirstTurn = turns === 1;
|
|
3254
|
+
const isRetryTurn = pendingRetryReason !== void 0;
|
|
3255
|
+
if (isFirstTurn || isRetryTurn) {
|
|
3256
|
+
const stage = pendingRetryReason ?? "initial";
|
|
3257
|
+
let pipelineAttempt = 1;
|
|
3258
|
+
let pipelineMaxAttempts = 1;
|
|
3259
|
+
if (stage === "guardrail") {
|
|
3260
|
+
pipelineAttempt = guardrailOutputRetries + 1;
|
|
3261
|
+
pipelineMaxAttempts = maxGuardrailRetries + 1;
|
|
3262
|
+
} else if (stage === "schema") {
|
|
3263
|
+
pipelineAttempt = schemaRetries + 1;
|
|
3264
|
+
pipelineMaxAttempts = (options?.retries ?? 3) + 1;
|
|
3265
|
+
} else if (stage === "validate") {
|
|
3266
|
+
pipelineAttempt = validateRetries + 1;
|
|
3267
|
+
pipelineMaxAttempts = (options?.validateRetries ?? 2) + 1;
|
|
3268
|
+
}
|
|
3269
|
+
lastStartStage = stage;
|
|
3270
|
+
lastStartAttempt = pipelineAttempt;
|
|
3271
|
+
lastStartMaxAttempts = pipelineMaxAttempts;
|
|
3272
|
+
this.emitEvent({
|
|
3273
|
+
type: "pipeline",
|
|
3274
|
+
agent: agent2._name,
|
|
3275
|
+
status: "start",
|
|
3276
|
+
stage,
|
|
3277
|
+
attempt: pipelineAttempt,
|
|
3278
|
+
maxAttempts: pipelineMaxAttempts
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
2666
3281
|
const turnStart = Date.now();
|
|
2667
3282
|
const chatOptions = {
|
|
2668
3283
|
model,
|
|
@@ -2680,72 +3295,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2680
3295
|
if (options?.schema && toolDefs.length === 0) {
|
|
2681
3296
|
chatOptions.responseFormat = { type: "json_object" };
|
|
2682
3297
|
}
|
|
2683
|
-
this.
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
content2 += chunk.content;
|
|
2694
|
-
this.onToken(chunk.content);
|
|
2695
|
-
} else if (chunk.type === "thinking_delta") {
|
|
2696
|
-
thinkingContent += chunk.content;
|
|
2697
|
-
} else if (chunk.type === "tool_call_delta") {
|
|
2698
|
-
let buffer = toolCallBuffers.get(chunk.id);
|
|
2699
|
-
if (!buffer) {
|
|
2700
|
-
buffer = { id: chunk.id, name: "", arguments: "" };
|
|
2701
|
-
toolCallBuffers.set(chunk.id, buffer);
|
|
2702
|
-
}
|
|
2703
|
-
if (chunk.name) buffer.name = chunk.name;
|
|
2704
|
-
if (chunk.arguments) buffer.arguments += chunk.arguments;
|
|
2705
|
-
} else if (chunk.type === "done") {
|
|
2706
|
-
streamProviderMetadata = chunk.providerMetadata;
|
|
2707
|
-
if (chunk.usage) {
|
|
2708
|
-
response = {
|
|
2709
|
-
content: content2,
|
|
2710
|
-
tool_calls: void 0,
|
|
2711
|
-
usage: chunk.usage,
|
|
2712
|
-
cost: chunk.cost
|
|
2713
|
-
};
|
|
2714
|
-
}
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
for (const buffer of toolCallBuffers.values()) {
|
|
2718
|
-
toolCalls.push({
|
|
2719
|
-
id: buffer.id,
|
|
2720
|
-
type: "function",
|
|
2721
|
-
function: {
|
|
2722
|
-
name: buffer.name,
|
|
2723
|
-
arguments: buffer.arguments
|
|
2724
|
-
}
|
|
2725
|
-
});
|
|
2726
|
-
}
|
|
2727
|
-
response ??= {
|
|
2728
|
-
content: content2,
|
|
2729
|
-
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
2730
|
-
};
|
|
2731
|
-
if (toolCalls.length > 0) {
|
|
2732
|
-
response.tool_calls = toolCalls;
|
|
2733
|
-
}
|
|
2734
|
-
if (streamProviderMetadata) {
|
|
2735
|
-
response.providerMetadata = streamProviderMetadata;
|
|
2736
|
-
}
|
|
2737
|
-
if (thinkingContent) {
|
|
2738
|
-
response.thinking_content = thinkingContent;
|
|
2739
|
-
}
|
|
2740
|
-
} else {
|
|
2741
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
2742
|
-
}
|
|
2743
|
-
if (usageCapture && response.usage) {
|
|
2744
|
-
usageCapture.value = response.usage;
|
|
2745
|
-
}
|
|
2746
|
-
if (response.cost) {
|
|
2747
|
-
this._accumulateBudgetCost(response.cost);
|
|
2748
|
-
}
|
|
3298
|
+
const callbackMeta = this.currentCallbackMeta(agent2._name);
|
|
3299
|
+
const callParams = {
|
|
3300
|
+
...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
|
|
3301
|
+
maxTokens: chatOptions.maxTokens,
|
|
3302
|
+
...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
|
|
3303
|
+
...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
|
|
3304
|
+
...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
|
|
3305
|
+
...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
|
|
3306
|
+
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
3307
|
+
};
|
|
2749
3308
|
let messagesSnapshot;
|
|
2750
3309
|
if (verboseTrace) {
|
|
2751
3310
|
try {
|
|
@@ -2760,8 +3319,148 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2760
3319
|
}
|
|
2761
3320
|
const retryReason = pendingRetryReason;
|
|
2762
3321
|
pendingRetryReason = void 0;
|
|
2763
|
-
this.
|
|
2764
|
-
type: "
|
|
3322
|
+
this.emitEvent({
|
|
3323
|
+
type: "agent_call_start",
|
|
3324
|
+
agent: agent2._name,
|
|
3325
|
+
model: modelUri,
|
|
3326
|
+
turn: turns,
|
|
3327
|
+
data: {
|
|
3328
|
+
prompt,
|
|
3329
|
+
...systemPrompt ? { system: systemPrompt } : {},
|
|
3330
|
+
params: callParams,
|
|
3331
|
+
turn: turns,
|
|
3332
|
+
...retryReason ? { retryReason } : {},
|
|
3333
|
+
...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
|
|
3334
|
+
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
3335
|
+
}
|
|
3336
|
+
});
|
|
3337
|
+
this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
|
|
3338
|
+
let response;
|
|
3339
|
+
try {
|
|
3340
|
+
if (this.onToken) {
|
|
3341
|
+
let content2 = "";
|
|
3342
|
+
const toolCalls = [];
|
|
3343
|
+
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
3344
|
+
let streamProviderMetadata;
|
|
3345
|
+
let thinkingContent = "";
|
|
3346
|
+
const partialObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof import_zod.z.ZodObject;
|
|
3347
|
+
const currentAttempt = schemaRetries + 1;
|
|
3348
|
+
let inString = false;
|
|
3349
|
+
let escaped = false;
|
|
3350
|
+
let boundaryPending = false;
|
|
3351
|
+
for await (const chunk of provider.stream(currentMessages, chatOptions)) {
|
|
3352
|
+
if (chunk.type === "text_delta") {
|
|
3353
|
+
content2 += chunk.content;
|
|
3354
|
+
this.emitEvent({ type: "token", data: chunk.content });
|
|
3355
|
+
this.onToken(chunk.content, callbackMeta);
|
|
3356
|
+
if (partialObjectEnabled) {
|
|
3357
|
+
for (const ch of chunk.content) {
|
|
3358
|
+
if (escaped) {
|
|
3359
|
+
escaped = false;
|
|
3360
|
+
continue;
|
|
3361
|
+
}
|
|
3362
|
+
if (ch === "\\") {
|
|
3363
|
+
escaped = true;
|
|
3364
|
+
continue;
|
|
3365
|
+
}
|
|
3366
|
+
if (ch === '"') {
|
|
3367
|
+
inString = !inString;
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
if (!inString && (ch === "," || ch === "}" || ch === "]")) {
|
|
3371
|
+
boundaryPending = true;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
if (boundaryPending) {
|
|
3375
|
+
boundaryPending = false;
|
|
3376
|
+
let parsed;
|
|
3377
|
+
try {
|
|
3378
|
+
parsed = parsePartialJson(extractJson(content2));
|
|
3379
|
+
} catch {
|
|
3380
|
+
parsed = void 0;
|
|
3381
|
+
}
|
|
3382
|
+
if (parsed !== void 0) {
|
|
3383
|
+
this.emitEvent({
|
|
3384
|
+
type: "partial_object",
|
|
3385
|
+
agent: agent2._name,
|
|
3386
|
+
attempt: currentAttempt,
|
|
3387
|
+
data: { object: parsed }
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
} else if (chunk.type === "thinking_delta") {
|
|
3393
|
+
thinkingContent += chunk.content;
|
|
3394
|
+
} else if (chunk.type === "tool_call_delta") {
|
|
3395
|
+
let buffer = toolCallBuffers.get(chunk.id);
|
|
3396
|
+
if (!buffer) {
|
|
3397
|
+
buffer = { id: chunk.id, name: "", arguments: "" };
|
|
3398
|
+
toolCallBuffers.set(chunk.id, buffer);
|
|
3399
|
+
}
|
|
3400
|
+
if (chunk.name) buffer.name = chunk.name;
|
|
3401
|
+
if (chunk.arguments) buffer.arguments += chunk.arguments;
|
|
3402
|
+
} else if (chunk.type === "done") {
|
|
3403
|
+
streamProviderMetadata = chunk.providerMetadata;
|
|
3404
|
+
if (chunk.usage) {
|
|
3405
|
+
response = {
|
|
3406
|
+
content: content2,
|
|
3407
|
+
tool_calls: void 0,
|
|
3408
|
+
usage: chunk.usage,
|
|
3409
|
+
cost: chunk.cost
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
for (const buffer of toolCallBuffers.values()) {
|
|
3415
|
+
toolCalls.push({
|
|
3416
|
+
id: buffer.id,
|
|
3417
|
+
type: "function",
|
|
3418
|
+
function: {
|
|
3419
|
+
name: buffer.name,
|
|
3420
|
+
arguments: buffer.arguments
|
|
3421
|
+
}
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
response ??= {
|
|
3425
|
+
content: content2,
|
|
3426
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
3427
|
+
};
|
|
3428
|
+
if (toolCalls.length > 0) {
|
|
3429
|
+
response.tool_calls = toolCalls;
|
|
3430
|
+
}
|
|
3431
|
+
if (streamProviderMetadata) {
|
|
3432
|
+
response.providerMetadata = streamProviderMetadata;
|
|
3433
|
+
}
|
|
3434
|
+
if (thinkingContent) {
|
|
3435
|
+
response.thinking_content = thinkingContent;
|
|
3436
|
+
}
|
|
3437
|
+
} else {
|
|
3438
|
+
response = await provider.chat(currentMessages, chatOptions);
|
|
3439
|
+
}
|
|
3440
|
+
} catch (err) {
|
|
3441
|
+
this.emitEvent({
|
|
3442
|
+
type: "agent_call_end",
|
|
3443
|
+
agent: agent2._name,
|
|
3444
|
+
model: modelUri,
|
|
3445
|
+
promptVersion: agent2._config.version,
|
|
3446
|
+
duration: Date.now() - turnStart,
|
|
3447
|
+
data: {
|
|
3448
|
+
response: "",
|
|
3449
|
+
turn: turns,
|
|
3450
|
+
...retryReason ? { retryReason } : {},
|
|
3451
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
throw err;
|
|
3455
|
+
}
|
|
3456
|
+
if (usageCapture && response.usage) {
|
|
3457
|
+
usageCapture.value = response.usage;
|
|
3458
|
+
}
|
|
3459
|
+
if (response.cost) {
|
|
3460
|
+
this._accumulateBudgetCost(response.cost);
|
|
3461
|
+
}
|
|
3462
|
+
this.emitEvent({
|
|
3463
|
+
type: "agent_call_end",
|
|
2765
3464
|
agent: agent2._name,
|
|
2766
3465
|
model: modelUri,
|
|
2767
3466
|
promptVersion: agent2._config.version,
|
|
@@ -2773,22 +3472,10 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2773
3472
|
} : void 0,
|
|
2774
3473
|
duration: Date.now() - turnStart,
|
|
2775
3474
|
data: {
|
|
2776
|
-
prompt,
|
|
2777
3475
|
response: response.content,
|
|
2778
|
-
...systemPrompt ? { system: systemPrompt } : {},
|
|
2779
3476
|
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
2780
|
-
params: {
|
|
2781
|
-
...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
|
|
2782
|
-
maxTokens: chatOptions.maxTokens,
|
|
2783
|
-
...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
|
|
2784
|
-
...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
|
|
2785
|
-
...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
|
|
2786
|
-
...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
|
|
2787
|
-
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
2788
|
-
},
|
|
2789
3477
|
turn: turns,
|
|
2790
|
-
...retryReason ? { retryReason } : {}
|
|
2791
|
-
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
3478
|
+
...retryReason ? { retryReason } : {}
|
|
2792
3479
|
}
|
|
2793
3480
|
});
|
|
2794
3481
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
@@ -2814,6 +3501,19 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2814
3501
|
}
|
|
2815
3502
|
}
|
|
2816
3503
|
const handoffStart = Date.now();
|
|
3504
|
+
const sourceFrame = askStorage.getStore();
|
|
3505
|
+
const handoffFromAskId = sourceFrame?.askId ?? this.executionId;
|
|
3506
|
+
const handoffSourceDepth = sourceFrame?.depth ?? 0;
|
|
3507
|
+
const handoffToAskId = (0, import_node_crypto.randomUUID)();
|
|
3508
|
+
const handoffTargetDepth = handoffSourceDepth + 1;
|
|
3509
|
+
const targetFrame = {
|
|
3510
|
+
askId: handoffToAskId,
|
|
3511
|
+
parentAskId: handoffFromAskId,
|
|
3512
|
+
depth: handoffTargetDepth,
|
|
3513
|
+
agent: descriptor.agent._name,
|
|
3514
|
+
stepRef: sourceFrame?.stepRef ?? this.stepRefRoot,
|
|
3515
|
+
askCost: { value: 0 }
|
|
3516
|
+
};
|
|
2817
3517
|
const handoffOptions = options ? {
|
|
2818
3518
|
schema: options.schema,
|
|
2819
3519
|
retries: options.retries,
|
|
@@ -2821,13 +3521,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2821
3521
|
validate: options.validate,
|
|
2822
3522
|
validateRetries: options.validateRetries
|
|
2823
3523
|
} : void 0;
|
|
2824
|
-
const handoffFn = () =>
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
3524
|
+
const handoffFn = () => askStorage.run(targetFrame, async () => {
|
|
3525
|
+
this.emitEvent({ type: "ask_start", prompt: handoffPrompt });
|
|
3526
|
+
const targetAskStart = Date.now();
|
|
3527
|
+
try {
|
|
3528
|
+
const result = await this.executeAgentCall(
|
|
3529
|
+
descriptor.agent,
|
|
3530
|
+
handoffPrompt,
|
|
3531
|
+
handoffOptions,
|
|
3532
|
+
currentMessages,
|
|
3533
|
+
usageCapture
|
|
3534
|
+
);
|
|
3535
|
+
this.emitEvent({
|
|
3536
|
+
type: "ask_end",
|
|
3537
|
+
outcome: { ok: true, result },
|
|
3538
|
+
cost: targetFrame.askCost.value,
|
|
3539
|
+
duration: Date.now() - targetAskStart
|
|
3540
|
+
});
|
|
3541
|
+
return result;
|
|
3542
|
+
} catch (err) {
|
|
3543
|
+
this.emitEvent({
|
|
3544
|
+
type: "ask_end",
|
|
3545
|
+
outcome: {
|
|
3546
|
+
ok: false,
|
|
3547
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3548
|
+
},
|
|
3549
|
+
cost: targetFrame.askCost.value,
|
|
3550
|
+
duration: Date.now() - targetAskStart
|
|
3551
|
+
});
|
|
3552
|
+
throw err;
|
|
3553
|
+
}
|
|
3554
|
+
});
|
|
3555
|
+
this.emitEvent({
|
|
3556
|
+
type: "handoff_start",
|
|
3557
|
+
agent: agent2._name,
|
|
3558
|
+
fromAskId: handoffFromAskId,
|
|
3559
|
+
toAskId: handoffToAskId,
|
|
3560
|
+
sourceDepth: handoffSourceDepth,
|
|
3561
|
+
targetDepth: handoffTargetDepth,
|
|
3562
|
+
data: {
|
|
3563
|
+
source: agent2._name,
|
|
3564
|
+
target: targetName,
|
|
3565
|
+
mode,
|
|
3566
|
+
...mode === "roundtrip" && handoffPrompt !== prompt ? { message: handoffPrompt } : {}
|
|
3567
|
+
}
|
|
3568
|
+
});
|
|
2831
3569
|
if (mode === "roundtrip") {
|
|
2832
3570
|
const executeRoundtrip = async () => {
|
|
2833
3571
|
const result = await handoffFn();
|
|
@@ -2848,36 +3586,41 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2848
3586
|
"axl.handoff.mode": mode
|
|
2849
3587
|
},
|
|
2850
3588
|
async (span) => {
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
3589
|
+
try {
|
|
3590
|
+
return await executeRoundtrip();
|
|
3591
|
+
} finally {
|
|
3592
|
+
const duration = Date.now() - handoffStart;
|
|
3593
|
+
span.setAttribute("axl.handoff.duration", duration);
|
|
3594
|
+
this.emitEvent({
|
|
3595
|
+
type: "handoff_return",
|
|
3596
|
+
agent: agent2._name,
|
|
3597
|
+
fromAskId: handoffFromAskId,
|
|
3598
|
+
toAskId: handoffToAskId,
|
|
3599
|
+
sourceDepth: handoffSourceDepth,
|
|
3600
|
+
targetDepth: handoffTargetDepth,
|
|
3601
|
+
data: { source: agent2._name, target: targetName, duration }
|
|
3602
|
+
});
|
|
3603
|
+
}
|
|
2866
3604
|
}
|
|
2867
3605
|
);
|
|
2868
3606
|
} else {
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
3607
|
+
try {
|
|
3608
|
+
await executeRoundtrip();
|
|
3609
|
+
} finally {
|
|
3610
|
+
this.emitEvent({
|
|
3611
|
+
type: "handoff_return",
|
|
3612
|
+
agent: agent2._name,
|
|
3613
|
+
fromAskId: handoffFromAskId,
|
|
3614
|
+
toAskId: handoffToAskId,
|
|
3615
|
+
sourceDepth: handoffSourceDepth,
|
|
3616
|
+
targetDepth: handoffTargetDepth,
|
|
3617
|
+
data: {
|
|
3618
|
+
source: agent2._name,
|
|
3619
|
+
target: targetName,
|
|
3620
|
+
duration: Date.now() - handoffStart
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
2881
3624
|
}
|
|
2882
3625
|
continue;
|
|
2883
3626
|
}
|
|
@@ -2891,29 +3634,12 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2891
3634
|
},
|
|
2892
3635
|
async (span) => {
|
|
2893
3636
|
const result = await handoffFn();
|
|
2894
|
-
|
|
2895
|
-
span.setAttribute("axl.handoff.duration", duration);
|
|
2896
|
-
this.emitTrace({
|
|
2897
|
-
type: "handoff",
|
|
2898
|
-
agent: agent2._name,
|
|
2899
|
-
data: { target: targetName, mode, duration, source: agent2._name }
|
|
2900
|
-
});
|
|
3637
|
+
span.setAttribute("axl.handoff.duration", Date.now() - handoffStart);
|
|
2901
3638
|
return result;
|
|
2902
3639
|
}
|
|
2903
3640
|
);
|
|
2904
3641
|
}
|
|
2905
|
-
|
|
2906
|
-
this.emitTrace({
|
|
2907
|
-
type: "handoff",
|
|
2908
|
-
agent: agent2._name,
|
|
2909
|
-
data: {
|
|
2910
|
-
target: targetName,
|
|
2911
|
-
mode,
|
|
2912
|
-
duration: Date.now() - handoffStart,
|
|
2913
|
-
source: agent2._name
|
|
2914
|
-
}
|
|
2915
|
-
});
|
|
2916
|
-
return onewayResult;
|
|
3642
|
+
return await handoffFn();
|
|
2917
3643
|
}
|
|
2918
3644
|
}
|
|
2919
3645
|
const toolOverride = this.toolOverrides?.get(toolName);
|
|
@@ -2929,7 +3655,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2929
3655
|
});
|
|
2930
3656
|
continue;
|
|
2931
3657
|
}
|
|
2932
|
-
this.
|
|
3658
|
+
this.emitEvent({
|
|
3659
|
+
type: "tool_call_start",
|
|
3660
|
+
tool: toolName,
|
|
3661
|
+
callId: toolCall.id,
|
|
3662
|
+
data: { args: toolArgs2 }
|
|
3663
|
+
});
|
|
3664
|
+
this.onToolCall?.(
|
|
3665
|
+
{ name: toolName, args: toolArgs2, callId: toolCall.id },
|
|
3666
|
+
callbackMeta
|
|
3667
|
+
);
|
|
2933
3668
|
const toolStart2 = Date.now();
|
|
2934
3669
|
const executeOverride = async () => {
|
|
2935
3670
|
let toolResult3;
|
|
@@ -2957,8 +3692,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2957
3692
|
}
|
|
2958
3693
|
) : await executeOverride();
|
|
2959
3694
|
const resultContent2 = JSON.stringify(toolResult2);
|
|
2960
|
-
this.
|
|
2961
|
-
type: "
|
|
3695
|
+
this.emitEvent({
|
|
3696
|
+
type: "tool_call_end",
|
|
2962
3697
|
agent: agent2._name,
|
|
2963
3698
|
tool: toolName,
|
|
2964
3699
|
duration: Date.now() - toolStart2,
|
|
@@ -2974,7 +3709,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2974
3709
|
const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
|
|
2975
3710
|
const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
|
|
2976
3711
|
if (!tool2 && !isMcpTool) {
|
|
2977
|
-
this.
|
|
3712
|
+
this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
|
|
2978
3713
|
currentMessages.push({
|
|
2979
3714
|
role: "tool",
|
|
2980
3715
|
content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
|
|
@@ -2993,7 +3728,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2993
3728
|
});
|
|
2994
3729
|
continue;
|
|
2995
3730
|
}
|
|
2996
|
-
this.
|
|
3731
|
+
this.emitEvent({
|
|
3732
|
+
type: "tool_call_start",
|
|
3733
|
+
tool: toolName,
|
|
3734
|
+
callId: toolCall.id,
|
|
3735
|
+
data: { args: toolArgs }
|
|
3736
|
+
});
|
|
3737
|
+
this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
|
|
2997
3738
|
const toolStart = Date.now();
|
|
2998
3739
|
if (tool2 && tool2.requireApproval) {
|
|
2999
3740
|
const approvalFn = async () => {
|
|
@@ -3030,7 +3771,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3030
3771
|
} else {
|
|
3031
3772
|
approvalOutcome = await approvalFn();
|
|
3032
3773
|
}
|
|
3033
|
-
this.
|
|
3774
|
+
this.emitEvent({
|
|
3034
3775
|
type: "tool_approval",
|
|
3035
3776
|
agent: agent2._name,
|
|
3036
3777
|
tool: toolName,
|
|
@@ -3073,7 +3814,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3073
3814
|
resultContent2 = JSON.stringify(toolResult2);
|
|
3074
3815
|
}
|
|
3075
3816
|
} else if (tool2) {
|
|
3076
|
-
const childCtx = this.createChildContext(
|
|
3817
|
+
const childCtx = this.createChildContext();
|
|
3077
3818
|
try {
|
|
3078
3819
|
toolResult2 = await tool2._execute(toolArgs, childCtx);
|
|
3079
3820
|
} catch (err) {
|
|
@@ -3115,8 +3856,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3115
3856
|
return r;
|
|
3116
3857
|
}
|
|
3117
3858
|
) : await executeTool();
|
|
3118
|
-
this.
|
|
3119
|
-
type: "
|
|
3859
|
+
this.emitEvent({
|
|
3860
|
+
type: "tool_call_end",
|
|
3120
3861
|
agent: agent2._name,
|
|
3121
3862
|
tool: traceName,
|
|
3122
3863
|
duration: Date.now() - toolStart,
|
|
@@ -3140,7 +3881,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3140
3881
|
if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
|
|
3141
3882
|
feedbackMessage = `Your previous response was blocked by a safety guardrail: ${outputResult.reason ?? "Output blocked"}. Please provide a different response that complies with the guidelines.`;
|
|
3142
3883
|
}
|
|
3143
|
-
this.
|
|
3884
|
+
this.emitEvent({
|
|
3144
3885
|
type: "guardrail",
|
|
3145
3886
|
agent: agent2._name,
|
|
3146
3887
|
data: {
|
|
@@ -3161,6 +3902,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3161
3902
|
});
|
|
3162
3903
|
if (outputResult.block) {
|
|
3163
3904
|
if (feedbackMessage) {
|
|
3905
|
+
this.emitEvent({
|
|
3906
|
+
type: "pipeline",
|
|
3907
|
+
agent: agent2._name,
|
|
3908
|
+
status: "failed",
|
|
3909
|
+
stage: "guardrail",
|
|
3910
|
+
attempt: guardrailOutputRetries + 1,
|
|
3911
|
+
maxAttempts: maxGuardrailRetries + 1,
|
|
3912
|
+
reason: feedbackMessage
|
|
3913
|
+
});
|
|
3164
3914
|
guardrailOutputRetries++;
|
|
3165
3915
|
appendRetryMessages(
|
|
3166
3916
|
currentMessages,
|
|
@@ -3199,7 +3949,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3199
3949
|
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
|
|
3200
3950
|
}
|
|
3201
3951
|
}
|
|
3202
|
-
this.
|
|
3952
|
+
this.emitEvent({
|
|
3203
3953
|
type: "schema_check",
|
|
3204
3954
|
agent: agent2._name,
|
|
3205
3955
|
data: {
|
|
@@ -3218,6 +3968,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3218
3968
|
});
|
|
3219
3969
|
if (!schemaValid) {
|
|
3220
3970
|
if (schemaFeedback) {
|
|
3971
|
+
this.emitEvent({
|
|
3972
|
+
type: "pipeline",
|
|
3973
|
+
agent: agent2._name,
|
|
3974
|
+
status: "failed",
|
|
3975
|
+
stage: "schema",
|
|
3976
|
+
attempt: schemaRetries + 1,
|
|
3977
|
+
maxAttempts: (options.retries ?? 3) + 1,
|
|
3978
|
+
reason: schemaFeedback
|
|
3979
|
+
});
|
|
3221
3980
|
schemaRetries++;
|
|
3222
3981
|
appendRetryMessages(
|
|
3223
3982
|
currentMessages,
|
|
@@ -3255,7 +4014,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3255
4014
|
if (!validateResult.valid && validateRetries < maxValidateRetries) {
|
|
3256
4015
|
validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
|
|
3257
4016
|
}
|
|
3258
|
-
this.
|
|
4017
|
+
this.emitEvent({
|
|
3259
4018
|
type: "validate",
|
|
3260
4019
|
agent: agent2._name,
|
|
3261
4020
|
data: {
|
|
@@ -3274,6 +4033,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3274
4033
|
});
|
|
3275
4034
|
if (!validateResult.valid) {
|
|
3276
4035
|
if (validateFeedback) {
|
|
4036
|
+
this.emitEvent({
|
|
4037
|
+
type: "pipeline",
|
|
4038
|
+
agent: agent2._name,
|
|
4039
|
+
status: "failed",
|
|
4040
|
+
stage: "validate",
|
|
4041
|
+
attempt: validateRetries + 1,
|
|
4042
|
+
maxAttempts: (options.validateRetries ?? 2) + 1,
|
|
4043
|
+
reason: validateFeedback
|
|
4044
|
+
});
|
|
3277
4045
|
validateRetries++;
|
|
3278
4046
|
appendRetryMessages(
|
|
3279
4047
|
currentMessages,
|
|
@@ -3291,6 +4059,14 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3291
4059
|
);
|
|
3292
4060
|
}
|
|
3293
4061
|
}
|
|
4062
|
+
this.emitEvent({
|
|
4063
|
+
type: "pipeline",
|
|
4064
|
+
agent: agent2._name,
|
|
4065
|
+
status: "committed",
|
|
4066
|
+
stage: lastStartStage,
|
|
4067
|
+
attempt: lastStartAttempt,
|
|
4068
|
+
maxAttempts: lastStartMaxAttempts
|
|
4069
|
+
});
|
|
3294
4070
|
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3295
4071
|
return validated ?? content;
|
|
3296
4072
|
}
|
|
@@ -3419,43 +4195,100 @@ ${summaryResponse.content}`
|
|
|
3419
4195
|
/**
|
|
3420
4196
|
* Execute a function with checkpoint-replay semantics.
|
|
3421
4197
|
*
|
|
3422
|
-
* On first execution, runs `fn()`, saves the result
|
|
3423
|
-
* On replay (resume after restart), returns the saved result
|
|
3424
|
-
* This prevents duplicate side effects (double API
|
|
4198
|
+
* On first execution, runs `fn()`, saves the result under `name`, and
|
|
4199
|
+
* returns it. On replay (resume after restart), returns the saved result
|
|
4200
|
+
* without re-executing. This prevents duplicate side effects (double API
|
|
4201
|
+
* calls, double refunds, etc.).
|
|
4202
|
+
*
|
|
4203
|
+
* `name` must be a stable, caller-supplied identifier — the same call
|
|
4204
|
+
* site MUST pass the same name across runs of the same execution for
|
|
4205
|
+
* replay to work. Names are scoped to a single execution and must be
|
|
4206
|
+
* unique within it; reusing a name (e.g. inside a loop) gives
|
|
4207
|
+
* last-write-wins behavior. For loops, compose names from the iterator:
|
|
4208
|
+
* `ctx.checkpoint(`process-${id}`, fn)`.
|
|
4209
|
+
*
|
|
4210
|
+
* Names starting with `__auto/` are reserved for runtime-internal
|
|
4211
|
+
* auto-checkpointing of `ctx.ask` / spawn / race / parallel / map.
|
|
3425
4212
|
*/
|
|
3426
|
-
async checkpoint(fn) {
|
|
3427
|
-
|
|
4213
|
+
async checkpoint(name, fn) {
|
|
4214
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
4215
|
+
throw new Error("ctx.checkpoint name must be a non-empty string.");
|
|
4216
|
+
}
|
|
4217
|
+
if (name !== name.trim()) {
|
|
4218
|
+
throw new Error(
|
|
4219
|
+
`ctx.checkpoint name "${name}" has leading/trailing whitespace \u2014 trim before passing.`
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
if (name.toLowerCase().startsWith("__auto/")) {
|
|
4223
|
+
throw new Error(
|
|
4224
|
+
`ctx.checkpoint name "${name}" is reserved \u2014 names starting with "__auto/" (case-insensitive) are used by the runtime for ask/spawn/race/parallel/map auto-checkpointing.`
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
4227
|
+
return this._checkpoint(name, fn);
|
|
3428
4228
|
}
|
|
3429
4229
|
/**
|
|
3430
|
-
* Internal checkpoint implementation shared by both the public
|
|
3431
|
-
* and the automatic checkpointing in
|
|
4230
|
+
* Internal checkpoint implementation shared by both the public
|
|
4231
|
+
* `checkpoint(name, fn)` and the automatic checkpointing in
|
|
4232
|
+
* ask/spawn/race/parallel/map. Internal callers pass an `__auto/...`
|
|
4233
|
+
* name composed by `_autoCheckpointName`.
|
|
4234
|
+
*
|
|
4235
|
+
* `agent` is the originating agent for ask auto-checkpoints — passing
|
|
4236
|
+
* it explicitly stamps `event.agent` on the trace events even though
|
|
4237
|
+
* the emit fires before/after `fn()` (where the ask's ALS frame
|
|
4238
|
+
* either doesn't exist yet or has already torn down). Without this,
|
|
4239
|
+
* post-fn `checkpoint_save` rows show no agent in the trace UI.
|
|
3432
4240
|
*/
|
|
3433
|
-
async _checkpoint(fn) {
|
|
3434
|
-
const step = this.checkpointCounter++;
|
|
4241
|
+
async _checkpoint(name, fn, options) {
|
|
3435
4242
|
if (!this.stateStore) {
|
|
3436
4243
|
return fn();
|
|
3437
4244
|
}
|
|
3438
|
-
const
|
|
4245
|
+
const agentStamp = options?.agent ? { agent: options.agent } : {};
|
|
4246
|
+
const saved = await this.stateStore.getCheckpoint(this.executionId, name);
|
|
3439
4247
|
if (saved !== null) {
|
|
3440
|
-
this.
|
|
3441
|
-
type: "
|
|
3442
|
-
|
|
4248
|
+
this.emitEvent({
|
|
4249
|
+
type: "checkpoint_replay",
|
|
4250
|
+
...agentStamp,
|
|
4251
|
+
data: { name }
|
|
3443
4252
|
});
|
|
3444
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.
|
|
4253
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.name": name });
|
|
3445
4254
|
return saved;
|
|
3446
4255
|
}
|
|
3447
4256
|
const result = await fn();
|
|
3448
|
-
await this.stateStore.saveCheckpoint(this.executionId,
|
|
3449
|
-
this.
|
|
3450
|
-
type: "
|
|
3451
|
-
|
|
4257
|
+
await this.stateStore.saveCheckpoint(this.executionId, name, result);
|
|
4258
|
+
this.emitEvent({
|
|
4259
|
+
type: "checkpoint_save",
|
|
4260
|
+
...agentStamp,
|
|
4261
|
+
data: { name }
|
|
3452
4262
|
});
|
|
3453
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.
|
|
4263
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.name": name });
|
|
3454
4264
|
return result;
|
|
3455
4265
|
}
|
|
4266
|
+
/** Generate a stable auto-checkpoint name for an internal primitive.
|
|
4267
|
+
*
|
|
4268
|
+
* For `ctx.ask`, the originating agent is known and gets a per-agent
|
|
4269
|
+
* counter — the resulting name reads as "orchestrator's first ask"
|
|
4270
|
+
* (`__auto/orchestrator-agent/ask/0`) instead of a global ordinal.
|
|
4271
|
+
* For `spawn` / `race` / `parallel` / `map`, no agent context is
|
|
4272
|
+
* available (those wrap arbitrary fn callbacks), so the name uses
|
|
4273
|
+
* the root counter: `__auto/spawn/0`, `__auto/race/3`, etc.
|
|
4274
|
+
*
|
|
4275
|
+
* All counters live on a shared ref passed through `createChildContext`,
|
|
4276
|
+
* so a tool handler's nested `ctx.ask()` (which runs in a child
|
|
4277
|
+
* WorkflowContext) can never collide with the parent's auto-names in
|
|
4278
|
+
* the state store. */
|
|
4279
|
+
_autoCheckpointName(primitive, agent2) {
|
|
4280
|
+
const counters = this.autoCheckpointCounters;
|
|
4281
|
+
if (primitive === "ask" && agent2) {
|
|
4282
|
+
const n2 = counters.byAgent.get(agent2) ?? 0;
|
|
4283
|
+
counters.byAgent.set(agent2, n2 + 1);
|
|
4284
|
+
return `__auto/${agent2}/ask/${n2}`;
|
|
4285
|
+
}
|
|
4286
|
+
const n = counters.root++;
|
|
4287
|
+
return `__auto/${primitive}/${n}`;
|
|
4288
|
+
}
|
|
3456
4289
|
// ── ctx.spawn() ───────────────────────────────────────────────────────
|
|
3457
4290
|
async spawn(n, fn, options) {
|
|
3458
|
-
return this._checkpoint(() => {
|
|
4291
|
+
return this._checkpoint(this._autoCheckpointName("spawn"), () => {
|
|
3459
4292
|
if (this.spanManager) {
|
|
3460
4293
|
return this.spanManager.withSpanAsync(
|
|
3461
4294
|
"axl.ctx.spawn",
|
|
@@ -3651,7 +4484,7 @@ ${summaryResponse.content}`
|
|
|
3651
4484
|
const maxRetries = options?.retries ?? 3;
|
|
3652
4485
|
let lastRetry = void 0;
|
|
3653
4486
|
const emitVerifyOutcome = (passed, attempts, lastError) => {
|
|
3654
|
-
this.
|
|
4487
|
+
this.emitEvent({
|
|
3655
4488
|
type: "verify",
|
|
3656
4489
|
agent: void 0,
|
|
3657
4490
|
data: {
|
|
@@ -3791,7 +4624,7 @@ ${summaryResponse.content}`
|
|
|
3791
4624
|
}
|
|
3792
4625
|
// ── ctx.race() ────────────────────────────────────────────────────────
|
|
3793
4626
|
async race(fns, options) {
|
|
3794
|
-
return this._checkpoint(() => {
|
|
4627
|
+
return this._checkpoint(this._autoCheckpointName("race"), () => {
|
|
3795
4628
|
if (this.spanManager) {
|
|
3796
4629
|
return this.spanManager.withSpanAsync(
|
|
3797
4630
|
"axl.ctx.race",
|
|
@@ -3896,11 +4729,17 @@ ${summaryResponse.content}`
|
|
|
3896
4729
|
}
|
|
3897
4730
|
// ── ctx.parallel() ────────────────────────────────────────────────────
|
|
3898
4731
|
async parallel(fns) {
|
|
3899
|
-
return this._checkpoint(
|
|
4732
|
+
return this._checkpoint(
|
|
4733
|
+
this._autoCheckpointName("parallel"),
|
|
4734
|
+
() => Promise.all(fns.map((fn) => fn()))
|
|
4735
|
+
);
|
|
3900
4736
|
}
|
|
3901
4737
|
// ── ctx.map() ─────────────────────────────────────────────────────────
|
|
3902
4738
|
async map(items, fn, options) {
|
|
3903
|
-
return this._checkpoint(
|
|
4739
|
+
return this._checkpoint(
|
|
4740
|
+
this._autoCheckpointName("map"),
|
|
4741
|
+
() => this._mapImpl(items, fn, options)
|
|
4742
|
+
);
|
|
3904
4743
|
}
|
|
3905
4744
|
async _mapImpl(items, fn, options) {
|
|
3906
4745
|
const concurrency = options?.concurrency ?? 5;
|
|
@@ -3978,10 +4817,14 @@ ${summaryResponse.content}`
|
|
|
3978
4817
|
}
|
|
3979
4818
|
async _awaitHumanImpl(options) {
|
|
3980
4819
|
if (this.awaitHumanHandler) {
|
|
4820
|
+
this.emitEvent({
|
|
4821
|
+
type: "await_human",
|
|
4822
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
4823
|
+
});
|
|
3981
4824
|
const decision2 = await this.awaitHumanHandler(options);
|
|
3982
|
-
this.
|
|
3983
|
-
type: "
|
|
3984
|
-
data: {
|
|
4825
|
+
this.emitEvent({
|
|
4826
|
+
type: "await_human_resolved",
|
|
4827
|
+
data: { channel: options.channel, decision: decision2 }
|
|
3985
4828
|
});
|
|
3986
4829
|
return decision2;
|
|
3987
4830
|
}
|
|
@@ -4001,7 +4844,7 @@ ${summaryResponse.content}`
|
|
|
4001
4844
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
4002
4845
|
workflow: this.workflowName ?? "unknown",
|
|
4003
4846
|
input: this.input,
|
|
4004
|
-
step: this.
|
|
4847
|
+
step: this.stepRefRoot.value,
|
|
4005
4848
|
status: "waiting",
|
|
4006
4849
|
metadata: {
|
|
4007
4850
|
...this.metadata,
|
|
@@ -4010,18 +4853,22 @@ ${summaryResponse.content}`
|
|
|
4010
4853
|
}
|
|
4011
4854
|
});
|
|
4012
4855
|
}
|
|
4013
|
-
this.
|
|
4014
|
-
type: "
|
|
4015
|
-
data: {
|
|
4856
|
+
this.emitEvent({
|
|
4857
|
+
type: "await_human",
|
|
4858
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
4016
4859
|
});
|
|
4017
4860
|
const decision = await new Promise((resolve) => {
|
|
4018
4861
|
this.pendingDecisions.set(this.executionId, resolve);
|
|
4019
4862
|
});
|
|
4863
|
+
this.emitEvent({
|
|
4864
|
+
type: "await_human_resolved",
|
|
4865
|
+
data: { channel: options.channel, decision }
|
|
4866
|
+
});
|
|
4020
4867
|
if (this.stateStore) {
|
|
4021
4868
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
4022
4869
|
workflow: this.workflowName ?? "unknown",
|
|
4023
4870
|
input: this.input,
|
|
4024
|
-
step: this.
|
|
4871
|
+
step: this.stepRefRoot.value,
|
|
4025
4872
|
status: "running"
|
|
4026
4873
|
});
|
|
4027
4874
|
}
|
|
@@ -4074,17 +4921,28 @@ ${summaryResponse.content}`
|
|
|
4074
4921
|
// the previous `ctx.log('workflow_start', ...)` indirection — so consumers
|
|
4075
4922
|
// that narrow via `event.type === 'workflow_start'` actually see them.
|
|
4076
4923
|
// Internal: the runtime is the only caller, user workflows never call these.
|
|
4077
|
-
/** @internal */
|
|
4924
|
+
/** @internal — idempotent; no-ops on second+ call within one ctx. */
|
|
4078
4925
|
_emitWorkflowStart(input) {
|
|
4079
|
-
this.
|
|
4926
|
+
if (this._workflowStartEmitted) return;
|
|
4927
|
+
this._workflowStartEmitted = true;
|
|
4928
|
+
this.emitEvent({
|
|
4080
4929
|
type: "workflow_start",
|
|
4081
4930
|
workflow: this.workflowName,
|
|
4082
4931
|
data: { input }
|
|
4083
4932
|
});
|
|
4084
4933
|
}
|
|
4085
|
-
/** @internal
|
|
4934
|
+
/** @internal — idempotent; no-ops on second+ call within one ctx.
|
|
4935
|
+
* Protects against the post-emit-side-effect double-fire (reviewer
|
|
4936
|
+
* bug B1): `runtime.execute()` / `runtime.stream()` emit
|
|
4937
|
+
* `workflow_end(completed)`, then call `deleteCheckpoints` /
|
|
4938
|
+
* `persistExecution`. If either throws, the outer catch would
|
|
4939
|
+
* otherwise fire a second `workflow_end(failed)` with conflicting
|
|
4940
|
+
* status. First-wins semantics: the completed event stands, the
|
|
4941
|
+
* inner side-effect failure propagates as a normal thrown error. */
|
|
4086
4942
|
_emitWorkflowEnd(info) {
|
|
4087
|
-
this.
|
|
4943
|
+
if (this._workflowEndEmitted) return;
|
|
4944
|
+
this._workflowEndEmitted = true;
|
|
4945
|
+
this.emitEvent({
|
|
4088
4946
|
type: "workflow_end",
|
|
4089
4947
|
workflow: this.workflowName,
|
|
4090
4948
|
duration: info.duration,
|
|
@@ -4099,7 +4957,7 @@ ${summaryResponse.content}`
|
|
|
4099
4957
|
}
|
|
4100
4958
|
// ── ctx.log() ─────────────────────────────────────────────────────────
|
|
4101
4959
|
log(event, data) {
|
|
4102
|
-
this.
|
|
4960
|
+
this.emitEvent({
|
|
4103
4961
|
type: "log",
|
|
4104
4962
|
data: {
|
|
4105
4963
|
event,
|
|
@@ -4153,12 +5011,11 @@ ${summaryResponse.content}`
|
|
|
4153
5011
|
if (usage?.cost != null) {
|
|
4154
5012
|
this._accumulateBudgetCost(usage.cost);
|
|
4155
5013
|
}
|
|
4156
|
-
this.
|
|
4157
|
-
type: "
|
|
5014
|
+
this.emitEvent({
|
|
5015
|
+
type: "memory_remember",
|
|
4158
5016
|
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4159
5017
|
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4160
5018
|
data: {
|
|
4161
|
-
event: "memory_remember",
|
|
4162
5019
|
key,
|
|
4163
5020
|
scope,
|
|
4164
5021
|
embed: options?.embed === true,
|
|
@@ -4170,12 +5027,11 @@ ${summaryResponse.content}`
|
|
|
4170
5027
|
if (partialUsage?.cost != null) {
|
|
4171
5028
|
this._accumulateBudgetCost(partialUsage.cost);
|
|
4172
5029
|
}
|
|
4173
|
-
this.
|
|
4174
|
-
type: "
|
|
5030
|
+
this.emitEvent({
|
|
5031
|
+
type: "memory_remember",
|
|
4175
5032
|
...partialUsage?.cost != null ? { cost: partialUsage.cost } : {},
|
|
4176
5033
|
...partialUsage?.tokens != null ? { tokens: { input: partialUsage.tokens } } : {},
|
|
4177
5034
|
data: {
|
|
4178
|
-
event: "memory_remember",
|
|
4179
5035
|
key,
|
|
4180
5036
|
scope,
|
|
4181
5037
|
embed: options?.embed === true,
|
|
@@ -4227,26 +5083,24 @@ ${summaryResponse.content}`
|
|
|
4227
5083
|
if (usage?.cost != null) {
|
|
4228
5084
|
this._accumulateBudgetCost(usage.cost);
|
|
4229
5085
|
}
|
|
4230
|
-
this.
|
|
4231
|
-
type: "
|
|
5086
|
+
this.emitEvent({
|
|
5087
|
+
type: "memory_recall",
|
|
4232
5088
|
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4233
5089
|
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4234
5090
|
data: {
|
|
4235
|
-
event: "memory_recall",
|
|
4236
5091
|
key,
|
|
4237
5092
|
scope,
|
|
4238
5093
|
semantic,
|
|
4239
5094
|
hit,
|
|
4240
|
-
...resultCount !== void 0 ? { resultCount } : {},
|
|
5095
|
+
...resultCount !== void 0 ? { resultCount, count: resultCount } : {},
|
|
4241
5096
|
...usage ? { usage } : {}
|
|
4242
5097
|
}
|
|
4243
5098
|
});
|
|
4244
5099
|
return data;
|
|
4245
5100
|
} catch (err) {
|
|
4246
|
-
this.
|
|
4247
|
-
type: "
|
|
5101
|
+
this.emitEvent({
|
|
5102
|
+
type: "memory_recall",
|
|
4248
5103
|
data: {
|
|
4249
|
-
event: "memory_recall",
|
|
4250
5104
|
key,
|
|
4251
5105
|
scope,
|
|
4252
5106
|
semantic,
|
|
@@ -4271,15 +5125,14 @@ ${summaryResponse.content}`
|
|
|
4271
5125
|
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4272
5126
|
try {
|
|
4273
5127
|
await this.memoryManager.forget(key, this.stateStore, sessionId, options);
|
|
4274
|
-
this.
|
|
4275
|
-
type: "
|
|
4276
|
-
data: {
|
|
5128
|
+
this.emitEvent({
|
|
5129
|
+
type: "memory_forget",
|
|
5130
|
+
data: { key, scope }
|
|
4277
5131
|
});
|
|
4278
5132
|
} catch (err) {
|
|
4279
|
-
this.
|
|
4280
|
-
type: "
|
|
5133
|
+
this.emitEvent({
|
|
5134
|
+
type: "memory_forget",
|
|
4281
5135
|
data: {
|
|
4282
|
-
event: "memory_forget",
|
|
4283
5136
|
key,
|
|
4284
5137
|
scope,
|
|
4285
5138
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -4314,7 +5167,7 @@ ${summaryResponse.content}`
|
|
|
4314
5167
|
names.add(a._name);
|
|
4315
5168
|
}
|
|
4316
5169
|
if (agents.length === 1) {
|
|
4317
|
-
this.
|
|
5170
|
+
this.emitEvent({
|
|
4318
5171
|
type: "delegate",
|
|
4319
5172
|
agent: agents[0]._name,
|
|
4320
5173
|
data: {
|
|
@@ -4358,7 +5211,7 @@ ${summaryResponse.content}`
|
|
|
4358
5211
|
resolveModel: () => routerModelUri,
|
|
4359
5212
|
resolveSystem: () => routerSystem
|
|
4360
5213
|
};
|
|
4361
|
-
this.
|
|
5214
|
+
this.emitEvent({
|
|
4362
5215
|
type: "delegate",
|
|
4363
5216
|
agent: "_delegate_router",
|
|
4364
5217
|
data: {
|
|
@@ -4379,107 +5232,42 @@ ${summaryResponse.content}`
|
|
|
4379
5232
|
/**
|
|
4380
5233
|
* Internal emitter input — intentionally loose so call sites don't need to
|
|
4381
5234
|
* build a perfectly-narrowed discriminated-union member. The resulting
|
|
4382
|
-
* `
|
|
5235
|
+
* `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
|
|
4383
5236
|
* consumer sites via the `type` discriminator.
|
|
4384
5237
|
*/
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
prompt: "[redacted]",
|
|
4393
|
-
response: "[redacted]"
|
|
4394
|
-
};
|
|
4395
|
-
if (d.system !== void 0) redacted.system = "[redacted]";
|
|
4396
|
-
if (d.thinking !== void 0) redacted.thinking = "[redacted]";
|
|
4397
|
-
if (Array.isArray(d.messages)) {
|
|
4398
|
-
redacted.messages = [
|
|
4399
|
-
{ role: "system", content: `[${d.messages.length} messages redacted]` }
|
|
4400
|
-
];
|
|
4401
|
-
}
|
|
4402
|
-
data = redacted;
|
|
4403
|
-
} else if (partial.type === "guardrail" || partial.type === "schema_check" || partial.type === "validate") {
|
|
4404
|
-
const d = data;
|
|
4405
|
-
const needsRedact = d.feedbackMessage !== void 0 || d.reason !== void 0;
|
|
4406
|
-
if (needsRedact) {
|
|
4407
|
-
data = {
|
|
4408
|
-
...d,
|
|
4409
|
-
...d.reason !== void 0 ? { reason: "[redacted]" } : {},
|
|
4410
|
-
...d.feedbackMessage !== void 0 ? { feedbackMessage: "[redacted]" } : {}
|
|
4411
|
-
};
|
|
4412
|
-
}
|
|
4413
|
-
} else if (partial.type === "tool_call") {
|
|
4414
|
-
const d = data;
|
|
4415
|
-
data = {
|
|
4416
|
-
...d,
|
|
4417
|
-
args: "[redacted]",
|
|
4418
|
-
result: "[redacted]"
|
|
4419
|
-
};
|
|
4420
|
-
} else if (partial.type === "tool_approval") {
|
|
4421
|
-
const d = data;
|
|
4422
|
-
data = {
|
|
4423
|
-
...d,
|
|
4424
|
-
args: "[redacted]",
|
|
4425
|
-
...d.reason !== void 0 ? { reason: "[redacted]" } : {}
|
|
4426
|
-
};
|
|
4427
|
-
} else if (partial.type === "handoff") {
|
|
4428
|
-
const d = data;
|
|
4429
|
-
if (d.message !== void 0) {
|
|
4430
|
-
data = { ...d, message: "[redacted]" };
|
|
4431
|
-
}
|
|
4432
|
-
} else if (partial.type === "workflow_start") {
|
|
4433
|
-
const d = data;
|
|
4434
|
-
if (d.input !== void 0) {
|
|
4435
|
-
data = { ...d, input: "[redacted]" };
|
|
4436
|
-
}
|
|
4437
|
-
} else if (partial.type === "workflow_end") {
|
|
4438
|
-
const d = data;
|
|
4439
|
-
const redacted = { ...d };
|
|
4440
|
-
if (d.result !== void 0) redacted.result = "[redacted]";
|
|
4441
|
-
if (d.error !== void 0) redacted.error = "[redacted]";
|
|
4442
|
-
data = redacted;
|
|
4443
|
-
} else if (partial.type === "log") {
|
|
4444
|
-
const d = data;
|
|
4445
|
-
const redacted = {};
|
|
4446
|
-
for (const [k, v] of Object.entries(d)) {
|
|
4447
|
-
if (k === "event" || typeof v === "number" || typeof v === "boolean") {
|
|
4448
|
-
redacted[k] = v;
|
|
4449
|
-
} else if (typeof v === "string") {
|
|
4450
|
-
redacted[k] = "[redacted]";
|
|
4451
|
-
} else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
4452
|
-
const inner = v;
|
|
4453
|
-
const innerRedacted = {};
|
|
4454
|
-
for (const [ik, iv] of Object.entries(inner)) {
|
|
4455
|
-
if (typeof iv === "number" || typeof iv === "boolean") {
|
|
4456
|
-
innerRedacted[ik] = iv;
|
|
4457
|
-
} else if (typeof iv === "string") {
|
|
4458
|
-
innerRedacted[ik] = "[redacted]";
|
|
4459
|
-
} else {
|
|
4460
|
-
innerRedacted[ik] = "[redacted]";
|
|
4461
|
-
}
|
|
4462
|
-
}
|
|
4463
|
-
redacted[k] = innerRedacted;
|
|
4464
|
-
} else {
|
|
4465
|
-
redacted[k] = "[redacted]";
|
|
4466
|
-
}
|
|
4467
|
-
}
|
|
4468
|
-
data = redacted;
|
|
4469
|
-
}
|
|
5238
|
+
emitEvent(partial) {
|
|
5239
|
+
const data = partial.data;
|
|
5240
|
+
const frame = askStorage.getStore();
|
|
5241
|
+
const step = (frame?.stepRef ?? this.stepRefRoot).value++;
|
|
5242
|
+
const cost = partial.cost;
|
|
5243
|
+
if (frame && typeof cost === "number" && Number.isFinite(cost) && cost >= 0 && COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
5244
|
+
frame.askCost.value += cost;
|
|
4470
5245
|
}
|
|
4471
5246
|
const event = {
|
|
4472
5247
|
executionId: this.executionId,
|
|
4473
|
-
step
|
|
5248
|
+
step,
|
|
4474
5249
|
timestamp: Date.now(),
|
|
4475
5250
|
...this.workflowName ? { workflow: this.workflowName } : {},
|
|
4476
|
-
|
|
5251
|
+
// Stamp ask correlation from ALS. Variants like `workflow_start` /
|
|
5252
|
+
// `workflow_end` and out-of-ask events (log/memory/checkpoint/await_human
|
|
5253
|
+
// when emitted at workflow scope) may legitimately have no frame —
|
|
5254
|
+
// they still get `depth: 0` so consumers can render them at root
|
|
5255
|
+
// level without having to guess. `askId`/`parentAskId`/`agent` stay
|
|
5256
|
+
// unset for non-ask-scoped events, matching the `Partial<AskScoped>`
|
|
5257
|
+
// shape on those union members.
|
|
5258
|
+
...frame ? {
|
|
5259
|
+
askId: frame.askId,
|
|
5260
|
+
...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
|
|
5261
|
+
depth: frame.depth,
|
|
5262
|
+
...frame.agent ? { agent: frame.agent } : {}
|
|
5263
|
+
} : { depth: 0 },
|
|
4477
5264
|
...partial,
|
|
4478
5265
|
data
|
|
4479
5266
|
};
|
|
5267
|
+
const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
|
|
4480
5268
|
if (this.onTrace) {
|
|
4481
5269
|
try {
|
|
4482
|
-
this.onTrace(
|
|
5270
|
+
this.onTrace(finalEvent);
|
|
4483
5271
|
} catch (err) {
|
|
4484
5272
|
console.error(
|
|
4485
5273
|
"[axl] onTrace handler threw; trace event dropped:",
|
|
@@ -4491,7 +5279,7 @@ ${summaryResponse.content}`
|
|
|
4491
5279
|
};
|
|
4492
5280
|
|
|
4493
5281
|
// src/agent.ts
|
|
4494
|
-
var
|
|
5282
|
+
var import_node_crypto2 = require("crypto");
|
|
4495
5283
|
var agentCounter = 0;
|
|
4496
5284
|
function agent(config) {
|
|
4497
5285
|
agentCounter++;
|
|
@@ -4511,7 +5299,7 @@ function agent(config) {
|
|
|
4511
5299
|
const registry = new ProviderRegistry();
|
|
4512
5300
|
const ctx = new WorkflowContext({
|
|
4513
5301
|
input: prompt,
|
|
4514
|
-
executionId: (0,
|
|
5302
|
+
executionId: (0, import_node_crypto2.randomUUID)(),
|
|
4515
5303
|
config: {},
|
|
4516
5304
|
providerRegistry: registry
|
|
4517
5305
|
});
|
|
@@ -4535,7 +5323,7 @@ function workflow(config) {
|
|
|
4535
5323
|
|
|
4536
5324
|
// src/runtime.ts
|
|
4537
5325
|
var import_node_events2 = require("events");
|
|
4538
|
-
var
|
|
5326
|
+
var import_node_crypto3 = require("crypto");
|
|
4539
5327
|
var import_node_async_hooks2 = require("async_hooks");
|
|
4540
5328
|
var import_node_fs2 = require("fs");
|
|
4541
5329
|
var import_node_path2 = require("path");
|
|
@@ -4558,29 +5346,20 @@ var MemoryStore = class {
|
|
|
4558
5346
|
constructor() {
|
|
4559
5347
|
this.loadPersistedState();
|
|
4560
5348
|
}
|
|
4561
|
-
async saveCheckpoint(executionId,
|
|
4562
|
-
let
|
|
4563
|
-
if (!
|
|
4564
|
-
|
|
4565
|
-
this.checkpoints.set(executionId,
|
|
5349
|
+
async saveCheckpoint(executionId, name, data) {
|
|
5350
|
+
let entries = this.checkpoints.get(executionId);
|
|
5351
|
+
if (!entries) {
|
|
5352
|
+
entries = /* @__PURE__ */ new Map();
|
|
5353
|
+
this.checkpoints.set(executionId, entries);
|
|
4566
5354
|
}
|
|
4567
|
-
|
|
5355
|
+
entries.set(name, structuredClone(data));
|
|
4568
5356
|
}
|
|
4569
|
-
async getCheckpoint(executionId,
|
|
4570
|
-
const
|
|
4571
|
-
if (!
|
|
4572
|
-
const data =
|
|
5357
|
+
async getCheckpoint(executionId, name) {
|
|
5358
|
+
const entries = this.checkpoints.get(executionId);
|
|
5359
|
+
if (!entries) return null;
|
|
5360
|
+
const data = entries.get(name);
|
|
4573
5361
|
return data !== void 0 ? structuredClone(data) : null;
|
|
4574
5362
|
}
|
|
4575
|
-
async getLatestCheckpoint(executionId) {
|
|
4576
|
-
const steps = this.checkpoints.get(executionId);
|
|
4577
|
-
if (!steps || steps.size === 0) return null;
|
|
4578
|
-
let maxStep = -1;
|
|
4579
|
-
for (const step of steps.keys()) {
|
|
4580
|
-
if (step > maxStep) maxStep = step;
|
|
4581
|
-
}
|
|
4582
|
-
return { step: maxStep, data: structuredClone(steps.get(maxStep)) };
|
|
4583
|
-
}
|
|
4584
5363
|
async saveSession(sessionId, history) {
|
|
4585
5364
|
this.sessions.set(sessionId, structuredClone(history));
|
|
4586
5365
|
}
|
|
@@ -4753,7 +5532,7 @@ function rowToExecutionInfo(row) {
|
|
|
4753
5532
|
completedAt: row.completed_at ?? void 0,
|
|
4754
5533
|
duration: row.duration,
|
|
4755
5534
|
error: row.error ?? void 0,
|
|
4756
|
-
|
|
5535
|
+
events: safeJsonParse(row.events) ?? []
|
|
4757
5536
|
};
|
|
4758
5537
|
}
|
|
4759
5538
|
var SQLiteStore = class {
|
|
@@ -4769,15 +5548,80 @@ var SQLiteStore = class {
|
|
|
4769
5548
|
}
|
|
4770
5549
|
this.db = new BetterSqlite3(filePath);
|
|
4771
5550
|
this.db.pragma("journal_mode = WAL");
|
|
5551
|
+
this.migrate();
|
|
4772
5552
|
this.initTables();
|
|
4773
5553
|
}
|
|
5554
|
+
/**
|
|
5555
|
+
* Idempotent schema migration. Tracks version via `PRAGMA user_version`
|
|
5556
|
+
* so reopens skip applied steps. Wrapped in `BEGIN IMMEDIATE` so
|
|
5557
|
+
* concurrent `SQLiteStore` constructors serialize at the file level
|
|
5558
|
+
* and rollback on failure leaves the DB in a clean pre-migration
|
|
5559
|
+
* state.
|
|
5560
|
+
*
|
|
5561
|
+
* Migration history:
|
|
5562
|
+
* - v1 (spec/16): rename `execution_history.steps` → `events`. The
|
|
5563
|
+
* JS-level `ExecutionInfo.steps` was renamed to `.events` in PR 1
|
|
5564
|
+
* commit 1; this brings the on-disk schema in line.
|
|
5565
|
+
* - v2 (named checkpoints): rename `checkpoints.step` (INTEGER) →
|
|
5566
|
+
* `checkpoints.name` (TEXT). The runtime now requires
|
|
5567
|
+
* `ctx.checkpoint(name, fn)` so the per-context numeric counter is
|
|
5568
|
+
* gone — names are caller-supplied stable identifiers. Existing
|
|
5569
|
+
* numeric step values become stringified names ("0", "1", …) to
|
|
5570
|
+
* preserve replay continuity for in-flight executions across the
|
|
5571
|
+
* upgrade boundary.
|
|
5572
|
+
*
|
|
5573
|
+
* Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
|
|
5574
|
+
* IF NOT EXISTS` runs against the post-migration column name. Fresh
|
|
5575
|
+
* installs just create the table with `events` directly and bump
|
|
5576
|
+
* `user_version` to the current version.
|
|
5577
|
+
*
|
|
5578
|
+
* TOCTOU guard (review B-3): the initial `user_version` read is
|
|
5579
|
+
* informational only — a concurrent process can complete the
|
|
5580
|
+
* migration between that read and our `BEGIN IMMEDIATE`. We
|
|
5581
|
+
* re-read `user_version` inside the transaction and short-circuit
|
|
5582
|
+
* if it's already at target, so the non-idempotent half (writes
|
|
5583
|
+
* that went beyond a simple ALTER) never double-applies under a
|
|
5584
|
+
* race. The current v0→v1 step IS idempotent via the column-
|
|
5585
|
+
* presence check, but future migrations may not be, and the
|
|
5586
|
+
* transactional re-read costs nothing.
|
|
5587
|
+
*/
|
|
5588
|
+
migrate() {
|
|
5589
|
+
const TARGET_VERSION = 2;
|
|
5590
|
+
const current = this.db.pragma("user_version", { simple: true });
|
|
5591
|
+
if (current >= TARGET_VERSION) return;
|
|
5592
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
5593
|
+
try {
|
|
5594
|
+
const committed = this.db.pragma("user_version", { simple: true });
|
|
5595
|
+
if (committed >= TARGET_VERSION) {
|
|
5596
|
+
this.db.exec("COMMIT");
|
|
5597
|
+
return;
|
|
5598
|
+
}
|
|
5599
|
+
if (committed < 1) {
|
|
5600
|
+
const cols = this.db.pragma("table_info(execution_history)");
|
|
5601
|
+
if (cols.some((c) => c.name === "steps") && !cols.some((c) => c.name === "events")) {
|
|
5602
|
+
this.db.exec("ALTER TABLE execution_history RENAME COLUMN steps TO events");
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5605
|
+
if (committed < 2) {
|
|
5606
|
+
const cols = this.db.pragma("table_info(checkpoints)");
|
|
5607
|
+
if (cols.some((c) => c.name === "step") && !cols.some((c) => c.name === "name")) {
|
|
5608
|
+
this.db.exec("ALTER TABLE checkpoints RENAME COLUMN step TO name");
|
|
5609
|
+
}
|
|
5610
|
+
}
|
|
5611
|
+
this.db.pragma(`user_version = ${TARGET_VERSION}`);
|
|
5612
|
+
this.db.exec("COMMIT");
|
|
5613
|
+
} catch (err) {
|
|
5614
|
+
this.db.exec("ROLLBACK");
|
|
5615
|
+
throw err;
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
4774
5618
|
initTables() {
|
|
4775
5619
|
this.db.exec(`
|
|
4776
5620
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
4777
5621
|
execution_id TEXT NOT NULL,
|
|
4778
|
-
|
|
5622
|
+
name TEXT NOT NULL,
|
|
4779
5623
|
data TEXT NOT NULL,
|
|
4780
|
-
PRIMARY KEY (execution_id,
|
|
5624
|
+
PRIMARY KEY (execution_id, name)
|
|
4781
5625
|
);
|
|
4782
5626
|
|
|
4783
5627
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -4826,7 +5670,7 @@ var SQLiteStore = class {
|
|
|
4826
5670
|
completed_at INTEGER,
|
|
4827
5671
|
duration INTEGER NOT NULL DEFAULT 0,
|
|
4828
5672
|
error TEXT,
|
|
4829
|
-
|
|
5673
|
+
events TEXT NOT NULL
|
|
4830
5674
|
);
|
|
4831
5675
|
|
|
4832
5676
|
CREATE TABLE IF NOT EXISTS eval_history (
|
|
@@ -4841,26 +5685,19 @@ var SQLiteStore = class {
|
|
|
4841
5685
|
`);
|
|
4842
5686
|
}
|
|
4843
5687
|
// ── Checkpoints ────────────────────────────────────────────────────────
|
|
4844
|
-
async saveCheckpoint(executionId,
|
|
5688
|
+
async saveCheckpoint(executionId, name, data) {
|
|
4845
5689
|
const stmt = this.db.prepare(
|
|
4846
|
-
"INSERT OR REPLACE INTO checkpoints (execution_id,
|
|
5690
|
+
"INSERT OR REPLACE INTO checkpoints (execution_id, name, data) VALUES (?, ?, ?)"
|
|
4847
5691
|
);
|
|
4848
|
-
stmt.run(executionId,
|
|
5692
|
+
stmt.run(executionId, name, JSON.stringify(data));
|
|
4849
5693
|
}
|
|
4850
|
-
async getCheckpoint(executionId,
|
|
5694
|
+
async getCheckpoint(executionId, name) {
|
|
4851
5695
|
const stmt = this.db.prepare(
|
|
4852
|
-
"SELECT data FROM checkpoints WHERE execution_id = ? AND
|
|
5696
|
+
"SELECT data FROM checkpoints WHERE execution_id = ? AND name = ?"
|
|
4853
5697
|
);
|
|
4854
|
-
const row = stmt.get(executionId,
|
|
5698
|
+
const row = stmt.get(executionId, name);
|
|
4855
5699
|
return row ? safeJsonParse(row.data) : null;
|
|
4856
5700
|
}
|
|
4857
|
-
async getLatestCheckpoint(executionId) {
|
|
4858
|
-
const stmt = this.db.prepare(
|
|
4859
|
-
"SELECT step, data FROM checkpoints WHERE execution_id = ? ORDER BY step DESC LIMIT 1"
|
|
4860
|
-
);
|
|
4861
|
-
const row = stmt.get(executionId);
|
|
4862
|
-
return row ? { step: row.step, data: safeJsonParse(row.data) } : null;
|
|
4863
|
-
}
|
|
4864
5701
|
// ── Sessions ────────────────────────────────────────────────────────────
|
|
4865
5702
|
async saveSession(sessionId, history) {
|
|
4866
5703
|
const stmt = this.db.prepare(
|
|
@@ -4954,7 +5791,7 @@ var SQLiteStore = class {
|
|
|
4954
5791
|
// ── Execution History ────────────────────────────────────────────────────
|
|
4955
5792
|
async saveExecution(execution) {
|
|
4956
5793
|
this.db.prepare(
|
|
4957
|
-
"INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error,
|
|
5794
|
+
"INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4958
5795
|
).run(
|
|
4959
5796
|
execution.executionId,
|
|
4960
5797
|
execution.workflow,
|
|
@@ -4964,7 +5801,7 @@ var SQLiteStore = class {
|
|
|
4964
5801
|
execution.completedAt ?? null,
|
|
4965
5802
|
execution.duration,
|
|
4966
5803
|
execution.error ?? null,
|
|
4967
|
-
JSON.stringify(execution.
|
|
5804
|
+
JSON.stringify(execution.events)
|
|
4968
5805
|
);
|
|
4969
5806
|
}
|
|
4970
5807
|
async getExecution(executionId) {
|
|
@@ -5138,7 +5975,8 @@ var Session = class _Session {
|
|
|
5138
5975
|
}
|
|
5139
5976
|
};
|
|
5140
5977
|
axlStream.on("done", (event) => {
|
|
5141
|
-
|
|
5978
|
+
const data = event.data;
|
|
5979
|
+
updateHistory(data.result).catch((err) => {
|
|
5142
5980
|
this.runtime.emit("error", {
|
|
5143
5981
|
type: "session_history_save_failed",
|
|
5144
5982
|
sessionId: this.sessionId,
|
|
@@ -5180,9 +6018,88 @@ var Session = class _Session {
|
|
|
5180
6018
|
// src/stream.ts
|
|
5181
6019
|
var import_node_stream = require("stream");
|
|
5182
6020
|
var import_node_events = require("events");
|
|
6021
|
+
|
|
6022
|
+
// src/types.ts
|
|
6023
|
+
var AXL_EVENT_TYPES = [
|
|
6024
|
+
// Workflow lifecycle
|
|
6025
|
+
"workflow_start",
|
|
6026
|
+
"workflow_end",
|
|
6027
|
+
// Ask boundary
|
|
6028
|
+
"ask_start",
|
|
6029
|
+
"ask_end",
|
|
6030
|
+
// Agent turn lifecycle
|
|
6031
|
+
"agent_call_start",
|
|
6032
|
+
"agent_call_end",
|
|
6033
|
+
// Content delivery (stream-only)
|
|
6034
|
+
"token",
|
|
6035
|
+
// Tool invocation lifecycle
|
|
6036
|
+
"tool_call_start",
|
|
6037
|
+
"tool_call_end",
|
|
6038
|
+
// Single-point tool events
|
|
6039
|
+
"tool_approval",
|
|
6040
|
+
"tool_denied",
|
|
6041
|
+
// Delegation
|
|
6042
|
+
"delegate",
|
|
6043
|
+
// Handoff (spans two asks; not AskScoped).
|
|
6044
|
+
// `handoff_start` emits before the target ask begins; always fired.
|
|
6045
|
+
// `handoff_return` emits when control returns to source; roundtrip only
|
|
6046
|
+
// (oneway handoffs have no return trip — the target's `ask_end` IS the
|
|
6047
|
+
// end of the chain).
|
|
6048
|
+
"handoff_start",
|
|
6049
|
+
"handoff_return",
|
|
6050
|
+
// Pipeline (retry/validation lifecycle) — added in PR 2; reserved here.
|
|
6051
|
+
"pipeline",
|
|
6052
|
+
// Progressive structured output — added in PR 2; reserved here.
|
|
6053
|
+
"partial_object",
|
|
6054
|
+
// Verification
|
|
6055
|
+
"verify",
|
|
6056
|
+
// Observability
|
|
6057
|
+
"log",
|
|
6058
|
+
"memory_remember",
|
|
6059
|
+
"memory_recall",
|
|
6060
|
+
"memory_forget",
|
|
6061
|
+
// Durable execution checkpoints (`ctx.checkpoint`)
|
|
6062
|
+
"checkpoint_save",
|
|
6063
|
+
"checkpoint_replay",
|
|
6064
|
+
// Human-in-the-loop (`ctx.awaitHuman`)
|
|
6065
|
+
"await_human",
|
|
6066
|
+
"await_human_resolved",
|
|
6067
|
+
// Legacy gate events — emitted by current code; collapsed into `pipeline`
|
|
6068
|
+
// in PR 2 (spec/16-streaming-wire-reliability §4.2).
|
|
6069
|
+
"guardrail",
|
|
6070
|
+
"schema_check",
|
|
6071
|
+
"validate",
|
|
6072
|
+
// Terminal workflow markers
|
|
6073
|
+
"done",
|
|
6074
|
+
"error"
|
|
6075
|
+
];
|
|
6076
|
+
|
|
6077
|
+
// src/stream.ts
|
|
5183
6078
|
var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
5184
6079
|
bus = new import_node_events.EventEmitter();
|
|
5185
|
-
|
|
6080
|
+
/**
|
|
6081
|
+
* Per-ask token buffers split into "in-progress" and "committed"
|
|
6082
|
+
* halves so `fullText` only includes tokens from attempts that
|
|
6083
|
+
* actually won. Scoping is per-`askId` so concurrent root-level asks
|
|
6084
|
+
* (`ctx.parallel`, `ctx.spawn`, `ctx.race`, `ctx.map`) don't
|
|
6085
|
+
* interleave each other's tokens — each branch's chunks stay
|
|
6086
|
+
* contiguous, and a `pipeline(failed)` on one branch only discards
|
|
6087
|
+
* THAT branch's in-progress buffer (previously: shared buffer caused
|
|
6088
|
+
* a failure on one branch to discard a peer's in-flight successful
|
|
6089
|
+
* tokens). Insertion order in the Maps reflects which ask emitted
|
|
6090
|
+
* its first token first, which is what `fullText` joins on.
|
|
6091
|
+
*
|
|
6092
|
+
* On `pipeline(status: 'committed')` for an ask, that ask's
|
|
6093
|
+
* in-progress entry flushes to its committed entry. On
|
|
6094
|
+
* `pipeline(status: 'failed')` or `ask_end({ok:false})` for an ask,
|
|
6095
|
+
* that ask's in-progress entry is discarded. Spec/16 §4.3.
|
|
6096
|
+
*
|
|
6097
|
+
* Tokens emitted outside any ask (synthesized test fixtures with no
|
|
6098
|
+
* `askId`) fall back to an empty-string sentinel key — preserves the
|
|
6099
|
+
* single-buffer behavior for that legacy case.
|
|
6100
|
+
*/
|
|
6101
|
+
attemptByAsk = /* @__PURE__ */ new Map();
|
|
6102
|
+
committedByAsk = /* @__PURE__ */ new Map();
|
|
5186
6103
|
result = void 0;
|
|
5187
6104
|
finished = false;
|
|
5188
6105
|
promise;
|
|
@@ -5204,18 +6121,10 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5204
6121
|
this.promise.catch(() => {
|
|
5205
6122
|
});
|
|
5206
6123
|
}
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
"tool_approval",
|
|
5212
|
-
"agent_start",
|
|
5213
|
-
"agent_end",
|
|
5214
|
-
"handoff",
|
|
5215
|
-
"step",
|
|
5216
|
-
"done",
|
|
5217
|
-
"error"
|
|
5218
|
-
]);
|
|
6124
|
+
/** Wire-format event names callers can subscribe to via `.on(name, fn)`.
|
|
6125
|
+
* Derived from the canonical `AXL_EVENT_TYPES` tuple — adding a new
|
|
6126
|
+
* variant in `types.ts` automatically extends the subscribable set. */
|
|
6127
|
+
static STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
|
|
5219
6128
|
on(event, handler) {
|
|
5220
6129
|
if (_AxlStream.STREAM_EVENTS.has(event)) {
|
|
5221
6130
|
this.bus.on(event, handler);
|
|
@@ -5265,21 +6174,83 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5265
6174
|
while (true) {
|
|
5266
6175
|
const { value, done } = await iter.next();
|
|
5267
6176
|
if (done) return { value: void 0, done: true };
|
|
5268
|
-
if (value.type === "token"
|
|
6177
|
+
if (value.type === "token" && isRootLevel(value)) {
|
|
6178
|
+
return { value: value.data, done: false };
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
6184
|
+
};
|
|
6185
|
+
}
|
|
6186
|
+
/**
|
|
6187
|
+
* Iterator over `{ askId, text }` pairs — one emission per token chunk,
|
|
6188
|
+
* tagged with the ask frame that produced it. Complements `.text`
|
|
6189
|
+
* (root-only stream): consumers building a split-pane UI that shows
|
|
6190
|
+
* each sub-agent's output in its own lane can group by `askId`
|
|
6191
|
+
* without iterating the raw stream and hand-filtering on `event.type`.
|
|
6192
|
+
*
|
|
6193
|
+
* Nested and root tokens both flow through. The `agent` field names
|
|
6194
|
+
* the producing agent for UI labelling; it's undefined when the token
|
|
6195
|
+
* was emitted outside any ask (rare — synthesized test fixtures).
|
|
6196
|
+
*/
|
|
6197
|
+
get textByAsk() {
|
|
6198
|
+
const self = this;
|
|
6199
|
+
return {
|
|
6200
|
+
[Symbol.asyncIterator]() {
|
|
6201
|
+
const iter = self[Symbol.asyncIterator]();
|
|
6202
|
+
return {
|
|
6203
|
+
async next() {
|
|
6204
|
+
while (true) {
|
|
6205
|
+
const { value, done } = await iter.next();
|
|
6206
|
+
if (done) {
|
|
6207
|
+
return {
|
|
6208
|
+
value: void 0,
|
|
6209
|
+
done: true
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
if (value.type === "token") {
|
|
6213
|
+
return {
|
|
6214
|
+
value: { askId: value.askId, agent: value.agent, text: value.data },
|
|
6215
|
+
done: false
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
5269
6218
|
}
|
|
5270
6219
|
}
|
|
5271
6220
|
};
|
|
5272
6221
|
}
|
|
5273
6222
|
};
|
|
5274
6223
|
}
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
6224
|
+
/**
|
|
6225
|
+
* Iterator over structural lifecycle events only — skips per-token chatter
|
|
6226
|
+
* and progressive partial_object emissions. Useful for waterfall UIs and
|
|
6227
|
+
* any consumer that wants the "what happened" timeline without per-chunk
|
|
6228
|
+
* noise. Renamed from `.steps` in the unified-event-model migration
|
|
6229
|
+
* because these are events, not pipeline steps.
|
|
6230
|
+
*/
|
|
6231
|
+
get lifecycle() {
|
|
6232
|
+
const lifecycleTypes = /* @__PURE__ */ new Set([
|
|
6233
|
+
"ask_start",
|
|
6234
|
+
"ask_end",
|
|
6235
|
+
"agent_call_start",
|
|
6236
|
+
"agent_call_end",
|
|
6237
|
+
"tool_call_start",
|
|
6238
|
+
"tool_call_end",
|
|
5281
6239
|
"tool_approval",
|
|
5282
|
-
"
|
|
6240
|
+
"tool_denied",
|
|
6241
|
+
"handoff_start",
|
|
6242
|
+
"handoff_return",
|
|
6243
|
+
"delegate",
|
|
6244
|
+
"pipeline",
|
|
6245
|
+
"verify",
|
|
6246
|
+
"workflow_start",
|
|
6247
|
+
"workflow_end",
|
|
6248
|
+
// Durable-execution checkpoints — structural points in the timeline.
|
|
6249
|
+
"checkpoint_save",
|
|
6250
|
+
"checkpoint_replay",
|
|
6251
|
+
// Human-in-the-loop — pause/resume are major timeline landmarks.
|
|
6252
|
+
"await_human",
|
|
6253
|
+
"await_human_resolved"
|
|
5283
6254
|
]);
|
|
5284
6255
|
const self = this;
|
|
5285
6256
|
return {
|
|
@@ -5290,7 +6261,7 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5290
6261
|
while (true) {
|
|
5291
6262
|
const { value, done } = await iter.next();
|
|
5292
6263
|
if (done) return { value: void 0, done: true };
|
|
5293
|
-
if (
|
|
6264
|
+
if (lifecycleTypes.has(value.type)) return { value, done: false };
|
|
5294
6265
|
}
|
|
5295
6266
|
}
|
|
5296
6267
|
};
|
|
@@ -5310,10 +6281,34 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5310
6281
|
});
|
|
5311
6282
|
return destination;
|
|
5312
6283
|
}
|
|
5313
|
-
/** Push
|
|
6284
|
+
/** Push an event onto the stream. Called by the runtime. */
|
|
5314
6285
|
_push(event) {
|
|
5315
6286
|
if (this.finished) return;
|
|
5316
|
-
if (event.type === "token"
|
|
6287
|
+
if (event.type === "token" && isRootLevel(event)) {
|
|
6288
|
+
const key = event.askId ?? "";
|
|
6289
|
+
const attempt = this.attemptByAsk.get(key);
|
|
6290
|
+
if (attempt) {
|
|
6291
|
+
attempt.push(event.data);
|
|
6292
|
+
} else {
|
|
6293
|
+
this.attemptByAsk.set(key, [event.data]);
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
if (event.type === "pipeline" && isRootLevel(event)) {
|
|
6297
|
+
const key = event.askId ?? "";
|
|
6298
|
+
if (event.status === "committed") {
|
|
6299
|
+
const attempt = this.attemptByAsk.get(key);
|
|
6300
|
+
if (attempt && attempt.length > 0) {
|
|
6301
|
+
const prev = this.committedByAsk.get(key) ?? "";
|
|
6302
|
+
this.committedByAsk.set(key, prev + attempt.join(""));
|
|
6303
|
+
this.attemptByAsk.set(key, []);
|
|
6304
|
+
}
|
|
6305
|
+
} else if (event.status === "failed") {
|
|
6306
|
+
this.attemptByAsk.set(key, []);
|
|
6307
|
+
}
|
|
6308
|
+
}
|
|
6309
|
+
if (event.type === "ask_end" && isRootLevel(event) && !event.outcome.ok) {
|
|
6310
|
+
this.attemptByAsk.set(event.askId ?? "", []);
|
|
6311
|
+
}
|
|
5317
6312
|
this.bus.emit(event.type, event);
|
|
5318
6313
|
this.push(event);
|
|
5319
6314
|
const waiter = this.waiters.shift();
|
|
@@ -5323,12 +6318,25 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5323
6318
|
this.eventQueue.push(event);
|
|
5324
6319
|
}
|
|
5325
6320
|
}
|
|
5326
|
-
/**
|
|
5327
|
-
|
|
6321
|
+
/**
|
|
6322
|
+
* Signal successful completion.
|
|
6323
|
+
*
|
|
6324
|
+
* `executionId` is required — the runtime must allocate it before
|
|
6325
|
+
* calling `stream()` so terminal events always carry a real id.
|
|
6326
|
+
* Previously the default-empty parameter surfaced blank executionIds
|
|
6327
|
+
* on error paths that threw before `execInfo` was assigned (review S4).
|
|
6328
|
+
*/
|
|
6329
|
+
_done(result, executionId) {
|
|
5328
6330
|
if (this.finished) return;
|
|
5329
6331
|
this.finished = true;
|
|
5330
6332
|
this.result = result;
|
|
5331
|
-
const doneEvent = {
|
|
6333
|
+
const doneEvent = {
|
|
6334
|
+
type: "done",
|
|
6335
|
+
executionId,
|
|
6336
|
+
step: Number.MAX_SAFE_INTEGER,
|
|
6337
|
+
timestamp: Date.now(),
|
|
6338
|
+
data: { result }
|
|
6339
|
+
};
|
|
5332
6340
|
this.bus.emit("done", doneEvent);
|
|
5333
6341
|
this.push(doneEvent);
|
|
5334
6342
|
this.push(null);
|
|
@@ -5344,11 +6352,19 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5344
6352
|
this.waiters.length = 0;
|
|
5345
6353
|
this.bus.emit("__resolve", result);
|
|
5346
6354
|
}
|
|
5347
|
-
/** Signal an error.
|
|
5348
|
-
|
|
6355
|
+
/** Signal an error. `executionId` is required for the same reason as
|
|
6356
|
+
* `_done`: terminal events must carry a real id even when the
|
|
6357
|
+
* failure happens before any real trace event fires (review S4). */
|
|
6358
|
+
_error(error, executionId) {
|
|
5349
6359
|
if (this.finished) return;
|
|
5350
6360
|
this.finished = true;
|
|
5351
|
-
const errorEvent = {
|
|
6361
|
+
const errorEvent = {
|
|
6362
|
+
type: "error",
|
|
6363
|
+
executionId,
|
|
6364
|
+
step: Number.MAX_SAFE_INTEGER,
|
|
6365
|
+
timestamp: Date.now(),
|
|
6366
|
+
data: { message: error.message, name: error.name }
|
|
6367
|
+
};
|
|
5352
6368
|
this.bus.emit("error", errorEvent);
|
|
5353
6369
|
this.push(errorEvent);
|
|
5354
6370
|
this.push(null);
|
|
@@ -5364,8 +6380,31 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
5364
6380
|
this.waiters.length = 0;
|
|
5365
6381
|
this.bus.emit("__reject", error);
|
|
5366
6382
|
}
|
|
6383
|
+
/** Concatenated root-only text from committed attempts plus the
|
|
6384
|
+
* current in-flight attempt(s). Retried (gate-rejected) attempts
|
|
6385
|
+
* are excluded — see spec/16 §4.3. Reading mid-attempt returns the
|
|
6386
|
+
* in-progress text; reading after `pipeline(committed)` (which
|
|
6387
|
+
* fires before `done`) returns the canonical winning text.
|
|
6388
|
+
*
|
|
6389
|
+
* With concurrent root-level asks (`ctx.parallel`, `ctx.spawn`,
|
|
6390
|
+
* `ctx.race`, `ctx.map`), each branch's tokens are scoped per-`askId`
|
|
6391
|
+
* and emitted contiguously in the order each ask first started
|
|
6392
|
+
* emitting tokens. A `pipeline(failed)` or `ask_end({ok:false})` on
|
|
6393
|
+
* one branch only discards THAT branch's in-progress buffer —
|
|
6394
|
+
* sibling branches are unaffected. For UIs that want each sub-agent
|
|
6395
|
+
* in its own lane, prefer `textByAsk`. */
|
|
5367
6396
|
get fullText() {
|
|
5368
|
-
|
|
6397
|
+
let out = "";
|
|
6398
|
+
for (const [key, committed] of this.committedByAsk) {
|
|
6399
|
+
out += committed;
|
|
6400
|
+
const attempt = this.attemptByAsk.get(key);
|
|
6401
|
+
if (attempt && attempt.length > 0) out += attempt.join("");
|
|
6402
|
+
}
|
|
6403
|
+
for (const [key, attempt] of this.attemptByAsk) {
|
|
6404
|
+
if (this.committedByAsk.has(key)) continue;
|
|
6405
|
+
if (attempt.length > 0) out += attempt.join("");
|
|
6406
|
+
}
|
|
6407
|
+
return out;
|
|
5369
6408
|
}
|
|
5370
6409
|
};
|
|
5371
6410
|
|
|
@@ -5912,6 +6951,32 @@ function hashInput(input) {
|
|
|
5912
6951
|
}
|
|
5913
6952
|
return (hash >>> 0).toString(16);
|
|
5914
6953
|
}
|
|
6954
|
+
var DEFAULT_MAX_EVENTS_PER_EXECUTION = 5e4;
|
|
6955
|
+
function isAbortError(err) {
|
|
6956
|
+
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
6957
|
+
}
|
|
6958
|
+
function pushEventBounded(execInfo, event, cap) {
|
|
6959
|
+
if (event.type === "token" || event.type === "partial_object") return;
|
|
6960
|
+
if (cap === Infinity || execInfo.events.length < cap) {
|
|
6961
|
+
execInfo.events.push(event);
|
|
6962
|
+
return;
|
|
6963
|
+
}
|
|
6964
|
+
const last = execInfo.events[execInfo.events.length - 1];
|
|
6965
|
+
const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
|
|
6966
|
+
if (!alreadyTruncated) {
|
|
6967
|
+
execInfo.events[execInfo.events.length - 1] = {
|
|
6968
|
+
type: "log",
|
|
6969
|
+
executionId: event.executionId,
|
|
6970
|
+
step: event.step,
|
|
6971
|
+
timestamp: Date.now(),
|
|
6972
|
+
data: {
|
|
6973
|
+
event: "events_truncated",
|
|
6974
|
+
cap,
|
|
6975
|
+
message: `ExecutionInfo.events truncated at ${cap} entries. Subsequent events are still delivered to runtime.on('trace') and the WS broadcast \u2014 only the in-memory array is bounded. Raise via config.state.maxEventsPerExecution.`
|
|
6976
|
+
}
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
5915
6980
|
var costScopeStorage = new import_node_async_hooks2.AsyncLocalStorage();
|
|
5916
6981
|
var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
5917
6982
|
config;
|
|
@@ -5931,11 +6996,27 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5931
6996
|
historicalExecutionsLoadPromise = null;
|
|
5932
6997
|
evalHistory = [];
|
|
5933
6998
|
evalHistoryLoadPromise = null;
|
|
6999
|
+
/** Resolved cap on `ExecutionInfo.events` per execution. Cached on
|
|
7000
|
+
* the instance so per-event onTrace handlers don't reach into
|
|
7001
|
+
* `this.config.state` on every emission. */
|
|
7002
|
+
maxEventsPerExecution;
|
|
5934
7003
|
constructor(config) {
|
|
5935
7004
|
super();
|
|
5936
7005
|
this.config = resolveConfig(config ?? {});
|
|
5937
7006
|
this.providerRegistry = new ProviderRegistry();
|
|
5938
7007
|
this.stateStore = this.createStateStore();
|
|
7008
|
+
const requested = this.config.state?.maxEventsPerExecution;
|
|
7009
|
+
if (requested === void 0) {
|
|
7010
|
+
this.maxEventsPerExecution = DEFAULT_MAX_EVENTS_PER_EXECUTION;
|
|
7011
|
+
} else if (requested === Infinity) {
|
|
7012
|
+
this.maxEventsPerExecution = Infinity;
|
|
7013
|
+
} else if (typeof requested !== "number" || !Number.isFinite(requested) || !Number.isInteger(requested) || requested < 1) {
|
|
7014
|
+
throw new RangeError(
|
|
7015
|
+
`config.state.maxEventsPerExecution must be a positive integer or Infinity; got ${requested}`
|
|
7016
|
+
);
|
|
7017
|
+
} else {
|
|
7018
|
+
this.maxEventsPerExecution = requested;
|
|
7019
|
+
}
|
|
5939
7020
|
if (this.config.memory) {
|
|
5940
7021
|
this.memoryManager = new MemoryManager({
|
|
5941
7022
|
vectorStore: this.config.memory.vectorStore,
|
|
@@ -5991,6 +7072,71 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5991
7072
|
return new MemoryStore();
|
|
5992
7073
|
}
|
|
5993
7074
|
}
|
|
7075
|
+
/**
|
|
7076
|
+
* Run a workflow body inside an ALREADY-CONSTRUCTED `WorkflowContext`
|
|
7077
|
+
* with full lifecycle invariants: emit `workflow_start` → run handler
|
|
7078
|
+
* → parse output → emit `workflow_end` → delete checkpoints → persist.
|
|
7079
|
+
*
|
|
7080
|
+
* Shared between `execute()` and `stream()`. Centralizes the
|
|
7081
|
+
* "start iff end" pairing invariant so a future emit path
|
|
7082
|
+
* (e.g., `resumeExecution`) can inherit correct behavior by routing
|
|
7083
|
+
* through this helper. Also factors out the AbortError detection.
|
|
7084
|
+
*
|
|
7085
|
+
* Side effects on `execInfo`: sets `status` / `completedAt` /
|
|
7086
|
+
* `duration` / `result` (or `error`) before emitting `workflow_end`.
|
|
7087
|
+
* On throw: emits `workflow_end({status: 'failed', aborted?})` and
|
|
7088
|
+
* persists before rethrowing. `abortControllers.delete` runs in
|
|
7089
|
+
* `finally` regardless of outcome.
|
|
7090
|
+
*
|
|
7091
|
+
* Caller responsibilities:
|
|
7092
|
+
* - Allocate `executionId` and register it in `abortControllers` map
|
|
7093
|
+
* BEFORE calling this helper (so early-throw paths still have a
|
|
7094
|
+
* correlation id available for terminal events).
|
|
7095
|
+
* - Construct `execInfo` and `ctx` before calling.
|
|
7096
|
+
* - Pass `span` if the call site is inside a tracer span — this
|
|
7097
|
+
* helper sets `axl.workflow.cost` / `.duration` attributes on
|
|
7098
|
+
* success.
|
|
7099
|
+
*/
|
|
7100
|
+
async runWorkflowBody(args) {
|
|
7101
|
+
const { workflow: workflow2, ctx, execInfo, validated, span } = args;
|
|
7102
|
+
try {
|
|
7103
|
+
ctx._emitWorkflowStart(validated);
|
|
7104
|
+
const raw = await workflow2.handler(ctx);
|
|
7105
|
+
const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
|
|
7106
|
+
execInfo.status = "completed";
|
|
7107
|
+
execInfo.completedAt = Date.now();
|
|
7108
|
+
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
7109
|
+
execInfo.result = result;
|
|
7110
|
+
ctx._emitWorkflowEnd({
|
|
7111
|
+
status: "completed",
|
|
7112
|
+
duration: execInfo.duration,
|
|
7113
|
+
result
|
|
7114
|
+
});
|
|
7115
|
+
if (this.stateStore.deleteCheckpoints) {
|
|
7116
|
+
await this.stateStore.deleteCheckpoints(execInfo.executionId);
|
|
7117
|
+
}
|
|
7118
|
+
span?.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
7119
|
+
span?.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
7120
|
+
this.persistExecution(execInfo);
|
|
7121
|
+
return result;
|
|
7122
|
+
} catch (err) {
|
|
7123
|
+
const aborted = isAbortError(err);
|
|
7124
|
+
execInfo.status = "failed";
|
|
7125
|
+
execInfo.completedAt = Date.now();
|
|
7126
|
+
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
7127
|
+
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
7128
|
+
ctx._emitWorkflowEnd({
|
|
7129
|
+
status: "failed",
|
|
7130
|
+
duration: execInfo.duration,
|
|
7131
|
+
error: execInfo.error,
|
|
7132
|
+
...aborted ? { aborted: true } : {}
|
|
7133
|
+
});
|
|
7134
|
+
this.persistExecution(execInfo);
|
|
7135
|
+
throw err;
|
|
7136
|
+
} finally {
|
|
7137
|
+
this.abortControllers.delete(execInfo.executionId);
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
5994
7140
|
/**
|
|
5995
7141
|
* Persist a completed/failed execution to the state store (fire-and-forget)
|
|
5996
7142
|
* and move it from the active executions map to the historical cache.
|
|
@@ -6006,7 +7152,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6006
7152
|
this.executions.delete(id);
|
|
6007
7153
|
}
|
|
6008
7154
|
/** Register a workflow with the runtime. */
|
|
6009
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6010
7155
|
register(workflow2) {
|
|
6011
7156
|
this.workflows.set(workflow2.name, workflow2);
|
|
6012
7157
|
}
|
|
@@ -6132,7 +7277,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6132
7277
|
}
|
|
6133
7278
|
try {
|
|
6134
7279
|
await this.saveEvalResult({
|
|
6135
|
-
id: resultObj.id ?? (0,
|
|
7280
|
+
id: resultObj.id ?? (0, import_node_crypto3.randomUUID)(),
|
|
6136
7281
|
eval: name,
|
|
6137
7282
|
timestamp: Date.now(),
|
|
6138
7283
|
data: structuredClone(result)
|
|
@@ -6169,7 +7314,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6169
7314
|
* and automatically emits trace events and tracks cost.
|
|
6170
7315
|
*/
|
|
6171
7316
|
createContext(options) {
|
|
6172
|
-
const executionId = (0,
|
|
7317
|
+
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
6173
7318
|
const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
|
|
6174
7319
|
this.registerWithCostScope(executionId);
|
|
6175
7320
|
return new WorkflowContext({
|
|
@@ -6185,10 +7330,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6185
7330
|
sessionHistory: options?.sessionHistory,
|
|
6186
7331
|
signal: options?.signal,
|
|
6187
7332
|
onToken: options?.onToken,
|
|
7333
|
+
onToolCall: options?.onToolCall,
|
|
7334
|
+
onAgentStart: options?.onAgentStart,
|
|
6188
7335
|
awaitHumanHandler: options?.awaitHumanHandler,
|
|
6189
7336
|
onTrace: (event) => {
|
|
6190
7337
|
this.emit("trace", event);
|
|
6191
|
-
this.
|
|
7338
|
+
this.outputAxlEvent(event);
|
|
6192
7339
|
},
|
|
6193
7340
|
budgetContext: {
|
|
6194
7341
|
totalCost: 0,
|
|
@@ -6215,7 +7362,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6215
7362
|
);
|
|
6216
7363
|
}
|
|
6217
7364
|
const validated = workflow2.inputSchema.parse(input);
|
|
6218
|
-
const executionId = (0,
|
|
7365
|
+
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
6219
7366
|
const controller = new AbortController();
|
|
6220
7367
|
this.abortControllers.set(executionId, controller);
|
|
6221
7368
|
this.registerWithCostScope(executionId);
|
|
@@ -6223,7 +7370,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6223
7370
|
executionId,
|
|
6224
7371
|
workflow: name,
|
|
6225
7372
|
status: "running",
|
|
6226
|
-
|
|
7373
|
+
events: [],
|
|
6227
7374
|
totalCost: 0,
|
|
6228
7375
|
startedAt: Date.now(),
|
|
6229
7376
|
duration: 0
|
|
@@ -6239,20 +7386,35 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6239
7386
|
sessionHistory,
|
|
6240
7387
|
signal: controller.signal,
|
|
6241
7388
|
onTrace: (event) => {
|
|
6242
|
-
execInfo.
|
|
6243
|
-
|
|
7389
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7390
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
6244
7391
|
this.emit("trace", event);
|
|
6245
|
-
this.
|
|
6246
|
-
if (event.type === "
|
|
7392
|
+
this.outputAxlEvent(event);
|
|
7393
|
+
if (event.type === "handoff_start") {
|
|
6247
7394
|
const sessionId = options?.metadata?.sessionId;
|
|
6248
7395
|
if (sessionId) {
|
|
6249
|
-
const data = event.data;
|
|
6250
7396
|
this.appendHandoffRecord(sessionId, {
|
|
6251
|
-
source: event.
|
|
6252
|
-
target: data
|
|
6253
|
-
mode: data
|
|
7397
|
+
source: event.data.source,
|
|
7398
|
+
target: event.data.target,
|
|
7399
|
+
mode: event.data.mode,
|
|
6254
7400
|
timestamp: event.timestamp,
|
|
6255
|
-
|
|
7401
|
+
toAskId: event.toAskId
|
|
7402
|
+
});
|
|
7403
|
+
}
|
|
7404
|
+
} else if (event.type === "handoff_return") {
|
|
7405
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7406
|
+
if (sessionId) {
|
|
7407
|
+
this.updateHandoffDuration(sessionId, {
|
|
7408
|
+
toAskId: event.toAskId,
|
|
7409
|
+
duration: event.data.duration
|
|
7410
|
+
});
|
|
7411
|
+
}
|
|
7412
|
+
} else if (event.type === "ask_end") {
|
|
7413
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7414
|
+
if (sessionId && event.askId && typeof event.duration === "number") {
|
|
7415
|
+
this.updateHandoffDuration(sessionId, {
|
|
7416
|
+
toAskId: event.askId,
|
|
7417
|
+
duration: event.duration
|
|
6256
7418
|
});
|
|
6257
7419
|
}
|
|
6258
7420
|
}
|
|
@@ -6279,45 +7441,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6279
7441
|
"axl.execution.id": executionId,
|
|
6280
7442
|
"axl.workflow.input_hash": hashInput(validated)
|
|
6281
7443
|
},
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6290
|
-
execInfo.result = output;
|
|
6291
|
-
ctx._emitWorkflowEnd({
|
|
6292
|
-
status: "completed",
|
|
6293
|
-
duration: execInfo.duration,
|
|
6294
|
-
result: output
|
|
6295
|
-
});
|
|
6296
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
6297
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
6298
|
-
}
|
|
6299
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
6300
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
6301
|
-
this.persistExecution(execInfo);
|
|
6302
|
-
return output;
|
|
6303
|
-
} catch (err) {
|
|
6304
|
-
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
6305
|
-
execInfo.status = "failed";
|
|
6306
|
-
execInfo.completedAt = Date.now();
|
|
6307
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6308
|
-
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
6309
|
-
ctx._emitWorkflowEnd({
|
|
6310
|
-
status: "failed",
|
|
6311
|
-
duration: execInfo.duration,
|
|
6312
|
-
error: execInfo.error,
|
|
6313
|
-
...aborted ? { aborted: true } : {}
|
|
6314
|
-
});
|
|
6315
|
-
this.persistExecution(execInfo);
|
|
6316
|
-
throw err;
|
|
6317
|
-
} finally {
|
|
6318
|
-
this.abortControllers.delete(executionId);
|
|
6319
|
-
}
|
|
6320
|
-
}
|
|
7444
|
+
(span) => this.runWorkflowBody({
|
|
7445
|
+
workflow: workflow2,
|
|
7446
|
+
ctx,
|
|
7447
|
+
execInfo,
|
|
7448
|
+
validated,
|
|
7449
|
+
span
|
|
7450
|
+
})
|
|
6321
7451
|
);
|
|
6322
7452
|
}
|
|
6323
7453
|
/** Execute a workflow and return a stream. */
|
|
@@ -6325,21 +7455,20 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6325
7455
|
const axlStream = new AxlStream();
|
|
6326
7456
|
const controller = new AbortController();
|
|
6327
7457
|
axlStream.on("close", () => controller.abort());
|
|
7458
|
+
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
7459
|
+
this.abortControllers.set(executionId, controller);
|
|
6328
7460
|
let execInfo;
|
|
6329
|
-
let ctx;
|
|
6330
7461
|
const run = async () => {
|
|
6331
7462
|
const workflow2 = this.workflows.get(name);
|
|
6332
7463
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
6333
7464
|
const validated = workflow2.inputSchema.parse(input);
|
|
6334
|
-
const executionId = (0, import_node_crypto2.randomUUID)();
|
|
6335
|
-
this.abortControllers.set(executionId, controller);
|
|
6336
7465
|
const sessionHistory = options?.metadata?.sessionHistory ?? void 0;
|
|
6337
7466
|
this.registerWithCostScope(executionId);
|
|
6338
7467
|
execInfo = {
|
|
6339
7468
|
executionId,
|
|
6340
7469
|
workflow: name,
|
|
6341
7470
|
status: "running",
|
|
6342
|
-
|
|
7471
|
+
events: [],
|
|
6343
7472
|
totalCost: 0,
|
|
6344
7473
|
startedAt: Date.now(),
|
|
6345
7474
|
duration: 0
|
|
@@ -6353,69 +7482,46 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6353
7482
|
providerRegistry: this.providerRegistry,
|
|
6354
7483
|
sessionHistory,
|
|
6355
7484
|
signal: controller.signal,
|
|
7485
|
+
// Sentinel `onToken` so the streaming code path in WorkflowContext
|
|
7486
|
+
// is entered (it's gated on `this.onToken` being defined). The
|
|
7487
|
+
// actual `token` AxlEvents are emitted via `emitEvent` and reach
|
|
7488
|
+
// the wire through `onTrace` — this callback is just the gate.
|
|
7489
|
+
onToken: () => {
|
|
7490
|
+
},
|
|
6356
7491
|
onTrace: (event) => {
|
|
6357
|
-
execInfo.
|
|
6358
|
-
|
|
7492
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7493
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
6359
7494
|
this.emit("trace", event);
|
|
6360
|
-
this.
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
axlStream._push({
|
|
6364
|
-
type: "handoff",
|
|
6365
|
-
source: event.agent ?? "",
|
|
6366
|
-
target: data?.target ?? "",
|
|
6367
|
-
mode: data?.mode ?? void 0
|
|
6368
|
-
});
|
|
7495
|
+
this.outputAxlEvent(event);
|
|
7496
|
+
axlStream._push(event);
|
|
7497
|
+
if (event.type === "handoff_start") {
|
|
6369
7498
|
const sessionId = options?.metadata?.sessionId;
|
|
6370
7499
|
if (sessionId) {
|
|
6371
7500
|
this.appendHandoffRecord(sessionId, {
|
|
6372
|
-
source: event.
|
|
6373
|
-
target: data
|
|
6374
|
-
mode: data
|
|
7501
|
+
source: event.data.source,
|
|
7502
|
+
target: event.data.target,
|
|
7503
|
+
mode: event.data.mode,
|
|
6375
7504
|
timestamp: event.timestamp,
|
|
6376
|
-
|
|
7505
|
+
toAskId: event.toAskId
|
|
7506
|
+
});
|
|
7507
|
+
}
|
|
7508
|
+
} else if (event.type === "handoff_return") {
|
|
7509
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7510
|
+
if (sessionId) {
|
|
7511
|
+
this.updateHandoffDuration(sessionId, {
|
|
7512
|
+
toAskId: event.toAskId,
|
|
7513
|
+
duration: event.data.duration
|
|
7514
|
+
});
|
|
7515
|
+
}
|
|
7516
|
+
} else if (event.type === "ask_end") {
|
|
7517
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7518
|
+
if (sessionId && event.askId && typeof event.duration === "number") {
|
|
7519
|
+
this.updateHandoffDuration(sessionId, {
|
|
7520
|
+
toAskId: event.askId,
|
|
7521
|
+
duration: event.duration
|
|
6377
7522
|
});
|
|
6378
7523
|
}
|
|
6379
|
-
} else if (event.type === "tool_approval") {
|
|
6380
|
-
const data = event.data;
|
|
6381
|
-
axlStream._push({
|
|
6382
|
-
type: "tool_approval",
|
|
6383
|
-
name: event.tool ?? "",
|
|
6384
|
-
args: data?.args,
|
|
6385
|
-
approved: data?.approved === true,
|
|
6386
|
-
...data?.reason ? { reason: data.reason } : {}
|
|
6387
|
-
});
|
|
6388
|
-
} else if (event.type === "tool_denied") {
|
|
6389
|
-
} else if (event.type === "agent_call") {
|
|
6390
|
-
axlStream._push({
|
|
6391
|
-
type: "agent_end",
|
|
6392
|
-
agent: event.agent ?? "",
|
|
6393
|
-
cost: event.cost,
|
|
6394
|
-
duration: event.duration
|
|
6395
|
-
});
|
|
6396
|
-
} else if (event.type === "tool_call") {
|
|
6397
|
-
axlStream._push({
|
|
6398
|
-
type: "tool_result",
|
|
6399
|
-
name: event.tool ?? "",
|
|
6400
|
-
result: event.data?.result,
|
|
6401
|
-
callId: event.data?.callId
|
|
6402
|
-
});
|
|
6403
7524
|
}
|
|
6404
|
-
axlStream._push({ type: "step", step: event.step, data: event });
|
|
6405
|
-
},
|
|
6406
|
-
onToken: (token) => {
|
|
6407
|
-
axlStream._push({ type: "token", data: token });
|
|
6408
|
-
},
|
|
6409
|
-
onToolCall: (call) => {
|
|
6410
|
-
axlStream._push({
|
|
6411
|
-
type: "tool_call",
|
|
6412
|
-
name: call.name,
|
|
6413
|
-
args: call.args,
|
|
6414
|
-
callId: call.callId
|
|
6415
|
-
});
|
|
6416
|
-
},
|
|
6417
|
-
onAgentStart: (info) => {
|
|
6418
|
-
axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
|
|
6419
7525
|
},
|
|
6420
7526
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
6421
7527
|
awaitHumanHandler: options?.awaitHumanHandler,
|
|
@@ -6432,7 +7538,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6432
7538
|
policy: "finish_and_stop"
|
|
6433
7539
|
}
|
|
6434
7540
|
});
|
|
6435
|
-
ctx = wfCtx;
|
|
6436
7541
|
return this.spanManager.withSpanAsync(
|
|
6437
7542
|
"axl.workflow.execute",
|
|
6438
7543
|
{
|
|
@@ -6440,49 +7545,25 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6440
7545
|
"axl.execution.id": executionId,
|
|
6441
7546
|
"axl.workflow.input_hash": hashInput(validated)
|
|
6442
7547
|
},
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6451
|
-
execInfo.result = result;
|
|
6452
|
-
wfCtx._emitWorkflowEnd({
|
|
6453
|
-
status: "completed",
|
|
6454
|
-
duration: execInfo.duration,
|
|
6455
|
-
result
|
|
6456
|
-
});
|
|
6457
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
6458
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
6459
|
-
}
|
|
6460
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
6461
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
6462
|
-
this.persistExecution(execInfo);
|
|
6463
|
-
return result;
|
|
6464
|
-
} finally {
|
|
6465
|
-
this.abortControllers.delete(executionId);
|
|
6466
|
-
}
|
|
6467
|
-
}
|
|
7548
|
+
(span) => this.runWorkflowBody({
|
|
7549
|
+
workflow: workflow2,
|
|
7550
|
+
ctx: wfCtx,
|
|
7551
|
+
execInfo,
|
|
7552
|
+
validated,
|
|
7553
|
+
span
|
|
7554
|
+
})
|
|
6468
7555
|
);
|
|
6469
7556
|
};
|
|
6470
|
-
run().then((result) => axlStream._done(result)).catch((err) => {
|
|
6471
|
-
if (execInfo) {
|
|
6472
|
-
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
7557
|
+
run().then((result) => axlStream._done(result, executionId)).catch((err) => {
|
|
7558
|
+
if (execInfo && execInfo.status === "running") {
|
|
6473
7559
|
execInfo.status = "failed";
|
|
6474
7560
|
execInfo.completedAt = Date.now();
|
|
6475
7561
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6476
7562
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
6477
|
-
ctx?._emitWorkflowEnd({
|
|
6478
|
-
status: "failed",
|
|
6479
|
-
duration: execInfo.duration,
|
|
6480
|
-
error: execInfo.error,
|
|
6481
|
-
...aborted ? { aborted: true } : {}
|
|
6482
|
-
});
|
|
6483
7563
|
this.persistExecution(execInfo);
|
|
6484
7564
|
}
|
|
6485
|
-
|
|
7565
|
+
this.abortControllers.delete(executionId);
|
|
7566
|
+
axlStream._error(err instanceof Error ? err : new Error(String(err)), executionId);
|
|
6486
7567
|
});
|
|
6487
7568
|
return axlStream;
|
|
6488
7569
|
}
|
|
@@ -6742,11 +7823,11 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6742
7823
|
* need embedder token counts, subscribe to `runtime.on('trace', ...)` and
|
|
6743
7824
|
* read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
|
|
6744
7825
|
*
|
|
6745
|
-
* Pass `{ captureTraces: true }` to also collect the raw `
|
|
7826
|
+
* Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
|
|
6746
7827
|
* during `fn()`. This is opt-in because it keeps every event in memory for the
|
|
6747
7828
|
* duration of the call — useful for eval per-item capture, debugging, and test
|
|
6748
7829
|
* assertions, but overhead grows with trace volume. When enabled, verbose-mode
|
|
6749
|
-
* `
|
|
7830
|
+
* `agent_call_start.data.messages` snapshots are omitted from captured events (still
|
|
6750
7831
|
* broadcast via onTrace) to keep memory bounded — callers who need the full
|
|
6751
7832
|
* verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
|
|
6752
7833
|
*
|
|
@@ -6766,8 +7847,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6766
7847
|
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
6767
7848
|
const listener = (event) => {
|
|
6768
7849
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
6769
|
-
|
|
6770
|
-
if (event.type === "
|
|
7850
|
+
scope.totalCost += eventCostContribution(event);
|
|
7851
|
+
if (event.type === "agent_call_end") {
|
|
6771
7852
|
if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
|
|
6772
7853
|
agentCalls++;
|
|
6773
7854
|
if (event.tokens) {
|
|
@@ -6780,7 +7861,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6780
7861
|
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
6781
7862
|
}
|
|
6782
7863
|
if (capturedTraces) {
|
|
6783
|
-
if (event.type === "
|
|
7864
|
+
if (event.type === "token" || event.type === "partial_object") return;
|
|
7865
|
+
if (event.type === "agent_call_start" && event.data) {
|
|
6784
7866
|
const d = event.data;
|
|
6785
7867
|
if ("messages" in d) {
|
|
6786
7868
|
const rest = {};
|
|
@@ -6845,7 +7927,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6845
7927
|
* The emit('trace', event) call happens before this method is called, so
|
|
6846
7928
|
* programmatic subscribers always receive events regardless of trace config.
|
|
6847
7929
|
*/
|
|
6848
|
-
|
|
7930
|
+
outputAxlEvent(event) {
|
|
6849
7931
|
const traceConfig = this.config.trace;
|
|
6850
7932
|
if (!traceConfig?.enabled) return;
|
|
6851
7933
|
const level = traceConfig.level ?? "steps";
|
|
@@ -6869,9 +7951,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6869
7951
|
}
|
|
6870
7952
|
return;
|
|
6871
7953
|
}
|
|
6872
|
-
this.
|
|
7954
|
+
this.logAxlEvent(event);
|
|
6873
7955
|
}
|
|
6874
|
-
|
|
7956
|
+
logAxlEvent(event) {
|
|
6875
7957
|
const level = this.config.trace?.level ?? "steps";
|
|
6876
7958
|
const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
|
|
6877
7959
|
const parts = [`[axl] execution:${event.executionId}`];
|
|
@@ -6882,7 +7964,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6882
7964
|
const status = d?.aborted ? "aborted" : d?.status ?? "completed";
|
|
6883
7965
|
parts.push(`${workflowPrefix}${status}`);
|
|
6884
7966
|
if (event.duration != null) parts.push(`${(event.duration / 1e3).toFixed(1)}s`);
|
|
6885
|
-
} else if (event.type === "
|
|
7967
|
+
} else if (event.type === "agent_call_end") {
|
|
6886
7968
|
parts.push(`${workflowPrefix}step:${event.step} agent_call`);
|
|
6887
7969
|
if (event.agent) parts.push(`agent:${event.agent}`);
|
|
6888
7970
|
if (event.promptVersion) parts.push(`version:${event.promptVersion}`);
|
|
@@ -6892,7 +7974,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6892
7974
|
if (level === "full" && event.data) {
|
|
6893
7975
|
parts.push(`data:${JSON.stringify(event.data)}`);
|
|
6894
7976
|
}
|
|
6895
|
-
} else if (event.type === "
|
|
7977
|
+
} else if (event.type === "tool_call_end") {
|
|
6896
7978
|
parts.push(`${workflowPrefix}step:${event.step} tool_call`);
|
|
6897
7979
|
if (event.tool) parts.push(`tool:${event.tool}`);
|
|
6898
7980
|
if (event.duration) parts.push(`${event.duration}ms`);
|
|
@@ -6909,8 +7991,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6909
7991
|
parts.push(`${workflowPrefix}log: ${JSON.stringify(event.data)}`);
|
|
6910
7992
|
} else {
|
|
6911
7993
|
parts.push(`${workflowPrefix}${event.type}`);
|
|
6912
|
-
|
|
6913
|
-
|
|
7994
|
+
const data = event.data;
|
|
7995
|
+
if (level === "full" && data !== void 0) {
|
|
7996
|
+
parts.push(`data:${JSON.stringify(data)}`);
|
|
6914
7997
|
}
|
|
6915
7998
|
}
|
|
6916
7999
|
console.log(parts.join(" | "));
|
|
@@ -6930,6 +8013,35 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6930
8013
|
}).catch(() => {
|
|
6931
8014
|
});
|
|
6932
8015
|
}
|
|
8016
|
+
/**
|
|
8017
|
+
* Patch `duration` on the handoff record matching `toAskId` (the target
|
|
8018
|
+
* frame's askId, which the runtime stamps on `handoff_start` and which
|
|
8019
|
+
* appears as `askId` on the target's `ask_end`).
|
|
8020
|
+
*
|
|
8021
|
+
* Called from two places per handoff:
|
|
8022
|
+
* - target's `ask_end` — first-write source for ONEWAY (no return event).
|
|
8023
|
+
* - `handoff_return` — overwrites for ROUNDTRIP with the round-trip
|
|
8024
|
+
* wall-clock figure (intentional: round-trip
|
|
8025
|
+
* duration is the more useful metric for the
|
|
8026
|
+
* source-paused-for-N-ms semantic).
|
|
8027
|
+
*
|
|
8028
|
+
* No-op if no record matches `toAskId` (e.g., stream subscribed mid-
|
|
8029
|
+
* handoff, or session metadata got wiped) — `duration` stays undefined,
|
|
8030
|
+
* which is the documented default for `HandoffRecord`.
|
|
8031
|
+
*/
|
|
8032
|
+
updateHandoffDuration(sessionId, match) {
|
|
8033
|
+
this.stateStore.getSessionMeta(sessionId, "handoffHistory").then((existing) => {
|
|
8034
|
+
const history = existing ?? [];
|
|
8035
|
+
for (const r of history) {
|
|
8036
|
+
if (r.toAskId === match.toAskId) {
|
|
8037
|
+
r.duration = match.duration;
|
|
8038
|
+
return this.stateStore.saveSessionMeta(sessionId, "handoffHistory", history);
|
|
8039
|
+
}
|
|
8040
|
+
}
|
|
8041
|
+
return void 0;
|
|
8042
|
+
}).catch(() => {
|
|
8043
|
+
});
|
|
8044
|
+
}
|
|
6933
8045
|
};
|
|
6934
8046
|
|
|
6935
8047
|
// src/state/redis.ts
|
|
@@ -6992,27 +8104,13 @@ var RedisStore = class _RedisStore {
|
|
|
6992
8104
|
return "axl:eval-history-ids";
|
|
6993
8105
|
}
|
|
6994
8106
|
// ── Checkpoints ──────────────────────────────────────────────────────
|
|
6995
|
-
async saveCheckpoint(executionId,
|
|
6996
|
-
await this.client.hSet(this.checkpointKey(executionId),
|
|
8107
|
+
async saveCheckpoint(executionId, name, data) {
|
|
8108
|
+
await this.client.hSet(this.checkpointKey(executionId), name, JSON.stringify(data));
|
|
6997
8109
|
}
|
|
6998
|
-
async getCheckpoint(executionId,
|
|
6999
|
-
const raw = await this.client.hGet(this.checkpointKey(executionId),
|
|
8110
|
+
async getCheckpoint(executionId, name) {
|
|
8111
|
+
const raw = await this.client.hGet(this.checkpointKey(executionId), name);
|
|
7000
8112
|
return raw != null ? JSON.parse(raw) : null;
|
|
7001
8113
|
}
|
|
7002
|
-
async getLatestCheckpoint(executionId) {
|
|
7003
|
-
const all = await this.client.hGetAll(this.checkpointKey(executionId));
|
|
7004
|
-
if (!all || Object.keys(all).length === 0) return null;
|
|
7005
|
-
let maxStep = -1;
|
|
7006
|
-
let maxData = null;
|
|
7007
|
-
for (const [stepStr, raw] of Object.entries(all)) {
|
|
7008
|
-
const step = Number(stepStr);
|
|
7009
|
-
if (step > maxStep) {
|
|
7010
|
-
maxStep = step;
|
|
7011
|
-
maxData = JSON.parse(raw);
|
|
7012
|
-
}
|
|
7013
|
-
}
|
|
7014
|
-
return { step: maxStep, data: maxData };
|
|
7015
|
-
}
|
|
7016
8114
|
// ── Sessions ─────────────────────────────────────────────────────────
|
|
7017
8115
|
async saveSession(sessionId, history) {
|
|
7018
8116
|
await this.client.set(this.sessionKey(sessionId), JSON.stringify(history));
|
|
@@ -7298,11 +8396,13 @@ function cosineSimilarity2(a, b) {
|
|
|
7298
8396
|
}
|
|
7299
8397
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7300
8398
|
0 && (module.exports = {
|
|
8399
|
+
AXL_EVENT_TYPES,
|
|
7301
8400
|
AnthropicProvider,
|
|
7302
8401
|
AxlError,
|
|
7303
8402
|
AxlRuntime,
|
|
7304
8403
|
AxlStream,
|
|
7305
8404
|
BudgetExceededError,
|
|
8405
|
+
COST_BEARING_LEAF_TYPES,
|
|
7306
8406
|
GeminiProvider,
|
|
7307
8407
|
GuardrailError,
|
|
7308
8408
|
InMemoryVectorStore,
|
|
@@ -7317,6 +8417,8 @@ function cosineSimilarity2(a, b) {
|
|
|
7317
8417
|
OpenAIResponsesProvider,
|
|
7318
8418
|
ProviderRegistry,
|
|
7319
8419
|
QuorumNotMet,
|
|
8420
|
+
REDACTED,
|
|
8421
|
+
REDACTION_RULES,
|
|
7320
8422
|
RedisStore,
|
|
7321
8423
|
SQLiteStore,
|
|
7322
8424
|
Session,
|
|
@@ -7329,7 +8431,12 @@ function cosineSimilarity2(a, b) {
|
|
|
7329
8431
|
agent,
|
|
7330
8432
|
createSpanManager,
|
|
7331
8433
|
defineConfig,
|
|
8434
|
+
eventCostContribution,
|
|
7332
8435
|
extractJson,
|
|
8436
|
+
isCostBearingLeaf,
|
|
8437
|
+
isRootLevel,
|
|
8438
|
+
parsePartialJson,
|
|
8439
|
+
redactEvent,
|
|
7333
8440
|
resolveThinkingOptions,
|
|
7334
8441
|
tool,
|
|
7335
8442
|
workflow,
|