@axlsdk/axl 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/dist/index.cjs +2257 -569
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1191 -114
- package/dist/index.d.ts +1191 -114
- package/dist/index.js +2248 -569
- 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
|
|
@@ -2112,6 +2159,21 @@ var TimeoutError = class extends AxlError {
|
|
|
2112
2159
|
this.name = "TimeoutError";
|
|
2113
2160
|
}
|
|
2114
2161
|
};
|
|
2162
|
+
function formatBudgetCost(cost) {
|
|
2163
|
+
if (Number.isNaN(cost)) return "$NaN";
|
|
2164
|
+
if (cost === Infinity) return "$Infinity";
|
|
2165
|
+
if (cost === -Infinity) return "-$Infinity";
|
|
2166
|
+
if (cost === 0) return "$0.00";
|
|
2167
|
+
const sign = cost < 0 ? "-" : "";
|
|
2168
|
+
const abs = Math.abs(cost);
|
|
2169
|
+
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
2170
|
+
if (abs < 1e-4) {
|
|
2171
|
+
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
2172
|
+
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
2173
|
+
}
|
|
2174
|
+
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
2175
|
+
return `${sign}$${abs.toFixed(2)}`;
|
|
2176
|
+
}
|
|
2115
2177
|
var BudgetExceededError = class extends AxlError {
|
|
2116
2178
|
limit;
|
|
2117
2179
|
spent;
|
|
@@ -2119,7 +2181,9 @@ var BudgetExceededError = class extends AxlError {
|
|
|
2119
2181
|
constructor(limit, spent, policy) {
|
|
2120
2182
|
super(
|
|
2121
2183
|
"BUDGET_EXCEEDED",
|
|
2122
|
-
`Budget exceeded: spent
|
|
2184
|
+
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
2185
|
+
limit
|
|
2186
|
+
)} limit (policy: ${policy})`
|
|
2123
2187
|
);
|
|
2124
2188
|
this.name = "BudgetExceededError";
|
|
2125
2189
|
this.limit = limit;
|
|
@@ -2171,6 +2235,432 @@ var ToolDenied = class extends AxlError {
|
|
|
2171
2235
|
}
|
|
2172
2236
|
};
|
|
2173
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
|
+
|
|
2174
2664
|
// src/config.ts
|
|
2175
2665
|
function defineConfig(config) {
|
|
2176
2666
|
return config;
|
|
@@ -2248,6 +2738,7 @@ function resolveConfig(config) {
|
|
|
2248
2738
|
|
|
2249
2739
|
// src/context.ts
|
|
2250
2740
|
var signalStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
2741
|
+
var askStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
2251
2742
|
function zodToJsonSchema(schema) {
|
|
2252
2743
|
const result = import_zod.z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
2253
2744
|
delete result.$schema;
|
|
@@ -2307,6 +2798,14 @@ function extractBalanced(str, start, openChar, closeChar) {
|
|
|
2307
2798
|
}
|
|
2308
2799
|
return null;
|
|
2309
2800
|
}
|
|
2801
|
+
function appendRetryMessages(messages, content, feedbackMessage, providerMetadata) {
|
|
2802
|
+
messages.push({
|
|
2803
|
+
role: "assistant",
|
|
2804
|
+
content,
|
|
2805
|
+
...providerMetadata ? { providerMetadata } : {}
|
|
2806
|
+
});
|
|
2807
|
+
messages.push({ role: "system", content: feedbackMessage });
|
|
2808
|
+
}
|
|
2310
2809
|
function estimateMessagesTokens(messages) {
|
|
2311
2810
|
let total = 0;
|
|
2312
2811
|
for (const msg of messages) {
|
|
@@ -2341,8 +2840,34 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2341
2840
|
pendingDecisions;
|
|
2342
2841
|
budgetContext;
|
|
2343
2842
|
stateStore;
|
|
2344
|
-
|
|
2345
|
-
|
|
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;
|
|
2346
2871
|
signal;
|
|
2347
2872
|
summaryCache;
|
|
2348
2873
|
workflowName;
|
|
@@ -2377,6 +2902,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2377
2902
|
this.awaitHumanHandler = init.awaitHumanHandler;
|
|
2378
2903
|
this.onAgentStart = init.onAgentStart;
|
|
2379
2904
|
this.onAgentCallComplete = init.onAgentCallComplete;
|
|
2905
|
+
this.autoCheckpointCounters = init.autoCheckpointCounters ?? {
|
|
2906
|
+
byAgent: /* @__PURE__ */ new Map(),
|
|
2907
|
+
root: 0
|
|
2908
|
+
};
|
|
2380
2909
|
if (init.metadata?.summaryCache) {
|
|
2381
2910
|
this.summaryCache = init.metadata.summaryCache;
|
|
2382
2911
|
}
|
|
@@ -2385,8 +2914,21 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2385
2914
|
* Create a child context for nested agent invocations (e.g., agent-as-tool).
|
|
2386
2915
|
* Shares: budget tracking, abort signals, trace emission, provider registry,
|
|
2387
2916
|
* state store, span manager, memory manager, MCP manager, config,
|
|
2388
|
-
* awaitHuman handler, pending decisions, tool overrides
|
|
2389
|
-
*
|
|
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.
|
|
2926
|
+
*
|
|
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.
|
|
2390
2932
|
*/
|
|
2391
2933
|
createChildContext() {
|
|
2392
2934
|
return new _WorkflowContext({
|
|
@@ -2402,14 +2944,20 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2402
2944
|
spanManager: this.spanManager,
|
|
2403
2945
|
memoryManager: this.memoryManager,
|
|
2404
2946
|
onTrace: this.onTrace,
|
|
2947
|
+
onToken: this.onToken,
|
|
2948
|
+
onToolCall: this.onToolCall,
|
|
2949
|
+
onAgentStart: this.onAgentStart,
|
|
2405
2950
|
onAgentCallComplete: this.onAgentCallComplete,
|
|
2406
2951
|
awaitHumanHandler: this.awaitHumanHandler,
|
|
2407
2952
|
pendingDecisions: this.pendingDecisions,
|
|
2408
2953
|
toolOverrides: this.toolOverrides,
|
|
2409
2954
|
signal: this.signal,
|
|
2410
|
-
workflowName: this.workflowName
|
|
2411
|
-
//
|
|
2412
|
-
//
|
|
2955
|
+
workflowName: this.workflowName,
|
|
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)
|
|
2413
2961
|
});
|
|
2414
2962
|
}
|
|
2415
2963
|
/**
|
|
@@ -2420,72 +2968,150 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2420
2968
|
get currentSignal() {
|
|
2421
2969
|
return signalStorage.getStore() ?? this.signal;
|
|
2422
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
|
+
}
|
|
2423
2994
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
2424
2995
|
async ask(agent2, prompt, options) {
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
const
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
"axl.agent.completion_tokens",
|
|
2455
|
-
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
|
|
2456
3025
|
);
|
|
2457
|
-
|
|
2458
|
-
|
|
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
|
+
});
|
|
2459
3104
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
this.onAgentCallComplete?.({
|
|
2465
|
-
agent: agent2._name,
|
|
2466
|
-
prompt,
|
|
2467
|
-
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
2468
|
-
model: agent2.resolveModel(resolveCtx),
|
|
2469
|
-
cost: costAfter - costBefore,
|
|
2470
|
-
duration: Date.now() - startTime,
|
|
2471
|
-
promptVersion: agent2._config.version,
|
|
2472
|
-
temperature: options?.temperature ?? agent2._config.temperature,
|
|
2473
|
-
maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
|
|
2474
|
-
effort: options?.effort ?? agent2._config.effort,
|
|
2475
|
-
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
2476
|
-
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
2477
|
-
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
2478
|
-
stop: options?.stop ?? agent2._config.stop,
|
|
2479
|
-
providerOptions: options?.providerOptions ?? agent2._config.providerOptions
|
|
2480
|
-
});
|
|
2481
|
-
return result;
|
|
2482
|
-
});
|
|
3105
|
+
});
|
|
3106
|
+
},
|
|
3107
|
+
{ agent: agentName }
|
|
3108
|
+
);
|
|
2483
3109
|
}
|
|
2484
3110
|
async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
|
|
2485
3111
|
if (this.budgetContext?.exceeded) {
|
|
2486
3112
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
2487
3113
|
if (policy === "warn") {
|
|
2488
|
-
this.
|
|
3114
|
+
this.emitEvent({
|
|
2489
3115
|
type: "log",
|
|
2490
3116
|
data: { warning: "Budget exceeded", limit, spent, policy }
|
|
2491
3117
|
});
|
|
@@ -2574,14 +3200,25 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2574
3200
|
const guardrails = agent2._config.guardrails;
|
|
2575
3201
|
if (guardrails?.input) {
|
|
2576
3202
|
const inputResult = await guardrails.input(prompt, { metadata: this.metadata });
|
|
2577
|
-
this.
|
|
3203
|
+
this.emitEvent({
|
|
2578
3204
|
type: "guardrail",
|
|
2579
3205
|
agent: agent2._name,
|
|
2580
|
-
data: {
|
|
3206
|
+
data: {
|
|
3207
|
+
guardrailType: "input",
|
|
3208
|
+
blocked: inputResult.block,
|
|
3209
|
+
...inputResult.reason ? { reason: inputResult.reason } : {},
|
|
3210
|
+
// Input guardrails can't retry (prompt is user-supplied), so attempt
|
|
3211
|
+
// and maxAttempts are always 1 — emit them for shape consistency with
|
|
3212
|
+
// output guardrails so consumers don't need two narrowers.
|
|
3213
|
+
attempt: 1,
|
|
3214
|
+
maxAttempts: 1
|
|
3215
|
+
}
|
|
2581
3216
|
});
|
|
2582
3217
|
this.spanManager?.addEventToActiveSpan("axl.guardrail.check", {
|
|
2583
3218
|
"axl.guardrail.type": "input",
|
|
2584
3219
|
"axl.guardrail.blocked": inputResult.block,
|
|
3220
|
+
"axl.guardrail.attempt": 1,
|
|
3221
|
+
"axl.guardrail.maxAttempts": 1,
|
|
2585
3222
|
...inputResult.reason ? { "axl.guardrail.reason": inputResult.reason } : {}
|
|
2586
3223
|
});
|
|
2587
3224
|
if (inputResult.block) {
|
|
@@ -2597,22 +3234,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2597
3234
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
2598
3235
|
const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
|
|
2599
3236
|
const startTime = Date.now();
|
|
2600
|
-
if (this.onToken && options?.validate) {
|
|
2601
|
-
throw new AxlError(
|
|
2602
|
-
"INVALID_CONFIG",
|
|
2603
|
-
"Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
|
|
2604
|
-
);
|
|
2605
|
-
}
|
|
2606
3237
|
const currentMessages = [...messages];
|
|
2607
3238
|
let turns = 0;
|
|
2608
3239
|
let guardrailOutputRetries = 0;
|
|
2609
3240
|
let schemaRetries = 0;
|
|
2610
3241
|
let validateRetries = 0;
|
|
3242
|
+
const maxGuardrailRetries = guardrails?.maxRetries ?? 2;
|
|
3243
|
+
let pendingRetryReason;
|
|
3244
|
+
const verboseTrace = this.config.trace?.level === "full";
|
|
3245
|
+
let lastStartStage = "initial";
|
|
3246
|
+
let lastStartAttempt = 1;
|
|
3247
|
+
let lastStartMaxAttempts = 1;
|
|
2611
3248
|
while (turns < maxTurns) {
|
|
2612
3249
|
if (Date.now() - startTime > timeoutMs) {
|
|
2613
3250
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
2614
3251
|
}
|
|
2615
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
|
+
}
|
|
3281
|
+
const turnStart = Date.now();
|
|
2616
3282
|
const chatOptions = {
|
|
2617
3283
|
model,
|
|
2618
3284
|
temperature: options?.temperature ?? agent2._config.temperature,
|
|
@@ -2629,82 +3295,172 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2629
3295
|
if (options?.schema && toolDefs.length === 0) {
|
|
2630
3296
|
chatOptions.responseFormat = { type: "json_object" };
|
|
2631
3297
|
}
|
|
2632
|
-
this.
|
|
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
|
+
};
|
|
3308
|
+
let messagesSnapshot;
|
|
3309
|
+
if (verboseTrace) {
|
|
3310
|
+
try {
|
|
3311
|
+
messagesSnapshot = structuredClone(currentMessages);
|
|
3312
|
+
} catch (err) {
|
|
3313
|
+
console.warn(
|
|
3314
|
+
"[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
|
|
3315
|
+
err instanceof Error ? err.message : String(err)
|
|
3316
|
+
);
|
|
3317
|
+
messagesSnapshot = [...currentMessages];
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
const retryReason = pendingRetryReason;
|
|
3321
|
+
pendingRetryReason = void 0;
|
|
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);
|
|
2633
3338
|
let response;
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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
|
+
}
|
|
2663
3412
|
}
|
|
2664
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);
|
|
2665
3439
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
};
|
|
2680
|
-
|
|
2681
|
-
response.tool_calls = toolCalls;
|
|
2682
|
-
}
|
|
2683
|
-
if (streamProviderMetadata) {
|
|
2684
|
-
response.providerMetadata = streamProviderMetadata;
|
|
2685
|
-
}
|
|
2686
|
-
if (thinkingContent) {
|
|
2687
|
-
response.thinking_content = thinkingContent;
|
|
2688
|
-
}
|
|
2689
|
-
} else {
|
|
2690
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
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;
|
|
2691
3455
|
}
|
|
2692
3456
|
if (usageCapture && response.usage) {
|
|
2693
3457
|
usageCapture.value = response.usage;
|
|
2694
3458
|
}
|
|
2695
3459
|
if (response.cost) {
|
|
2696
|
-
|
|
2697
|
-
this.budgetContext.totalCost += response.cost;
|
|
2698
|
-
if (this.budgetContext.totalCost >= this.budgetContext.limit) {
|
|
2699
|
-
this.budgetContext.exceeded = true;
|
|
2700
|
-
if (this.budgetContext.policy === "hard_stop" && this.budgetContext.abortController) {
|
|
2701
|
-
this.budgetContext.abortController.abort();
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
3460
|
+
this._accumulateBudgetCost(response.cost);
|
|
2705
3461
|
}
|
|
2706
|
-
this.
|
|
2707
|
-
type: "
|
|
3462
|
+
this.emitEvent({
|
|
3463
|
+
type: "agent_call_end",
|
|
2708
3464
|
agent: agent2._name,
|
|
2709
3465
|
model: modelUri,
|
|
2710
3466
|
promptVersion: agent2._config.version,
|
|
@@ -2714,8 +3470,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2714
3470
|
output: response.usage.completion_tokens,
|
|
2715
3471
|
reasoning: response.usage.reasoning_tokens
|
|
2716
3472
|
} : void 0,
|
|
2717
|
-
duration: Date.now() -
|
|
2718
|
-
data: {
|
|
3473
|
+
duration: Date.now() - turnStart,
|
|
3474
|
+
data: {
|
|
3475
|
+
response: response.content,
|
|
3476
|
+
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
3477
|
+
turn: turns,
|
|
3478
|
+
...retryReason ? { retryReason } : {}
|
|
3479
|
+
}
|
|
2719
3480
|
});
|
|
2720
3481
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2721
3482
|
currentMessages.push({
|
|
@@ -2740,6 +3501,19 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2740
3501
|
}
|
|
2741
3502
|
}
|
|
2742
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
|
+
};
|
|
2743
3517
|
const handoffOptions = options ? {
|
|
2744
3518
|
schema: options.schema,
|
|
2745
3519
|
retries: options.retries,
|
|
@@ -2747,13 +3521,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2747
3521
|
validate: options.validate,
|
|
2748
3522
|
validateRetries: options.validateRetries
|
|
2749
3523
|
} : void 0;
|
|
2750
|
-
const handoffFn = () =>
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
+
});
|
|
2757
3569
|
if (mode === "roundtrip") {
|
|
2758
3570
|
const executeRoundtrip = async () => {
|
|
2759
3571
|
const result = await handoffFn();
|
|
@@ -2774,24 +3586,41 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2774
3586
|
"axl.handoff.mode": mode
|
|
2775
3587
|
},
|
|
2776
3588
|
async (span) => {
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
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
|
+
}
|
|
2786
3604
|
}
|
|
2787
3605
|
);
|
|
2788
3606
|
} else {
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
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
|
+
}
|
|
2795
3624
|
}
|
|
2796
3625
|
continue;
|
|
2797
3626
|
}
|
|
@@ -2805,24 +3634,12 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2805
3634
|
},
|
|
2806
3635
|
async (span) => {
|
|
2807
3636
|
const result = await handoffFn();
|
|
2808
|
-
|
|
2809
|
-
span.setAttribute("axl.handoff.duration", duration);
|
|
2810
|
-
this.emitTrace({
|
|
2811
|
-
type: "handoff",
|
|
2812
|
-
agent: agent2._name,
|
|
2813
|
-
data: { target: targetName, mode, duration }
|
|
2814
|
-
});
|
|
3637
|
+
span.setAttribute("axl.handoff.duration", Date.now() - handoffStart);
|
|
2815
3638
|
return result;
|
|
2816
3639
|
}
|
|
2817
3640
|
);
|
|
2818
3641
|
}
|
|
2819
|
-
|
|
2820
|
-
this.emitTrace({
|
|
2821
|
-
type: "handoff",
|
|
2822
|
-
agent: agent2._name,
|
|
2823
|
-
data: { target: targetName, mode, duration: Date.now() - handoffStart }
|
|
2824
|
-
});
|
|
2825
|
-
return onewayResult;
|
|
3642
|
+
return await handoffFn();
|
|
2826
3643
|
}
|
|
2827
3644
|
}
|
|
2828
3645
|
const toolOverride = this.toolOverrides?.get(toolName);
|
|
@@ -2838,7 +3655,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2838
3655
|
});
|
|
2839
3656
|
continue;
|
|
2840
3657
|
}
|
|
2841
|
-
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
|
+
);
|
|
2842
3668
|
const toolStart2 = Date.now();
|
|
2843
3669
|
const executeOverride = async () => {
|
|
2844
3670
|
let toolResult3;
|
|
@@ -2866,8 +3692,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2866
3692
|
}
|
|
2867
3693
|
) : await executeOverride();
|
|
2868
3694
|
const resultContent2 = JSON.stringify(toolResult2);
|
|
2869
|
-
this.
|
|
2870
|
-
type: "
|
|
3695
|
+
this.emitEvent({
|
|
3696
|
+
type: "tool_call_end",
|
|
2871
3697
|
agent: agent2._name,
|
|
2872
3698
|
tool: toolName,
|
|
2873
3699
|
duration: Date.now() - toolStart2,
|
|
@@ -2883,7 +3709,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2883
3709
|
const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
|
|
2884
3710
|
const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
|
|
2885
3711
|
if (!tool2 && !isMcpTool) {
|
|
2886
|
-
this.
|
|
3712
|
+
this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
|
|
2887
3713
|
currentMessages.push({
|
|
2888
3714
|
role: "tool",
|
|
2889
3715
|
content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
|
|
@@ -2902,7 +3728,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2902
3728
|
});
|
|
2903
3729
|
continue;
|
|
2904
3730
|
}
|
|
2905
|
-
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);
|
|
2906
3738
|
const toolStart = Date.now();
|
|
2907
3739
|
if (tool2 && tool2.requireApproval) {
|
|
2908
3740
|
const approvalFn = async () => {
|
|
@@ -2913,24 +3745,18 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2913
3745
|
});
|
|
2914
3746
|
if (!decision.approved) {
|
|
2915
3747
|
const reason = decision.reason ?? "Denied by human";
|
|
2916
|
-
this.emitTrace({
|
|
2917
|
-
type: "tool_denied",
|
|
2918
|
-
agent: agent2._name,
|
|
2919
|
-
tool: toolName,
|
|
2920
|
-
data: { denied: true, reason, args: toolArgs }
|
|
2921
|
-
});
|
|
2922
3748
|
currentMessages.push({
|
|
2923
3749
|
role: "tool",
|
|
2924
3750
|
content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
|
|
2925
3751
|
tool_call_id: toolCall.id
|
|
2926
3752
|
});
|
|
2927
|
-
return false;
|
|
3753
|
+
return { approved: false, reason };
|
|
2928
3754
|
}
|
|
2929
|
-
return true;
|
|
3755
|
+
return { approved: true };
|
|
2930
3756
|
};
|
|
2931
|
-
let
|
|
3757
|
+
let approvalOutcome;
|
|
2932
3758
|
if (this.spanManager) {
|
|
2933
|
-
|
|
3759
|
+
approvalOutcome = await this.spanManager.withSpanAsync(
|
|
2934
3760
|
"axl.tool.approval",
|
|
2935
3761
|
{
|
|
2936
3762
|
"axl.tool.name": toolName,
|
|
@@ -2938,20 +3764,24 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2938
3764
|
},
|
|
2939
3765
|
async (span) => {
|
|
2940
3766
|
const result = await approvalFn();
|
|
2941
|
-
span.setAttribute("axl.tool.approval.approved", result);
|
|
3767
|
+
span.setAttribute("axl.tool.approval.approved", result.approved);
|
|
2942
3768
|
return result;
|
|
2943
3769
|
}
|
|
2944
3770
|
);
|
|
2945
3771
|
} else {
|
|
2946
|
-
|
|
3772
|
+
approvalOutcome = await approvalFn();
|
|
2947
3773
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
type: "tool_denied",
|
|
3774
|
+
this.emitEvent({
|
|
3775
|
+
type: "tool_approval",
|
|
2951
3776
|
agent: agent2._name,
|
|
2952
3777
|
tool: toolName,
|
|
2953
|
-
data: {
|
|
3778
|
+
data: {
|
|
3779
|
+
approved: approvalOutcome.approved,
|
|
3780
|
+
args: toolArgs,
|
|
3781
|
+
...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
|
|
3782
|
+
}
|
|
2954
3783
|
});
|
|
3784
|
+
if (!approvalOutcome.approved) continue;
|
|
2955
3785
|
}
|
|
2956
3786
|
if (tool2 && tool2.hooks?.before) {
|
|
2957
3787
|
try {
|
|
@@ -3026,8 +3856,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3026
3856
|
return r;
|
|
3027
3857
|
}
|
|
3028
3858
|
) : await executeTool();
|
|
3029
|
-
this.
|
|
3030
|
-
type: "
|
|
3859
|
+
this.emitEvent({
|
|
3860
|
+
type: "tool_call_end",
|
|
3031
3861
|
agent: agent2._name,
|
|
3032
3862
|
tool: traceName,
|
|
3033
3863
|
duration: Date.now() - toolStart,
|
|
@@ -3044,37 +3874,52 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3044
3874
|
const content = response.content;
|
|
3045
3875
|
if (guardrails?.output) {
|
|
3046
3876
|
const outputResult = await guardrails.output(content, { metadata: this.metadata });
|
|
3047
|
-
|
|
3877
|
+
const attempt = guardrailOutputRetries + 1;
|
|
3878
|
+
const maxAttempts = maxGuardrailRetries + 1;
|
|
3879
|
+
const onBlock = guardrails.onBlock ?? "throw";
|
|
3880
|
+
let feedbackMessage;
|
|
3881
|
+
if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
|
|
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.`;
|
|
3883
|
+
}
|
|
3884
|
+
this.emitEvent({
|
|
3048
3885
|
type: "guardrail",
|
|
3049
3886
|
agent: agent2._name,
|
|
3050
3887
|
data: {
|
|
3051
3888
|
guardrailType: "output",
|
|
3052
3889
|
blocked: outputResult.block,
|
|
3053
|
-
reason: outputResult.reason
|
|
3890
|
+
...outputResult.reason ? { reason: outputResult.reason } : {},
|
|
3891
|
+
attempt,
|
|
3892
|
+
maxAttempts,
|
|
3893
|
+
...feedbackMessage ? { feedbackMessage } : {}
|
|
3054
3894
|
}
|
|
3055
3895
|
});
|
|
3056
3896
|
this.spanManager?.addEventToActiveSpan("axl.guardrail.check", {
|
|
3057
3897
|
"axl.guardrail.type": "output",
|
|
3058
3898
|
"axl.guardrail.blocked": outputResult.block,
|
|
3899
|
+
"axl.guardrail.attempt": attempt,
|
|
3900
|
+
"axl.guardrail.maxAttempts": maxAttempts,
|
|
3059
3901
|
...outputResult.reason ? { "axl.guardrail.reason": outputResult.reason } : {}
|
|
3060
3902
|
});
|
|
3061
3903
|
if (outputResult.block) {
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
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
|
+
});
|
|
3914
|
+
guardrailOutputRetries++;
|
|
3915
|
+
appendRetryMessages(
|
|
3916
|
+
currentMessages,
|
|
3917
|
+
content,
|
|
3918
|
+
feedbackMessage,
|
|
3919
|
+
response.providerMetadata
|
|
3920
|
+
);
|
|
3921
|
+
pendingRetryReason = "guardrail";
|
|
3922
|
+
continue;
|
|
3078
3923
|
}
|
|
3079
3924
|
if (typeof onBlock === "function") {
|
|
3080
3925
|
return onBlock(outputResult.reason ?? "Output blocked by guardrail", {
|
|
@@ -3086,30 +3931,67 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3086
3931
|
}
|
|
3087
3932
|
let validated = void 0;
|
|
3088
3933
|
if (options?.schema) {
|
|
3934
|
+
const maxSchemaRetries = options.retries ?? 3;
|
|
3935
|
+
const schemaAttempt = schemaRetries + 1;
|
|
3936
|
+
const schemaMaxAttempts = maxSchemaRetries + 1;
|
|
3937
|
+
let schemaValid = false;
|
|
3938
|
+
let schemaReason;
|
|
3939
|
+
let schemaFeedback;
|
|
3940
|
+
let schemaErr;
|
|
3089
3941
|
try {
|
|
3090
3942
|
const parsed = JSON.parse(extractJson(content));
|
|
3091
3943
|
validated = options.schema.parse(parsed);
|
|
3944
|
+
schemaValid = true;
|
|
3092
3945
|
} catch (err) {
|
|
3093
|
-
|
|
3946
|
+
schemaErr = err;
|
|
3947
|
+
schemaReason = err instanceof Error ? err.message : String(err);
|
|
3094
3948
|
if (schemaRetries < maxSchemaRetries) {
|
|
3949
|
+
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
this.emitEvent({
|
|
3953
|
+
type: "schema_check",
|
|
3954
|
+
agent: agent2._name,
|
|
3955
|
+
data: {
|
|
3956
|
+
valid: schemaValid,
|
|
3957
|
+
...schemaReason ? { reason: schemaReason } : {},
|
|
3958
|
+
attempt: schemaAttempt,
|
|
3959
|
+
maxAttempts: schemaMaxAttempts,
|
|
3960
|
+
...schemaFeedback ? { feedbackMessage: schemaFeedback } : {}
|
|
3961
|
+
}
|
|
3962
|
+
});
|
|
3963
|
+
this.spanManager?.addEventToActiveSpan("axl.schema.check", {
|
|
3964
|
+
"axl.schema.valid": schemaValid,
|
|
3965
|
+
"axl.schema.attempt": schemaAttempt,
|
|
3966
|
+
"axl.schema.maxAttempts": schemaMaxAttempts,
|
|
3967
|
+
...schemaReason ? { "axl.schema.reason": schemaReason } : {}
|
|
3968
|
+
});
|
|
3969
|
+
if (!schemaValid) {
|
|
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
|
+
});
|
|
3095
3980
|
schemaRetries++;
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
role: "assistant",
|
|
3981
|
+
appendRetryMessages(
|
|
3982
|
+
currentMessages,
|
|
3099
3983
|
content,
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
content: `Your response was not valid JSON or did not match the required schema: ${errorMsg}. Please fix and try again.`
|
|
3105
|
-
});
|
|
3984
|
+
schemaFeedback,
|
|
3985
|
+
response.providerMetadata
|
|
3986
|
+
);
|
|
3987
|
+
pendingRetryReason = "schema";
|
|
3106
3988
|
continue;
|
|
3107
3989
|
}
|
|
3108
|
-
const zodErr =
|
|
3990
|
+
const zodErr = schemaErr instanceof import_zod.ZodError ? schemaErr : new import_zod.ZodError([
|
|
3109
3991
|
{
|
|
3110
3992
|
code: "custom",
|
|
3111
3993
|
path: [],
|
|
3112
|
-
message:
|
|
3994
|
+
message: schemaReason ?? "Schema parse failed"
|
|
3113
3995
|
}
|
|
3114
3996
|
]);
|
|
3115
3997
|
throw new VerifyError(content, zodErr, maxSchemaRetries);
|
|
@@ -3125,31 +4007,49 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3125
4007
|
const reason = err instanceof Error ? err.message : String(err);
|
|
3126
4008
|
validateResult = { valid: false, reason: `Validator error: ${reason}` };
|
|
3127
4009
|
}
|
|
3128
|
-
|
|
4010
|
+
const maxValidateRetries = options.validateRetries ?? 2;
|
|
4011
|
+
const validateAttempt = validateRetries + 1;
|
|
4012
|
+
const validateMaxAttempts = maxValidateRetries + 1;
|
|
4013
|
+
let validateFeedback;
|
|
4014
|
+
if (!validateResult.valid && validateRetries < maxValidateRetries) {
|
|
4015
|
+
validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
|
|
4016
|
+
}
|
|
4017
|
+
this.emitEvent({
|
|
3129
4018
|
type: "validate",
|
|
3130
4019
|
agent: agent2._name,
|
|
3131
4020
|
data: {
|
|
3132
4021
|
valid: validateResult.valid,
|
|
3133
|
-
...validateResult.reason ? { reason: validateResult.reason } : {}
|
|
4022
|
+
...validateResult.reason ? { reason: validateResult.reason } : {},
|
|
4023
|
+
attempt: validateAttempt,
|
|
4024
|
+
maxAttempts: validateMaxAttempts,
|
|
4025
|
+
...validateFeedback ? { feedbackMessage: validateFeedback } : {}
|
|
3134
4026
|
}
|
|
3135
4027
|
});
|
|
3136
4028
|
this.spanManager?.addEventToActiveSpan("axl.validate.check", {
|
|
3137
4029
|
"axl.validate.valid": validateResult.valid,
|
|
4030
|
+
"axl.validate.attempt": validateAttempt,
|
|
4031
|
+
"axl.validate.maxAttempts": validateMaxAttempts,
|
|
3138
4032
|
...validateResult.reason ? { "axl.validate.reason": validateResult.reason } : {}
|
|
3139
4033
|
});
|
|
3140
4034
|
if (!validateResult.valid) {
|
|
3141
|
-
|
|
3142
|
-
|
|
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
|
+
});
|
|
3143
4045
|
validateRetries++;
|
|
3144
|
-
|
|
3145
|
-
|
|
4046
|
+
appendRetryMessages(
|
|
4047
|
+
currentMessages,
|
|
3146
4048
|
content,
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
content: `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`
|
|
3152
|
-
});
|
|
4049
|
+
validateFeedback,
|
|
4050
|
+
response.providerMetadata
|
|
4051
|
+
);
|
|
4052
|
+
pendingRetryReason = "validate";
|
|
3153
4053
|
continue;
|
|
3154
4054
|
}
|
|
3155
4055
|
throw new ValidationError(
|
|
@@ -3159,6 +4059,14 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3159
4059
|
);
|
|
3160
4060
|
}
|
|
3161
4061
|
}
|
|
4062
|
+
this.emitEvent({
|
|
4063
|
+
type: "pipeline",
|
|
4064
|
+
agent: agent2._name,
|
|
4065
|
+
status: "committed",
|
|
4066
|
+
stage: lastStartStage,
|
|
4067
|
+
attempt: lastStartAttempt,
|
|
4068
|
+
maxAttempts: lastStartMaxAttempts
|
|
4069
|
+
});
|
|
3162
4070
|
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3163
4071
|
return validated ?? content;
|
|
3164
4072
|
}
|
|
@@ -3287,43 +4195,100 @@ ${summaryResponse.content}`
|
|
|
3287
4195
|
/**
|
|
3288
4196
|
* Execute a function with checkpoint-replay semantics.
|
|
3289
4197
|
*
|
|
3290
|
-
* On first execution, runs `fn()`, saves the result
|
|
3291
|
-
* On replay (resume after restart), returns the saved result
|
|
3292
|
-
* 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.
|
|
3293
4212
|
*/
|
|
3294
|
-
async checkpoint(fn) {
|
|
3295
|
-
|
|
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);
|
|
3296
4228
|
}
|
|
3297
4229
|
/**
|
|
3298
|
-
* Internal checkpoint implementation shared by both the public
|
|
3299
|
-
* 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.
|
|
3300
4240
|
*/
|
|
3301
|
-
async _checkpoint(fn) {
|
|
3302
|
-
const step = this.checkpointCounter++;
|
|
4241
|
+
async _checkpoint(name, fn, options) {
|
|
3303
4242
|
if (!this.stateStore) {
|
|
3304
4243
|
return fn();
|
|
3305
4244
|
}
|
|
3306
|
-
const
|
|
4245
|
+
const agentStamp = options?.agent ? { agent: options.agent } : {};
|
|
4246
|
+
const saved = await this.stateStore.getCheckpoint(this.executionId, name);
|
|
3307
4247
|
if (saved !== null) {
|
|
3308
|
-
this.
|
|
3309
|
-
type: "
|
|
3310
|
-
|
|
4248
|
+
this.emitEvent({
|
|
4249
|
+
type: "checkpoint_replay",
|
|
4250
|
+
...agentStamp,
|
|
4251
|
+
data: { name }
|
|
3311
4252
|
});
|
|
3312
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.
|
|
4253
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.name": name });
|
|
3313
4254
|
return saved;
|
|
3314
4255
|
}
|
|
3315
4256
|
const result = await fn();
|
|
3316
|
-
await this.stateStore.saveCheckpoint(this.executionId,
|
|
3317
|
-
this.
|
|
3318
|
-
type: "
|
|
3319
|
-
|
|
4257
|
+
await this.stateStore.saveCheckpoint(this.executionId, name, result);
|
|
4258
|
+
this.emitEvent({
|
|
4259
|
+
type: "checkpoint_save",
|
|
4260
|
+
...agentStamp,
|
|
4261
|
+
data: { name }
|
|
3320
4262
|
});
|
|
3321
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.
|
|
4263
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.name": name });
|
|
3322
4264
|
return result;
|
|
3323
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
|
+
}
|
|
3324
4289
|
// ── ctx.spawn() ───────────────────────────────────────────────────────
|
|
3325
4290
|
async spawn(n, fn, options) {
|
|
3326
|
-
return this._checkpoint(() => {
|
|
4291
|
+
return this._checkpoint(this._autoCheckpointName("spawn"), () => {
|
|
3327
4292
|
if (this.spanManager) {
|
|
3328
4293
|
return this.spanManager.withSpanAsync(
|
|
3329
4294
|
"axl.ctx.spawn",
|
|
@@ -3518,6 +4483,17 @@ ${summaryResponse.content}`
|
|
|
3518
4483
|
async verify(fn, schema, options) {
|
|
3519
4484
|
const maxRetries = options?.retries ?? 3;
|
|
3520
4485
|
let lastRetry = void 0;
|
|
4486
|
+
const emitVerifyOutcome = (passed, attempts, lastError) => {
|
|
4487
|
+
this.emitEvent({
|
|
4488
|
+
type: "verify",
|
|
4489
|
+
agent: void 0,
|
|
4490
|
+
data: {
|
|
4491
|
+
passed,
|
|
4492
|
+
attempts,
|
|
4493
|
+
...lastError ? { lastError } : {}
|
|
4494
|
+
}
|
|
4495
|
+
});
|
|
4496
|
+
};
|
|
3521
4497
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3522
4498
|
let rawOutput;
|
|
3523
4499
|
try {
|
|
@@ -3536,12 +4512,14 @@ ${summaryResponse.content}`
|
|
|
3536
4512
|
const errorMsg = validateResult.reason ?? "Validation failed";
|
|
3537
4513
|
lastRetry = { error: errorMsg, output: rawOutput, parsed };
|
|
3538
4514
|
if (attempt === maxRetries) {
|
|
4515
|
+
emitVerifyOutcome(false, attempt + 1, errorMsg);
|
|
3539
4516
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3540
4517
|
throw new ValidationError(parsed, errorMsg, maxRetries);
|
|
3541
4518
|
}
|
|
3542
4519
|
continue;
|
|
3543
4520
|
}
|
|
3544
4521
|
}
|
|
4522
|
+
emitVerifyOutcome(true, attempt + 1);
|
|
3545
4523
|
return parsed;
|
|
3546
4524
|
} catch (err) {
|
|
3547
4525
|
if (err instanceof ValidationError) {
|
|
@@ -3551,6 +4529,7 @@ ${summaryResponse.content}`
|
|
|
3551
4529
|
parsed: err.lastOutput
|
|
3552
4530
|
};
|
|
3553
4531
|
if (attempt === maxRetries) {
|
|
4532
|
+
emitVerifyOutcome(false, attempt + 1, err.reason);
|
|
3554
4533
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3555
4534
|
throw err;
|
|
3556
4535
|
}
|
|
@@ -3559,6 +4538,7 @@ ${summaryResponse.content}`
|
|
|
3559
4538
|
if (err instanceof VerifyError) {
|
|
3560
4539
|
lastRetry = { error: err.message, output: rawOutput ?? err.lastOutput };
|
|
3561
4540
|
if (attempt === maxRetries) {
|
|
4541
|
+
emitVerifyOutcome(false, attempt + 1, err.message);
|
|
3562
4542
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3563
4543
|
throw err;
|
|
3564
4544
|
}
|
|
@@ -3567,12 +4547,14 @@ ${summaryResponse.content}`
|
|
|
3567
4547
|
const errorMsg = err instanceof import_zod.ZodError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3568
4548
|
lastRetry = { error: errorMsg, output: rawOutput };
|
|
3569
4549
|
if (attempt === maxRetries) {
|
|
4550
|
+
emitVerifyOutcome(false, attempt + 1, errorMsg);
|
|
3570
4551
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3571
4552
|
const zodErr = err instanceof import_zod.ZodError ? err : new import_zod.ZodError([{ code: "custom", path: [], message: errorMsg }]);
|
|
3572
4553
|
throw new VerifyError(rawOutput, zodErr, maxRetries);
|
|
3573
4554
|
}
|
|
3574
4555
|
}
|
|
3575
4556
|
}
|
|
4557
|
+
emitVerifyOutcome(false, maxRetries + 1, lastRetry?.error ?? "Verify failed");
|
|
3576
4558
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3577
4559
|
throw new VerifyError(
|
|
3578
4560
|
lastRetry?.output,
|
|
@@ -3642,7 +4624,7 @@ ${summaryResponse.content}`
|
|
|
3642
4624
|
}
|
|
3643
4625
|
// ── ctx.race() ────────────────────────────────────────────────────────
|
|
3644
4626
|
async race(fns, options) {
|
|
3645
|
-
return this._checkpoint(() => {
|
|
4627
|
+
return this._checkpoint(this._autoCheckpointName("race"), () => {
|
|
3646
4628
|
if (this.spanManager) {
|
|
3647
4629
|
return this.spanManager.withSpanAsync(
|
|
3648
4630
|
"axl.ctx.race",
|
|
@@ -3747,11 +4729,17 @@ ${summaryResponse.content}`
|
|
|
3747
4729
|
}
|
|
3748
4730
|
// ── ctx.parallel() ────────────────────────────────────────────────────
|
|
3749
4731
|
async parallel(fns) {
|
|
3750
|
-
return this._checkpoint(
|
|
4732
|
+
return this._checkpoint(
|
|
4733
|
+
this._autoCheckpointName("parallel"),
|
|
4734
|
+
() => Promise.all(fns.map((fn) => fn()))
|
|
4735
|
+
);
|
|
3751
4736
|
}
|
|
3752
4737
|
// ── ctx.map() ─────────────────────────────────────────────────────────
|
|
3753
4738
|
async map(items, fn, options) {
|
|
3754
|
-
return this._checkpoint(
|
|
4739
|
+
return this._checkpoint(
|
|
4740
|
+
this._autoCheckpointName("map"),
|
|
4741
|
+
() => this._mapImpl(items, fn, options)
|
|
4742
|
+
);
|
|
3755
4743
|
}
|
|
3756
4744
|
async _mapImpl(items, fn, options) {
|
|
3757
4745
|
const concurrency = options?.concurrency ?? 5;
|
|
@@ -3829,10 +4817,14 @@ ${summaryResponse.content}`
|
|
|
3829
4817
|
}
|
|
3830
4818
|
async _awaitHumanImpl(options) {
|
|
3831
4819
|
if (this.awaitHumanHandler) {
|
|
4820
|
+
this.emitEvent({
|
|
4821
|
+
type: "await_human",
|
|
4822
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
4823
|
+
});
|
|
3832
4824
|
const decision2 = await this.awaitHumanHandler(options);
|
|
3833
|
-
this.
|
|
3834
|
-
type: "
|
|
3835
|
-
data: {
|
|
4825
|
+
this.emitEvent({
|
|
4826
|
+
type: "await_human_resolved",
|
|
4827
|
+
data: { channel: options.channel, decision: decision2 }
|
|
3836
4828
|
});
|
|
3837
4829
|
return decision2;
|
|
3838
4830
|
}
|
|
@@ -3852,7 +4844,7 @@ ${summaryResponse.content}`
|
|
|
3852
4844
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
3853
4845
|
workflow: this.workflowName ?? "unknown",
|
|
3854
4846
|
input: this.input,
|
|
3855
|
-
step: this.
|
|
4847
|
+
step: this.stepRefRoot.value,
|
|
3856
4848
|
status: "waiting",
|
|
3857
4849
|
metadata: {
|
|
3858
4850
|
...this.metadata,
|
|
@@ -3861,26 +4853,111 @@ ${summaryResponse.content}`
|
|
|
3861
4853
|
}
|
|
3862
4854
|
});
|
|
3863
4855
|
}
|
|
3864
|
-
this.
|
|
3865
|
-
type: "
|
|
3866
|
-
data: {
|
|
4856
|
+
this.emitEvent({
|
|
4857
|
+
type: "await_human",
|
|
4858
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
3867
4859
|
});
|
|
3868
4860
|
const decision = await new Promise((resolve) => {
|
|
3869
4861
|
this.pendingDecisions.set(this.executionId, resolve);
|
|
3870
4862
|
});
|
|
4863
|
+
this.emitEvent({
|
|
4864
|
+
type: "await_human_resolved",
|
|
4865
|
+
data: { channel: options.channel, decision }
|
|
4866
|
+
});
|
|
3871
4867
|
if (this.stateStore) {
|
|
3872
4868
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
3873
4869
|
workflow: this.workflowName ?? "unknown",
|
|
3874
4870
|
input: this.input,
|
|
3875
|
-
step: this.
|
|
4871
|
+
step: this.stepRefRoot.value,
|
|
3876
4872
|
status: "running"
|
|
3877
4873
|
});
|
|
3878
4874
|
}
|
|
3879
4875
|
return decision;
|
|
3880
4876
|
}
|
|
4877
|
+
/**
|
|
4878
|
+
* Compose an `AbortSignal` for a memory operation's underlying embedder
|
|
4879
|
+
* fetch. Combines the parent context signal (user cancellation,
|
|
4880
|
+
* `runtime.execute({ signal })`) with the budget hard_stop abort so
|
|
4881
|
+
* that either cause correctly aborts an in-flight embed call.
|
|
4882
|
+
*
|
|
4883
|
+
* Returns `undefined` if there's nothing to cancel on — the embedder
|
|
4884
|
+
* runs without a signal in that case, matching its prior behavior.
|
|
4885
|
+
*
|
|
4886
|
+
* @internal
|
|
4887
|
+
*/
|
|
4888
|
+
_composeMemorySignal() {
|
|
4889
|
+
const budgetSignal = this.budgetContext?.abortController?.signal;
|
|
4890
|
+
if (this.signal && budgetSignal) return AbortSignal.any([this.signal, budgetSignal]);
|
|
4891
|
+
return this.signal ?? budgetSignal;
|
|
4892
|
+
}
|
|
4893
|
+
/**
|
|
4894
|
+
* Accumulate a cost amount into the active `budgetContext` and trip the
|
|
4895
|
+
* `exceeded` flag if we've crossed the limit. On `hard_stop` policy,
|
|
4896
|
+
* also fires the abort controller so in-flight operations cancel.
|
|
4897
|
+
*
|
|
4898
|
+
* Called from every code path that spends money: the `agent_call` loop,
|
|
4899
|
+
* semantic memory operations (`ctx.remember({embed:true})`, `ctx.recall({query})`),
|
|
4900
|
+
* and any future cost-emitting primitive. Centralizing the logic here
|
|
4901
|
+
* means `ctx.budget({ limit, policy })` accurately enforces the limit
|
|
4902
|
+
* across ALL cost sources — not just agent calls.
|
|
4903
|
+
*
|
|
4904
|
+
* @internal
|
|
4905
|
+
*/
|
|
4906
|
+
_accumulateBudgetCost(amount) {
|
|
4907
|
+
if (!this.budgetContext) return;
|
|
4908
|
+
if (!Number.isFinite(amount) || amount <= 0) return;
|
|
4909
|
+
this.budgetContext.totalCost += amount;
|
|
4910
|
+
if (this.budgetContext.totalCost >= this.budgetContext.limit) {
|
|
4911
|
+
this.budgetContext.exceeded = true;
|
|
4912
|
+
if (this.budgetContext.policy === "hard_stop" && this.budgetContext.abortController) {
|
|
4913
|
+
this.budgetContext.abortController.abort();
|
|
4914
|
+
}
|
|
4915
|
+
}
|
|
4916
|
+
}
|
|
4917
|
+
// ── workflow lifecycle trace emission ──────────────────────────────────
|
|
4918
|
+
//
|
|
4919
|
+
// These are called by the runtime at execution boundaries. They emit
|
|
4920
|
+
// first-class `workflow_start` / `workflow_end` trace events instead of
|
|
4921
|
+
// the previous `ctx.log('workflow_start', ...)` indirection — so consumers
|
|
4922
|
+
// that narrow via `event.type === 'workflow_start'` actually see them.
|
|
4923
|
+
// Internal: the runtime is the only caller, user workflows never call these.
|
|
4924
|
+
/** @internal — idempotent; no-ops on second+ call within one ctx. */
|
|
4925
|
+
_emitWorkflowStart(input) {
|
|
4926
|
+
if (this._workflowStartEmitted) return;
|
|
4927
|
+
this._workflowStartEmitted = true;
|
|
4928
|
+
this.emitEvent({
|
|
4929
|
+
type: "workflow_start",
|
|
4930
|
+
workflow: this.workflowName,
|
|
4931
|
+
data: { input }
|
|
4932
|
+
});
|
|
4933
|
+
}
|
|
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. */
|
|
4942
|
+
_emitWorkflowEnd(info) {
|
|
4943
|
+
if (this._workflowEndEmitted) return;
|
|
4944
|
+
this._workflowEndEmitted = true;
|
|
4945
|
+
this.emitEvent({
|
|
4946
|
+
type: "workflow_end",
|
|
4947
|
+
workflow: this.workflowName,
|
|
4948
|
+
duration: info.duration,
|
|
4949
|
+
data: {
|
|
4950
|
+
status: info.status,
|
|
4951
|
+
duration: info.duration,
|
|
4952
|
+
...info.result !== void 0 ? { result: info.result } : {},
|
|
4953
|
+
...info.error !== void 0 ? { error: info.error } : {},
|
|
4954
|
+
...info.aborted ? { aborted: true } : {}
|
|
4955
|
+
}
|
|
4956
|
+
});
|
|
4957
|
+
}
|
|
3881
4958
|
// ── ctx.log() ─────────────────────────────────────────────────────────
|
|
3882
4959
|
log(event, data) {
|
|
3883
|
-
this.
|
|
4960
|
+
this.emitEvent({
|
|
3884
4961
|
type: "log",
|
|
3885
4962
|
data: {
|
|
3886
4963
|
event,
|
|
@@ -3913,8 +4990,57 @@ ${summaryResponse.content}`
|
|
|
3913
4990
|
if (!this.stateStore) {
|
|
3914
4991
|
throw new Error("A state store is required for memory operations.");
|
|
3915
4992
|
}
|
|
4993
|
+
if (this.budgetContext?.exceeded) {
|
|
4994
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
4995
|
+
if (policy !== "warn") {
|
|
4996
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
3916
4999
|
const sessionId = this.metadata?.sessionId;
|
|
3917
|
-
|
|
5000
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
5001
|
+
try {
|
|
5002
|
+
const memorySignal = this._composeMemorySignal();
|
|
5003
|
+
const { usage } = await this.memoryManager.remember(
|
|
5004
|
+
key,
|
|
5005
|
+
value,
|
|
5006
|
+
this.stateStore,
|
|
5007
|
+
sessionId,
|
|
5008
|
+
options,
|
|
5009
|
+
memorySignal
|
|
5010
|
+
);
|
|
5011
|
+
if (usage?.cost != null) {
|
|
5012
|
+
this._accumulateBudgetCost(usage.cost);
|
|
5013
|
+
}
|
|
5014
|
+
this.emitEvent({
|
|
5015
|
+
type: "memory_remember",
|
|
5016
|
+
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
5017
|
+
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
5018
|
+
data: {
|
|
5019
|
+
key,
|
|
5020
|
+
scope,
|
|
5021
|
+
embed: options?.embed === true,
|
|
5022
|
+
...usage ? { usage } : {}
|
|
5023
|
+
}
|
|
5024
|
+
});
|
|
5025
|
+
} catch (err) {
|
|
5026
|
+
const partialUsage = err.axlEmbedUsage;
|
|
5027
|
+
if (partialUsage?.cost != null) {
|
|
5028
|
+
this._accumulateBudgetCost(partialUsage.cost);
|
|
5029
|
+
}
|
|
5030
|
+
this.emitEvent({
|
|
5031
|
+
type: "memory_remember",
|
|
5032
|
+
...partialUsage?.cost != null ? { cost: partialUsage.cost } : {},
|
|
5033
|
+
...partialUsage?.tokens != null ? { tokens: { input: partialUsage.tokens } } : {},
|
|
5034
|
+
data: {
|
|
5035
|
+
key,
|
|
5036
|
+
scope,
|
|
5037
|
+
embed: options?.embed === true,
|
|
5038
|
+
...partialUsage ? { usage: partialUsage } : {},
|
|
5039
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5040
|
+
}
|
|
5041
|
+
});
|
|
5042
|
+
throw err;
|
|
5043
|
+
}
|
|
3918
5044
|
}
|
|
3919
5045
|
/**
|
|
3920
5046
|
* Recall a value from memory by key, or perform semantic search if query option is provided.
|
|
@@ -3928,8 +5054,62 @@ ${summaryResponse.content}`
|
|
|
3928
5054
|
if (!this.stateStore) {
|
|
3929
5055
|
throw new Error("A state store is required for memory operations.");
|
|
3930
5056
|
}
|
|
5057
|
+
if (this.budgetContext?.exceeded) {
|
|
5058
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
5059
|
+
if (policy !== "warn") {
|
|
5060
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
3931
5063
|
const sessionId = this.metadata?.sessionId;
|
|
3932
|
-
|
|
5064
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
5065
|
+
const semantic = options?.query !== void 0;
|
|
5066
|
+
try {
|
|
5067
|
+
const memorySignal = this._composeMemorySignal();
|
|
5068
|
+
const { data, usage } = await this.memoryManager.recall(
|
|
5069
|
+
key,
|
|
5070
|
+
this.stateStore,
|
|
5071
|
+
sessionId,
|
|
5072
|
+
options,
|
|
5073
|
+
memorySignal
|
|
5074
|
+
);
|
|
5075
|
+
let hit;
|
|
5076
|
+
let resultCount;
|
|
5077
|
+
if (semantic) {
|
|
5078
|
+
resultCount = Array.isArray(data) ? data.length : 0;
|
|
5079
|
+
hit = resultCount > 0;
|
|
5080
|
+
} else {
|
|
5081
|
+
hit = data !== null && data !== void 0;
|
|
5082
|
+
}
|
|
5083
|
+
if (usage?.cost != null) {
|
|
5084
|
+
this._accumulateBudgetCost(usage.cost);
|
|
5085
|
+
}
|
|
5086
|
+
this.emitEvent({
|
|
5087
|
+
type: "memory_recall",
|
|
5088
|
+
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
5089
|
+
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
5090
|
+
data: {
|
|
5091
|
+
key,
|
|
5092
|
+
scope,
|
|
5093
|
+
semantic,
|
|
5094
|
+
hit,
|
|
5095
|
+
...resultCount !== void 0 ? { resultCount, count: resultCount } : {},
|
|
5096
|
+
...usage ? { usage } : {}
|
|
5097
|
+
}
|
|
5098
|
+
});
|
|
5099
|
+
return data;
|
|
5100
|
+
} catch (err) {
|
|
5101
|
+
this.emitEvent({
|
|
5102
|
+
type: "memory_recall",
|
|
5103
|
+
data: {
|
|
5104
|
+
key,
|
|
5105
|
+
scope,
|
|
5106
|
+
semantic,
|
|
5107
|
+
hit: false,
|
|
5108
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5109
|
+
}
|
|
5110
|
+
});
|
|
5111
|
+
throw err;
|
|
5112
|
+
}
|
|
3933
5113
|
}
|
|
3934
5114
|
/** Delete a memory entry by key. */
|
|
3935
5115
|
async forget(key, options) {
|
|
@@ -3942,7 +5122,24 @@ ${summaryResponse.content}`
|
|
|
3942
5122
|
throw new Error("A state store is required for memory operations.");
|
|
3943
5123
|
}
|
|
3944
5124
|
const sessionId = this.metadata?.sessionId;
|
|
3945
|
-
|
|
5125
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
5126
|
+
try {
|
|
5127
|
+
await this.memoryManager.forget(key, this.stateStore, sessionId, options);
|
|
5128
|
+
this.emitEvent({
|
|
5129
|
+
type: "memory_forget",
|
|
5130
|
+
data: { key, scope }
|
|
5131
|
+
});
|
|
5132
|
+
} catch (err) {
|
|
5133
|
+
this.emitEvent({
|
|
5134
|
+
type: "memory_forget",
|
|
5135
|
+
data: {
|
|
5136
|
+
key,
|
|
5137
|
+
scope,
|
|
5138
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5139
|
+
}
|
|
5140
|
+
});
|
|
5141
|
+
throw err;
|
|
5142
|
+
}
|
|
3946
5143
|
}
|
|
3947
5144
|
// ── ctx.delegate() ──────────────────────────────────────────────────
|
|
3948
5145
|
/**
|
|
@@ -3970,6 +5167,15 @@ ${summaryResponse.content}`
|
|
|
3970
5167
|
names.add(a._name);
|
|
3971
5168
|
}
|
|
3972
5169
|
if (agents.length === 1) {
|
|
5170
|
+
this.emitEvent({
|
|
5171
|
+
type: "delegate",
|
|
5172
|
+
agent: agents[0]._name,
|
|
5173
|
+
data: {
|
|
5174
|
+
candidates: [agents[0]._name],
|
|
5175
|
+
selected: agents[0]._name,
|
|
5176
|
+
reason: "single_candidate"
|
|
5177
|
+
}
|
|
5178
|
+
});
|
|
3973
5179
|
return this.ask(agents[0], prompt, {
|
|
3974
5180
|
schema: options?.schema,
|
|
3975
5181
|
retries: options?.retries,
|
|
@@ -4005,12 +5211,13 @@ ${summaryResponse.content}`
|
|
|
4005
5211
|
resolveModel: () => routerModelUri,
|
|
4006
5212
|
resolveSystem: () => routerSystem
|
|
4007
5213
|
};
|
|
4008
|
-
this.
|
|
5214
|
+
this.emitEvent({
|
|
4009
5215
|
type: "delegate",
|
|
4010
5216
|
agent: "_delegate_router",
|
|
4011
5217
|
data: {
|
|
4012
5218
|
candidates: agents.map((a) => a._name),
|
|
4013
|
-
routerModel: routerModelUri
|
|
5219
|
+
routerModel: routerModelUri,
|
|
5220
|
+
reason: "routed"
|
|
4014
5221
|
}
|
|
4015
5222
|
});
|
|
4016
5223
|
return this.ask(routerAgent, prompt, {
|
|
@@ -4022,24 +5229,57 @@ ${summaryResponse.content}`
|
|
|
4022
5229
|
});
|
|
4023
5230
|
}
|
|
4024
5231
|
// ── Private ───────────────────────────────────────────────────────────
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
5232
|
+
/**
|
|
5233
|
+
* Internal emitter input — intentionally loose so call sites don't need to
|
|
5234
|
+
* build a perfectly-narrowed discriminated-union member. The resulting
|
|
5235
|
+
* `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
|
|
5236
|
+
* consumer sites via the `type` discriminator.
|
|
5237
|
+
*/
|
|
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;
|
|
4029
5245
|
}
|
|
4030
5246
|
const event = {
|
|
4031
5247
|
executionId: this.executionId,
|
|
4032
|
-
step
|
|
5248
|
+
step,
|
|
4033
5249
|
timestamp: Date.now(),
|
|
5250
|
+
...this.workflowName ? { workflow: this.workflowName } : {},
|
|
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 },
|
|
4034
5264
|
...partial,
|
|
4035
5265
|
data
|
|
4036
5266
|
};
|
|
4037
|
-
this.
|
|
5267
|
+
const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
|
|
5268
|
+
if (this.onTrace) {
|
|
5269
|
+
try {
|
|
5270
|
+
this.onTrace(finalEvent);
|
|
5271
|
+
} catch (err) {
|
|
5272
|
+
console.error(
|
|
5273
|
+
"[axl] onTrace handler threw; trace event dropped:",
|
|
5274
|
+
err instanceof Error ? err.message : String(err)
|
|
5275
|
+
);
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
4038
5278
|
}
|
|
4039
5279
|
};
|
|
4040
5280
|
|
|
4041
5281
|
// src/agent.ts
|
|
4042
|
-
var
|
|
5282
|
+
var import_node_crypto2 = require("crypto");
|
|
4043
5283
|
var agentCounter = 0;
|
|
4044
5284
|
function agent(config) {
|
|
4045
5285
|
agentCounter++;
|
|
@@ -4059,7 +5299,7 @@ function agent(config) {
|
|
|
4059
5299
|
const registry = new ProviderRegistry();
|
|
4060
5300
|
const ctx = new WorkflowContext({
|
|
4061
5301
|
input: prompt,
|
|
4062
|
-
executionId: (0,
|
|
5302
|
+
executionId: (0, import_node_crypto2.randomUUID)(),
|
|
4063
5303
|
config: {},
|
|
4064
5304
|
providerRegistry: registry
|
|
4065
5305
|
});
|
|
@@ -4083,7 +5323,7 @@ function workflow(config) {
|
|
|
4083
5323
|
|
|
4084
5324
|
// src/runtime.ts
|
|
4085
5325
|
var import_node_events2 = require("events");
|
|
4086
|
-
var
|
|
5326
|
+
var import_node_crypto3 = require("crypto");
|
|
4087
5327
|
var import_node_async_hooks2 = require("async_hooks");
|
|
4088
5328
|
var import_node_fs2 = require("fs");
|
|
4089
5329
|
var import_node_path2 = require("path");
|
|
@@ -4106,29 +5346,20 @@ var MemoryStore = class {
|
|
|
4106
5346
|
constructor() {
|
|
4107
5347
|
this.loadPersistedState();
|
|
4108
5348
|
}
|
|
4109
|
-
async saveCheckpoint(executionId,
|
|
4110
|
-
let
|
|
4111
|
-
if (!
|
|
4112
|
-
|
|
4113
|
-
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);
|
|
4114
5354
|
}
|
|
4115
|
-
|
|
5355
|
+
entries.set(name, structuredClone(data));
|
|
4116
5356
|
}
|
|
4117
|
-
async getCheckpoint(executionId,
|
|
4118
|
-
const
|
|
4119
|
-
if (!
|
|
4120
|
-
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);
|
|
4121
5361
|
return data !== void 0 ? structuredClone(data) : null;
|
|
4122
5362
|
}
|
|
4123
|
-
async getLatestCheckpoint(executionId) {
|
|
4124
|
-
const steps = this.checkpoints.get(executionId);
|
|
4125
|
-
if (!steps || steps.size === 0) return null;
|
|
4126
|
-
let maxStep = -1;
|
|
4127
|
-
for (const step of steps.keys()) {
|
|
4128
|
-
if (step > maxStep) maxStep = step;
|
|
4129
|
-
}
|
|
4130
|
-
return { step: maxStep, data: structuredClone(steps.get(maxStep)) };
|
|
4131
|
-
}
|
|
4132
5363
|
async saveSession(sessionId, history) {
|
|
4133
5364
|
this.sessions.set(sessionId, structuredClone(history));
|
|
4134
5365
|
}
|
|
@@ -4301,7 +5532,7 @@ function rowToExecutionInfo(row) {
|
|
|
4301
5532
|
completedAt: row.completed_at ?? void 0,
|
|
4302
5533
|
duration: row.duration,
|
|
4303
5534
|
error: row.error ?? void 0,
|
|
4304
|
-
|
|
5535
|
+
events: safeJsonParse(row.events) ?? []
|
|
4305
5536
|
};
|
|
4306
5537
|
}
|
|
4307
5538
|
var SQLiteStore = class {
|
|
@@ -4317,15 +5548,80 @@ var SQLiteStore = class {
|
|
|
4317
5548
|
}
|
|
4318
5549
|
this.db = new BetterSqlite3(filePath);
|
|
4319
5550
|
this.db.pragma("journal_mode = WAL");
|
|
5551
|
+
this.migrate();
|
|
4320
5552
|
this.initTables();
|
|
4321
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
|
+
}
|
|
4322
5618
|
initTables() {
|
|
4323
5619
|
this.db.exec(`
|
|
4324
5620
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
4325
5621
|
execution_id TEXT NOT NULL,
|
|
4326
|
-
|
|
5622
|
+
name TEXT NOT NULL,
|
|
4327
5623
|
data TEXT NOT NULL,
|
|
4328
|
-
PRIMARY KEY (execution_id,
|
|
5624
|
+
PRIMARY KEY (execution_id, name)
|
|
4329
5625
|
);
|
|
4330
5626
|
|
|
4331
5627
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -4374,7 +5670,7 @@ var SQLiteStore = class {
|
|
|
4374
5670
|
completed_at INTEGER,
|
|
4375
5671
|
duration INTEGER NOT NULL DEFAULT 0,
|
|
4376
5672
|
error TEXT,
|
|
4377
|
-
|
|
5673
|
+
events TEXT NOT NULL
|
|
4378
5674
|
);
|
|
4379
5675
|
|
|
4380
5676
|
CREATE TABLE IF NOT EXISTS eval_history (
|
|
@@ -4389,26 +5685,19 @@ var SQLiteStore = class {
|
|
|
4389
5685
|
`);
|
|
4390
5686
|
}
|
|
4391
5687
|
// ── Checkpoints ────────────────────────────────────────────────────────
|
|
4392
|
-
async saveCheckpoint(executionId,
|
|
5688
|
+
async saveCheckpoint(executionId, name, data) {
|
|
4393
5689
|
const stmt = this.db.prepare(
|
|
4394
|
-
"INSERT OR REPLACE INTO checkpoints (execution_id,
|
|
5690
|
+
"INSERT OR REPLACE INTO checkpoints (execution_id, name, data) VALUES (?, ?, ?)"
|
|
4395
5691
|
);
|
|
4396
|
-
stmt.run(executionId,
|
|
5692
|
+
stmt.run(executionId, name, JSON.stringify(data));
|
|
4397
5693
|
}
|
|
4398
|
-
async getCheckpoint(executionId,
|
|
5694
|
+
async getCheckpoint(executionId, name) {
|
|
4399
5695
|
const stmt = this.db.prepare(
|
|
4400
|
-
"SELECT data FROM checkpoints WHERE execution_id = ? AND
|
|
5696
|
+
"SELECT data FROM checkpoints WHERE execution_id = ? AND name = ?"
|
|
4401
5697
|
);
|
|
4402
|
-
const row = stmt.get(executionId,
|
|
5698
|
+
const row = stmt.get(executionId, name);
|
|
4403
5699
|
return row ? safeJsonParse(row.data) : null;
|
|
4404
5700
|
}
|
|
4405
|
-
async getLatestCheckpoint(executionId) {
|
|
4406
|
-
const stmt = this.db.prepare(
|
|
4407
|
-
"SELECT step, data FROM checkpoints WHERE execution_id = ? ORDER BY step DESC LIMIT 1"
|
|
4408
|
-
);
|
|
4409
|
-
const row = stmt.get(executionId);
|
|
4410
|
-
return row ? { step: row.step, data: safeJsonParse(row.data) } : null;
|
|
4411
|
-
}
|
|
4412
5701
|
// ── Sessions ────────────────────────────────────────────────────────────
|
|
4413
5702
|
async saveSession(sessionId, history) {
|
|
4414
5703
|
const stmt = this.db.prepare(
|
|
@@ -4502,7 +5791,7 @@ var SQLiteStore = class {
|
|
|
4502
5791
|
// ── Execution History ────────────────────────────────────────────────────
|
|
4503
5792
|
async saveExecution(execution) {
|
|
4504
5793
|
this.db.prepare(
|
|
4505
|
-
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4506
5795
|
).run(
|
|
4507
5796
|
execution.executionId,
|
|
4508
5797
|
execution.workflow,
|
|
@@ -4512,7 +5801,7 @@ var SQLiteStore = class {
|
|
|
4512
5801
|
execution.completedAt ?? null,
|
|
4513
5802
|
execution.duration,
|
|
4514
5803
|
execution.error ?? null,
|
|
4515
|
-
JSON.stringify(execution.
|
|
5804
|
+
JSON.stringify(execution.events)
|
|
4516
5805
|
);
|
|
4517
5806
|
}
|
|
4518
5807
|
async getExecution(executionId) {
|
|
@@ -4686,7 +5975,8 @@ var Session = class _Session {
|
|
|
4686
5975
|
}
|
|
4687
5976
|
};
|
|
4688
5977
|
axlStream.on("done", (event) => {
|
|
4689
|
-
|
|
5978
|
+
const data = event.data;
|
|
5979
|
+
updateHistory(data.result).catch((err) => {
|
|
4690
5980
|
this.runtime.emit("error", {
|
|
4691
5981
|
type: "session_history_save_failed",
|
|
4692
5982
|
sessionId: this.sessionId,
|
|
@@ -4728,9 +6018,88 @@ var Session = class _Session {
|
|
|
4728
6018
|
// src/stream.ts
|
|
4729
6019
|
var import_node_stream = require("stream");
|
|
4730
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
|
|
4731
6078
|
var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
4732
6079
|
bus = new import_node_events.EventEmitter();
|
|
4733
|
-
|
|
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();
|
|
4734
6103
|
result = void 0;
|
|
4735
6104
|
finished = false;
|
|
4736
6105
|
promise;
|
|
@@ -4752,18 +6121,10 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4752
6121
|
this.promise.catch(() => {
|
|
4753
6122
|
});
|
|
4754
6123
|
}
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
"tool_approval",
|
|
4760
|
-
"agent_start",
|
|
4761
|
-
"agent_end",
|
|
4762
|
-
"handoff",
|
|
4763
|
-
"step",
|
|
4764
|
-
"done",
|
|
4765
|
-
"error"
|
|
4766
|
-
]);
|
|
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);
|
|
4767
6128
|
on(event, handler) {
|
|
4768
6129
|
if (_AxlStream.STREAM_EVENTS.has(event)) {
|
|
4769
6130
|
this.bus.on(event, handler);
|
|
@@ -4813,21 +6174,83 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4813
6174
|
while (true) {
|
|
4814
6175
|
const { value, done } = await iter.next();
|
|
4815
6176
|
if (done) return { value: void 0, done: true };
|
|
4816
|
-
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
|
+
}
|
|
4817
6218
|
}
|
|
4818
6219
|
}
|
|
4819
6220
|
};
|
|
4820
6221
|
}
|
|
4821
6222
|
};
|
|
4822
6223
|
}
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
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",
|
|
4829
6239
|
"tool_approval",
|
|
4830
|
-
"
|
|
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"
|
|
4831
6254
|
]);
|
|
4832
6255
|
const self = this;
|
|
4833
6256
|
return {
|
|
@@ -4838,7 +6261,7 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4838
6261
|
while (true) {
|
|
4839
6262
|
const { value, done } = await iter.next();
|
|
4840
6263
|
if (done) return { value: void 0, done: true };
|
|
4841
|
-
if (
|
|
6264
|
+
if (lifecycleTypes.has(value.type)) return { value, done: false };
|
|
4842
6265
|
}
|
|
4843
6266
|
}
|
|
4844
6267
|
};
|
|
@@ -4858,10 +6281,34 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4858
6281
|
});
|
|
4859
6282
|
return destination;
|
|
4860
6283
|
}
|
|
4861
|
-
/** Push
|
|
6284
|
+
/** Push an event onto the stream. Called by the runtime. */
|
|
4862
6285
|
_push(event) {
|
|
4863
6286
|
if (this.finished) return;
|
|
4864
|
-
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
|
+
}
|
|
4865
6312
|
this.bus.emit(event.type, event);
|
|
4866
6313
|
this.push(event);
|
|
4867
6314
|
const waiter = this.waiters.shift();
|
|
@@ -4871,12 +6318,25 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4871
6318
|
this.eventQueue.push(event);
|
|
4872
6319
|
}
|
|
4873
6320
|
}
|
|
4874
|
-
/**
|
|
4875
|
-
|
|
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) {
|
|
4876
6330
|
if (this.finished) return;
|
|
4877
6331
|
this.finished = true;
|
|
4878
6332
|
this.result = result;
|
|
4879
|
-
const doneEvent = {
|
|
6333
|
+
const doneEvent = {
|
|
6334
|
+
type: "done",
|
|
6335
|
+
executionId,
|
|
6336
|
+
step: Number.MAX_SAFE_INTEGER,
|
|
6337
|
+
timestamp: Date.now(),
|
|
6338
|
+
data: { result }
|
|
6339
|
+
};
|
|
4880
6340
|
this.bus.emit("done", doneEvent);
|
|
4881
6341
|
this.push(doneEvent);
|
|
4882
6342
|
this.push(null);
|
|
@@ -4892,11 +6352,19 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4892
6352
|
this.waiters.length = 0;
|
|
4893
6353
|
this.bus.emit("__resolve", result);
|
|
4894
6354
|
}
|
|
4895
|
-
/** Signal an error.
|
|
4896
|
-
|
|
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) {
|
|
4897
6359
|
if (this.finished) return;
|
|
4898
6360
|
this.finished = true;
|
|
4899
|
-
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
|
+
};
|
|
4900
6368
|
this.bus.emit("error", errorEvent);
|
|
4901
6369
|
this.push(errorEvent);
|
|
4902
6370
|
this.push(null);
|
|
@@ -4912,8 +6380,31 @@ var AxlStream = class _AxlStream extends import_node_stream.Readable {
|
|
|
4912
6380
|
this.waiters.length = 0;
|
|
4913
6381
|
this.bus.emit("__reject", error);
|
|
4914
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`. */
|
|
4915
6396
|
get fullText() {
|
|
4916
|
-
|
|
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;
|
|
4917
6408
|
}
|
|
4918
6409
|
};
|
|
4919
6410
|
|
|
@@ -5276,6 +6767,18 @@ var McpManager = class {
|
|
|
5276
6767
|
};
|
|
5277
6768
|
|
|
5278
6769
|
// src/memory/manager.ts
|
|
6770
|
+
function assertEmbedResult(result) {
|
|
6771
|
+
if (Array.isArray(result)) {
|
|
6772
|
+
throw new Error(
|
|
6773
|
+
"Embedder.embed() returned a bare number[][] (legacy shape). As of @axlsdk/axl 0.15, Embedder.embed() must return { vectors: number[][]; usage?: { tokens?, cost?, model? } }. Wrap your existing return value: `return { vectors: await old() }`."
|
|
6774
|
+
);
|
|
6775
|
+
}
|
|
6776
|
+
if (!result || typeof result !== "object" || !Array.isArray(result.vectors)) {
|
|
6777
|
+
throw new Error(
|
|
6778
|
+
`Embedder.embed() must return { vectors: number[][]; usage?: EmbedUsage }. Got ${result === null ? "null" : typeof result}.`
|
|
6779
|
+
);
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
5279
6782
|
var MemoryManager = class {
|
|
5280
6783
|
vectorStore;
|
|
5281
6784
|
embedder;
|
|
@@ -5290,8 +6793,11 @@ var MemoryManager = class {
|
|
|
5290
6793
|
* Store a key-value pair in memory.
|
|
5291
6794
|
* If a vector store and embedder are configured and embed is true,
|
|
5292
6795
|
* also embeds the value for semantic search.
|
|
6796
|
+
*
|
|
6797
|
+
* Returns `{ usage }` when embedding happened, carrying the embedder's
|
|
6798
|
+
* cost/token reporting out to the caller for cost attribution.
|
|
5293
6799
|
*/
|
|
5294
|
-
async remember(key, value, stateStore, sessionId, options) {
|
|
6800
|
+
async remember(key, value, stateStore, sessionId, options, signal) {
|
|
5295
6801
|
const scope = options?.scope ?? "session";
|
|
5296
6802
|
if (scope === "session" && !sessionId) {
|
|
5297
6803
|
throw new Error(
|
|
@@ -5310,23 +6816,44 @@ var MemoryManager = class {
|
|
|
5310
6816
|
}
|
|
5311
6817
|
if (options?.embed === true && this.vectorStore && this.embedder) {
|
|
5312
6818
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
5313
|
-
const
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
6819
|
+
const raw = await this.embedder.embed([text], signal);
|
|
6820
|
+
assertEmbedResult(raw);
|
|
6821
|
+
const { vectors, usage } = raw;
|
|
6822
|
+
try {
|
|
6823
|
+
await this.vectorStore.upsert([
|
|
6824
|
+
{
|
|
6825
|
+
id: `${storeScope}:${key}`,
|
|
6826
|
+
content: text,
|
|
6827
|
+
embedding: vectors[0],
|
|
6828
|
+
metadata: { key, scope, ...options?.metadata }
|
|
6829
|
+
}
|
|
6830
|
+
]);
|
|
6831
|
+
} catch (err) {
|
|
6832
|
+
if (usage && typeof err === "object" && err !== null) {
|
|
6833
|
+
Object.defineProperty(err, "axlEmbedUsage", {
|
|
6834
|
+
value: usage,
|
|
6835
|
+
enumerable: false,
|
|
6836
|
+
writable: true,
|
|
6837
|
+
configurable: true
|
|
6838
|
+
});
|
|
5320
6839
|
}
|
|
5321
|
-
|
|
6840
|
+
throw err;
|
|
6841
|
+
}
|
|
6842
|
+
return usage ? { usage } : {};
|
|
5322
6843
|
}
|
|
6844
|
+
return {};
|
|
5323
6845
|
}
|
|
5324
6846
|
/**
|
|
5325
6847
|
* Recall a value from memory by key, or perform semantic search if query is provided.
|
|
6848
|
+
*
|
|
6849
|
+
* Always returns `{ data }`; semantic recalls additionally include
|
|
6850
|
+
* `usage` from the embedder (when the embedder reported it).
|
|
5326
6851
|
*/
|
|
5327
|
-
async recall(key, stateStore, sessionId, options) {
|
|
6852
|
+
async recall(key, stateStore, sessionId, options, signal) {
|
|
5328
6853
|
if (options?.query && this.vectorStore && this.embedder) {
|
|
5329
|
-
const
|
|
6854
|
+
const raw = await this.embedder.embed([options.query], signal);
|
|
6855
|
+
assertEmbedResult(raw);
|
|
6856
|
+
const { vectors, usage } = raw;
|
|
5330
6857
|
const topK = options?.topK ?? 5;
|
|
5331
6858
|
const searchScope = options?.scope ?? "session";
|
|
5332
6859
|
if (searchScope === "session" && !sessionId) {
|
|
@@ -5334,13 +6861,14 @@ var MemoryManager = class {
|
|
|
5334
6861
|
'sessionId is required for session-scoped memory. Use { scope: "global" } or provide a sessionId in metadata.'
|
|
5335
6862
|
);
|
|
5336
6863
|
}
|
|
5337
|
-
const rawResults = await this.vectorStore.search(
|
|
6864
|
+
const rawResults = await this.vectorStore.search(vectors[0], topK * 3);
|
|
5338
6865
|
const filtered = rawResults.filter((r) => {
|
|
5339
6866
|
if (r.id.startsWith("global:")) return true;
|
|
5340
6867
|
if (searchScope === "session") return r.id.startsWith(`session:${sessionId}:`);
|
|
5341
6868
|
return true;
|
|
5342
6869
|
});
|
|
5343
|
-
|
|
6870
|
+
const data = filtered.slice(0, topK);
|
|
6871
|
+
return usage ? { data, usage } : { data };
|
|
5344
6872
|
}
|
|
5345
6873
|
const scope = options?.scope ?? "session";
|
|
5346
6874
|
if (scope === "session" && !sessionId) {
|
|
@@ -5350,10 +6878,10 @@ var MemoryManager = class {
|
|
|
5350
6878
|
}
|
|
5351
6879
|
const storeScope = scope === "session" ? `session:${sessionId}` : "global";
|
|
5352
6880
|
if (stateStore.getMemory) {
|
|
5353
|
-
return stateStore.getMemory(storeScope, key);
|
|
6881
|
+
return { data: await stateStore.getMemory(storeScope, key) };
|
|
5354
6882
|
}
|
|
5355
6883
|
const storeKey = `memory:${storeScope}:${key}`;
|
|
5356
|
-
return stateStore.getSessionMeta(storeKey, "value");
|
|
6884
|
+
return { data: await stateStore.getSessionMeta(storeKey, "value") };
|
|
5357
6885
|
}
|
|
5358
6886
|
/** Delete a memory entry. If a vector store is configured, also removes the embedding. */
|
|
5359
6887
|
async forget(key, stateStore, sessionId, options) {
|
|
@@ -5423,6 +6951,32 @@ function hashInput(input) {
|
|
|
5423
6951
|
}
|
|
5424
6952
|
return (hash >>> 0).toString(16);
|
|
5425
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
|
+
}
|
|
5426
6980
|
var costScopeStorage = new import_node_async_hooks2.AsyncLocalStorage();
|
|
5427
6981
|
var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
5428
6982
|
config;
|
|
@@ -5442,11 +6996,27 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5442
6996
|
historicalExecutionsLoadPromise = null;
|
|
5443
6997
|
evalHistory = [];
|
|
5444
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;
|
|
5445
7003
|
constructor(config) {
|
|
5446
7004
|
super();
|
|
5447
7005
|
this.config = resolveConfig(config ?? {});
|
|
5448
7006
|
this.providerRegistry = new ProviderRegistry();
|
|
5449
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
|
+
}
|
|
5450
7020
|
if (this.config.memory) {
|
|
5451
7021
|
this.memoryManager = new MemoryManager({
|
|
5452
7022
|
vectorStore: this.config.memory.vectorStore,
|
|
@@ -5454,6 +7024,22 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5454
7024
|
});
|
|
5455
7025
|
}
|
|
5456
7026
|
}
|
|
7027
|
+
/**
|
|
7028
|
+
* Whether `config.trace.redact` is enabled on this runtime. A narrow
|
|
7029
|
+
* boolean getter is preferred over exposing the full config because:
|
|
7030
|
+
* (a) `Readonly<AxlConfig>` is shallow so the config would be mutable
|
|
7031
|
+
* at runtime through sub-objects, subverting any compliance guarantee;
|
|
7032
|
+
* (b) observability consumers like Studio only need the boolean, not
|
|
7033
|
+
* the whole config tree; (c) if future observability config needs to
|
|
7034
|
+
* expand, each new flag gets its own narrow getter.
|
|
7035
|
+
*
|
|
7036
|
+
* Studio's server-side redaction helpers (executions, memory, sessions,
|
|
7037
|
+
* evals, decisions, tools, playground, workflows) all route through
|
|
7038
|
+
* this accessor to decide whether to scrub response payloads.
|
|
7039
|
+
*/
|
|
7040
|
+
isRedactEnabled() {
|
|
7041
|
+
return this.config.trace?.redact === true;
|
|
7042
|
+
}
|
|
5457
7043
|
/**
|
|
5458
7044
|
* Initialize MCP servers configured in the config.
|
|
5459
7045
|
* Call this before executing workflows that use MCP tools.
|
|
@@ -5486,6 +7072,71 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5486
7072
|
return new MemoryStore();
|
|
5487
7073
|
}
|
|
5488
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
|
+
}
|
|
5489
7140
|
/**
|
|
5490
7141
|
* Persist a completed/failed execution to the state store (fire-and-forget)
|
|
5491
7142
|
* and move it from the active executions map to the historical cache.
|
|
@@ -5501,7 +7152,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5501
7152
|
this.executions.delete(id);
|
|
5502
7153
|
}
|
|
5503
7154
|
/** Register a workflow with the runtime. */
|
|
5504
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5505
7155
|
register(workflow2) {
|
|
5506
7156
|
this.workflows.set(workflow2.name, workflow2);
|
|
5507
7157
|
}
|
|
@@ -5603,10 +7253,19 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5603
7253
|
metadata: result2.metadata ?? metadata
|
|
5604
7254
|
};
|
|
5605
7255
|
};
|
|
5606
|
-
result = await runEvalFn(entry.config, wrappedExecuteFn, this
|
|
7256
|
+
result = await runEvalFn(entry.config, wrappedExecuteFn, this, {
|
|
7257
|
+
onProgress: options?.onProgress,
|
|
7258
|
+
signal: options?.signal,
|
|
7259
|
+
captureTraces: options?.captureTraces
|
|
7260
|
+
});
|
|
5607
7261
|
} else {
|
|
5608
7262
|
result = await this.eval(
|
|
5609
|
-
entry.config
|
|
7263
|
+
entry.config,
|
|
7264
|
+
{
|
|
7265
|
+
onProgress: options?.onProgress,
|
|
7266
|
+
signal: options?.signal,
|
|
7267
|
+
captureTraces: options?.captureTraces
|
|
7268
|
+
}
|
|
5610
7269
|
);
|
|
5611
7270
|
}
|
|
5612
7271
|
const resultObj = result;
|
|
@@ -5618,7 +7277,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5618
7277
|
}
|
|
5619
7278
|
try {
|
|
5620
7279
|
await this.saveEvalResult({
|
|
5621
|
-
id: resultObj.id ?? (0,
|
|
7280
|
+
id: resultObj.id ?? (0, import_node_crypto3.randomUUID)(),
|
|
5622
7281
|
eval: name,
|
|
5623
7282
|
timestamp: Date.now(),
|
|
5624
7283
|
data: structuredClone(result)
|
|
@@ -5655,7 +7314,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5655
7314
|
* and automatically emits trace events and tracks cost.
|
|
5656
7315
|
*/
|
|
5657
7316
|
createContext(options) {
|
|
5658
|
-
const executionId = (0,
|
|
7317
|
+
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
5659
7318
|
const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
|
|
5660
7319
|
this.registerWithCostScope(executionId);
|
|
5661
7320
|
return new WorkflowContext({
|
|
@@ -5671,10 +7330,12 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5671
7330
|
sessionHistory: options?.sessionHistory,
|
|
5672
7331
|
signal: options?.signal,
|
|
5673
7332
|
onToken: options?.onToken,
|
|
7333
|
+
onToolCall: options?.onToolCall,
|
|
7334
|
+
onAgentStart: options?.onAgentStart,
|
|
5674
7335
|
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5675
7336
|
onTrace: (event) => {
|
|
5676
7337
|
this.emit("trace", event);
|
|
5677
|
-
this.
|
|
7338
|
+
this.outputAxlEvent(event);
|
|
5678
7339
|
},
|
|
5679
7340
|
budgetContext: {
|
|
5680
7341
|
totalCost: 0,
|
|
@@ -5701,7 +7362,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5701
7362
|
);
|
|
5702
7363
|
}
|
|
5703
7364
|
const validated = workflow2.inputSchema.parse(input);
|
|
5704
|
-
const executionId = (0,
|
|
7365
|
+
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
5705
7366
|
const controller = new AbortController();
|
|
5706
7367
|
this.abortControllers.set(executionId, controller);
|
|
5707
7368
|
this.registerWithCostScope(executionId);
|
|
@@ -5709,7 +7370,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5709
7370
|
executionId,
|
|
5710
7371
|
workflow: name,
|
|
5711
7372
|
status: "running",
|
|
5712
|
-
|
|
7373
|
+
events: [],
|
|
5713
7374
|
totalCost: 0,
|
|
5714
7375
|
startedAt: Date.now(),
|
|
5715
7376
|
duration: 0
|
|
@@ -5725,25 +7386,41 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5725
7386
|
sessionHistory,
|
|
5726
7387
|
signal: controller.signal,
|
|
5727
7388
|
onTrace: (event) => {
|
|
5728
|
-
execInfo.
|
|
5729
|
-
|
|
7389
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7390
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
5730
7391
|
this.emit("trace", event);
|
|
5731
|
-
this.
|
|
5732
|
-
if (event.type === "
|
|
7392
|
+
this.outputAxlEvent(event);
|
|
7393
|
+
if (event.type === "handoff_start") {
|
|
5733
7394
|
const sessionId = options?.metadata?.sessionId;
|
|
5734
7395
|
if (sessionId) {
|
|
5735
|
-
const data = event.data;
|
|
5736
7396
|
this.appendHandoffRecord(sessionId, {
|
|
5737
|
-
source: event.
|
|
5738
|
-
target: data
|
|
5739
|
-
mode: data
|
|
7397
|
+
source: event.data.source,
|
|
7398
|
+
target: event.data.target,
|
|
7399
|
+
mode: event.data.mode,
|
|
5740
7400
|
timestamp: event.timestamp,
|
|
5741
|
-
|
|
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
|
|
5742
7418
|
});
|
|
5743
7419
|
}
|
|
5744
7420
|
}
|
|
5745
7421
|
},
|
|
5746
7422
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
7423
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5747
7424
|
stateStore: this.stateStore,
|
|
5748
7425
|
workflowName: name,
|
|
5749
7426
|
mcpManager: this.mcpManager,
|
|
@@ -5757,7 +7434,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5757
7434
|
policy: "finish_and_stop"
|
|
5758
7435
|
}
|
|
5759
7436
|
});
|
|
5760
|
-
ctx.log("workflow_start", { workflow: name, executionId });
|
|
5761
7437
|
return this.spanManager.withSpanAsync(
|
|
5762
7438
|
"axl.workflow.execute",
|
|
5763
7439
|
{
|
|
@@ -5765,39 +7441,13 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5765
7441
|
"axl.execution.id": executionId,
|
|
5766
7442
|
"axl.workflow.input_hash": hashInput(validated)
|
|
5767
7443
|
},
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
execInfo.result = output;
|
|
5776
|
-
ctx.log("workflow_end", {
|
|
5777
|
-
workflow: name,
|
|
5778
|
-
status: "completed",
|
|
5779
|
-
duration: execInfo.duration,
|
|
5780
|
-
cost: execInfo.totalCost
|
|
5781
|
-
});
|
|
5782
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
5783
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
5784
|
-
}
|
|
5785
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
5786
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
5787
|
-
this.persistExecution(execInfo);
|
|
5788
|
-
return output;
|
|
5789
|
-
} catch (err) {
|
|
5790
|
-
execInfo.status = "failed";
|
|
5791
|
-
execInfo.completedAt = Date.now();
|
|
5792
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5793
|
-
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5794
|
-
ctx.log("workflow_end", { workflow: name, status: "failed", error: execInfo.error });
|
|
5795
|
-
this.persistExecution(execInfo);
|
|
5796
|
-
throw err;
|
|
5797
|
-
} finally {
|
|
5798
|
-
this.abortControllers.delete(executionId);
|
|
5799
|
-
}
|
|
5800
|
-
}
|
|
7444
|
+
(span) => this.runWorkflowBody({
|
|
7445
|
+
workflow: workflow2,
|
|
7446
|
+
ctx,
|
|
7447
|
+
execInfo,
|
|
7448
|
+
validated,
|
|
7449
|
+
span
|
|
7450
|
+
})
|
|
5801
7451
|
);
|
|
5802
7452
|
}
|
|
5803
7453
|
/** Execute a workflow and return a stream. */
|
|
@@ -5805,21 +7455,20 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5805
7455
|
const axlStream = new AxlStream();
|
|
5806
7456
|
const controller = new AbortController();
|
|
5807
7457
|
axlStream.on("close", () => controller.abort());
|
|
7458
|
+
const executionId = (0, import_node_crypto3.randomUUID)();
|
|
7459
|
+
this.abortControllers.set(executionId, controller);
|
|
5808
7460
|
let execInfo;
|
|
5809
|
-
let ctx;
|
|
5810
7461
|
const run = async () => {
|
|
5811
7462
|
const workflow2 = this.workflows.get(name);
|
|
5812
7463
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
5813
7464
|
const validated = workflow2.inputSchema.parse(input);
|
|
5814
|
-
const executionId = (0, import_node_crypto2.randomUUID)();
|
|
5815
|
-
this.abortControllers.set(executionId, controller);
|
|
5816
7465
|
const sessionHistory = options?.metadata?.sessionHistory ?? void 0;
|
|
5817
7466
|
this.registerWithCostScope(executionId);
|
|
5818
7467
|
execInfo = {
|
|
5819
7468
|
executionId,
|
|
5820
7469
|
workflow: name,
|
|
5821
7470
|
status: "running",
|
|
5822
|
-
|
|
7471
|
+
events: [],
|
|
5823
7472
|
totalCost: 0,
|
|
5824
7473
|
startedAt: Date.now(),
|
|
5825
7474
|
duration: 0
|
|
@@ -5833,79 +7482,49 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5833
7482
|
providerRegistry: this.providerRegistry,
|
|
5834
7483
|
sessionHistory,
|
|
5835
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
|
+
},
|
|
5836
7491
|
onTrace: (event) => {
|
|
5837
|
-
execInfo.
|
|
5838
|
-
|
|
7492
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7493
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
5839
7494
|
this.emit("trace", event);
|
|
5840
|
-
this.
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
axlStream._push({
|
|
5844
|
-
type: "handoff",
|
|
5845
|
-
source: event.agent ?? "",
|
|
5846
|
-
target: data?.target ?? "",
|
|
5847
|
-
mode: data?.mode ?? void 0
|
|
5848
|
-
});
|
|
7495
|
+
this.outputAxlEvent(event);
|
|
7496
|
+
axlStream._push(event);
|
|
7497
|
+
if (event.type === "handoff_start") {
|
|
5849
7498
|
const sessionId = options?.metadata?.sessionId;
|
|
5850
7499
|
if (sessionId) {
|
|
5851
7500
|
this.appendHandoffRecord(sessionId, {
|
|
5852
|
-
source: event.
|
|
5853
|
-
target: data
|
|
5854
|
-
mode: data
|
|
7501
|
+
source: event.data.source,
|
|
7502
|
+
target: event.data.target,
|
|
7503
|
+
mode: event.data.mode,
|
|
5855
7504
|
timestamp: event.timestamp,
|
|
5856
|
-
|
|
7505
|
+
toAskId: event.toAskId
|
|
5857
7506
|
});
|
|
5858
7507
|
}
|
|
5859
|
-
} else if (event.type === "
|
|
5860
|
-
const
|
|
5861
|
-
if (
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
args: data?.args,
|
|
5866
|
-
approved: true
|
|
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
|
|
5867
7514
|
});
|
|
5868
|
-
}
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
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
|
|
5875
7522
|
});
|
|
5876
7523
|
}
|
|
5877
|
-
} else if (event.type === "agent_call") {
|
|
5878
|
-
axlStream._push({
|
|
5879
|
-
type: "agent_end",
|
|
5880
|
-
agent: event.agent ?? "",
|
|
5881
|
-
cost: event.cost,
|
|
5882
|
-
duration: event.duration
|
|
5883
|
-
});
|
|
5884
|
-
} else if (event.type === "tool_call") {
|
|
5885
|
-
axlStream._push({
|
|
5886
|
-
type: "tool_result",
|
|
5887
|
-
name: event.tool ?? "",
|
|
5888
|
-
result: event.data?.result,
|
|
5889
|
-
callId: event.data?.callId
|
|
5890
|
-
});
|
|
5891
7524
|
}
|
|
5892
|
-
axlStream._push({ type: "step", step: event.step, data: event });
|
|
5893
|
-
},
|
|
5894
|
-
onToken: (token) => {
|
|
5895
|
-
axlStream._push({ type: "token", data: token });
|
|
5896
|
-
},
|
|
5897
|
-
onToolCall: (call) => {
|
|
5898
|
-
axlStream._push({
|
|
5899
|
-
type: "tool_call",
|
|
5900
|
-
name: call.name,
|
|
5901
|
-
args: call.args,
|
|
5902
|
-
callId: call.callId
|
|
5903
|
-
});
|
|
5904
|
-
},
|
|
5905
|
-
onAgentStart: (info) => {
|
|
5906
|
-
axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
|
|
5907
7525
|
},
|
|
5908
7526
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
7527
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5909
7528
|
stateStore: this.stateStore,
|
|
5910
7529
|
workflowName: name,
|
|
5911
7530
|
mcpManager: this.mcpManager,
|
|
@@ -5919,7 +7538,6 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5919
7538
|
policy: "finish_and_stop"
|
|
5920
7539
|
}
|
|
5921
7540
|
});
|
|
5922
|
-
ctx = wfCtx;
|
|
5923
7541
|
return this.spanManager.withSpanAsync(
|
|
5924
7542
|
"axl.workflow.execute",
|
|
5925
7543
|
{
|
|
@@ -5927,47 +7545,25 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
5927
7545
|
"axl.execution.id": executionId,
|
|
5928
7546
|
"axl.workflow.input_hash": hashInput(validated)
|
|
5929
7547
|
},
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
execInfo.result = result;
|
|
5938
|
-
wfCtx.log("workflow_end", {
|
|
5939
|
-
workflow: name,
|
|
5940
|
-
status: "completed",
|
|
5941
|
-
duration: execInfo.duration,
|
|
5942
|
-
cost: execInfo.totalCost
|
|
5943
|
-
});
|
|
5944
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
5945
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
5946
|
-
}
|
|
5947
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
5948
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
5949
|
-
this.persistExecution(execInfo);
|
|
5950
|
-
return result;
|
|
5951
|
-
} finally {
|
|
5952
|
-
this.abortControllers.delete(executionId);
|
|
5953
|
-
}
|
|
5954
|
-
}
|
|
7548
|
+
(span) => this.runWorkflowBody({
|
|
7549
|
+
workflow: workflow2,
|
|
7550
|
+
ctx: wfCtx,
|
|
7551
|
+
execInfo,
|
|
7552
|
+
validated,
|
|
7553
|
+
span
|
|
7554
|
+
})
|
|
5955
7555
|
);
|
|
5956
7556
|
};
|
|
5957
|
-
run().then((result) => axlStream._done(result)).catch((err) => {
|
|
5958
|
-
if (execInfo) {
|
|
7557
|
+
run().then((result) => axlStream._done(result, executionId)).catch((err) => {
|
|
7558
|
+
if (execInfo && execInfo.status === "running") {
|
|
5959
7559
|
execInfo.status = "failed";
|
|
5960
7560
|
execInfo.completedAt = Date.now();
|
|
5961
7561
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5962
7562
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5963
|
-
ctx?.log("workflow_end", {
|
|
5964
|
-
workflow: name,
|
|
5965
|
-
status: "failed",
|
|
5966
|
-
error: execInfo.error
|
|
5967
|
-
});
|
|
5968
7563
|
this.persistExecution(execInfo);
|
|
5969
7564
|
}
|
|
5970
|
-
|
|
7565
|
+
this.abortControllers.delete(executionId);
|
|
7566
|
+
axlStream._error(err instanceof Error ? err : new Error(String(err)), executionId);
|
|
5971
7567
|
});
|
|
5972
7568
|
return axlStream;
|
|
5973
7569
|
}
|
|
@@ -6028,6 +7624,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6028
7624
|
if (this.stateStore.saveEvalResult) {
|
|
6029
7625
|
await this.stateStore.saveEvalResult(entry);
|
|
6030
7626
|
}
|
|
7627
|
+
this.emit("eval_result", entry);
|
|
6031
7628
|
}
|
|
6032
7629
|
/**
|
|
6033
7630
|
* Delete an eval history entry by id. Removes from in-memory cache and
|
|
@@ -6159,7 +7756,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6159
7756
|
*
|
|
6160
7757
|
* @see Spec Section 13.5
|
|
6161
7758
|
*/
|
|
6162
|
-
async eval(config) {
|
|
7759
|
+
async eval(config, options) {
|
|
6163
7760
|
let runEvalFn;
|
|
6164
7761
|
try {
|
|
6165
7762
|
({ runEval: runEvalFn } = await import("@axlsdk/eval"));
|
|
@@ -6174,7 +7771,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6174
7771
|
});
|
|
6175
7772
|
return { output: result, cost, metadata };
|
|
6176
7773
|
};
|
|
6177
|
-
return runEvalFn(config, executeWorkflow, this);
|
|
7774
|
+
return runEvalFn(config, executeWorkflow, this, options);
|
|
6178
7775
|
}
|
|
6179
7776
|
/**
|
|
6180
7777
|
* Compare two evaluation results to detect regressions and improvements.
|
|
@@ -6212,9 +7809,31 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6212
7809
|
* Returns cost (same as `trackCost`) plus metadata extracted from trace events:
|
|
6213
7810
|
* models (unique URIs), tokens (input/output/reasoning sums), and agent call count.
|
|
6214
7811
|
*
|
|
7812
|
+
* ## Cost vs tokens semantics
|
|
7813
|
+
*
|
|
7814
|
+
* - `cost` is the full aggregate across EVERY event with a top-level
|
|
7815
|
+
* `event.cost` set: agent calls, tool calls, semantic memory ops, etc.
|
|
7816
|
+
* This is the number to reconcile against your provider bill.
|
|
7817
|
+
*
|
|
7818
|
+
* - `metadata.tokens` is narrowly scoped to **agent** prompt/completion/
|
|
7819
|
+
* reasoning tokens. Embedder tokens from semantic `ctx.remember({embed:true})`
|
|
7820
|
+
* / `ctx.recall({query})` are deliberately NOT summed here — they're a
|
|
7821
|
+
* different category (input-only, different pricing, different model).
|
|
7822
|
+
* Conflating them would make "prompt tokens" misleading in the UI. If you
|
|
7823
|
+
* need embedder token counts, subscribe to `runtime.on('trace', ...)` and
|
|
7824
|
+
* read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
|
|
7825
|
+
*
|
|
7826
|
+
* Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
|
|
7827
|
+
* during `fn()`. This is opt-in because it keeps every event in memory for the
|
|
7828
|
+
* duration of the call — useful for eval per-item capture, debugging, and test
|
|
7829
|
+
* assertions, but overhead grows with trace volume. When enabled, verbose-mode
|
|
7830
|
+
* `agent_call_start.data.messages` snapshots are omitted from captured events (still
|
|
7831
|
+
* broadcast via onTrace) to keep memory bounded — callers who need the full
|
|
7832
|
+
* verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
|
|
7833
|
+
*
|
|
6215
7834
|
* Works with both `createContext()` and `execute()` calls inside `fn`.
|
|
6216
7835
|
*/
|
|
6217
|
-
async trackExecution(fn) {
|
|
7836
|
+
async trackExecution(fn, options) {
|
|
6218
7837
|
const parentScope = costScopeStorage.getStore();
|
|
6219
7838
|
const scope = {
|
|
6220
7839
|
totalCost: 0,
|
|
@@ -6225,10 +7844,11 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6225
7844
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
6226
7845
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
6227
7846
|
let agentCalls = 0;
|
|
7847
|
+
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
6228
7848
|
const listener = (event) => {
|
|
6229
7849
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
6230
|
-
|
|
6231
|
-
if (event.type === "
|
|
7850
|
+
scope.totalCost += eventCostContribution(event);
|
|
7851
|
+
if (event.type === "agent_call_end") {
|
|
6232
7852
|
if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
|
|
6233
7853
|
agentCalls++;
|
|
6234
7854
|
if (event.tokens) {
|
|
@@ -6237,17 +7857,23 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6237
7857
|
tokens.reasoning += event.tokens.reasoning ?? 0;
|
|
6238
7858
|
}
|
|
6239
7859
|
}
|
|
6240
|
-
let workflowName;
|
|
6241
7860
|
if (event.type === "workflow_start" && event.workflow) {
|
|
6242
|
-
|
|
6243
|
-
} else if (event.type === "log" && event.data && typeof event.data === "object") {
|
|
6244
|
-
const d = event.data;
|
|
6245
|
-
if (d.event === "workflow_start" && typeof d.workflow === "string") {
|
|
6246
|
-
workflowName = d.workflow;
|
|
6247
|
-
}
|
|
7861
|
+
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
6248
7862
|
}
|
|
6249
|
-
if (
|
|
6250
|
-
|
|
7863
|
+
if (capturedTraces) {
|
|
7864
|
+
if (event.type === "token" || event.type === "partial_object") return;
|
|
7865
|
+
if (event.type === "agent_call_start" && event.data) {
|
|
7866
|
+
const d = event.data;
|
|
7867
|
+
if ("messages" in d) {
|
|
7868
|
+
const rest = {};
|
|
7869
|
+
for (const k of Object.keys(d)) {
|
|
7870
|
+
if (k !== "messages") rest[k] = d[k];
|
|
7871
|
+
}
|
|
7872
|
+
capturedTraces.push({ ...event, data: rest });
|
|
7873
|
+
return;
|
|
7874
|
+
}
|
|
7875
|
+
}
|
|
7876
|
+
capturedTraces.push(event);
|
|
6251
7877
|
}
|
|
6252
7878
|
};
|
|
6253
7879
|
this.setMaxListeners(this.getMaxListeners() + 1);
|
|
@@ -6257,6 +7883,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6257
7883
|
return {
|
|
6258
7884
|
result,
|
|
6259
7885
|
cost: scope.totalCost,
|
|
7886
|
+
...capturedTraces ? { traces: capturedTraces } : {},
|
|
6260
7887
|
metadata: {
|
|
6261
7888
|
models: [...modelCalls.keys()],
|
|
6262
7889
|
modelCallCounts: modelCalls.size > 0 ? Object.fromEntries(modelCalls) : void 0,
|
|
@@ -6266,6 +7893,16 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6266
7893
|
workflowCallCounts: workflowCalls.size > 0 ? Object.fromEntries(workflowCalls) : void 0
|
|
6267
7894
|
}
|
|
6268
7895
|
};
|
|
7896
|
+
} catch (err) {
|
|
7897
|
+
if (capturedTraces && typeof err === "object" && err !== null) {
|
|
7898
|
+
Object.defineProperty(err, "axlCapturedTraces", {
|
|
7899
|
+
value: capturedTraces,
|
|
7900
|
+
enumerable: false,
|
|
7901
|
+
writable: true,
|
|
7902
|
+
configurable: true
|
|
7903
|
+
});
|
|
7904
|
+
}
|
|
7905
|
+
throw err;
|
|
6269
7906
|
} finally {
|
|
6270
7907
|
this.off("trace", listener);
|
|
6271
7908
|
this.setMaxListeners(this.getMaxListeners() - 1);
|
|
@@ -6290,7 +7927,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6290
7927
|
* The emit('trace', event) call happens before this method is called, so
|
|
6291
7928
|
* programmatic subscribers always receive events regardless of trace config.
|
|
6292
7929
|
*/
|
|
6293
|
-
|
|
7930
|
+
outputAxlEvent(event) {
|
|
6294
7931
|
const traceConfig = this.config.trace;
|
|
6295
7932
|
if (!traceConfig?.enabled) return;
|
|
6296
7933
|
const level = traceConfig.level ?? "steps";
|
|
@@ -6314,18 +7951,20 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6314
7951
|
}
|
|
6315
7952
|
return;
|
|
6316
7953
|
}
|
|
6317
|
-
this.
|
|
7954
|
+
this.logAxlEvent(event);
|
|
6318
7955
|
}
|
|
6319
|
-
|
|
7956
|
+
logAxlEvent(event) {
|
|
6320
7957
|
const level = this.config.trace?.level ?? "steps";
|
|
6321
7958
|
const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
|
|
6322
7959
|
const parts = [`[axl] execution:${event.executionId}`];
|
|
6323
|
-
const data = event.data;
|
|
6324
7960
|
if (event.type === "workflow_start") {
|
|
6325
|
-
parts.push(`${workflowPrefix}
|
|
7961
|
+
parts.push(`${workflowPrefix}started`);
|
|
6326
7962
|
} else if (event.type === "workflow_end") {
|
|
6327
|
-
|
|
6328
|
-
|
|
7963
|
+
const d = event.data;
|
|
7964
|
+
const status = d?.aborted ? "aborted" : d?.status ?? "completed";
|
|
7965
|
+
parts.push(`${workflowPrefix}${status}`);
|
|
7966
|
+
if (event.duration != null) parts.push(`${(event.duration / 1e3).toFixed(1)}s`);
|
|
7967
|
+
} else if (event.type === "agent_call_end") {
|
|
6329
7968
|
parts.push(`${workflowPrefix}step:${event.step} agent_call`);
|
|
6330
7969
|
if (event.agent) parts.push(`agent:${event.agent}`);
|
|
6331
7970
|
if (event.promptVersion) parts.push(`version:${event.promptVersion}`);
|
|
@@ -6335,7 +7974,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6335
7974
|
if (level === "full" && event.data) {
|
|
6336
7975
|
parts.push(`data:${JSON.stringify(event.data)}`);
|
|
6337
7976
|
}
|
|
6338
|
-
} else if (event.type === "
|
|
7977
|
+
} else if (event.type === "tool_call_end") {
|
|
6339
7978
|
parts.push(`${workflowPrefix}step:${event.step} tool_call`);
|
|
6340
7979
|
if (event.tool) parts.push(`tool:${event.tool}`);
|
|
6341
7980
|
if (event.duration) parts.push(`${event.duration}ms`);
|
|
@@ -6352,8 +7991,9 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6352
7991
|
parts.push(`${workflowPrefix}log: ${JSON.stringify(event.data)}`);
|
|
6353
7992
|
} else {
|
|
6354
7993
|
parts.push(`${workflowPrefix}${event.type}`);
|
|
6355
|
-
|
|
6356
|
-
|
|
7994
|
+
const data = event.data;
|
|
7995
|
+
if (level === "full" && data !== void 0) {
|
|
7996
|
+
parts.push(`data:${JSON.stringify(data)}`);
|
|
6357
7997
|
}
|
|
6358
7998
|
}
|
|
6359
7999
|
console.log(parts.join(" | "));
|
|
@@ -6373,6 +8013,35 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
6373
8013
|
}).catch(() => {
|
|
6374
8014
|
});
|
|
6375
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
|
+
}
|
|
6376
8045
|
};
|
|
6377
8046
|
|
|
6378
8047
|
// src/state/redis.ts
|
|
@@ -6435,27 +8104,13 @@ var RedisStore = class _RedisStore {
|
|
|
6435
8104
|
return "axl:eval-history-ids";
|
|
6436
8105
|
}
|
|
6437
8106
|
// ── Checkpoints ──────────────────────────────────────────────────────
|
|
6438
|
-
async saveCheckpoint(executionId,
|
|
6439
|
-
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));
|
|
6440
8109
|
}
|
|
6441
|
-
async getCheckpoint(executionId,
|
|
6442
|
-
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);
|
|
6443
8112
|
return raw != null ? JSON.parse(raw) : null;
|
|
6444
8113
|
}
|
|
6445
|
-
async getLatestCheckpoint(executionId) {
|
|
6446
|
-
const all = await this.client.hGetAll(this.checkpointKey(executionId));
|
|
6447
|
-
if (!all || Object.keys(all).length === 0) return null;
|
|
6448
|
-
let maxStep = -1;
|
|
6449
|
-
let maxData = null;
|
|
6450
|
-
for (const [stepStr, raw] of Object.entries(all)) {
|
|
6451
|
-
const step = Number(stepStr);
|
|
6452
|
-
if (step > maxStep) {
|
|
6453
|
-
maxStep = step;
|
|
6454
|
-
maxData = JSON.parse(raw);
|
|
6455
|
-
}
|
|
6456
|
-
}
|
|
6457
|
-
return { step: maxStep, data: maxData };
|
|
6458
|
-
}
|
|
6459
8114
|
// ── Sessions ─────────────────────────────────────────────────────────
|
|
6460
8115
|
async saveSession(sessionId, history) {
|
|
6461
8116
|
await this.client.set(this.sessionKey(sessionId), JSON.stringify(history));
|
|
@@ -6560,6 +8215,16 @@ var RedisStore = class _RedisStore {
|
|
|
6560
8215
|
};
|
|
6561
8216
|
|
|
6562
8217
|
// src/memory/embedder-openai.ts
|
|
8218
|
+
var EMBEDDING_PRICE_PER_1M_TOKENS = {
|
|
8219
|
+
"text-embedding-3-small": 0.02,
|
|
8220
|
+
"text-embedding-3-large": 0.13,
|
|
8221
|
+
"text-embedding-ada-002": 0.1
|
|
8222
|
+
};
|
|
8223
|
+
function computeCost(model, tokens) {
|
|
8224
|
+
const pricePerMillion = EMBEDDING_PRICE_PER_1M_TOKENS[model];
|
|
8225
|
+
if (pricePerMillion == null) return void 0;
|
|
8226
|
+
return tokens / 1e6 * pricePerMillion;
|
|
8227
|
+
}
|
|
6563
8228
|
var OpenAIEmbedder = class {
|
|
6564
8229
|
apiKey;
|
|
6565
8230
|
model;
|
|
@@ -6574,8 +8239,8 @@ var OpenAIEmbedder = class {
|
|
|
6574
8239
|
this.baseUrl = options.baseUrl ?? "https://api.openai.com";
|
|
6575
8240
|
this.dimensions = options.dimensions ?? 1536;
|
|
6576
8241
|
}
|
|
6577
|
-
async embed(texts) {
|
|
6578
|
-
const response = await
|
|
8242
|
+
async embed(texts, signal) {
|
|
8243
|
+
const response = await fetchWithRetry(`${this.baseUrl}/v1/embeddings`, {
|
|
6579
8244
|
method: "POST",
|
|
6580
8245
|
headers: {
|
|
6581
8246
|
"Content-Type": "application/json",
|
|
@@ -6585,14 +8250,28 @@ var OpenAIEmbedder = class {
|
|
|
6585
8250
|
model: this.model,
|
|
6586
8251
|
input: texts,
|
|
6587
8252
|
dimensions: this.dimensions
|
|
6588
|
-
})
|
|
8253
|
+
}),
|
|
8254
|
+
signal
|
|
6589
8255
|
});
|
|
6590
8256
|
if (!response.ok) {
|
|
6591
8257
|
const body = await response.text();
|
|
6592
8258
|
throw new Error(`OpenAI embeddings API error (${response.status}): ${body}`);
|
|
6593
8259
|
}
|
|
6594
8260
|
const json = await response.json();
|
|
6595
|
-
|
|
8261
|
+
const vectors = json.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
8262
|
+
const tokens = json.usage?.prompt_tokens ?? json.usage?.total_tokens;
|
|
8263
|
+
const cost = tokens != null ? computeCost(this.model, tokens) : void 0;
|
|
8264
|
+
if (tokens == null && cost == null) {
|
|
8265
|
+
return { vectors };
|
|
8266
|
+
}
|
|
8267
|
+
return {
|
|
8268
|
+
vectors,
|
|
8269
|
+
usage: {
|
|
8270
|
+
...tokens != null ? { tokens } : {},
|
|
8271
|
+
...cost != null ? { cost } : {},
|
|
8272
|
+
model: this.model
|
|
8273
|
+
}
|
|
8274
|
+
};
|
|
6596
8275
|
}
|
|
6597
8276
|
};
|
|
6598
8277
|
|
|
@@ -6717,11 +8396,13 @@ function cosineSimilarity2(a, b) {
|
|
|
6717
8396
|
}
|
|
6718
8397
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6719
8398
|
0 && (module.exports = {
|
|
8399
|
+
AXL_EVENT_TYPES,
|
|
6720
8400
|
AnthropicProvider,
|
|
6721
8401
|
AxlError,
|
|
6722
8402
|
AxlRuntime,
|
|
6723
8403
|
AxlStream,
|
|
6724
8404
|
BudgetExceededError,
|
|
8405
|
+
COST_BEARING_LEAF_TYPES,
|
|
6725
8406
|
GeminiProvider,
|
|
6726
8407
|
GuardrailError,
|
|
6727
8408
|
InMemoryVectorStore,
|
|
@@ -6736,6 +8417,8 @@ function cosineSimilarity2(a, b) {
|
|
|
6736
8417
|
OpenAIResponsesProvider,
|
|
6737
8418
|
ProviderRegistry,
|
|
6738
8419
|
QuorumNotMet,
|
|
8420
|
+
REDACTED,
|
|
8421
|
+
REDACTION_RULES,
|
|
6739
8422
|
RedisStore,
|
|
6740
8423
|
SQLiteStore,
|
|
6741
8424
|
Session,
|
|
@@ -6748,7 +8431,12 @@ function cosineSimilarity2(a, b) {
|
|
|
6748
8431
|
agent,
|
|
6749
8432
|
createSpanManager,
|
|
6750
8433
|
defineConfig,
|
|
8434
|
+
eventCostContribution,
|
|
6751
8435
|
extractJson,
|
|
8436
|
+
isCostBearingLeaf,
|
|
8437
|
+
isRootLevel,
|
|
8438
|
+
parsePartialJson,
|
|
8439
|
+
redactEvent,
|
|
6752
8440
|
resolveThinkingOptions,
|
|
6753
8441
|
tool,
|
|
6754
8442
|
workflow,
|