@axlsdk/axl 0.22.3 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/index.cjs +723 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +254 -29
- package/dist/index.d.ts +254 -29
- package/dist/index.js +722 -166
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -167,6 +167,7 @@ __export(index_exports, {
|
|
|
167
167
|
SQLiteStore: () => SQLiteStore,
|
|
168
168
|
Session: () => Session,
|
|
169
169
|
SqliteVectorStore: () => SqliteVectorStore,
|
|
170
|
+
StallTimeoutError: () => StallTimeoutError,
|
|
170
171
|
ThinkTagScanner: () => ThinkTagScanner,
|
|
171
172
|
TimeoutError: () => TimeoutError,
|
|
172
173
|
ToolFailure: () => ToolFailure,
|
|
@@ -392,9 +393,22 @@ var NoConsensus = class extends AxlError {
|
|
|
392
393
|
}
|
|
393
394
|
};
|
|
394
395
|
var TimeoutError = class extends AxlError {
|
|
395
|
-
|
|
396
|
-
|
|
396
|
+
/** Latency attribution for the timed-out operation, when measurable. */
|
|
397
|
+
breakdown;
|
|
398
|
+
constructor(operation, timeoutMs, breakdown, agent2) {
|
|
399
|
+
super(
|
|
400
|
+
"TIMEOUT",
|
|
401
|
+
`${operation} exceeded timeout of ${timeoutMs}ms` + (breakdown ? ` (elapsed ${breakdown.elapsedMs}ms: queued ${breakdown.queuedMs}ms, retries ${breakdown.retryMs}ms, wire ${breakdown.wireMs}ms, other ${breakdown.otherMs}ms)` : "") + (agent2 ? ` for agent '${agent2}'` : "")
|
|
402
|
+
);
|
|
397
403
|
this.name = "TimeoutError";
|
|
404
|
+
if (breakdown) this.breakdown = breakdown;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
var StallTimeoutError = class extends TimeoutError {
|
|
408
|
+
constructor(operation, timeoutMs, agent2) {
|
|
409
|
+
super(operation, timeoutMs, void 0, agent2);
|
|
410
|
+
this.name = "StallTimeoutError";
|
|
411
|
+
this.message = `${operation} provider request stalled for ${timeoutMs}ms` + (agent2 ? ` for agent '${agent2}'` : "");
|
|
398
412
|
}
|
|
399
413
|
};
|
|
400
414
|
function formatBudgetCost(cost) {
|
|
@@ -941,6 +955,7 @@ var ProviderError = class extends AxlError {
|
|
|
941
955
|
this.retryAfterMs = args.retryAfterMs;
|
|
942
956
|
this.requestId = args.requestId;
|
|
943
957
|
this.body = args.body;
|
|
958
|
+
if (args.timing) this.timing = args.timing;
|
|
944
959
|
}
|
|
945
960
|
};
|
|
946
961
|
function isRetryableStatus(status) {
|
|
@@ -992,7 +1007,8 @@ function buildProviderError(args) {
|
|
|
992
1007
|
message: args.message,
|
|
993
1008
|
retryAfterMs: args.headers ? parseRetryAfter(args.headers) : void 0,
|
|
994
1009
|
requestId: args.headers ? extractRequestId(args.headers) : void 0,
|
|
995
|
-
body: args.body
|
|
1010
|
+
body: args.body,
|
|
1011
|
+
timing: args.timing
|
|
996
1012
|
});
|
|
997
1013
|
}
|
|
998
1014
|
|
|
@@ -1029,14 +1045,33 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
1029
1045
|
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
1030
1046
|
const governor = opts?.governor;
|
|
1031
1047
|
const provider = opts?.provider ?? "unknown";
|
|
1048
|
+
const observer = opts?.timing;
|
|
1032
1049
|
let acquired = false;
|
|
1050
|
+
let queuedMs = 0;
|
|
1033
1051
|
if (governor) {
|
|
1052
|
+
const acquireStart = Date.now();
|
|
1034
1053
|
await governor.acquire(init?.signal ?? void 0);
|
|
1035
1054
|
acquired = true;
|
|
1036
|
-
|
|
1055
|
+
queuedMs = Date.now() - acquireStart;
|
|
1056
|
+
}
|
|
1057
|
+
let firstDispatchedAt = 0;
|
|
1058
|
+
let dispatchedAt = 0;
|
|
1059
|
+
let headersAt = 0;
|
|
1060
|
+
const reportComplete = (attempts) => {
|
|
1061
|
+
observer?.onComplete?.({
|
|
1062
|
+
queuedMs,
|
|
1063
|
+
attempts,
|
|
1064
|
+
retryMs: dispatchedAt - firstDispatchedAt,
|
|
1065
|
+
dispatchedAt,
|
|
1066
|
+
headersAt
|
|
1067
|
+
});
|
|
1068
|
+
};
|
|
1037
1069
|
try {
|
|
1038
1070
|
for (let attempt = 0; ; attempt++) {
|
|
1039
1071
|
let res;
|
|
1072
|
+
dispatchedAt = Date.now();
|
|
1073
|
+
if (attempt === 0) firstDispatchedAt = dispatchedAt;
|
|
1074
|
+
observer?.onDispatch?.(attempt + 1, dispatchedAt);
|
|
1040
1075
|
try {
|
|
1041
1076
|
res = await fetch(input, { ...init, redirect: "manual" });
|
|
1042
1077
|
} catch (err) {
|
|
@@ -1048,18 +1083,23 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
1048
1083
|
message: err instanceof Error ? err.message : String(err)
|
|
1049
1084
|
});
|
|
1050
1085
|
}
|
|
1086
|
+
observer?.onRetry?.(attempt + 1, Date.now());
|
|
1051
1087
|
await sleep2(jitter(BASE_DELAY_MS * 2 ** attempt), init?.signal ?? void 0);
|
|
1052
1088
|
continue;
|
|
1053
1089
|
}
|
|
1090
|
+
headersAt = Date.now();
|
|
1054
1091
|
governor?.observe(res);
|
|
1055
1092
|
if (res.ok || !RETRYABLE_STATUS_CODES.has(res.status) || attempt >= maxRetries) {
|
|
1093
|
+
reportComplete(attempt + 1);
|
|
1056
1094
|
return res;
|
|
1057
1095
|
}
|
|
1058
1096
|
if (init?.signal?.aborted) {
|
|
1097
|
+
reportComplete(attempt + 1);
|
|
1059
1098
|
return res;
|
|
1060
1099
|
}
|
|
1061
1100
|
const retryAfterMs = parseRetryAfter(res.headers);
|
|
1062
1101
|
const baseDelay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, MAX_BACKOFF_MS) : BASE_DELAY_MS * 2 ** attempt;
|
|
1102
|
+
observer?.onRetry?.(attempt + 1, Date.now());
|
|
1063
1103
|
await sleep2(jitter(baseDelay), init?.signal ?? void 0);
|
|
1064
1104
|
}
|
|
1065
1105
|
} finally {
|
|
@@ -1067,10 +1107,110 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
1067
1107
|
}
|
|
1068
1108
|
}
|
|
1069
1109
|
|
|
1070
|
-
// src/providers/
|
|
1071
|
-
|
|
1072
|
-
|
|
1110
|
+
// src/providers/call-timing.ts
|
|
1111
|
+
var CallTimingRecorder = class {
|
|
1112
|
+
constructor(requestLifecycle) {
|
|
1113
|
+
this.requestLifecycle = requestLifecycle;
|
|
1114
|
+
}
|
|
1115
|
+
fetchTiming;
|
|
1116
|
+
firstTokenAt;
|
|
1117
|
+
/** Consumer suspension after pre-content chunks (for example, tool deltas). */
|
|
1118
|
+
preFirstTokenConsumerMs = 0;
|
|
1119
|
+
/** Cumulative time awaiting `reader.read()`, i.e. transport, not consumer. */
|
|
1120
|
+
readWaitMs = 0;
|
|
1121
|
+
/** Pass as `timing` in the `fetchWithRetry` options object. */
|
|
1122
|
+
observer = {
|
|
1123
|
+
onDispatch: () => {
|
|
1124
|
+
this.requestLifecycle?.onDispatch?.();
|
|
1125
|
+
},
|
|
1126
|
+
onRetry: () => {
|
|
1127
|
+
this.requestLifecycle?.onRetry?.();
|
|
1128
|
+
},
|
|
1129
|
+
onComplete: (timing) => {
|
|
1130
|
+
this.fetchTiming = timing;
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
/**
|
|
1134
|
+
* Await one body read, charging only the wait itself to `wireMs`. Time the
|
|
1135
|
+
* consumer spends between reads is excluded because it elapses outside this
|
|
1136
|
+
* call.
|
|
1137
|
+
*/
|
|
1138
|
+
async read(reader) {
|
|
1139
|
+
const start = Date.now();
|
|
1140
|
+
try {
|
|
1141
|
+
return await reader.read();
|
|
1142
|
+
} finally {
|
|
1143
|
+
this.readWaitMs += Date.now() - start;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
/** Stamp the first content delta. Idempotent — only the first call counts. */
|
|
1147
|
+
markFirstToken() {
|
|
1148
|
+
this.firstTokenAt ??= Date.now();
|
|
1149
|
+
}
|
|
1150
|
+
/** Exclude backpressure after a pre-content yield from first-content latency. */
|
|
1151
|
+
recordPreFirstTokenConsumerPause(startedAt) {
|
|
1152
|
+
if (this.firstTokenAt === void 0) {
|
|
1153
|
+
this.preFirstTokenConsumerMs += Math.max(0, Date.now() - startedAt);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Timing for a non-streaming call. Call once the response body is parsed.
|
|
1158
|
+
*
|
|
1159
|
+
* Also the right reading at an adapter's `!res.ok` throw site on BOTH
|
|
1160
|
+
* transports: headers have arrived, no body will be streamed, so
|
|
1161
|
+
* dispatch → now is the whole of what the provider cost us.
|
|
1162
|
+
*/
|
|
1163
|
+
chatTiming() {
|
|
1164
|
+
const t = this.fetchTiming;
|
|
1165
|
+
if (!t) return void 0;
|
|
1166
|
+
return {
|
|
1167
|
+
queuedMs: t.queuedMs,
|
|
1168
|
+
attempts: t.attempts,
|
|
1169
|
+
retryMs: t.retryMs,
|
|
1170
|
+
ttfbMs: Math.max(0, t.headersAt - t.dispatchedAt),
|
|
1171
|
+
wireMs: Math.max(0, Date.now() - t.dispatchedAt)
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
/** Timing for a streamed call. Call when building the terminal `done` chunk. */
|
|
1175
|
+
streamTiming() {
|
|
1176
|
+
const t = this.fetchTiming;
|
|
1177
|
+
if (!t) return void 0;
|
|
1178
|
+
const ttfbMs = Math.max(0, t.headersAt - t.dispatchedAt);
|
|
1179
|
+
const firstTokenMs = this.firstTokenAt !== void 0 ? Math.max(0, this.firstTokenAt - t.dispatchedAt - this.preFirstTokenConsumerMs) : void 0;
|
|
1180
|
+
return {
|
|
1181
|
+
queuedMs: t.queuedMs,
|
|
1182
|
+
attempts: t.attempts,
|
|
1183
|
+
retryMs: t.retryMs,
|
|
1184
|
+
ttfbMs,
|
|
1185
|
+
...firstTokenMs !== void 0 ? { firstTokenMs } : {},
|
|
1186
|
+
// Parsing and generator scheduling can happen after the first read has
|
|
1187
|
+
// settled. Include that pre-first-token gap so the public relationship
|
|
1188
|
+
// stays intuitive, without charging pauses after any chunk is yielded.
|
|
1189
|
+
wireMs: Math.max(ttfbMs + this.readWaitMs, firstTokenMs ?? 0)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
async function* withCallTiming(recorder, source) {
|
|
1194
|
+
for await (const chunk of source) {
|
|
1195
|
+
if (chunk.type === "text_delta" || chunk.type === "thinking_delta") {
|
|
1196
|
+
recorder.markFirstToken();
|
|
1197
|
+
yield chunk;
|
|
1198
|
+
} else if (chunk.type === "done") {
|
|
1199
|
+
const timing = recorder.streamTiming();
|
|
1200
|
+
yield timing ? { ...chunk, timing } : chunk;
|
|
1201
|
+
} else {
|
|
1202
|
+
const yieldedAt = Date.now();
|
|
1203
|
+
yield chunk;
|
|
1204
|
+
recorder.recordPreFirstTokenConsumerPause(yieldedAt);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1073
1207
|
}
|
|
1208
|
+
function withChatTiming(recorder, response) {
|
|
1209
|
+
const timing = recorder.chatTiming();
|
|
1210
|
+
return timing ? { ...response, timing } : response;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/providers/rate-limiter.ts
|
|
1074
1214
|
var RateLimiter = class {
|
|
1075
1215
|
maxConcurrent;
|
|
1076
1216
|
minIntervalMs;
|
|
@@ -1119,11 +1259,11 @@ var RateLimiter = class {
|
|
|
1119
1259
|
* With `acquireTimeoutMs`, a waiter that sits in the queue too long rejects.
|
|
1120
1260
|
*/
|
|
1121
1261
|
acquire(signal) {
|
|
1122
|
-
if (signal?.aborted) return Promise.reject(
|
|
1262
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
1123
1263
|
return new Promise((resolve2, reject) => {
|
|
1124
1264
|
const waiter = { resolve: resolve2, reject, signal };
|
|
1125
1265
|
if (signal) {
|
|
1126
|
-
waiter.onAbort = () => this.settleWaiter(waiter,
|
|
1266
|
+
waiter.onAbort = () => this.settleWaiter(waiter, signal.reason);
|
|
1127
1267
|
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
1128
1268
|
}
|
|
1129
1269
|
if (this.acquireTimeoutMs != null) {
|
|
@@ -1423,6 +1563,7 @@ function normalizeMessageContent(content) {
|
|
|
1423
1563
|
}
|
|
1424
1564
|
var OpenAICompatibleProvider = class {
|
|
1425
1565
|
name;
|
|
1566
|
+
reportsRequestLifecycle = true;
|
|
1426
1567
|
inputCapabilities(model) {
|
|
1427
1568
|
return this.name === "openrouter" && model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64"] } } : {};
|
|
1428
1569
|
}
|
|
@@ -1519,6 +1660,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1519
1660
|
async chat(messages, options) {
|
|
1520
1661
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
1521
1662
|
const body = this.buildRequestBody(messages, options, false);
|
|
1663
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
1522
1664
|
const res = await fetchWithRetry(
|
|
1523
1665
|
`${this.baseUrl}/chat/completions`,
|
|
1524
1666
|
{
|
|
@@ -1527,7 +1669,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1527
1669
|
body: JSON.stringify(body),
|
|
1528
1670
|
signal: options.signal
|
|
1529
1671
|
},
|
|
1530
|
-
{ governor: this.governor, provider: this.name }
|
|
1672
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
1531
1673
|
);
|
|
1532
1674
|
if (!res.ok) {
|
|
1533
1675
|
const errorBody = await res.text();
|
|
@@ -1536,15 +1678,20 @@ var OpenAICompatibleProvider = class {
|
|
|
1536
1678
|
status: res.status,
|
|
1537
1679
|
headers: res.headers,
|
|
1538
1680
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
1539
|
-
body: errorBody
|
|
1681
|
+
body: errorBody,
|
|
1682
|
+
timing: recorder.chatTiming()
|
|
1540
1683
|
});
|
|
1541
1684
|
}
|
|
1542
1685
|
const json = await res.json();
|
|
1543
|
-
return
|
|
1686
|
+
return withChatTiming(
|
|
1687
|
+
recorder,
|
|
1688
|
+
this.parseResponse(json, this.requestModel(body, options.model), body)
|
|
1689
|
+
);
|
|
1544
1690
|
}
|
|
1545
1691
|
async *stream(messages, options) {
|
|
1546
1692
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
1547
1693
|
const body = this.buildRequestBody(messages, options, true);
|
|
1694
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
1548
1695
|
const res = await fetchWithRetry(
|
|
1549
1696
|
`${this.baseUrl}/chat/completions`,
|
|
1550
1697
|
{
|
|
@@ -1553,7 +1700,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1553
1700
|
body: JSON.stringify(body),
|
|
1554
1701
|
signal: options.signal
|
|
1555
1702
|
},
|
|
1556
|
-
{ governor: this.governor, provider: this.name }
|
|
1703
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
1557
1704
|
);
|
|
1558
1705
|
if (!res.ok) {
|
|
1559
1706
|
const errorBody = await res.text();
|
|
@@ -1562,13 +1709,17 @@ var OpenAICompatibleProvider = class {
|
|
|
1562
1709
|
status: res.status,
|
|
1563
1710
|
headers: res.headers,
|
|
1564
1711
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
1565
|
-
body: errorBody
|
|
1712
|
+
body: errorBody,
|
|
1713
|
+
timing: recorder.chatTiming()
|
|
1566
1714
|
});
|
|
1567
1715
|
}
|
|
1568
1716
|
if (!res.body) {
|
|
1569
1717
|
throw new Error(`${this.profile.label ?? this.profile.name} stream response has no body`);
|
|
1570
1718
|
}
|
|
1571
|
-
yield*
|
|
1719
|
+
yield* withCallTiming(
|
|
1720
|
+
recorder,
|
|
1721
|
+
this.parseSSEStream(res.body, this.requestModel(body, options.model), body, recorder)
|
|
1722
|
+
);
|
|
1572
1723
|
}
|
|
1573
1724
|
// ---------------------------------------------------------------------------
|
|
1574
1725
|
// Request building
|
|
@@ -1795,7 +1946,21 @@ var OpenAICompatibleProvider = class {
|
|
|
1795
1946
|
// ---------------------------------------------------------------------------
|
|
1796
1947
|
// SSE streaming
|
|
1797
1948
|
// ---------------------------------------------------------------------------
|
|
1798
|
-
|
|
1949
|
+
/**
|
|
1950
|
+
* PRIVATE, and deliberately not an extension point. It was `protected`, but
|
|
1951
|
+
* nothing in or out of this repo overrides it, and `protected` could not
|
|
1952
|
+
* carry the guarantee it appeared to: TypeScript checks an override for
|
|
1953
|
+
* assignability, and a function type with fewer parameters is assignable to
|
|
1954
|
+
* one with more — so an override that simply dropped the recorder still
|
|
1955
|
+
* compiled and then reported `wireMs === ttfbMs` for the whole subclass. Its
|
|
1956
|
+
* type is not exported from the barrel either, so a subclass author could not
|
|
1957
|
+
* name what they were required to accept.
|
|
1958
|
+
*
|
|
1959
|
+
* A provider with a different wire format implements `Provider` (see
|
|
1960
|
+
* docs/providers.md); an OpenAI-compatible one adds a `ProviderProfile`.
|
|
1961
|
+
* Neither needs to reach in here.
|
|
1962
|
+
*/
|
|
1963
|
+
async *parseSSEStream(body, model, request, timing) {
|
|
1799
1964
|
const reader = body.getReader();
|
|
1800
1965
|
const decoder = new TextDecoder();
|
|
1801
1966
|
let buffer = "";
|
|
@@ -1855,7 +2020,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1855
2020
|
};
|
|
1856
2021
|
try {
|
|
1857
2022
|
for (; ; ) {
|
|
1858
|
-
const { done, value } = await
|
|
2023
|
+
const { done, value } = await timing.read(reader);
|
|
1859
2024
|
if (done) break;
|
|
1860
2025
|
buffer += decoder.decode(value, { stream: true });
|
|
1861
2026
|
const lines = buffer.split("\n");
|
|
@@ -1887,7 +2052,8 @@ var OpenAICompatibleProvider = class {
|
|
|
1887
2052
|
status: 0,
|
|
1888
2053
|
retryable: false,
|
|
1889
2054
|
message,
|
|
1890
|
-
body: JSON.stringify(parsed.error)
|
|
2055
|
+
body: JSON.stringify(parsed.error),
|
|
2056
|
+
timing: timing.streamTiming()
|
|
1891
2057
|
});
|
|
1892
2058
|
}
|
|
1893
2059
|
if (parsed.model !== void 0 || parsed.service_tier !== void 0 || parsed.serviceTier !== void 0) {
|
|
@@ -1959,7 +2125,8 @@ var OpenAICompatibleProvider = class {
|
|
|
1959
2125
|
provider: this.name,
|
|
1960
2126
|
status: 0,
|
|
1961
2127
|
retryable: true,
|
|
1962
|
-
message: `${this.profile.label ?? this.profile.name} stream ended before [DONE]
|
|
2128
|
+
message: `${this.profile.label ?? this.profile.name} stream ended before [DONE]`,
|
|
2129
|
+
timing: timing.streamTiming()
|
|
1963
2130
|
});
|
|
1964
2131
|
} finally {
|
|
1965
2132
|
reader.releaseLock();
|
|
@@ -1998,12 +2165,12 @@ var DIRECT_OPENAI_CATALOG = [
|
|
|
1998
2165
|
{
|
|
1999
2166
|
aliases: ["gpt-5.6", "gpt-5.6-sol"],
|
|
2000
2167
|
snapshotBase: "gpt-5.6-sol",
|
|
2001
|
-
short: { input:
|
|
2168
|
+
short: { input: 4 / M, cachedInput: 0.4 / M, cacheWrite: 5 / M, output: 20 / M },
|
|
2002
2169
|
long: withLongContext({
|
|
2003
|
-
input:
|
|
2004
|
-
cachedInput: 0.
|
|
2005
|
-
cacheWrite:
|
|
2006
|
-
output:
|
|
2170
|
+
input: 4 / M,
|
|
2171
|
+
cachedInput: 0.4 / M,
|
|
2172
|
+
cacheWrite: 5 / M,
|
|
2173
|
+
output: 20 / M
|
|
2007
2174
|
}),
|
|
2008
2175
|
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
2009
2176
|
},
|
|
@@ -2432,6 +2599,7 @@ function responseImageParts(parts) {
|
|
|
2432
2599
|
}
|
|
2433
2600
|
var OpenAIResponsesProvider = class {
|
|
2434
2601
|
name = "openai-responses";
|
|
2602
|
+
reportsRequestLifecycle = true;
|
|
2435
2603
|
inputCapabilities(model) {
|
|
2436
2604
|
return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
2437
2605
|
}
|
|
@@ -2518,6 +2686,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2518
2686
|
async chat(messages, options) {
|
|
2519
2687
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
2520
2688
|
const body = this.buildRequestBody(messages, options, false);
|
|
2689
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
2521
2690
|
const res = await fetchWithRetry(
|
|
2522
2691
|
`${this.baseUrl}/responses`,
|
|
2523
2692
|
{
|
|
@@ -2526,7 +2695,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2526
2695
|
body: JSON.stringify(body),
|
|
2527
2696
|
signal: options.signal
|
|
2528
2697
|
},
|
|
2529
|
-
{ governor: this.governor, provider: this.name }
|
|
2698
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
2530
2699
|
);
|
|
2531
2700
|
if (!res.ok) {
|
|
2532
2701
|
const errorBody = await res.text();
|
|
@@ -2536,11 +2705,15 @@ var OpenAIResponsesProvider = class {
|
|
|
2536
2705
|
status: res.status,
|
|
2537
2706
|
headers: res.headers,
|
|
2538
2707
|
message,
|
|
2539
|
-
body: errorBody
|
|
2708
|
+
body: errorBody,
|
|
2709
|
+
timing: recorder.chatTiming()
|
|
2540
2710
|
});
|
|
2541
2711
|
}
|
|
2542
2712
|
const json = await res.json();
|
|
2543
|
-
return
|
|
2713
|
+
return withChatTiming(
|
|
2714
|
+
recorder,
|
|
2715
|
+
this.parseResponse(json, this.requestModel(body, options.model), body)
|
|
2716
|
+
);
|
|
2544
2717
|
}
|
|
2545
2718
|
// ---------------------------------------------------------------------------
|
|
2546
2719
|
// stream - SSE streaming
|
|
@@ -2548,6 +2721,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2548
2721
|
async *stream(messages, options) {
|
|
2549
2722
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
2550
2723
|
const body = this.buildRequestBody(messages, options, true);
|
|
2724
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
2551
2725
|
const res = await fetchWithRetry(
|
|
2552
2726
|
`${this.baseUrl}/responses`,
|
|
2553
2727
|
{
|
|
@@ -2556,7 +2730,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2556
2730
|
body: JSON.stringify(body),
|
|
2557
2731
|
signal: options.signal
|
|
2558
2732
|
},
|
|
2559
|
-
{ governor: this.governor, provider: this.name }
|
|
2733
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
2560
2734
|
);
|
|
2561
2735
|
if (!res.ok) {
|
|
2562
2736
|
const errorBody = await res.text();
|
|
@@ -2566,13 +2740,17 @@ var OpenAIResponsesProvider = class {
|
|
|
2566
2740
|
status: res.status,
|
|
2567
2741
|
headers: res.headers,
|
|
2568
2742
|
message,
|
|
2569
|
-
body: errorBody
|
|
2743
|
+
body: errorBody,
|
|
2744
|
+
timing: recorder.chatTiming()
|
|
2570
2745
|
});
|
|
2571
2746
|
}
|
|
2572
2747
|
if (!res.body) {
|
|
2573
2748
|
throw new Error("OpenAI Responses stream has no body");
|
|
2574
2749
|
}
|
|
2575
|
-
yield*
|
|
2750
|
+
yield* withCallTiming(
|
|
2751
|
+
recorder,
|
|
2752
|
+
this.parseSSEStream(res.body, this.requestModel(body, options.model), body, recorder)
|
|
2753
|
+
);
|
|
2576
2754
|
}
|
|
2577
2755
|
// ---------------------------------------------------------------------------
|
|
2578
2756
|
// Internal: build request body
|
|
@@ -2774,7 +2952,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2774
2952
|
// ---------------------------------------------------------------------------
|
|
2775
2953
|
// Internal: parse SSE stream
|
|
2776
2954
|
// ---------------------------------------------------------------------------
|
|
2777
|
-
async *parseSSEStream(body, model, request) {
|
|
2955
|
+
async *parseSSEStream(body, model, request, timing) {
|
|
2778
2956
|
const reader = body.getReader();
|
|
2779
2957
|
const decoder = new TextDecoder();
|
|
2780
2958
|
let buffer = "";
|
|
@@ -2782,7 +2960,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2782
2960
|
let eventType = "";
|
|
2783
2961
|
try {
|
|
2784
2962
|
while (true) {
|
|
2785
|
-
const { done, value } = await
|
|
2963
|
+
const { done, value } = await timing.read(reader);
|
|
2786
2964
|
if (done) break;
|
|
2787
2965
|
buffer += decoder.decode(value, { stream: true });
|
|
2788
2966
|
const lines = buffer.split("\n");
|
|
@@ -2802,7 +2980,14 @@ var OpenAIResponsesProvider = class {
|
|
|
2802
2980
|
} catch {
|
|
2803
2981
|
continue;
|
|
2804
2982
|
}
|
|
2805
|
-
const chunk = this.handleStreamEvent(
|
|
2983
|
+
const chunk = this.handleStreamEvent(
|
|
2984
|
+
eventType,
|
|
2985
|
+
data,
|
|
2986
|
+
model,
|
|
2987
|
+
callIdMap,
|
|
2988
|
+
request,
|
|
2989
|
+
timing
|
|
2990
|
+
);
|
|
2806
2991
|
if (chunk) {
|
|
2807
2992
|
yield chunk;
|
|
2808
2993
|
if (chunk.type === "done") return;
|
|
@@ -2816,7 +3001,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2816
3001
|
reader.releaseLock();
|
|
2817
3002
|
}
|
|
2818
3003
|
}
|
|
2819
|
-
handleStreamEvent(eventType, data, model, callIdMap, request) {
|
|
3004
|
+
handleStreamEvent(eventType, data, model, callIdMap, request, timing) {
|
|
2820
3005
|
switch (eventType) {
|
|
2821
3006
|
case "response.output_text.delta":
|
|
2822
3007
|
return { type: "text_delta", content: data.delta ?? "" };
|
|
@@ -2872,7 +3057,8 @@ var OpenAIResponsesProvider = class {
|
|
|
2872
3057
|
provider: this.name,
|
|
2873
3058
|
status: 0,
|
|
2874
3059
|
retryable: false,
|
|
2875
|
-
message: `OpenAI Responses API error: ${errorMsg}
|
|
3060
|
+
message: `OpenAI Responses API error: ${errorMsg}`,
|
|
3061
|
+
timing: timing.streamTiming()
|
|
2876
3062
|
});
|
|
2877
3063
|
}
|
|
2878
3064
|
default:
|
|
@@ -2940,6 +3126,12 @@ function hasAnthropicProviderFileImage(messages) {
|
|
|
2940
3126
|
);
|
|
2941
3127
|
}
|
|
2942
3128
|
var CLAUDE_CAPABILITIES = {
|
|
3129
|
+
"claude-fable-5-1": {
|
|
3130
|
+
thinking: "adaptive-always-on",
|
|
3131
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
3132
|
+
manualBudget: false,
|
|
3133
|
+
stripTemperature: true
|
|
3134
|
+
},
|
|
2943
3135
|
"claude-fable-5": {
|
|
2944
3136
|
thinking: "adaptive-always-on",
|
|
2945
3137
|
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
@@ -3030,10 +3222,18 @@ var PRE_INFERENCE_GEO_MODELS = /* @__PURE__ */ new Set([
|
|
|
3030
3222
|
function resolveClaudeCapability(model) {
|
|
3031
3223
|
return CLAUDE_CAPABILITIES[model] ?? (LEGACY_CLAUDE_MODELS.has(model) ? LEGACY_CLAUDE_CAPABILITY : void 0);
|
|
3032
3224
|
}
|
|
3225
|
+
var DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
|
|
3033
3226
|
var ANTHROPIC_RATES = {
|
|
3227
|
+
"claude-fable-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
|
|
3034
3228
|
"claude-fable-5": { input: 1e-5, output: 5e-5 },
|
|
3229
|
+
// Limited availability (Glasswing). Priced from the public pricing table; no
|
|
3230
|
+
// CLAUDE_CAPABILITIES entry, because their thinking semantics are not
|
|
3231
|
+
// documented per-model and an unverified capability would send the wrong
|
|
3232
|
+
// request shape. Unknown IDs pass through unmodified, which is the safe side.
|
|
3233
|
+
"claude-mythos-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
|
|
3234
|
+
"claude-mythos-5": { input: 1e-5, output: 5e-5 },
|
|
3035
3235
|
"claude-opus-5": { input: 5e-6, output: 25e-6 },
|
|
3036
|
-
"claude-sonnet-5": { input:
|
|
3236
|
+
"claude-sonnet-5": { input: 2e-6, output: 1e-5 },
|
|
3037
3237
|
"claude-opus-4-8": { input: 5e-6, output: 25e-6 },
|
|
3038
3238
|
"claude-opus-4-7": { input: 5e-6, output: 25e-6 },
|
|
3039
3239
|
"claude-opus-4-6": { input: 5e-6, output: 25e-6 },
|
|
@@ -3072,7 +3272,7 @@ function anthropicStreamErrorStatus(type) {
|
|
|
3072
3272
|
return 0;
|
|
3073
3273
|
}
|
|
3074
3274
|
}
|
|
3075
|
-
function estimateAnthropicCost(model, usage
|
|
3275
|
+
function estimateAnthropicCost(model, usage) {
|
|
3076
3276
|
if (!model || !isValidTokenCount(usage.inputTokens) || !isValidTokenCount(usage.outputTokens)) {
|
|
3077
3277
|
return void 0;
|
|
3078
3278
|
}
|
|
@@ -3091,8 +3291,9 @@ function estimateAnthropicCost(model, usage, now = /* @__PURE__ */ new Date()) {
|
|
|
3091
3291
|
if (usage.aggregateCacheWriteTokens !== void 0 && (!isValidTokenCount(usage.aggregateCacheWriteTokens) || usage.aggregateCacheWriteTokens !== cacheWriteTotal)) {
|
|
3092
3292
|
return void 0;
|
|
3093
3293
|
}
|
|
3094
|
-
const { input, output } = rate
|
|
3095
|
-
|
|
3294
|
+
const { input, output } = rate;
|
|
3295
|
+
const cacheReadRate = input * (rate.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER);
|
|
3296
|
+
return usage.inputTokens * input + cacheRead * cacheReadRate + cacheWrite5m * input * 1.25 + cacheWrite1h * input * 2 + usage.outputTokens * output;
|
|
3096
3297
|
}
|
|
3097
3298
|
var THINKING_BUDGETS = {
|
|
3098
3299
|
low: 1024,
|
|
@@ -3324,6 +3525,7 @@ function mergeAnthropicUsage(initial, update) {
|
|
|
3324
3525
|
}
|
|
3325
3526
|
var AnthropicProvider = class {
|
|
3326
3527
|
name = "anthropic";
|
|
3528
|
+
reportsRequestLifecycle = true;
|
|
3327
3529
|
/** Report a clamped `effort`. Anthropic clamps to each model's supported
|
|
3328
3530
|
* `output_config.effort` levels, and models that always think fall back to
|
|
3329
3531
|
* adaptive `'low'` when asked to disable thinking. Only reported when the
|
|
@@ -3333,6 +3535,14 @@ var AnthropicProvider = class {
|
|
|
3333
3535
|
const resolved = resolveThinkingOptions(options);
|
|
3334
3536
|
if (resolved.effort === void 0 || resolved.hasBudgetOverride) return void 0;
|
|
3335
3537
|
const model = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
3538
|
+
if (ANTHROPIC_RATES[model] && !resolveClaudeCapability(model)) {
|
|
3539
|
+
return {
|
|
3540
|
+
requested: resolved.effort,
|
|
3541
|
+
effective: "unset",
|
|
3542
|
+
clamped: true,
|
|
3543
|
+
cause: `Axl has no capability entry for Anthropic ${model}, so no reasoning control was sent and effort '${resolved.effort}' had no effect`
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3336
3546
|
const config = resolveClaudeThinking(model, resolved);
|
|
3337
3547
|
const wireEffort = typeof config.outputConfig?.effort === "string" ? config.outputConfig.effort : void 0;
|
|
3338
3548
|
const effective = config.clamp?.effective ?? wireEffort;
|
|
@@ -3388,6 +3598,10 @@ var AnthropicProvider = class {
|
|
|
3388
3598
|
}
|
|
3389
3599
|
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
3390
3600
|
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
3601
|
+
/** The only built-in adapter that honors `promptCache` (see buildBody). */
|
|
3602
|
+
realizesPromptCache() {
|
|
3603
|
+
return true;
|
|
3604
|
+
}
|
|
3391
3605
|
nativeStructuredOutputSupport() {
|
|
3392
3606
|
return "unsupported";
|
|
3393
3607
|
}
|
|
@@ -3429,6 +3643,7 @@ var AnthropicProvider = class {
|
|
|
3429
3643
|
);
|
|
3430
3644
|
const body = this.buildRequestBody(messages, options, false);
|
|
3431
3645
|
const pricingContext = pricingContextFromBody(body);
|
|
3646
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
3432
3647
|
const res = await fetchWithRetry(
|
|
3433
3648
|
`${this.baseUrl}/messages`,
|
|
3434
3649
|
{
|
|
@@ -3437,7 +3652,7 @@ var AnthropicProvider = class {
|
|
|
3437
3652
|
body: JSON.stringify(body),
|
|
3438
3653
|
signal: options.signal
|
|
3439
3654
|
},
|
|
3440
|
-
{ governor: this.governor, provider: this.name }
|
|
3655
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
3441
3656
|
);
|
|
3442
3657
|
if (!res.ok) {
|
|
3443
3658
|
const errorBody = await res.text();
|
|
@@ -3447,11 +3662,12 @@ var AnthropicProvider = class {
|
|
|
3447
3662
|
status: res.status,
|
|
3448
3663
|
headers: res.headers,
|
|
3449
3664
|
message,
|
|
3450
|
-
body: errorBody
|
|
3665
|
+
body: errorBody,
|
|
3666
|
+
timing: recorder.chatTiming()
|
|
3451
3667
|
});
|
|
3452
3668
|
}
|
|
3453
3669
|
const json = await res.json();
|
|
3454
|
-
return this.parseResponse(json, pricingContext);
|
|
3670
|
+
return withChatTiming(recorder, this.parseResponse(json, pricingContext));
|
|
3455
3671
|
}
|
|
3456
3672
|
// ---------------------------------------------------------------------------
|
|
3457
3673
|
// stream - SSE streaming completion
|
|
@@ -3463,6 +3679,7 @@ var AnthropicProvider = class {
|
|
|
3463
3679
|
);
|
|
3464
3680
|
const body = this.buildRequestBody(messages, options, true);
|
|
3465
3681
|
const pricingContext = pricingContextFromBody(body);
|
|
3682
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
3466
3683
|
const res = await fetchWithRetry(
|
|
3467
3684
|
`${this.baseUrl}/messages`,
|
|
3468
3685
|
{
|
|
@@ -3471,7 +3688,7 @@ var AnthropicProvider = class {
|
|
|
3471
3688
|
body: JSON.stringify(body),
|
|
3472
3689
|
signal: options.signal
|
|
3473
3690
|
},
|
|
3474
|
-
{ governor: this.governor, provider: this.name }
|
|
3691
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
3475
3692
|
);
|
|
3476
3693
|
if (!res.ok) {
|
|
3477
3694
|
const errorBody = await res.text();
|
|
@@ -3481,13 +3698,17 @@ var AnthropicProvider = class {
|
|
|
3481
3698
|
status: res.status,
|
|
3482
3699
|
headers: res.headers,
|
|
3483
3700
|
message,
|
|
3484
|
-
body: errorBody
|
|
3701
|
+
body: errorBody,
|
|
3702
|
+
timing: recorder.chatTiming()
|
|
3485
3703
|
});
|
|
3486
3704
|
}
|
|
3487
3705
|
if (!res.body) {
|
|
3488
3706
|
throw new Error("Anthropic stream response has no body");
|
|
3489
3707
|
}
|
|
3490
|
-
yield*
|
|
3708
|
+
yield* withCallTiming(
|
|
3709
|
+
recorder,
|
|
3710
|
+
this.parseSSEStream(res.body, pricingContext, res.headers, recorder)
|
|
3711
|
+
);
|
|
3491
3712
|
}
|
|
3492
3713
|
// ---------------------------------------------------------------------------
|
|
3493
3714
|
// Internal: request building
|
|
@@ -3522,14 +3743,31 @@ var AnthropicProvider = class {
|
|
|
3522
3743
|
max_tokens: options.maxTokens ?? 4096,
|
|
3523
3744
|
stream
|
|
3524
3745
|
};
|
|
3525
|
-
|
|
3746
|
+
const po = options.providerOptions;
|
|
3747
|
+
const callerOwnsPrefix = po !== void 0 && (po.cache_control !== void 0 || po.system !== void 0 || po.tools !== void 0);
|
|
3748
|
+
const promptCache = options.promptCache === true && !callerOwnsPrefix;
|
|
3749
|
+
const anchorOnSystem = promptCache && systemMessages.length > 0 && systemMessages[0].origin !== "runtime";
|
|
3750
|
+
if (anchorOnSystem) {
|
|
3751
|
+
body.system = systemMessages.map((m, index) => ({
|
|
3752
|
+
type: "text",
|
|
3753
|
+
text: typeof m.content === "string" ? m.content : "",
|
|
3754
|
+
...index === 0 ? { cache_control: { type: "ephemeral" } } : {}
|
|
3755
|
+
}));
|
|
3756
|
+
} else if (systemText) {
|
|
3526
3757
|
body.system = systemText;
|
|
3527
3758
|
}
|
|
3528
3759
|
if (options.stop) {
|
|
3529
3760
|
body.stop_sequences = options.stop;
|
|
3530
3761
|
}
|
|
3531
3762
|
if (options.tools && options.tools.length > 0) {
|
|
3532
|
-
|
|
3763
|
+
const tools = options.tools.map((t) => this.mapToolDefinition(t));
|
|
3764
|
+
if (promptCache && !anchorOnSystem) {
|
|
3765
|
+
tools[tools.length - 1] = {
|
|
3766
|
+
...tools[tools.length - 1],
|
|
3767
|
+
cache_control: { type: "ephemeral" }
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
body.tools = tools;
|
|
3533
3771
|
}
|
|
3534
3772
|
if (options.toolChoice !== void 0) {
|
|
3535
3773
|
body.tool_choice = this.mapToolChoice(options.toolChoice);
|
|
@@ -3548,9 +3786,13 @@ var AnthropicProvider = class {
|
|
|
3548
3786
|
}
|
|
3549
3787
|
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
3550
3788
|
const jsonInstruction = "You must respond with valid JSON only. No markdown fences, no extra text.";
|
|
3551
|
-
|
|
3789
|
+
if (Array.isArray(body.system)) {
|
|
3790
|
+
body.system = [...body.system, { type: "text", text: jsonInstruction }];
|
|
3791
|
+
} else {
|
|
3792
|
+
body.system = body.system ? `${body.system}
|
|
3552
3793
|
|
|
3553
3794
|
${jsonInstruction}` : jsonInstruction;
|
|
3795
|
+
}
|
|
3554
3796
|
}
|
|
3555
3797
|
if (options.providerOptions) {
|
|
3556
3798
|
Object.assign(body, options.providerOptions);
|
|
@@ -3730,7 +3972,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3730
3972
|
// ---------------------------------------------------------------------------
|
|
3731
3973
|
// Internal: SSE stream parsing
|
|
3732
3974
|
// ---------------------------------------------------------------------------
|
|
3733
|
-
async *parseSSEStream(body, pricingContext, responseHeaders) {
|
|
3975
|
+
async *parseSSEStream(body, pricingContext, responseHeaders, timing) {
|
|
3734
3976
|
const reader = body.getReader();
|
|
3735
3977
|
const decoder = new TextDecoder();
|
|
3736
3978
|
let buffer = "";
|
|
@@ -3759,7 +4001,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3759
4001
|
});
|
|
3760
4002
|
try {
|
|
3761
4003
|
while (true) {
|
|
3762
|
-
const { done, value } = await
|
|
4004
|
+
const { done, value } = await timing.read(reader);
|
|
3763
4005
|
if (done) break;
|
|
3764
4006
|
buffer += decoder.decode(value, { stream: true });
|
|
3765
4007
|
const lines = buffer.split("\n");
|
|
@@ -3782,7 +4024,8 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3782
4024
|
provider: this.name,
|
|
3783
4025
|
status,
|
|
3784
4026
|
headers: responseHeaders,
|
|
3785
|
-
message: event.error?.message ?? "Anthropic stream failed"
|
|
4027
|
+
message: event.error?.message ?? "Anthropic stream failed",
|
|
4028
|
+
timing: timing.streamTiming()
|
|
3786
4029
|
});
|
|
3787
4030
|
}
|
|
3788
4031
|
case "content_block_start": {
|
|
@@ -4018,7 +4261,6 @@ var GEMINI_PRICING = {
|
|
|
4018
4261
|
},
|
|
4019
4262
|
"gemini-2.5-flash": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
4020
4263
|
"gemini-2.5-flash-lite": { input: 1e-7, cached: 1e-8, output: 4e-7 },
|
|
4021
|
-
"gemini-3-flash-preview": { input: 5e-7, cached: 5e-8, output: 3e-6 },
|
|
4022
4264
|
"gemini-3.1-pro-preview": {
|
|
4023
4265
|
input: 2e-6,
|
|
4024
4266
|
cached: 2e-7,
|
|
@@ -4034,7 +4276,9 @@ var GEMINI_PRICING = {
|
|
|
4034
4276
|
"gemini-3.1-flash-lite": { input: 25e-8, cached: 25e-9, output: 15e-7 },
|
|
4035
4277
|
"gemini-3.5-flash": { input: 15e-7, cached: 15e-8, output: 9e-6 },
|
|
4036
4278
|
"gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
4037
|
-
"gemini-3.6-flash": { input:
|
|
4279
|
+
"gemini-3.6-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 },
|
|
4280
|
+
"gemini-3.7-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 },
|
|
4281
|
+
"gemini-3.8-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 }
|
|
4038
4282
|
};
|
|
4039
4283
|
function isValidTokenCount2(value) {
|
|
4040
4284
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
@@ -4262,6 +4506,7 @@ function resolveGeminiThinking(model, resolved) {
|
|
|
4262
4506
|
}
|
|
4263
4507
|
var GeminiProvider = class {
|
|
4264
4508
|
name = "google";
|
|
4509
|
+
reportsRequestLifecycle = true;
|
|
4265
4510
|
inputCapabilities(model) {
|
|
4266
4511
|
return model.trim().length > 0 ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
|
|
4267
4512
|
}
|
|
@@ -4379,6 +4624,7 @@ var GeminiProvider = class {
|
|
|
4379
4624
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
4380
4625
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
4381
4626
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4627
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4382
4628
|
const res = await fetchWithRetry(
|
|
4383
4629
|
`${this.baseUrl}/models/${pricingContext.model}:generateContent`,
|
|
4384
4630
|
{
|
|
@@ -4387,7 +4633,7 @@ var GeminiProvider = class {
|
|
|
4387
4633
|
body: JSON.stringify(body),
|
|
4388
4634
|
signal: options.signal
|
|
4389
4635
|
},
|
|
4390
|
-
{ governor: this.governor, provider: this.name }
|
|
4636
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4391
4637
|
);
|
|
4392
4638
|
if (!res.ok) {
|
|
4393
4639
|
const errorBody = await res.text();
|
|
@@ -4397,11 +4643,12 @@ var GeminiProvider = class {
|
|
|
4397
4643
|
status: res.status,
|
|
4398
4644
|
headers: res.headers,
|
|
4399
4645
|
message,
|
|
4400
|
-
body: errorBody
|
|
4646
|
+
body: errorBody,
|
|
4647
|
+
timing: recorder.chatTiming()
|
|
4401
4648
|
});
|
|
4402
4649
|
}
|
|
4403
4650
|
const json = await res.json();
|
|
4404
|
-
return this.parseResponse(json, pricingContext);
|
|
4651
|
+
return withChatTiming(recorder, this.parseResponse(json, pricingContext));
|
|
4405
4652
|
}
|
|
4406
4653
|
// ---------------------------------------------------------------------------
|
|
4407
4654
|
// stream - SSE streaming completion
|
|
@@ -4415,6 +4662,7 @@ var GeminiProvider = class {
|
|
|
4415
4662
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
4416
4663
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
4417
4664
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4665
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4418
4666
|
const res = await fetchWithRetry(
|
|
4419
4667
|
`${this.baseUrl}/models/${pricingContext.model}:streamGenerateContent?alt=sse`,
|
|
4420
4668
|
{
|
|
@@ -4423,7 +4671,7 @@ var GeminiProvider = class {
|
|
|
4423
4671
|
body: JSON.stringify(body),
|
|
4424
4672
|
signal: options.signal
|
|
4425
4673
|
},
|
|
4426
|
-
{ governor: this.governor, provider: this.name }
|
|
4674
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4427
4675
|
);
|
|
4428
4676
|
if (!res.ok) {
|
|
4429
4677
|
const errorBody = await res.text();
|
|
@@ -4433,13 +4681,14 @@ var GeminiProvider = class {
|
|
|
4433
4681
|
status: res.status,
|
|
4434
4682
|
headers: res.headers,
|
|
4435
4683
|
message,
|
|
4436
|
-
body: errorBody
|
|
4684
|
+
body: errorBody,
|
|
4685
|
+
timing: recorder.chatTiming()
|
|
4437
4686
|
});
|
|
4438
4687
|
}
|
|
4439
4688
|
if (!res.body) {
|
|
4440
4689
|
throw new Error("Gemini stream response has no body");
|
|
4441
4690
|
}
|
|
4442
|
-
yield* this.parseSSEStream(res.body, pricingContext);
|
|
4691
|
+
yield* withCallTiming(recorder, this.parseSSEStream(res.body, pricingContext, recorder));
|
|
4443
4692
|
}
|
|
4444
4693
|
// ---------------------------------------------------------------------------
|
|
4445
4694
|
// Rich image requests use Gemini Interactions. It is deliberately stateless:
|
|
@@ -4450,10 +4699,11 @@ var GeminiProvider = class {
|
|
|
4450
4699
|
this.assertSafeInteractionOptions(options);
|
|
4451
4700
|
const body = this.buildInteractionRequestBody(messages, options, false);
|
|
4452
4701
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4702
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4453
4703
|
const res = await fetchWithRetry(
|
|
4454
4704
|
`${this.baseUrl}/interactions`,
|
|
4455
4705
|
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4456
|
-
{ governor: this.governor, provider: this.name }
|
|
4706
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4457
4707
|
);
|
|
4458
4708
|
if (!res.ok) {
|
|
4459
4709
|
const errorBody = await res.text();
|
|
@@ -4462,19 +4712,24 @@ var GeminiProvider = class {
|
|
|
4462
4712
|
status: res.status,
|
|
4463
4713
|
headers: res.headers,
|
|
4464
4714
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
4465
|
-
body: errorBody
|
|
4715
|
+
body: errorBody,
|
|
4716
|
+
timing: recorder.chatTiming()
|
|
4466
4717
|
});
|
|
4467
4718
|
}
|
|
4468
|
-
return
|
|
4719
|
+
return withChatTiming(
|
|
4720
|
+
recorder,
|
|
4721
|
+
this.parseInteractionResponse(await res.json())
|
|
4722
|
+
);
|
|
4469
4723
|
}
|
|
4470
4724
|
async *streamInteraction(messages, options) {
|
|
4471
4725
|
this.assertSafeInteractionOptions(options);
|
|
4472
4726
|
const body = this.buildInteractionRequestBody(messages, options, true);
|
|
4473
4727
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4728
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4474
4729
|
const res = await fetchWithRetry(
|
|
4475
4730
|
`${this.baseUrl}/interactions?alt=sse`,
|
|
4476
4731
|
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4477
|
-
{ governor: this.governor, provider: this.name }
|
|
4732
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4478
4733
|
);
|
|
4479
4734
|
if (!res.ok) {
|
|
4480
4735
|
const errorBody = await res.text();
|
|
@@ -4483,11 +4738,15 @@ var GeminiProvider = class {
|
|
|
4483
4738
|
status: res.status,
|
|
4484
4739
|
headers: res.headers,
|
|
4485
4740
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
4486
|
-
body: errorBody
|
|
4741
|
+
body: errorBody,
|
|
4742
|
+
timing: recorder.chatTiming()
|
|
4487
4743
|
});
|
|
4488
4744
|
}
|
|
4489
4745
|
if (!res.body) throw new Error("Gemini Interactions stream has no body");
|
|
4490
|
-
yield*
|
|
4746
|
+
yield* withCallTiming(
|
|
4747
|
+
recorder,
|
|
4748
|
+
this.parseInteractionSSEStream(res.body, res.headers, recorder)
|
|
4749
|
+
);
|
|
4491
4750
|
}
|
|
4492
4751
|
buildInteractionRequestBody(messages, options, stream) {
|
|
4493
4752
|
const model = this.requestModel(options);
|
|
@@ -4643,7 +4902,7 @@ var GeminiProvider = class {
|
|
|
4643
4902
|
providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
|
|
4644
4903
|
};
|
|
4645
4904
|
}
|
|
4646
|
-
async *parseInteractionSSEStream(body, responseHeaders) {
|
|
4905
|
+
async *parseInteractionSSEStream(body, responseHeaders, timing) {
|
|
4647
4906
|
const reader = body.getReader();
|
|
4648
4907
|
const decoder = new TextDecoder();
|
|
4649
4908
|
let buffer = "";
|
|
@@ -4653,7 +4912,7 @@ var GeminiProvider = class {
|
|
|
4653
4912
|
let completed = false;
|
|
4654
4913
|
try {
|
|
4655
4914
|
while (true) {
|
|
4656
|
-
const { done, value } = await
|
|
4915
|
+
const { done, value } = await timing.read(reader);
|
|
4657
4916
|
if (done) break;
|
|
4658
4917
|
buffer += decoder.decode(value, { stream: true });
|
|
4659
4918
|
const lines = buffer.split("\n");
|
|
@@ -4676,14 +4935,16 @@ var GeminiProvider = class {
|
|
|
4676
4935
|
provider: this.name,
|
|
4677
4936
|
status,
|
|
4678
4937
|
headers: responseHeaders,
|
|
4679
|
-
message
|
|
4938
|
+
message,
|
|
4939
|
+
timing: timing.streamTiming()
|
|
4680
4940
|
});
|
|
4681
4941
|
}
|
|
4682
4942
|
throw new ProviderError({
|
|
4683
4943
|
provider: this.name,
|
|
4684
4944
|
status: 0,
|
|
4685
4945
|
retryable: false,
|
|
4686
|
-
message
|
|
4946
|
+
message,
|
|
4947
|
+
timing: timing.streamTiming()
|
|
4687
4948
|
});
|
|
4688
4949
|
}
|
|
4689
4950
|
if (event.event_type === "step.start" && event.step) {
|
|
@@ -5161,7 +5422,7 @@ var GeminiProvider = class {
|
|
|
5161
5422
|
// ---------------------------------------------------------------------------
|
|
5162
5423
|
// Internal: SSE stream parsing
|
|
5163
5424
|
// ---------------------------------------------------------------------------
|
|
5164
|
-
async *parseSSEStream(body, pricingContext) {
|
|
5425
|
+
async *parseSSEStream(body, pricingContext, timing) {
|
|
5165
5426
|
const reader = body.getReader();
|
|
5166
5427
|
const decoder = new TextDecoder();
|
|
5167
5428
|
let buffer = "";
|
|
@@ -5174,7 +5435,7 @@ var GeminiProvider = class {
|
|
|
5174
5435
|
const accumulatedParts = [];
|
|
5175
5436
|
try {
|
|
5176
5437
|
while (true) {
|
|
5177
|
-
const { done, value } = await
|
|
5438
|
+
const { done, value } = await timing.read(reader);
|
|
5178
5439
|
if (done) break;
|
|
5179
5440
|
buffer += decoder.decode(value, { stream: true });
|
|
5180
5441
|
const lines = buffer.split("\n");
|
|
@@ -6183,7 +6444,7 @@ var GEMINI_AUDIO_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
|
6183
6444
|
"audio/mulaw",
|
|
6184
6445
|
"audio/webm"
|
|
6185
6446
|
]);
|
|
6186
|
-
function
|
|
6447
|
+
function abortError() {
|
|
6187
6448
|
return new DOMException("The transcription operation was aborted", "AbortError");
|
|
6188
6449
|
}
|
|
6189
6450
|
function audioMime(audio) {
|
|
@@ -6215,13 +6476,13 @@ function secondsOffset(value) {
|
|
|
6215
6476
|
function sleep3(ms, signal) {
|
|
6216
6477
|
return new Promise((resolve2, reject) => {
|
|
6217
6478
|
if (signal?.aborted) {
|
|
6218
|
-
reject(
|
|
6479
|
+
reject(abortError());
|
|
6219
6480
|
return;
|
|
6220
6481
|
}
|
|
6221
6482
|
const timer = setTimeout(done, ms);
|
|
6222
6483
|
const onAbort = () => {
|
|
6223
6484
|
clearTimeout(timer);
|
|
6224
|
-
done(
|
|
6485
|
+
done(abortError());
|
|
6225
6486
|
};
|
|
6226
6487
|
function done(error) {
|
|
6227
6488
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -7542,7 +7803,7 @@ function isCostBearingLeaf(event) {
|
|
|
7542
7803
|
return COST_LEAF_SET.has(event.type);
|
|
7543
7804
|
}
|
|
7544
7805
|
function isUnpricedLeaf(event) {
|
|
7545
|
-
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveBillableWork(event);
|
|
7806
|
+
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && (event.unpriced === true || hasPositiveBillableWork(event));
|
|
7546
7807
|
}
|
|
7547
7808
|
function isRootLevel(event) {
|
|
7548
7809
|
const d = event.depth;
|
|
@@ -8828,18 +9089,27 @@ function parseDuration(duration) {
|
|
|
8828
9089
|
if (!match) throw new Error(`Invalid duration format: "${duration}"`);
|
|
8829
9090
|
const value = parseFloat(match[1]);
|
|
8830
9091
|
const unit = match[2];
|
|
9092
|
+
let milliseconds;
|
|
8831
9093
|
switch (unit) {
|
|
8832
9094
|
case "ms":
|
|
8833
|
-
|
|
9095
|
+
milliseconds = value;
|
|
9096
|
+
break;
|
|
8834
9097
|
case "s":
|
|
8835
|
-
|
|
9098
|
+
milliseconds = value * 1e3;
|
|
9099
|
+
break;
|
|
8836
9100
|
case "m":
|
|
8837
|
-
|
|
9101
|
+
milliseconds = value * 6e4;
|
|
9102
|
+
break;
|
|
8838
9103
|
case "h":
|
|
8839
|
-
|
|
9104
|
+
milliseconds = value * 36e5;
|
|
9105
|
+
break;
|
|
8840
9106
|
default:
|
|
8841
9107
|
throw new Error(`Unknown duration unit: "${unit}"`);
|
|
8842
9108
|
}
|
|
9109
|
+
if (!Number.isFinite(milliseconds)) {
|
|
9110
|
+
throw new Error(`Duration is too large: "${duration}"`);
|
|
9111
|
+
}
|
|
9112
|
+
return milliseconds;
|
|
8843
9113
|
}
|
|
8844
9114
|
function parseCost(cost) {
|
|
8845
9115
|
const match = cost.match(/^\$?([\d.]+)$/);
|
|
@@ -9495,6 +9765,42 @@ function recordToolSpanOutcome(span, outcome) {
|
|
|
9495
9765
|
|
|
9496
9766
|
// src/context.ts
|
|
9497
9767
|
var signalStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
9768
|
+
var hardSignalStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
9769
|
+
var timeoutTrackerStorage = new import_node_async_hooks.AsyncLocalStorage();
|
|
9770
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
9771
|
+
function composeAbortSignals(...signals) {
|
|
9772
|
+
const active = signals.filter((signal) => signal !== void 0);
|
|
9773
|
+
if (active.length === 0) return void 0;
|
|
9774
|
+
if (active.length === 1) return active[0];
|
|
9775
|
+
return AbortSignal.any(active);
|
|
9776
|
+
}
|
|
9777
|
+
function raceWithAbortSignal(promise, signal) {
|
|
9778
|
+
if (!signal) return promise;
|
|
9779
|
+
if (signal.aborted) {
|
|
9780
|
+
void promise.catch(() => {
|
|
9781
|
+
});
|
|
9782
|
+
return Promise.reject(signal.reason);
|
|
9783
|
+
}
|
|
9784
|
+
return new Promise((resolve2, reject) => {
|
|
9785
|
+
let settled = false;
|
|
9786
|
+
const finish = (callback) => {
|
|
9787
|
+
if (settled) return;
|
|
9788
|
+
settled = true;
|
|
9789
|
+
signal.removeEventListener("abort", onAbort);
|
|
9790
|
+
callback();
|
|
9791
|
+
};
|
|
9792
|
+
const onAbort = () => finish(() => reject(signal.reason));
|
|
9793
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
9794
|
+
if (signal.aborted) onAbort();
|
|
9795
|
+
promise.then(
|
|
9796
|
+
(value) => finish(() => resolve2(value)),
|
|
9797
|
+
(error) => finish(() => reject(error))
|
|
9798
|
+
);
|
|
9799
|
+
});
|
|
9800
|
+
}
|
|
9801
|
+
function pausedHumanWaitMs(tracker, now = Date.now()) {
|
|
9802
|
+
return tracker.humanWaitMs + (tracker.activeHumanWaits > 0 && tracker.humanWaitStartedAt !== void 0 ? now - tracker.humanWaitStartedAt : 0);
|
|
9803
|
+
}
|
|
9498
9804
|
var DEFAULT_BRANCH_DRAIN_TIMEOUT_MS = 5e3;
|
|
9499
9805
|
function resolveBranchDrainTimeoutMs(value) {
|
|
9500
9806
|
const resolved = value === void 0 ? DEFAULT_BRANCH_DRAIN_TIMEOUT_MS : value;
|
|
@@ -9684,6 +9990,13 @@ function describeValue(value) {
|
|
|
9684
9990
|
}
|
|
9685
9991
|
return `a value of type ${typeof value}`;
|
|
9686
9992
|
}
|
|
9993
|
+
var PROMPT_CACHE_COLD_WRITE_WARN_AFTER = 2;
|
|
9994
|
+
function nextUserTurn(history, from) {
|
|
9995
|
+
for (let i = Math.max(0, from); i < history.length; i++) {
|
|
9996
|
+
if (history[i].role === "user") return i;
|
|
9997
|
+
}
|
|
9998
|
+
return void 0;
|
|
9999
|
+
}
|
|
9687
10000
|
function estimateMessagesTokens(messages) {
|
|
9688
10001
|
let total = 0;
|
|
9689
10002
|
let unmeasured = false;
|
|
@@ -9823,6 +10136,13 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
9823
10136
|
workflowLifecycleState;
|
|
9824
10137
|
signal;
|
|
9825
10138
|
summaryCache;
|
|
10139
|
+
/** Consecutive provider calls with promptCache on that wrote to the cache
|
|
10140
|
+
* without reading from it (prefix changes per call), and consecutive calls
|
|
10141
|
+
* with no cache activity at all (prefix below the model's minimum, which
|
|
10142
|
+
* Anthropic silently ignores). Either reports once per context. */
|
|
10143
|
+
promptCacheColdWrites = 0;
|
|
10144
|
+
promptCacheInactive = 0;
|
|
10145
|
+
promptCacheWarned = false;
|
|
9826
10146
|
workflowName;
|
|
9827
10147
|
mcpManager;
|
|
9828
10148
|
spanManager;
|
|
@@ -10282,7 +10602,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10282
10602
|
tool_call_id: parsed.callId
|
|
10283
10603
|
});
|
|
10284
10604
|
}
|
|
10285
|
-
if (settlement.abortError !== void 0)
|
|
10605
|
+
if (settlement.abortError !== void 0) {
|
|
10606
|
+
signal?.throwIfAborted();
|
|
10607
|
+
throw settlement.abortError;
|
|
10608
|
+
}
|
|
10286
10609
|
}
|
|
10287
10610
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
10288
10611
|
async ask(agent2, prompt, options) {
|
|
@@ -10298,7 +10621,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10298
10621
|
const promptText = inputText(normalizedInput);
|
|
10299
10622
|
const inputDescriptor = describeModelInput(normalizedInput);
|
|
10300
10623
|
const agentName = agent2._name;
|
|
10301
|
-
|
|
10624
|
+
const inheritedHardSignal = hardSignalStorage.getStore() ?? this.signal;
|
|
10625
|
+
const hardAskSignal = composeAbortSignals(inheritedHardSignal, options?.signal);
|
|
10626
|
+
const askSignal = composeAbortSignals(this.currentSignal, options?.signal);
|
|
10627
|
+
const runAsk = () => this._checkpoint(
|
|
10302
10628
|
this._autoCheckpointName("ask", agentName),
|
|
10303
10629
|
async () => {
|
|
10304
10630
|
const parentFrame = askStorage.getStore();
|
|
@@ -10355,6 +10681,17 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10355
10681
|
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
10356
10682
|
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
10357
10683
|
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
10684
|
+
if (usageCapture.timing) {
|
|
10685
|
+
const t = usageCapture.timing;
|
|
10686
|
+
span.setAttribute("axl.agent.queued_ms", t.queuedMs);
|
|
10687
|
+
span.setAttribute("axl.agent.retry_ms", t.retryMs);
|
|
10688
|
+
span.setAttribute("axl.agent.attempts", t.attempts);
|
|
10689
|
+
span.setAttribute("axl.agent.ttfb_ms", t.ttfbMs);
|
|
10690
|
+
span.setAttribute("axl.agent.wire_ms", t.wireMs);
|
|
10691
|
+
if (t.firstTokenMs !== void 0) {
|
|
10692
|
+
span.setAttribute("axl.agent.first_token_ms", t.firstTokenMs);
|
|
10693
|
+
}
|
|
10694
|
+
}
|
|
10358
10695
|
if (usageCapture.value) {
|
|
10359
10696
|
span.setAttribute(
|
|
10360
10697
|
"axl.agent.prompt_tokens",
|
|
@@ -10442,8 +10779,25 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10442
10779
|
},
|
|
10443
10780
|
{ agent: agentName }
|
|
10444
10781
|
);
|
|
10782
|
+
const runWithAskSignal = () => askSignal ? signalStorage.run(askSignal, runAsk) : runAsk();
|
|
10783
|
+
return hardAskSignal ? hardSignalStorage.run(hardAskSignal, runWithAskSignal) : runWithAskSignal();
|
|
10445
10784
|
}
|
|
10446
10785
|
async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
10786
|
+
const tracker = { humanWaitMs: 0, activeHumanWaits: 0 };
|
|
10787
|
+
const enclosing = timeoutTrackerStorage.getStore() ?? [];
|
|
10788
|
+
return timeoutTrackerStorage.run(
|
|
10789
|
+
[...enclosing, tracker],
|
|
10790
|
+
() => this.executeAgentCallImpl(
|
|
10791
|
+
agent2,
|
|
10792
|
+
input,
|
|
10793
|
+
options,
|
|
10794
|
+
handoffMessages,
|
|
10795
|
+
usageCapture,
|
|
10796
|
+
sessionHistory
|
|
10797
|
+
)
|
|
10798
|
+
);
|
|
10799
|
+
}
|
|
10800
|
+
async executeAgentCallImpl(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
10447
10801
|
if (this.budgetContext?.exceeded) {
|
|
10448
10802
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
10449
10803
|
if (policy === "warn") {
|
|
@@ -10562,6 +10916,14 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10562
10916
|
const toolTokens = toolDefs.length > 0 ? estimateTokens(JSON.stringify(toolDefs)) : 0;
|
|
10563
10917
|
const overhead = systemTokens + toolTokens + reserveTokens;
|
|
10564
10918
|
const availableForHistory = maxContext - overhead;
|
|
10919
|
+
if (availableForHistory <= 0) {
|
|
10920
|
+
this.emitEvent({
|
|
10921
|
+
type: "log",
|
|
10922
|
+
data: {
|
|
10923
|
+
warning: `Agent '${agent2._name}' maxContext (${maxContext}) is at or below its fixed overhead (${overhead} = system ${systemTokens} + tools ${toolTokens} + reserve ${reserveTokens}); no session history can fit, so every ask summarizes, retaining at most the most recent exchange and sometimes none of it verbatim. The current input is always sent. Raise maxContext or lower contextManagement.reserveTokens.`
|
|
10924
|
+
}
|
|
10925
|
+
});
|
|
10926
|
+
}
|
|
10565
10927
|
const historyEstimate = estimateMessagesTokens(sessionHistory);
|
|
10566
10928
|
if (historyEstimate.unmeasured) {
|
|
10567
10929
|
this.emitEvent({
|
|
@@ -10627,6 +10989,7 @@ ${appendedSchemaText}`;
|
|
|
10627
10989
|
if (handoffContext.length > 0) {
|
|
10628
10990
|
messages.push({
|
|
10629
10991
|
role: "system",
|
|
10992
|
+
origin: "runtime",
|
|
10630
10993
|
content: "The following is the conversation history from the previous agent that handed off to you:"
|
|
10631
10994
|
});
|
|
10632
10995
|
for (const msg of handoffContext) {
|
|
@@ -10674,8 +11037,19 @@ ${appendedSchemaText}`;
|
|
|
10674
11037
|
}
|
|
10675
11038
|
}
|
|
10676
11039
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
10677
|
-
const timeoutMs = parseDuration(
|
|
11040
|
+
const timeoutMs = parseDuration(
|
|
11041
|
+
options?.timeout ?? agent2._config.timeout ?? this.config.defaults?.timeout ?? "60s"
|
|
11042
|
+
);
|
|
11043
|
+
const stallTimeout = options?.stallTimeout ?? agent2._config.stallTimeout ?? this.config.defaults?.stallTimeout;
|
|
11044
|
+
const stallTimeoutMs = stallTimeout === void 0 ? void 0 : parseDuration(stallTimeout);
|
|
11045
|
+
if (stallTimeoutMs !== void 0 && stallTimeoutMs > MAX_TIMER_DELAY_MS) {
|
|
11046
|
+
throw new RangeError(
|
|
11047
|
+
`stallTimeout exceeds the maximum supported timer duration of ${MAX_TIMER_DELAY_MS}ms`
|
|
11048
|
+
);
|
|
11049
|
+
}
|
|
10678
11050
|
const startTime = Date.now();
|
|
11051
|
+
const timeoutTracker = timeoutTrackerStorage.getStore()?.at(-1);
|
|
11052
|
+
const timingTotals = { turns: 0, queuedMs: 0, retryMs: 0, wireMs: 0 };
|
|
10679
11053
|
const currentMessages = [...messages];
|
|
10680
11054
|
let turns = 0;
|
|
10681
11055
|
let guardrailOutputRetries = 0;
|
|
@@ -10689,8 +11063,24 @@ ${appendedSchemaText}`;
|
|
|
10689
11063
|
let lastStartMaxAttempts = 1;
|
|
10690
11064
|
while (turns < maxTurns) {
|
|
10691
11065
|
this.currentSignal?.throwIfAborted();
|
|
10692
|
-
|
|
10693
|
-
|
|
11066
|
+
const now = Date.now();
|
|
11067
|
+
const elapsedMs = now - startTime - (timeoutTracker ? pausedHumanWaitMs(timeoutTracker, now) : 0);
|
|
11068
|
+
if (elapsedMs > timeoutMs) {
|
|
11069
|
+
throw new TimeoutError(
|
|
11070
|
+
"ctx.ask()",
|
|
11071
|
+
timeoutMs,
|
|
11072
|
+
timingTotals.turns > 0 ? {
|
|
11073
|
+
elapsedMs,
|
|
11074
|
+
queuedMs: timingTotals.queuedMs,
|
|
11075
|
+
retryMs: timingTotals.retryMs,
|
|
11076
|
+
wireMs: timingTotals.wireMs,
|
|
11077
|
+
otherMs: Math.max(
|
|
11078
|
+
0,
|
|
11079
|
+
elapsedMs - timingTotals.queuedMs - timingTotals.retryMs - timingTotals.wireMs
|
|
11080
|
+
)
|
|
11081
|
+
} : void 0,
|
|
11082
|
+
agent2._name
|
|
11083
|
+
);
|
|
10694
11084
|
}
|
|
10695
11085
|
turns++;
|
|
10696
11086
|
const isFirstTurn = turns === 1;
|
|
@@ -10730,11 +11120,49 @@ ${appendedSchemaText}`;
|
|
|
10730
11120
|
effort: options?.effort ?? agent2._config.effort,
|
|
10731
11121
|
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
10732
11122
|
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
11123
|
+
promptCache: options?.promptCache ?? agent2._config.promptCache,
|
|
10733
11124
|
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
10734
11125
|
stop: options?.stop ?? agent2._config.stop,
|
|
10735
11126
|
providerOptions,
|
|
10736
11127
|
signal: this.currentSignal
|
|
10737
11128
|
};
|
|
11129
|
+
const requestSignal = chatOptions.signal;
|
|
11130
|
+
const hardRequestSignal = hardSignalStorage.getStore() ?? this.signal;
|
|
11131
|
+
const stallController = stallTimeoutMs === void 0 ? void 0 : new AbortController();
|
|
11132
|
+
const stallReason = stallController === void 0 ? void 0 : new DOMException("Provider request stalled", "StallTimeout");
|
|
11133
|
+
let stallTimer;
|
|
11134
|
+
let providerCallActive = true;
|
|
11135
|
+
let requestDispatched = false;
|
|
11136
|
+
const clearStallTimer = () => {
|
|
11137
|
+
if (stallTimer !== void 0) {
|
|
11138
|
+
clearTimeout(stallTimer);
|
|
11139
|
+
stallTimer = void 0;
|
|
11140
|
+
}
|
|
11141
|
+
};
|
|
11142
|
+
const armStallTimer = () => {
|
|
11143
|
+
if (!providerCallActive || !stallController || !stallReason || stallTimeoutMs === void 0)
|
|
11144
|
+
return;
|
|
11145
|
+
requestDispatched = true;
|
|
11146
|
+
clearStallTimer();
|
|
11147
|
+
if (stallController.signal.aborted) return;
|
|
11148
|
+
stallTimer = setTimeout(() => stallController.abort(stallReason), stallTimeoutMs);
|
|
11149
|
+
};
|
|
11150
|
+
if (stallController) {
|
|
11151
|
+
chatOptions.signal = composeAbortSignals(requestSignal, stallController.signal);
|
|
11152
|
+
chatOptions.requestLifecycle = {
|
|
11153
|
+
onDispatch: armStallTimer,
|
|
11154
|
+
onRetry: clearStallTimer
|
|
11155
|
+
};
|
|
11156
|
+
}
|
|
11157
|
+
const providerSettlementSignal = composeAbortSignals(
|
|
11158
|
+
hardRequestSignal,
|
|
11159
|
+
stallController?.signal
|
|
11160
|
+
);
|
|
11161
|
+
const throwIfRequestStalled = () => {
|
|
11162
|
+
if (stallReason !== void 0 && providerSettlementSignal?.reason === stallReason) {
|
|
11163
|
+
throw new StallTimeoutError("ctx.ask()", stallTimeoutMs, agent2._name);
|
|
11164
|
+
}
|
|
11165
|
+
};
|
|
10738
11166
|
if (options?.schema && toolDefs.length === 0) {
|
|
10739
11167
|
const nativeOut = options?.nativeStructuredOutput ?? agent2._config.nativeStructuredOutput ?? false;
|
|
10740
11168
|
chatOptions.responseFormat = nativeOut ? {
|
|
@@ -10807,6 +11235,7 @@ ${appendedSchemaText}`;
|
|
|
10807
11235
|
const toolCalls = [];
|
|
10808
11236
|
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
10809
11237
|
let streamProviderMetadata;
|
|
11238
|
+
let streamTiming;
|
|
10810
11239
|
let thinkingContent = "";
|
|
10811
11240
|
const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof import_zod2.z.ZodObject;
|
|
10812
11241
|
const currentAttempt = schemaRetries + 1;
|
|
@@ -10820,54 +11249,77 @@ ${appendedSchemaText}`;
|
|
|
10820
11249
|
});
|
|
10821
11250
|
}
|
|
10822
11251
|
}) : void 0;
|
|
10823
|
-
|
|
11252
|
+
if (stallController && provider.reportsRequestLifecycle !== true) armStallTimer();
|
|
11253
|
+
const providerStream = provider.stream(
|
|
10824
11254
|
cloneMessagesForProvider(currentMessages),
|
|
10825
11255
|
chatOptions
|
|
10826
|
-
)
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
11256
|
+
);
|
|
11257
|
+
const iterator = providerStream[Symbol.asyncIterator]();
|
|
11258
|
+
try {
|
|
11259
|
+
while (true) {
|
|
11260
|
+
const next = await raceWithAbortSignal(
|
|
11261
|
+
Promise.resolve(iterator.next()),
|
|
11262
|
+
providerSettlementSignal
|
|
11263
|
+
);
|
|
11264
|
+
if (next.done) break;
|
|
11265
|
+
const chunk = next.value;
|
|
11266
|
+
if (stallController) armStallTimer();
|
|
11267
|
+
if (chunk.type === "text_delta") {
|
|
11268
|
+
content2 += chunk.content;
|
|
11269
|
+
this.emitEvent({ type: "token", data: chunk.content });
|
|
11270
|
+
if (walker) {
|
|
11271
|
+
walker.processChunk(chunk.content);
|
|
11272
|
+
if (walker.consumeBoundary()) {
|
|
11273
|
+
let parsed;
|
|
11274
|
+
try {
|
|
11275
|
+
parsed = parsePartialJson(extractJson(content2));
|
|
11276
|
+
} catch {
|
|
11277
|
+
parsed = void 0;
|
|
11278
|
+
}
|
|
11279
|
+
if (parsed !== void 0) {
|
|
11280
|
+
this.emitEvent({
|
|
11281
|
+
type: "partial_object",
|
|
11282
|
+
agent: agent2._name,
|
|
11283
|
+
attempt: currentAttempt,
|
|
11284
|
+
data: { object: parsed }
|
|
11285
|
+
});
|
|
11286
|
+
}
|
|
10846
11287
|
}
|
|
10847
11288
|
}
|
|
10848
|
-
}
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
if (chunk.
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
if (chunk.usage) {
|
|
11289
|
+
} else if (chunk.type === "thinking_delta") {
|
|
11290
|
+
thinkingContent += chunk.content;
|
|
11291
|
+
} else if (chunk.type === "tool_call_delta") {
|
|
11292
|
+
let buffer = toolCallBuffers.get(chunk.id);
|
|
11293
|
+
if (!buffer) {
|
|
11294
|
+
buffer = { id: chunk.id, name: "", arguments: "" };
|
|
11295
|
+
toolCallBuffers.set(chunk.id, buffer);
|
|
11296
|
+
}
|
|
11297
|
+
if (chunk.name) buffer.name = chunk.name;
|
|
11298
|
+
if (chunk.arguments) buffer.arguments += chunk.arguments;
|
|
11299
|
+
} else if (chunk.type === "done") {
|
|
11300
|
+
streamProviderMetadata = chunk.providerMetadata;
|
|
11301
|
+
streamTiming = chunk.timing;
|
|
10862
11302
|
response = {
|
|
10863
11303
|
content: content2,
|
|
10864
11304
|
tool_calls: void 0,
|
|
10865
11305
|
usage: chunk.usage,
|
|
10866
|
-
cost: chunk.cost
|
|
11306
|
+
cost: chunk.cost,
|
|
11307
|
+
providerMetadata: chunk.providerMetadata,
|
|
11308
|
+
timing: chunk.timing
|
|
10867
11309
|
};
|
|
10868
11310
|
}
|
|
10869
11311
|
}
|
|
11312
|
+
} catch (error) {
|
|
11313
|
+
if (iterator.return) {
|
|
11314
|
+
try {
|
|
11315
|
+
void Promise.resolve(iterator.return(void 0)).catch(() => {
|
|
11316
|
+
});
|
|
11317
|
+
} catch {
|
|
11318
|
+
}
|
|
11319
|
+
}
|
|
11320
|
+
throw error;
|
|
10870
11321
|
}
|
|
11322
|
+
throwIfRequestStalled();
|
|
10871
11323
|
for (const buffer of toolCallBuffers.values()) {
|
|
10872
11324
|
toolCalls.push({
|
|
10873
11325
|
id: buffer.id,
|
|
@@ -10885,39 +11337,91 @@ ${appendedSchemaText}`;
|
|
|
10885
11337
|
if (streamProviderMetadata) {
|
|
10886
11338
|
response.providerMetadata = streamProviderMetadata;
|
|
10887
11339
|
}
|
|
11340
|
+
if (streamTiming) {
|
|
11341
|
+
response.timing = streamTiming;
|
|
11342
|
+
}
|
|
10888
11343
|
if (thinkingContent) {
|
|
10889
11344
|
response.thinking_content = thinkingContent;
|
|
10890
11345
|
}
|
|
10891
11346
|
} else {
|
|
10892
|
-
|
|
11347
|
+
if (stallController && provider.reportsRequestLifecycle !== true) armStallTimer();
|
|
11348
|
+
response = await raceWithAbortSignal(
|
|
11349
|
+
provider.chat(cloneMessagesForProvider(currentMessages), chatOptions),
|
|
11350
|
+
providerSettlementSignal
|
|
11351
|
+
);
|
|
11352
|
+
throwIfRequestStalled();
|
|
10893
11353
|
}
|
|
10894
11354
|
} catch (err) {
|
|
10895
|
-
|
|
11355
|
+
const callError = stallReason !== void 0 && providerSettlementSignal?.reason === stallReason ? new StallTimeoutError("ctx.ask()", stallTimeoutMs, agent2._name) : err;
|
|
11356
|
+
if (richRequest) this.recordRichFailure(callError);
|
|
11357
|
+
const failureTiming = callError instanceof ProviderError ? callError.timing : void 0;
|
|
10896
11358
|
this.emitEvent({
|
|
10897
11359
|
type: "agent_call_end",
|
|
10898
11360
|
agent: agent2._name,
|
|
10899
11361
|
model: effectiveModelUri,
|
|
10900
11362
|
promptVersion: agent2._config.version,
|
|
11363
|
+
// A dispatched request abandoned at the runtime boundary may still
|
|
11364
|
+
// be billed even though a non-cooperative provider supplied no
|
|
11365
|
+
// usage. Preserve budget honesty by marking the known total as a
|
|
11366
|
+
// lower bound rather than presenting an exact $0.
|
|
11367
|
+
...callError instanceof StallTimeoutError && requestDispatched ? { unpriced: true } : {},
|
|
10901
11368
|
duration: Date.now() - turnStart,
|
|
11369
|
+
// Top-level beside `duration`, exactly as on the success path.
|
|
11370
|
+
...failureTiming ? { timing: failureTiming } : {},
|
|
10902
11371
|
data: {
|
|
10903
11372
|
response: "",
|
|
10904
11373
|
turn: turns,
|
|
10905
11374
|
...retryReason ? { retryReason } : {},
|
|
10906
|
-
error: this._observerErrorProjection(
|
|
11375
|
+
error: this._observerErrorProjection(callError)?.message ?? legacyErrorMessage(callError),
|
|
10907
11376
|
// Enrich with typed-error metadata when available. `body` is
|
|
10908
11377
|
// deliberately NOT emitted — it's redaction-eligible (see
|
|
10909
11378
|
// docs/security.md). `data.error` already carries the message.
|
|
10910
|
-
...providerErrorEventMetadata(
|
|
11379
|
+
...providerErrorEventMetadata(callError, richRequest)
|
|
10911
11380
|
}
|
|
10912
11381
|
});
|
|
10913
|
-
throw
|
|
11382
|
+
throw callError;
|
|
11383
|
+
} finally {
|
|
11384
|
+
providerCallActive = false;
|
|
11385
|
+
clearStallTimer();
|
|
10914
11386
|
}
|
|
10915
11387
|
if (usageCapture && response.usage) {
|
|
10916
11388
|
usageCapture.value = response.usage;
|
|
10917
11389
|
}
|
|
11390
|
+
if (usageCapture && response.timing) {
|
|
11391
|
+
usageCapture.timing = response.timing;
|
|
11392
|
+
}
|
|
11393
|
+
if (chatOptions.promptCache && provider.realizesPromptCache?.(model) === true && response.usage && !this.promptCacheWarned) {
|
|
11394
|
+
const wrote = (response.usage.cache_write_tokens ?? 0) > 0;
|
|
11395
|
+
const read = (response.usage.cached_tokens ?? 0) > 0;
|
|
11396
|
+
if (read) {
|
|
11397
|
+
this.promptCacheColdWrites = 0;
|
|
11398
|
+
this.promptCacheInactive = 0;
|
|
11399
|
+
} else if (wrote) {
|
|
11400
|
+
this.promptCacheColdWrites += 1;
|
|
11401
|
+
this.promptCacheInactive = 0;
|
|
11402
|
+
} else {
|
|
11403
|
+
this.promptCacheInactive += 1;
|
|
11404
|
+
}
|
|
11405
|
+
let warning;
|
|
11406
|
+
if (this.promptCacheColdWrites >= PROMPT_CACHE_COLD_WRITE_WARN_AFTER) {
|
|
11407
|
+
warning = `Agent '${agent2._name}' has promptCache on, but its prefix was written to the cache ${this.promptCacheColdWrites} times in a row with no cache reads. Each write bills 1.25x input. If this agent's system prompt or tool set changes per call, set promptCache: false for it.`;
|
|
11408
|
+
} else if (this.promptCacheInactive >= PROMPT_CACHE_COLD_WRITE_WARN_AFTER) {
|
|
11409
|
+
warning = `Agent '${agent2._name}' has promptCache on, but ${this.promptCacheInactive} calls in a row showed no cache writes or reads. Anthropic silently ignores a breakpoint below the model's minimum cacheable length (4,096 tokens on Haiku 4.5; 1,024 or 512 on others) -- the system prompt plus tools may be too short to cache on this model, or the provider may not support prompt caching.`;
|
|
11410
|
+
}
|
|
11411
|
+
if (warning) {
|
|
11412
|
+
this.promptCacheWarned = true;
|
|
11413
|
+
this.emitEvent({ type: "log", agent: agent2._name, data: { warning } });
|
|
11414
|
+
}
|
|
11415
|
+
}
|
|
10918
11416
|
if (response.cost) {
|
|
10919
11417
|
this._accumulateBudgetCost(response.cost);
|
|
10920
11418
|
}
|
|
11419
|
+
if (response.timing) {
|
|
11420
|
+
timingTotals.turns += 1;
|
|
11421
|
+
timingTotals.queuedMs += response.timing.queuedMs;
|
|
11422
|
+
timingTotals.retryMs += response.timing.retryMs;
|
|
11423
|
+
timingTotals.wireMs += response.timing.wireMs;
|
|
11424
|
+
}
|
|
10921
11425
|
this.emitEvent({
|
|
10922
11426
|
type: "agent_call_end",
|
|
10923
11427
|
agent: agent2._name,
|
|
@@ -10932,6 +11436,8 @@ ${appendedSchemaText}`;
|
|
|
10932
11436
|
cacheWrite: response.usage.cache_write_tokens
|
|
10933
11437
|
} : void 0,
|
|
10934
11438
|
duration: Date.now() - turnStart,
|
|
11439
|
+
// Additive beside `duration`, which keeps its turn-wall-clock meaning.
|
|
11440
|
+
...response.timing ? { timing: response.timing } : {},
|
|
10935
11441
|
data: {
|
|
10936
11442
|
response: response.content,
|
|
10937
11443
|
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
@@ -11650,6 +12156,7 @@ ${appendedSchemaText}`;
|
|
|
11650
12156
|
if (this.summaryCache) {
|
|
11651
12157
|
const summaryMsg2 = {
|
|
11652
12158
|
role: "system",
|
|
12159
|
+
origin: "runtime",
|
|
11653
12160
|
content: `Summary of earlier conversation:
|
|
11654
12161
|
${this.summaryCache}`
|
|
11655
12162
|
};
|
|
@@ -11663,6 +12170,7 @@ ${this.summaryCache}`
|
|
|
11663
12170
|
recentTokens2 += msgTokens;
|
|
11664
12171
|
splitIdx2 = i;
|
|
11665
12172
|
}
|
|
12173
|
+
splitIdx2 = nextUserTurn(history, splitIdx2) ?? history.length;
|
|
11666
12174
|
if (splitIdx2 < history.length) {
|
|
11667
12175
|
return [summaryMsg2, ...history.slice(splitIdx2)];
|
|
11668
12176
|
}
|
|
@@ -11676,6 +12184,19 @@ ${this.summaryCache}`
|
|
|
11676
12184
|
recentTokens += msgTokens;
|
|
11677
12185
|
splitIdx = i;
|
|
11678
12186
|
}
|
|
12187
|
+
const anchored = nextUserTurn(history, splitIdx);
|
|
12188
|
+
if (anchored !== void 0) {
|
|
12189
|
+
splitIdx = anchored;
|
|
12190
|
+
} else if (splitIdx === history.length && history.length > 1) {
|
|
12191
|
+
for (let i = history.length - 1; i > 0; i--) {
|
|
12192
|
+
if (history[i].role === "user") {
|
|
12193
|
+
splitIdx = i;
|
|
12194
|
+
break;
|
|
12195
|
+
}
|
|
12196
|
+
}
|
|
12197
|
+
} else {
|
|
12198
|
+
splitIdx = history.length;
|
|
12199
|
+
}
|
|
11679
12200
|
if (splitIdx === 0) return history;
|
|
11680
12201
|
const oldMessages = history.slice(0, splitIdx);
|
|
11681
12202
|
const summaryModelUri = this.config.contextManagement?.summaryModel;
|
|
@@ -11707,6 +12228,7 @@ ${this.summaryCache}`
|
|
|
11707
12228
|
}
|
|
11708
12229
|
const summaryMsg = {
|
|
11709
12230
|
role: "system",
|
|
12231
|
+
origin: "runtime",
|
|
11710
12232
|
content: `Summary of earlier conversation:
|
|
11711
12233
|
${summaryResponse.content}`
|
|
11712
12234
|
};
|
|
@@ -12385,46 +12907,52 @@ ${summaryResponse.content}`
|
|
|
12385
12907
|
}
|
|
12386
12908
|
// ── ctx.awaitHuman() ──────────────────────────────────────────────────
|
|
12387
12909
|
async awaitHuman(options) {
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12910
|
+
const trackers = timeoutTrackerStorage.getStore();
|
|
12911
|
+
const startedAt = Date.now();
|
|
12912
|
+
for (const tracker of trackers ?? []) {
|
|
12913
|
+
if (tracker.activeHumanWaits++ === 0) tracker.humanWaitStartedAt = startedAt;
|
|
12914
|
+
}
|
|
12915
|
+
try {
|
|
12916
|
+
if (this.spanManager) {
|
|
12917
|
+
return await this.spanManager.withSpanAsync(
|
|
12918
|
+
"axl.ctx.awaitHuman",
|
|
12919
|
+
{
|
|
12920
|
+
"axl.awaitHuman.channel": options.channel
|
|
12921
|
+
},
|
|
12922
|
+
async (span) => {
|
|
12923
|
+
const start = Date.now();
|
|
12924
|
+
const result = await this._awaitHumanImpl(options);
|
|
12925
|
+
span.setAttribute("axl.awaitHuman.wait_duration", Date.now() - start);
|
|
12926
|
+
span.setAttribute("axl.awaitHuman.approved", result.approved);
|
|
12927
|
+
return result;
|
|
12928
|
+
}
|
|
12929
|
+
);
|
|
12930
|
+
}
|
|
12931
|
+
return await this._awaitHumanImpl(options);
|
|
12932
|
+
} finally {
|
|
12933
|
+
const finishedAt = Date.now();
|
|
12934
|
+
for (const tracker of trackers ?? []) {
|
|
12935
|
+
tracker.activeHumanWaits--;
|
|
12936
|
+
if (tracker.activeHumanWaits === 0 && tracker.humanWaitStartedAt !== void 0) {
|
|
12937
|
+
tracker.humanWaitMs += finishedAt - tracker.humanWaitStartedAt;
|
|
12938
|
+
tracker.humanWaitStartedAt = void 0;
|
|
12400
12939
|
}
|
|
12401
|
-
|
|
12940
|
+
}
|
|
12402
12941
|
}
|
|
12403
|
-
return this._awaitHumanImpl(options);
|
|
12404
12942
|
}
|
|
12405
12943
|
async _awaitHumanImpl(options) {
|
|
12406
12944
|
const branchSignal = this.currentSignal;
|
|
12407
|
-
const makeAbortError = (reason) => {
|
|
12408
|
-
if (typeof DOMException !== "undefined") {
|
|
12409
|
-
return new DOMException(
|
|
12410
|
-
typeof reason === "string" ? reason : "awaitHuman aborted",
|
|
12411
|
-
"AbortError"
|
|
12412
|
-
);
|
|
12413
|
-
}
|
|
12414
|
-
const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
|
|
12415
|
-
err.name = "AbortError";
|
|
12416
|
-
return err;
|
|
12417
|
-
};
|
|
12418
12945
|
const awaitWithAbort = async (promise, signal2 = branchSignal) => {
|
|
12419
12946
|
if (!signal2) return promise;
|
|
12420
|
-
|
|
12947
|
+
signal2.throwIfAborted();
|
|
12421
12948
|
let onAbort;
|
|
12422
12949
|
try {
|
|
12423
12950
|
return await Promise.race([
|
|
12424
12951
|
promise,
|
|
12425
12952
|
new Promise((_, reject) => {
|
|
12426
|
-
onAbort = () => reject(
|
|
12953
|
+
onAbort = () => reject(signal2.reason);
|
|
12427
12954
|
signal2.addEventListener("abort", onAbort, { once: true });
|
|
12955
|
+
if (signal2.aborted) onAbort();
|
|
12428
12956
|
})
|
|
12429
12957
|
]);
|
|
12430
12958
|
} finally {
|
|
@@ -12432,12 +12960,12 @@ ${summaryResponse.content}`
|
|
|
12432
12960
|
}
|
|
12433
12961
|
};
|
|
12434
12962
|
if (this.awaitHumanHandler) {
|
|
12435
|
-
|
|
12963
|
+
branchSignal?.throwIfAborted();
|
|
12436
12964
|
this.emitEvent({
|
|
12437
12965
|
type: "await_human",
|
|
12438
12966
|
data: { channel: options.channel, prompt: options.prompt }
|
|
12439
12967
|
});
|
|
12440
|
-
|
|
12968
|
+
branchSignal?.throwIfAborted();
|
|
12441
12969
|
const decision2 = parseHumanDecision(
|
|
12442
12970
|
await awaitWithAbort(Promise.resolve(this.awaitHumanHandler(options)))
|
|
12443
12971
|
);
|
|
@@ -12492,12 +13020,12 @@ ${summaryResponse.content}`
|
|
|
12492
13020
|
const abortListener = () => {
|
|
12493
13021
|
registeredResolver.cancelled = true;
|
|
12494
13022
|
markReady();
|
|
12495
|
-
rejectDecision(
|
|
13023
|
+
rejectDecision(signal.reason);
|
|
12496
13024
|
};
|
|
12497
13025
|
signal.addEventListener("abort", abortListener, { once: true });
|
|
12498
13026
|
let decision;
|
|
12499
13027
|
try {
|
|
12500
|
-
|
|
13028
|
+
signal.throwIfAborted();
|
|
12501
13029
|
if (this.stateStore) {
|
|
12502
13030
|
saveAttempted = true;
|
|
12503
13031
|
await this.stateStore.savePendingDecision(this.executionId, {
|
|
@@ -12508,7 +13036,7 @@ ${summaryResponse.content}`
|
|
|
12508
13036
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12509
13037
|
});
|
|
12510
13038
|
}
|
|
12511
|
-
|
|
13039
|
+
signal.throwIfAborted();
|
|
12512
13040
|
this.emitEvent({
|
|
12513
13041
|
type: "await_human",
|
|
12514
13042
|
data: { channel: options.channel, prompt: options.prompt }
|
|
@@ -12978,7 +13506,7 @@ ${summaryResponse.content}`
|
|
|
12978
13506
|
data: partial.data
|
|
12979
13507
|
});
|
|
12980
13508
|
};
|
|
12981
|
-
const
|
|
13509
|
+
const abortError2 = () => new DOMException("The transcription operation was aborted", "AbortError");
|
|
12982
13510
|
const cleanupStatus = (value) => value === "not_required" || value === "deleted" || value === "failed" || value === "timed_out" ? value : void 0;
|
|
12983
13511
|
const safeProviderFailure = (error, provider, model) => {
|
|
12984
13512
|
const details = error && typeof error === "object" ? error : void 0;
|
|
@@ -13029,7 +13557,7 @@ ${summaryResponse.content}`
|
|
|
13029
13557
|
hasWork: false
|
|
13030
13558
|
})
|
|
13031
13559
|
});
|
|
13032
|
-
throw
|
|
13560
|
+
throw abortError2();
|
|
13033
13561
|
}
|
|
13034
13562
|
if (this.budgetContext?.exceeded) {
|
|
13035
13563
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
@@ -13095,7 +13623,7 @@ ${summaryResponse.content}`
|
|
|
13095
13623
|
...failure.cleanup ? [{ cleanupStatus: failure.cleanup }] : []
|
|
13096
13624
|
)
|
|
13097
13625
|
});
|
|
13098
|
-
throw
|
|
13626
|
+
throw abortError2();
|
|
13099
13627
|
}
|
|
13100
13628
|
if (failure.accounting.cost !== void 0)
|
|
13101
13629
|
this._accumulateBudgetCost(failure.accounting.cost);
|
|
@@ -13138,7 +13666,7 @@ ${summaryResponse.content}`
|
|
|
13138
13666
|
...cleanupStatus(result.cleanupStatus) ? [{ cleanupStatus: cleanupStatus(result.cleanupStatus) }] : []
|
|
13139
13667
|
)
|
|
13140
13668
|
});
|
|
13141
|
-
throw
|
|
13669
|
+
throw abortError2();
|
|
13142
13670
|
}
|
|
13143
13671
|
let transcript;
|
|
13144
13672
|
try {
|
|
@@ -13224,9 +13752,9 @@ ${summaryResponse.content}`
|
|
|
13224
13752
|
const cost = partial.cost;
|
|
13225
13753
|
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
13226
13754
|
const usable = isUsableCost(cost);
|
|
13227
|
-
const unpriced = !usable && hasPositiveBillableWork(
|
|
13755
|
+
const unpriced = !usable && (partial.unpriced === true || hasPositiveBillableWork(
|
|
13228
13756
|
partial
|
|
13229
|
-
);
|
|
13757
|
+
));
|
|
13230
13758
|
if (frame) {
|
|
13231
13759
|
if (usable) frame.askCost.value += cost;
|
|
13232
13760
|
else if (unpriced) frame.askUnpriced = true;
|
|
@@ -13237,7 +13765,7 @@ ${summaryResponse.content}`
|
|
|
13237
13765
|
if (!this.budgetContext.unpricedWarned && Number.isFinite(this.budgetContext.limit)) {
|
|
13238
13766
|
this.budgetContext.unpricedWarned = true;
|
|
13239
13767
|
console.warn(
|
|
13240
|
-
"Budget honesty:
|
|
13768
|
+
"Budget honesty: unknown-cost work detected \u2014 this budget's cost limit is a lower bound and is NOT enforced on unknown spend. See docs/observability.md#budget-honesty."
|
|
13241
13769
|
);
|
|
13242
13770
|
}
|
|
13243
13771
|
}
|
|
@@ -15206,12 +15734,13 @@ function forwardAbortSignal(external, internal) {
|
|
|
15206
15734
|
if (!external) return () => {
|
|
15207
15735
|
};
|
|
15208
15736
|
if (external.aborted) {
|
|
15209
|
-
internal.abort();
|
|
15737
|
+
internal.abort(external.reason);
|
|
15210
15738
|
return () => {
|
|
15211
15739
|
};
|
|
15212
15740
|
}
|
|
15213
|
-
const onAbort = () => internal.abort();
|
|
15741
|
+
const onAbort = () => internal.abort(external.reason);
|
|
15214
15742
|
external.addEventListener("abort", onAbort, { once: true });
|
|
15743
|
+
if (external.aborted) onAbort();
|
|
15215
15744
|
return () => external.removeEventListener("abort", onAbort);
|
|
15216
15745
|
}
|
|
15217
15746
|
var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
@@ -16482,6 +17011,8 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
16482
17011
|
parent: parentScope
|
|
16483
17012
|
};
|
|
16484
17013
|
const modelCalls = /* @__PURE__ */ new Map();
|
|
17014
|
+
const modelTiming = /* @__PURE__ */ new Map();
|
|
17015
|
+
const captureTimingSamples = options?.captureTimingSamples === true;
|
|
16485
17016
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
16486
17017
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
16487
17018
|
let agentCalls = 0;
|
|
@@ -16499,6 +17030,30 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
16499
17030
|
tokens.output += event.tokens.output ?? 0;
|
|
16500
17031
|
tokens.reasoning += event.tokens.reasoning ?? 0;
|
|
16501
17032
|
}
|
|
17033
|
+
const failed2 = event.data?.error != null;
|
|
17034
|
+
if (event.model && event.timing && !failed2) {
|
|
17035
|
+
const t = event.timing;
|
|
17036
|
+
let bucket = modelTiming.get(event.model);
|
|
17037
|
+
if (!bucket) {
|
|
17038
|
+
bucket = {
|
|
17039
|
+
calls: 0,
|
|
17040
|
+
queuedMs: 0,
|
|
17041
|
+
retryMs: 0,
|
|
17042
|
+
wireMs: 0,
|
|
17043
|
+
...captureTimingSamples ? { samples: [] } : {}
|
|
17044
|
+
};
|
|
17045
|
+
modelTiming.set(event.model, bucket);
|
|
17046
|
+
}
|
|
17047
|
+
bucket.calls++;
|
|
17048
|
+
bucket.samples?.push(t);
|
|
17049
|
+
bucket.queuedMs += t.queuedMs;
|
|
17050
|
+
bucket.retryMs += t.retryMs;
|
|
17051
|
+
bucket.wireMs += t.wireMs;
|
|
17052
|
+
if (t.firstTokenMs != null) {
|
|
17053
|
+
bucket.firstTokenMs = (bucket.firstTokenMs ?? 0) + t.firstTokenMs;
|
|
17054
|
+
bucket.firstTokenCalls = (bucket.firstTokenCalls ?? 0) + 1;
|
|
17055
|
+
}
|
|
17056
|
+
}
|
|
16502
17057
|
}
|
|
16503
17058
|
if (event.type === "workflow_start" && event.workflow) {
|
|
16504
17059
|
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
@@ -16529,6 +17084,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
|
|
|
16529
17084
|
result,
|
|
16530
17085
|
cost: scope.totalCost,
|
|
16531
17086
|
unpriced,
|
|
17087
|
+
...modelTiming.size > 0 ? { modelTiming: Object.fromEntries(modelTiming) } : {},
|
|
16532
17088
|
...capturedTraces ? { traces: capturedTraces } : {},
|
|
16533
17089
|
metadata: {
|
|
16534
17090
|
models: [...modelCalls.keys()],
|
|
@@ -17666,6 +18222,7 @@ function cosineSimilarity2(a, b) {
|
|
|
17666
18222
|
SQLiteStore,
|
|
17667
18223
|
Session,
|
|
17668
18224
|
SqliteVectorStore,
|
|
18225
|
+
StallTimeoutError,
|
|
17669
18226
|
ThinkTagScanner,
|
|
17670
18227
|
TimeoutError,
|
|
17671
18228
|
ToolFailure,
|