@axlsdk/axl 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/dist/index.cjs +2257 -569
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1191 -114
- package/dist/index.d.ts +1191 -114
- package/dist/index.js +2248 -569
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.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
|
|
@@ -1965,6 +2003,21 @@ var TimeoutError = class extends AxlError {
|
|
|
1965
2003
|
this.name = "TimeoutError";
|
|
1966
2004
|
}
|
|
1967
2005
|
};
|
|
2006
|
+
function formatBudgetCost(cost) {
|
|
2007
|
+
if (Number.isNaN(cost)) return "$NaN";
|
|
2008
|
+
if (cost === Infinity) return "$Infinity";
|
|
2009
|
+
if (cost === -Infinity) return "-$Infinity";
|
|
2010
|
+
if (cost === 0) return "$0.00";
|
|
2011
|
+
const sign = cost < 0 ? "-" : "";
|
|
2012
|
+
const abs = Math.abs(cost);
|
|
2013
|
+
if (abs < 1e-6) return `${sign}< $0.000001`;
|
|
2014
|
+
if (abs < 1e-4) {
|
|
2015
|
+
const [mantissa, exponent] = abs.toExponential(2).split("e");
|
|
2016
|
+
return `${sign}$${mantissa}e${parseInt(exponent, 10)}`;
|
|
2017
|
+
}
|
|
2018
|
+
if (abs < 0.01) return `${sign}$${abs.toFixed(6)}`;
|
|
2019
|
+
return `${sign}$${abs.toFixed(2)}`;
|
|
2020
|
+
}
|
|
1968
2021
|
var BudgetExceededError = class extends AxlError {
|
|
1969
2022
|
limit;
|
|
1970
2023
|
spent;
|
|
@@ -1972,7 +2025,9 @@ var BudgetExceededError = class extends AxlError {
|
|
|
1972
2025
|
constructor(limit, spent, policy) {
|
|
1973
2026
|
super(
|
|
1974
2027
|
"BUDGET_EXCEEDED",
|
|
1975
|
-
`Budget exceeded: spent
|
|
2028
|
+
`Budget exceeded: spent ${formatBudgetCost(spent)} of ${formatBudgetCost(
|
|
2029
|
+
limit
|
|
2030
|
+
)} limit (policy: ${policy})`
|
|
1976
2031
|
);
|
|
1977
2032
|
this.name = "BudgetExceededError";
|
|
1978
2033
|
this.limit = limit;
|
|
@@ -2024,6 +2079,432 @@ var ToolDenied = class extends AxlError {
|
|
|
2024
2079
|
}
|
|
2025
2080
|
};
|
|
2026
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
|
+
|
|
2027
2508
|
// src/config.ts
|
|
2028
2509
|
function defineConfig(config) {
|
|
2029
2510
|
return config;
|
|
@@ -2101,6 +2582,7 @@ function resolveConfig(config) {
|
|
|
2101
2582
|
|
|
2102
2583
|
// src/context.ts
|
|
2103
2584
|
var signalStorage = new AsyncLocalStorage();
|
|
2585
|
+
var askStorage = new AsyncLocalStorage();
|
|
2104
2586
|
function zodToJsonSchema(schema) {
|
|
2105
2587
|
const result = z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
2106
2588
|
delete result.$schema;
|
|
@@ -2160,6 +2642,14 @@ function extractBalanced(str, start, openChar, closeChar) {
|
|
|
2160
2642
|
}
|
|
2161
2643
|
return null;
|
|
2162
2644
|
}
|
|
2645
|
+
function appendRetryMessages(messages, content, feedbackMessage, providerMetadata) {
|
|
2646
|
+
messages.push({
|
|
2647
|
+
role: "assistant",
|
|
2648
|
+
content,
|
|
2649
|
+
...providerMetadata ? { providerMetadata } : {}
|
|
2650
|
+
});
|
|
2651
|
+
messages.push({ role: "system", content: feedbackMessage });
|
|
2652
|
+
}
|
|
2163
2653
|
function estimateMessagesTokens(messages) {
|
|
2164
2654
|
let total = 0;
|
|
2165
2655
|
for (const msg of messages) {
|
|
@@ -2194,8 +2684,34 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2194
2684
|
pendingDecisions;
|
|
2195
2685
|
budgetContext;
|
|
2196
2686
|
stateStore;
|
|
2197
|
-
|
|
2198
|
-
|
|
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;
|
|
2199
2715
|
signal;
|
|
2200
2716
|
summaryCache;
|
|
2201
2717
|
workflowName;
|
|
@@ -2230,6 +2746,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2230
2746
|
this.awaitHumanHandler = init.awaitHumanHandler;
|
|
2231
2747
|
this.onAgentStart = init.onAgentStart;
|
|
2232
2748
|
this.onAgentCallComplete = init.onAgentCallComplete;
|
|
2749
|
+
this.autoCheckpointCounters = init.autoCheckpointCounters ?? {
|
|
2750
|
+
byAgent: /* @__PURE__ */ new Map(),
|
|
2751
|
+
root: 0
|
|
2752
|
+
};
|
|
2233
2753
|
if (init.metadata?.summaryCache) {
|
|
2234
2754
|
this.summaryCache = init.metadata.summaryCache;
|
|
2235
2755
|
}
|
|
@@ -2238,8 +2758,21 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2238
2758
|
* Create a child context for nested agent invocations (e.g., agent-as-tool).
|
|
2239
2759
|
* Shares: budget tracking, abort signals, trace emission, provider registry,
|
|
2240
2760
|
* state store, span manager, memory manager, MCP manager, config,
|
|
2241
|
-
* awaitHuman handler, pending decisions, tool overrides
|
|
2242
|
-
*
|
|
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.
|
|
2770
|
+
*
|
|
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.
|
|
2243
2776
|
*/
|
|
2244
2777
|
createChildContext() {
|
|
2245
2778
|
return new _WorkflowContext({
|
|
@@ -2255,14 +2788,20 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2255
2788
|
spanManager: this.spanManager,
|
|
2256
2789
|
memoryManager: this.memoryManager,
|
|
2257
2790
|
onTrace: this.onTrace,
|
|
2791
|
+
onToken: this.onToken,
|
|
2792
|
+
onToolCall: this.onToolCall,
|
|
2793
|
+
onAgentStart: this.onAgentStart,
|
|
2258
2794
|
onAgentCallComplete: this.onAgentCallComplete,
|
|
2259
2795
|
awaitHumanHandler: this.awaitHumanHandler,
|
|
2260
2796
|
pendingDecisions: this.pendingDecisions,
|
|
2261
2797
|
toolOverrides: this.toolOverrides,
|
|
2262
2798
|
signal: this.signal,
|
|
2263
|
-
workflowName: this.workflowName
|
|
2264
|
-
//
|
|
2265
|
-
//
|
|
2799
|
+
workflowName: this.workflowName,
|
|
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)
|
|
2266
2805
|
});
|
|
2267
2806
|
}
|
|
2268
2807
|
/**
|
|
@@ -2273,72 +2812,150 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
2273
2812
|
get currentSignal() {
|
|
2274
2813
|
return signalStorage.getStore() ?? this.signal;
|
|
2275
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
|
+
}
|
|
2276
2838
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
2277
2839
|
async ask(agent2, prompt, options) {
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
"axl.agent.completion_tokens",
|
|
2308
|
-
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
|
|
2309
2869
|
);
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
+
});
|
|
2312
2948
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
this.onAgentCallComplete?.({
|
|
2318
|
-
agent: agent2._name,
|
|
2319
|
-
prompt,
|
|
2320
|
-
response: typeof result === "string" ? result : JSON.stringify(result),
|
|
2321
|
-
model: agent2.resolveModel(resolveCtx),
|
|
2322
|
-
cost: costAfter - costBefore,
|
|
2323
|
-
duration: Date.now() - startTime,
|
|
2324
|
-
promptVersion: agent2._config.version,
|
|
2325
|
-
temperature: options?.temperature ?? agent2._config.temperature,
|
|
2326
|
-
maxTokens: options?.maxTokens ?? agent2._config.maxTokens ?? 4096,
|
|
2327
|
-
effort: options?.effort ?? agent2._config.effort,
|
|
2328
|
-
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
2329
|
-
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
2330
|
-
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
2331
|
-
stop: options?.stop ?? agent2._config.stop,
|
|
2332
|
-
providerOptions: options?.providerOptions ?? agent2._config.providerOptions
|
|
2333
|
-
});
|
|
2334
|
-
return result;
|
|
2335
|
-
});
|
|
2949
|
+
});
|
|
2950
|
+
},
|
|
2951
|
+
{ agent: agentName }
|
|
2952
|
+
);
|
|
2336
2953
|
}
|
|
2337
2954
|
async executeAgentCall(agent2, prompt, options, handoffMessages, usageCapture) {
|
|
2338
2955
|
if (this.budgetContext?.exceeded) {
|
|
2339
2956
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
2340
2957
|
if (policy === "warn") {
|
|
2341
|
-
this.
|
|
2958
|
+
this.emitEvent({
|
|
2342
2959
|
type: "log",
|
|
2343
2960
|
data: { warning: "Budget exceeded", limit, spent, policy }
|
|
2344
2961
|
});
|
|
@@ -2427,14 +3044,25 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2427
3044
|
const guardrails = agent2._config.guardrails;
|
|
2428
3045
|
if (guardrails?.input) {
|
|
2429
3046
|
const inputResult = await guardrails.input(prompt, { metadata: this.metadata });
|
|
2430
|
-
this.
|
|
3047
|
+
this.emitEvent({
|
|
2431
3048
|
type: "guardrail",
|
|
2432
3049
|
agent: agent2._name,
|
|
2433
|
-
data: {
|
|
3050
|
+
data: {
|
|
3051
|
+
guardrailType: "input",
|
|
3052
|
+
blocked: inputResult.block,
|
|
3053
|
+
...inputResult.reason ? { reason: inputResult.reason } : {},
|
|
3054
|
+
// Input guardrails can't retry (prompt is user-supplied), so attempt
|
|
3055
|
+
// and maxAttempts are always 1 — emit them for shape consistency with
|
|
3056
|
+
// output guardrails so consumers don't need two narrowers.
|
|
3057
|
+
attempt: 1,
|
|
3058
|
+
maxAttempts: 1
|
|
3059
|
+
}
|
|
2434
3060
|
});
|
|
2435
3061
|
this.spanManager?.addEventToActiveSpan("axl.guardrail.check", {
|
|
2436
3062
|
"axl.guardrail.type": "input",
|
|
2437
3063
|
"axl.guardrail.blocked": inputResult.block,
|
|
3064
|
+
"axl.guardrail.attempt": 1,
|
|
3065
|
+
"axl.guardrail.maxAttempts": 1,
|
|
2438
3066
|
...inputResult.reason ? { "axl.guardrail.reason": inputResult.reason } : {}
|
|
2439
3067
|
});
|
|
2440
3068
|
if (inputResult.block) {
|
|
@@ -2450,22 +3078,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2450
3078
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
2451
3079
|
const timeoutMs = parseDuration(agent2._config.timeout ?? "60s");
|
|
2452
3080
|
const startTime = Date.now();
|
|
2453
|
-
if (this.onToken && options?.validate) {
|
|
2454
|
-
throw new AxlError(
|
|
2455
|
-
"INVALID_CONFIG",
|
|
2456
|
-
"Cannot use validate with streaming. Validate requires schema (JSON output) which does not benefit from token streaming. Use a non-streaming call instead."
|
|
2457
|
-
);
|
|
2458
|
-
}
|
|
2459
3081
|
const currentMessages = [...messages];
|
|
2460
3082
|
let turns = 0;
|
|
2461
3083
|
let guardrailOutputRetries = 0;
|
|
2462
3084
|
let schemaRetries = 0;
|
|
2463
3085
|
let validateRetries = 0;
|
|
3086
|
+
const maxGuardrailRetries = guardrails?.maxRetries ?? 2;
|
|
3087
|
+
let pendingRetryReason;
|
|
3088
|
+
const verboseTrace = this.config.trace?.level === "full";
|
|
3089
|
+
let lastStartStage = "initial";
|
|
3090
|
+
let lastStartAttempt = 1;
|
|
3091
|
+
let lastStartMaxAttempts = 1;
|
|
2464
3092
|
while (turns < maxTurns) {
|
|
2465
3093
|
if (Date.now() - startTime > timeoutMs) {
|
|
2466
3094
|
throw new TimeoutError("ctx.ask()", timeoutMs);
|
|
2467
3095
|
}
|
|
2468
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
|
+
}
|
|
3125
|
+
const turnStart = Date.now();
|
|
2469
3126
|
const chatOptions = {
|
|
2470
3127
|
model,
|
|
2471
3128
|
temperature: options?.temperature ?? agent2._config.temperature,
|
|
@@ -2482,82 +3139,172 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2482
3139
|
if (options?.schema && toolDefs.length === 0) {
|
|
2483
3140
|
chatOptions.responseFormat = { type: "json_object" };
|
|
2484
3141
|
}
|
|
2485
|
-
this.
|
|
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
|
+
};
|
|
3152
|
+
let messagesSnapshot;
|
|
3153
|
+
if (verboseTrace) {
|
|
3154
|
+
try {
|
|
3155
|
+
messagesSnapshot = structuredClone(currentMessages);
|
|
3156
|
+
} catch (err) {
|
|
3157
|
+
console.warn(
|
|
3158
|
+
"[axl] verbose trace messages snapshot failed to clone; emitting shallow copy:",
|
|
3159
|
+
err instanceof Error ? err.message : String(err)
|
|
3160
|
+
);
|
|
3161
|
+
messagesSnapshot = [...currentMessages];
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
const retryReason = pendingRetryReason;
|
|
3165
|
+
pendingRetryReason = void 0;
|
|
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);
|
|
2486
3182
|
let response;
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
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
|
+
}
|
|
2516
3256
|
}
|
|
2517
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);
|
|
2518
3283
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
};
|
|
2533
|
-
|
|
2534
|
-
response.tool_calls = toolCalls;
|
|
2535
|
-
}
|
|
2536
|
-
if (streamProviderMetadata) {
|
|
2537
|
-
response.providerMetadata = streamProviderMetadata;
|
|
2538
|
-
}
|
|
2539
|
-
if (thinkingContent) {
|
|
2540
|
-
response.thinking_content = thinkingContent;
|
|
2541
|
-
}
|
|
2542
|
-
} else {
|
|
2543
|
-
response = await provider.chat(currentMessages, chatOptions);
|
|
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;
|
|
2544
3299
|
}
|
|
2545
3300
|
if (usageCapture && response.usage) {
|
|
2546
3301
|
usageCapture.value = response.usage;
|
|
2547
3302
|
}
|
|
2548
3303
|
if (response.cost) {
|
|
2549
|
-
|
|
2550
|
-
this.budgetContext.totalCost += response.cost;
|
|
2551
|
-
if (this.budgetContext.totalCost >= this.budgetContext.limit) {
|
|
2552
|
-
this.budgetContext.exceeded = true;
|
|
2553
|
-
if (this.budgetContext.policy === "hard_stop" && this.budgetContext.abortController) {
|
|
2554
|
-
this.budgetContext.abortController.abort();
|
|
2555
|
-
}
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
3304
|
+
this._accumulateBudgetCost(response.cost);
|
|
2558
3305
|
}
|
|
2559
|
-
this.
|
|
2560
|
-
type: "
|
|
3306
|
+
this.emitEvent({
|
|
3307
|
+
type: "agent_call_end",
|
|
2561
3308
|
agent: agent2._name,
|
|
2562
3309
|
model: modelUri,
|
|
2563
3310
|
promptVersion: agent2._config.version,
|
|
@@ -2567,8 +3314,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2567
3314
|
output: response.usage.completion_tokens,
|
|
2568
3315
|
reasoning: response.usage.reasoning_tokens
|
|
2569
3316
|
} : void 0,
|
|
2570
|
-
duration: Date.now() -
|
|
2571
|
-
data: {
|
|
3317
|
+
duration: Date.now() - turnStart,
|
|
3318
|
+
data: {
|
|
3319
|
+
response: response.content,
|
|
3320
|
+
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
3321
|
+
turn: turns,
|
|
3322
|
+
...retryReason ? { retryReason } : {}
|
|
3323
|
+
}
|
|
2572
3324
|
});
|
|
2573
3325
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2574
3326
|
currentMessages.push({
|
|
@@ -2593,6 +3345,19 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2593
3345
|
}
|
|
2594
3346
|
}
|
|
2595
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
|
+
};
|
|
2596
3361
|
const handoffOptions = options ? {
|
|
2597
3362
|
schema: options.schema,
|
|
2598
3363
|
retries: options.retries,
|
|
@@ -2600,13 +3365,51 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2600
3365
|
validate: options.validate,
|
|
2601
3366
|
validateRetries: options.validateRetries
|
|
2602
3367
|
} : void 0;
|
|
2603
|
-
const handoffFn = () =>
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
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
|
+
});
|
|
2610
3413
|
if (mode === "roundtrip") {
|
|
2611
3414
|
const executeRoundtrip = async () => {
|
|
2612
3415
|
const result = await handoffFn();
|
|
@@ -2627,24 +3430,41 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2627
3430
|
"axl.handoff.mode": mode
|
|
2628
3431
|
},
|
|
2629
3432
|
async (span) => {
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
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
|
+
}
|
|
2639
3448
|
}
|
|
2640
3449
|
);
|
|
2641
3450
|
} else {
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
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
|
+
}
|
|
2648
3468
|
}
|
|
2649
3469
|
continue;
|
|
2650
3470
|
}
|
|
@@ -2658,24 +3478,12 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2658
3478
|
},
|
|
2659
3479
|
async (span) => {
|
|
2660
3480
|
const result = await handoffFn();
|
|
2661
|
-
|
|
2662
|
-
span.setAttribute("axl.handoff.duration", duration);
|
|
2663
|
-
this.emitTrace({
|
|
2664
|
-
type: "handoff",
|
|
2665
|
-
agent: agent2._name,
|
|
2666
|
-
data: { target: targetName, mode, duration }
|
|
2667
|
-
});
|
|
3481
|
+
span.setAttribute("axl.handoff.duration", Date.now() - handoffStart);
|
|
2668
3482
|
return result;
|
|
2669
3483
|
}
|
|
2670
3484
|
);
|
|
2671
3485
|
}
|
|
2672
|
-
|
|
2673
|
-
this.emitTrace({
|
|
2674
|
-
type: "handoff",
|
|
2675
|
-
agent: agent2._name,
|
|
2676
|
-
data: { target: targetName, mode, duration: Date.now() - handoffStart }
|
|
2677
|
-
});
|
|
2678
|
-
return onewayResult;
|
|
3486
|
+
return await handoffFn();
|
|
2679
3487
|
}
|
|
2680
3488
|
}
|
|
2681
3489
|
const toolOverride = this.toolOverrides?.get(toolName);
|
|
@@ -2691,7 +3499,16 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2691
3499
|
});
|
|
2692
3500
|
continue;
|
|
2693
3501
|
}
|
|
2694
|
-
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
|
+
);
|
|
2695
3512
|
const toolStart2 = Date.now();
|
|
2696
3513
|
const executeOverride = async () => {
|
|
2697
3514
|
let toolResult3;
|
|
@@ -2719,8 +3536,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2719
3536
|
}
|
|
2720
3537
|
) : await executeOverride();
|
|
2721
3538
|
const resultContent2 = JSON.stringify(toolResult2);
|
|
2722
|
-
this.
|
|
2723
|
-
type: "
|
|
3539
|
+
this.emitEvent({
|
|
3540
|
+
type: "tool_call_end",
|
|
2724
3541
|
agent: agent2._name,
|
|
2725
3542
|
tool: toolName,
|
|
2726
3543
|
duration: Date.now() - toolStart2,
|
|
@@ -2736,7 +3553,7 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2736
3553
|
const tool2 = agent2._config.tools?.find((t) => t.name === toolName);
|
|
2737
3554
|
const isMcpTool = !tool2 && this.mcpManager?.isMcpTool(toolName);
|
|
2738
3555
|
if (!tool2 && !isMcpTool) {
|
|
2739
|
-
this.
|
|
3556
|
+
this.emitEvent({ type: "tool_denied", agent: agent2._name, tool: toolName });
|
|
2740
3557
|
currentMessages.push({
|
|
2741
3558
|
role: "tool",
|
|
2742
3559
|
content: `Tool "${toolName}" is not available. Available tools: ${agent2._config.tools?.map((t) => t.name).join(", ") ?? "none"}`,
|
|
@@ -2755,7 +3572,13 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2755
3572
|
});
|
|
2756
3573
|
continue;
|
|
2757
3574
|
}
|
|
2758
|
-
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);
|
|
2759
3582
|
const toolStart = Date.now();
|
|
2760
3583
|
if (tool2 && tool2.requireApproval) {
|
|
2761
3584
|
const approvalFn = async () => {
|
|
@@ -2766,24 +3589,18 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2766
3589
|
});
|
|
2767
3590
|
if (!decision.approved) {
|
|
2768
3591
|
const reason = decision.reason ?? "Denied by human";
|
|
2769
|
-
this.emitTrace({
|
|
2770
|
-
type: "tool_denied",
|
|
2771
|
-
agent: agent2._name,
|
|
2772
|
-
tool: toolName,
|
|
2773
|
-
data: { denied: true, reason, args: toolArgs }
|
|
2774
|
-
});
|
|
2775
3592
|
currentMessages.push({
|
|
2776
3593
|
role: "tool",
|
|
2777
3594
|
content: JSON.stringify({ error: `Tool denied by human: ${reason}` }),
|
|
2778
3595
|
tool_call_id: toolCall.id
|
|
2779
3596
|
});
|
|
2780
|
-
return false;
|
|
3597
|
+
return { approved: false, reason };
|
|
2781
3598
|
}
|
|
2782
|
-
return true;
|
|
3599
|
+
return { approved: true };
|
|
2783
3600
|
};
|
|
2784
|
-
let
|
|
3601
|
+
let approvalOutcome;
|
|
2785
3602
|
if (this.spanManager) {
|
|
2786
|
-
|
|
3603
|
+
approvalOutcome = await this.spanManager.withSpanAsync(
|
|
2787
3604
|
"axl.tool.approval",
|
|
2788
3605
|
{
|
|
2789
3606
|
"axl.tool.name": toolName,
|
|
@@ -2791,20 +3608,24 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2791
3608
|
},
|
|
2792
3609
|
async (span) => {
|
|
2793
3610
|
const result = await approvalFn();
|
|
2794
|
-
span.setAttribute("axl.tool.approval.approved", result);
|
|
3611
|
+
span.setAttribute("axl.tool.approval.approved", result.approved);
|
|
2795
3612
|
return result;
|
|
2796
3613
|
}
|
|
2797
3614
|
);
|
|
2798
3615
|
} else {
|
|
2799
|
-
|
|
3616
|
+
approvalOutcome = await approvalFn();
|
|
2800
3617
|
}
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
type: "tool_denied",
|
|
3618
|
+
this.emitEvent({
|
|
3619
|
+
type: "tool_approval",
|
|
2804
3620
|
agent: agent2._name,
|
|
2805
3621
|
tool: toolName,
|
|
2806
|
-
data: {
|
|
3622
|
+
data: {
|
|
3623
|
+
approved: approvalOutcome.approved,
|
|
3624
|
+
args: toolArgs,
|
|
3625
|
+
...approvalOutcome.reason ? { reason: approvalOutcome.reason } : {}
|
|
3626
|
+
}
|
|
2807
3627
|
});
|
|
3628
|
+
if (!approvalOutcome.approved) continue;
|
|
2808
3629
|
}
|
|
2809
3630
|
if (tool2 && tool2.hooks?.before) {
|
|
2810
3631
|
try {
|
|
@@ -2879,8 +3700,8 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2879
3700
|
return r;
|
|
2880
3701
|
}
|
|
2881
3702
|
) : await executeTool();
|
|
2882
|
-
this.
|
|
2883
|
-
type: "
|
|
3703
|
+
this.emitEvent({
|
|
3704
|
+
type: "tool_call_end",
|
|
2884
3705
|
agent: agent2._name,
|
|
2885
3706
|
tool: traceName,
|
|
2886
3707
|
duration: Date.now() - toolStart,
|
|
@@ -2897,37 +3718,52 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2897
3718
|
const content = response.content;
|
|
2898
3719
|
if (guardrails?.output) {
|
|
2899
3720
|
const outputResult = await guardrails.output(content, { metadata: this.metadata });
|
|
2900
|
-
|
|
3721
|
+
const attempt = guardrailOutputRetries + 1;
|
|
3722
|
+
const maxAttempts = maxGuardrailRetries + 1;
|
|
3723
|
+
const onBlock = guardrails.onBlock ?? "throw";
|
|
3724
|
+
let feedbackMessage;
|
|
3725
|
+
if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
|
|
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.`;
|
|
3727
|
+
}
|
|
3728
|
+
this.emitEvent({
|
|
2901
3729
|
type: "guardrail",
|
|
2902
3730
|
agent: agent2._name,
|
|
2903
3731
|
data: {
|
|
2904
3732
|
guardrailType: "output",
|
|
2905
3733
|
blocked: outputResult.block,
|
|
2906
|
-
reason: outputResult.reason
|
|
3734
|
+
...outputResult.reason ? { reason: outputResult.reason } : {},
|
|
3735
|
+
attempt,
|
|
3736
|
+
maxAttempts,
|
|
3737
|
+
...feedbackMessage ? { feedbackMessage } : {}
|
|
2907
3738
|
}
|
|
2908
3739
|
});
|
|
2909
3740
|
this.spanManager?.addEventToActiveSpan("axl.guardrail.check", {
|
|
2910
3741
|
"axl.guardrail.type": "output",
|
|
2911
3742
|
"axl.guardrail.blocked": outputResult.block,
|
|
3743
|
+
"axl.guardrail.attempt": attempt,
|
|
3744
|
+
"axl.guardrail.maxAttempts": maxAttempts,
|
|
2912
3745
|
...outputResult.reason ? { "axl.guardrail.reason": outputResult.reason } : {}
|
|
2913
3746
|
});
|
|
2914
3747
|
if (outputResult.block) {
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
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
|
+
});
|
|
3758
|
+
guardrailOutputRetries++;
|
|
3759
|
+
appendRetryMessages(
|
|
3760
|
+
currentMessages,
|
|
3761
|
+
content,
|
|
3762
|
+
feedbackMessage,
|
|
3763
|
+
response.providerMetadata
|
|
3764
|
+
);
|
|
3765
|
+
pendingRetryReason = "guardrail";
|
|
3766
|
+
continue;
|
|
2931
3767
|
}
|
|
2932
3768
|
if (typeof onBlock === "function") {
|
|
2933
3769
|
return onBlock(outputResult.reason ?? "Output blocked by guardrail", {
|
|
@@ -2939,30 +3775,67 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2939
3775
|
}
|
|
2940
3776
|
let validated = void 0;
|
|
2941
3777
|
if (options?.schema) {
|
|
3778
|
+
const maxSchemaRetries = options.retries ?? 3;
|
|
3779
|
+
const schemaAttempt = schemaRetries + 1;
|
|
3780
|
+
const schemaMaxAttempts = maxSchemaRetries + 1;
|
|
3781
|
+
let schemaValid = false;
|
|
3782
|
+
let schemaReason;
|
|
3783
|
+
let schemaFeedback;
|
|
3784
|
+
let schemaErr;
|
|
2942
3785
|
try {
|
|
2943
3786
|
const parsed = JSON.parse(extractJson(content));
|
|
2944
3787
|
validated = options.schema.parse(parsed);
|
|
3788
|
+
schemaValid = true;
|
|
2945
3789
|
} catch (err) {
|
|
2946
|
-
|
|
3790
|
+
schemaErr = err;
|
|
3791
|
+
schemaReason = err instanceof Error ? err.message : String(err);
|
|
2947
3792
|
if (schemaRetries < maxSchemaRetries) {
|
|
3793
|
+
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
this.emitEvent({
|
|
3797
|
+
type: "schema_check",
|
|
3798
|
+
agent: agent2._name,
|
|
3799
|
+
data: {
|
|
3800
|
+
valid: schemaValid,
|
|
3801
|
+
...schemaReason ? { reason: schemaReason } : {},
|
|
3802
|
+
attempt: schemaAttempt,
|
|
3803
|
+
maxAttempts: schemaMaxAttempts,
|
|
3804
|
+
...schemaFeedback ? { feedbackMessage: schemaFeedback } : {}
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
this.spanManager?.addEventToActiveSpan("axl.schema.check", {
|
|
3808
|
+
"axl.schema.valid": schemaValid,
|
|
3809
|
+
"axl.schema.attempt": schemaAttempt,
|
|
3810
|
+
"axl.schema.maxAttempts": schemaMaxAttempts,
|
|
3811
|
+
...schemaReason ? { "axl.schema.reason": schemaReason } : {}
|
|
3812
|
+
});
|
|
3813
|
+
if (!schemaValid) {
|
|
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
|
+
});
|
|
2948
3824
|
schemaRetries++;
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
role: "assistant",
|
|
3825
|
+
appendRetryMessages(
|
|
3826
|
+
currentMessages,
|
|
2952
3827
|
content,
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
content: `Your response was not valid JSON or did not match the required schema: ${errorMsg}. Please fix and try again.`
|
|
2958
|
-
});
|
|
3828
|
+
schemaFeedback,
|
|
3829
|
+
response.providerMetadata
|
|
3830
|
+
);
|
|
3831
|
+
pendingRetryReason = "schema";
|
|
2959
3832
|
continue;
|
|
2960
3833
|
}
|
|
2961
|
-
const zodErr =
|
|
3834
|
+
const zodErr = schemaErr instanceof ZodError ? schemaErr : new ZodError([
|
|
2962
3835
|
{
|
|
2963
3836
|
code: "custom",
|
|
2964
3837
|
path: [],
|
|
2965
|
-
message:
|
|
3838
|
+
message: schemaReason ?? "Schema parse failed"
|
|
2966
3839
|
}
|
|
2967
3840
|
]);
|
|
2968
3841
|
throw new VerifyError(content, zodErr, maxSchemaRetries);
|
|
@@ -2978,31 +3851,49 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
2978
3851
|
const reason = err instanceof Error ? err.message : String(err);
|
|
2979
3852
|
validateResult = { valid: false, reason: `Validator error: ${reason}` };
|
|
2980
3853
|
}
|
|
2981
|
-
|
|
3854
|
+
const maxValidateRetries = options.validateRetries ?? 2;
|
|
3855
|
+
const validateAttempt = validateRetries + 1;
|
|
3856
|
+
const validateMaxAttempts = maxValidateRetries + 1;
|
|
3857
|
+
let validateFeedback;
|
|
3858
|
+
if (!validateResult.valid && validateRetries < maxValidateRetries) {
|
|
3859
|
+
validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
|
|
3860
|
+
}
|
|
3861
|
+
this.emitEvent({
|
|
2982
3862
|
type: "validate",
|
|
2983
3863
|
agent: agent2._name,
|
|
2984
3864
|
data: {
|
|
2985
3865
|
valid: validateResult.valid,
|
|
2986
|
-
...validateResult.reason ? { reason: validateResult.reason } : {}
|
|
3866
|
+
...validateResult.reason ? { reason: validateResult.reason } : {},
|
|
3867
|
+
attempt: validateAttempt,
|
|
3868
|
+
maxAttempts: validateMaxAttempts,
|
|
3869
|
+
...validateFeedback ? { feedbackMessage: validateFeedback } : {}
|
|
2987
3870
|
}
|
|
2988
3871
|
});
|
|
2989
3872
|
this.spanManager?.addEventToActiveSpan("axl.validate.check", {
|
|
2990
3873
|
"axl.validate.valid": validateResult.valid,
|
|
3874
|
+
"axl.validate.attempt": validateAttempt,
|
|
3875
|
+
"axl.validate.maxAttempts": validateMaxAttempts,
|
|
2991
3876
|
...validateResult.reason ? { "axl.validate.reason": validateResult.reason } : {}
|
|
2992
3877
|
});
|
|
2993
3878
|
if (!validateResult.valid) {
|
|
2994
|
-
|
|
2995
|
-
|
|
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
|
+
});
|
|
2996
3889
|
validateRetries++;
|
|
2997
|
-
|
|
2998
|
-
|
|
3890
|
+
appendRetryMessages(
|
|
3891
|
+
currentMessages,
|
|
2999
3892
|
content,
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
content: `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`
|
|
3005
|
-
});
|
|
3893
|
+
validateFeedback,
|
|
3894
|
+
response.providerMetadata
|
|
3895
|
+
);
|
|
3896
|
+
pendingRetryReason = "validate";
|
|
3006
3897
|
continue;
|
|
3007
3898
|
}
|
|
3008
3899
|
throw new ValidationError(
|
|
@@ -3012,6 +3903,14 @@ ${JSON.stringify(jsonSchema, null, 2)}`;
|
|
|
3012
3903
|
);
|
|
3013
3904
|
}
|
|
3014
3905
|
}
|
|
3906
|
+
this.emitEvent({
|
|
3907
|
+
type: "pipeline",
|
|
3908
|
+
agent: agent2._name,
|
|
3909
|
+
status: "committed",
|
|
3910
|
+
stage: lastStartStage,
|
|
3911
|
+
attempt: lastStartAttempt,
|
|
3912
|
+
maxAttempts: lastStartMaxAttempts
|
|
3913
|
+
});
|
|
3015
3914
|
this.pushAssistantToSessionHistory(content, response.providerMetadata);
|
|
3016
3915
|
return validated ?? content;
|
|
3017
3916
|
}
|
|
@@ -3140,43 +4039,100 @@ ${summaryResponse.content}`
|
|
|
3140
4039
|
/**
|
|
3141
4040
|
* Execute a function with checkpoint-replay semantics.
|
|
3142
4041
|
*
|
|
3143
|
-
* On first execution, runs `fn()`, saves the result
|
|
3144
|
-
* On replay (resume after restart), returns the saved result
|
|
3145
|
-
* 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.
|
|
3146
4056
|
*/
|
|
3147
|
-
async checkpoint(fn) {
|
|
3148
|
-
|
|
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);
|
|
3149
4072
|
}
|
|
3150
4073
|
/**
|
|
3151
|
-
* Internal checkpoint implementation shared by both the public
|
|
3152
|
-
* 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.
|
|
3153
4084
|
*/
|
|
3154
|
-
async _checkpoint(fn) {
|
|
3155
|
-
const step = this.checkpointCounter++;
|
|
4085
|
+
async _checkpoint(name, fn, options) {
|
|
3156
4086
|
if (!this.stateStore) {
|
|
3157
4087
|
return fn();
|
|
3158
4088
|
}
|
|
3159
|
-
const
|
|
4089
|
+
const agentStamp = options?.agent ? { agent: options.agent } : {};
|
|
4090
|
+
const saved = await this.stateStore.getCheckpoint(this.executionId, name);
|
|
3160
4091
|
if (saved !== null) {
|
|
3161
|
-
this.
|
|
3162
|
-
type: "
|
|
3163
|
-
|
|
4092
|
+
this.emitEvent({
|
|
4093
|
+
type: "checkpoint_replay",
|
|
4094
|
+
...agentStamp,
|
|
4095
|
+
data: { name }
|
|
3164
4096
|
});
|
|
3165
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.
|
|
4097
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.hit", { "axl.checkpoint.name": name });
|
|
3166
4098
|
return saved;
|
|
3167
4099
|
}
|
|
3168
4100
|
const result = await fn();
|
|
3169
|
-
await this.stateStore.saveCheckpoint(this.executionId,
|
|
3170
|
-
this.
|
|
3171
|
-
type: "
|
|
3172
|
-
|
|
4101
|
+
await this.stateStore.saveCheckpoint(this.executionId, name, result);
|
|
4102
|
+
this.emitEvent({
|
|
4103
|
+
type: "checkpoint_save",
|
|
4104
|
+
...agentStamp,
|
|
4105
|
+
data: { name }
|
|
3173
4106
|
});
|
|
3174
|
-
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.
|
|
4107
|
+
this.spanManager?.addEventToActiveSpan("axl.checkpoint.miss", { "axl.checkpoint.name": name });
|
|
3175
4108
|
return result;
|
|
3176
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
|
+
}
|
|
3177
4133
|
// ── ctx.spawn() ───────────────────────────────────────────────────────
|
|
3178
4134
|
async spawn(n, fn, options) {
|
|
3179
|
-
return this._checkpoint(() => {
|
|
4135
|
+
return this._checkpoint(this._autoCheckpointName("spawn"), () => {
|
|
3180
4136
|
if (this.spanManager) {
|
|
3181
4137
|
return this.spanManager.withSpanAsync(
|
|
3182
4138
|
"axl.ctx.spawn",
|
|
@@ -3371,6 +4327,17 @@ ${summaryResponse.content}`
|
|
|
3371
4327
|
async verify(fn, schema, options) {
|
|
3372
4328
|
const maxRetries = options?.retries ?? 3;
|
|
3373
4329
|
let lastRetry = void 0;
|
|
4330
|
+
const emitVerifyOutcome = (passed, attempts, lastError) => {
|
|
4331
|
+
this.emitEvent({
|
|
4332
|
+
type: "verify",
|
|
4333
|
+
agent: void 0,
|
|
4334
|
+
data: {
|
|
4335
|
+
passed,
|
|
4336
|
+
attempts,
|
|
4337
|
+
...lastError ? { lastError } : {}
|
|
4338
|
+
}
|
|
4339
|
+
});
|
|
4340
|
+
};
|
|
3374
4341
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3375
4342
|
let rawOutput;
|
|
3376
4343
|
try {
|
|
@@ -3389,12 +4356,14 @@ ${summaryResponse.content}`
|
|
|
3389
4356
|
const errorMsg = validateResult.reason ?? "Validation failed";
|
|
3390
4357
|
lastRetry = { error: errorMsg, output: rawOutput, parsed };
|
|
3391
4358
|
if (attempt === maxRetries) {
|
|
4359
|
+
emitVerifyOutcome(false, attempt + 1, errorMsg);
|
|
3392
4360
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3393
4361
|
throw new ValidationError(parsed, errorMsg, maxRetries);
|
|
3394
4362
|
}
|
|
3395
4363
|
continue;
|
|
3396
4364
|
}
|
|
3397
4365
|
}
|
|
4366
|
+
emitVerifyOutcome(true, attempt + 1);
|
|
3398
4367
|
return parsed;
|
|
3399
4368
|
} catch (err) {
|
|
3400
4369
|
if (err instanceof ValidationError) {
|
|
@@ -3404,6 +4373,7 @@ ${summaryResponse.content}`
|
|
|
3404
4373
|
parsed: err.lastOutput
|
|
3405
4374
|
};
|
|
3406
4375
|
if (attempt === maxRetries) {
|
|
4376
|
+
emitVerifyOutcome(false, attempt + 1, err.reason);
|
|
3407
4377
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3408
4378
|
throw err;
|
|
3409
4379
|
}
|
|
@@ -3412,6 +4382,7 @@ ${summaryResponse.content}`
|
|
|
3412
4382
|
if (err instanceof VerifyError) {
|
|
3413
4383
|
lastRetry = { error: err.message, output: rawOutput ?? err.lastOutput };
|
|
3414
4384
|
if (attempt === maxRetries) {
|
|
4385
|
+
emitVerifyOutcome(false, attempt + 1, err.message);
|
|
3415
4386
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3416
4387
|
throw err;
|
|
3417
4388
|
}
|
|
@@ -3420,12 +4391,14 @@ ${summaryResponse.content}`
|
|
|
3420
4391
|
const errorMsg = err instanceof ZodError ? err.message : err instanceof Error ? err.message : String(err);
|
|
3421
4392
|
lastRetry = { error: errorMsg, output: rawOutput };
|
|
3422
4393
|
if (attempt === maxRetries) {
|
|
4394
|
+
emitVerifyOutcome(false, attempt + 1, errorMsg);
|
|
3423
4395
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3424
4396
|
const zodErr = err instanceof ZodError ? err : new ZodError([{ code: "custom", path: [], message: errorMsg }]);
|
|
3425
4397
|
throw new VerifyError(rawOutput, zodErr, maxRetries);
|
|
3426
4398
|
}
|
|
3427
4399
|
}
|
|
3428
4400
|
}
|
|
4401
|
+
emitVerifyOutcome(false, maxRetries + 1, lastRetry?.error ?? "Verify failed");
|
|
3429
4402
|
if (options?.fallback !== void 0) return options.fallback;
|
|
3430
4403
|
throw new VerifyError(
|
|
3431
4404
|
lastRetry?.output,
|
|
@@ -3495,7 +4468,7 @@ ${summaryResponse.content}`
|
|
|
3495
4468
|
}
|
|
3496
4469
|
// ── ctx.race() ────────────────────────────────────────────────────────
|
|
3497
4470
|
async race(fns, options) {
|
|
3498
|
-
return this._checkpoint(() => {
|
|
4471
|
+
return this._checkpoint(this._autoCheckpointName("race"), () => {
|
|
3499
4472
|
if (this.spanManager) {
|
|
3500
4473
|
return this.spanManager.withSpanAsync(
|
|
3501
4474
|
"axl.ctx.race",
|
|
@@ -3600,11 +4573,17 @@ ${summaryResponse.content}`
|
|
|
3600
4573
|
}
|
|
3601
4574
|
// ── ctx.parallel() ────────────────────────────────────────────────────
|
|
3602
4575
|
async parallel(fns) {
|
|
3603
|
-
return this._checkpoint(
|
|
4576
|
+
return this._checkpoint(
|
|
4577
|
+
this._autoCheckpointName("parallel"),
|
|
4578
|
+
() => Promise.all(fns.map((fn) => fn()))
|
|
4579
|
+
);
|
|
3604
4580
|
}
|
|
3605
4581
|
// ── ctx.map() ─────────────────────────────────────────────────────────
|
|
3606
4582
|
async map(items, fn, options) {
|
|
3607
|
-
return this._checkpoint(
|
|
4583
|
+
return this._checkpoint(
|
|
4584
|
+
this._autoCheckpointName("map"),
|
|
4585
|
+
() => this._mapImpl(items, fn, options)
|
|
4586
|
+
);
|
|
3608
4587
|
}
|
|
3609
4588
|
async _mapImpl(items, fn, options) {
|
|
3610
4589
|
const concurrency = options?.concurrency ?? 5;
|
|
@@ -3682,10 +4661,14 @@ ${summaryResponse.content}`
|
|
|
3682
4661
|
}
|
|
3683
4662
|
async _awaitHumanImpl(options) {
|
|
3684
4663
|
if (this.awaitHumanHandler) {
|
|
4664
|
+
this.emitEvent({
|
|
4665
|
+
type: "await_human",
|
|
4666
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
4667
|
+
});
|
|
3685
4668
|
const decision2 = await this.awaitHumanHandler(options);
|
|
3686
|
-
this.
|
|
3687
|
-
type: "
|
|
3688
|
-
data: {
|
|
4669
|
+
this.emitEvent({
|
|
4670
|
+
type: "await_human_resolved",
|
|
4671
|
+
data: { channel: options.channel, decision: decision2 }
|
|
3689
4672
|
});
|
|
3690
4673
|
return decision2;
|
|
3691
4674
|
}
|
|
@@ -3705,7 +4688,7 @@ ${summaryResponse.content}`
|
|
|
3705
4688
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
3706
4689
|
workflow: this.workflowName ?? "unknown",
|
|
3707
4690
|
input: this.input,
|
|
3708
|
-
step: this.
|
|
4691
|
+
step: this.stepRefRoot.value,
|
|
3709
4692
|
status: "waiting",
|
|
3710
4693
|
metadata: {
|
|
3711
4694
|
...this.metadata,
|
|
@@ -3714,26 +4697,111 @@ ${summaryResponse.content}`
|
|
|
3714
4697
|
}
|
|
3715
4698
|
});
|
|
3716
4699
|
}
|
|
3717
|
-
this.
|
|
3718
|
-
type: "
|
|
3719
|
-
data: {
|
|
4700
|
+
this.emitEvent({
|
|
4701
|
+
type: "await_human",
|
|
4702
|
+
data: { channel: options.channel, prompt: options.prompt }
|
|
3720
4703
|
});
|
|
3721
4704
|
const decision = await new Promise((resolve) => {
|
|
3722
4705
|
this.pendingDecisions.set(this.executionId, resolve);
|
|
3723
4706
|
});
|
|
4707
|
+
this.emitEvent({
|
|
4708
|
+
type: "await_human_resolved",
|
|
4709
|
+
data: { channel: options.channel, decision }
|
|
4710
|
+
});
|
|
3724
4711
|
if (this.stateStore) {
|
|
3725
4712
|
await this.stateStore.saveExecutionState(this.executionId, {
|
|
3726
4713
|
workflow: this.workflowName ?? "unknown",
|
|
3727
4714
|
input: this.input,
|
|
3728
|
-
step: this.
|
|
4715
|
+
step: this.stepRefRoot.value,
|
|
3729
4716
|
status: "running"
|
|
3730
4717
|
});
|
|
3731
4718
|
}
|
|
3732
4719
|
return decision;
|
|
3733
4720
|
}
|
|
4721
|
+
/**
|
|
4722
|
+
* Compose an `AbortSignal` for a memory operation's underlying embedder
|
|
4723
|
+
* fetch. Combines the parent context signal (user cancellation,
|
|
4724
|
+
* `runtime.execute({ signal })`) with the budget hard_stop abort so
|
|
4725
|
+
* that either cause correctly aborts an in-flight embed call.
|
|
4726
|
+
*
|
|
4727
|
+
* Returns `undefined` if there's nothing to cancel on — the embedder
|
|
4728
|
+
* runs without a signal in that case, matching its prior behavior.
|
|
4729
|
+
*
|
|
4730
|
+
* @internal
|
|
4731
|
+
*/
|
|
4732
|
+
_composeMemorySignal() {
|
|
4733
|
+
const budgetSignal = this.budgetContext?.abortController?.signal;
|
|
4734
|
+
if (this.signal && budgetSignal) return AbortSignal.any([this.signal, budgetSignal]);
|
|
4735
|
+
return this.signal ?? budgetSignal;
|
|
4736
|
+
}
|
|
4737
|
+
/**
|
|
4738
|
+
* Accumulate a cost amount into the active `budgetContext` and trip the
|
|
4739
|
+
* `exceeded` flag if we've crossed the limit. On `hard_stop` policy,
|
|
4740
|
+
* also fires the abort controller so in-flight operations cancel.
|
|
4741
|
+
*
|
|
4742
|
+
* Called from every code path that spends money: the `agent_call` loop,
|
|
4743
|
+
* semantic memory operations (`ctx.remember({embed:true})`, `ctx.recall({query})`),
|
|
4744
|
+
* and any future cost-emitting primitive. Centralizing the logic here
|
|
4745
|
+
* means `ctx.budget({ limit, policy })` accurately enforces the limit
|
|
4746
|
+
* across ALL cost sources — not just agent calls.
|
|
4747
|
+
*
|
|
4748
|
+
* @internal
|
|
4749
|
+
*/
|
|
4750
|
+
_accumulateBudgetCost(amount) {
|
|
4751
|
+
if (!this.budgetContext) return;
|
|
4752
|
+
if (!Number.isFinite(amount) || amount <= 0) return;
|
|
4753
|
+
this.budgetContext.totalCost += amount;
|
|
4754
|
+
if (this.budgetContext.totalCost >= this.budgetContext.limit) {
|
|
4755
|
+
this.budgetContext.exceeded = true;
|
|
4756
|
+
if (this.budgetContext.policy === "hard_stop" && this.budgetContext.abortController) {
|
|
4757
|
+
this.budgetContext.abortController.abort();
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
// ── workflow lifecycle trace emission ──────────────────────────────────
|
|
4762
|
+
//
|
|
4763
|
+
// These are called by the runtime at execution boundaries. They emit
|
|
4764
|
+
// first-class `workflow_start` / `workflow_end` trace events instead of
|
|
4765
|
+
// the previous `ctx.log('workflow_start', ...)` indirection — so consumers
|
|
4766
|
+
// that narrow via `event.type === 'workflow_start'` actually see them.
|
|
4767
|
+
// Internal: the runtime is the only caller, user workflows never call these.
|
|
4768
|
+
/** @internal — idempotent; no-ops on second+ call within one ctx. */
|
|
4769
|
+
_emitWorkflowStart(input) {
|
|
4770
|
+
if (this._workflowStartEmitted) return;
|
|
4771
|
+
this._workflowStartEmitted = true;
|
|
4772
|
+
this.emitEvent({
|
|
4773
|
+
type: "workflow_start",
|
|
4774
|
+
workflow: this.workflowName,
|
|
4775
|
+
data: { input }
|
|
4776
|
+
});
|
|
4777
|
+
}
|
|
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. */
|
|
4786
|
+
_emitWorkflowEnd(info) {
|
|
4787
|
+
if (this._workflowEndEmitted) return;
|
|
4788
|
+
this._workflowEndEmitted = true;
|
|
4789
|
+
this.emitEvent({
|
|
4790
|
+
type: "workflow_end",
|
|
4791
|
+
workflow: this.workflowName,
|
|
4792
|
+
duration: info.duration,
|
|
4793
|
+
data: {
|
|
4794
|
+
status: info.status,
|
|
4795
|
+
duration: info.duration,
|
|
4796
|
+
...info.result !== void 0 ? { result: info.result } : {},
|
|
4797
|
+
...info.error !== void 0 ? { error: info.error } : {},
|
|
4798
|
+
...info.aborted ? { aborted: true } : {}
|
|
4799
|
+
}
|
|
4800
|
+
});
|
|
4801
|
+
}
|
|
3734
4802
|
// ── ctx.log() ─────────────────────────────────────────────────────────
|
|
3735
4803
|
log(event, data) {
|
|
3736
|
-
this.
|
|
4804
|
+
this.emitEvent({
|
|
3737
4805
|
type: "log",
|
|
3738
4806
|
data: {
|
|
3739
4807
|
event,
|
|
@@ -3766,8 +4834,57 @@ ${summaryResponse.content}`
|
|
|
3766
4834
|
if (!this.stateStore) {
|
|
3767
4835
|
throw new Error("A state store is required for memory operations.");
|
|
3768
4836
|
}
|
|
4837
|
+
if (this.budgetContext?.exceeded) {
|
|
4838
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
4839
|
+
if (policy !== "warn") {
|
|
4840
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
3769
4843
|
const sessionId = this.metadata?.sessionId;
|
|
3770
|
-
|
|
4844
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4845
|
+
try {
|
|
4846
|
+
const memorySignal = this._composeMemorySignal();
|
|
4847
|
+
const { usage } = await this.memoryManager.remember(
|
|
4848
|
+
key,
|
|
4849
|
+
value,
|
|
4850
|
+
this.stateStore,
|
|
4851
|
+
sessionId,
|
|
4852
|
+
options,
|
|
4853
|
+
memorySignal
|
|
4854
|
+
);
|
|
4855
|
+
if (usage?.cost != null) {
|
|
4856
|
+
this._accumulateBudgetCost(usage.cost);
|
|
4857
|
+
}
|
|
4858
|
+
this.emitEvent({
|
|
4859
|
+
type: "memory_remember",
|
|
4860
|
+
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4861
|
+
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4862
|
+
data: {
|
|
4863
|
+
key,
|
|
4864
|
+
scope,
|
|
4865
|
+
embed: options?.embed === true,
|
|
4866
|
+
...usage ? { usage } : {}
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
} catch (err) {
|
|
4870
|
+
const partialUsage = err.axlEmbedUsage;
|
|
4871
|
+
if (partialUsage?.cost != null) {
|
|
4872
|
+
this._accumulateBudgetCost(partialUsage.cost);
|
|
4873
|
+
}
|
|
4874
|
+
this.emitEvent({
|
|
4875
|
+
type: "memory_remember",
|
|
4876
|
+
...partialUsage?.cost != null ? { cost: partialUsage.cost } : {},
|
|
4877
|
+
...partialUsage?.tokens != null ? { tokens: { input: partialUsage.tokens } } : {},
|
|
4878
|
+
data: {
|
|
4879
|
+
key,
|
|
4880
|
+
scope,
|
|
4881
|
+
embed: options?.embed === true,
|
|
4882
|
+
...partialUsage ? { usage: partialUsage } : {},
|
|
4883
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4884
|
+
}
|
|
4885
|
+
});
|
|
4886
|
+
throw err;
|
|
4887
|
+
}
|
|
3771
4888
|
}
|
|
3772
4889
|
/**
|
|
3773
4890
|
* Recall a value from memory by key, or perform semantic search if query option is provided.
|
|
@@ -3781,8 +4898,62 @@ ${summaryResponse.content}`
|
|
|
3781
4898
|
if (!this.stateStore) {
|
|
3782
4899
|
throw new Error("A state store is required for memory operations.");
|
|
3783
4900
|
}
|
|
4901
|
+
if (this.budgetContext?.exceeded) {
|
|
4902
|
+
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
4903
|
+
if (policy !== "warn") {
|
|
4904
|
+
throw new BudgetExceededError(limit, spent, policy);
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
3784
4907
|
const sessionId = this.metadata?.sessionId;
|
|
3785
|
-
|
|
4908
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4909
|
+
const semantic = options?.query !== void 0;
|
|
4910
|
+
try {
|
|
4911
|
+
const memorySignal = this._composeMemorySignal();
|
|
4912
|
+
const { data, usage } = await this.memoryManager.recall(
|
|
4913
|
+
key,
|
|
4914
|
+
this.stateStore,
|
|
4915
|
+
sessionId,
|
|
4916
|
+
options,
|
|
4917
|
+
memorySignal
|
|
4918
|
+
);
|
|
4919
|
+
let hit;
|
|
4920
|
+
let resultCount;
|
|
4921
|
+
if (semantic) {
|
|
4922
|
+
resultCount = Array.isArray(data) ? data.length : 0;
|
|
4923
|
+
hit = resultCount > 0;
|
|
4924
|
+
} else {
|
|
4925
|
+
hit = data !== null && data !== void 0;
|
|
4926
|
+
}
|
|
4927
|
+
if (usage?.cost != null) {
|
|
4928
|
+
this._accumulateBudgetCost(usage.cost);
|
|
4929
|
+
}
|
|
4930
|
+
this.emitEvent({
|
|
4931
|
+
type: "memory_recall",
|
|
4932
|
+
...usage?.cost != null ? { cost: usage.cost } : {},
|
|
4933
|
+
...usage?.tokens != null ? { tokens: { input: usage.tokens } } : {},
|
|
4934
|
+
data: {
|
|
4935
|
+
key,
|
|
4936
|
+
scope,
|
|
4937
|
+
semantic,
|
|
4938
|
+
hit,
|
|
4939
|
+
...resultCount !== void 0 ? { resultCount, count: resultCount } : {},
|
|
4940
|
+
...usage ? { usage } : {}
|
|
4941
|
+
}
|
|
4942
|
+
});
|
|
4943
|
+
return data;
|
|
4944
|
+
} catch (err) {
|
|
4945
|
+
this.emitEvent({
|
|
4946
|
+
type: "memory_recall",
|
|
4947
|
+
data: {
|
|
4948
|
+
key,
|
|
4949
|
+
scope,
|
|
4950
|
+
semantic,
|
|
4951
|
+
hit: false,
|
|
4952
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4953
|
+
}
|
|
4954
|
+
});
|
|
4955
|
+
throw err;
|
|
4956
|
+
}
|
|
3786
4957
|
}
|
|
3787
4958
|
/** Delete a memory entry by key. */
|
|
3788
4959
|
async forget(key, options) {
|
|
@@ -3795,7 +4966,24 @@ ${summaryResponse.content}`
|
|
|
3795
4966
|
throw new Error("A state store is required for memory operations.");
|
|
3796
4967
|
}
|
|
3797
4968
|
const sessionId = this.metadata?.sessionId;
|
|
3798
|
-
|
|
4969
|
+
const scope = options?.scope ?? (sessionId ? "session" : "global");
|
|
4970
|
+
try {
|
|
4971
|
+
await this.memoryManager.forget(key, this.stateStore, sessionId, options);
|
|
4972
|
+
this.emitEvent({
|
|
4973
|
+
type: "memory_forget",
|
|
4974
|
+
data: { key, scope }
|
|
4975
|
+
});
|
|
4976
|
+
} catch (err) {
|
|
4977
|
+
this.emitEvent({
|
|
4978
|
+
type: "memory_forget",
|
|
4979
|
+
data: {
|
|
4980
|
+
key,
|
|
4981
|
+
scope,
|
|
4982
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4983
|
+
}
|
|
4984
|
+
});
|
|
4985
|
+
throw err;
|
|
4986
|
+
}
|
|
3799
4987
|
}
|
|
3800
4988
|
// ── ctx.delegate() ──────────────────────────────────────────────────
|
|
3801
4989
|
/**
|
|
@@ -3823,6 +5011,15 @@ ${summaryResponse.content}`
|
|
|
3823
5011
|
names.add(a._name);
|
|
3824
5012
|
}
|
|
3825
5013
|
if (agents.length === 1) {
|
|
5014
|
+
this.emitEvent({
|
|
5015
|
+
type: "delegate",
|
|
5016
|
+
agent: agents[0]._name,
|
|
5017
|
+
data: {
|
|
5018
|
+
candidates: [agents[0]._name],
|
|
5019
|
+
selected: agents[0]._name,
|
|
5020
|
+
reason: "single_candidate"
|
|
5021
|
+
}
|
|
5022
|
+
});
|
|
3826
5023
|
return this.ask(agents[0], prompt, {
|
|
3827
5024
|
schema: options?.schema,
|
|
3828
5025
|
retries: options?.retries,
|
|
@@ -3858,12 +5055,13 @@ ${summaryResponse.content}`
|
|
|
3858
5055
|
resolveModel: () => routerModelUri,
|
|
3859
5056
|
resolveSystem: () => routerSystem
|
|
3860
5057
|
};
|
|
3861
|
-
this.
|
|
5058
|
+
this.emitEvent({
|
|
3862
5059
|
type: "delegate",
|
|
3863
5060
|
agent: "_delegate_router",
|
|
3864
5061
|
data: {
|
|
3865
5062
|
candidates: agents.map((a) => a._name),
|
|
3866
|
-
routerModel: routerModelUri
|
|
5063
|
+
routerModel: routerModelUri,
|
|
5064
|
+
reason: "routed"
|
|
3867
5065
|
}
|
|
3868
5066
|
});
|
|
3869
5067
|
return this.ask(routerAgent, prompt, {
|
|
@@ -3875,24 +5073,57 @@ ${summaryResponse.content}`
|
|
|
3875
5073
|
});
|
|
3876
5074
|
}
|
|
3877
5075
|
// ── Private ───────────────────────────────────────────────────────────
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
5076
|
+
/**
|
|
5077
|
+
* Internal emitter input — intentionally loose so call sites don't need to
|
|
5078
|
+
* build a perfectly-narrowed discriminated-union member. The resulting
|
|
5079
|
+
* `AxlEvent` (exported type) remains strict, and TypeScript narrows it at
|
|
5080
|
+
* consumer sites via the `type` discriminator.
|
|
5081
|
+
*/
|
|
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;
|
|
3882
5089
|
}
|
|
3883
5090
|
const event = {
|
|
3884
5091
|
executionId: this.executionId,
|
|
3885
|
-
step
|
|
5092
|
+
step,
|
|
3886
5093
|
timestamp: Date.now(),
|
|
5094
|
+
...this.workflowName ? { workflow: this.workflowName } : {},
|
|
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 },
|
|
3887
5108
|
...partial,
|
|
3888
5109
|
data
|
|
3889
5110
|
};
|
|
3890
|
-
this.
|
|
5111
|
+
const finalEvent = this.config.trace?.redact ? redactEvent(event) : event;
|
|
5112
|
+
if (this.onTrace) {
|
|
5113
|
+
try {
|
|
5114
|
+
this.onTrace(finalEvent);
|
|
5115
|
+
} catch (err) {
|
|
5116
|
+
console.error(
|
|
5117
|
+
"[axl] onTrace handler threw; trace event dropped:",
|
|
5118
|
+
err instanceof Error ? err.message : String(err)
|
|
5119
|
+
);
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
3891
5122
|
}
|
|
3892
5123
|
};
|
|
3893
5124
|
|
|
3894
5125
|
// src/agent.ts
|
|
3895
|
-
import { randomUUID } from "crypto";
|
|
5126
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3896
5127
|
var agentCounter = 0;
|
|
3897
5128
|
function agent(config) {
|
|
3898
5129
|
agentCounter++;
|
|
@@ -3912,7 +5143,7 @@ function agent(config) {
|
|
|
3912
5143
|
const registry = new ProviderRegistry();
|
|
3913
5144
|
const ctx = new WorkflowContext({
|
|
3914
5145
|
input: prompt,
|
|
3915
|
-
executionId:
|
|
5146
|
+
executionId: randomUUID2(),
|
|
3916
5147
|
config: {},
|
|
3917
5148
|
providerRegistry: registry
|
|
3918
5149
|
});
|
|
@@ -3936,7 +5167,7 @@ function workflow(config) {
|
|
|
3936
5167
|
|
|
3937
5168
|
// src/runtime.ts
|
|
3938
5169
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
3939
|
-
import { randomUUID as
|
|
5170
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3940
5171
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
3941
5172
|
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
3942
5173
|
import { dirname } from "path";
|
|
@@ -3959,29 +5190,20 @@ var MemoryStore = class {
|
|
|
3959
5190
|
constructor() {
|
|
3960
5191
|
this.loadPersistedState();
|
|
3961
5192
|
}
|
|
3962
|
-
async saveCheckpoint(executionId,
|
|
3963
|
-
let
|
|
3964
|
-
if (!
|
|
3965
|
-
|
|
3966
|
-
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);
|
|
3967
5198
|
}
|
|
3968
|
-
|
|
5199
|
+
entries.set(name, structuredClone(data));
|
|
3969
5200
|
}
|
|
3970
|
-
async getCheckpoint(executionId,
|
|
3971
|
-
const
|
|
3972
|
-
if (!
|
|
3973
|
-
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);
|
|
3974
5205
|
return data !== void 0 ? structuredClone(data) : null;
|
|
3975
5206
|
}
|
|
3976
|
-
async getLatestCheckpoint(executionId) {
|
|
3977
|
-
const steps = this.checkpoints.get(executionId);
|
|
3978
|
-
if (!steps || steps.size === 0) return null;
|
|
3979
|
-
let maxStep = -1;
|
|
3980
|
-
for (const step of steps.keys()) {
|
|
3981
|
-
if (step > maxStep) maxStep = step;
|
|
3982
|
-
}
|
|
3983
|
-
return { step: maxStep, data: structuredClone(steps.get(maxStep)) };
|
|
3984
|
-
}
|
|
3985
5207
|
async saveSession(sessionId, history) {
|
|
3986
5208
|
this.sessions.set(sessionId, structuredClone(history));
|
|
3987
5209
|
}
|
|
@@ -4154,7 +5376,7 @@ function rowToExecutionInfo(row) {
|
|
|
4154
5376
|
completedAt: row.completed_at ?? void 0,
|
|
4155
5377
|
duration: row.duration,
|
|
4156
5378
|
error: row.error ?? void 0,
|
|
4157
|
-
|
|
5379
|
+
events: safeJsonParse(row.events) ?? []
|
|
4158
5380
|
};
|
|
4159
5381
|
}
|
|
4160
5382
|
var SQLiteStore = class {
|
|
@@ -4170,15 +5392,80 @@ var SQLiteStore = class {
|
|
|
4170
5392
|
}
|
|
4171
5393
|
this.db = new BetterSqlite3(filePath);
|
|
4172
5394
|
this.db.pragma("journal_mode = WAL");
|
|
5395
|
+
this.migrate();
|
|
4173
5396
|
this.initTables();
|
|
4174
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
|
+
}
|
|
4175
5462
|
initTables() {
|
|
4176
5463
|
this.db.exec(`
|
|
4177
5464
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
4178
5465
|
execution_id TEXT NOT NULL,
|
|
4179
|
-
|
|
5466
|
+
name TEXT NOT NULL,
|
|
4180
5467
|
data TEXT NOT NULL,
|
|
4181
|
-
PRIMARY KEY (execution_id,
|
|
5468
|
+
PRIMARY KEY (execution_id, name)
|
|
4182
5469
|
);
|
|
4183
5470
|
|
|
4184
5471
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -4227,7 +5514,7 @@ var SQLiteStore = class {
|
|
|
4227
5514
|
completed_at INTEGER,
|
|
4228
5515
|
duration INTEGER NOT NULL DEFAULT 0,
|
|
4229
5516
|
error TEXT,
|
|
4230
|
-
|
|
5517
|
+
events TEXT NOT NULL
|
|
4231
5518
|
);
|
|
4232
5519
|
|
|
4233
5520
|
CREATE TABLE IF NOT EXISTS eval_history (
|
|
@@ -4242,26 +5529,19 @@ var SQLiteStore = class {
|
|
|
4242
5529
|
`);
|
|
4243
5530
|
}
|
|
4244
5531
|
// ── Checkpoints ────────────────────────────────────────────────────────
|
|
4245
|
-
async saveCheckpoint(executionId,
|
|
5532
|
+
async saveCheckpoint(executionId, name, data) {
|
|
4246
5533
|
const stmt = this.db.prepare(
|
|
4247
|
-
"INSERT OR REPLACE INTO checkpoints (execution_id,
|
|
5534
|
+
"INSERT OR REPLACE INTO checkpoints (execution_id, name, data) VALUES (?, ?, ?)"
|
|
4248
5535
|
);
|
|
4249
|
-
stmt.run(executionId,
|
|
5536
|
+
stmt.run(executionId, name, JSON.stringify(data));
|
|
4250
5537
|
}
|
|
4251
|
-
async getCheckpoint(executionId,
|
|
5538
|
+
async getCheckpoint(executionId, name) {
|
|
4252
5539
|
const stmt = this.db.prepare(
|
|
4253
|
-
"SELECT data FROM checkpoints WHERE execution_id = ? AND
|
|
5540
|
+
"SELECT data FROM checkpoints WHERE execution_id = ? AND name = ?"
|
|
4254
5541
|
);
|
|
4255
|
-
const row = stmt.get(executionId,
|
|
5542
|
+
const row = stmt.get(executionId, name);
|
|
4256
5543
|
return row ? safeJsonParse(row.data) : null;
|
|
4257
5544
|
}
|
|
4258
|
-
async getLatestCheckpoint(executionId) {
|
|
4259
|
-
const stmt = this.db.prepare(
|
|
4260
|
-
"SELECT step, data FROM checkpoints WHERE execution_id = ? ORDER BY step DESC LIMIT 1"
|
|
4261
|
-
);
|
|
4262
|
-
const row = stmt.get(executionId);
|
|
4263
|
-
return row ? { step: row.step, data: safeJsonParse(row.data) } : null;
|
|
4264
|
-
}
|
|
4265
5545
|
// ── Sessions ────────────────────────────────────────────────────────────
|
|
4266
5546
|
async saveSession(sessionId, history) {
|
|
4267
5547
|
const stmt = this.db.prepare(
|
|
@@ -4355,7 +5635,7 @@ var SQLiteStore = class {
|
|
|
4355
5635
|
// ── Execution History ────────────────────────────────────────────────────
|
|
4356
5636
|
async saveExecution(execution) {
|
|
4357
5637
|
this.db.prepare(
|
|
4358
|
-
"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 (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
4359
5639
|
).run(
|
|
4360
5640
|
execution.executionId,
|
|
4361
5641
|
execution.workflow,
|
|
@@ -4365,7 +5645,7 @@ var SQLiteStore = class {
|
|
|
4365
5645
|
execution.completedAt ?? null,
|
|
4366
5646
|
execution.duration,
|
|
4367
5647
|
execution.error ?? null,
|
|
4368
|
-
JSON.stringify(execution.
|
|
5648
|
+
JSON.stringify(execution.events)
|
|
4369
5649
|
);
|
|
4370
5650
|
}
|
|
4371
5651
|
async getExecution(executionId) {
|
|
@@ -4539,7 +5819,8 @@ var Session = class _Session {
|
|
|
4539
5819
|
}
|
|
4540
5820
|
};
|
|
4541
5821
|
axlStream.on("done", (event) => {
|
|
4542
|
-
|
|
5822
|
+
const data = event.data;
|
|
5823
|
+
updateHistory(data.result).catch((err) => {
|
|
4543
5824
|
this.runtime.emit("error", {
|
|
4544
5825
|
type: "session_history_save_failed",
|
|
4545
5826
|
sessionId: this.sessionId,
|
|
@@ -4581,9 +5862,88 @@ var Session = class _Session {
|
|
|
4581
5862
|
// src/stream.ts
|
|
4582
5863
|
import { Readable } from "stream";
|
|
4583
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
|
|
4584
5922
|
var AxlStream = class _AxlStream extends Readable {
|
|
4585
5923
|
bus = new EventEmitter();
|
|
4586
|
-
|
|
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();
|
|
4587
5947
|
result = void 0;
|
|
4588
5948
|
finished = false;
|
|
4589
5949
|
promise;
|
|
@@ -4605,18 +5965,10 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4605
5965
|
this.promise.catch(() => {
|
|
4606
5966
|
});
|
|
4607
5967
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
"tool_approval",
|
|
4613
|
-
"agent_start",
|
|
4614
|
-
"agent_end",
|
|
4615
|
-
"handoff",
|
|
4616
|
-
"step",
|
|
4617
|
-
"done",
|
|
4618
|
-
"error"
|
|
4619
|
-
]);
|
|
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);
|
|
4620
5972
|
on(event, handler) {
|
|
4621
5973
|
if (_AxlStream.STREAM_EVENTS.has(event)) {
|
|
4622
5974
|
this.bus.on(event, handler);
|
|
@@ -4666,21 +6018,83 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4666
6018
|
while (true) {
|
|
4667
6019
|
const { value, done } = await iter.next();
|
|
4668
6020
|
if (done) return { value: void 0, done: true };
|
|
4669
|
-
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
|
+
}
|
|
4670
6062
|
}
|
|
4671
6063
|
}
|
|
4672
6064
|
};
|
|
4673
6065
|
}
|
|
4674
6066
|
};
|
|
4675
6067
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
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",
|
|
4682
6083
|
"tool_approval",
|
|
4683
|
-
"
|
|
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"
|
|
4684
6098
|
]);
|
|
4685
6099
|
const self = this;
|
|
4686
6100
|
return {
|
|
@@ -4691,7 +6105,7 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4691
6105
|
while (true) {
|
|
4692
6106
|
const { value, done } = await iter.next();
|
|
4693
6107
|
if (done) return { value: void 0, done: true };
|
|
4694
|
-
if (
|
|
6108
|
+
if (lifecycleTypes.has(value.type)) return { value, done: false };
|
|
4695
6109
|
}
|
|
4696
6110
|
}
|
|
4697
6111
|
};
|
|
@@ -4711,10 +6125,34 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4711
6125
|
});
|
|
4712
6126
|
return destination;
|
|
4713
6127
|
}
|
|
4714
|
-
/** Push
|
|
6128
|
+
/** Push an event onto the stream. Called by the runtime. */
|
|
4715
6129
|
_push(event) {
|
|
4716
6130
|
if (this.finished) return;
|
|
4717
|
-
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
|
+
}
|
|
4718
6156
|
this.bus.emit(event.type, event);
|
|
4719
6157
|
this.push(event);
|
|
4720
6158
|
const waiter = this.waiters.shift();
|
|
@@ -4724,12 +6162,25 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4724
6162
|
this.eventQueue.push(event);
|
|
4725
6163
|
}
|
|
4726
6164
|
}
|
|
4727
|
-
/**
|
|
4728
|
-
|
|
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) {
|
|
4729
6174
|
if (this.finished) return;
|
|
4730
6175
|
this.finished = true;
|
|
4731
6176
|
this.result = result;
|
|
4732
|
-
const doneEvent = {
|
|
6177
|
+
const doneEvent = {
|
|
6178
|
+
type: "done",
|
|
6179
|
+
executionId,
|
|
6180
|
+
step: Number.MAX_SAFE_INTEGER,
|
|
6181
|
+
timestamp: Date.now(),
|
|
6182
|
+
data: { result }
|
|
6183
|
+
};
|
|
4733
6184
|
this.bus.emit("done", doneEvent);
|
|
4734
6185
|
this.push(doneEvent);
|
|
4735
6186
|
this.push(null);
|
|
@@ -4745,11 +6196,19 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4745
6196
|
this.waiters.length = 0;
|
|
4746
6197
|
this.bus.emit("__resolve", result);
|
|
4747
6198
|
}
|
|
4748
|
-
/** Signal an error.
|
|
4749
|
-
|
|
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) {
|
|
4750
6203
|
if (this.finished) return;
|
|
4751
6204
|
this.finished = true;
|
|
4752
|
-
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
|
+
};
|
|
4753
6212
|
this.bus.emit("error", errorEvent);
|
|
4754
6213
|
this.push(errorEvent);
|
|
4755
6214
|
this.push(null);
|
|
@@ -4765,8 +6224,31 @@ var AxlStream = class _AxlStream extends Readable {
|
|
|
4765
6224
|
this.waiters.length = 0;
|
|
4766
6225
|
this.bus.emit("__reject", error);
|
|
4767
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`. */
|
|
4768
6240
|
get fullText() {
|
|
4769
|
-
|
|
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;
|
|
4770
6252
|
}
|
|
4771
6253
|
};
|
|
4772
6254
|
|
|
@@ -5129,6 +6611,18 @@ var McpManager = class {
|
|
|
5129
6611
|
};
|
|
5130
6612
|
|
|
5131
6613
|
// src/memory/manager.ts
|
|
6614
|
+
function assertEmbedResult(result) {
|
|
6615
|
+
if (Array.isArray(result)) {
|
|
6616
|
+
throw new Error(
|
|
6617
|
+
"Embedder.embed() returned a bare number[][] (legacy shape). As of @axlsdk/axl 0.15, Embedder.embed() must return { vectors: number[][]; usage?: { tokens?, cost?, model? } }. Wrap your existing return value: `return { vectors: await old() }`."
|
|
6618
|
+
);
|
|
6619
|
+
}
|
|
6620
|
+
if (!result || typeof result !== "object" || !Array.isArray(result.vectors)) {
|
|
6621
|
+
throw new Error(
|
|
6622
|
+
`Embedder.embed() must return { vectors: number[][]; usage?: EmbedUsage }. Got ${result === null ? "null" : typeof result}.`
|
|
6623
|
+
);
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
5132
6626
|
var MemoryManager = class {
|
|
5133
6627
|
vectorStore;
|
|
5134
6628
|
embedder;
|
|
@@ -5143,8 +6637,11 @@ var MemoryManager = class {
|
|
|
5143
6637
|
* Store a key-value pair in memory.
|
|
5144
6638
|
* If a vector store and embedder are configured and embed is true,
|
|
5145
6639
|
* also embeds the value for semantic search.
|
|
6640
|
+
*
|
|
6641
|
+
* Returns `{ usage }` when embedding happened, carrying the embedder's
|
|
6642
|
+
* cost/token reporting out to the caller for cost attribution.
|
|
5146
6643
|
*/
|
|
5147
|
-
async remember(key, value, stateStore, sessionId, options) {
|
|
6644
|
+
async remember(key, value, stateStore, sessionId, options, signal) {
|
|
5148
6645
|
const scope = options?.scope ?? "session";
|
|
5149
6646
|
if (scope === "session" && !sessionId) {
|
|
5150
6647
|
throw new Error(
|
|
@@ -5163,23 +6660,44 @@ var MemoryManager = class {
|
|
|
5163
6660
|
}
|
|
5164
6661
|
if (options?.embed === true && this.vectorStore && this.embedder) {
|
|
5165
6662
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
5166
|
-
const
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
6663
|
+
const raw = await this.embedder.embed([text], signal);
|
|
6664
|
+
assertEmbedResult(raw);
|
|
6665
|
+
const { vectors, usage } = raw;
|
|
6666
|
+
try {
|
|
6667
|
+
await this.vectorStore.upsert([
|
|
6668
|
+
{
|
|
6669
|
+
id: `${storeScope}:${key}`,
|
|
6670
|
+
content: text,
|
|
6671
|
+
embedding: vectors[0],
|
|
6672
|
+
metadata: { key, scope, ...options?.metadata }
|
|
6673
|
+
}
|
|
6674
|
+
]);
|
|
6675
|
+
} catch (err) {
|
|
6676
|
+
if (usage && typeof err === "object" && err !== null) {
|
|
6677
|
+
Object.defineProperty(err, "axlEmbedUsage", {
|
|
6678
|
+
value: usage,
|
|
6679
|
+
enumerable: false,
|
|
6680
|
+
writable: true,
|
|
6681
|
+
configurable: true
|
|
6682
|
+
});
|
|
5173
6683
|
}
|
|
5174
|
-
|
|
6684
|
+
throw err;
|
|
6685
|
+
}
|
|
6686
|
+
return usage ? { usage } : {};
|
|
5175
6687
|
}
|
|
6688
|
+
return {};
|
|
5176
6689
|
}
|
|
5177
6690
|
/**
|
|
5178
6691
|
* Recall a value from memory by key, or perform semantic search if query is provided.
|
|
6692
|
+
*
|
|
6693
|
+
* Always returns `{ data }`; semantic recalls additionally include
|
|
6694
|
+
* `usage` from the embedder (when the embedder reported it).
|
|
5179
6695
|
*/
|
|
5180
|
-
async recall(key, stateStore, sessionId, options) {
|
|
6696
|
+
async recall(key, stateStore, sessionId, options, signal) {
|
|
5181
6697
|
if (options?.query && this.vectorStore && this.embedder) {
|
|
5182
|
-
const
|
|
6698
|
+
const raw = await this.embedder.embed([options.query], signal);
|
|
6699
|
+
assertEmbedResult(raw);
|
|
6700
|
+
const { vectors, usage } = raw;
|
|
5183
6701
|
const topK = options?.topK ?? 5;
|
|
5184
6702
|
const searchScope = options?.scope ?? "session";
|
|
5185
6703
|
if (searchScope === "session" && !sessionId) {
|
|
@@ -5187,13 +6705,14 @@ var MemoryManager = class {
|
|
|
5187
6705
|
'sessionId is required for session-scoped memory. Use { scope: "global" } or provide a sessionId in metadata.'
|
|
5188
6706
|
);
|
|
5189
6707
|
}
|
|
5190
|
-
const rawResults = await this.vectorStore.search(
|
|
6708
|
+
const rawResults = await this.vectorStore.search(vectors[0], topK * 3);
|
|
5191
6709
|
const filtered = rawResults.filter((r) => {
|
|
5192
6710
|
if (r.id.startsWith("global:")) return true;
|
|
5193
6711
|
if (searchScope === "session") return r.id.startsWith(`session:${sessionId}:`);
|
|
5194
6712
|
return true;
|
|
5195
6713
|
});
|
|
5196
|
-
|
|
6714
|
+
const data = filtered.slice(0, topK);
|
|
6715
|
+
return usage ? { data, usage } : { data };
|
|
5197
6716
|
}
|
|
5198
6717
|
const scope = options?.scope ?? "session";
|
|
5199
6718
|
if (scope === "session" && !sessionId) {
|
|
@@ -5203,10 +6722,10 @@ var MemoryManager = class {
|
|
|
5203
6722
|
}
|
|
5204
6723
|
const storeScope = scope === "session" ? `session:${sessionId}` : "global";
|
|
5205
6724
|
if (stateStore.getMemory) {
|
|
5206
|
-
return stateStore.getMemory(storeScope, key);
|
|
6725
|
+
return { data: await stateStore.getMemory(storeScope, key) };
|
|
5207
6726
|
}
|
|
5208
6727
|
const storeKey = `memory:${storeScope}:${key}`;
|
|
5209
|
-
return stateStore.getSessionMeta(storeKey, "value");
|
|
6728
|
+
return { data: await stateStore.getSessionMeta(storeKey, "value") };
|
|
5210
6729
|
}
|
|
5211
6730
|
/** Delete a memory entry. If a vector store is configured, also removes the embedding. */
|
|
5212
6731
|
async forget(key, stateStore, sessionId, options) {
|
|
@@ -5275,6 +6794,32 @@ function hashInput(input) {
|
|
|
5275
6794
|
}
|
|
5276
6795
|
return (hash >>> 0).toString(16);
|
|
5277
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
|
+
}
|
|
5278
6823
|
var costScopeStorage = new AsyncLocalStorage2();
|
|
5279
6824
|
var AxlRuntime = class extends EventEmitter2 {
|
|
5280
6825
|
config;
|
|
@@ -5294,11 +6839,27 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5294
6839
|
historicalExecutionsLoadPromise = null;
|
|
5295
6840
|
evalHistory = [];
|
|
5296
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;
|
|
5297
6846
|
constructor(config) {
|
|
5298
6847
|
super();
|
|
5299
6848
|
this.config = resolveConfig(config ?? {});
|
|
5300
6849
|
this.providerRegistry = new ProviderRegistry();
|
|
5301
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
|
+
}
|
|
5302
6863
|
if (this.config.memory) {
|
|
5303
6864
|
this.memoryManager = new MemoryManager({
|
|
5304
6865
|
vectorStore: this.config.memory.vectorStore,
|
|
@@ -5306,6 +6867,22 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5306
6867
|
});
|
|
5307
6868
|
}
|
|
5308
6869
|
}
|
|
6870
|
+
/**
|
|
6871
|
+
* Whether `config.trace.redact` is enabled on this runtime. A narrow
|
|
6872
|
+
* boolean getter is preferred over exposing the full config because:
|
|
6873
|
+
* (a) `Readonly<AxlConfig>` is shallow so the config would be mutable
|
|
6874
|
+
* at runtime through sub-objects, subverting any compliance guarantee;
|
|
6875
|
+
* (b) observability consumers like Studio only need the boolean, not
|
|
6876
|
+
* the whole config tree; (c) if future observability config needs to
|
|
6877
|
+
* expand, each new flag gets its own narrow getter.
|
|
6878
|
+
*
|
|
6879
|
+
* Studio's server-side redaction helpers (executions, memory, sessions,
|
|
6880
|
+
* evals, decisions, tools, playground, workflows) all route through
|
|
6881
|
+
* this accessor to decide whether to scrub response payloads.
|
|
6882
|
+
*/
|
|
6883
|
+
isRedactEnabled() {
|
|
6884
|
+
return this.config.trace?.redact === true;
|
|
6885
|
+
}
|
|
5309
6886
|
/**
|
|
5310
6887
|
* Initialize MCP servers configured in the config.
|
|
5311
6888
|
* Call this before executing workflows that use MCP tools.
|
|
@@ -5338,6 +6915,71 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5338
6915
|
return new MemoryStore();
|
|
5339
6916
|
}
|
|
5340
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
|
+
}
|
|
5341
6983
|
/**
|
|
5342
6984
|
* Persist a completed/failed execution to the state store (fire-and-forget)
|
|
5343
6985
|
* and move it from the active executions map to the historical cache.
|
|
@@ -5353,7 +6995,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5353
6995
|
this.executions.delete(id);
|
|
5354
6996
|
}
|
|
5355
6997
|
/** Register a workflow with the runtime. */
|
|
5356
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5357
6998
|
register(workflow2) {
|
|
5358
6999
|
this.workflows.set(workflow2.name, workflow2);
|
|
5359
7000
|
}
|
|
@@ -5455,10 +7096,19 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5455
7096
|
metadata: result2.metadata ?? metadata
|
|
5456
7097
|
};
|
|
5457
7098
|
};
|
|
5458
|
-
result = await runEvalFn(entry.config, wrappedExecuteFn, this
|
|
7099
|
+
result = await runEvalFn(entry.config, wrappedExecuteFn, this, {
|
|
7100
|
+
onProgress: options?.onProgress,
|
|
7101
|
+
signal: options?.signal,
|
|
7102
|
+
captureTraces: options?.captureTraces
|
|
7103
|
+
});
|
|
5459
7104
|
} else {
|
|
5460
7105
|
result = await this.eval(
|
|
5461
|
-
entry.config
|
|
7106
|
+
entry.config,
|
|
7107
|
+
{
|
|
7108
|
+
onProgress: options?.onProgress,
|
|
7109
|
+
signal: options?.signal,
|
|
7110
|
+
captureTraces: options?.captureTraces
|
|
7111
|
+
}
|
|
5462
7112
|
);
|
|
5463
7113
|
}
|
|
5464
7114
|
const resultObj = result;
|
|
@@ -5470,7 +7120,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5470
7120
|
}
|
|
5471
7121
|
try {
|
|
5472
7122
|
await this.saveEvalResult({
|
|
5473
|
-
id: resultObj.id ??
|
|
7123
|
+
id: resultObj.id ?? randomUUID3(),
|
|
5474
7124
|
eval: name,
|
|
5475
7125
|
timestamp: Date.now(),
|
|
5476
7126
|
data: structuredClone(result)
|
|
@@ -5507,7 +7157,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5507
7157
|
* and automatically emits trace events and tracks cost.
|
|
5508
7158
|
*/
|
|
5509
7159
|
createContext(options) {
|
|
5510
|
-
const executionId =
|
|
7160
|
+
const executionId = randomUUID3();
|
|
5511
7161
|
const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
|
|
5512
7162
|
this.registerWithCostScope(executionId);
|
|
5513
7163
|
return new WorkflowContext({
|
|
@@ -5523,10 +7173,12 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5523
7173
|
sessionHistory: options?.sessionHistory,
|
|
5524
7174
|
signal: options?.signal,
|
|
5525
7175
|
onToken: options?.onToken,
|
|
7176
|
+
onToolCall: options?.onToolCall,
|
|
7177
|
+
onAgentStart: options?.onAgentStart,
|
|
5526
7178
|
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5527
7179
|
onTrace: (event) => {
|
|
5528
7180
|
this.emit("trace", event);
|
|
5529
|
-
this.
|
|
7181
|
+
this.outputAxlEvent(event);
|
|
5530
7182
|
},
|
|
5531
7183
|
budgetContext: {
|
|
5532
7184
|
totalCost: 0,
|
|
@@ -5553,7 +7205,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5553
7205
|
);
|
|
5554
7206
|
}
|
|
5555
7207
|
const validated = workflow2.inputSchema.parse(input);
|
|
5556
|
-
const executionId =
|
|
7208
|
+
const executionId = randomUUID3();
|
|
5557
7209
|
const controller = new AbortController();
|
|
5558
7210
|
this.abortControllers.set(executionId, controller);
|
|
5559
7211
|
this.registerWithCostScope(executionId);
|
|
@@ -5561,7 +7213,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5561
7213
|
executionId,
|
|
5562
7214
|
workflow: name,
|
|
5563
7215
|
status: "running",
|
|
5564
|
-
|
|
7216
|
+
events: [],
|
|
5565
7217
|
totalCost: 0,
|
|
5566
7218
|
startedAt: Date.now(),
|
|
5567
7219
|
duration: 0
|
|
@@ -5577,25 +7229,41 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5577
7229
|
sessionHistory,
|
|
5578
7230
|
signal: controller.signal,
|
|
5579
7231
|
onTrace: (event) => {
|
|
5580
|
-
execInfo.
|
|
5581
|
-
|
|
7232
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7233
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
5582
7234
|
this.emit("trace", event);
|
|
5583
|
-
this.
|
|
5584
|
-
if (event.type === "
|
|
7235
|
+
this.outputAxlEvent(event);
|
|
7236
|
+
if (event.type === "handoff_start") {
|
|
5585
7237
|
const sessionId = options?.metadata?.sessionId;
|
|
5586
7238
|
if (sessionId) {
|
|
5587
|
-
const data = event.data;
|
|
5588
7239
|
this.appendHandoffRecord(sessionId, {
|
|
5589
|
-
source: event.
|
|
5590
|
-
target: data
|
|
5591
|
-
mode: data
|
|
7240
|
+
source: event.data.source,
|
|
7241
|
+
target: event.data.target,
|
|
7242
|
+
mode: event.data.mode,
|
|
5592
7243
|
timestamp: event.timestamp,
|
|
5593
|
-
|
|
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
|
|
5594
7261
|
});
|
|
5595
7262
|
}
|
|
5596
7263
|
}
|
|
5597
7264
|
},
|
|
5598
7265
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
7266
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5599
7267
|
stateStore: this.stateStore,
|
|
5600
7268
|
workflowName: name,
|
|
5601
7269
|
mcpManager: this.mcpManager,
|
|
@@ -5609,7 +7277,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5609
7277
|
policy: "finish_and_stop"
|
|
5610
7278
|
}
|
|
5611
7279
|
});
|
|
5612
|
-
ctx.log("workflow_start", { workflow: name, executionId });
|
|
5613
7280
|
return this.spanManager.withSpanAsync(
|
|
5614
7281
|
"axl.workflow.execute",
|
|
5615
7282
|
{
|
|
@@ -5617,39 +7284,13 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5617
7284
|
"axl.execution.id": executionId,
|
|
5618
7285
|
"axl.workflow.input_hash": hashInput(validated)
|
|
5619
7286
|
},
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
execInfo.result = output;
|
|
5628
|
-
ctx.log("workflow_end", {
|
|
5629
|
-
workflow: name,
|
|
5630
|
-
status: "completed",
|
|
5631
|
-
duration: execInfo.duration,
|
|
5632
|
-
cost: execInfo.totalCost
|
|
5633
|
-
});
|
|
5634
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
5635
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
5636
|
-
}
|
|
5637
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
5638
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
5639
|
-
this.persistExecution(execInfo);
|
|
5640
|
-
return output;
|
|
5641
|
-
} catch (err) {
|
|
5642
|
-
execInfo.status = "failed";
|
|
5643
|
-
execInfo.completedAt = Date.now();
|
|
5644
|
-
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5645
|
-
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5646
|
-
ctx.log("workflow_end", { workflow: name, status: "failed", error: execInfo.error });
|
|
5647
|
-
this.persistExecution(execInfo);
|
|
5648
|
-
throw err;
|
|
5649
|
-
} finally {
|
|
5650
|
-
this.abortControllers.delete(executionId);
|
|
5651
|
-
}
|
|
5652
|
-
}
|
|
7287
|
+
(span) => this.runWorkflowBody({
|
|
7288
|
+
workflow: workflow2,
|
|
7289
|
+
ctx,
|
|
7290
|
+
execInfo,
|
|
7291
|
+
validated,
|
|
7292
|
+
span
|
|
7293
|
+
})
|
|
5653
7294
|
);
|
|
5654
7295
|
}
|
|
5655
7296
|
/** Execute a workflow and return a stream. */
|
|
@@ -5657,21 +7298,20 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5657
7298
|
const axlStream = new AxlStream();
|
|
5658
7299
|
const controller = new AbortController();
|
|
5659
7300
|
axlStream.on("close", () => controller.abort());
|
|
7301
|
+
const executionId = randomUUID3();
|
|
7302
|
+
this.abortControllers.set(executionId, controller);
|
|
5660
7303
|
let execInfo;
|
|
5661
|
-
let ctx;
|
|
5662
7304
|
const run = async () => {
|
|
5663
7305
|
const workflow2 = this.workflows.get(name);
|
|
5664
7306
|
if (!workflow2) throw new Error(`Workflow "${name}" not registered`);
|
|
5665
7307
|
const validated = workflow2.inputSchema.parse(input);
|
|
5666
|
-
const executionId = randomUUID2();
|
|
5667
|
-
this.abortControllers.set(executionId, controller);
|
|
5668
7308
|
const sessionHistory = options?.metadata?.sessionHistory ?? void 0;
|
|
5669
7309
|
this.registerWithCostScope(executionId);
|
|
5670
7310
|
execInfo = {
|
|
5671
7311
|
executionId,
|
|
5672
7312
|
workflow: name,
|
|
5673
7313
|
status: "running",
|
|
5674
|
-
|
|
7314
|
+
events: [],
|
|
5675
7315
|
totalCost: 0,
|
|
5676
7316
|
startedAt: Date.now(),
|
|
5677
7317
|
duration: 0
|
|
@@ -5685,79 +7325,49 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5685
7325
|
providerRegistry: this.providerRegistry,
|
|
5686
7326
|
sessionHistory,
|
|
5687
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
|
+
},
|
|
5688
7334
|
onTrace: (event) => {
|
|
5689
|
-
execInfo.
|
|
5690
|
-
|
|
7335
|
+
pushEventBounded(execInfo, event, this.maxEventsPerExecution);
|
|
7336
|
+
execInfo.totalCost += eventCostContribution(event);
|
|
5691
7337
|
this.emit("trace", event);
|
|
5692
|
-
this.
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
axlStream._push({
|
|
5696
|
-
type: "handoff",
|
|
5697
|
-
source: event.agent ?? "",
|
|
5698
|
-
target: data?.target ?? "",
|
|
5699
|
-
mode: data?.mode ?? void 0
|
|
5700
|
-
});
|
|
7338
|
+
this.outputAxlEvent(event);
|
|
7339
|
+
axlStream._push(event);
|
|
7340
|
+
if (event.type === "handoff_start") {
|
|
5701
7341
|
const sessionId = options?.metadata?.sessionId;
|
|
5702
7342
|
if (sessionId) {
|
|
5703
7343
|
this.appendHandoffRecord(sessionId, {
|
|
5704
|
-
source: event.
|
|
5705
|
-
target: data
|
|
5706
|
-
mode: data
|
|
7344
|
+
source: event.data.source,
|
|
7345
|
+
target: event.data.target,
|
|
7346
|
+
mode: event.data.mode,
|
|
5707
7347
|
timestamp: event.timestamp,
|
|
5708
|
-
|
|
7348
|
+
toAskId: event.toAskId
|
|
5709
7349
|
});
|
|
5710
7350
|
}
|
|
5711
|
-
} else if (event.type === "
|
|
5712
|
-
const
|
|
5713
|
-
if (
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
args: data?.args,
|
|
5718
|
-
approved: true
|
|
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
|
|
5719
7357
|
});
|
|
5720
|
-
}
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
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
|
|
5727
7365
|
});
|
|
5728
7366
|
}
|
|
5729
|
-
} else if (event.type === "agent_call") {
|
|
5730
|
-
axlStream._push({
|
|
5731
|
-
type: "agent_end",
|
|
5732
|
-
agent: event.agent ?? "",
|
|
5733
|
-
cost: event.cost,
|
|
5734
|
-
duration: event.duration
|
|
5735
|
-
});
|
|
5736
|
-
} else if (event.type === "tool_call") {
|
|
5737
|
-
axlStream._push({
|
|
5738
|
-
type: "tool_result",
|
|
5739
|
-
name: event.tool ?? "",
|
|
5740
|
-
result: event.data?.result,
|
|
5741
|
-
callId: event.data?.callId
|
|
5742
|
-
});
|
|
5743
7367
|
}
|
|
5744
|
-
axlStream._push({ type: "step", step: event.step, data: event });
|
|
5745
|
-
},
|
|
5746
|
-
onToken: (token) => {
|
|
5747
|
-
axlStream._push({ type: "token", data: token });
|
|
5748
|
-
},
|
|
5749
|
-
onToolCall: (call) => {
|
|
5750
|
-
axlStream._push({
|
|
5751
|
-
type: "tool_call",
|
|
5752
|
-
name: call.name,
|
|
5753
|
-
args: call.args,
|
|
5754
|
-
callId: call.callId
|
|
5755
|
-
});
|
|
5756
|
-
},
|
|
5757
|
-
onAgentStart: (info) => {
|
|
5758
|
-
axlStream._push({ type: "agent_start", agent: info.agent, model: info.model });
|
|
5759
7368
|
},
|
|
5760
7369
|
pendingDecisions: this.pendingDecisionResolvers,
|
|
7370
|
+
awaitHumanHandler: options?.awaitHumanHandler,
|
|
5761
7371
|
stateStore: this.stateStore,
|
|
5762
7372
|
workflowName: name,
|
|
5763
7373
|
mcpManager: this.mcpManager,
|
|
@@ -5771,7 +7381,6 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5771
7381
|
policy: "finish_and_stop"
|
|
5772
7382
|
}
|
|
5773
7383
|
});
|
|
5774
|
-
ctx = wfCtx;
|
|
5775
7384
|
return this.spanManager.withSpanAsync(
|
|
5776
7385
|
"axl.workflow.execute",
|
|
5777
7386
|
{
|
|
@@ -5779,47 +7388,25 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5779
7388
|
"axl.execution.id": executionId,
|
|
5780
7389
|
"axl.workflow.input_hash": hashInput(validated)
|
|
5781
7390
|
},
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
execInfo.result = result;
|
|
5790
|
-
wfCtx.log("workflow_end", {
|
|
5791
|
-
workflow: name,
|
|
5792
|
-
status: "completed",
|
|
5793
|
-
duration: execInfo.duration,
|
|
5794
|
-
cost: execInfo.totalCost
|
|
5795
|
-
});
|
|
5796
|
-
if (this.stateStore.deleteCheckpoints) {
|
|
5797
|
-
await this.stateStore.deleteCheckpoints(executionId);
|
|
5798
|
-
}
|
|
5799
|
-
span.setAttribute("axl.workflow.cost", execInfo.totalCost);
|
|
5800
|
-
span.setAttribute("axl.workflow.duration", execInfo.duration);
|
|
5801
|
-
this.persistExecution(execInfo);
|
|
5802
|
-
return result;
|
|
5803
|
-
} finally {
|
|
5804
|
-
this.abortControllers.delete(executionId);
|
|
5805
|
-
}
|
|
5806
|
-
}
|
|
7391
|
+
(span) => this.runWorkflowBody({
|
|
7392
|
+
workflow: workflow2,
|
|
7393
|
+
ctx: wfCtx,
|
|
7394
|
+
execInfo,
|
|
7395
|
+
validated,
|
|
7396
|
+
span
|
|
7397
|
+
})
|
|
5807
7398
|
);
|
|
5808
7399
|
};
|
|
5809
|
-
run().then((result) => axlStream._done(result)).catch((err) => {
|
|
5810
|
-
if (execInfo) {
|
|
7400
|
+
run().then((result) => axlStream._done(result, executionId)).catch((err) => {
|
|
7401
|
+
if (execInfo && execInfo.status === "running") {
|
|
5811
7402
|
execInfo.status = "failed";
|
|
5812
7403
|
execInfo.completedAt = Date.now();
|
|
5813
7404
|
execInfo.duration = execInfo.completedAt - execInfo.startedAt;
|
|
5814
7405
|
execInfo.error = err instanceof Error ? err.message : String(err);
|
|
5815
|
-
ctx?.log("workflow_end", {
|
|
5816
|
-
workflow: name,
|
|
5817
|
-
status: "failed",
|
|
5818
|
-
error: execInfo.error
|
|
5819
|
-
});
|
|
5820
7406
|
this.persistExecution(execInfo);
|
|
5821
7407
|
}
|
|
5822
|
-
|
|
7408
|
+
this.abortControllers.delete(executionId);
|
|
7409
|
+
axlStream._error(err instanceof Error ? err : new Error(String(err)), executionId);
|
|
5823
7410
|
});
|
|
5824
7411
|
return axlStream;
|
|
5825
7412
|
}
|
|
@@ -5880,6 +7467,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
5880
7467
|
if (this.stateStore.saveEvalResult) {
|
|
5881
7468
|
await this.stateStore.saveEvalResult(entry);
|
|
5882
7469
|
}
|
|
7470
|
+
this.emit("eval_result", entry);
|
|
5883
7471
|
}
|
|
5884
7472
|
/**
|
|
5885
7473
|
* Delete an eval history entry by id. Removes from in-memory cache and
|
|
@@ -6011,7 +7599,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6011
7599
|
*
|
|
6012
7600
|
* @see Spec Section 13.5
|
|
6013
7601
|
*/
|
|
6014
|
-
async eval(config) {
|
|
7602
|
+
async eval(config, options) {
|
|
6015
7603
|
let runEvalFn;
|
|
6016
7604
|
try {
|
|
6017
7605
|
({ runEval: runEvalFn } = await import("@axlsdk/eval"));
|
|
@@ -6026,7 +7614,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6026
7614
|
});
|
|
6027
7615
|
return { output: result, cost, metadata };
|
|
6028
7616
|
};
|
|
6029
|
-
return runEvalFn(config, executeWorkflow, this);
|
|
7617
|
+
return runEvalFn(config, executeWorkflow, this, options);
|
|
6030
7618
|
}
|
|
6031
7619
|
/**
|
|
6032
7620
|
* Compare two evaluation results to detect regressions and improvements.
|
|
@@ -6064,9 +7652,31 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6064
7652
|
* Returns cost (same as `trackCost`) plus metadata extracted from trace events:
|
|
6065
7653
|
* models (unique URIs), tokens (input/output/reasoning sums), and agent call count.
|
|
6066
7654
|
*
|
|
7655
|
+
* ## Cost vs tokens semantics
|
|
7656
|
+
*
|
|
7657
|
+
* - `cost` is the full aggregate across EVERY event with a top-level
|
|
7658
|
+
* `event.cost` set: agent calls, tool calls, semantic memory ops, etc.
|
|
7659
|
+
* This is the number to reconcile against your provider bill.
|
|
7660
|
+
*
|
|
7661
|
+
* - `metadata.tokens` is narrowly scoped to **agent** prompt/completion/
|
|
7662
|
+
* reasoning tokens. Embedder tokens from semantic `ctx.remember({embed:true})`
|
|
7663
|
+
* / `ctx.recall({query})` are deliberately NOT summed here — they're a
|
|
7664
|
+
* different category (input-only, different pricing, different model).
|
|
7665
|
+
* Conflating them would make "prompt tokens" misleading in the UI. If you
|
|
7666
|
+
* need embedder token counts, subscribe to `runtime.on('trace', ...)` and
|
|
7667
|
+
* read `data.usage.tokens` on `memory_remember` / `memory_recall` events.
|
|
7668
|
+
*
|
|
7669
|
+
* Pass `{ captureTraces: true }` to also collect the raw `AxlEvent[]` observed
|
|
7670
|
+
* during `fn()`. This is opt-in because it keeps every event in memory for the
|
|
7671
|
+
* duration of the call — useful for eval per-item capture, debugging, and test
|
|
7672
|
+
* assertions, but overhead grows with trace volume. When enabled, verbose-mode
|
|
7673
|
+
* `agent_call_start.data.messages` snapshots are omitted from captured events (still
|
|
7674
|
+
* broadcast via onTrace) to keep memory bounded — callers who need the full
|
|
7675
|
+
* verbose snapshot should subscribe to `runtime.on('trace', ...)` directly.
|
|
7676
|
+
*
|
|
6067
7677
|
* Works with both `createContext()` and `execute()` calls inside `fn`.
|
|
6068
7678
|
*/
|
|
6069
|
-
async trackExecution(fn) {
|
|
7679
|
+
async trackExecution(fn, options) {
|
|
6070
7680
|
const parentScope = costScopeStorage.getStore();
|
|
6071
7681
|
const scope = {
|
|
6072
7682
|
totalCost: 0,
|
|
@@ -6077,10 +7687,11 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6077
7687
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
6078
7688
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
6079
7689
|
let agentCalls = 0;
|
|
7690
|
+
const capturedTraces = options?.captureTraces ? [] : void 0;
|
|
6080
7691
|
const listener = (event) => {
|
|
6081
7692
|
if (!scope.trackedIds.has(event.executionId)) return;
|
|
6082
|
-
|
|
6083
|
-
if (event.type === "
|
|
7693
|
+
scope.totalCost += eventCostContribution(event);
|
|
7694
|
+
if (event.type === "agent_call_end") {
|
|
6084
7695
|
if (event.model) modelCalls.set(event.model, (modelCalls.get(event.model) ?? 0) + 1);
|
|
6085
7696
|
agentCalls++;
|
|
6086
7697
|
if (event.tokens) {
|
|
@@ -6089,17 +7700,23 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6089
7700
|
tokens.reasoning += event.tokens.reasoning ?? 0;
|
|
6090
7701
|
}
|
|
6091
7702
|
}
|
|
6092
|
-
let workflowName;
|
|
6093
7703
|
if (event.type === "workflow_start" && event.workflow) {
|
|
6094
|
-
|
|
6095
|
-
} else if (event.type === "log" && event.data && typeof event.data === "object") {
|
|
6096
|
-
const d = event.data;
|
|
6097
|
-
if (d.event === "workflow_start" && typeof d.workflow === "string") {
|
|
6098
|
-
workflowName = d.workflow;
|
|
6099
|
-
}
|
|
7704
|
+
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
6100
7705
|
}
|
|
6101
|
-
if (
|
|
6102
|
-
|
|
7706
|
+
if (capturedTraces) {
|
|
7707
|
+
if (event.type === "token" || event.type === "partial_object") return;
|
|
7708
|
+
if (event.type === "agent_call_start" && event.data) {
|
|
7709
|
+
const d = event.data;
|
|
7710
|
+
if ("messages" in d) {
|
|
7711
|
+
const rest = {};
|
|
7712
|
+
for (const k of Object.keys(d)) {
|
|
7713
|
+
if (k !== "messages") rest[k] = d[k];
|
|
7714
|
+
}
|
|
7715
|
+
capturedTraces.push({ ...event, data: rest });
|
|
7716
|
+
return;
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
capturedTraces.push(event);
|
|
6103
7720
|
}
|
|
6104
7721
|
};
|
|
6105
7722
|
this.setMaxListeners(this.getMaxListeners() + 1);
|
|
@@ -6109,6 +7726,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6109
7726
|
return {
|
|
6110
7727
|
result,
|
|
6111
7728
|
cost: scope.totalCost,
|
|
7729
|
+
...capturedTraces ? { traces: capturedTraces } : {},
|
|
6112
7730
|
metadata: {
|
|
6113
7731
|
models: [...modelCalls.keys()],
|
|
6114
7732
|
modelCallCounts: modelCalls.size > 0 ? Object.fromEntries(modelCalls) : void 0,
|
|
@@ -6118,6 +7736,16 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6118
7736
|
workflowCallCounts: workflowCalls.size > 0 ? Object.fromEntries(workflowCalls) : void 0
|
|
6119
7737
|
}
|
|
6120
7738
|
};
|
|
7739
|
+
} catch (err) {
|
|
7740
|
+
if (capturedTraces && typeof err === "object" && err !== null) {
|
|
7741
|
+
Object.defineProperty(err, "axlCapturedTraces", {
|
|
7742
|
+
value: capturedTraces,
|
|
7743
|
+
enumerable: false,
|
|
7744
|
+
writable: true,
|
|
7745
|
+
configurable: true
|
|
7746
|
+
});
|
|
7747
|
+
}
|
|
7748
|
+
throw err;
|
|
6121
7749
|
} finally {
|
|
6122
7750
|
this.off("trace", listener);
|
|
6123
7751
|
this.setMaxListeners(this.getMaxListeners() - 1);
|
|
@@ -6142,7 +7770,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6142
7770
|
* The emit('trace', event) call happens before this method is called, so
|
|
6143
7771
|
* programmatic subscribers always receive events regardless of trace config.
|
|
6144
7772
|
*/
|
|
6145
|
-
|
|
7773
|
+
outputAxlEvent(event) {
|
|
6146
7774
|
const traceConfig = this.config.trace;
|
|
6147
7775
|
if (!traceConfig?.enabled) return;
|
|
6148
7776
|
const level = traceConfig.level ?? "steps";
|
|
@@ -6166,18 +7794,20 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6166
7794
|
}
|
|
6167
7795
|
return;
|
|
6168
7796
|
}
|
|
6169
|
-
this.
|
|
7797
|
+
this.logAxlEvent(event);
|
|
6170
7798
|
}
|
|
6171
|
-
|
|
7799
|
+
logAxlEvent(event) {
|
|
6172
7800
|
const level = this.config.trace?.level ?? "steps";
|
|
6173
7801
|
const workflowPrefix = event.workflow ? `workflow:${event.workflow} | ` : "";
|
|
6174
7802
|
const parts = [`[axl] execution:${event.executionId}`];
|
|
6175
|
-
const data = event.data;
|
|
6176
7803
|
if (event.type === "workflow_start") {
|
|
6177
|
-
parts.push(`${workflowPrefix}
|
|
7804
|
+
parts.push(`${workflowPrefix}started`);
|
|
6178
7805
|
} else if (event.type === "workflow_end") {
|
|
6179
|
-
|
|
6180
|
-
|
|
7806
|
+
const d = event.data;
|
|
7807
|
+
const status = d?.aborted ? "aborted" : d?.status ?? "completed";
|
|
7808
|
+
parts.push(`${workflowPrefix}${status}`);
|
|
7809
|
+
if (event.duration != null) parts.push(`${(event.duration / 1e3).toFixed(1)}s`);
|
|
7810
|
+
} else if (event.type === "agent_call_end") {
|
|
6181
7811
|
parts.push(`${workflowPrefix}step:${event.step} agent_call`);
|
|
6182
7812
|
if (event.agent) parts.push(`agent:${event.agent}`);
|
|
6183
7813
|
if (event.promptVersion) parts.push(`version:${event.promptVersion}`);
|
|
@@ -6187,7 +7817,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6187
7817
|
if (level === "full" && event.data) {
|
|
6188
7818
|
parts.push(`data:${JSON.stringify(event.data)}`);
|
|
6189
7819
|
}
|
|
6190
|
-
} else if (event.type === "
|
|
7820
|
+
} else if (event.type === "tool_call_end") {
|
|
6191
7821
|
parts.push(`${workflowPrefix}step:${event.step} tool_call`);
|
|
6192
7822
|
if (event.tool) parts.push(`tool:${event.tool}`);
|
|
6193
7823
|
if (event.duration) parts.push(`${event.duration}ms`);
|
|
@@ -6204,8 +7834,9 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6204
7834
|
parts.push(`${workflowPrefix}log: ${JSON.stringify(event.data)}`);
|
|
6205
7835
|
} else {
|
|
6206
7836
|
parts.push(`${workflowPrefix}${event.type}`);
|
|
6207
|
-
|
|
6208
|
-
|
|
7837
|
+
const data = event.data;
|
|
7838
|
+
if (level === "full" && data !== void 0) {
|
|
7839
|
+
parts.push(`data:${JSON.stringify(data)}`);
|
|
6209
7840
|
}
|
|
6210
7841
|
}
|
|
6211
7842
|
console.log(parts.join(" | "));
|
|
@@ -6225,6 +7856,35 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
6225
7856
|
}).catch(() => {
|
|
6226
7857
|
});
|
|
6227
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
|
+
}
|
|
6228
7888
|
};
|
|
6229
7889
|
|
|
6230
7890
|
// src/state/redis.ts
|
|
@@ -6287,27 +7947,13 @@ var RedisStore = class _RedisStore {
|
|
|
6287
7947
|
return "axl:eval-history-ids";
|
|
6288
7948
|
}
|
|
6289
7949
|
// ── Checkpoints ──────────────────────────────────────────────────────
|
|
6290
|
-
async saveCheckpoint(executionId,
|
|
6291
|
-
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));
|
|
6292
7952
|
}
|
|
6293
|
-
async getCheckpoint(executionId,
|
|
6294
|
-
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);
|
|
6295
7955
|
return raw != null ? JSON.parse(raw) : null;
|
|
6296
7956
|
}
|
|
6297
|
-
async getLatestCheckpoint(executionId) {
|
|
6298
|
-
const all = await this.client.hGetAll(this.checkpointKey(executionId));
|
|
6299
|
-
if (!all || Object.keys(all).length === 0) return null;
|
|
6300
|
-
let maxStep = -1;
|
|
6301
|
-
let maxData = null;
|
|
6302
|
-
for (const [stepStr, raw] of Object.entries(all)) {
|
|
6303
|
-
const step = Number(stepStr);
|
|
6304
|
-
if (step > maxStep) {
|
|
6305
|
-
maxStep = step;
|
|
6306
|
-
maxData = JSON.parse(raw);
|
|
6307
|
-
}
|
|
6308
|
-
}
|
|
6309
|
-
return { step: maxStep, data: maxData };
|
|
6310
|
-
}
|
|
6311
7957
|
// ── Sessions ─────────────────────────────────────────────────────────
|
|
6312
7958
|
async saveSession(sessionId, history) {
|
|
6313
7959
|
await this.client.set(this.sessionKey(sessionId), JSON.stringify(history));
|
|
@@ -6412,6 +8058,16 @@ var RedisStore = class _RedisStore {
|
|
|
6412
8058
|
};
|
|
6413
8059
|
|
|
6414
8060
|
// src/memory/embedder-openai.ts
|
|
8061
|
+
var EMBEDDING_PRICE_PER_1M_TOKENS = {
|
|
8062
|
+
"text-embedding-3-small": 0.02,
|
|
8063
|
+
"text-embedding-3-large": 0.13,
|
|
8064
|
+
"text-embedding-ada-002": 0.1
|
|
8065
|
+
};
|
|
8066
|
+
function computeCost(model, tokens) {
|
|
8067
|
+
const pricePerMillion = EMBEDDING_PRICE_PER_1M_TOKENS[model];
|
|
8068
|
+
if (pricePerMillion == null) return void 0;
|
|
8069
|
+
return tokens / 1e6 * pricePerMillion;
|
|
8070
|
+
}
|
|
6415
8071
|
var OpenAIEmbedder = class {
|
|
6416
8072
|
apiKey;
|
|
6417
8073
|
model;
|
|
@@ -6426,8 +8082,8 @@ var OpenAIEmbedder = class {
|
|
|
6426
8082
|
this.baseUrl = options.baseUrl ?? "https://api.openai.com";
|
|
6427
8083
|
this.dimensions = options.dimensions ?? 1536;
|
|
6428
8084
|
}
|
|
6429
|
-
async embed(texts) {
|
|
6430
|
-
const response = await
|
|
8085
|
+
async embed(texts, signal) {
|
|
8086
|
+
const response = await fetchWithRetry(`${this.baseUrl}/v1/embeddings`, {
|
|
6431
8087
|
method: "POST",
|
|
6432
8088
|
headers: {
|
|
6433
8089
|
"Content-Type": "application/json",
|
|
@@ -6437,14 +8093,28 @@ var OpenAIEmbedder = class {
|
|
|
6437
8093
|
model: this.model,
|
|
6438
8094
|
input: texts,
|
|
6439
8095
|
dimensions: this.dimensions
|
|
6440
|
-
})
|
|
8096
|
+
}),
|
|
8097
|
+
signal
|
|
6441
8098
|
});
|
|
6442
8099
|
if (!response.ok) {
|
|
6443
8100
|
const body = await response.text();
|
|
6444
8101
|
throw new Error(`OpenAI embeddings API error (${response.status}): ${body}`);
|
|
6445
8102
|
}
|
|
6446
8103
|
const json = await response.json();
|
|
6447
|
-
|
|
8104
|
+
const vectors = json.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
8105
|
+
const tokens = json.usage?.prompt_tokens ?? json.usage?.total_tokens;
|
|
8106
|
+
const cost = tokens != null ? computeCost(this.model, tokens) : void 0;
|
|
8107
|
+
if (tokens == null && cost == null) {
|
|
8108
|
+
return { vectors };
|
|
8109
|
+
}
|
|
8110
|
+
return {
|
|
8111
|
+
vectors,
|
|
8112
|
+
usage: {
|
|
8113
|
+
...tokens != null ? { tokens } : {},
|
|
8114
|
+
...cost != null ? { cost } : {},
|
|
8115
|
+
model: this.model
|
|
8116
|
+
}
|
|
8117
|
+
};
|
|
6448
8118
|
}
|
|
6449
8119
|
};
|
|
6450
8120
|
|
|
@@ -6568,11 +8238,13 @@ function cosineSimilarity2(a, b) {
|
|
|
6568
8238
|
return denom === 0 ? 0 : dot / denom;
|
|
6569
8239
|
}
|
|
6570
8240
|
export {
|
|
8241
|
+
AXL_EVENT_TYPES,
|
|
6571
8242
|
AnthropicProvider,
|
|
6572
8243
|
AxlError,
|
|
6573
8244
|
AxlRuntime,
|
|
6574
8245
|
AxlStream,
|
|
6575
8246
|
BudgetExceededError,
|
|
8247
|
+
COST_BEARING_LEAF_TYPES,
|
|
6576
8248
|
GeminiProvider,
|
|
6577
8249
|
GuardrailError,
|
|
6578
8250
|
InMemoryVectorStore,
|
|
@@ -6587,6 +8259,8 @@ export {
|
|
|
6587
8259
|
OpenAIResponsesProvider,
|
|
6588
8260
|
ProviderRegistry,
|
|
6589
8261
|
QuorumNotMet,
|
|
8262
|
+
REDACTED,
|
|
8263
|
+
REDACTION_RULES,
|
|
6590
8264
|
RedisStore,
|
|
6591
8265
|
SQLiteStore,
|
|
6592
8266
|
Session,
|
|
@@ -6599,7 +8273,12 @@ export {
|
|
|
6599
8273
|
agent,
|
|
6600
8274
|
createSpanManager,
|
|
6601
8275
|
defineConfig,
|
|
8276
|
+
eventCostContribution,
|
|
6602
8277
|
extractJson,
|
|
8278
|
+
isCostBearingLeaf,
|
|
8279
|
+
isRootLevel,
|
|
8280
|
+
parsePartialJson,
|
|
8281
|
+
redactEvent,
|
|
6603
8282
|
resolveThinkingOptions,
|
|
6604
8283
|
tool,
|
|
6605
8284
|
workflow,
|