@axlsdk/axl 0.15.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 +11 -7
- package/dist/index.cjs +1734 -627
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +941 -236
- package/dist/index.d.ts +941 -236
- package/dist/index.js +1725 -627
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1316,6 +1316,41 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
1316
1316
|
};
|
|
1317
1317
|
|
|
1318
1318
|
// src/providers/gemini.ts
|
|
1319
|
+
var GEMINI_DISALLOWED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
|
|
1320
|
+
"additionalProperties",
|
|
1321
|
+
"$schema",
|
|
1322
|
+
"$ref",
|
|
1323
|
+
"$defs",
|
|
1324
|
+
"definitions",
|
|
1325
|
+
"not",
|
|
1326
|
+
"allOf",
|
|
1327
|
+
"patternProperties",
|
|
1328
|
+
"unevaluatedProperties",
|
|
1329
|
+
"unevaluatedItems"
|
|
1330
|
+
]);
|
|
1331
|
+
function sanitizeSchemaForGemini(schema) {
|
|
1332
|
+
if (Array.isArray(schema)) {
|
|
1333
|
+
return schema.map((item) => sanitizeSchemaForGemini(item));
|
|
1334
|
+
}
|
|
1335
|
+
if (schema === null || typeof schema !== "object") {
|
|
1336
|
+
return schema;
|
|
1337
|
+
}
|
|
1338
|
+
const src = schema;
|
|
1339
|
+
const out = {};
|
|
1340
|
+
for (const [key, value] of Object.entries(src)) {
|
|
1341
|
+
if (GEMINI_DISALLOWED_SCHEMA_KEYS.has(key)) continue;
|
|
1342
|
+
if (key === "oneOf") {
|
|
1343
|
+
out.anyOf = sanitizeSchemaForGemini(value);
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (key === "const") {
|
|
1347
|
+
if (!("enum" in src)) out.enum = [value];
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
out[key] = sanitizeSchemaForGemini(value);
|
|
1351
|
+
}
|
|
1352
|
+
return out;
|
|
1353
|
+
}
|
|
1319
1354
|
var GEMINI_PRICING = {
|
|
1320
1355
|
"gemini-2.5-pro": [125e-8, 1e-5],
|
|
1321
1356
|
"gemini-2.5-flash": [3e-7, 25e-7],
|
|
@@ -1486,7 +1521,9 @@ var GeminiProvider = class {
|
|
|
1486
1521
|
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
1487
1522
|
generationConfig.responseMimeType = "application/json";
|
|
1488
1523
|
if (options.responseFormat.type === "json_schema" && options.responseFormat.json_schema?.schema) {
|
|
1489
|
-
generationConfig.responseSchema =
|
|
1524
|
+
generationConfig.responseSchema = sanitizeSchemaForGemini(
|
|
1525
|
+
options.responseFormat.json_schema.schema
|
|
1526
|
+
);
|
|
1490
1527
|
}
|
|
1491
1528
|
}
|
|
1492
1529
|
if (Object.keys(generationConfig).length > 0) {
|
|
@@ -1663,7 +1700,7 @@ var GeminiProvider = class {
|
|
|
1663
1700
|
return {
|
|
1664
1701
|
name: tool2.function.name,
|
|
1665
1702
|
description: tool2.function.description,
|
|
1666
|
-
parameters: tool2.function.parameters
|
|
1703
|
+
parameters: sanitizeSchemaForGemini(tool2.function.parameters)
|
|
1667
1704
|
};
|
|
1668
1705
|
}
|
|
1669
1706
|
// ---------------------------------------------------------------------------
|
|
@@ -1922,6 +1959,7 @@ var defaultRegistry = new ProviderRegistry();
|
|
|
1922
1959
|
|
|
1923
1960
|
// src/context.ts
|
|
1924
1961
|
import { AsyncLocalStorage } from "async_hooks";
|
|
1962
|
+
import { randomUUID } from "crypto";
|
|
1925
1963
|
import { z, ZodError } from "zod";
|
|
1926
1964
|
|
|
1927
1965
|
// src/errors.ts
|
|
@@ -2041,6 +2079,432 @@ var ToolDenied = class extends AxlError {
|
|
|
2041
2079
|
}
|
|
2042
2080
|
};
|
|
2043
2081
|
|
|
2082
|
+
// src/partial-json.ts
|
|
2083
|
+
var MAX_DEPTH = 256;
|
|
2084
|
+
function parsePartialJson(input) {
|
|
2085
|
+
const parser = new PartialParser(input);
|
|
2086
|
+
parser.skipWhitespace();
|
|
2087
|
+
if (parser.eof()) return void 0;
|
|
2088
|
+
const result = parser.parseValue();
|
|
2089
|
+
return result.value;
|
|
2090
|
+
}
|
|
2091
|
+
var PartialParser = class {
|
|
2092
|
+
constructor(src) {
|
|
2093
|
+
this.src = src;
|
|
2094
|
+
}
|
|
2095
|
+
pos = 0;
|
|
2096
|
+
depth = 0;
|
|
2097
|
+
eof() {
|
|
2098
|
+
return this.pos >= this.src.length;
|
|
2099
|
+
}
|
|
2100
|
+
peek() {
|
|
2101
|
+
return this.src[this.pos];
|
|
2102
|
+
}
|
|
2103
|
+
advance() {
|
|
2104
|
+
return this.src[this.pos++];
|
|
2105
|
+
}
|
|
2106
|
+
skipWhitespace() {
|
|
2107
|
+
while (!this.eof()) {
|
|
2108
|
+
const c = this.peek();
|
|
2109
|
+
if (c === " " || c === "\n" || c === " " || c === "\r") {
|
|
2110
|
+
this.pos++;
|
|
2111
|
+
} else {
|
|
2112
|
+
break;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
parseValue() {
|
|
2117
|
+
this.skipWhitespace();
|
|
2118
|
+
if (this.eof()) return { value: void 0, complete: false };
|
|
2119
|
+
const c = this.peek();
|
|
2120
|
+
if (c === "{" || c === "[") {
|
|
2121
|
+
if (++this.depth > MAX_DEPTH) {
|
|
2122
|
+
throw new SyntaxError(
|
|
2123
|
+
`Maximum nesting depth exceeded (${MAX_DEPTH}) at position ${this.pos}`
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
try {
|
|
2127
|
+
return c === "{" ? this.parseObject() : this.parseArray();
|
|
2128
|
+
} finally {
|
|
2129
|
+
this.depth--;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
if (c === '"') return this.parseString();
|
|
2133
|
+
if (c === "t" || c === "f") return this.parseBool();
|
|
2134
|
+
if (c === "n") return this.parseNull();
|
|
2135
|
+
if (c === "-" || c >= "0" && c <= "9") return this.parseNumber();
|
|
2136
|
+
throw new SyntaxError(`Unexpected character '${c}' at position ${this.pos}`);
|
|
2137
|
+
}
|
|
2138
|
+
parseObject() {
|
|
2139
|
+
this.advance();
|
|
2140
|
+
const obj = {};
|
|
2141
|
+
this.skipWhitespace();
|
|
2142
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2143
|
+
if (this.peek() === "}") {
|
|
2144
|
+
this.advance();
|
|
2145
|
+
return { value: obj, complete: true };
|
|
2146
|
+
}
|
|
2147
|
+
while (!this.eof()) {
|
|
2148
|
+
this.skipWhitespace();
|
|
2149
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2150
|
+
if (this.peek() === "}") {
|
|
2151
|
+
this.advance();
|
|
2152
|
+
return { value: obj, complete: true };
|
|
2153
|
+
}
|
|
2154
|
+
if (this.peek() !== '"') {
|
|
2155
|
+
throw new SyntaxError(`Expected '"' at position ${this.pos}, got '${this.peek()}'`);
|
|
2156
|
+
}
|
|
2157
|
+
const keyResult = this.parseString();
|
|
2158
|
+
if (!keyResult.complete) {
|
|
2159
|
+
return { value: obj, complete: false };
|
|
2160
|
+
}
|
|
2161
|
+
const key = keyResult.value;
|
|
2162
|
+
this.skipWhitespace();
|
|
2163
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2164
|
+
if (this.peek() !== ":") {
|
|
2165
|
+
return { value: obj, complete: false };
|
|
2166
|
+
}
|
|
2167
|
+
this.advance();
|
|
2168
|
+
this.skipWhitespace();
|
|
2169
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2170
|
+
const valueResult = this.parseValue();
|
|
2171
|
+
if (valueResult.value !== void 0 || valueResult.complete) {
|
|
2172
|
+
obj[key] = valueResult.value;
|
|
2173
|
+
}
|
|
2174
|
+
if (!valueResult.complete) {
|
|
2175
|
+
return { value: obj, complete: false };
|
|
2176
|
+
}
|
|
2177
|
+
this.skipWhitespace();
|
|
2178
|
+
if (this.eof()) return { value: obj, complete: false };
|
|
2179
|
+
const next = this.peek();
|
|
2180
|
+
if (next === ",") {
|
|
2181
|
+
this.advance();
|
|
2182
|
+
continue;
|
|
2183
|
+
}
|
|
2184
|
+
if (next === "}") {
|
|
2185
|
+
this.advance();
|
|
2186
|
+
return { value: obj, complete: true };
|
|
2187
|
+
}
|
|
2188
|
+
throw new SyntaxError(`Expected ',' or '}' at position ${this.pos}, got '${next}'`);
|
|
2189
|
+
}
|
|
2190
|
+
return { value: obj, complete: false };
|
|
2191
|
+
}
|
|
2192
|
+
parseArray() {
|
|
2193
|
+
this.advance();
|
|
2194
|
+
const arr = [];
|
|
2195
|
+
this.skipWhitespace();
|
|
2196
|
+
if (this.eof()) return { value: arr, complete: false };
|
|
2197
|
+
if (this.peek() === "]") {
|
|
2198
|
+
this.advance();
|
|
2199
|
+
return { value: arr, complete: true };
|
|
2200
|
+
}
|
|
2201
|
+
while (!this.eof()) {
|
|
2202
|
+
this.skipWhitespace();
|
|
2203
|
+
if (this.eof()) return { value: arr, complete: false };
|
|
2204
|
+
if (this.peek() === "]") {
|
|
2205
|
+
this.advance();
|
|
2206
|
+
return { value: arr, complete: true };
|
|
2207
|
+
}
|
|
2208
|
+
const elemResult = this.parseValue();
|
|
2209
|
+
if (elemResult.value !== void 0 || elemResult.complete) {
|
|
2210
|
+
arr.push(elemResult.value);
|
|
2211
|
+
}
|
|
2212
|
+
if (!elemResult.complete) {
|
|
2213
|
+
return { value: arr, complete: false };
|
|
2214
|
+
}
|
|
2215
|
+
this.skipWhitespace();
|
|
2216
|
+
if (this.eof()) return { value: arr, complete: false };
|
|
2217
|
+
const next = this.peek();
|
|
2218
|
+
if (next === ",") {
|
|
2219
|
+
this.advance();
|
|
2220
|
+
continue;
|
|
2221
|
+
}
|
|
2222
|
+
if (next === "]") {
|
|
2223
|
+
this.advance();
|
|
2224
|
+
return { value: arr, complete: true };
|
|
2225
|
+
}
|
|
2226
|
+
throw new SyntaxError(`Expected ',' or ']' at position ${this.pos}, got '${next}'`);
|
|
2227
|
+
}
|
|
2228
|
+
return { value: arr, complete: false };
|
|
2229
|
+
}
|
|
2230
|
+
parseString() {
|
|
2231
|
+
this.advance();
|
|
2232
|
+
let result = "";
|
|
2233
|
+
while (!this.eof()) {
|
|
2234
|
+
const c = this.advance();
|
|
2235
|
+
if (c === '"') return { value: result, complete: true };
|
|
2236
|
+
if (c === "\\") {
|
|
2237
|
+
if (this.eof()) {
|
|
2238
|
+
return { value: result, complete: false };
|
|
2239
|
+
}
|
|
2240
|
+
const esc = this.advance();
|
|
2241
|
+
switch (esc) {
|
|
2242
|
+
case '"':
|
|
2243
|
+
result += '"';
|
|
2244
|
+
break;
|
|
2245
|
+
case "\\":
|
|
2246
|
+
result += "\\";
|
|
2247
|
+
break;
|
|
2248
|
+
case "/":
|
|
2249
|
+
result += "/";
|
|
2250
|
+
break;
|
|
2251
|
+
case "b":
|
|
2252
|
+
result += "\b";
|
|
2253
|
+
break;
|
|
2254
|
+
case "f":
|
|
2255
|
+
result += "\f";
|
|
2256
|
+
break;
|
|
2257
|
+
case "n":
|
|
2258
|
+
result += "\n";
|
|
2259
|
+
break;
|
|
2260
|
+
case "r":
|
|
2261
|
+
result += "\r";
|
|
2262
|
+
break;
|
|
2263
|
+
case "t":
|
|
2264
|
+
result += " ";
|
|
2265
|
+
break;
|
|
2266
|
+
case "u": {
|
|
2267
|
+
if (this.pos + 4 > this.src.length) {
|
|
2268
|
+
return { value: result, complete: false };
|
|
2269
|
+
}
|
|
2270
|
+
const hex = this.src.slice(this.pos, this.pos + 4);
|
|
2271
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
2272
|
+
throw new SyntaxError(`Invalid \\u escape '${hex}' at position ${this.pos}`);
|
|
2273
|
+
}
|
|
2274
|
+
this.pos += 4;
|
|
2275
|
+
result += String.fromCharCode(parseInt(hex, 16));
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
2278
|
+
default:
|
|
2279
|
+
throw new SyntaxError(`Invalid escape '\\${esc}' at position ${this.pos - 1}`);
|
|
2280
|
+
}
|
|
2281
|
+
} else {
|
|
2282
|
+
result += c;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
return { value: result, complete: false };
|
|
2286
|
+
}
|
|
2287
|
+
parseNumber() {
|
|
2288
|
+
const start = this.pos;
|
|
2289
|
+
if (this.peek() === "-") this.advance();
|
|
2290
|
+
while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
|
|
2291
|
+
if (!this.eof() && this.peek() === ".") {
|
|
2292
|
+
this.advance();
|
|
2293
|
+
while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
|
|
2294
|
+
}
|
|
2295
|
+
if (!this.eof() && (this.peek() === "e" || this.peek() === "E")) {
|
|
2296
|
+
this.advance();
|
|
2297
|
+
if (!this.eof() && (this.peek() === "+" || this.peek() === "-")) this.advance();
|
|
2298
|
+
while (!this.eof() && this.peek() >= "0" && this.peek() <= "9") this.advance();
|
|
2299
|
+
}
|
|
2300
|
+
const text = this.src.slice(start, this.pos);
|
|
2301
|
+
if (text === "" || text === "-" || /\.$/.test(text) || /[eE][+-]?$/.test(text)) {
|
|
2302
|
+
return { value: void 0, complete: false };
|
|
2303
|
+
}
|
|
2304
|
+
const n = Number(text);
|
|
2305
|
+
if (Number.isNaN(n)) {
|
|
2306
|
+
return { value: void 0, complete: false };
|
|
2307
|
+
}
|
|
2308
|
+
return { value: n, complete: true };
|
|
2309
|
+
}
|
|
2310
|
+
parseBool() {
|
|
2311
|
+
const remaining = this.src.slice(this.pos);
|
|
2312
|
+
if (remaining.startsWith("true")) {
|
|
2313
|
+
this.pos += 4;
|
|
2314
|
+
return { value: true, complete: true };
|
|
2315
|
+
}
|
|
2316
|
+
if (remaining.startsWith("false")) {
|
|
2317
|
+
this.pos += 5;
|
|
2318
|
+
return { value: false, complete: true };
|
|
2319
|
+
}
|
|
2320
|
+
if ("true".startsWith(remaining) || "false".startsWith(remaining)) {
|
|
2321
|
+
this.pos = this.src.length;
|
|
2322
|
+
return { value: void 0, complete: false };
|
|
2323
|
+
}
|
|
2324
|
+
throw new SyntaxError(`Expected 'true' or 'false' at position ${this.pos}`);
|
|
2325
|
+
}
|
|
2326
|
+
parseNull() {
|
|
2327
|
+
const remaining = this.src.slice(this.pos);
|
|
2328
|
+
if (remaining.startsWith("null")) {
|
|
2329
|
+
this.pos += 4;
|
|
2330
|
+
return { value: null, complete: true };
|
|
2331
|
+
}
|
|
2332
|
+
if ("null".startsWith(remaining)) {
|
|
2333
|
+
this.pos = this.src.length;
|
|
2334
|
+
return { value: void 0, complete: false };
|
|
2335
|
+
}
|
|
2336
|
+
throw new SyntaxError(`Expected 'null' at position ${this.pos}`);
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
|
|
2340
|
+
// src/event-utils.ts
|
|
2341
|
+
var COST_BEARING_LEAF_TYPES = [
|
|
2342
|
+
"agent_call_end",
|
|
2343
|
+
"tool_call_end",
|
|
2344
|
+
"memory_remember",
|
|
2345
|
+
"memory_recall"
|
|
2346
|
+
];
|
|
2347
|
+
var COST_LEAF_SET = new Set(COST_BEARING_LEAF_TYPES);
|
|
2348
|
+
function eventCostContribution(event) {
|
|
2349
|
+
if (event.type === "ask_end") return 0;
|
|
2350
|
+
const c = event.cost;
|
|
2351
|
+
return typeof c === "number" && Number.isFinite(c) && c >= 0 ? c : 0;
|
|
2352
|
+
}
|
|
2353
|
+
function isCostBearingLeaf(event) {
|
|
2354
|
+
return COST_LEAF_SET.has(event.type);
|
|
2355
|
+
}
|
|
2356
|
+
function isRootLevel(event) {
|
|
2357
|
+
const d = event.depth;
|
|
2358
|
+
return (d ?? 0) === 0;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
// src/redaction.ts
|
|
2362
|
+
var REDACTED = "[redacted]";
|
|
2363
|
+
function walkObjectOneLevel(obj, preserveKeys) {
|
|
2364
|
+
const out = {};
|
|
2365
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2366
|
+
if (preserveKeys.has(k) || typeof v === "number" || typeof v === "boolean") {
|
|
2367
|
+
out[k] = v;
|
|
2368
|
+
} else if (typeof v === "string") {
|
|
2369
|
+
out[k] = REDACTED;
|
|
2370
|
+
} else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
2371
|
+
const inner = v;
|
|
2372
|
+
const innerOut = {};
|
|
2373
|
+
for (const [ik, iv] of Object.entries(inner)) {
|
|
2374
|
+
if (typeof iv === "number" || typeof iv === "boolean") {
|
|
2375
|
+
innerOut[ik] = iv;
|
|
2376
|
+
} else {
|
|
2377
|
+
innerOut[ik] = REDACTED;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
out[k] = innerOut;
|
|
2381
|
+
} else {
|
|
2382
|
+
out[k] = REDACTED;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return out;
|
|
2386
|
+
}
|
|
2387
|
+
var LOG_PRESERVE_KEYS = /* @__PURE__ */ new Set(["event"]);
|
|
2388
|
+
var MEMORY_PRESERVE_KEYS = /* @__PURE__ */ new Set(["scope"]);
|
|
2389
|
+
function redactGate(event) {
|
|
2390
|
+
const d = event.data;
|
|
2391
|
+
if (!d) return event;
|
|
2392
|
+
if (d.reason === void 0 && d.feedbackMessage === void 0) return event;
|
|
2393
|
+
return {
|
|
2394
|
+
...event,
|
|
2395
|
+
data: {
|
|
2396
|
+
...d,
|
|
2397
|
+
...d.reason !== void 0 ? { reason: REDACTED } : {},
|
|
2398
|
+
...d.feedbackMessage !== void 0 ? { feedbackMessage: REDACTED } : {}
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
function redactMemory(event) {
|
|
2403
|
+
const d = event.data;
|
|
2404
|
+
if (!d || typeof d !== "object") return event;
|
|
2405
|
+
return {
|
|
2406
|
+
...event,
|
|
2407
|
+
data: walkObjectOneLevel(d, MEMORY_PRESERVE_KEYS)
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
var passthrough = (e) => e;
|
|
2411
|
+
var REDACTION_RULES = {
|
|
2412
|
+
workflow_start: (e) => e.data.input !== void 0 ? { ...e, data: { ...e.data, input: REDACTED } } : e,
|
|
2413
|
+
workflow_end: (e) => {
|
|
2414
|
+
const d = e.data;
|
|
2415
|
+
if (d.result === void 0 && d.error === void 0) return e;
|
|
2416
|
+
return {
|
|
2417
|
+
...e,
|
|
2418
|
+
data: {
|
|
2419
|
+
...d,
|
|
2420
|
+
...d.result !== void 0 ? { result: REDACTED } : {},
|
|
2421
|
+
...d.error !== void 0 ? { error: REDACTED } : {}
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
2424
|
+
},
|
|
2425
|
+
ask_start: (e) => ({ ...e, prompt: REDACTED }),
|
|
2426
|
+
ask_end: (e) => ({
|
|
2427
|
+
...e,
|
|
2428
|
+
outcome: e.outcome.ok ? { ok: true, result: REDACTED } : { ok: false, error: REDACTED }
|
|
2429
|
+
}),
|
|
2430
|
+
agent_call_start: (e) => {
|
|
2431
|
+
const d = e.data;
|
|
2432
|
+
const out = { ...d, prompt: REDACTED };
|
|
2433
|
+
if (d.system !== void 0) out.system = REDACTED;
|
|
2434
|
+
if (Array.isArray(d.messages)) {
|
|
2435
|
+
out.messages = [{ role: "system", content: `[${d.messages.length} messages redacted]` }];
|
|
2436
|
+
}
|
|
2437
|
+
return { ...e, data: out };
|
|
2438
|
+
},
|
|
2439
|
+
agent_call_end: (e) => {
|
|
2440
|
+
const d = e.data;
|
|
2441
|
+
const out = { ...d, response: REDACTED };
|
|
2442
|
+
if (d.thinking !== void 0) out.thinking = REDACTED;
|
|
2443
|
+
if (d.error !== void 0) out.error = REDACTED;
|
|
2444
|
+
return { ...e, data: out };
|
|
2445
|
+
},
|
|
2446
|
+
token: (e) => ({ ...e, data: REDACTED }),
|
|
2447
|
+
tool_call_start: (e) => ({ ...e, data: { ...e.data, args: REDACTED } }),
|
|
2448
|
+
tool_call_end: (e) => ({ ...e, data: { ...e.data, args: REDACTED, result: REDACTED } }),
|
|
2449
|
+
tool_approval: (e) => ({
|
|
2450
|
+
...e,
|
|
2451
|
+
data: {
|
|
2452
|
+
...e.data,
|
|
2453
|
+
args: REDACTED,
|
|
2454
|
+
...e.data.reason !== void 0 ? { reason: REDACTED } : {}
|
|
2455
|
+
}
|
|
2456
|
+
}),
|
|
2457
|
+
tool_denied: (e) => {
|
|
2458
|
+
if (!e.data) return e;
|
|
2459
|
+
const d = e.data;
|
|
2460
|
+
if (d.args === void 0 && d.reason === void 0) return e;
|
|
2461
|
+
return {
|
|
2462
|
+
...e,
|
|
2463
|
+
data: {
|
|
2464
|
+
...d,
|
|
2465
|
+
...d.args !== void 0 ? { args: REDACTED } : {},
|
|
2466
|
+
...d.reason !== void 0 ? { reason: REDACTED } : {}
|
|
2467
|
+
}
|
|
2468
|
+
};
|
|
2469
|
+
},
|
|
2470
|
+
delegate: passthrough,
|
|
2471
|
+
handoff_start: (e) => e.data.message !== void 0 ? { ...e, data: { ...e.data, message: REDACTED } } : e,
|
|
2472
|
+
handoff_return: passthrough,
|
|
2473
|
+
pipeline: (e) => e.status === "failed" ? { ...e, reason: REDACTED } : e,
|
|
2474
|
+
partial_object: (e) => ({ ...e, data: { ...e.data, object: REDACTED } }),
|
|
2475
|
+
verify: (e) => e.data.lastError !== void 0 ? { ...e, data: { ...e.data, lastError: REDACTED } } : e,
|
|
2476
|
+
log: (e) => {
|
|
2477
|
+
if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
|
|
2478
|
+
return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
|
|
2479
|
+
},
|
|
2480
|
+
memory_remember: redactMemory,
|
|
2481
|
+
memory_recall: redactMemory,
|
|
2482
|
+
memory_forget: redactMemory,
|
|
2483
|
+
checkpoint_save: passthrough,
|
|
2484
|
+
checkpoint_replay: passthrough,
|
|
2485
|
+
await_human: (e) => e.data.prompt !== void 0 ? { ...e, data: { ...e.data, prompt: REDACTED } } : e,
|
|
2486
|
+
await_human_resolved: (e) => {
|
|
2487
|
+
const dec = e.data.decision;
|
|
2488
|
+
if (!dec) return e;
|
|
2489
|
+
const hasData = "data" in dec && dec.data !== void 0;
|
|
2490
|
+
const hasReason = "reason" in dec && dec.reason !== void 0;
|
|
2491
|
+
if (!hasData && !hasReason) return e;
|
|
2492
|
+
const scrubbed = { ...dec };
|
|
2493
|
+
if (hasData) scrubbed.data = REDACTED;
|
|
2494
|
+
if (hasReason) scrubbed.reason = REDACTED;
|
|
2495
|
+
return { ...e, data: { ...e.data, decision: scrubbed } };
|
|
2496
|
+
},
|
|
2497
|
+
guardrail: redactGate,
|
|
2498
|
+
schema_check: redactGate,
|
|
2499
|
+
validate: redactGate,
|
|
2500
|
+
done: (e) => ({ ...e, data: { result: REDACTED } }),
|
|
2501
|
+
error: (e) => ({ ...e, data: { ...e.data, message: REDACTED } })
|
|
2502
|
+
};
|
|
2503
|
+
function redactEvent(event) {
|
|
2504
|
+
const rule = REDACTION_RULES[event.type];
|
|
2505
|
+
return rule(event);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2044
2508
|
// src/config.ts
|
|
2045
2509
|
function defineConfig(config) {
|
|
2046
2510
|
return config;
|
|
@@ -2118,6 +2582,7 @@ function resolveConfig(config) {
|
|
|
2118
2582
|
|
|
2119
2583
|
// src/context.ts
|
|
2120
2584
|
var signalStorage = new AsyncLocalStorage();
|
|
2585
|
+
var askStorage = new AsyncLocalStorage();
|
|
2121
2586
|
function zodToJsonSchema(schema) {
|
|
2122
2587
|
const result = z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
2123
2588
|
delete result.$schema;
|
|
@@ -2219,8 +2684,34 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2219
2684
|
pendingDecisions;
|
|
2220
2685
|
budgetContext;
|
|
2221
2686
|
stateStore;
|
|
2222
|
-
|
|
2223
|
-
|
|
2687
|
+
/** Root step counter for this execution. Inherited by every ctx.ask()
|
|
2688
|
+
* frame so all events from this WorkflowContext (root + nested) share
|
|
2689
|
+
* a single monotonic counter, even when concurrent branch primitives
|
|
2690
|
+
* fire asks before any single parent ask exists. Per-instance so
|
|
2691
|
+
* separate executions don't cross-talk. */
|
|
2692
|
+
stepRefRoot = { value: 0 };
|
|
2693
|
+
/**
|
|
2694
|
+
* Auto-checkpoint counters for internal `_checkpoint` callers. Held by
|
|
2695
|
+
* reference so child contexts created via `createChildContext` share
|
|
2696
|
+
* the same number space — otherwise a tool handler's nested `ctx.ask()`
|
|
2697
|
+
* (which auto-checkpoints) would collide with the parent's
|
|
2698
|
+
* auto-checkpoints in the state store. See `WorkflowContextInit
|
|
2699
|
+
* .autoCheckpointCounters` for the per-agent vs root split.
|
|
2700
|
+
* User-facing `ctx.checkpoint(name, fn)` requires an explicit name and
|
|
2701
|
+
* does NOT touch these counters — it goes straight to the store under
|
|
2702
|
+
* the caller-supplied key.
|
|
2703
|
+
*/
|
|
2704
|
+
autoCheckpointCounters;
|
|
2705
|
+
/** Idempotency guards for `workflow_start` / `workflow_end` emission.
|
|
2706
|
+
* Both `runtime.execute()` and `runtime.stream()` have paths where a
|
|
2707
|
+
* post-emit side-effect (checkpoint deletion, state-store persistence)
|
|
2708
|
+
* can throw AFTER `_emitWorkflowEnd({status: 'completed'})` has already
|
|
2709
|
+
* fired — the outer catch would then fire `_emitWorkflowEnd({status:
|
|
2710
|
+
* 'failed'})` for a second time. These flags make both emitters
|
|
2711
|
+
* single-fire so consumers never see paired-then-conflicting terminal
|
|
2712
|
+
* events for one execution. */
|
|
2713
|
+
_workflowStartEmitted = false;
|
|
2714
|
+
_workflowEndEmitted = false;
|
|
2224
2715
|
signal;
|
|
2225
2716
|
summaryCache;
|
|
2226
2717
|
workflowName;
|
|
@@ -2232,7 +2723,6 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2232
2723
|
awaitHumanHandler;
|
|
2233
2724
|
onAgentStart;
|
|
2234
2725
|
onAgentCallComplete;
|
|
2235
|
-
parentToolCallId;
|
|
2236
2726
|
constructor(init) {
|
|
2237
2727
|
this.input = init.input;
|
|
2238
2728
|
this.executionId = init.executionId;
|
|
@@ -2256,7 +2746,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2256
2746
|
this.awaitHumanHandler = init.awaitHumanHandler;
|
|
2257
2747
|
this.onAgentStart = init.onAgentStart;
|
|
2258
2748
|
this.onAgentCallComplete = init.onAgentCallComplete;
|
|
2259
|
-
this.
|
|
2749
|
+
this.autoCheckpointCounters = init.autoCheckpointCounters ?? {
|
|
2750
|
+
byAgent: /* @__PURE__ */ new Map(),
|
|
2751
|
+
root: 0
|
|
2752
|
+
};
|
|
2260
2753
|
if (init.metadata?.summaryCache) {
|
|
2261
2754
|
this.summaryCache = init.metadata.summaryCache;
|
|
2262
2755
|
}
|
|
@@ -2265,15 +2758,23 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2265
2758
|
* Create a child context for nested agent invocations (e.g., agent-as-tool).
|
|
2266
2759
|
* Shares: budget tracking, abort signals, trace emission, provider registry,
|
|
2267
2760
|
* state store, span manager, memory manager, MCP manager, config,
|
|
2268
|
-
* awaitHuman handler, pending decisions, tool overrides
|
|
2269
|
-
*
|
|
2761
|
+
* awaitHuman handler, pending decisions, tool overrides, AND the
|
|
2762
|
+
* streaming callbacks (onToken / onAgentStart / onToolCall).
|
|
2763
|
+
* Isolates: session history.
|
|
2764
|
+
*
|
|
2765
|
+
* Streaming callbacks now propagate into nested asks because every callback
|
|
2766
|
+
* invocation carries `meta.askId`/`meta.parentAskId`/`meta.depth` so
|
|
2767
|
+
* consumers that want root-only behavior can filter on `meta.depth === 0`
|
|
2768
|
+
* instead of relying on the runtime to drop nested events. This is the
|
|
2769
|
+
* "nested ask visibility" fix from spec §3.2.
|
|
2270
2770
|
*
|
|
2271
|
-
*
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2274
|
-
*
|
|
2771
|
+
* The shared step counter lives in `askStorage` (ALS), so there is no
|
|
2772
|
+
* per-context counter to pass through anymore. Nested events are
|
|
2773
|
+
* correlated to their parent via `parentAskId` (set on every nested ask
|
|
2774
|
+
* by ALS frame allocation) — the deprecated `parentToolCallId` field
|
|
2775
|
+
* was removed in 0.16.0.
|
|
2275
2776
|
*/
|
|
2276
|
-
createChildContext(
|
|
2777
|
+
createChildContext() {
|
|
2277
2778
|
return new _WorkflowContext({
|
|
2278
2779
|
input: this.input,
|
|
2279
2780
|
executionId: this.executionId,
|
|
@@ -2287,18 +2788,20 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2287
2788
|
spanManager: this.spanManager,
|
|
2288
2789
|
memoryManager: this.memoryManager,
|
|
2289
2790
|
onTrace: this.onTrace,
|
|
2791
|
+
onToken: this.onToken,
|
|
2792
|
+
onToolCall: this.onToolCall,
|
|
2793
|
+
onAgentStart: this.onAgentStart,
|
|
2290
2794
|
onAgentCallComplete: this.onAgentCallComplete,
|
|
2291
2795
|
awaitHumanHandler: this.awaitHumanHandler,
|
|
2292
2796
|
pendingDecisions: this.pendingDecisions,
|
|
2293
2797
|
toolOverrides: this.toolOverrides,
|
|
2294
2798
|
signal: this.signal,
|
|
2295
2799
|
workflowName: this.workflowName,
|
|
2296
|
-
//
|
|
2297
|
-
// `
|
|
2298
|
-
// child
|
|
2299
|
-
|
|
2300
|
-
// Isolated: sessionHistory (empty)
|
|
2301
|
-
// onToken (null), onAgentStart (null), onToolCall (null)
|
|
2800
|
+
// Share the parent's auto-checkpoint counters so internal checkpoints
|
|
2801
|
+
// emitted from `ctx.ask` / spawn / race / parallel / map in this
|
|
2802
|
+
// child context don't collide with the parent's in the state store.
|
|
2803
|
+
autoCheckpointCounters: this.autoCheckpointCounters
|
|
2804
|
+
// Isolated: sessionHistory (empty)
|
|
2302
2805
|
});
|
|
2303
2806
|
}
|
|
2304
2807
|
/**
|
|
@@ -2309,72 +2812,150 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2309
2812
|
get currentSignal() {
|
|
2310
2813
|
return signalStorage.getStore() ?? this.signal;
|
|
2311
2814
|
}
|
|
2815
|
+
/**
|
|
2816
|
+
* Build a `CallbackMeta` for the current ask frame. Used at every
|
|
2817
|
+
* `onToken`/`onToolCall`/`onAgentStart` call site so consumers can
|
|
2818
|
+
* route or filter by ask correlation (`meta.depth === 0` for root
|
|
2819
|
+
* chat UIs, etc.).
|
|
2820
|
+
*
|
|
2821
|
+
* If the call is somehow outside an ask frame (e.g., a programmatic
|
|
2822
|
+
* `tool.run()` test harness), falls back to a synthetic meta keyed off
|
|
2823
|
+
* `executionId` so the type contract holds — consumers see `depth: 0`
|
|
2824
|
+
* and can ignore those events.
|
|
2825
|
+
*/
|
|
2826
|
+
currentCallbackMeta(agentName) {
|
|
2827
|
+
const frame = askStorage.getStore();
|
|
2828
|
+
if (frame) {
|
|
2829
|
+
return {
|
|
2830
|
+
askId: frame.askId,
|
|
2831
|
+
...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
|
|
2832
|
+
depth: frame.depth,
|
|
2833
|
+
agent: agentName
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2836
|
+
return { askId: this.executionId, depth: 0, agent: agentName };
|
|
2837
|
+
}
|
|
2312
2838
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
2313
2839
|
async ask(agent2, prompt, options) {
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
const
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
"axl.agent.completion_tokens",
|
|
2344
|
-
usageCapture.value.completion_tokens
|
|
2840
|
+
const agentName = agent2._name;
|
|
2841
|
+
return this._checkpoint(
|
|
2842
|
+
this._autoCheckpointName("ask", agentName),
|
|
2843
|
+
async () => {
|
|
2844
|
+
const parentFrame = askStorage.getStore();
|
|
2845
|
+
const askId = randomUUID();
|
|
2846
|
+
const depth = (parentFrame?.depth ?? -1) + 1;
|
|
2847
|
+
const stepRef = parentFrame?.stepRef ?? this.stepRefRoot;
|
|
2848
|
+
const frame = {
|
|
2849
|
+
askId,
|
|
2850
|
+
parentAskId: parentFrame?.askId,
|
|
2851
|
+
depth,
|
|
2852
|
+
agent: agent2._name,
|
|
2853
|
+
stepRef,
|
|
2854
|
+
askCost: { value: 0 }
|
|
2855
|
+
};
|
|
2856
|
+
return askStorage.run(frame, async () => {
|
|
2857
|
+
const askStart = Date.now();
|
|
2858
|
+
this.emitEvent({ type: "ask_start", prompt });
|
|
2859
|
+
const costBefore = this.budgetContext?.totalCost ?? 0;
|
|
2860
|
+
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
2861
|
+
const usageCapture = {};
|
|
2862
|
+
const doCall = async () => {
|
|
2863
|
+
const result = await this.executeAgentCall(
|
|
2864
|
+
agent2,
|
|
2865
|
+
prompt,
|
|
2866
|
+
options,
|
|
2867
|
+
void 0,
|
|
2868
|
+
usageCapture
|
|
2345
2869
|
);
|
|
2346
|
-
|
|
2347
|
-
|
|
2870
|
+
return result;
|
|
2871
|
+
};
|
|
2872
|
+
let outcome;
|
|
2873
|
+
try {
|
|
2874
|
+
const result = this.spanManager ? await this.spanManager.withSpanAsync(
|
|
2875
|
+
"axl.agent.ask",
|
|
2876
|
+
{
|
|
2877
|
+
"axl.agent.name": agent2._name,
|
|
2878
|
+
"axl.agent.model": agent2.resolveModel(resolveCtx)
|
|
2879
|
+
},
|
|
2880
|
+
async (span) => {
|
|
2881
|
+
const r = await doCall();
|
|
2882
|
+
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
2883
|
+
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
2884
|
+
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
2885
|
+
if (usageCapture.value) {
|
|
2886
|
+
span.setAttribute(
|
|
2887
|
+
"axl.agent.prompt_tokens",
|
|
2888
|
+
usageCapture.value.prompt_tokens
|
|
2889
|
+
);
|
|
2890
|
+
span.setAttribute(
|
|
2891
|
+
"axl.agent.completion_tokens",
|
|
2892
|
+
usageCapture.value.completion_tokens
|
|
2893
|
+
);
|
|
2894
|
+
if (usageCapture.value.cached_tokens)
|
|
2895
|
+
span.setAttribute(
|
|
2896
|
+
"axl.agent.cached_tokens",
|
|
2897
|
+
usageCapture.value.cached_tokens
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
return r;
|
|
2901
|
+
}
|
|
2902
|
+
) : await doCall();
|
|
2903
|
+
outcome = { ok: true, result };
|
|
2904
|
+
const costAfter = this.budgetContext?.totalCost ?? 0;
|
|
2905
|
+
if (this.onAgentCallComplete) {
|
|
2906
|
+
try {
|
|
2907
|
+
this.onAgentCallComplete({
|
|
2908
|
+
agent: agent2._name,
|
|
2909
|
+
prompt,
|
|
2910
|
+
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
2911
|
+
model: agent2.resolveModel(resolveCtx),
|
|
2912
|
+
cost: costAfter - costBefore,
|
|
2913
|
+
duration: Date.now() - askStart,
|
|
2914
|
+
promptVersion: agent2._config.version,
|
|
2915
|
+
temperature: options?.temperature ?? agent2._config.temperature,
|
|
2916
|
+
maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
|
|
2917
|
+
effort: options?.effort ?? agent2._config.effort,
|
|
2918
|
+
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
2919
|
+
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
2920
|
+
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
2921
|
+
stop: options?.stop ?? agent2._config.stop,
|
|
2922
|
+
providerOptions: options?.providerOptions ?? agent2._config.providerOptions
|
|
2923
|
+
});
|
|
2924
|
+
} catch (hookErr) {
|
|
2925
|
+
console.error(
|
|
2926
|
+
"[axl] onAgentCallComplete hook threw; ask outcome unchanged:",
|
|
2927
|
+
hookErr instanceof Error ? hookErr.message : String(hookErr)
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
return result;
|
|
2932
|
+
} catch (err) {
|
|
2933
|
+
outcome = {
|
|
2934
|
+
ok: false,
|
|
2935
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2936
|
+
};
|
|
2937
|
+
throw err;
|
|
2938
|
+
} finally {
|
|
2939
|
+
this.emitEvent({
|
|
2940
|
+
type: "ask_end",
|
|
2941
|
+
outcome: outcome ?? {
|
|
2942
|
+
ok: false,
|
|
2943
|
+
error: "ask_end emitted without outcome \u2014 internal bug"
|
|
2944
|
+
},
|
|
2945
|
+
cost: frame.askCost.value,
|
|
2946
|
+
duration: Date.now() - askStart
|
|
2947
|
+
});
|
|
2348
2948
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
this.onAgentCallComplete?.({
|
|
2354
|
-
agent: agent2._name,
|
|
2355
|
-
prompt,
|
|
2356
|
-
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
2357
|
-
model: agent2.resolveModel(resolveCtx),
|
|
2358
|
-
cost: costAfter - costBefore,
|
|
2359
|
-
duration: Date.now() - startTime,
|
|
2360
|
-
promptVersion: agent2._config.version,
|
|
2361
|
-
temperature: options?.temperature ?? agent2._config.temperature,
|
|
2362
|
-
maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
|
|
2363
|
-
effort: options?.effort ?? agent2._config.effort,
|
|
2364
|
-
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
2365
|
-
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
2366
|
-
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
2367
|
-
stop: options?.stop ?? agent2._config.stop,
|
|
2368
|
-
providerOptions: options?.providerOptions ?? agent2._config.providerOptions
|
|
2369
|
-
});
|
|
2370
|
-
return result;
|
|
2371
|
-
});
|
|
2949
|
+
});
|
|
2950
|
+
},
|
|
2951
|
+
{ agent: agentName }
|
|
2952
|
+
);
|
|
2372
2953
|
}
|
|
2373
2954
|
async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
|
|
2374
2955
|
if (this.budgetContext?.exceeded) {
|
|
2375
2956
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
2376
2957
|
if (policy === "warn") {
|
|
2377
|
-
this.
|
|
2958
|
+
this.emitEvent({
|
|
2378
2959
|
type: "log",
|
|
2379
2960
|
data: { warning: "Budget exceeded", limit, spent, policy }
|
|
2380
2961
|
});
|
|
@@ -2463,7 +3044,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2463
3044
|
const guardrails = agent2._config.guardrails;
|
|
2464
3045
|
if (guardrails?.input) {
|
|
2465
3046
|
const inputResult = await guardrails.input(prompt, { metadata: this.metadata });
|
|
2466
|
-
this.
|
|
3047
|
+
this.emitEvent({
|
|
2467
3048
|
type: "guardrail",
|
|
2468
3049
|
agent: agent2._name,
|
|
2469
3050
|
data: {
|
|
@@ -2497,12 +3078,6 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2497
3078
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
2498
3079
|
const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
|
|
2499
3080
|
const startTime = Date.now();
|
|
2500
|
-
if (this.onToken && options?.validate) {
|
|
2501
|
-
throw new AxlError(
|
|
2502
|
-
"INVALID_CONFIG",
|
|
2503
|
-
"Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
|
|
2504
|
-
);
|
|
2505
|
-
}
|
|
2506
3081
|
const currentMessages = [...messages];
|
|
2507
3082
|
let turns = 0;
|
|
2508
3083
|
let guardrailOutputRetries = 0;
|
|
@@ -2511,11 +3086,42 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2511
3086
|
const maxGuardrailRetries = guardrails?.maxRetries ?? 2;
|
|
2512
3087
|
let pendingRetryReason;
|
|
2513
3088
|
const verboseTrace = this.config.trace?.level === "full";
|
|
3089
|
+
let lastStartStage = "initial";
|
|
3090
|
+
let lastStartAttempt = 1;
|
|
3091
|
+
let lastStartMaxAttempts = 1;
|
|
2514
3092
|
while (turns < maxTurns) {
|
|
2515
3093
|
if (Date.now() - startTime > timeoutMs) {
|
|
2516
3094
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
2517
3095
|
}
|
|
2518
3096
|
turns++;
|
|
3097
|
+
const isFirstTurn = turns === 1;
|
|
3098
|
+
const isRetryTurn = pendingRetryReason !== void 0;
|
|
3099
|
+
if (isFirstTurn || isRetryTurn) {
|
|
3100
|
+
const stage = pendingRetryReason ?? "initial";
|
|
3101
|
+
let pipelineAttempt = 1;
|
|
3102
|
+
let pipelineMaxAttempts = 1;
|
|
3103
|
+
if (stage === "guardrail") {
|
|
3104
|
+
pipelineAttempt = guardrailOutputRetries + 1;
|
|
3105
|
+
pipelineMaxAttempts = maxGuardrailRetries + 1;
|
|
3106
|
+
} else if (stage === "schema") {
|
|
3107
|
+
pipelineAttempt = schemaRetries + 1;
|
|
3108
|
+
pipelineMaxAttempts = (options?.retries ?? 3) + 1;
|
|
3109
|
+
} else if (stage === "validate") {
|
|
3110
|
+
pipelineAttempt = validateRetries + 1;
|
|
3111
|
+
pipelineMaxAttempts = (options?.validateRetries ?? 2) + 1;
|
|
3112
|
+
}
|
|
3113
|
+
lastStartStage = stage;
|
|
3114
|
+
lastStartAttempt = pipelineAttempt;
|
|
3115
|
+
lastStartMaxAttempts = pipelineMaxAttempts;
|
|
3116
|
+
this.emitEvent({
|
|
3117
|
+
type: "pipeline",
|
|
3118
|
+
agent: agent2._name,
|
|
3119
|
+
status: "start",
|
|
3120
|
+
stage,
|
|
3121
|
+
attempt: pipelineAttempt,
|
|
3122
|
+
maxAttempts: pipelineMaxAttempts
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
2519
3125
|
const turnStart = Date.now();
|
|
2520
3126
|
const chatOptions = {
|
|
2521
3127
|
model,
|
|
@@ -2533,72 +3139,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2533
3139
|
if (options?.schema && toolDefs.length === 0) {
|
|
2534
3140
|
chatOptions.responseFormat = { type: "json_object" };
|
|
2535
3141
|
}
|
|
2536
|
-
this.
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
content2 += chunk.content;
|
|
2547
|
-
this.onToken(chunk.content);
|
|
2548
|
-
} else if (chunk.type === "thinking_delta") {
|
|
2549
|
-
thinkingContent += chunk.content;
|
|
2550
|
-
} else if (chunk.type === "tool_call_delta") {
|
|
2551
|
-
let buffer = toolCallBuffers.get(chunk.id);
|
|
2552
|
-
if (!buffer) {
|
|
2553
|
-
buffer = { id: chunk.id, name: "", arguments: "" };
|
|
2554
|
-
toolCallBuffers.set(chunk.id, buffer);
|
|
2555
|
-
}
|
|
2556
|
-
if (chunk.name) buffer.name = chunk.name;
|
|
2557
|
-
if (chunk.arguments) buffer.arguments += chunk.arguments;
|
|
2558
|
-
} else if (chunk.type === "done") {
|
|
2559
|
-
streamProviderMetadata = chunk.providerMetadata;
|
|
2560
|
-
if (chunk.usage) {
|
|
2561
|
-
response = {
|
|
2562
|
-
content: content2,
|
|
2563
|
-
tool_calls: void 0,
|
|
2564
|
-
usage: chunk.usage,
|
|
2565
|
-
cost: chunk.cost
|
|
2566
|
-
};
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
for (const buffer of toolCallBuffers.values()) {
|
|
2571
|
-
toolCalls.push({
|
|
2572
|
-
id: buffer.id,
|
|
2573
|
-
type: "function",
|
|
2574
|
-
function: {
|
|
2575
|
-
name: buffer.name,
|
|
2576
|
-
arguments: buffer.arguments
|
|
2577
|
-
}
|
|
2578
|
-
});
|
|
2579
|
-
}
|
|
2580
|
-
response ??= {
|
|
2581
|
-
content: content2,
|
|
2582
|
-
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
2583
|
-
};
|
|
2584
|
-
if (toolCalls.length > 0) {
|
|
2585
|
-
response.tool_calls = toolCalls;
|
|
2586
|
-
}
|
|
2587
|
-
if (streamProviderMetadata) {
|
|
2588
|
-
response.providerMetadata = streamProviderMetadata;
|
|
2589
|
-
}
|
|
2590
|
-
if (thinkingContent) {
|
|
2591
|
-
response.thinking_content = thinkingContent;
|
|
2592
|
-
}
|
|
2593
|
-
} else {
|
|
2594
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
2595
|
-
}
|
|
2596
|
-
if (usageCapture && response.usage) {
|
|
2597
|
-
usageCapture.value = response.usage;
|
|
2598
|
-
}
|
|
2599
|
-
if (response.cost) {
|
|
2600
|
-
this._accumulateBudgetCost(response.cost);
|
|
2601
|
-
}
|
|
3142
|
+
const callbackMeta = this.currentCallbackMeta(agent2._name);
|
|
3143
|
+
const callParams = {
|
|
3144
|
+
...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
|
|
3145
|
+
maxTokens: chatOptions.maxTokens,
|
|
3146
|
+
...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
|
|
3147
|
+
...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
|
|
3148
|
+
...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
|
|
3149
|
+
...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
|
|
3150
|
+
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
3151
|
+
};
|
|
2602
3152
|
let messagesSnapshot;
|
|
2603
3153
|
if (verboseTrace) {
|
|
2604
3154
|
try {
|
|
@@ -2613,8 +3163,148 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2613
3163
|
}
|
|
2614
3164
|
const retryReason = pendingRetryReason;
|
|
2615
3165
|
pendingRetryReason = void 0;
|
|
2616
|
-
this.
|
|
2617
|
-
type: "
|
|
3166
|
+
this.emitEvent({
|
|
3167
|
+
type: "agent_call_start",
|
|
3168
|
+
agent: agent2._name,
|
|
3169
|
+
model: modelUri,
|
|
3170
|
+
turn: turns,
|
|
3171
|
+
data: {
|
|
3172
|
+
prompt,
|
|
3173
|
+
...systemPrompt ? { system: systemPrompt } : {},
|
|
3174
|
+
params: callParams,
|
|
3175
|
+
turn: turns,
|
|
3176
|
+
...retryReason ? { retryReason } : {},
|
|
3177
|
+
...toolDefs.length > 0 ? { toolNames: toolDefs.map((t) => t.function.name) } : {},
|
|
3178
|
+
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
3179
|
+
}
|
|
3180
|
+
});
|
|
3181
|
+
this.onAgentStart?.({ agent: agent2._name, model: modelUri }, callbackMeta);
|
|
3182
|
+
let response;
|
|
3183
|
+
try {
|
|
3184
|
+
if (this.onToken) {
|
|
3185
|
+
let content2 = "";
|
|
3186
|
+
const toolCalls = [];
|
|
3187
|
+
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
3188
|
+
let streamProviderMetadata;
|
|
3189
|
+
let thinkingContent = "";
|
|
3190
|
+
const partialObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof z.ZodObject;
|
|
3191
|
+
const currentAttempt = schemaRetries + 1;
|
|
3192
|
+
let inString = false;
|
|
3193
|
+
let escaped = false;
|
|
3194
|
+
let boundaryPending = false;
|
|
3195
|
+
for await (const chunk of provider.stream(currentMessages, chatOptions)) {
|
|
3196
|
+
if (chunk.type === "text_delta") {
|
|
3197
|
+
content2 += chunk.content;
|
|
3198
|
+
this.emitEvent({ type: "token", data: chunk.content });
|
|
3199
|
+
this.onToken(chunk.content, callbackMeta);
|
|
3200
|
+
if (partialObjectEnabled) {
|
|
3201
|
+
for (const ch of chunk.content) {
|
|
3202
|
+
if (escaped) {
|
|
3203
|
+
escaped = false;
|
|
3204
|
+
continue;
|
|
3205
|
+
}
|
|
3206
|
+
if (ch === "\\") {
|
|
3207
|
+
escaped = true;
|
|
3208
|
+
continue;
|
|
3209
|
+
}
|
|
3210
|
+
if (ch === '"') {
|
|
3211
|
+
inString = !inString;
|
|
3212
|
+
continue;
|
|
3213
|
+
}
|
|
3214
|
+
if (!inString && (ch === "," || ch === "}" || ch === "]")) {
|
|
3215
|
+
boundaryPending = true;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (boundaryPending) {
|
|
3219
|
+
boundaryPending = false;
|
|
3220
|
+
let parsed;
|
|
3221
|
+
try {
|
|
3222
|
+
parsed = parsePartialJson(extractJson(content2));
|
|
3223
|
+
} catch {
|
|
3224
|
+
parsed = void 0;
|
|
3225
|
+
}
|
|
3226
|
+
if (parsed !== void 0) {
|
|
3227
|
+
this.emitEvent({
|
|
3228
|
+
type: "partial_object",
|
|
3229
|
+
agent: agent2._name,
|
|
3230
|
+
attempt: currentAttempt,
|
|
3231
|
+
data: { object: parsed }
|
|
3232
|
+
});
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
} else if (chunk.type === "thinking_delta") {
|
|
3237
|
+
thinkingContent += chunk.content;
|
|
3238
|
+
} else if (chunk.type === "tool_call_delta") {
|
|
3239
|
+
let buffer = toolCallBuffers.get(chunk.id);
|
|
3240
|
+
if (!buffer) {
|
|
3241
|
+
buffer = { id: chunk.id, name: "", arguments: "" };
|
|
3242
|
+
toolCallBuffers.set(chunk.id, buffer);
|
|
3243
|
+
}
|
|
3244
|
+
if (chunk.name) buffer.name = chunk.name;
|
|
3245
|
+
if (chunk.arguments) buffer.arguments += chunk.arguments;
|
|
3246
|
+
} else if (chunk.type === "done") {
|
|
3247
|
+
streamProviderMetadata = chunk.providerMetadata;
|
|
3248
|
+
if (chunk.usage) {
|
|
3249
|
+
response = {
|
|
3250
|
+
content: content2,
|
|
3251
|
+
tool_calls: void 0,
|
|
3252
|
+
usage: chunk.usage,
|
|
3253
|
+
cost: chunk.cost
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
for (const buffer of toolCallBuffers.values()) {
|
|
3259
|
+
toolCalls.push({
|
|
3260
|
+
id: buffer.id,
|
|
3261
|
+
type: "function",
|
|
3262
|
+
function: {
|
|
3263
|
+
name: buffer.name,
|
|
3264
|
+
arguments: buffer.arguments
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
response ??= {
|
|
3269
|
+
content: content2,
|
|
3270
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
3271
|
+
};
|
|
3272
|
+
if (toolCalls.length > 0) {
|
|
3273
|
+
response.tool_calls = toolCalls;
|
|
3274
|
+
}
|
|
3275
|
+
if (streamProviderMetadata) {
|
|
3276
|
+
response.providerMetadata = streamProviderMetadata;
|
|
3277
|
+
}
|
|
3278
|
+
if (thinkingContent) {
|
|
3279
|
+
response.thinking_content = thinkingContent;
|
|
3280
|
+
}
|
|
3281
|
+
} else {
|
|
3282
|
+
response = await provider.chat(currentMessages, chatOptions);
|
|
3283
|
+
}
|
|
3284
|
+
} catch (err) {
|
|
3285
|
+
this.emitEvent({
|
|
3286
|
+
type: "agent_call_end",
|
|
3287
|
+
agent: agent2._name,
|
|
3288
|
+
model: modelUri,
|
|
3289
|
+
promptVersion: agent2._config.version,
|
|
3290
|
+
duration: Date.now() - turnStart,
|
|
3291
|
+
data: {
|
|
3292
|
+
response: "",
|
|
3293
|
+
turn: turns,
|
|
3294
|
+
...retryReason ? { retryReason } : {},
|
|
3295
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
throw err;
|
|
3299
|
+
}
|
|
3300
|
+
if (usageCapture && response.usage) {
|
|
3301
|
+
usageCapture.value = response.usage;
|
|
3302
|
+
}
|
|
3303
|
+
if (response.cost) {
|
|
3304
|
+
this._accumulateBudgetCost(response.cost);
|
|
3305
|
+
}
|
|
3306
|
+
this.emitEvent({
|
|
3307
|
+
type: "agent_call_end",
|
|
2618
3308
|
agent: agent2._name,
|
|
2619
3309
|
model: modelUri,
|
|
2620
3310
|
promptVersion: agent2._config.version,
|
|
@@ -2626,22 +3316,10 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2626
3316
|
} : void 0,
|
|
2627
3317
|
duration: Date.now() - turnStart,
|
|
2628
3318
|
data: {
|
|
2629
|
-
prompt,
|
|
2630
3319
|
response: response.content,
|
|
2631
|
-
...systemPrompt ? { system: systemPrompt } : {},
|
|
2632
3320
|
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
2633
|
-
params: {
|
|
2634
|
-
...chatOptions.temperature !== void 0 ? { temperature: chatOptions.temperature } : {},
|
|
2635
|
-
maxTokens: chatOptions.maxTokens,
|
|
2636
|
-
...chatOptions.effort !== void 0 ? { effort: chatOptions.effort } : {},
|
|
2637
|
-
...chatOptions.thinkingBudget !== void 0 ? { thinkingBudget: chatOptions.thinkingBudget } : {},
|
|
2638
|
-
...chatOptions.includeThoughts !== void 0 ? { includeThoughts: chatOptions.includeThoughts } : {},
|
|
2639
|
-
...chatOptions.toolChoice !== void 0 ? { toolChoice: chatOptions.toolChoice } : {},
|
|
2640
|
-
...chatOptions.stop !== void 0 ? { stop: chatOptions.stop } : {}
|
|
2641
|
-
},
|
|
2642
3321
|
turn: turns,
|
|
2643
|
-
...retryReason ? { retryReason } : {}
|
|
2644
|
-
...messagesSnapshot ? { messages: messagesSnapshot } : {}
|
|
3322
|
+
...retryReason ? { retryReason } : {}
|
|
2645
3323
|
}
|
|
2646
3324
|
});
|
|
2647
3325
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
@@ -2667,6 +3345,19 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2667
3345
|
}
|
|
2668
3346
|
}
|
|
2669
3347
|
const handoffStart = Date.now();
|
|
3348
|
+
const sourceFrame = askStorage.getStore();
|
|
3349
|
+
const handoffFromAskId = sourceFrame?.askId ?? this.executionId;
|
|
3350
|
+
const handoffSourceDepth = sourceFrame?.depth ?? 0;
|
|
3351
|
+
const handoffToAskId = randomUUID();
|
|
3352
|
+
const handoffTargetDepth = handoffSourceDepth + 1;
|
|
3353
|
+
const targetFrame = {
|
|
3354
|
+
askId: handoffToAskId,
|
|
3355
|
+
parentAskId: handoffFromAskId,
|
|
3356
|
+
depth: handoffTargetDepth,
|
|
3357
|
+
agent: descriptor.agent._name,
|
|
3358
|
+
stepRef: sourceFrame?.stepRef ?? this.stepRefRoot,
|
|
3359
|
+
askCost: { value: 0 }
|
|
3360
|
+
};
|
|
2670
3361
|
const handoffOptions = options ? {
|
|
2671
3362
|
schema: options.schema,
|
|
2672
3363
|
retries: options.retries,
|
|
@@ -2674,13 +3365,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2674
3365
|
validate: options.validate,
|
|
2675
3366
|
validateRetries: options.validateRetries
|
|
2676
3367
|
} : void 0;
|
|
2677
|
-
const handoffFn = () =>
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3368
|
+
const handoffFn = () => askStorage.run(targetFrame, async () => {
|
|
3369
|
+
this.emitEvent({ type: "ask_start", prompt: handoffPrompt });
|
|
3370
|
+
const targetAskStart = Date.now();
|
|
3371
|
+
try {
|
|
3372
|
+
const result = await this.executeAgentCall(
|
|
3373
|
+
descriptor.agent,
|
|
3374
|
+
handoffPrompt,
|
|
3375
|
+
handoffOptions,
|
|
3376
|
+
currentMessages,
|
|
3377
|
+
usageCapture
|
|
3378
|
+
);
|
|
3379
|
+
this.emitEvent({
|
|
3380
|
+
type: "ask_end",
|
|
3381
|
+
outcome: { ok: true, result },
|
|
3382
|
+
cost: targetFrame.askCost.value,
|
|
3383
|
+
duration: Date.now() - targetAskStart
|
|
3384
|
+
});
|
|
3385
|
+
return result;
|
|
3386
|
+
} catch (err) {
|
|
3387
|
+
this.emitEvent({
|
|
3388
|
+
type: "ask_end",
|
|
3389
|
+
outcome: {
|
|
3390
|
+
ok: false,
|
|
3391
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3392
|
+
},
|
|
3393
|
+
cost: targetFrame.askCost.value,
|
|
3394
|
+
duration: Date.now() - targetAskStart
|
|
3395
|
+
});
|
|
3396
|
+
throw err;
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
this.emitEvent({
|
|
3400
|
+
type: "handoff_start",
|
|
3401
|
+
agent: agent2._name,
|
|
3402
|
+
fromAskId: handoffFromAskId,
|
|
3403
|
+
toAskId: handoffToAskId,
|
|
3404
|
+
sourceDepth: handoffSourceDepth,
|
|
3405
|
+
targetDepth: handoffTargetDepth,
|
|
3406
|
+
data: {
|
|
3407
|
+
source: agent2._name,
|
|
3408
|
+
target: targetName,
|
|
3409
|
+
mode,
|
|
3410
|
+
...mode === "roundtrip" && handoffPrompt !== prompt ? { message: handoffPrompt } : {}
|
|
3411
|
+
}
|
|
3412
|
+
});
|
|
2684
3413
|
if (mode === "roundtrip") {
|
|
2685
3414
|
const executeRoundtrip = async () => {
|
|
2686
3415
|
const result = await handoffFn();
|
|
@@ -2701,36 +3430,41 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2701
3430
|
"axl.handoff.mode": mode
|
|
2702
3431
|
},
|
|
2703
3432
|
async (span) => {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
3433
|
+
try {
|
|
3434
|
+
return await executeRoundtrip();
|
|
3435
|
+
} finally {
|
|
3436
|
+
const duration = Date.now() - handoffStart;
|
|
3437
|
+
span.setAttribute("axl.handoff.duration", duration);
|
|
3438
|
+
this.emitEvent({
|
|
3439
|
+
type: "handoff_return",
|
|
3440
|
+
agent: agent2._name,
|
|
3441
|
+
fromAskId: handoffFromAskId,
|
|
3442
|
+
toAskId: handoffToAskId,
|
|
3443
|
+
sourceDepth: handoffSourceDepth,
|
|
3444
|
+
targetDepth: handoffTargetDepth,
|
|
3445
|
+
data: { source: agent2._name, target: targetName, duration }
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
2719
3448
|
}
|
|
2720
3449
|
);
|
|
2721
3450
|
} else {
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
3451
|
+
try {
|
|
3452
|
+
await executeRoundtrip();
|
|
3453
|
+
} finally {
|
|
3454
|
+
this.emitEvent({
|
|
3455
|
+
type: "handoff_return",
|
|
3456
|
+
agent: agent2._name,
|
|
3457
|
+
fromAskId: handoffFromAskId,
|
|
3458
|
+
toAskId: handoffToAskId,
|
|
3459
|
+
sourceDepth: handoffSourceDepth,
|
|
3460
|
+
targetDepth: handoffTargetDepth,
|
|
3461
|
+
data: {
|
|
3462
|
+
source: agent2._name,
|
|
3463
|
+
target: targetName,
|
|
3464
|
+
duration: Date.now() - handoffStart
|
|
3465
|
+
}
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
2734
3468
|
}
|
|
2735
3469
|
continue;
|
|
2736
3470
|
}
|
|
@@ -2744,29 +3478,12 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2744
3478
|
},
|
|
2745
3479
|
async (span) => {
|
|
2746
3480
|
const result = await handoffFn();
|
|
2747
|
-
|
|
2748
|
-
span.setAttribute("axl.handoff.duration", duration);
|
|
2749
|
-
this.emitTrace({
|
|
2750
|
-
type: "handoff",
|
|
2751
|
-
agent: agent2._name,
|
|
2752
|
-
data: { target: targetName, mode, duration, source: agent2._name }
|
|
2753
|
-
});
|
|
3481
|
+
span.setAttribute("axl.handoff.duration", Date.now() - handoffStart);
|
|
2754
3482
|
return result;
|
|
2755
3483
|
}
|
|
2756
3484
|
);
|
|
2757
3485
|
}
|
|
2758
|
-
|
|
2759
|
-
this.emitTrace({
|
|
2760
|
-
type: "handoff",
|
|
2761
|
-
agent: agent2._name,
|
|
2762
|
-
data: {
|
|
2763
|
-
target: targetName,
|
|
2764
|
-
mode,
|
|
2765
|
-
duration: Date.now() - handoffStart,
|
|
2766
|
-
source: agent2._name
|
|
2767
|
-
}
|
|
2768
|
-
});
|
|
2769
|
-
return onewayResult;
|
|
3486
|
+
return await handoffFn();
|
|
2770
3487
|
}
|
|
2771
3488
|
}
|
|
2772
3489
|
const toolOverride = this.toolOverrides?.get(toolName);
|
|
@@ -2782,7 +3499,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2782
3499
|
});
|
|
2783
3500
|
continue;
|
|
2784
3501
|
}
|
|
2785
|
-
this.
|
|
3502
|
+
this.emitEvent({
|
|
3503
|
+
type: "tool_call_start",
|
|
3504
|
+
tool: toolName,
|
|
3505
|
+
callId: toolCall.id,
|
|
3506
|
+
data: { args: toolArgs2 }
|
|
3507
|
+
});
|
|
3508
|
+
this.onToolCall?.(
|
|
3509
|
+
{ name: toolName, args: toolArgs2, callId: toolCall.id },
|
|
3510
|
+
callbackMeta
|
|
3511
|
+
);
|
|
2786
3512
|
const toolStart2 = Date.now();
|
|
2787
3513
|
const executeOverride = async () => {
|
|
2788
3514
|
let toolResult3;
|
|
@@ -2810,8 +3536,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2810
3536
|
}
|
|
2811
3537
|
) : await executeOverride();
|
|
2812
3538
|
const resultContent2 = JSON.stringify(toolResult2);
|
|
2813
|
-
this.
|
|
2814
|
-
type: "
|
|
3539
|
+
this.emitEvent({
|
|
3540
|
+
type: "tool_call_end",
|
|
2815
3541
|
agent: agent2._name,
|
|
2816
3542
|
tool: toolName,
|
|
2817
3543
|
duration: Date.now() - toolStart2,
|
|
@@ -2827,7 +3553,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2827
3553
|
const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
|
|
2828
3554
|
const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
|
|
2829
3555
|
if (!tool2 && !isMcpTool) {
|
|
2830
|
-
this.
|
|
3556
|
+
this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
|
|
2831
3557
|
currentMessages.push({
|
|
2832
3558
|
role: "tool",
|
|
2833
3559
|
content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
|
|
@@ -2846,7 +3572,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2846
3572
|
});
|
|
2847
3573
|
continue;
|
|
2848
3574
|
}
|
|
2849
|
-
this.
|
|
3575
|
+
this.emitEvent({
|
|
3576
|
+
type: "tool_call_start",
|
|
3577
|
+
tool: toolName,
|
|
3578
|
+
callId: toolCall.id,
|
|
3579
|
+
data: { args: toolArgs }
|
|
3580
|
+
});
|
|
3581
|
+
this.onToolCall?.({ name: toolName, args: toolArgs, callId: toolCall.id }, callbackMeta);
|
|
2850
3582
|
const toolStart = Date.now();
|
|
2851
3583
|
if (tool2 && tool2.requireApproval) {
|
|
2852
3584
|
const approvalFn = async () => {
|
|
@@ -2883,7 +3615,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2883
3615
|
} else {
|
|
2884
3616
|
approvalOutcome = await approvalFn();
|
|
2885
3617
|
}
|
|
2886
|
-
this.
|
|
3618
|
+
this.emitEvent({
|
|
2887
3619
|
type: "tool_approval",
|
|
2888
3620
|
agent: agent2._name,
|
|
2889
3621
|
tool: toolName,
|
|
@@ -2926,7 +3658,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2926
3658
|
resultContent2 = JSON.stringify(toolResult2);
|
|
2927
3659
|
}
|
|
2928
3660
|
} else if (tool2) {
|
|
2929
|
-
const childCtx = this.createChildContext(
|
|
3661
|
+
const childCtx = this.createChildContext();
|
|
2930
3662
|
try {
|
|
2931
3663
|
toolResult2 = await tool2._execute(toolArgs, childCtx);
|
|
2932
3664
|
} catch (err) {
|
|
@@ -2968,8 +3700,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2968
3700
|
return r;
|
|
2969
3701
|
}
|
|
2970
3702
|
) : await executeTool();
|
|
2971
|
-
this.
|
|
2972
|
-
type: "
|
|
3703
|
+
this.emitEvent({
|
|
3704
|
+
type: "tool_call_end",
|
|
2973
3705
|
agent: agent2._name,
|
|
2974
3706
|
tool: traceName,
|
|
2975
3707
|
duration: Date.now() - toolStart,
|
|
@@ -2993,7 +3725,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2993
3725
|
if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
|
|
2994
3726
|
feedbackMessage = `Your previous response was blocked by a safety guardrail: ${outputResult.reason ?? "Output blocked"}. Please provide a different response that complies with the guidelines.`;
|
|
2995
3727
|
}
|
|
2996
|
-
this.
|
|
3728
|
+
this.emitEvent({
|
|
2997
3729
|
type: "guardrail",
|
|
2998
3730
|
agent: agent2._name,
|
|
2999
3731
|
data: {
|
|
@@ -3014,6 +3746,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3014
3746
|
});
|
|
3015
3747
|
if (outputResult.block) {
|
|
3016
3748
|
if (feedbackMessage) {
|
|
3749
|
+
this.emitEvent({
|
|
3750
|
+
type: "pipeline",
|
|
3751
|
+
agent: agent2._name,
|
|
3752
|
+
status: "failed",
|
|
3753
|
+
stage: "guardrail",
|
|
3754
|
+
attempt: guardrailOutputRetries + 1,
|
|
3755
|
+
maxAttempts: maxGuardrailRetries + 1,
|
|
3756
|
+
reason: feedbackMessage
|
|
3757
|
+
});
|
|
3017
3758
|
guardrailOutputRetries++;
|
|
3018
3759
|
appendRetryMessages(
|
|
3019
3760
|
currentMessages,
|
|
@@ -3052,7 +3793,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3052
3793
|
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
|
|
3053
3794
|
}
|
|
3054
3795
|
}
|
|
3055
|
-
this.
|
|
3796
|
+
this.emitEvent({
|
|
3056
3797
|
type: "schema_check",
|
|
3057
3798
|
agent: agent2._name,
|
|
3058
3799
|
data: {
|
|
@@ -3071,6 +3812,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3071
3812
|
});
|
|
3072
3813
|
if (!schemaValid) {
|
|
3073
3814
|
if (schemaFeedback) {
|
|
3815
|
+
this.emitEvent({
|
|
3816
|
+
type: "pipeline",
|
|
3817
|
+
agent: agent2._name,
|
|
3818
|
+
status: "failed",
|
|
3819
|
+
stage: "schema",
|
|
3820
|
+
attempt: schemaRetries + 1,
|
|
3821
|
+
maxAttempts: (options.retries ?? 3) + 1,
|
|
3822
|
+
reason: schemaFeedback
|
|
3823
|
+
});
|
|
3074
3824
|
schemaRetries++;
|
|
3075
3825
|
appendRetryMessages(
|
|
3076
3826
|
currentMessages,
|
|
@@ -3108,7 +3858,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3108
3858
|
if (!validateResult.valid && validateRetries < maxValidateRetries) {
|
|
3109
3859
|
validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
|
|
3110
3860
|
}
|
|
3111
|
-
this.
|
|
3861
|
+
this.emitEvent({
|
|
3112
3862
|
type: "validate",
|
|
3113
3863
|
agent: agent2._name,
|
|
3114
3864
|
data: {
|
|
@@ -3127,6 +3877,15 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3127
3877
|
});
|
|
3128
3878
|
if (!validateResult.valid) {
|
|
3129
3879
|
if (validateFeedback) {
|
|
3880
|
+
this.emitEvent({
|
|
3881
|
+
type: "pipeline",
|
|
3882
|
+
agent: agent2._name,
|
|
3883
|
+
status: "failed",
|
|
3884
|
+
stage: "validate",
|
|
3885
|
+
attempt: validateRetries + 1,
|
|
3886
|
+
maxAttempts: (options.validateRetries ?? 2) + 1,
|
|
3887
|
+
reason: validateFeedback
|
|
3888
|
+
});
|
|
3130
3889
|
validateRetries++;
|
|
3131
3890
|
appendRetryMessages(
|
|
3132
3891
|
currentMessages,
|
|
@@ -3144,6 +3903,14 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3144
3903
|
);
|
|
3145
3904
|
}
|
|
3146
3905
|
}
|
|
3906
|
+
this.emitEvent({
|
|
3907
|
+
type: "pipeline",
|
|
3908
|
+
agent: agent2._name,
|
|
3909
|
+
status: "committed",
|
|
3910
|
+
stage: lastStartStage,
|
|
3911
|
+
attempt: lastStartAttempt,
|
|
3912
|
+
maxAttempts: lastStartMaxAttempts
|
|
3913
|
+
});
|
|
3147
3914
|
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3148
3915
|
return validated ?? content;
|
|
3149
3916
|
}
|
|
@@ -3272,43 +4039,100 @@ ${summaryResponse.content}`
|
|
|
3272
4039
|
/**
|
|
3273
4040
|
* Execute a function with checkpoint-replay semantics.
|
|
3274
4041
|
*
|
|
3275
|
-
* On first execution, runs `fn()`, saves the result
|
|
3276
|
-
* On replay (resume after restart), returns the saved result
|
|
3277
|
-
* This prevents duplicate side effects (double API
|
|
4042
|
+
* On first execution, runs `fn()`, saves the result under `name`, and
|
|
4043
|
+
* returns it. On replay (resume after restart), returns the saved result
|
|
4044
|
+
* without re-executing. This prevents duplicate side effects (double API
|
|
4045
|
+
* calls, double refunds, etc.).
|
|
4046
|
+
*
|
|
4047
|
+
* `name` must be a stable, caller-supplied identifier — the same call
|
|
4048
|
+
* site MUST pass the same name across runs of the same execution for
|
|
4049
|
+
* replay to work. Names are scoped to a single execution and must be
|
|
4050
|
+
* unique within it; reusing a name (e.g. inside a loop) gives
|
|
4051
|
+
* last-write-wins behavior. For loops, compose names from the iterator:
|
|
4052
|
+
* `ctx.checkpoint(`process-${id}`, fn)`.
|
|
4053
|
+
*
|
|
4054
|
+
* Names starting with `__auto/` are reserved for runtime-internal
|
|
4055
|
+
* auto-checkpointing of `ctx.ask` / spawn / race / parallel / map.
|
|
3278
4056
|
*/
|
|
3279
|
-
async checkpoint(fn) {
|
|
3280
|
-
|
|
4057
|
+
async checkpoint(name, fn) {
|
|
4058
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
4059
|
+
throw new Error("ctx.checkpoint name must be a non-empty string.");
|
|
4060
|
+
}
|
|
4061
|
+
if (name !== name.trim()) {
|
|
4062
|
+
throw new Error(
|
|
4063
|
+
`ctx.checkpoint name "${name}" has leading/trailing whitespace \u2014 trim before passing.`
|
|
4064
|
+
);
|
|
4065
|
+
}
|
|
4066
|
+
if (name.toLowerCase().startsWith("__auto/")) {
|
|
4067
|
+
throw new Error(
|
|
4068
|
+
`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.`
|
|
4069
|
+
);
|
|
4070
|
+
}
|
|
4071
|
+
return this._checkpoint(name, fn);
|
|
3281
4072
|
}
|
|
3282
4073
|
/**
|
|
3283
|
-
* Internal checkpoint implementation shared by both the public
|
|
3284
|
-
* and the automatic checkpointing in
|
|
4074
|
+
* Internal checkpoint implementation shared by both the public
|
|
4075
|
+
* `checkpoint(name, fn)` and the automatic checkpointing in
|
|
4076
|
+
* ask/spawn/race/parallel/map. Internal callers pass an `__auto/...`
|
|
4077
|
+
* name composed by `_autoCheckpointName`.
|
|
4078
|
+
*
|
|
4079
|
+
* `agent` is the originating agent for ask auto-checkpoints — passing
|
|
4080
|
+
* it explicitly stamps `event.agent` on the trace events even though
|
|
4081
|
+
* the emit fires before/after `fn()` (where the ask's ALS frame
|
|
4082
|
+
* either doesn't exist yet or has already torn down). Without this,
|
|
4083
|
+
* post-fn `checkpoint_save` rows show no agent in the trace UI.
|
|
3285
4084
|
*/
|
|
3286
|
-
async _checkpoint(fn) {
|
|
3287
|
-
const step = this.checkpointCounter++;
|
|
4085
|
+
async _checkpoint(name, fn, options) {
|
|
3288
4086
|
if (!this.stateStore) {
|
|
3289
4087
|
return fn();
|
|
3290
4088
|
}
|
|
3291
|
-
const
|
|
4089
|
+
const agentStamp = options?.agent ? { agent: options.agent } : {};
|
|
4090
|
+
const saved = await this.stateStore.getCheckpoint(this.executionId, name);
|
|
3292
4091
|
if (saved !== null) {
|
|
3293
|
-
this.
|
|
3294
|
-
type: "
|
|
3295
|
-
|
|
4092
|
+
this.emitEvent({
|
|
4093
|
+
type: "checkpoint_replay",
|
|
4094
|
+
...agentStamp,
|
|
4095
|
+
data: { name }
|
|
3296
4096
|
});
|
|
3297
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.
|
|
4097
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.name": name });
|
|
3298
4098
|
return saved;
|
|
3299
4099
|
}
|
|
3300
4100
|
const result = await fn();
|
|
3301
|
-
await this.stateStore.saveCheckpoint(this.executionId,
|
|
3302
|
-
this.
|
|
3303
|
-
type: "
|
|
3304
|
-
|
|
4101
|
+
await this.stateStore.saveCheckpoint(this.executionId, name, result);
|
|
4102
|
+
this.emitEvent({
|
|
4103
|
+
type: "checkpoint_save",
|
|
4104
|
+
...agentStamp,
|
|
4105
|
+
data: { name }
|
|
3305
4106
|
});
|
|
3306
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.
|
|
4107
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.name": name });
|
|
3307
4108
|
return result;
|
|
3308
4109
|
}
|
|
4110
|
+
/** Generate a stable auto-checkpoint name for an internal primitive.
|
|
4111
|
+
*
|
|
4112
|
+
* For `ctx.ask`, the originating agent is known and gets a per-agent
|
|
4113
|
+
* counter — the resulting name reads as "orchestrator's first ask"
|
|
4114
|
+
* (`__auto/orchestrator-agent/ask/0`) instead of a global ordinal.
|
|
4115
|
+
* For `spawn` / `race` / `parallel` / `map`, no agent context is
|
|
4116
|
+
* available (those wrap arbitrary fn callbacks), so the name uses
|
|
4117
|
+
* the root counter: `__auto/spawn/0`, `__auto/race/3`, etc.
|
|
4118
|
+
*
|
|
4119
|
+
* All counters live on a shared ref passed through `createChildContext`,
|
|
4120
|
+
* so a tool handler's nested `ctx.ask()` (which runs in a child
|
|
4121
|
+
* WorkflowContext) can never collide with the parent's auto-names in
|
|
4122
|
+
* the state store. */
|
|
4123
|
+
_autoCheckpointName(primitive, agent2) {
|
|
4124
|
+
const counters = this.autoCheckpointCounters;
|
|
4125
|
+
if (primitive === "ask" && agent2) {
|
|
4126
|
+
const n2 = counters.byAgent.get(agent2) ?? 0;
|
|
4127
|
+
counters.byAgent.set(agent2, n2 + 1);
|
|
4128
|
+
return `__auto/${agent2}/ask/${n2}`;
|
|
4129
|
+
}
|
|
4130
|
+
const n = counters.root++;
|
|
4131
|
+
return `__auto/${primitive}/${n}`;
|
|
4132
|
+
}
|
|
3309
4133
|
// ── ctx.spawn() ───────────────────────────────────────────────────────
|
|
3310
4134
|
async spawn(n, fn, options) {
|
|
3311
|
-
return this._checkpoint(() => {
|
|
4135
|
+
return this._checkpoint(this._autoCheckpointName("spawn"), () => {
|
|
3312
4136
|
if (this.spanManager) {
|
|
3313
4137
|
return this.spanManager.withSpanAsync(
|
|
3314
4138
|
"axl.ctx.spawn",
|
|
@@ -3504,7 +4328,7 @@ ${summaryResponse.content}`
|
|
|
3504
4328
|
const maxRetries = options?.retries ?? 3;
|
|
3505
4329
|
let lastRetry = void 0;
|
|
3506
4330
|
const emitVerifyOutcome = (passed, attempts, lastError) => {
|
|
3507
|
-
this.
|
|
4331
|
+
this.emitEvent({
|
|
3508
4332
|
type: "verify",
|
|
3509
4333
|
agent: void 0,
|
|
3510
4334
|
data: {
|
|
@@ -3644,7 +4468,7 @@ ${summaryResponse.content}`
|
|
|
3644
4468
|
}
|
|
3645
4469
|
// ── ctx.race() ────────────────────────────────────────────────────────
|
|
3646
4470
|
async race(fns, options) {
|
|
3647
|
-
return this._checkpoint(() => {
|
|
4471
|
+
return this._checkpoint(this._autoCheckpointName("race"), () => {
|
|
3648
4472
|
if (this.spanManager) {
|
|
3649
4473
|
return this.spanManager.withSpanAsync(
|
|
3650
4474
|
"axl.ctx.race",
|
|
@@ -3749,11 +4573,17 @@ ${summaryResponse.content}`
|
|
|
3749
4573
|
}
|
|
3750
4574
|
// ── ctx.parallel() ────────────────────────────────────────────────────
|
|
3751
4575
|
async parallel(fns) {
|
|
3752
|
-
return this._checkpoint(
|
|
4576
|
+
return this._checkpoint(
|
|
4577
|
+
this._autoCheckpointName("parallel"),
|
|
4578
|
+
() => Promise.all(fns.map((fn) => fn()))
|
|
4579
|
+
);
|
|
3753
4580
|
}
|
|
3754
4581
|
// ── ctx.map() ─────────────────────────────────────────────────────────
|
|
3755
4582
|
async map(items, fn, options) {
|
|
3756
|
-
return this._checkpoint(
|
|
4583
|
+
return this._checkpoint(
|
|
4584
|
+
this._autoCheckpointName("map"),
|
|
4585
|
+
() => this._mapImpl(items, fn, options)
|
|
4586
|
+
);
|
|
3757
4587
|
}
|
|
3758
4588
|
async _mapImpl(items, fn, options) {
|
|
3759
4589
|
const concurrency = options?.concurrency ?? 5;
|
|
@@ -3831,10 +4661,14 @@ ${summaryResponse.content}`
|
|
|
3831
4661
|
}
|
|
3832
4662
|
async _awaitHumanImpl(options) {
|
|
3833
4663
|
if (this.awaitHumanHandler) {
|
|
4664
|
+
this.emitEvent({
|
|
4665
|
+
type: "await_human",
|
|
4666
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
4667
|
+
});
|
|
3834
4668
|
const decision2 = await this.awaitHumanHandler(options);
|
|
3835
|
-
this.
|
|
3836
|
-
type: "
|
|
3837
|
-
data: {
|
|
4669
|
+
this.emitEvent({
|
|
4670
|
+
type: "await_human_resolved",
|
|
4671
|
+
data: { channel: options.channel, decision: decision2 }
|
|
3838
4672
|
});
|
|
3839
4673
|
return decision2;
|
|
3840
4674
|
}
|
|
@@ -3854,7 +4688,7 @@ ${summaryResponse.content}`
|
|
|
3854
4688
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
3855
4689
|
workflow: this.workflowName ?? "unknown",
|
|
3856
4690
|
input: this.input,
|
|
3857
|
-
step: this.
|
|
4691
|
+
step: this.stepRefRoot.value,
|
|
3858
4692
|
status: "waiting",
|
|
3859
4693
|
metadata: {
|
|
3860
4694
|
...this.metadata,
|
|
@@ -3863,18 +4697,22 @@ ${summaryResponse.content}`
|
|
|
3863
4697
|
}
|
|
3864
4698
|
});
|
|
3865
4699
|
}
|
|
3866
|
-
this.
|
|
3867
|
-
type: "
|
|
3868
|
-
data: {
|
|
4700
|
+
this.emitEvent({
|
|
4701
|
+
type: "await_human",
|
|
4702
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
3869
4703
|
});
|
|
3870
4704
|
const decision = await new Promise((resolve) => {
|
|
3871
4705
|
this.pendingDecisions.set(this.executionId, resolve);
|
|
3872
4706
|
});
|
|
4707
|
+
this.emitEvent({
|
|
4708
|
+
type: "await_human_resolved",
|
|
4709
|
+
data: { channel: options.channel, decision }
|
|
4710
|
+
});
|
|
3873
4711
|
if (this.stateStore) {
|
|
3874
4712
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
3875
4713
|
workflow: this.workflowName ?? "unknown",
|
|
3876
4714
|
input: this.input,
|
|
3877
|
-
step: this.
|
|
4715
|
+
step: this.stepRefRoot.value,
|
|
3878
4716
|
status: "running"
|
|
3879
4717
|
});
|
|
3880
4718
|
}
|
|
@@ -3927,17 +4765,28 @@ ${summaryResponse.content}`
|
|
|
3927
4765
|
// the previous `ctx.log('workflow_start', ...)` indirection — so consumers
|
|
3928
4766
|
// that narrow via `event.type === 'workflow_start'` actually see them.
|
|
3929
4767
|
// Internal: the runtime is the only caller, user workflows never call these.
|
|
3930
|
-
/** @internal */
|
|
4768
|
+
/** @internal — idempotent; no-ops on second+ call within one ctx. */
|
|
3931
4769
|
_emitWorkflowStart(input) {
|
|
3932
|
-
this.
|
|
4770
|
+
if (this._workflowStartEmitted) return;
|
|
4771
|
+
this._workflowStartEmitted = true;
|
|
4772
|
+
this.emitEvent({
|
|
3933
4773
|
type: "workflow_start",
|
|
3934
4774
|
workflow: this.workflowName,
|
|
3935
4775
|
data: { input }
|
|
3936
4776
|
});
|
|
3937
4777
|
}
|
|
3938
|
-
/** @internal
|
|
4778
|
+
/** @internal — idempotent; no-ops on second+ call within one ctx.
|
|
4779
|
+
* Protects against the post-emit-side-effect double-fire (reviewer
|
|
4780
|
+
* bug B1): `runtime.execute()` / `runtime.stream()` emit
|
|
4781
|
+
* `workflow_end(completed)`, then call `deleteCheckpoints` /
|
|
4782
|
+
* `persistExecution`. If either throws, the outer catch would
|
|
4783
|
+
* otherwise fire a second `workflow_end(failed)` with conflicting
|
|
4784
|
+
* status. First-wins semantics: the completed event stands, the
|
|
4785
|
+
* inner side-effect failure propagates as a normal thrown error. */
|
|
3939
4786
|
_emitWorkflowEnd(info) {
|
|
3940
|
-
this.
|
|
4787
|
+
if (this._workflowEndEmitted) return;
|
|
4788
|
+
this._workflowEndEmitted = true;
|
|
4789
|
+
this.emitEvent({
|
|
3941
4790
|
type: "workflow_end",
|
|
3942
4791
|
workflow: this.workflowName,
|
|
3943
4792
|
duration: info.duration,
|
|
@@ -3952,7 +4801,7 @@ ${summaryResponse.content}`
|
|
|
3952
4801
|
}
|
|
3953
4802
|
// ── ctx.log() ─────────────────────────────────────────────────────────
|
|
3954
4803
|
log(event, data) {
|
|
3955
|
-
this.
|
|
4804
|
+
this.emitEvent({
|
|
3956
4805
|
type: "log",
|
|
3957
4806
|
data: {
|
|
3958
4807
|
event,
|
|
@@ -4006,12 +4855,11 @@ ${summaryResponse.content}`
|
|
|
4006
4855
|
if (usage?.cost != null) {
|
|
4007
4856
|
this._accumulateBudgetCost(usage.cost);
|
|
4008
4857
|
}
|
|
4009
|
-
this.
|
|
4010
|
-
type: "
|
|
4858
|
+
this.emitEvent({
|
|
4859
|
+
type: "memory_remember",
|
|
4011
4860
|
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4012
4861
|
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4013
4862
|
data: {
|
|
4014
|
-
event: "memory_remember",
|
|
4015
4863
|
key,
|
|
4016
4864
|
scope,
|
|
4017
4865
|
embed: options?.embed === true,
|
|
@@ -4023,12 +4871,11 @@ ${summaryResponse.content}`
|
|
|
4023
4871
|
if (partialUsage?.cost != null) {
|
|
4024
4872
|
this._accumulateBudgetCost(partialUsage.cost);
|
|
4025
4873
|
}
|
|
4026
|
-
this.
|
|
4027
|
-
type: "
|
|
4874
|
+
this.emitEvent({
|
|
4875
|
+
type: "memory_remember",
|
|
4028
4876
|
...partialUsage?.cost != null ? { cost: partialUsage.cost } : {},
|
|
4029
4877
|
...partialUsage?.tokens != null ? { tokens: { input: partialUsage.tokens } } : {},
|
|
4030
4878
|
data: {
|
|
4031
|
-
event: "memory_remember",
|
|
4032
4879
|
key,
|
|
4033
4880
|
scope,
|
|
4034
4881
|
embed: options?.embed === true,
|
|
@@ -4080,26 +4927,24 @@ ${summaryResponse.content}`
|
|
|
4080
4927
|
if (usage?.cost != null) {
|
|
4081
4928
|
this._accumulateBudgetCost(usage.cost);
|
|
4082
4929
|
}
|
|
4083
|
-
this.
|
|
4084
|
-
type: "
|
|
4930
|
+
this.emitEvent({
|
|
4931
|
+
type: "memory_recall",
|
|
4085
4932
|
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4086
4933
|
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4087
4934
|
data: {
|
|
4088
|
-
event: "memory_recall",
|
|
4089
4935
|
key,
|
|
4090
4936
|
scope,
|
|
4091
4937
|
semantic,
|
|
4092
4938
|
hit,
|
|
4093
|
-
...resultCount !== void 0 ? { resultCount } : {},
|
|
4939
|
+
...resultCount !== void 0 ? { resultCount, count: resultCount } : {},
|
|
4094
4940
|
...usage ? { usage } : {}
|
|
4095
4941
|
}
|
|
4096
4942
|
});
|
|
4097
4943
|
return data;
|
|
4098
4944
|
} catch (err) {
|
|
4099
|
-
this.
|
|
4100
|
-
type: "
|
|
4945
|
+
this.emitEvent({
|
|
4946
|
+
type: "memory_recall",
|
|
4101
4947
|
data: {
|
|
4102
|
-
event: "memory_recall",
|
|
4103
4948
|
key,
|
|
4104
4949
|
scope,
|
|
4105
4950
|
semantic,
|
|
@@ -4124,15 +4969,14 @@ ${summaryResponse.content}`
|
|
|
4124
4969
|
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4125
4970
|
try {
|
|
4126
4971
|
await this.memoryManager.forget(key, this.stateStore, sessionId, options);
|
|
4127
|
-
this.
|
|
4128
|
-
type: "
|
|
4129
|
-
data: {
|
|
4972
|
+
this.emitEvent({
|
|
4973
|
+
type: "memory_forget",
|
|
4974
|
+
data: { key, scope }
|
|
4130
4975
|
});
|
|
4131
4976
|
} catch (err) {
|
|
4132
|
-
this.
|
|
4133
|
-
type: "
|
|
4977
|
+
this.emitEvent({
|
|
4978
|
+
type: "memory_forget",
|
|
4134
4979
|
data: {
|
|
4135
|
-
event: "memory_forget",
|
|
4136
4980
|
key,
|
|
4137
4981
|
scope,
|
|
4138
4982
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -4167,7 +5011,7 @@ ${summaryResponse.content}`
|
|
|
4167
5011
|
names.add(a._name);
|
|
4168
5012
|
}
|
|
4169
5013
|
if (agents.length === 1) {
|
|
4170
|
-
this.
|
|
5014
|
+
this.emitEvent({
|
|
4171
5015
|
type: "delegate",
|
|
4172
5016
|
agent: agents[0]._name,
|
|
4173
5017
|
data: {
|
|
@@ -4211,7 +5055,7 @@ ${summaryResponse.content}`
|
|
|
4211
5055
|
resolveModel: () => routerModelUri,
|
|
4212
5056
|
resolveSystem: () => routerSystem
|
|
4213
5057
|
};
|
|
4214
|
-
this.
|
|
5058
|
+
this.emitEvent({
|
|
4215
5059
|
type: "delegate",
|
|
4216
5060
|
agent: "_delegate_router",
|
|
4217
5061
|
data: {
|
|
@@ -4232,107 +5076,42 @@ ${summaryResponse.content}`
|
|
|
4232
5076
|
/**
|
|
4233
5077
|
* Internal emitter input — intentionally loose so call sites don't need to
|
|
4234
5078
|
* build a perfectly-narrowed discriminated-union member. The resulting
|
|
4235
|
-
* `
|
|
5079
|
+
* `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
|
|
4236
5080
|
* consumer sites via the `type` discriminator.
|
|
4237
5081
|
*/
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
prompt: "[redacted]",
|
|
4246
|
-
response: "[redacted]"
|
|
4247
|
-
};
|
|
4248
|
-
if (d.system !== void 0) redacted.system = "[redacted]";
|
|
4249
|
-
if (d.thinking !== void 0) redacted.thinking = "[redacted]";
|
|
4250
|
-
if (Array.isArray(d.messages)) {
|
|
4251
|
-
redacted.messages = [
|
|
4252
|
-
{ role: "system", content: `[${d.messages.length} messages redacted]` }
|
|
4253
|
-
];
|
|
4254
|
-
}
|
|
4255
|
-
data = redacted;
|
|
4256
|
-
} else if (partial.type === "guardrail" || partial.type === "schema_check" || partial.type === "validate") {
|
|
4257
|
-
const d = data;
|
|
4258
|
-
const needsRedact = d.feedbackMessage !== void 0 || d.reason !== void 0;
|
|
4259
|
-
if (needsRedact) {
|
|
4260
|
-
data = {
|
|
4261
|
-
...d,
|
|
4262
|
-
...d.reason !== void 0 ? { reason: "[redacted]" } : {},
|
|
4263
|
-
...d.feedbackMessage !== void 0 ? { feedbackMessage: "[redacted]" } : {}
|
|
4264
|
-
};
|
|
4265
|
-
}
|
|
4266
|
-
} else if (partial.type === "tool_call") {
|
|
4267
|
-
const d = data;
|
|
4268
|
-
data = {
|
|
4269
|
-
...d,
|
|
4270
|
-
args: "[redacted]",
|
|
4271
|
-
result: "[redacted]"
|
|
4272
|
-
};
|
|
4273
|
-
} else if (partial.type === "tool_approval") {
|
|
4274
|
-
const d = data;
|
|
4275
|
-
data = {
|
|
4276
|
-
...d,
|
|
4277
|
-
args: "[redacted]",
|
|
4278
|
-
...d.reason !== void 0 ? { reason: "[redacted]" } : {}
|
|
4279
|
-
};
|
|
4280
|
-
} else if (partial.type === "handoff") {
|
|
4281
|
-
const d = data;
|
|
4282
|
-
if (d.message !== void 0) {
|
|
4283
|
-
data = { ...d, message: "[redacted]" };
|
|
4284
|
-
}
|
|
4285
|
-
} else if (partial.type === "workflow_start") {
|
|
4286
|
-
const d = data;
|
|
4287
|
-
if (d.input !== void 0) {
|
|
4288
|
-
data = { ...d, input: "[redacted]" };
|
|
4289
|
-
}
|
|
4290
|
-
} else if (partial.type === "workflow_end") {
|
|
4291
|
-
const d = data;
|
|
4292
|
-
const redacted = { ...d };
|
|
4293
|
-
if (d.result !== void 0) redacted.result = "[redacted]";
|
|
4294
|
-
if (d.error !== void 0) redacted.error = "[redacted]";
|
|
4295
|
-
data = redacted;
|
|
4296
|
-
} else if (partial.type === "log") {
|
|
4297
|
-
const d = data;
|
|
4298
|
-
const redacted = {};
|
|
4299
|
-
for (const [k, v] of Object.entries(d)) {
|
|
4300
|
-
if (k === "event" || typeof v === "number" || typeof v === "boolean") {
|
|
4301
|
-
redacted[k] = v;
|
|
4302
|
-
} else if (typeof v === "string") {
|
|
4303
|
-
redacted[k] = "[redacted]";
|
|
4304
|
-
} else if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
4305
|
-
const inner = v;
|
|
4306
|
-
const innerRedacted = {};
|
|
4307
|
-
for (const [ik, iv] of Object.entries(inner)) {
|
|
4308
|
-
if (typeof iv === "number" || typeof iv === "boolean") {
|
|
4309
|
-
innerRedacted[ik] = iv;
|
|
4310
|
-
} else if (typeof iv === "string") {
|
|
4311
|
-
innerRedacted[ik] = "[redacted]";
|
|
4312
|
-
} else {
|
|
4313
|
-
innerRedacted[ik] = "[redacted]";
|
|
4314
|
-
}
|
|
4315
|
-
}
|
|
4316
|
-
redacted[k] = innerRedacted;
|
|
4317
|
-
} else {
|
|
4318
|
-
redacted[k] = "[redacted]";
|
|
4319
|
-
}
|
|
4320
|
-
}
|
|
4321
|
-
data = redacted;
|
|
4322
|
-
}
|
|
5082
|
+
emitEvent(partial) {
|
|
5083
|
+
const data = partial.data;
|
|
5084
|
+
const frame = askStorage.getStore();
|
|
5085
|
+
const step = (frame?.stepRef ?? this.stepRefRoot).value++;
|
|
5086
|
+
const cost = partial.cost;
|
|
5087
|
+
if (frame && typeof cost === "number" && Number.isFinite(cost) && cost >= 0 && COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
5088
|
+
frame.askCost.value += cost;
|
|
4323
5089
|
}
|
|
4324
5090
|
const event = {
|
|
4325
5091
|
executionId: this.executionId,
|
|
4326
|
-
step
|
|
5092
|
+
step,
|
|
4327
5093
|
timestamp: Date.now(),
|
|
4328
5094
|
...this.workflowName ? { workflow: this.workflowName } : {},
|
|
4329
|
-
|
|
5095
|
+
// Stamp ask correlation from ALS. Variants like `workflow_start` /
|
|
5096
|
+
// `workflow_end` and out-of-ask events (log/memory/checkpoint/await_human
|
|
5097
|
+
// when emitted at workflow scope) may legitimately have no frame —
|
|
5098
|
+
// they still get `depth: 0` so consumers can render them at root
|
|
5099
|
+
// level without having to guess. `askId`/`parentAskId`/`agent` stay
|
|
5100
|
+
// unset for non-ask-scoped events, matching the `Partial<AskScoped>`
|
|
5101
|
+
// shape on those union members.
|
|
5102
|
+
...frame ? {
|
|
5103
|
+
askId: frame.askId,
|
|
5104
|
+
...frame.parentAskId ? { parentAskId: frame.parentAskId } : {},
|
|
5105
|
+
depth: frame.depth,
|
|
5106
|
+
...frame.agent ? { agent: frame.agent } : {}
|
|
5107
|
+
} : { depth: 0 },
|
|
4330
5108
|
...partial,
|
|
4331
5109
|
data
|
|
4332
5110
|
};
|
|
5111
|
+
const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
|
|
4333
5112
|
if (this.onTrace) {
|
|
4334
5113
|
try {
|
|
4335
|
-
this.onTrace(
|
|
5114
|
+
this.onTrace(finalEvent);
|
|
4336
5115
|
} catch (err) {
|
|
4337
5116
|
console.error(
|
|
4338
5117
|
"[axl] onTrace handler threw; trace event dropped:",
|
|
@@ -4344,7 +5123,7 @@ ${summaryResponse.content}`
|
|
|
4344
5123
|
};
|
|
4345
5124
|
|
|
4346
5125
|
// src/agent.ts
|
|
4347
|
-
import { randomUUID } from "crypto";
|
|
5126
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4348
5127
|
var agentCounter = 0;
|
|
4349
5128
|
function agent(config) {
|
|
4350
5129
|
agentCounter++;
|
|
@@ -4364,7 +5143,7 @@ function agent(config) {
|
|
|
4364
5143
|
const registry = new ProviderRegistry();
|
|
4365
5144
|
const ctx = new WorkflowContext({
|
|
4366
5145
|
input: prompt,
|
|
4367
|
-
executionId:
|
|
5146
|
+
executionId: randomUUID2(),
|
|
4368
5147
|
config: {},
|
|
4369
5148
|
providerRegistry: registry
|
|
4370
5149
|
});
|
|
@@ -4388,7 +5167,7 @@ function workflow(config) {
|
|
|
4388
5167
|
|
|
4389
5168
|
// src/runtime.ts
|
|
4390
5169
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
4391
|
-
import { randomUUID as
|
|
5170
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4392
5171
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
4393
5172
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
4394
5173
|
import { dirname } from "path";
|
|
@@ -4411,29 +5190,20 @@ var MemoryStore = class {
|
|
|
4411
5190
|
constructor() {
|
|
4412
5191
|
this.loadPersistedState();
|
|
4413
5192
|
}
|
|
4414
|
-
async saveCheckpoint(executionId,
|
|
4415
|
-
let
|
|
4416
|
-
if (!
|
|
4417
|
-
|
|
4418
|
-
this.checkpoints.set(executionId,
|
|
5193
|
+
async saveCheckpoint(executionId, name, data) {
|
|
5194
|
+
let entries = this.checkpoints.get(executionId);
|
|
5195
|
+
if (!entries) {
|
|
5196
|
+
entries = /* @__PURE__ */ new Map();
|
|
5197
|
+
this.checkpoints.set(executionId, entries);
|
|
4419
5198
|
}
|
|
4420
|
-
|
|
5199
|
+
entries.set(name, structuredClone(data));
|
|
4421
5200
|
}
|
|
4422
|
-
async getCheckpoint(executionId,
|
|
4423
|
-
const
|
|
4424
|
-
if (!
|
|
4425
|
-
const data =
|
|
5201
|
+
async getCheckpoint(executionId, name) {
|
|
5202
|
+
const entries = this.checkpoints.get(executionId);
|
|
5203
|
+
if (!entries) return null;
|
|
5204
|
+
const data = entries.get(name);
|
|
4426
5205
|
return data !== void 0 ? structuredClone(data) : null;
|
|
4427
5206
|
}
|
|
4428
|
-
async getLatestCheckpoint(executionId) {
|
|
4429
|
-
const steps = this.checkpoints.get(executionId);
|
|
4430
|
-
if (!steps || steps.size === 0) return null;
|
|
4431
|
-
let maxStep = -1;
|
|
4432
|
-
for (const step of steps.keys()) {
|
|
4433
|
-
if (step > maxStep) maxStep = step;
|
|
4434
|
-
}
|
|
4435
|
-
return { step: maxStep, data: structuredClone(steps.get(maxStep)) };
|
|
4436
|
-
}
|
|
4437
5207
|
async saveSession(sessionId, history) {
|
|
4438
5208
|
this.sessions.set(sessionId, structuredClone(history));
|
|
4439
5209
|
}
|
|
@@ -4606,7 +5376,7 @@ function rowToExecutionInfo(row) {
|
|
|
4606
5376
|
completedAt: row.completed_at ?? void 0,
|
|
4607
5377
|
duration: row.duration,
|
|
4608
5378
|
error: row.error ?? void 0,
|
|
4609
|
-
|
|
5379
|
+
events: safeJsonParse(row.events) ?? []
|
|
4610
5380
|
};
|
|
4611
5381
|
}
|
|
4612
5382
|
var SQLiteStore = class {
|
|
@@ -4622,15 +5392,80 @@ var SQLiteStore = class {
|
|
|
4622
5392
|
}
|
|
4623
5393
|
this.db = new BetterSqlite3(filePath);
|
|
4624
5394
|
this.db.pragma("journal_mode = WAL");
|
|
5395
|
+
this.migrate();
|
|
4625
5396
|
this.initTables();
|
|
4626
5397
|
}
|
|
5398
|
+
/**
|
|
5399
|
+
* Idempotent schema migration. Tracks version via `PRAGMA user_version`
|
|
5400
|
+
* so reopens skip applied steps. Wrapped in `BEGIN IMMEDIATE` so
|
|
5401
|
+
* concurrent `SQLiteStore` constructors serialize at the file level
|
|
5402
|
+
* and rollback on failure leaves the DB in a clean pre-migration
|
|
5403
|
+
* state.
|
|
5404
|
+
*
|
|
5405
|
+
* Migration history:
|
|
5406
|
+
* - v1 (spec/16): rename `execution_history.steps` → `events`. The
|
|
5407
|
+
* JS-level `ExecutionInfo.steps` was renamed to `.events` in PR 1
|
|
5408
|
+
* commit 1; this brings the on-disk schema in line.
|
|
5409
|
+
* - v2 (named checkpoints): rename `checkpoints.step` (INTEGER) →
|
|
5410
|
+
* `checkpoints.name` (TEXT). The runtime now requires
|
|
5411
|
+
* `ctx.checkpoint(name, fn)` so the per-context numeric counter is
|
|
5412
|
+
* gone — names are caller-supplied stable identifiers. Existing
|
|
5413
|
+
* numeric step values become stringified names ("0", "1", …) to
|
|
5414
|
+
* preserve replay continuity for in-flight executions across the
|
|
5415
|
+
* upgrade boundary.
|
|
5416
|
+
*
|
|
5417
|
+
* Applied BEFORE `initTables()` so the subsequent `CREATE TABLE
|
|
5418
|
+
* IF NOT EXISTS` runs against the post-migration column name. Fresh
|
|
5419
|
+
* installs just create the table with `events` directly and bump
|
|
5420
|
+
* `user_version` to the current version.
|
|
5421
|
+
*
|
|
5422
|
+
* TOCTOU guard (review B-3): the initial `user_version` read is
|
|
5423
|
+
* informational only — a concurrent process can complete the
|
|
5424
|
+
* migration between that read and our `BEGIN IMMEDIATE`. We
|
|
5425
|
+
* re-read `user_version` inside the transaction and short-circuit
|
|
5426
|
+
* if it's already at target, so the non-idempotent half (writes
|
|
5427
|
+
* that went beyond a simple ALTER) never double-applies under a
|
|
5428
|
+
* race. The current v0→v1 step IS idempotent via the column-
|
|
5429
|
+
* presence check, but future migrations may not be, and the
|
|
5430
|
+
* transactional re-read costs nothing.
|
|
5431
|
+
*/
|
|
5432
|
+
migrate() {
|
|
5433
|
+
const TARGET_VERSION = 2;
|
|
5434
|
+
const current = this.db.pragma("user_version", { simple: true });
|
|
5435
|
+
if (current >= TARGET_VERSION) return;
|
|
5436
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
5437
|
+
try {
|
|
5438
|
+
const committed = this.db.pragma("user_version", { simple: true });
|
|
5439
|
+
if (committed >= TARGET_VERSION) {
|
|
5440
|
+
this.db.exec("COMMIT");
|
|
5441
|
+
return;
|
|
5442
|
+
}
|
|
5443
|
+
if (committed < 1) {
|
|
5444
|
+
const cols = this.db.pragma("table_info(execution_history)");
|
|
5445
|
+
if (cols.some((c) => c.name === "steps") && !cols.some((c) => c.name === "events")) {
|
|
5446
|
+
this.db.exec("ALTER TABLE execution_history RENAME COLUMN steps TO events");
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
if (committed < 2) {
|
|
5450
|
+
const cols = this.db.pragma("table_info(checkpoints)");
|
|
5451
|
+
if (cols.some((c) => c.name === "step") && !cols.some((c) => c.name === "name")) {
|
|
5452
|
+
this.db.exec("ALTER TABLE checkpoints RENAME COLUMN step TO name");
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
this.db.pragma(`user_version = ${TARGET_VERSION}`);
|
|
5456
|
+
this.db.exec("COMMIT");
|
|
5457
|
+
} catch (err) {
|
|
5458
|
+
this.db.exec("ROLLBACK");
|
|
5459
|
+
throw err;
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
4627
5462
|
initTables() {
|
|
4628
5463
|
this.db.exec(`
|
|
4629
5464
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
4630
5465
|
execution_id TEXT NOT NULL,
|
|
4631
|
-
|
|
5466
|
+
name TEXT NOT NULL,
|
|
4632
5467
|
data TEXT NOT NULL,
|
|
4633
|
-
PRIMARY KEY (execution_id,
|
|
5468
|
+
PRIMARY KEY (execution_id, name)
|
|
4634
5469
|
);
|
|
4635
5470
|
|
|
4636
5471
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -4679,7 +5514,7 @@ var SQLiteStore = class {
|
|
|
4679
5514
|
completed_at INTEGER,
|
|
4680
5515
|
duration INTEGER NOT NULL DEFAULT 0,
|
|
4681
5516
|
error TEXT,
|
|
4682
|
-
|
|
5517
|
+
events TEXT NOT NULL
|
|
4683
5518
|
);
|
|
4684
5519
|
|
|
4685
5520
|
CREATE TABLE IF NOT EXISTS eval_history (
|
|
@@ -4694,26 +5529,19 @@ var SQLiteStore = class {
|
|
|
4694
5529
|
`);
|
|
4695
5530
|
}
|
|
4696
5531
|
// ── Checkpoints ────────────────────────────────────────────────────────
|
|
4697
|
-
async saveCheckpoint(executionId,
|
|
5532
|
+
async saveCheckpoint(executionId, name, data) {
|
|
4698
5533
|
const stmt = this.db.prepare(
|
|
4699
|
-
"INSERT OR REPLACE INTO checkpoints (execution_id,
|
|
5534
|
+
"INSERT OR REPLACE INTO checkpoints (execution_id, name, data) VALUES (?, ?, ?)"
|
|
4700
5535
|
);
|
|
4701
|
-
stmt.run(executionId,
|
|
5536
|
+
stmt.run(executionId, name, JSON.stringify(data));
|
|
4702
5537
|
}
|
|
4703
|
-
async getCheckpoint(executionId,
|
|
5538
|
+
async getCheckpoint(executionId, name) {
|
|
4704
5539
|
const stmt = this.db.prepare(
|
|
4705
|
-
"SELECT data FROM checkpoints WHERE execution_id = ? AND
|
|
5540
|
+
"SELECT data FROM checkpoints WHERE execution_id = ? AND name = ?"
|
|
4706
5541
|
);
|
|
4707
|
-
const row = stmt.get(executionId,
|
|
5542
|
+
const row = stmt.get(executionId, name);
|
|
4708
5543
|
return row ? safeJsonParse(row.data) : null;
|
|
4709
5544
|
}
|
|
4710
|
-
async getLatestCheckpoint(executionId) {
|
|
4711
|
-
const stmt = this.db.prepare(
|
|
4712
|
-
"SELECT step, data FROM checkpoints WHERE execution_id = ? ORDER BY step DESC LIMIT 1"
|
|
4713
|
-
);
|
|
4714
|
-
const row = stmt.get(executionId);
|
|
4715
|
-
return row ? { step: row.step, data: safeJsonParse(row.data) } : null;
|
|
4716
|
-
}
|
|
4717
5545
|
// ── Sessions ────────────────────────────────────────────────────────────
|
|
4718
5546
|
async saveSession(sessionId, history) {
|
|
4719
5547
|
const stmt = this.db.prepare(
|
|
@@ -4807,7 +5635,7 @@ var SQLiteStore = class {
|
|
|
4807
5635
|
// ── Execution History ────────────────────────────────────────────────────
|
|
4808
5636
|
async saveExecution(execution) {
|
|
4809
5637
|
this.db.prepare(
|
|
4810
|
-
"INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error,
|
|
5638
|
+
"INSERT OR REPLACE INTO execution_history (execution_id, workflow, status, total_cost, started_at, completed_at, duration, error, events) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4811
5639
|
).run(
|
|
4812
5640
|
execution.executionId,
|
|
4813
5641
|
execution.workflow,
|
|
@@ -4817,7 +5645,7 @@ var SQLiteStore = class {
|
|
|
4817
5645
|
execution.completedAt ?? null,
|
|
4818
5646
|
execution.duration,
|
|
4819
5647
|
execution.error ?? null,
|
|
4820
|
-
JSON.stringify(execution.
|
|
5648
|
+
JSON.stringify(execution.events)
|
|
4821
5649
|
);
|
|
4822
5650
|
}
|
|
4823
5651
|
async getExecution(executionId) {
|
|
@@ -4991,7 +5819,8 @@ var Session = class _Session {
|
|
|
4991
5819
|
}
|
|
4992
5820
|
};
|
|
4993
5821
|
axlStream.on("done", (event) => {
|
|
4994
|
-
|
|
5822
|
+
const data = event.data;
|
|
5823
|
+
updateHistory(data.result).catch((err) => {
|
|
4995
5824
|
this.runtime.emit("error", {
|
|
4996
5825
|
type: "session_history_save_failed",
|
|
4997
5826
|
sessionId: this.sessionId,
|
|
@@ -5033,9 +5862,88 @@ var Session = class _Session {
|
|
|
5033
5862
|
// src/stream.ts
|
|
5034
5863
|
import { Readable } from "stream";
|
|
5035
5864
|
import { EventEmitter } from "events";
|
|
5865
|
+
|
|
5866
|
+
// src/types.ts
|
|
5867
|
+
var AXL_EVENT_TYPES = [
|
|
5868
|
+
// Workflow lifecycle
|
|
5869
|
+
"workflow_start",
|
|
5870
|
+
"workflow_end",
|
|
5871
|
+
// Ask boundary
|
|
5872
|
+
"ask_start",
|
|
5873
|
+
"ask_end",
|
|
5874
|
+
// Agent turn lifecycle
|
|
5875
|
+
"agent_call_start",
|
|
5876
|
+
"agent_call_end",
|
|
5877
|
+
// Content delivery (stream-only)
|
|
5878
|
+
"token",
|
|
5879
|
+
// Tool invocation lifecycle
|
|
5880
|
+
"tool_call_start",
|
|
5881
|
+
"tool_call_end",
|
|
5882
|
+
// Single-point tool events
|
|
5883
|
+
"tool_approval",
|
|
5884
|
+
"tool_denied",
|
|
5885
|
+
// Delegation
|
|
5886
|
+
"delegate",
|
|
5887
|
+
// Handoff (spans two asks; not AskScoped).
|
|
5888
|
+
// `handoff_start` emits before the target ask begins; always fired.
|
|
5889
|
+
// `handoff_return` emits when control returns to source; roundtrip only
|
|
5890
|
+
// (oneway handoffs have no return trip — the target's `ask_end` IS the
|
|
5891
|
+
// end of the chain).
|
|
5892
|
+
"handoff_start",
|
|
5893
|
+
"handoff_return",
|
|
5894
|
+
// Pipeline (retry/validation lifecycle) — added in PR 2; reserved here.
|
|
5895
|
+
"pipeline",
|
|
5896
|
+
// Progressive structured output — added in PR 2; reserved here.
|
|
5897
|
+
"partial_object",
|
|
5898
|
+
// Verification
|
|
5899
|
+
"verify",
|
|
5900
|
+
// Observability
|
|
5901
|
+
"log",
|
|
5902
|
+
"memory_remember",
|
|
5903
|
+
"memory_recall",
|
|
5904
|
+
"memory_forget",
|
|
5905
|
+
// Durable execution checkpoints (`ctx.checkpoint`)
|
|
5906
|
+
"checkpoint_save",
|
|
5907
|
+
"checkpoint_replay",
|
|
5908
|
+
// Human-in-the-loop (`ctx.awaitHuman`)
|
|
5909
|
+
"await_human",
|
|
5910
|
+
"await_human_resolved",
|
|
5911
|
+
// Legacy gate events — emitted by current code; collapsed into `pipeline`
|
|
5912
|
+
// in PR 2 (spec/16-streaming-wire-reliability §4.2).
|
|
5913
|
+
"guardrail",
|
|
5914
|
+
"schema_check",
|
|
5915
|
+
"validate",
|
|
5916
|
+
// Terminal workflow markers
|
|
5917
|
+
"done",
|
|
5918
|
+
"error"
|
|
5919
|
+
];
|
|
5920
|
+
|
|
5921
|
+
// src/stream.ts
|
|
5036
5922
|
var AxlStream = class _AxlStream extends Readable {
|
|
5037
5923
|
bus = new EventEmitter();
|
|
5038
|
-
|
|
5924
|
+
/**
|
|
5925
|
+
* Per-ask token buffers split into "in-progress" and "committed"
|
|
5926
|
+
* halves so `fullText` only includes tokens from attempts that
|
|
5927
|
+
* actually won. Scoping is per-`askId` so concurrent root-level asks
|
|
5928
|
+
* (`ctx.parallel`, `ctx.spawn`, `ctx.race`, `ctx.map`) don't
|
|
5929
|
+
* interleave each other's tokens — each branch's chunks stay
|
|
5930
|
+
* contiguous, and a `pipeline(failed)` on one branch only discards
|
|
5931
|
+
* THAT branch's in-progress buffer (previously: shared buffer caused
|
|
5932
|
+
* a failure on one branch to discard a peer's in-flight successful
|
|
5933
|
+
* tokens). Insertion order in the Maps reflects which ask emitted
|
|
5934
|
+
* its first token first, which is what `fullText` joins on.
|
|
5935
|
+
*
|
|
5936
|
+
* On `pipeline(status: 'committed')` for an ask, that ask's
|
|
5937
|
+
* in-progress entry flushes to its committed entry. On
|
|
5938
|
+
* `pipeline(status: 'failed')` or `ask_end({ok:false})` for an ask,
|
|
5939
|
+
* that ask's in-progress entry is discarded. Spec/16 §4.3.
|
|
5940
|
+
*
|
|
5941
|
+
* Tokens emitted outside any ask (synthesized test fixtures with no
|
|
5942
|
+
* `askId`) fall back to an empty-string sentinel key — preserves the
|
|
5943
|
+
* single-buffer behavior for that legacy case.
|
|
5944
|
+
*/
|
|
5945
|
+
attemptByAsk = /* @__PURE__ */ new Map();
|
|
5946
|
+
committedByAsk = /* @__PURE__ */ new Map();
|
|
5039
5947
|
result = void 0;
|
|
5040
5948
|
finished = false;
|
|
5041
5949
|
promise;
|
|
@@ -5057,18 +5965,10 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5057
5965
|
this.promise.catch(() => {
|
|
5058
5966
|
});
|
|
5059
5967
|
}
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
"tool_approval",
|
|
5065
|
-
"agent_start",
|
|
5066
|
-
"agent_end",
|
|
5067
|
-
"handoff",
|
|
5068
|
-
"step",
|
|
5069
|
-
"done",
|
|
5070
|
-
"error"
|
|
5071
|
-
]);
|
|
5968
|
+
/** Wire-format event names callers can subscribe to via `.on(name, fn)`.
|
|
5969
|
+
* Derived from the canonical `AXL_EVENT_TYPES` tuple — adding a new
|
|
5970
|
+
* variant in `types.ts` automatically extends the subscribable set. */
|
|
5971
|
+
static STREAM_EVENTS = new Set(AXL_EVENT_TYPES);
|
|
5072
5972
|
on(event, handler) {
|
|
5073
5973
|
if (_AxlStream.STREAM_EVENTS.has(event)) {
|
|
5074
5974
|
this.bus.on(event, handler);
|
|
@@ -5118,21 +6018,83 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5118
6018
|
while (true) {
|
|
5119
6019
|
const { value, done } = await iter.next();
|
|
5120
6020
|
if (done) return { value: void 0, done: true };
|
|
5121
|
-
if (value.type === "token"
|
|
6021
|
+
if (value.type === "token" && isRootLevel(value)) {
|
|
6022
|
+
return { value: value.data, done: false };
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
};
|
|
6027
|
+
}
|
|
6028
|
+
};
|
|
6029
|
+
}
|
|
6030
|
+
/**
|
|
6031
|
+
* Iterator over `{ askId, text }` pairs — one emission per token chunk,
|
|
6032
|
+
* tagged with the ask frame that produced it. Complements `.text`
|
|
6033
|
+
* (root-only stream): consumers building a split-pane UI that shows
|
|
6034
|
+
* each sub-agent's output in its own lane can group by `askId`
|
|
6035
|
+
* without iterating the raw stream and hand-filtering on `event.type`.
|
|
6036
|
+
*
|
|
6037
|
+
* Nested and root tokens both flow through. The `agent` field names
|
|
6038
|
+
* the producing agent for UI labelling; it's undefined when the token
|
|
6039
|
+
* was emitted outside any ask (rare — synthesized test fixtures).
|
|
6040
|
+
*/
|
|
6041
|
+
get textByAsk() {
|
|
6042
|
+
const self = this;
|
|
6043
|
+
return {
|
|
6044
|
+
[Symbol.asyncIterator]() {
|
|
6045
|
+
const iter = self[Symbol.asyncIterator]();
|
|
6046
|
+
return {
|
|
6047
|
+
async next() {
|
|
6048
|
+
while (true) {
|
|
6049
|
+
const { value, done } = await iter.next();
|
|
6050
|
+
if (done) {
|
|
6051
|
+
return {
|
|
6052
|
+
value: void 0,
|
|
6053
|
+
done: true
|
|
6054
|
+
};
|
|
6055
|
+
}
|
|
6056
|
+
if (value.type === "token") {
|
|
6057
|
+
return {
|
|
6058
|
+
value: { askId: value.askId, agent: value.agent, text: value.data },
|
|
6059
|
+
done: false
|
|
6060
|
+
};
|
|
6061
|
+
}
|
|
5122
6062
|
}
|
|
5123
6063
|
}
|
|
5124
6064
|
};
|
|
5125
6065
|
}
|
|
5126
6066
|
};
|
|
5127
6067
|
}
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
6068
|
+
/**
|
|
6069
|
+
* Iterator over structural lifecycle events only — skips per-token chatter
|
|
6070
|
+
* and progressive partial_object emissions. Useful for waterfall UIs and
|
|
6071
|
+
* any consumer that wants the "what happened" timeline without per-chunk
|
|
6072
|
+
* noise. Renamed from `.steps` in the unified-event-model migration
|
|
6073
|
+
* because these are events, not pipeline steps.
|
|
6074
|
+
*/
|
|
6075
|
+
get lifecycle() {
|
|
6076
|
+
const lifecycleTypes = /* @__PURE__ */ new Set([
|
|
6077
|
+
"ask_start",
|
|
6078
|
+
"ask_end",
|
|
6079
|
+
"agent_call_start",
|
|
6080
|
+
"agent_call_end",
|
|
6081
|
+
"tool_call_start",
|
|
6082
|
+
"tool_call_end",
|
|
5134
6083
|
"tool_approval",
|
|
5135
|
-
"
|
|
6084
|
+
"tool_denied",
|
|
6085
|
+
"handoff_start",
|
|
6086
|
+
"handoff_return",
|
|
6087
|
+
"delegate",
|
|
6088
|
+
"pipeline",
|
|
6089
|
+
"verify",
|
|
6090
|
+
"workflow_start",
|
|
6091
|
+
"workflow_end",
|
|
6092
|
+
// Durable-execution checkpoints — structural points in the timeline.
|
|
6093
|
+
"checkpoint_save",
|
|
6094
|
+
"checkpoint_replay",
|
|
6095
|
+
// Human-in-the-loop — pause/resume are major timeline landmarks.
|
|
6096
|
+
"await_human",
|
|
6097
|
+
"await_human_resolved"
|
|
5136
6098
|
]);
|
|
5137
6099
|
const self = this;
|
|
5138
6100
|
return {
|
|
@@ -5143,7 +6105,7 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5143
6105
|
while (true) {
|
|
5144
6106
|
const { value, done } = await iter.next();
|
|
5145
6107
|
if (done) return { value: void 0, done: true };
|
|
5146
|
-
if (
|
|
6108
|
+
if (lifecycleTypes.has(value.type)) return { value, done: false };
|
|
5147
6109
|
}
|
|
5148
6110
|
}
|
|
5149
6111
|
};
|
|
@@ -5163,10 +6125,34 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5163
6125
|
});
|
|
5164
6126
|
return destination;
|
|
5165
6127
|
}
|
|
5166
|
-
/** Push
|
|
6128
|
+
/** Push an event onto the stream. Called by the runtime. */
|
|
5167
6129
|
_push(event) {
|
|
5168
6130
|
if (this.finished) return;
|
|
5169
|
-
if (event.type === "token"
|
|
6131
|
+
if (event.type === "token" && isRootLevel(event)) {
|
|
6132
|
+
const key = event.askId ?? "";
|
|
6133
|
+
const attempt = this.attemptByAsk.get(key);
|
|
6134
|
+
if (attempt) {
|
|
6135
|
+
attempt.push(event.data);
|
|
6136
|
+
} else {
|
|
6137
|
+
this.attemptByAsk.set(key, [event.data]);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
if (event.type === "pipeline" && isRootLevel(event)) {
|
|
6141
|
+
const key = event.askId ?? "";
|
|
6142
|
+
if (event.status === "committed") {
|
|
6143
|
+
const attempt = this.attemptByAsk.get(key);
|
|
6144
|
+
if (attempt && attempt.length > 0) {
|
|
6145
|
+
const prev = this.committedByAsk.get(key) ?? "";
|
|
6146
|
+
this.committedByAsk.set(key, prev + attempt.join(""));
|
|
6147
|
+
this.attemptByAsk.set(key, []);
|
|
6148
|
+
}
|
|
6149
|
+
} else if (event.status === "failed") {
|
|
6150
|
+
this.attemptByAsk.set(key, []);
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
if (event.type === "ask_end" && isRootLevel(event) && !event.outcome.ok) {
|
|
6154
|
+
this.attemptByAsk.set(event.askId ?? "", []);
|
|
6155
|
+
}
|
|
5170
6156
|
this.bus.emit(event.type, event);
|
|
5171
6157
|
this.push(event);
|
|
5172
6158
|
const waiter = this.waiters.shift();
|
|
@@ -5176,12 +6162,25 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5176
6162
|
this.eventQueue.push(event);
|
|
5177
6163
|
}
|
|
5178
6164
|
}
|
|
5179
|
-
/**
|
|
5180
|
-
|
|
6165
|
+
/**
|
|
6166
|
+
* Signal successful completion.
|
|
6167
|
+
*
|
|
6168
|
+
* `executionId` is required — the runtime must allocate it before
|
|
6169
|
+
* calling `stream()` so terminal events always carry a real id.
|
|
6170
|
+
* Previously the default-empty parameter surfaced blank executionIds
|
|
6171
|
+
* on error paths that threw before `execInfo` was assigned (review S4).
|
|
6172
|
+
*/
|
|
6173
|
+
_done(result, executionId) {
|
|
5181
6174
|
if (this.finished) return;
|
|
5182
6175
|
this.finished = true;
|
|
5183
6176
|
this.result = result;
|
|
5184
|
-
const doneEvent = {
|
|
6177
|
+
const doneEvent = {
|
|
6178
|
+
type: "done",
|
|
6179
|
+
executionId,
|
|
6180
|
+
step: Number.MAX_SAFE_INTEGER,
|
|
6181
|
+
timestamp: Date.now(),
|
|
6182
|
+
data: { result }
|
|
6183
|
+
};
|
|
5185
6184
|
this.bus.emit("done", doneEvent);
|
|
5186
6185
|
this.push(doneEvent);
|
|
5187
6186
|
this.push(null);
|
|
@@ -5197,11 +6196,19 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5197
6196
|
this.waiters.length = 0;
|
|
5198
6197
|
this.bus.emit("__resolve", result);
|
|
5199
6198
|
}
|
|
5200
|
-
/** Signal an error.
|
|
5201
|
-
|
|
6199
|
+
/** Signal an error. `executionId` is required for the same reason as
|
|
6200
|
+
* `_done`: terminal events must carry a real id even when the
|
|
6201
|
+
* failure happens before any real trace event fires (review S4). */
|
|
6202
|
+
_error(error, executionId) {
|
|
5202
6203
|
if (this.finished) return;
|
|
5203
6204
|
this.finished = true;
|
|
5204
|
-
const errorEvent = {
|
|
6205
|
+
const errorEvent = {
|
|
6206
|
+
type: "error",
|
|
6207
|
+
executionId,
|
|
6208
|
+
step: Number.MAX_SAFE_INTEGER,
|
|
6209
|
+
timestamp: Date.now(),
|
|
6210
|
+
data: { message: error.message, name: error.name }
|
|
6211
|
+
};
|
|
5205
6212
|
this.bus.emit("error", errorEvent);
|
|
5206
6213
|
this.push(errorEvent);
|
|
5207
6214
|
this.push(null);
|
|
@@ -5217,8 +6224,31 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
5217
6224
|
this.waiters.length = 0;
|
|
5218
6225
|
this.bus.emit("__reject", error);
|
|
5219
6226
|
}
|
|
6227
|
+
/** Concatenated root-only text from committed attempts plus the
|
|
6228
|
+
* current in-flight attempt(s). Retried (gate-rejected) attempts
|
|
6229
|
+
* are excluded — see spec/16 §4.3. Reading mid-attempt returns the
|
|
6230
|
+
* in-progress text; reading after `pipeline(committed)` (which
|
|
6231
|
+
* fires before `done`) returns the canonical winning text.
|
|
6232
|
+
*
|
|
6233
|
+
* With concurrent root-level asks (`ctx.parallel`, `ctx.spawn`,
|
|
6234
|
+
* `ctx.race`, `ctx.map`), each branch's tokens are scoped per-`askId`
|
|
6235
|
+
* and emitted contiguously in the order each ask first started
|
|
6236
|
+
* emitting tokens. A `pipeline(failed)` or `ask_end({ok:false})` on
|
|
6237
|
+
* one branch only discards THAT branch's in-progress buffer —
|
|
6238
|
+
* sibling branches are unaffected. For UIs that want each sub-agent
|
|
6239
|
+
* in its own lane, prefer `textByAsk`. */
|
|
5220
6240
|
get fullText() {
|
|
5221
|
-
|
|
6241
|
+
let out = "";
|
|
6242
|
+
for (const [key, committed] of this.committedByAsk) {
|
|
6243
|
+
out += committed;
|
|
6244
|
+
const attempt = this.attemptByAsk.get(key);
|
|
6245
|
+
if (attempt && attempt.length > 0) out += attempt.join("");
|
|
6246
|
+
}
|
|
6247
|
+
for (const [key, attempt] of this.attemptByAsk) {
|
|
6248
|
+
if (this.committedByAsk.has(key)) continue;
|
|
6249
|
+
if (attempt.length > 0) out += attempt.join("");
|
|
6250
|
+
}
|
|
6251
|
+
return out;
|
|
5222
6252
|
}
|
|
5223
6253
|
};
|
|
5224
6254
|
|
|
@@ -5764,6 +6794,32 @@ function hashInput(input) {
|
|
|
5764
6794
|
}
|
|
5765
6795
|
return (hash >>> 0).toString(16);
|
|
5766
6796
|
}
|
|
6797
|
+
var DEFAULT_MAX_EVENTS_PER_EXECUTION = 5e4;
|
|
6798
|
+
function isAbortError(err) {
|
|
6799
|
+
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
6800
|
+
}
|
|
6801
|
+
function pushEventBounded(execInfo, event, cap) {
|
|
6802
|
+
if (event.type === "token" || event.type === "partial_object") return;
|
|
6803
|
+
if (cap === Infinity || execInfo.events.length < cap) {
|
|
6804
|
+
execInfo.events.push(event);
|
|
6805
|
+
return;
|
|
6806
|
+
}
|
|
6807
|
+
const last = execInfo.events[execInfo.events.length - 1];
|
|
6808
|
+
const alreadyTruncated = last?.type === "log" && typeof last.data === "object" && last.data !== null && last.data.event === "events_truncated";
|
|
6809
|
+
if (!alreadyTruncated) {
|
|
6810
|
+
execInfo.events[execInfo.events.length - 1] = {
|
|
6811
|
+
type: "log",
|
|
6812
|
+
executionId: event.executionId,
|
|
6813
|
+
step: event.step,
|
|
6814
|
+
timestamp: Date.now(),
|
|
6815
|
+
data: {
|
|
6816
|
+
event: "events_truncated",
|
|
6817
|
+
cap,
|
|
6818
|
+
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.`
|
|
6819
|
+
}
|
|
6820
|
+
};
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
5767
6823
|
var costScopeStorage = new AsyncLocalStorage2();
|
|
5768
6824
|
var AxlRuntime = class extends EventEmitter2 {
|
|
5769
6825
|
config;
|
|
@@ -5783,11 +6839,27 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5783
6839
|
historicalExecutionsLoadPromise = null;
|
|
5784
6840
|
evalHistory = [];
|
|
5785
6841
|
evalHistoryLoadPromise = null;
|
|
6842
|
+
/** Resolved cap on `ExecutionInfo.events` per execution. Cached on
|
|
6843
|
+
* the instance so per-event onTrace handlers don't reach into
|
|
6844
|
+
* `this.config.state` on every emission. */
|
|
6845
|
+
maxEventsPerExecution;
|
|
5786
6846
|
constructor(config) {
|
|
5787
6847
|
super();
|
|
5788
6848
|
this.config = resolveConfig(config ?? {});
|
|
5789
6849
|
this.providerRegistry = new ProviderRegistry();
|
|
5790
6850
|
this.stateStore = this.createStateStore();
|
|
6851
|
+
const requested = this.config.state?.maxEventsPerExecution;
|
|
6852
|
+
if (requested === void 0) {
|
|
6853
|
+
this.maxEventsPerExecution = DEFAULT_MAX_EVENTS_PER_EXECUTION;
|
|
6854
|
+
} else if (requested === Infinity) {
|
|
6855
|
+
this.maxEventsPerExecution = Infinity;
|
|
6856
|
+
} else if (typeof requested !== "number" || !Number.isFinite(requested) || !Number.isInteger(requested) || requested < 1) {
|
|
6857
|
+
throw new RangeError(
|
|
6858
|
+
`config.state.maxEventsPerExecution must be a positive integer or Infinity; got ${requested}`
|
|
6859
|
+
);
|
|
6860
|
+
} else {
|
|
6861
|
+
this.maxEventsPerExecution = requested;
|
|
6862
|
+
}
|
|
5791
6863
|
if (this.config.memory) {
|
|
5792
6864
|
this.memoryManager = new MemoryManager({
|
|
5793
6865
|
vectorStore: this.config.memory.vectorStore,
|
|
@@ -5843,6 +6915,71 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5843
6915
|
return new MemoryStore();
|
|
5844
6916
|
}
|
|
5845
6917
|
}
|
|
6918
|
+
/**
|
|
6919
|
+
* Run a workflow body inside an ALREADY-CONSTRUCTED `WorkflowContext`
|
|
6920
|
+
* with full lifecycle invariants: emit `workflow_start` → run handler
|
|
6921
|
+
* → parse output → emit `workflow_end` → delete checkpoints → persist.
|
|
6922
|
+
*
|
|
6923
|
+
* Shared between `execute()` and `stream()`. Centralizes the
|
|
6924
|
+
* "start iff end" pairing invariant so a future emit path
|
|
6925
|
+
* (e.g., `resumeExecution`) can inherit correct behavior by routing
|
|
6926
|
+
* through this helper. Also factors out the AbortError detection.
|
|
6927
|
+
*
|
|
6928
|
+
* Side effects on `execInfo`: sets `status` / `completedAt` /
|
|
6929
|
+
* `duration` / `result` (or `error`) before emitting `workflow_end`.
|
|
6930
|
+
* On throw: emits `workflow_end({status: 'failed', aborted?})` and
|
|
6931
|
+
* persists before rethrowing. `abortControllers.delete` runs in
|
|
6932
|
+
* `finally` regardless of outcome.
|
|
6933
|
+
*
|
|
6934
|
+
* Caller responsibilities:
|
|
6935
|
+
* - Allocate `executionId` and register it in `abortControllers` map
|
|
6936
|
+
* BEFORE calling this helper (so early-throw paths still have a
|
|
6937
|
+
* correlation id available for terminal events).
|
|
6938
|
+
* - Construct `execInfo` and `ctx` before calling.
|
|
6939
|
+
* - Pass `span` if the call site is inside a tracer span — this
|
|
6940
|
+
* helper sets `axl.workflow.cost` / `.duration` attributes on
|
|
6941
|
+
* success.
|
|
6942
|
+
*/
|
|
6943
|
+
async runWorkflowBody(args) {
|
|
6944
|
+
const { workflow: workflow2, ctx, execInfo, validated, span } = args;
|
|
6945
|
+
try {
|
|
6946
|
+
ctx._emitWorkflowStart(validated);
|
|
6947
|
+
const raw = await workflow2.handler(ctx);
|
|
6948
|
+
const result = workflow2.outputSchema ? workflow2.outputSchema.parse(raw) : raw;
|
|
6949
|
+
execInfo.status = "completed";
|
|
6950
|
+
execInfo.completedAt = Date.now();
|
|
6951
|
+
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6952
|
+
execInfo.result = result;
|
|
6953
|
+
ctx._emitWorkflowEnd({
|
|
6954
|
+
status: "completed",
|
|
6955
|
+
duration: execInfo.duration,
|
|
6956
|
+
result
|
|
6957
|
+
});
|
|
6958
|
+
if (this.stateStore.deleteCheckpoints) {
|
|
6959
|
+
await this.stateStore.deleteCheckpoints(execInfo.executionId);
|
|
6960
|
+
}
|
|
6961
|
+
span?.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
6962
|
+
span?.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
6963
|
+
this.persistExecution(execInfo);
|
|
6964
|
+
return result;
|
|
6965
|
+
} catch (err) {
|
|
6966
|
+
const aborted = isAbortError(err);
|
|
6967
|
+
execInfo.status = "failed";
|
|
6968
|
+
execInfo.completedAt = Date.now();
|
|
6969
|
+
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6970
|
+
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
6971
|
+
ctx._emitWorkflowEnd({
|
|
6972
|
+
status: "failed",
|
|
6973
|
+
duration: execInfo.duration,
|
|
6974
|
+
error: execInfo.error,
|
|
6975
|
+
...aborted ? { aborted: true } : {}
|
|
6976
|
+
});
|
|
6977
|
+
this.persistExecution(execInfo);
|
|
6978
|
+
throw err;
|
|
6979
|
+
} finally {
|
|
6980
|
+
this.abortControllers.delete(execInfo.executionId);
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
5846
6983
|
/**
|
|
5847
6984
|
* Persist a completed/failed execution to the state store (fire-and-forget)
|
|
5848
6985
|
* and move it from the active executions map to the historical cache.
|
|
@@ -5858,7 +6995,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5858
6995
|
this.executions.delete(id);
|
|
5859
6996
|
}
|
|
5860
6997
|
/** Register a workflow with the runtime. */
|
|
5861
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5862
6998
|
register(workflow2) {
|
|
5863
6999
|
this.workflows.set(workflow2.name, workflow2);
|
|
5864
7000
|
}
|
|
@@ -5984,7 +7120,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5984
7120
|
}
|
|
5985
7121
|
try {
|
|
5986
7122
|
await this.saveEvalResult({
|
|
5987
|
-
id: resultObj.id ??
|
|
7123
|
+
id: resultObj.id ?? randomUUID3(),
|
|
5988
7124
|
eval: name,
|
|
5989
7125
|
timestamp: Date.now(),
|
|
5990
7126
|
data: structuredClone(result)
|
|
@@ -6021,7 +7157,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6021
7157
|
* and automatically emits trace events and tracks cost.
|
|
6022
7158
|
*/
|
|
6023
7159
|
createContext(options) {
|
|
6024
|
-
const executionId =
|
|
7160
|
+
const executionId = randomUUID3();
|
|
6025
7161
|
const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
|
|
6026
7162
|
this.registerWithCostScope(executionId);
|
|
6027
7163
|
return new WorkflowContext({
|
|
@@ -6037,10 +7173,12 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6037
7173
|
sessionHistory: options?.sessionHistory,
|
|
6038
7174
|
signal: options?.signal,
|
|
6039
7175
|
onToken: options?.onToken,
|
|
7176
|
+
onToolCall: options?.onToolCall,
|
|
7177
|
+
onAgentStart: options?.onAgentStart,
|
|
6040
7178
|
awaitHumanHandler: options?.awaitHumanHandler,
|
|
6041
7179
|
onTrace: (event) => {
|
|
6042
7180
|
this.emit("trace", event);
|
|
6043
|
-
this.
|
|
7181
|
+
this.outputAxlEvent(event);
|
|
6044
7182
|
},
|
|
6045
7183
|
budgetContext: {
|
|
6046
7184
|
totalCost: 0,
|
|
@@ -6067,7 +7205,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6067
7205
|
);
|
|
6068
7206
|
}
|
|
6069
7207
|
const validated = workflow2.inputSchema.parse(input);
|
|
6070
|
-
const executionId =
|
|
7208
|
+
const executionId = randomUUID3();
|
|
6071
7209
|
const controller = new AbortController();
|
|
6072
7210
|
this.abortControllers.set(executionId, controller);
|
|
6073
7211
|
this.registerWithCostScope(executionId);
|
|
@@ -6075,7 +7213,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6075
7213
|
executionId,
|
|
6076
7214
|
workflow: name,
|
|
6077
7215
|
status: "running",
|
|
6078
|
-
|
|
7216
|
+
events: [],
|
|
6079
7217
|
totalCost: 0,
|
|
6080
7218
|
startedAt: Date.now(),
|
|
6081
7219
|
duration: 0
|
|
@@ -6091,20 +7229,35 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6091
7229
|
sessionHistory,
|
|
6092
7230
|
signal: controller.signal,
|
|
6093
7231
|
onTrace: (event) => {
|
|
6094
|
-
execInfo.
|
|
6095
|
-
|
|
7232
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7233
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
6096
7234
|
this.emit("trace", event);
|
|
6097
|
-
this.
|
|
6098
|
-
if (event.type === "
|
|
7235
|
+
this.outputAxlEvent(event);
|
|
7236
|
+
if (event.type === "handoff_start") {
|
|
6099
7237
|
const sessionId = options?.metadata?.sessionId;
|
|
6100
7238
|
if (sessionId) {
|
|
6101
|
-
const data = event.data;
|
|
6102
7239
|
this.appendHandoffRecord(sessionId, {
|
|
6103
|
-
source: event.
|
|
6104
|
-
target: data
|
|
6105
|
-
mode: data
|
|
7240
|
+
source: event.data.source,
|
|
7241
|
+
target: event.data.target,
|
|
7242
|
+
mode: event.data.mode,
|
|
6106
7243
|
timestamp: event.timestamp,
|
|
6107
|
-
|
|
7244
|
+
toAskId: event.toAskId
|
|
7245
|
+
});
|
|
7246
|
+
}
|
|
7247
|
+
} else if (event.type === "handoff_return") {
|
|
7248
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7249
|
+
if (sessionId) {
|
|
7250
|
+
this.updateHandoffDuration(sessionId, {
|
|
7251
|
+
toAskId: event.toAskId,
|
|
7252
|
+
duration: event.data.duration
|
|
7253
|
+
});
|
|
7254
|
+
}
|
|
7255
|
+
} else if (event.type === "ask_end") {
|
|
7256
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7257
|
+
if (sessionId && event.askId && typeof event.duration === "number") {
|
|
7258
|
+
this.updateHandoffDuration(sessionId, {
|
|
7259
|
+
toAskId: event.askId,
|
|
7260
|
+
duration: event.duration
|
|
6108
7261
|
});
|
|
6109
7262
|
}
|
|
6110
7263
|
}
|
|
@@ -6131,45 +7284,13 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6131
7284
|
"axl.execution.id": executionId,
|
|
6132
7285
|
"axl.workflow.input_hash": hashInput(validated)
|
|
6133
7286
|
},
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6142
|
-
execInfo.result = output;
|
|
6143
|
-
ctx._emitWorkflowEnd({
|
|
6144
|
-
status: "completed",
|
|
6145
|
-
duration: execInfo.duration,
|
|
6146
|
-
result: output
|
|
6147
|
-
});
|
|
6148
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
6149
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
6150
|
-
}
|
|
6151
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
6152
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
6153
|
-
this.persistExecution(execInfo);
|
|
6154
|
-
return output;
|
|
6155
|
-
} catch (err) {
|
|
6156
|
-
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
6157
|
-
execInfo.status = "failed";
|
|
6158
|
-
execInfo.completedAt = Date.now();
|
|
6159
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6160
|
-
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
6161
|
-
ctx._emitWorkflowEnd({
|
|
6162
|
-
status: "failed",
|
|
6163
|
-
duration: execInfo.duration,
|
|
6164
|
-
error: execInfo.error,
|
|
6165
|
-
...aborted ? { aborted: true } : {}
|
|
6166
|
-
});
|
|
6167
|
-
this.persistExecution(execInfo);
|
|
6168
|
-
throw err;
|
|
6169
|
-
} finally {
|
|
6170
|
-
this.abortControllers.delete(executionId);
|
|
6171
|
-
}
|
|
6172
|
-
}
|
|
7287
|
+
(span) => this.runWorkflowBody({
|
|
7288
|
+
workflow: workflow2,
|
|
7289
|
+
ctx,
|
|
7290
|
+
execInfo,
|
|
7291
|
+
validated,
|
|
7292
|
+
span
|
|
7293
|
+
})
|
|
6173
7294
|
);
|
|
6174
7295
|
}
|
|
6175
7296
|
/** Execute a workflow and return a stream. */
|
|
@@ -6177,21 +7298,20 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6177
7298
|
const axlStream = new AxlStream();
|
|
6178
7299
|
const controller = new AbortController();
|
|
6179
7300
|
axlStream.on("close", () => controller.abort());
|
|
7301
|
+
const executionId = randomUUID3();
|
|
7302
|
+
this.abortControllers.set(executionId, controller);
|
|
6180
7303
|
let execInfo;
|
|
6181
|
-
let ctx;
|
|
6182
7304
|
const run = async () => {
|
|
6183
7305
|
const workflow2 = this.workflows.get(name);
|
|
6184
7306
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
6185
7307
|
const validated = workflow2.inputSchema.parse(input);
|
|
6186
|
-
const executionId = randomUUID2();
|
|
6187
|
-
this.abortControllers.set(executionId, controller);
|
|
6188
7308
|
const sessionHistory = options?.metadata?.sessionHistory ?? void 0;
|
|
6189
7309
|
this.registerWithCostScope(executionId);
|
|
6190
7310
|
execInfo = {
|
|
6191
7311
|
executionId,
|
|
6192
7312
|
workflow: name,
|
|
6193
7313
|
status: "running",
|
|
6194
|
-
|
|
7314
|
+
events: [],
|
|
6195
7315
|
totalCost: 0,
|
|
6196
7316
|
startedAt: Date.now(),
|
|
6197
7317
|
duration: 0
|
|
@@ -6205,69 +7325,46 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6205
7325
|
providerRegistry: this.providerRegistry,
|
|
6206
7326
|
sessionHistory,
|
|
6207
7327
|
signal: controller.signal,
|
|
7328
|
+
// Sentinel `onToken` so the streaming code path in WorkflowContext
|
|
7329
|
+
// is entered (it's gated on `this.onToken` being defined). The
|
|
7330
|
+
// actual `token` AxlEvents are emitted via `emitEvent` and reach
|
|
7331
|
+
// the wire through `onTrace` — this callback is just the gate.
|
|
7332
|
+
onToken: () => {
|
|
7333
|
+
},
|
|
6208
7334
|
onTrace: (event) => {
|
|
6209
|
-
execInfo.
|
|
6210
|
-
|
|
7335
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7336
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
6211
7337
|
this.emit("trace", event);
|
|
6212
|
-
this.
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
axlStream._push({
|
|
6216
|
-
type: "handoff",
|
|
6217
|
-
source: event.agent ?? "",
|
|
6218
|
-
target: data?.target ?? "",
|
|
6219
|
-
mode: data?.mode ?? void 0
|
|
6220
|
-
});
|
|
7338
|
+
this.outputAxlEvent(event);
|
|
7339
|
+
axlStream._push(event);
|
|
7340
|
+
if (event.type === "handoff_start") {
|
|
6221
7341
|
const sessionId = options?.metadata?.sessionId;
|
|
6222
7342
|
if (sessionId) {
|
|
6223
7343
|
this.appendHandoffRecord(sessionId, {
|
|
6224
|
-
source: event.
|
|
6225
|
-
target: data
|
|
6226
|
-
mode: data
|
|
7344
|
+
source: event.data.source,
|
|
7345
|
+
target: event.data.target,
|
|
7346
|
+
mode: event.data.mode,
|
|
6227
7347
|
timestamp: event.timestamp,
|
|
6228
|
-
|
|
7348
|
+
toAskId: event.toAskId
|
|
7349
|
+
});
|
|
7350
|
+
}
|
|
7351
|
+
} else if (event.type === "handoff_return") {
|
|
7352
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7353
|
+
if (sessionId) {
|
|
7354
|
+
this.updateHandoffDuration(sessionId, {
|
|
7355
|
+
toAskId: event.toAskId,
|
|
7356
|
+
duration: event.data.duration
|
|
7357
|
+
});
|
|
7358
|
+
}
|
|
7359
|
+
} else if (event.type === "ask_end") {
|
|
7360
|
+
const sessionId = options?.metadata?.sessionId;
|
|
7361
|
+
if (sessionId && event.askId && typeof event.duration === "number") {
|
|
7362
|
+
this.updateHandoffDuration(sessionId, {
|
|
7363
|
+
toAskId: event.askId,
|
|
7364
|
+
duration: event.duration
|
|
6229
7365
|
});
|
|
6230
7366
|
}
|
|
6231
|
-
} else if (event.type === "tool_approval") {
|
|
6232
|
-
const data = event.data;
|
|
6233
|
-
axlStream._push({
|
|
6234
|
-
type: "tool_approval",
|
|
6235
|
-
name: event.tool ?? "",
|
|
6236
|
-
args: data?.args,
|
|
6237
|
-
approved: data?.approved === true,
|
|
6238
|
-
...data?.reason ? { reason: data.reason } : {}
|
|
6239
|
-
});
|
|
6240
|
-
} else if (event.type === "tool_denied") {
|
|
6241
|
-
} else if (event.type === "agent_call") {
|
|
6242
|
-
axlStream._push({
|
|
6243
|
-
type: "agent_end",
|
|
6244
|
-
agent: event.agent ?? "",
|
|
6245
|
-
cost: event.cost,
|
|
6246
|
-
duration: event.duration
|
|
6247
|
-
});
|
|
6248
|
-
} else if (event.type === "tool_call") {
|
|
6249
|
-
axlStream._push({
|
|
6250
|
-
type: "tool_result",
|
|
6251
|
-
name: event.tool ?? "",
|
|
6252
|
-
result: event.data?.result,
|
|
6253
|
-
callId: event.data?.callId
|
|
6254
|
-
});
|
|
6255
7367
|
}
|
|
6256
|
-
axlStream._push({ type: "step", step: event.step, data: event });
|
|
6257
|
-
},
|
|
6258
|
-
onToken: (token) => {
|
|
6259
|
-
axlStream._push({ type: "token", data: token });
|
|
6260
|
-
},
|
|
6261
|
-
onToolCall: (call) => {
|
|
6262
|
-
axlStream._push({
|
|
6263
|
-
type: "tool_call",
|
|
6264
|
-
name: call.name,
|
|
6265
|
-
args: call.args,
|
|
6266
|
-
callId: call.callId
|
|
6267
|
-
});
|
|
6268
|
-
},
|
|
6269
|
-
onAgentStart: (info) => {
|
|
6270
|
-
axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
|
|
6271
7368
|
},
|
|
6272
7369
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
6273
7370
|
awaitHumanHandler: options?.awaitHumanHandler,
|
|
@@ -6284,7 +7381,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6284
7381
|
policy: "finish_and_stop"
|
|
6285
7382
|
}
|
|
6286
7383
|
});
|
|
6287
|
-
ctx = wfCtx;
|
|
6288
7384
|
return this.spanManager.withSpanAsync(
|
|
6289
7385
|
"axl.workflow.execute",
|
|
6290
7386
|
{
|
|
@@ -6292,49 +7388,25 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6292
7388
|
"axl.execution.id": executionId,
|
|
6293
7389
|
"axl.workflow.input_hash": hashInput(validated)
|
|
6294
7390
|
},
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6303
|
-
execInfo.result = result;
|
|
6304
|
-
wfCtx._emitWorkflowEnd({
|
|
6305
|
-
status: "completed",
|
|
6306
|
-
duration: execInfo.duration,
|
|
6307
|
-
result
|
|
6308
|
-
});
|
|
6309
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
6310
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
6311
|
-
}
|
|
6312
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
6313
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
6314
|
-
this.persistExecution(execInfo);
|
|
6315
|
-
return result;
|
|
6316
|
-
} finally {
|
|
6317
|
-
this.abortControllers.delete(executionId);
|
|
6318
|
-
}
|
|
6319
|
-
}
|
|
7391
|
+
(span) => this.runWorkflowBody({
|
|
7392
|
+
workflow: workflow2,
|
|
7393
|
+
ctx: wfCtx,
|
|
7394
|
+
execInfo,
|
|
7395
|
+
validated,
|
|
7396
|
+
span
|
|
7397
|
+
})
|
|
6320
7398
|
);
|
|
6321
7399
|
};
|
|
6322
|
-
run().then((result) => axlStream._done(result)).catch((err) => {
|
|
6323
|
-
if (execInfo) {
|
|
6324
|
-
const aborted = typeof err === "object" && err !== null && err.name === "AbortError";
|
|
7400
|
+
run().then((result) => axlStream._done(result, executionId)).catch((err) => {
|
|
7401
|
+
if (execInfo && execInfo.status === "running") {
|
|
6325
7402
|
execInfo.status = "failed";
|
|
6326
7403
|
execInfo.completedAt = Date.now();
|
|
6327
7404
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
6328
7405
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
6329
|
-
ctx?._emitWorkflowEnd({
|
|
6330
|
-
status: "failed",
|
|
6331
|
-
duration: execInfo.duration,
|
|
6332
|
-
error: execInfo.error,
|
|
6333
|
-
...aborted ? { aborted: true } : {}
|
|
6334
|
-
});
|
|
6335
7406
|
this.persistExecution(execInfo);
|
|
6336
7407
|
}
|
|
6337
|
-
|
|
7408
|
+
this.abortControllers.delete(executionId);
|
|
7409
|
+
axlStream._error(err instanceof Error ? err : new Error(String(err)), executionId);
|
|
6338
7410
|
});
|
|
6339
7411
|
return axlStream;
|
|
6340
7412
|
}
|
|
@@ -6594,11 +7666,11 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6594
7666
|
* need embedder token counts, subscribe to `runtime.on('trace', ...)` and
|
|
6595
7667
|
* read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
|
|
6596
7668
|
*
|
|
6597
|
-
* Pass `{ captureTraces: true }` to also collect the raw `
|
|
7669
|
+
* Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
|
|
6598
7670
|
* during `fn()`. This is opt-in because it keeps every event in memory for the
|
|
6599
7671
|
* duration of the call — useful for eval per-item capture, debugging, and test
|
|
6600
7672
|
* assertions, but overhead grows with trace volume. When enabled, verbose-mode
|
|
6601
|
-
* `
|
|
7673
|
+
* `agent_call_start.data.messages` snapshots are omitted from captured events (still
|
|
6602
7674
|
* broadcast via onTrace) to keep memory bounded — callers who need the full
|
|
6603
7675
|
* verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
|
|
6604
7676
|
*
|
|
@@ -6618,8 +7690,8 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6618
7690
|
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
6619
7691
|
const listener = (event) => {
|
|
6620
7692
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
6621
|
-
|
|
6622
|
-
if (event.type === "
|
|
7693
|
+
scope.totalCost += eventCostContribution(event);
|
|
7694
|
+
if (event.type === "agent_call_end") {
|
|
6623
7695
|
if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
|
|
6624
7696
|
agentCalls++;
|
|
6625
7697
|
if (event.tokens) {
|
|
@@ -6632,7 +7704,8 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6632
7704
|
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
6633
7705
|
}
|
|
6634
7706
|
if (capturedTraces) {
|
|
6635
|
-
if (event.type === "
|
|
7707
|
+
if (event.type === "token" || event.type === "partial_object") return;
|
|
7708
|
+
if (event.type === "agent_call_start" && event.data) {
|
|
6636
7709
|
const d = event.data;
|
|
6637
7710
|
if ("messages" in d) {
|
|
6638
7711
|
const rest = {};
|
|
@@ -6697,7 +7770,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6697
7770
|
* The emit('trace', event) call happens before this method is called, so
|
|
6698
7771
|
* programmatic subscribers always receive events regardless of trace config.
|
|
6699
7772
|
*/
|
|
6700
|
-
|
|
7773
|
+
outputAxlEvent(event) {
|
|
6701
7774
|
const traceConfig = this.config.trace;
|
|
6702
7775
|
if (!traceConfig?.enabled) return;
|
|
6703
7776
|
const level = traceConfig.level ?? "steps";
|
|
@@ -6721,9 +7794,9 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6721
7794
|
}
|
|
6722
7795
|
return;
|
|
6723
7796
|
}
|
|
6724
|
-
this.
|
|
7797
|
+
this.logAxlEvent(event);
|
|
6725
7798
|
}
|
|
6726
|
-
|
|
7799
|
+
logAxlEvent(event) {
|
|
6727
7800
|
const level = this.config.trace?.level ?? "steps";
|
|
6728
7801
|
const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
|
|
6729
7802
|
const parts = [`[axl] execution:${event.executionId}`];
|
|
@@ -6734,7 +7807,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6734
7807
|
const status = d?.aborted ? "aborted" : d?.status ?? "completed";
|
|
6735
7808
|
parts.push(`${workflowPrefix}${status}`);
|
|
6736
7809
|
if (event.duration != null) parts.push(`${(event.duration / 1e3).toFixed(1)}s`);
|
|
6737
|
-
} else if (event.type === "
|
|
7810
|
+
} else if (event.type === "agent_call_end") {
|
|
6738
7811
|
parts.push(`${workflowPrefix}step:${event.step} agent_call`);
|
|
6739
7812
|
if (event.agent) parts.push(`agent:${event.agent}`);
|
|
6740
7813
|
if (event.promptVersion) parts.push(`version:${event.promptVersion}`);
|
|
@@ -6744,7 +7817,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6744
7817
|
if (level === "full" && event.data) {
|
|
6745
7818
|
parts.push(`data:${JSON.stringify(event.data)}`);
|
|
6746
7819
|
}
|
|
6747
|
-
} else if (event.type === "
|
|
7820
|
+
} else if (event.type === "tool_call_end") {
|
|
6748
7821
|
parts.push(`${workflowPrefix}step:${event.step} tool_call`);
|
|
6749
7822
|
if (event.tool) parts.push(`tool:${event.tool}`);
|
|
6750
7823
|
if (event.duration) parts.push(`${event.duration}ms`);
|
|
@@ -6761,8 +7834,9 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6761
7834
|
parts.push(`${workflowPrefix}log: ${JSON.stringify(event.data)}`);
|
|
6762
7835
|
} else {
|
|
6763
7836
|
parts.push(`${workflowPrefix}${event.type}`);
|
|
6764
|
-
|
|
6765
|
-
|
|
7837
|
+
const data = event.data;
|
|
7838
|
+
if (level === "full" && data !== void 0) {
|
|
7839
|
+
parts.push(`data:${JSON.stringify(data)}`);
|
|
6766
7840
|
}
|
|
6767
7841
|
}
|
|
6768
7842
|
console.log(parts.join(" | "));
|
|
@@ -6782,6 +7856,35 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6782
7856
|
}).catch(() => {
|
|
6783
7857
|
});
|
|
6784
7858
|
}
|
|
7859
|
+
/**
|
|
7860
|
+
* Patch `duration` on the handoff record matching `toAskId` (the target
|
|
7861
|
+
* frame's askId, which the runtime stamps on `handoff_start` and which
|
|
7862
|
+
* appears as `askId` on the target's `ask_end`).
|
|
7863
|
+
*
|
|
7864
|
+
* Called from two places per handoff:
|
|
7865
|
+
* - target's `ask_end` — first-write source for ONEWAY (no return event).
|
|
7866
|
+
* - `handoff_return` — overwrites for ROUNDTRIP with the round-trip
|
|
7867
|
+
* wall-clock figure (intentional: round-trip
|
|
7868
|
+
* duration is the more useful metric for the
|
|
7869
|
+
* source-paused-for-N-ms semantic).
|
|
7870
|
+
*
|
|
7871
|
+
* No-op if no record matches `toAskId` (e.g., stream subscribed mid-
|
|
7872
|
+
* handoff, or session metadata got wiped) — `duration` stays undefined,
|
|
7873
|
+
* which is the documented default for `HandoffRecord`.
|
|
7874
|
+
*/
|
|
7875
|
+
updateHandoffDuration(sessionId, match) {
|
|
7876
|
+
this.stateStore.getSessionMeta(sessionId, "handoffHistory").then((existing) => {
|
|
7877
|
+
const history = existing ?? [];
|
|
7878
|
+
for (const r of history) {
|
|
7879
|
+
if (r.toAskId === match.toAskId) {
|
|
7880
|
+
r.duration = match.duration;
|
|
7881
|
+
return this.stateStore.saveSessionMeta(sessionId, "handoffHistory", history);
|
|
7882
|
+
}
|
|
7883
|
+
}
|
|
7884
|
+
return void 0;
|
|
7885
|
+
}).catch(() => {
|
|
7886
|
+
});
|
|
7887
|
+
}
|
|
6785
7888
|
};
|
|
6786
7889
|
|
|
6787
7890
|
// src/state/redis.ts
|
|
@@ -6844,27 +7947,13 @@ var RedisStore = class _RedisStore {
|
|
|
6844
7947
|
return "axl:eval-history-ids";
|
|
6845
7948
|
}
|
|
6846
7949
|
// ── Checkpoints ──────────────────────────────────────────────────────
|
|
6847
|
-
async saveCheckpoint(executionId,
|
|
6848
|
-
await this.client.hSet(this.checkpointKey(executionId),
|
|
7950
|
+
async saveCheckpoint(executionId, name, data) {
|
|
7951
|
+
await this.client.hSet(this.checkpointKey(executionId), name, JSON.stringify(data));
|
|
6849
7952
|
}
|
|
6850
|
-
async getCheckpoint(executionId,
|
|
6851
|
-
const raw = await this.client.hGet(this.checkpointKey(executionId),
|
|
7953
|
+
async getCheckpoint(executionId, name) {
|
|
7954
|
+
const raw = await this.client.hGet(this.checkpointKey(executionId), name);
|
|
6852
7955
|
return raw != null ? JSON.parse(raw) : null;
|
|
6853
7956
|
}
|
|
6854
|
-
async getLatestCheckpoint(executionId) {
|
|
6855
|
-
const all = await this.client.hGetAll(this.checkpointKey(executionId));
|
|
6856
|
-
if (!all || Object.keys(all).length === 0) return null;
|
|
6857
|
-
let maxStep = -1;
|
|
6858
|
-
let maxData = null;
|
|
6859
|
-
for (const [stepStr, raw] of Object.entries(all)) {
|
|
6860
|
-
const step = Number(stepStr);
|
|
6861
|
-
if (step > maxStep) {
|
|
6862
|
-
maxStep = step;
|
|
6863
|
-
maxData = JSON.parse(raw);
|
|
6864
|
-
}
|
|
6865
|
-
}
|
|
6866
|
-
return { step: maxStep, data: maxData };
|
|
6867
|
-
}
|
|
6868
7957
|
// ── Sessions ─────────────────────────────────────────────────────────
|
|
6869
7958
|
async saveSession(sessionId, history) {
|
|
6870
7959
|
await this.client.set(this.sessionKey(sessionId), JSON.stringify(history));
|
|
@@ -7149,11 +8238,13 @@ function cosineSimilarity2(a, b) {
|
|
|
7149
8238
|
return denom === 0 ? 0 : dot / denom;
|
|
7150
8239
|
}
|
|
7151
8240
|
export {
|
|
8241
|
+
AXL_EVENT_TYPES,
|
|
7152
8242
|
AnthropicProvider,
|
|
7153
8243
|
AxlError,
|
|
7154
8244
|
AxlRuntime,
|
|
7155
8245
|
AxlStream,
|
|
7156
8246
|
BudgetExceededError,
|
|
8247
|
+
COST_BEARING_LEAF_TYPES,
|
|
7157
8248
|
GeminiProvider,
|
|
7158
8249
|
GuardrailError,
|
|
7159
8250
|
InMemoryVectorStore,
|
|
@@ -7168,6 +8259,8 @@ export {
|
|
|
7168
8259
|
OpenAIResponsesProvider,
|
|
7169
8260
|
ProviderRegistry,
|
|
7170
8261
|
QuorumNotMet,
|
|
8262
|
+
REDACTED,
|
|
8263
|
+
REDACTION_RULES,
|
|
7171
8264
|
RedisStore,
|
|
7172
8265
|
SQLiteStore,
|
|
7173
8266
|
Session,
|
|
@@ -7180,7 +8273,12 @@ export {
|
|
|
7180
8273
|
agent,
|
|
7181
8274
|
createSpanManager,
|
|
7182
8275
|
defineConfig,
|
|
8276
|
+
eventCostContribution,
|
|
7183
8277
|
extractJson,
|
|
8278
|
+
isCostBearingLeaf,
|
|
8279
|
+
isRootLevel,
|
|
8280
|
+
parsePartialJson,
|
|
8281
|
+
redactEvent,
|
|
7184
8282
|
resolveThinkingOptions,
|
|
7185
8283
|
tool,
|
|
7186
8284
|
workflow,
|