@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.js
CHANGED
|
@@ -170,9 +170,22 @@ var NoConsensus = class extends AxlError {
|
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
172
|
var TimeoutError = class extends AxlError {
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
/** Latency attribution for the timed-out operation, when measurable. */
|
|
174
|
+
breakdown;
|
|
175
|
+
constructor(operation, timeoutMs, breakdown, agent2) {
|
|
176
|
+
super(
|
|
177
|
+
"TIMEOUT",
|
|
178
|
+
`${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}'` : "")
|
|
179
|
+
);
|
|
175
180
|
this.name = "TimeoutError";
|
|
181
|
+
if (breakdown) this.breakdown = breakdown;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var StallTimeoutError = class extends TimeoutError {
|
|
185
|
+
constructor(operation, timeoutMs, agent2) {
|
|
186
|
+
super(operation, timeoutMs, void 0, agent2);
|
|
187
|
+
this.name = "StallTimeoutError";
|
|
188
|
+
this.message = `${operation} provider request stalled for ${timeoutMs}ms` + (agent2 ? ` for agent '${agent2}'` : "");
|
|
176
189
|
}
|
|
177
190
|
};
|
|
178
191
|
function formatBudgetCost(cost) {
|
|
@@ -719,6 +732,7 @@ var ProviderError = class extends AxlError {
|
|
|
719
732
|
this.retryAfterMs = args.retryAfterMs;
|
|
720
733
|
this.requestId = args.requestId;
|
|
721
734
|
this.body = args.body;
|
|
735
|
+
if (args.timing) this.timing = args.timing;
|
|
722
736
|
}
|
|
723
737
|
};
|
|
724
738
|
function isRetryableStatus(status) {
|
|
@@ -770,7 +784,8 @@ function buildProviderError(args) {
|
|
|
770
784
|
message: args.message,
|
|
771
785
|
retryAfterMs: args.headers ? parseRetryAfter(args.headers) : void 0,
|
|
772
786
|
requestId: args.headers ? extractRequestId(args.headers) : void 0,
|
|
773
|
-
body: args.body
|
|
787
|
+
body: args.body,
|
|
788
|
+
timing: args.timing
|
|
774
789
|
});
|
|
775
790
|
}
|
|
776
791
|
|
|
@@ -807,14 +822,33 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
807
822
|
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
808
823
|
const governor = opts?.governor;
|
|
809
824
|
const provider = opts?.provider ?? "unknown";
|
|
825
|
+
const observer = opts?.timing;
|
|
810
826
|
let acquired = false;
|
|
827
|
+
let queuedMs = 0;
|
|
811
828
|
if (governor) {
|
|
829
|
+
const acquireStart = Date.now();
|
|
812
830
|
await governor.acquire(init?.signal ?? void 0);
|
|
813
831
|
acquired = true;
|
|
814
|
-
|
|
832
|
+
queuedMs = Date.now() - acquireStart;
|
|
833
|
+
}
|
|
834
|
+
let firstDispatchedAt = 0;
|
|
835
|
+
let dispatchedAt = 0;
|
|
836
|
+
let headersAt = 0;
|
|
837
|
+
const reportComplete = (attempts) => {
|
|
838
|
+
observer?.onComplete?.({
|
|
839
|
+
queuedMs,
|
|
840
|
+
attempts,
|
|
841
|
+
retryMs: dispatchedAt - firstDispatchedAt,
|
|
842
|
+
dispatchedAt,
|
|
843
|
+
headersAt
|
|
844
|
+
});
|
|
845
|
+
};
|
|
815
846
|
try {
|
|
816
847
|
for (let attempt = 0; ; attempt++) {
|
|
817
848
|
let res;
|
|
849
|
+
dispatchedAt = Date.now();
|
|
850
|
+
if (attempt === 0) firstDispatchedAt = dispatchedAt;
|
|
851
|
+
observer?.onDispatch?.(attempt + 1, dispatchedAt);
|
|
818
852
|
try {
|
|
819
853
|
res = await fetch(input, { ...init, redirect: "manual" });
|
|
820
854
|
} catch (err) {
|
|
@@ -826,18 +860,23 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
826
860
|
message: err instanceof Error ? err.message : String(err)
|
|
827
861
|
});
|
|
828
862
|
}
|
|
863
|
+
observer?.onRetry?.(attempt + 1, Date.now());
|
|
829
864
|
await sleep2(jitter(BASE_DELAY_MS * 2 ** attempt), init?.signal ?? void 0);
|
|
830
865
|
continue;
|
|
831
866
|
}
|
|
867
|
+
headersAt = Date.now();
|
|
832
868
|
governor?.observe(res);
|
|
833
869
|
if (res.ok || !RETRYABLE_STATUS_CODES.has(res.status) || attempt >= maxRetries) {
|
|
870
|
+
reportComplete(attempt + 1);
|
|
834
871
|
return res;
|
|
835
872
|
}
|
|
836
873
|
if (init?.signal?.aborted) {
|
|
874
|
+
reportComplete(attempt + 1);
|
|
837
875
|
return res;
|
|
838
876
|
}
|
|
839
877
|
const retryAfterMs = parseRetryAfter(res.headers);
|
|
840
878
|
const baseDelay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, MAX_BACKOFF_MS) : BASE_DELAY_MS * 2 ** attempt;
|
|
879
|
+
observer?.onRetry?.(attempt + 1, Date.now());
|
|
841
880
|
await sleep2(jitter(baseDelay), init?.signal ?? void 0);
|
|
842
881
|
}
|
|
843
882
|
} finally {
|
|
@@ -845,10 +884,110 @@ async function fetchWithRetry(input, init, opts) {
|
|
|
845
884
|
}
|
|
846
885
|
}
|
|
847
886
|
|
|
848
|
-
// src/providers/
|
|
849
|
-
|
|
850
|
-
|
|
887
|
+
// src/providers/call-timing.ts
|
|
888
|
+
var CallTimingRecorder = class {
|
|
889
|
+
constructor(requestLifecycle) {
|
|
890
|
+
this.requestLifecycle = requestLifecycle;
|
|
891
|
+
}
|
|
892
|
+
fetchTiming;
|
|
893
|
+
firstTokenAt;
|
|
894
|
+
/** Consumer suspension after pre-content chunks (for example, tool deltas). */
|
|
895
|
+
preFirstTokenConsumerMs = 0;
|
|
896
|
+
/** Cumulative time awaiting `reader.read()`, i.e. transport, not consumer. */
|
|
897
|
+
readWaitMs = 0;
|
|
898
|
+
/** Pass as `timing` in the `fetchWithRetry` options object. */
|
|
899
|
+
observer = {
|
|
900
|
+
onDispatch: () => {
|
|
901
|
+
this.requestLifecycle?.onDispatch?.();
|
|
902
|
+
},
|
|
903
|
+
onRetry: () => {
|
|
904
|
+
this.requestLifecycle?.onRetry?.();
|
|
905
|
+
},
|
|
906
|
+
onComplete: (timing) => {
|
|
907
|
+
this.fetchTiming = timing;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* Await one body read, charging only the wait itself to `wireMs`. Time the
|
|
912
|
+
* consumer spends between reads is excluded because it elapses outside this
|
|
913
|
+
* call.
|
|
914
|
+
*/
|
|
915
|
+
async read(reader) {
|
|
916
|
+
const start = Date.now();
|
|
917
|
+
try {
|
|
918
|
+
return await reader.read();
|
|
919
|
+
} finally {
|
|
920
|
+
this.readWaitMs += Date.now() - start;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
/** Stamp the first content delta. Idempotent — only the first call counts. */
|
|
924
|
+
markFirstToken() {
|
|
925
|
+
this.firstTokenAt ??= Date.now();
|
|
926
|
+
}
|
|
927
|
+
/** Exclude backpressure after a pre-content yield from first-content latency. */
|
|
928
|
+
recordPreFirstTokenConsumerPause(startedAt) {
|
|
929
|
+
if (this.firstTokenAt === void 0) {
|
|
930
|
+
this.preFirstTokenConsumerMs += Math.max(0, Date.now() - startedAt);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Timing for a non-streaming call. Call once the response body is parsed.
|
|
935
|
+
*
|
|
936
|
+
* Also the right reading at an adapter's `!res.ok` throw site on BOTH
|
|
937
|
+
* transports: headers have arrived, no body will be streamed, so
|
|
938
|
+
* dispatch → now is the whole of what the provider cost us.
|
|
939
|
+
*/
|
|
940
|
+
chatTiming() {
|
|
941
|
+
const t = this.fetchTiming;
|
|
942
|
+
if (!t) return void 0;
|
|
943
|
+
return {
|
|
944
|
+
queuedMs: t.queuedMs,
|
|
945
|
+
attempts: t.attempts,
|
|
946
|
+
retryMs: t.retryMs,
|
|
947
|
+
ttfbMs: Math.max(0, t.headersAt - t.dispatchedAt),
|
|
948
|
+
wireMs: Math.max(0, Date.now() - t.dispatchedAt)
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
/** Timing for a streamed call. Call when building the terminal `done` chunk. */
|
|
952
|
+
streamTiming() {
|
|
953
|
+
const t = this.fetchTiming;
|
|
954
|
+
if (!t) return void 0;
|
|
955
|
+
const ttfbMs = Math.max(0, t.headersAt - t.dispatchedAt);
|
|
956
|
+
const firstTokenMs = this.firstTokenAt !== void 0 ? Math.max(0, this.firstTokenAt - t.dispatchedAt - this.preFirstTokenConsumerMs) : void 0;
|
|
957
|
+
return {
|
|
958
|
+
queuedMs: t.queuedMs,
|
|
959
|
+
attempts: t.attempts,
|
|
960
|
+
retryMs: t.retryMs,
|
|
961
|
+
ttfbMs,
|
|
962
|
+
...firstTokenMs !== void 0 ? { firstTokenMs } : {},
|
|
963
|
+
// Parsing and generator scheduling can happen after the first read has
|
|
964
|
+
// settled. Include that pre-first-token gap so the public relationship
|
|
965
|
+
// stays intuitive, without charging pauses after any chunk is yielded.
|
|
966
|
+
wireMs: Math.max(ttfbMs + this.readWaitMs, firstTokenMs ?? 0)
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
async function* withCallTiming(recorder, source) {
|
|
971
|
+
for await (const chunk of source) {
|
|
972
|
+
if (chunk.type === "text_delta" || chunk.type === "thinking_delta") {
|
|
973
|
+
recorder.markFirstToken();
|
|
974
|
+
yield chunk;
|
|
975
|
+
} else if (chunk.type === "done") {
|
|
976
|
+
const timing = recorder.streamTiming();
|
|
977
|
+
yield timing ? { ...chunk, timing } : chunk;
|
|
978
|
+
} else {
|
|
979
|
+
const yieldedAt = Date.now();
|
|
980
|
+
yield chunk;
|
|
981
|
+
recorder.recordPreFirstTokenConsumerPause(yieldedAt);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
851
984
|
}
|
|
985
|
+
function withChatTiming(recorder, response) {
|
|
986
|
+
const timing = recorder.chatTiming();
|
|
987
|
+
return timing ? { ...response, timing } : response;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/providers/rate-limiter.ts
|
|
852
991
|
var RateLimiter = class {
|
|
853
992
|
maxConcurrent;
|
|
854
993
|
minIntervalMs;
|
|
@@ -897,11 +1036,11 @@ var RateLimiter = class {
|
|
|
897
1036
|
* With `acquireTimeoutMs`, a waiter that sits in the queue too long rejects.
|
|
898
1037
|
*/
|
|
899
1038
|
acquire(signal) {
|
|
900
|
-
if (signal?.aborted) return Promise.reject(
|
|
1039
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
901
1040
|
return new Promise((resolve2, reject) => {
|
|
902
1041
|
const waiter = { resolve: resolve2, reject, signal };
|
|
903
1042
|
if (signal) {
|
|
904
|
-
waiter.onAbort = () => this.settleWaiter(waiter,
|
|
1043
|
+
waiter.onAbort = () => this.settleWaiter(waiter, signal.reason);
|
|
905
1044
|
signal.addEventListener("abort", waiter.onAbort, { once: true });
|
|
906
1045
|
}
|
|
907
1046
|
if (this.acquireTimeoutMs != null) {
|
|
@@ -1201,6 +1340,7 @@ function normalizeMessageContent(content) {
|
|
|
1201
1340
|
}
|
|
1202
1341
|
var OpenAICompatibleProvider = class {
|
|
1203
1342
|
name;
|
|
1343
|
+
reportsRequestLifecycle = true;
|
|
1204
1344
|
inputCapabilities(model) {
|
|
1205
1345
|
return this.name === "openrouter" && model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64"] } } : {};
|
|
1206
1346
|
}
|
|
@@ -1297,6 +1437,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1297
1437
|
async chat(messages, options) {
|
|
1298
1438
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
1299
1439
|
const body = this.buildRequestBody(messages, options, false);
|
|
1440
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
1300
1441
|
const res = await fetchWithRetry(
|
|
1301
1442
|
`${this.baseUrl}/chat/completions`,
|
|
1302
1443
|
{
|
|
@@ -1305,7 +1446,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1305
1446
|
body: JSON.stringify(body),
|
|
1306
1447
|
signal: options.signal
|
|
1307
1448
|
},
|
|
1308
|
-
{ governor: this.governor, provider: this.name }
|
|
1449
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
1309
1450
|
);
|
|
1310
1451
|
if (!res.ok) {
|
|
1311
1452
|
const errorBody = await res.text();
|
|
@@ -1314,15 +1455,20 @@ var OpenAICompatibleProvider = class {
|
|
|
1314
1455
|
status: res.status,
|
|
1315
1456
|
headers: res.headers,
|
|
1316
1457
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
1317
|
-
body: errorBody
|
|
1458
|
+
body: errorBody,
|
|
1459
|
+
timing: recorder.chatTiming()
|
|
1318
1460
|
});
|
|
1319
1461
|
}
|
|
1320
1462
|
const json = await res.json();
|
|
1321
|
-
return
|
|
1463
|
+
return withChatTiming(
|
|
1464
|
+
recorder,
|
|
1465
|
+
this.parseResponse(json, this.requestModel(body, options.model), body)
|
|
1466
|
+
);
|
|
1322
1467
|
}
|
|
1323
1468
|
async *stream(messages, options) {
|
|
1324
1469
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
1325
1470
|
const body = this.buildRequestBody(messages, options, true);
|
|
1471
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
1326
1472
|
const res = await fetchWithRetry(
|
|
1327
1473
|
`${this.baseUrl}/chat/completions`,
|
|
1328
1474
|
{
|
|
@@ -1331,7 +1477,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1331
1477
|
body: JSON.stringify(body),
|
|
1332
1478
|
signal: options.signal
|
|
1333
1479
|
},
|
|
1334
|
-
{ governor: this.governor, provider: this.name }
|
|
1480
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
1335
1481
|
);
|
|
1336
1482
|
if (!res.ok) {
|
|
1337
1483
|
const errorBody = await res.text();
|
|
@@ -1340,13 +1486,17 @@ var OpenAICompatibleProvider = class {
|
|
|
1340
1486
|
status: res.status,
|
|
1341
1487
|
headers: res.headers,
|
|
1342
1488
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
1343
|
-
body: errorBody
|
|
1489
|
+
body: errorBody,
|
|
1490
|
+
timing: recorder.chatTiming()
|
|
1344
1491
|
});
|
|
1345
1492
|
}
|
|
1346
1493
|
if (!res.body) {
|
|
1347
1494
|
throw new Error(`${this.profile.label ?? this.profile.name} stream response has no body`);
|
|
1348
1495
|
}
|
|
1349
|
-
yield*
|
|
1496
|
+
yield* withCallTiming(
|
|
1497
|
+
recorder,
|
|
1498
|
+
this.parseSSEStream(res.body, this.requestModel(body, options.model), body, recorder)
|
|
1499
|
+
);
|
|
1350
1500
|
}
|
|
1351
1501
|
// ---------------------------------------------------------------------------
|
|
1352
1502
|
// Request building
|
|
@@ -1573,7 +1723,21 @@ var OpenAICompatibleProvider = class {
|
|
|
1573
1723
|
// ---------------------------------------------------------------------------
|
|
1574
1724
|
// SSE streaming
|
|
1575
1725
|
// ---------------------------------------------------------------------------
|
|
1576
|
-
|
|
1726
|
+
/**
|
|
1727
|
+
* PRIVATE, and deliberately not an extension point. It was `protected`, but
|
|
1728
|
+
* nothing in or out of this repo overrides it, and `protected` could not
|
|
1729
|
+
* carry the guarantee it appeared to: TypeScript checks an override for
|
|
1730
|
+
* assignability, and a function type with fewer parameters is assignable to
|
|
1731
|
+
* one with more — so an override that simply dropped the recorder still
|
|
1732
|
+
* compiled and then reported `wireMs === ttfbMs` for the whole subclass. Its
|
|
1733
|
+
* type is not exported from the barrel either, so a subclass author could not
|
|
1734
|
+
* name what they were required to accept.
|
|
1735
|
+
*
|
|
1736
|
+
* A provider with a different wire format implements `Provider` (see
|
|
1737
|
+
* docs/providers.md); an OpenAI-compatible one adds a `ProviderProfile`.
|
|
1738
|
+
* Neither needs to reach in here.
|
|
1739
|
+
*/
|
|
1740
|
+
async *parseSSEStream(body, model, request, timing) {
|
|
1577
1741
|
const reader = body.getReader();
|
|
1578
1742
|
const decoder = new TextDecoder();
|
|
1579
1743
|
let buffer = "";
|
|
@@ -1633,7 +1797,7 @@ var OpenAICompatibleProvider = class {
|
|
|
1633
1797
|
};
|
|
1634
1798
|
try {
|
|
1635
1799
|
for (; ; ) {
|
|
1636
|
-
const { done, value } = await
|
|
1800
|
+
const { done, value } = await timing.read(reader);
|
|
1637
1801
|
if (done) break;
|
|
1638
1802
|
buffer += decoder.decode(value, { stream: true });
|
|
1639
1803
|
const lines = buffer.split("\n");
|
|
@@ -1665,7 +1829,8 @@ var OpenAICompatibleProvider = class {
|
|
|
1665
1829
|
status: 0,
|
|
1666
1830
|
retryable: false,
|
|
1667
1831
|
message,
|
|
1668
|
-
body: JSON.stringify(parsed.error)
|
|
1832
|
+
body: JSON.stringify(parsed.error),
|
|
1833
|
+
timing: timing.streamTiming()
|
|
1669
1834
|
});
|
|
1670
1835
|
}
|
|
1671
1836
|
if (parsed.model !== void 0 || parsed.service_tier !== void 0 || parsed.serviceTier !== void 0) {
|
|
@@ -1737,7 +1902,8 @@ var OpenAICompatibleProvider = class {
|
|
|
1737
1902
|
provider: this.name,
|
|
1738
1903
|
status: 0,
|
|
1739
1904
|
retryable: true,
|
|
1740
|
-
message: `${this.profile.label ?? this.profile.name} stream ended before [DONE]
|
|
1905
|
+
message: `${this.profile.label ?? this.profile.name} stream ended before [DONE]`,
|
|
1906
|
+
timing: timing.streamTiming()
|
|
1741
1907
|
});
|
|
1742
1908
|
} finally {
|
|
1743
1909
|
reader.releaseLock();
|
|
@@ -1776,12 +1942,12 @@ var DIRECT_OPENAI_CATALOG = [
|
|
|
1776
1942
|
{
|
|
1777
1943
|
aliases: ["gpt-5.6", "gpt-5.6-sol"],
|
|
1778
1944
|
snapshotBase: "gpt-5.6-sol",
|
|
1779
|
-
short: { input:
|
|
1945
|
+
short: { input: 4 / M, cachedInput: 0.4 / M, cacheWrite: 5 / M, output: 20 / M },
|
|
1780
1946
|
long: withLongContext({
|
|
1781
|
-
input:
|
|
1782
|
-
cachedInput: 0.
|
|
1783
|
-
cacheWrite:
|
|
1784
|
-
output:
|
|
1947
|
+
input: 4 / M,
|
|
1948
|
+
cachedInput: 0.4 / M,
|
|
1949
|
+
cacheWrite: 5 / M,
|
|
1950
|
+
output: 20 / M
|
|
1785
1951
|
}),
|
|
1786
1952
|
contextBoundary: LONG_CONTEXT_BOUNDARY
|
|
1787
1953
|
},
|
|
@@ -2210,6 +2376,7 @@ function responseImageParts(parts) {
|
|
|
2210
2376
|
}
|
|
2211
2377
|
var OpenAIResponsesProvider = class {
|
|
2212
2378
|
name = "openai-responses";
|
|
2379
|
+
reportsRequestLifecycle = true;
|
|
2213
2380
|
inputCapabilities(model) {
|
|
2214
2381
|
return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
2215
2382
|
}
|
|
@@ -2296,6 +2463,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2296
2463
|
async chat(messages, options) {
|
|
2297
2464
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
2298
2465
|
const body = this.buildRequestBody(messages, options, false);
|
|
2466
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
2299
2467
|
const res = await fetchWithRetry(
|
|
2300
2468
|
`${this.baseUrl}/responses`,
|
|
2301
2469
|
{
|
|
@@ -2304,7 +2472,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2304
2472
|
body: JSON.stringify(body),
|
|
2305
2473
|
signal: options.signal
|
|
2306
2474
|
},
|
|
2307
|
-
{ governor: this.governor, provider: this.name }
|
|
2475
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
2308
2476
|
);
|
|
2309
2477
|
if (!res.ok) {
|
|
2310
2478
|
const errorBody = await res.text();
|
|
@@ -2314,11 +2482,15 @@ var OpenAIResponsesProvider = class {
|
|
|
2314
2482
|
status: res.status,
|
|
2315
2483
|
headers: res.headers,
|
|
2316
2484
|
message,
|
|
2317
|
-
body: errorBody
|
|
2485
|
+
body: errorBody,
|
|
2486
|
+
timing: recorder.chatTiming()
|
|
2318
2487
|
});
|
|
2319
2488
|
}
|
|
2320
2489
|
const json = await res.json();
|
|
2321
|
-
return
|
|
2490
|
+
return withChatTiming(
|
|
2491
|
+
recorder,
|
|
2492
|
+
this.parseResponse(json, this.requestModel(body, options.model), body)
|
|
2493
|
+
);
|
|
2322
2494
|
}
|
|
2323
2495
|
// ---------------------------------------------------------------------------
|
|
2324
2496
|
// stream - SSE streaming
|
|
@@ -2326,6 +2498,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2326
2498
|
async *stream(messages, options) {
|
|
2327
2499
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
2328
2500
|
const body = this.buildRequestBody(messages, options, true);
|
|
2501
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
2329
2502
|
const res = await fetchWithRetry(
|
|
2330
2503
|
`${this.baseUrl}/responses`,
|
|
2331
2504
|
{
|
|
@@ -2334,7 +2507,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2334
2507
|
body: JSON.stringify(body),
|
|
2335
2508
|
signal: options.signal
|
|
2336
2509
|
},
|
|
2337
|
-
{ governor: this.governor, provider: this.name }
|
|
2510
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
2338
2511
|
);
|
|
2339
2512
|
if (!res.ok) {
|
|
2340
2513
|
const errorBody = await res.text();
|
|
@@ -2344,13 +2517,17 @@ var OpenAIResponsesProvider = class {
|
|
|
2344
2517
|
status: res.status,
|
|
2345
2518
|
headers: res.headers,
|
|
2346
2519
|
message,
|
|
2347
|
-
body: errorBody
|
|
2520
|
+
body: errorBody,
|
|
2521
|
+
timing: recorder.chatTiming()
|
|
2348
2522
|
});
|
|
2349
2523
|
}
|
|
2350
2524
|
if (!res.body) {
|
|
2351
2525
|
throw new Error("OpenAI Responses stream has no body");
|
|
2352
2526
|
}
|
|
2353
|
-
yield*
|
|
2527
|
+
yield* withCallTiming(
|
|
2528
|
+
recorder,
|
|
2529
|
+
this.parseSSEStream(res.body, this.requestModel(body, options.model), body, recorder)
|
|
2530
|
+
);
|
|
2354
2531
|
}
|
|
2355
2532
|
// ---------------------------------------------------------------------------
|
|
2356
2533
|
// Internal: build request body
|
|
@@ -2552,7 +2729,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2552
2729
|
// ---------------------------------------------------------------------------
|
|
2553
2730
|
// Internal: parse SSE stream
|
|
2554
2731
|
// ---------------------------------------------------------------------------
|
|
2555
|
-
async *parseSSEStream(body, model, request) {
|
|
2732
|
+
async *parseSSEStream(body, model, request, timing) {
|
|
2556
2733
|
const reader = body.getReader();
|
|
2557
2734
|
const decoder = new TextDecoder();
|
|
2558
2735
|
let buffer = "";
|
|
@@ -2560,7 +2737,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2560
2737
|
let eventType = "";
|
|
2561
2738
|
try {
|
|
2562
2739
|
while (true) {
|
|
2563
|
-
const { done, value } = await
|
|
2740
|
+
const { done, value } = await timing.read(reader);
|
|
2564
2741
|
if (done) break;
|
|
2565
2742
|
buffer += decoder.decode(value, { stream: true });
|
|
2566
2743
|
const lines = buffer.split("\n");
|
|
@@ -2580,7 +2757,14 @@ var OpenAIResponsesProvider = class {
|
|
|
2580
2757
|
} catch {
|
|
2581
2758
|
continue;
|
|
2582
2759
|
}
|
|
2583
|
-
const chunk = this.handleStreamEvent(
|
|
2760
|
+
const chunk = this.handleStreamEvent(
|
|
2761
|
+
eventType,
|
|
2762
|
+
data,
|
|
2763
|
+
model,
|
|
2764
|
+
callIdMap,
|
|
2765
|
+
request,
|
|
2766
|
+
timing
|
|
2767
|
+
);
|
|
2584
2768
|
if (chunk) {
|
|
2585
2769
|
yield chunk;
|
|
2586
2770
|
if (chunk.type === "done") return;
|
|
@@ -2594,7 +2778,7 @@ var OpenAIResponsesProvider = class {
|
|
|
2594
2778
|
reader.releaseLock();
|
|
2595
2779
|
}
|
|
2596
2780
|
}
|
|
2597
|
-
handleStreamEvent(eventType, data, model, callIdMap, request) {
|
|
2781
|
+
handleStreamEvent(eventType, data, model, callIdMap, request, timing) {
|
|
2598
2782
|
switch (eventType) {
|
|
2599
2783
|
case "response.output_text.delta":
|
|
2600
2784
|
return { type: "text_delta", content: data.delta ?? "" };
|
|
@@ -2650,7 +2834,8 @@ var OpenAIResponsesProvider = class {
|
|
|
2650
2834
|
provider: this.name,
|
|
2651
2835
|
status: 0,
|
|
2652
2836
|
retryable: false,
|
|
2653
|
-
message: `OpenAI Responses API error: ${errorMsg}
|
|
2837
|
+
message: `OpenAI Responses API error: ${errorMsg}`,
|
|
2838
|
+
timing: timing.streamTiming()
|
|
2654
2839
|
});
|
|
2655
2840
|
}
|
|
2656
2841
|
default:
|
|
@@ -2718,6 +2903,12 @@ function hasAnthropicProviderFileImage(messages) {
|
|
|
2718
2903
|
);
|
|
2719
2904
|
}
|
|
2720
2905
|
var CLAUDE_CAPABILITIES = {
|
|
2906
|
+
"claude-fable-5-1": {
|
|
2907
|
+
thinking: "adaptive-always-on",
|
|
2908
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
2909
|
+
manualBudget: false,
|
|
2910
|
+
stripTemperature: true
|
|
2911
|
+
},
|
|
2721
2912
|
"claude-fable-5": {
|
|
2722
2913
|
thinking: "adaptive-always-on",
|
|
2723
2914
|
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
@@ -2808,10 +2999,18 @@ var PRE_INFERENCE_GEO_MODELS = /* @__PURE__ */ new Set([
|
|
|
2808
2999
|
function resolveClaudeCapability(model) {
|
|
2809
3000
|
return CLAUDE_CAPABILITIES[model] ?? (LEGACY_CLAUDE_MODELS.has(model) ? LEGACY_CLAUDE_CAPABILITY : void 0);
|
|
2810
3001
|
}
|
|
3002
|
+
var DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
|
|
2811
3003
|
var ANTHROPIC_RATES = {
|
|
3004
|
+
"claude-fable-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
|
|
2812
3005
|
"claude-fable-5": { input: 1e-5, output: 5e-5 },
|
|
3006
|
+
// Limited availability (Glasswing). Priced from the public pricing table; no
|
|
3007
|
+
// CLAUDE_CAPABILITIES entry, because their thinking semantics are not
|
|
3008
|
+
// documented per-model and an unverified capability would send the wrong
|
|
3009
|
+
// request shape. Unknown IDs pass through unmodified, which is the safe side.
|
|
3010
|
+
"claude-mythos-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
|
|
3011
|
+
"claude-mythos-5": { input: 1e-5, output: 5e-5 },
|
|
2813
3012
|
"claude-opus-5": { input: 5e-6, output: 25e-6 },
|
|
2814
|
-
"claude-sonnet-5": { input:
|
|
3013
|
+
"claude-sonnet-5": { input: 2e-6, output: 1e-5 },
|
|
2815
3014
|
"claude-opus-4-8": { input: 5e-6, output: 25e-6 },
|
|
2816
3015
|
"claude-opus-4-7": { input: 5e-6, output: 25e-6 },
|
|
2817
3016
|
"claude-opus-4-6": { input: 5e-6, output: 25e-6 },
|
|
@@ -2850,7 +3049,7 @@ function anthropicStreamErrorStatus(type) {
|
|
|
2850
3049
|
return 0;
|
|
2851
3050
|
}
|
|
2852
3051
|
}
|
|
2853
|
-
function estimateAnthropicCost(model, usage
|
|
3052
|
+
function estimateAnthropicCost(model, usage) {
|
|
2854
3053
|
if (!model || !isValidTokenCount(usage.inputTokens) || !isValidTokenCount(usage.outputTokens)) {
|
|
2855
3054
|
return void 0;
|
|
2856
3055
|
}
|
|
@@ -2869,8 +3068,9 @@ function estimateAnthropicCost(model, usage, now = /* @__PURE__ */ new Date()) {
|
|
|
2869
3068
|
if (usage.aggregateCacheWriteTokens !== void 0 && (!isValidTokenCount(usage.aggregateCacheWriteTokens) || usage.aggregateCacheWriteTokens !== cacheWriteTotal)) {
|
|
2870
3069
|
return void 0;
|
|
2871
3070
|
}
|
|
2872
|
-
const { input, output } = rate
|
|
2873
|
-
|
|
3071
|
+
const { input, output } = rate;
|
|
3072
|
+
const cacheReadRate = input * (rate.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER);
|
|
3073
|
+
return usage.inputTokens * input + cacheRead * cacheReadRate + cacheWrite5m * input * 1.25 + cacheWrite1h * input * 2 + usage.outputTokens * output;
|
|
2874
3074
|
}
|
|
2875
3075
|
var THINKING_BUDGETS = {
|
|
2876
3076
|
low: 1024,
|
|
@@ -3102,6 +3302,7 @@ function mergeAnthropicUsage(initial, update) {
|
|
|
3102
3302
|
}
|
|
3103
3303
|
var AnthropicProvider = class {
|
|
3104
3304
|
name = "anthropic";
|
|
3305
|
+
reportsRequestLifecycle = true;
|
|
3105
3306
|
/** Report a clamped `effort`. Anthropic clamps to each model's supported
|
|
3106
3307
|
* `output_config.effort` levels, and models that always think fall back to
|
|
3107
3308
|
* adaptive `'low'` when asked to disable thinking. Only reported when the
|
|
@@ -3111,6 +3312,14 @@ var AnthropicProvider = class {
|
|
|
3111
3312
|
const resolved = resolveThinkingOptions(options);
|
|
3112
3313
|
if (resolved.effort === void 0 || resolved.hasBudgetOverride) return void 0;
|
|
3113
3314
|
const model = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
3315
|
+
if (ANTHROPIC_RATES[model] && !resolveClaudeCapability(model)) {
|
|
3316
|
+
return {
|
|
3317
|
+
requested: resolved.effort,
|
|
3318
|
+
effective: "unset",
|
|
3319
|
+
clamped: true,
|
|
3320
|
+
cause: `Axl has no capability entry for Anthropic ${model}, so no reasoning control was sent and effort '${resolved.effort}' had no effect`
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3114
3323
|
const config = resolveClaudeThinking(model, resolved);
|
|
3115
3324
|
const wireEffort = typeof config.outputConfig?.effort === "string" ? config.outputConfig.effort : void 0;
|
|
3116
3325
|
const effective = config.clamp?.effective ?? wireEffort;
|
|
@@ -3166,6 +3375,10 @@ var AnthropicProvider = class {
|
|
|
3166
3375
|
}
|
|
3167
3376
|
/** Anthropic ignores native `json_schema` structurally — Axl uses a system
|
|
3168
3377
|
* prompt JSON instruction + client-side Zod validation (see buildRequest). */
|
|
3378
|
+
/** The only built-in adapter that honors `promptCache` (see buildBody). */
|
|
3379
|
+
realizesPromptCache() {
|
|
3380
|
+
return true;
|
|
3381
|
+
}
|
|
3169
3382
|
nativeStructuredOutputSupport() {
|
|
3170
3383
|
return "unsupported";
|
|
3171
3384
|
}
|
|
@@ -3207,6 +3420,7 @@ var AnthropicProvider = class {
|
|
|
3207
3420
|
);
|
|
3208
3421
|
const body = this.buildRequestBody(messages, options, false);
|
|
3209
3422
|
const pricingContext = pricingContextFromBody(body);
|
|
3423
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
3210
3424
|
const res = await fetchWithRetry(
|
|
3211
3425
|
`${this.baseUrl}/messages`,
|
|
3212
3426
|
{
|
|
@@ -3215,7 +3429,7 @@ var AnthropicProvider = class {
|
|
|
3215
3429
|
body: JSON.stringify(body),
|
|
3216
3430
|
signal: options.signal
|
|
3217
3431
|
},
|
|
3218
|
-
{ governor: this.governor, provider: this.name }
|
|
3432
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
3219
3433
|
);
|
|
3220
3434
|
if (!res.ok) {
|
|
3221
3435
|
const errorBody = await res.text();
|
|
@@ -3225,11 +3439,12 @@ var AnthropicProvider = class {
|
|
|
3225
3439
|
status: res.status,
|
|
3226
3440
|
headers: res.headers,
|
|
3227
3441
|
message,
|
|
3228
|
-
body: errorBody
|
|
3442
|
+
body: errorBody,
|
|
3443
|
+
timing: recorder.chatTiming()
|
|
3229
3444
|
});
|
|
3230
3445
|
}
|
|
3231
3446
|
const json = await res.json();
|
|
3232
|
-
return this.parseResponse(json, pricingContext);
|
|
3447
|
+
return withChatTiming(recorder, this.parseResponse(json, pricingContext));
|
|
3233
3448
|
}
|
|
3234
3449
|
// ---------------------------------------------------------------------------
|
|
3235
3450
|
// stream - SSE streaming completion
|
|
@@ -3241,6 +3456,7 @@ var AnthropicProvider = class {
|
|
|
3241
3456
|
);
|
|
3242
3457
|
const body = this.buildRequestBody(messages, options, true);
|
|
3243
3458
|
const pricingContext = pricingContextFromBody(body);
|
|
3459
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
3244
3460
|
const res = await fetchWithRetry(
|
|
3245
3461
|
`${this.baseUrl}/messages`,
|
|
3246
3462
|
{
|
|
@@ -3249,7 +3465,7 @@ var AnthropicProvider = class {
|
|
|
3249
3465
|
body: JSON.stringify(body),
|
|
3250
3466
|
signal: options.signal
|
|
3251
3467
|
},
|
|
3252
|
-
{ governor: this.governor, provider: this.name }
|
|
3468
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
3253
3469
|
);
|
|
3254
3470
|
if (!res.ok) {
|
|
3255
3471
|
const errorBody = await res.text();
|
|
@@ -3259,13 +3475,17 @@ var AnthropicProvider = class {
|
|
|
3259
3475
|
status: res.status,
|
|
3260
3476
|
headers: res.headers,
|
|
3261
3477
|
message,
|
|
3262
|
-
body: errorBody
|
|
3478
|
+
body: errorBody,
|
|
3479
|
+
timing: recorder.chatTiming()
|
|
3263
3480
|
});
|
|
3264
3481
|
}
|
|
3265
3482
|
if (!res.body) {
|
|
3266
3483
|
throw new Error("Anthropic stream response has no body");
|
|
3267
3484
|
}
|
|
3268
|
-
yield*
|
|
3485
|
+
yield* withCallTiming(
|
|
3486
|
+
recorder,
|
|
3487
|
+
this.parseSSEStream(res.body, pricingContext, res.headers, recorder)
|
|
3488
|
+
);
|
|
3269
3489
|
}
|
|
3270
3490
|
// ---------------------------------------------------------------------------
|
|
3271
3491
|
// Internal: request building
|
|
@@ -3300,14 +3520,31 @@ var AnthropicProvider = class {
|
|
|
3300
3520
|
max_tokens: options.maxTokens ?? 4096,
|
|
3301
3521
|
stream
|
|
3302
3522
|
};
|
|
3303
|
-
|
|
3523
|
+
const po = options.providerOptions;
|
|
3524
|
+
const callerOwnsPrefix = po !== void 0 && (po.cache_control !== void 0 || po.system !== void 0 || po.tools !== void 0);
|
|
3525
|
+
const promptCache = options.promptCache === true && !callerOwnsPrefix;
|
|
3526
|
+
const anchorOnSystem = promptCache && systemMessages.length > 0 && systemMessages[0].origin !== "runtime";
|
|
3527
|
+
if (anchorOnSystem) {
|
|
3528
|
+
body.system = systemMessages.map((m, index) => ({
|
|
3529
|
+
type: "text",
|
|
3530
|
+
text: typeof m.content === "string" ? m.content : "",
|
|
3531
|
+
...index === 0 ? { cache_control: { type: "ephemeral" } } : {}
|
|
3532
|
+
}));
|
|
3533
|
+
} else if (systemText) {
|
|
3304
3534
|
body.system = systemText;
|
|
3305
3535
|
}
|
|
3306
3536
|
if (options.stop) {
|
|
3307
3537
|
body.stop_sequences = options.stop;
|
|
3308
3538
|
}
|
|
3309
3539
|
if (options.tools && options.tools.length > 0) {
|
|
3310
|
-
|
|
3540
|
+
const tools = options.tools.map((t) => this.mapToolDefinition(t));
|
|
3541
|
+
if (promptCache && !anchorOnSystem) {
|
|
3542
|
+
tools[tools.length - 1] = {
|
|
3543
|
+
...tools[tools.length - 1],
|
|
3544
|
+
cache_control: { type: "ephemeral" }
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
body.tools = tools;
|
|
3311
3548
|
}
|
|
3312
3549
|
if (options.toolChoice !== void 0) {
|
|
3313
3550
|
body.tool_choice = this.mapToolChoice(options.toolChoice);
|
|
@@ -3326,9 +3563,13 @@ var AnthropicProvider = class {
|
|
|
3326
3563
|
}
|
|
3327
3564
|
if (options.responseFormat && options.responseFormat.type !== "text") {
|
|
3328
3565
|
const jsonInstruction = "You must respond with valid JSON only. No markdown fences, no extra text.";
|
|
3329
|
-
|
|
3566
|
+
if (Array.isArray(body.system)) {
|
|
3567
|
+
body.system = [...body.system, { type: "text", text: jsonInstruction }];
|
|
3568
|
+
} else {
|
|
3569
|
+
body.system = body.system ? `${body.system}
|
|
3330
3570
|
|
|
3331
3571
|
${jsonInstruction}` : jsonInstruction;
|
|
3572
|
+
}
|
|
3332
3573
|
}
|
|
3333
3574
|
if (options.providerOptions) {
|
|
3334
3575
|
Object.assign(body, options.providerOptions);
|
|
@@ -3508,7 +3749,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3508
3749
|
// ---------------------------------------------------------------------------
|
|
3509
3750
|
// Internal: SSE stream parsing
|
|
3510
3751
|
// ---------------------------------------------------------------------------
|
|
3511
|
-
async *parseSSEStream(body, pricingContext, responseHeaders) {
|
|
3752
|
+
async *parseSSEStream(body, pricingContext, responseHeaders, timing) {
|
|
3512
3753
|
const reader = body.getReader();
|
|
3513
3754
|
const decoder = new TextDecoder();
|
|
3514
3755
|
let buffer = "";
|
|
@@ -3537,7 +3778,7 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3537
3778
|
});
|
|
3538
3779
|
try {
|
|
3539
3780
|
while (true) {
|
|
3540
|
-
const { done, value } = await
|
|
3781
|
+
const { done, value } = await timing.read(reader);
|
|
3541
3782
|
if (done) break;
|
|
3542
3783
|
buffer += decoder.decode(value, { stream: true });
|
|
3543
3784
|
const lines = buffer.split("\n");
|
|
@@ -3560,7 +3801,8 @@ ${jsonInstruction}` : jsonInstruction;
|
|
|
3560
3801
|
provider: this.name,
|
|
3561
3802
|
status,
|
|
3562
3803
|
headers: responseHeaders,
|
|
3563
|
-
message: event.error?.message ?? "Anthropic stream failed"
|
|
3804
|
+
message: event.error?.message ?? "Anthropic stream failed",
|
|
3805
|
+
timing: timing.streamTiming()
|
|
3564
3806
|
});
|
|
3565
3807
|
}
|
|
3566
3808
|
case "content_block_start": {
|
|
@@ -3796,7 +4038,6 @@ var GEMINI_PRICING = {
|
|
|
3796
4038
|
},
|
|
3797
4039
|
"gemini-2.5-flash": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3798
4040
|
"gemini-2.5-flash-lite": { input: 1e-7, cached: 1e-8, output: 4e-7 },
|
|
3799
|
-
"gemini-3-flash-preview": { input: 5e-7, cached: 5e-8, output: 3e-6 },
|
|
3800
4041
|
"gemini-3.1-pro-preview": {
|
|
3801
4042
|
input: 2e-6,
|
|
3802
4043
|
cached: 2e-7,
|
|
@@ -3812,7 +4053,9 @@ var GEMINI_PRICING = {
|
|
|
3812
4053
|
"gemini-3.1-flash-lite": { input: 25e-8, cached: 25e-9, output: 15e-7 },
|
|
3813
4054
|
"gemini-3.5-flash": { input: 15e-7, cached: 15e-8, output: 9e-6 },
|
|
3814
4055
|
"gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
|
|
3815
|
-
"gemini-3.6-flash": { input:
|
|
4056
|
+
"gemini-3.6-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 },
|
|
4057
|
+
"gemini-3.7-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 },
|
|
4058
|
+
"gemini-3.8-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 }
|
|
3816
4059
|
};
|
|
3817
4060
|
function isValidTokenCount2(value) {
|
|
3818
4061
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
@@ -4040,6 +4283,7 @@ function resolveGeminiThinking(model, resolved) {
|
|
|
4040
4283
|
}
|
|
4041
4284
|
var GeminiProvider = class {
|
|
4042
4285
|
name = "google";
|
|
4286
|
+
reportsRequestLifecycle = true;
|
|
4043
4287
|
inputCapabilities(model) {
|
|
4044
4288
|
return model.trim().length > 0 ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
|
|
4045
4289
|
}
|
|
@@ -4157,6 +4401,7 @@ var GeminiProvider = class {
|
|
|
4157
4401
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
4158
4402
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
4159
4403
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4404
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4160
4405
|
const res = await fetchWithRetry(
|
|
4161
4406
|
`${this.baseUrl}/models/${pricingContext.model}:generateContent`,
|
|
4162
4407
|
{
|
|
@@ -4165,7 +4410,7 @@ var GeminiProvider = class {
|
|
|
4165
4410
|
body: JSON.stringify(body),
|
|
4166
4411
|
signal: options.signal
|
|
4167
4412
|
},
|
|
4168
|
-
{ governor: this.governor, provider: this.name }
|
|
4413
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4169
4414
|
);
|
|
4170
4415
|
if (!res.ok) {
|
|
4171
4416
|
const errorBody = await res.text();
|
|
@@ -4175,11 +4420,12 @@ var GeminiProvider = class {
|
|
|
4175
4420
|
status: res.status,
|
|
4176
4421
|
headers: res.headers,
|
|
4177
4422
|
message,
|
|
4178
|
-
body: errorBody
|
|
4423
|
+
body: errorBody,
|
|
4424
|
+
timing: recorder.chatTiming()
|
|
4179
4425
|
});
|
|
4180
4426
|
}
|
|
4181
4427
|
const json = await res.json();
|
|
4182
|
-
return this.parseResponse(json, pricingContext);
|
|
4428
|
+
return withChatTiming(recorder, this.parseResponse(json, pricingContext));
|
|
4183
4429
|
}
|
|
4184
4430
|
// ---------------------------------------------------------------------------
|
|
4185
4431
|
// stream - SSE streaming completion
|
|
@@ -4193,6 +4439,7 @@ var GeminiProvider = class {
|
|
|
4193
4439
|
const pricingContext = this.pricingContext(this.requestModel(options), body);
|
|
4194
4440
|
this.assertSafeGemini3Continuation(messages, pricingContext.model);
|
|
4195
4441
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4442
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4196
4443
|
const res = await fetchWithRetry(
|
|
4197
4444
|
`${this.baseUrl}/models/${pricingContext.model}:streamGenerateContent?alt=sse`,
|
|
4198
4445
|
{
|
|
@@ -4201,7 +4448,7 @@ var GeminiProvider = class {
|
|
|
4201
4448
|
body: JSON.stringify(body),
|
|
4202
4449
|
signal: options.signal
|
|
4203
4450
|
},
|
|
4204
|
-
{ governor: this.governor, provider: this.name }
|
|
4451
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4205
4452
|
);
|
|
4206
4453
|
if (!res.ok) {
|
|
4207
4454
|
const errorBody = await res.text();
|
|
@@ -4211,13 +4458,14 @@ var GeminiProvider = class {
|
|
|
4211
4458
|
status: res.status,
|
|
4212
4459
|
headers: res.headers,
|
|
4213
4460
|
message,
|
|
4214
|
-
body: errorBody
|
|
4461
|
+
body: errorBody,
|
|
4462
|
+
timing: recorder.chatTiming()
|
|
4215
4463
|
});
|
|
4216
4464
|
}
|
|
4217
4465
|
if (!res.body) {
|
|
4218
4466
|
throw new Error("Gemini stream response has no body");
|
|
4219
4467
|
}
|
|
4220
|
-
yield* this.parseSSEStream(res.body, pricingContext);
|
|
4468
|
+
yield* withCallTiming(recorder, this.parseSSEStream(res.body, pricingContext, recorder));
|
|
4221
4469
|
}
|
|
4222
4470
|
// ---------------------------------------------------------------------------
|
|
4223
4471
|
// Rich image requests use Gemini Interactions. It is deliberately stateless:
|
|
@@ -4228,10 +4476,11 @@ var GeminiProvider = class {
|
|
|
4228
4476
|
this.assertSafeInteractionOptions(options);
|
|
4229
4477
|
const body = this.buildInteractionRequestBody(messages, options, false);
|
|
4230
4478
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4479
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4231
4480
|
const res = await fetchWithRetry(
|
|
4232
4481
|
`${this.baseUrl}/interactions`,
|
|
4233
4482
|
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4234
|
-
{ governor: this.governor, provider: this.name }
|
|
4483
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4235
4484
|
);
|
|
4236
4485
|
if (!res.ok) {
|
|
4237
4486
|
const errorBody = await res.text();
|
|
@@ -4240,19 +4489,24 @@ var GeminiProvider = class {
|
|
|
4240
4489
|
status: res.status,
|
|
4241
4490
|
headers: res.headers,
|
|
4242
4491
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
4243
|
-
body: errorBody
|
|
4492
|
+
body: errorBody,
|
|
4493
|
+
timing: recorder.chatTiming()
|
|
4244
4494
|
});
|
|
4245
4495
|
}
|
|
4246
|
-
return
|
|
4496
|
+
return withChatTiming(
|
|
4497
|
+
recorder,
|
|
4498
|
+
this.parseInteractionResponse(await res.json())
|
|
4499
|
+
);
|
|
4247
4500
|
}
|
|
4248
4501
|
async *streamInteraction(messages, options) {
|
|
4249
4502
|
this.assertSafeInteractionOptions(options);
|
|
4250
4503
|
const body = this.buildInteractionRequestBody(messages, options, true);
|
|
4251
4504
|
const headers = this.buildHeaders(await this.resolveKey());
|
|
4505
|
+
const recorder = new CallTimingRecorder(options.requestLifecycle);
|
|
4252
4506
|
const res = await fetchWithRetry(
|
|
4253
4507
|
`${this.baseUrl}/interactions?alt=sse`,
|
|
4254
4508
|
{ method: "POST", headers, body: JSON.stringify(body), signal: options.signal },
|
|
4255
|
-
{ governor: this.governor, provider: this.name }
|
|
4509
|
+
{ governor: this.governor, provider: this.name, timing: recorder.observer }
|
|
4256
4510
|
);
|
|
4257
4511
|
if (!res.ok) {
|
|
4258
4512
|
const errorBody = await res.text();
|
|
@@ -4261,11 +4515,15 @@ var GeminiProvider = class {
|
|
|
4261
4515
|
status: res.status,
|
|
4262
4516
|
headers: res.headers,
|
|
4263
4517
|
message: this.extractErrorMessage(errorBody, res.status),
|
|
4264
|
-
body: errorBody
|
|
4518
|
+
body: errorBody,
|
|
4519
|
+
timing: recorder.chatTiming()
|
|
4265
4520
|
});
|
|
4266
4521
|
}
|
|
4267
4522
|
if (!res.body) throw new Error("Gemini Interactions stream has no body");
|
|
4268
|
-
yield*
|
|
4523
|
+
yield* withCallTiming(
|
|
4524
|
+
recorder,
|
|
4525
|
+
this.parseInteractionSSEStream(res.body, res.headers, recorder)
|
|
4526
|
+
);
|
|
4269
4527
|
}
|
|
4270
4528
|
buildInteractionRequestBody(messages, options, stream) {
|
|
4271
4529
|
const model = this.requestModel(options);
|
|
@@ -4421,7 +4679,7 @@ var GeminiProvider = class {
|
|
|
4421
4679
|
providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
|
|
4422
4680
|
};
|
|
4423
4681
|
}
|
|
4424
|
-
async *parseInteractionSSEStream(body, responseHeaders) {
|
|
4682
|
+
async *parseInteractionSSEStream(body, responseHeaders, timing) {
|
|
4425
4683
|
const reader = body.getReader();
|
|
4426
4684
|
const decoder = new TextDecoder();
|
|
4427
4685
|
let buffer = "";
|
|
@@ -4431,7 +4689,7 @@ var GeminiProvider = class {
|
|
|
4431
4689
|
let completed = false;
|
|
4432
4690
|
try {
|
|
4433
4691
|
while (true) {
|
|
4434
|
-
const { done, value } = await
|
|
4692
|
+
const { done, value } = await timing.read(reader);
|
|
4435
4693
|
if (done) break;
|
|
4436
4694
|
buffer += decoder.decode(value, { stream: true });
|
|
4437
4695
|
const lines = buffer.split("\n");
|
|
@@ -4454,14 +4712,16 @@ var GeminiProvider = class {
|
|
|
4454
4712
|
provider: this.name,
|
|
4455
4713
|
status,
|
|
4456
4714
|
headers: responseHeaders,
|
|
4457
|
-
message
|
|
4715
|
+
message,
|
|
4716
|
+
timing: timing.streamTiming()
|
|
4458
4717
|
});
|
|
4459
4718
|
}
|
|
4460
4719
|
throw new ProviderError({
|
|
4461
4720
|
provider: this.name,
|
|
4462
4721
|
status: 0,
|
|
4463
4722
|
retryable: false,
|
|
4464
|
-
message
|
|
4723
|
+
message,
|
|
4724
|
+
timing: timing.streamTiming()
|
|
4465
4725
|
});
|
|
4466
4726
|
}
|
|
4467
4727
|
if (event.event_type === "step.start" && event.step) {
|
|
@@ -4939,7 +5199,7 @@ var GeminiProvider = class {
|
|
|
4939
5199
|
// ---------------------------------------------------------------------------
|
|
4940
5200
|
// Internal: SSE stream parsing
|
|
4941
5201
|
// ---------------------------------------------------------------------------
|
|
4942
|
-
async *parseSSEStream(body, pricingContext) {
|
|
5202
|
+
async *parseSSEStream(body, pricingContext, timing) {
|
|
4943
5203
|
const reader = body.getReader();
|
|
4944
5204
|
const decoder = new TextDecoder();
|
|
4945
5205
|
let buffer = "";
|
|
@@ -4952,7 +5212,7 @@ var GeminiProvider = class {
|
|
|
4952
5212
|
const accumulatedParts = [];
|
|
4953
5213
|
try {
|
|
4954
5214
|
while (true) {
|
|
4955
|
-
const { done, value } = await
|
|
5215
|
+
const { done, value } = await timing.read(reader);
|
|
4956
5216
|
if (done) break;
|
|
4957
5217
|
buffer += decoder.decode(value, { stream: true });
|
|
4958
5218
|
const lines = buffer.split("\n");
|
|
@@ -5961,7 +6221,7 @@ var GEMINI_AUDIO_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
|
5961
6221
|
"audio/mulaw",
|
|
5962
6222
|
"audio/webm"
|
|
5963
6223
|
]);
|
|
5964
|
-
function
|
|
6224
|
+
function abortError() {
|
|
5965
6225
|
return new DOMException("The transcription operation was aborted", "AbortError");
|
|
5966
6226
|
}
|
|
5967
6227
|
function audioMime(audio) {
|
|
@@ -5993,13 +6253,13 @@ function secondsOffset(value) {
|
|
|
5993
6253
|
function sleep3(ms, signal) {
|
|
5994
6254
|
return new Promise((resolve2, reject) => {
|
|
5995
6255
|
if (signal?.aborted) {
|
|
5996
|
-
reject(
|
|
6256
|
+
reject(abortError());
|
|
5997
6257
|
return;
|
|
5998
6258
|
}
|
|
5999
6259
|
const timer = setTimeout(done, ms);
|
|
6000
6260
|
const onAbort = () => {
|
|
6001
6261
|
clearTimeout(timer);
|
|
6002
|
-
done(
|
|
6262
|
+
done(abortError());
|
|
6003
6263
|
};
|
|
6004
6264
|
function done(error) {
|
|
6005
6265
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -7320,7 +7580,7 @@ function isCostBearingLeaf(event) {
|
|
|
7320
7580
|
return COST_LEAF_SET.has(event.type);
|
|
7321
7581
|
}
|
|
7322
7582
|
function isUnpricedLeaf(event) {
|
|
7323
|
-
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && hasPositiveBillableWork(event);
|
|
7583
|
+
return event.type !== void 0 && COST_LEAF_SET.has(event.type) && !isUsableCost(event.cost) && (event.unpriced === true || hasPositiveBillableWork(event));
|
|
7324
7584
|
}
|
|
7325
7585
|
function isRootLevel(event) {
|
|
7326
7586
|
const d = event.depth;
|
|
@@ -8606,18 +8866,27 @@ function parseDuration(duration) {
|
|
|
8606
8866
|
if (!match) throw new Error(`Invalid duration format: "${duration}"`);
|
|
8607
8867
|
const value = parseFloat(match[1]);
|
|
8608
8868
|
const unit = match[2];
|
|
8869
|
+
let milliseconds;
|
|
8609
8870
|
switch (unit) {
|
|
8610
8871
|
case "ms":
|
|
8611
|
-
|
|
8872
|
+
milliseconds = value;
|
|
8873
|
+
break;
|
|
8612
8874
|
case "s":
|
|
8613
|
-
|
|
8875
|
+
milliseconds = value * 1e3;
|
|
8876
|
+
break;
|
|
8614
8877
|
case "m":
|
|
8615
|
-
|
|
8878
|
+
milliseconds = value * 6e4;
|
|
8879
|
+
break;
|
|
8616
8880
|
case "h":
|
|
8617
|
-
|
|
8881
|
+
milliseconds = value * 36e5;
|
|
8882
|
+
break;
|
|
8618
8883
|
default:
|
|
8619
8884
|
throw new Error(`Unknown duration unit: "${unit}"`);
|
|
8620
8885
|
}
|
|
8886
|
+
if (!Number.isFinite(milliseconds)) {
|
|
8887
|
+
throw new Error(`Duration is too large: "${duration}"`);
|
|
8888
|
+
}
|
|
8889
|
+
return milliseconds;
|
|
8621
8890
|
}
|
|
8622
8891
|
function parseCost(cost) {
|
|
8623
8892
|
const match = cost.match(/^\$?([\d.]+)$/);
|
|
@@ -9273,6 +9542,42 @@ function recordToolSpanOutcome(span, outcome) {
|
|
|
9273
9542
|
|
|
9274
9543
|
// src/context.ts
|
|
9275
9544
|
var signalStorage = new AsyncLocalStorage();
|
|
9545
|
+
var hardSignalStorage = new AsyncLocalStorage();
|
|
9546
|
+
var timeoutTrackerStorage = new AsyncLocalStorage();
|
|
9547
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
9548
|
+
function composeAbortSignals(...signals) {
|
|
9549
|
+
const active = signals.filter((signal) => signal !== void 0);
|
|
9550
|
+
if (active.length === 0) return void 0;
|
|
9551
|
+
if (active.length === 1) return active[0];
|
|
9552
|
+
return AbortSignal.any(active);
|
|
9553
|
+
}
|
|
9554
|
+
function raceWithAbortSignal(promise, signal) {
|
|
9555
|
+
if (!signal) return promise;
|
|
9556
|
+
if (signal.aborted) {
|
|
9557
|
+
void promise.catch(() => {
|
|
9558
|
+
});
|
|
9559
|
+
return Promise.reject(signal.reason);
|
|
9560
|
+
}
|
|
9561
|
+
return new Promise((resolve2, reject) => {
|
|
9562
|
+
let settled = false;
|
|
9563
|
+
const finish = (callback) => {
|
|
9564
|
+
if (settled) return;
|
|
9565
|
+
settled = true;
|
|
9566
|
+
signal.removeEventListener("abort", onAbort);
|
|
9567
|
+
callback();
|
|
9568
|
+
};
|
|
9569
|
+
const onAbort = () => finish(() => reject(signal.reason));
|
|
9570
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
9571
|
+
if (signal.aborted) onAbort();
|
|
9572
|
+
promise.then(
|
|
9573
|
+
(value) => finish(() => resolve2(value)),
|
|
9574
|
+
(error) => finish(() => reject(error))
|
|
9575
|
+
);
|
|
9576
|
+
});
|
|
9577
|
+
}
|
|
9578
|
+
function pausedHumanWaitMs(tracker, now = Date.now()) {
|
|
9579
|
+
return tracker.humanWaitMs + (tracker.activeHumanWaits > 0 && tracker.humanWaitStartedAt !== void 0 ? now - tracker.humanWaitStartedAt : 0);
|
|
9580
|
+
}
|
|
9276
9581
|
var DEFAULT_BRANCH_DRAIN_TIMEOUT_MS = 5e3;
|
|
9277
9582
|
function resolveBranchDrainTimeoutMs(value) {
|
|
9278
9583
|
const resolved = value === void 0 ? DEFAULT_BRANCH_DRAIN_TIMEOUT_MS : value;
|
|
@@ -9462,6 +9767,13 @@ function describeValue(value) {
|
|
|
9462
9767
|
}
|
|
9463
9768
|
return `a value of type ${typeof value}`;
|
|
9464
9769
|
}
|
|
9770
|
+
var PROMPT_CACHE_COLD_WRITE_WARN_AFTER = 2;
|
|
9771
|
+
function nextUserTurn(history, from) {
|
|
9772
|
+
for (let i = Math.max(0, from); i < history.length; i++) {
|
|
9773
|
+
if (history[i].role === "user") return i;
|
|
9774
|
+
}
|
|
9775
|
+
return void 0;
|
|
9776
|
+
}
|
|
9465
9777
|
function estimateMessagesTokens(messages) {
|
|
9466
9778
|
let total = 0;
|
|
9467
9779
|
let unmeasured = false;
|
|
@@ -9601,6 +9913,13 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
9601
9913
|
workflowLifecycleState;
|
|
9602
9914
|
signal;
|
|
9603
9915
|
summaryCache;
|
|
9916
|
+
/** Consecutive provider calls with promptCache on that wrote to the cache
|
|
9917
|
+
* without reading from it (prefix changes per call), and consecutive calls
|
|
9918
|
+
* with no cache activity at all (prefix below the model's minimum, which
|
|
9919
|
+
* Anthropic silently ignores). Either reports once per context. */
|
|
9920
|
+
promptCacheColdWrites = 0;
|
|
9921
|
+
promptCacheInactive = 0;
|
|
9922
|
+
promptCacheWarned = false;
|
|
9604
9923
|
workflowName;
|
|
9605
9924
|
mcpManager;
|
|
9606
9925
|
spanManager;
|
|
@@ -10060,7 +10379,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10060
10379
|
tool_call_id: parsed.callId
|
|
10061
10380
|
});
|
|
10062
10381
|
}
|
|
10063
|
-
if (settlement.abortError !== void 0)
|
|
10382
|
+
if (settlement.abortError !== void 0) {
|
|
10383
|
+
signal?.throwIfAborted();
|
|
10384
|
+
throw settlement.abortError;
|
|
10385
|
+
}
|
|
10064
10386
|
}
|
|
10065
10387
|
// ── ctx.ask() ─────────────────────────────────────────────────────────
|
|
10066
10388
|
async ask(agent2, prompt, options) {
|
|
@@ -10076,7 +10398,10 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10076
10398
|
const promptText = inputText(normalizedInput);
|
|
10077
10399
|
const inputDescriptor = describeModelInput(normalizedInput);
|
|
10078
10400
|
const agentName = agent2._name;
|
|
10079
|
-
|
|
10401
|
+
const inheritedHardSignal = hardSignalStorage.getStore() ?? this.signal;
|
|
10402
|
+
const hardAskSignal = composeAbortSignals(inheritedHardSignal, options?.signal);
|
|
10403
|
+
const askSignal = composeAbortSignals(this.currentSignal, options?.signal);
|
|
10404
|
+
const runAsk = () => this._checkpoint(
|
|
10080
10405
|
this._autoCheckpointName("ask", agentName),
|
|
10081
10406
|
async () => {
|
|
10082
10407
|
const parentFrame = askStorage.getStore();
|
|
@@ -10133,6 +10458,17 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10133
10458
|
const costAfter2 = this.budgetContext?.totalCost ?? 0;
|
|
10134
10459
|
span.setAttribute("axl.agent.cost", costAfter2 - costBefore);
|
|
10135
10460
|
span.setAttribute("axl.agent.duration", Date.now() - askStart);
|
|
10461
|
+
if (usageCapture.timing) {
|
|
10462
|
+
const t = usageCapture.timing;
|
|
10463
|
+
span.setAttribute("axl.agent.queued_ms", t.queuedMs);
|
|
10464
|
+
span.setAttribute("axl.agent.retry_ms", t.retryMs);
|
|
10465
|
+
span.setAttribute("axl.agent.attempts", t.attempts);
|
|
10466
|
+
span.setAttribute("axl.agent.ttfb_ms", t.ttfbMs);
|
|
10467
|
+
span.setAttribute("axl.agent.wire_ms", t.wireMs);
|
|
10468
|
+
if (t.firstTokenMs !== void 0) {
|
|
10469
|
+
span.setAttribute("axl.agent.first_token_ms", t.firstTokenMs);
|
|
10470
|
+
}
|
|
10471
|
+
}
|
|
10136
10472
|
if (usageCapture.value) {
|
|
10137
10473
|
span.setAttribute(
|
|
10138
10474
|
"axl.agent.prompt_tokens",
|
|
@@ -10220,8 +10556,25 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10220
10556
|
},
|
|
10221
10557
|
{ agent: agentName }
|
|
10222
10558
|
);
|
|
10559
|
+
const runWithAskSignal = () => askSignal ? signalStorage.run(askSignal, runAsk) : runAsk();
|
|
10560
|
+
return hardAskSignal ? hardSignalStorage.run(hardAskSignal, runWithAskSignal) : runWithAskSignal();
|
|
10223
10561
|
}
|
|
10224
10562
|
async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
10563
|
+
const tracker = { humanWaitMs: 0, activeHumanWaits: 0 };
|
|
10564
|
+
const enclosing = timeoutTrackerStorage.getStore() ?? [];
|
|
10565
|
+
return timeoutTrackerStorage.run(
|
|
10566
|
+
[...enclosing, tracker],
|
|
10567
|
+
() => this.executeAgentCallImpl(
|
|
10568
|
+
agent2,
|
|
10569
|
+
input,
|
|
10570
|
+
options,
|
|
10571
|
+
handoffMessages,
|
|
10572
|
+
usageCapture,
|
|
10573
|
+
sessionHistory
|
|
10574
|
+
)
|
|
10575
|
+
);
|
|
10576
|
+
}
|
|
10577
|
+
async executeAgentCallImpl(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
|
|
10225
10578
|
if (this.budgetContext?.exceeded) {
|
|
10226
10579
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
10227
10580
|
if (policy === "warn") {
|
|
@@ -10340,6 +10693,14 @@ var WorkflowContext = class _WorkflowContext {
|
|
|
10340
10693
|
const toolTokens = toolDefs.length > 0 ? estimateTokens(JSON.stringify(toolDefs)) : 0;
|
|
10341
10694
|
const overhead = systemTokens + toolTokens + reserveTokens;
|
|
10342
10695
|
const availableForHistory = maxContext - overhead;
|
|
10696
|
+
if (availableForHistory <= 0) {
|
|
10697
|
+
this.emitEvent({
|
|
10698
|
+
type: "log",
|
|
10699
|
+
data: {
|
|
10700
|
+
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.`
|
|
10701
|
+
}
|
|
10702
|
+
});
|
|
10703
|
+
}
|
|
10343
10704
|
const historyEstimate = estimateMessagesTokens(sessionHistory);
|
|
10344
10705
|
if (historyEstimate.unmeasured) {
|
|
10345
10706
|
this.emitEvent({
|
|
@@ -10405,6 +10766,7 @@ ${appendedSchemaText}`;
|
|
|
10405
10766
|
if (handoffContext.length > 0) {
|
|
10406
10767
|
messages.push({
|
|
10407
10768
|
role: "system",
|
|
10769
|
+
origin: "runtime",
|
|
10408
10770
|
content: "The following is the conversation history from the previous agent that handed off to you:"
|
|
10409
10771
|
});
|
|
10410
10772
|
for (const msg of handoffContext) {
|
|
@@ -10452,8 +10814,19 @@ ${appendedSchemaText}`;
|
|
|
10452
10814
|
}
|
|
10453
10815
|
}
|
|
10454
10816
|
const maxTurns = agent2._config.maxTurns ?? 25;
|
|
10455
|
-
const timeoutMs = parseDuration(
|
|
10817
|
+
const timeoutMs = parseDuration(
|
|
10818
|
+
options?.timeout ?? agent2._config.timeout ?? this.config.defaults?.timeout ?? "60s"
|
|
10819
|
+
);
|
|
10820
|
+
const stallTimeout = options?.stallTimeout ?? agent2._config.stallTimeout ?? this.config.defaults?.stallTimeout;
|
|
10821
|
+
const stallTimeoutMs = stallTimeout === void 0 ? void 0 : parseDuration(stallTimeout);
|
|
10822
|
+
if (stallTimeoutMs !== void 0 && stallTimeoutMs > MAX_TIMER_DELAY_MS) {
|
|
10823
|
+
throw new RangeError(
|
|
10824
|
+
`stallTimeout exceeds the maximum supported timer duration of ${MAX_TIMER_DELAY_MS}ms`
|
|
10825
|
+
);
|
|
10826
|
+
}
|
|
10456
10827
|
const startTime = Date.now();
|
|
10828
|
+
const timeoutTracker = timeoutTrackerStorage.getStore()?.at(-1);
|
|
10829
|
+
const timingTotals = { turns: 0, queuedMs: 0, retryMs: 0, wireMs: 0 };
|
|
10457
10830
|
const currentMessages = [...messages];
|
|
10458
10831
|
let turns = 0;
|
|
10459
10832
|
let guardrailOutputRetries = 0;
|
|
@@ -10467,8 +10840,24 @@ ${appendedSchemaText}`;
|
|
|
10467
10840
|
let lastStartMaxAttempts = 1;
|
|
10468
10841
|
while (turns < maxTurns) {
|
|
10469
10842
|
this.currentSignal?.throwIfAborted();
|
|
10470
|
-
|
|
10471
|
-
|
|
10843
|
+
const now = Date.now();
|
|
10844
|
+
const elapsedMs = now - startTime - (timeoutTracker ? pausedHumanWaitMs(timeoutTracker, now) : 0);
|
|
10845
|
+
if (elapsedMs > timeoutMs) {
|
|
10846
|
+
throw new TimeoutError(
|
|
10847
|
+
"ctx.ask()",
|
|
10848
|
+
timeoutMs,
|
|
10849
|
+
timingTotals.turns > 0 ? {
|
|
10850
|
+
elapsedMs,
|
|
10851
|
+
queuedMs: timingTotals.queuedMs,
|
|
10852
|
+
retryMs: timingTotals.retryMs,
|
|
10853
|
+
wireMs: timingTotals.wireMs,
|
|
10854
|
+
otherMs: Math.max(
|
|
10855
|
+
0,
|
|
10856
|
+
elapsedMs - timingTotals.queuedMs - timingTotals.retryMs - timingTotals.wireMs
|
|
10857
|
+
)
|
|
10858
|
+
} : void 0,
|
|
10859
|
+
agent2._name
|
|
10860
|
+
);
|
|
10472
10861
|
}
|
|
10473
10862
|
turns++;
|
|
10474
10863
|
const isFirstTurn = turns === 1;
|
|
@@ -10508,11 +10897,49 @@ ${appendedSchemaText}`;
|
|
|
10508
10897
|
effort: options?.effort ?? agent2._config.effort,
|
|
10509
10898
|
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
10510
10899
|
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
10900
|
+
promptCache: options?.promptCache ?? agent2._config.promptCache,
|
|
10511
10901
|
toolChoice: options?.toolChoice ?? agent2._config.toolChoice,
|
|
10512
10902
|
stop: options?.stop ?? agent2._config.stop,
|
|
10513
10903
|
providerOptions,
|
|
10514
10904
|
signal: this.currentSignal
|
|
10515
10905
|
};
|
|
10906
|
+
const requestSignal = chatOptions.signal;
|
|
10907
|
+
const hardRequestSignal = hardSignalStorage.getStore() ?? this.signal;
|
|
10908
|
+
const stallController = stallTimeoutMs === void 0 ? void 0 : new AbortController();
|
|
10909
|
+
const stallReason = stallController === void 0 ? void 0 : new DOMException("Provider request stalled", "StallTimeout");
|
|
10910
|
+
let stallTimer;
|
|
10911
|
+
let providerCallActive = true;
|
|
10912
|
+
let requestDispatched = false;
|
|
10913
|
+
const clearStallTimer = () => {
|
|
10914
|
+
if (stallTimer !== void 0) {
|
|
10915
|
+
clearTimeout(stallTimer);
|
|
10916
|
+
stallTimer = void 0;
|
|
10917
|
+
}
|
|
10918
|
+
};
|
|
10919
|
+
const armStallTimer = () => {
|
|
10920
|
+
if (!providerCallActive || !stallController || !stallReason || stallTimeoutMs === void 0)
|
|
10921
|
+
return;
|
|
10922
|
+
requestDispatched = true;
|
|
10923
|
+
clearStallTimer();
|
|
10924
|
+
if (stallController.signal.aborted) return;
|
|
10925
|
+
stallTimer = setTimeout(() => stallController.abort(stallReason), stallTimeoutMs);
|
|
10926
|
+
};
|
|
10927
|
+
if (stallController) {
|
|
10928
|
+
chatOptions.signal = composeAbortSignals(requestSignal, stallController.signal);
|
|
10929
|
+
chatOptions.requestLifecycle = {
|
|
10930
|
+
onDispatch: armStallTimer,
|
|
10931
|
+
onRetry: clearStallTimer
|
|
10932
|
+
};
|
|
10933
|
+
}
|
|
10934
|
+
const providerSettlementSignal = composeAbortSignals(
|
|
10935
|
+
hardRequestSignal,
|
|
10936
|
+
stallController?.signal
|
|
10937
|
+
);
|
|
10938
|
+
const throwIfRequestStalled = () => {
|
|
10939
|
+
if (stallReason !== void 0 && providerSettlementSignal?.reason === stallReason) {
|
|
10940
|
+
throw new StallTimeoutError("ctx.ask()", stallTimeoutMs, agent2._name);
|
|
10941
|
+
}
|
|
10942
|
+
};
|
|
10516
10943
|
if (options?.schema && toolDefs.length === 0) {
|
|
10517
10944
|
const nativeOut = options?.nativeStructuredOutput ?? agent2._config.nativeStructuredOutput ?? false;
|
|
10518
10945
|
chatOptions.responseFormat = nativeOut ? {
|
|
@@ -10585,6 +11012,7 @@ ${appendedSchemaText}`;
|
|
|
10585
11012
|
const toolCalls = [];
|
|
10586
11013
|
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
10587
11014
|
let streamProviderMetadata;
|
|
11015
|
+
let streamTiming;
|
|
10588
11016
|
let thinkingContent = "";
|
|
10589
11017
|
const streamingObjectEnabled = !!options?.schema && toolDefs.length === 0 && options.schema instanceof z.ZodObject;
|
|
10590
11018
|
const currentAttempt = schemaRetries + 1;
|
|
@@ -10598,54 +11026,77 @@ ${appendedSchemaText}`;
|
|
|
10598
11026
|
});
|
|
10599
11027
|
}
|
|
10600
11028
|
}) : void 0;
|
|
10601
|
-
|
|
11029
|
+
if (stallController && provider.reportsRequestLifecycle !== true) armStallTimer();
|
|
11030
|
+
const providerStream = provider.stream(
|
|
10602
11031
|
cloneMessagesForProvider(currentMessages),
|
|
10603
11032
|
chatOptions
|
|
10604
|
-
)
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
11033
|
+
);
|
|
11034
|
+
const iterator = providerStream[Symbol.asyncIterator]();
|
|
11035
|
+
try {
|
|
11036
|
+
while (true) {
|
|
11037
|
+
const next = await raceWithAbortSignal(
|
|
11038
|
+
Promise.resolve(iterator.next()),
|
|
11039
|
+
providerSettlementSignal
|
|
11040
|
+
);
|
|
11041
|
+
if (next.done) break;
|
|
11042
|
+
const chunk = next.value;
|
|
11043
|
+
if (stallController) armStallTimer();
|
|
11044
|
+
if (chunk.type === "text_delta") {
|
|
11045
|
+
content2 += chunk.content;
|
|
11046
|
+
this.emitEvent({ type: "token", data: chunk.content });
|
|
11047
|
+
if (walker) {
|
|
11048
|
+
walker.processChunk(chunk.content);
|
|
11049
|
+
if (walker.consumeBoundary()) {
|
|
11050
|
+
let parsed;
|
|
11051
|
+
try {
|
|
11052
|
+
parsed = parsePartialJson(extractJson(content2));
|
|
11053
|
+
} catch {
|
|
11054
|
+
parsed = void 0;
|
|
11055
|
+
}
|
|
11056
|
+
if (parsed !== void 0) {
|
|
11057
|
+
this.emitEvent({
|
|
11058
|
+
type: "partial_object",
|
|
11059
|
+
agent: agent2._name,
|
|
11060
|
+
attempt: currentAttempt,
|
|
11061
|
+
data: { object: parsed }
|
|
11062
|
+
});
|
|
11063
|
+
}
|
|
10624
11064
|
}
|
|
10625
11065
|
}
|
|
10626
|
-
}
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
|
|
10636
|
-
if (chunk.
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
if (chunk.usage) {
|
|
11066
|
+
} else if (chunk.type === "thinking_delta") {
|
|
11067
|
+
thinkingContent += chunk.content;
|
|
11068
|
+
} else if (chunk.type === "tool_call_delta") {
|
|
11069
|
+
let buffer = toolCallBuffers.get(chunk.id);
|
|
11070
|
+
if (!buffer) {
|
|
11071
|
+
buffer = { id: chunk.id, name: "", arguments: "" };
|
|
11072
|
+
toolCallBuffers.set(chunk.id, buffer);
|
|
11073
|
+
}
|
|
11074
|
+
if (chunk.name) buffer.name = chunk.name;
|
|
11075
|
+
if (chunk.arguments) buffer.arguments += chunk.arguments;
|
|
11076
|
+
} else if (chunk.type === "done") {
|
|
11077
|
+
streamProviderMetadata = chunk.providerMetadata;
|
|
11078
|
+
streamTiming = chunk.timing;
|
|
10640
11079
|
response = {
|
|
10641
11080
|
content: content2,
|
|
10642
11081
|
tool_calls: void 0,
|
|
10643
11082
|
usage: chunk.usage,
|
|
10644
|
-
cost: chunk.cost
|
|
11083
|
+
cost: chunk.cost,
|
|
11084
|
+
providerMetadata: chunk.providerMetadata,
|
|
11085
|
+
timing: chunk.timing
|
|
10645
11086
|
};
|
|
10646
11087
|
}
|
|
10647
11088
|
}
|
|
11089
|
+
} catch (error) {
|
|
11090
|
+
if (iterator.return) {
|
|
11091
|
+
try {
|
|
11092
|
+
void Promise.resolve(iterator.return(void 0)).catch(() => {
|
|
11093
|
+
});
|
|
11094
|
+
} catch {
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
throw error;
|
|
10648
11098
|
}
|
|
11099
|
+
throwIfRequestStalled();
|
|
10649
11100
|
for (const buffer of toolCallBuffers.values()) {
|
|
10650
11101
|
toolCalls.push({
|
|
10651
11102
|
id: buffer.id,
|
|
@@ -10663,39 +11114,91 @@ ${appendedSchemaText}`;
|
|
|
10663
11114
|
if (streamProviderMetadata) {
|
|
10664
11115
|
response.providerMetadata = streamProviderMetadata;
|
|
10665
11116
|
}
|
|
11117
|
+
if (streamTiming) {
|
|
11118
|
+
response.timing = streamTiming;
|
|
11119
|
+
}
|
|
10666
11120
|
if (thinkingContent) {
|
|
10667
11121
|
response.thinking_content = thinkingContent;
|
|
10668
11122
|
}
|
|
10669
11123
|
} else {
|
|
10670
|
-
|
|
11124
|
+
if (stallController && provider.reportsRequestLifecycle !== true) armStallTimer();
|
|
11125
|
+
response = await raceWithAbortSignal(
|
|
11126
|
+
provider.chat(cloneMessagesForProvider(currentMessages), chatOptions),
|
|
11127
|
+
providerSettlementSignal
|
|
11128
|
+
);
|
|
11129
|
+
throwIfRequestStalled();
|
|
10671
11130
|
}
|
|
10672
11131
|
} catch (err) {
|
|
10673
|
-
|
|
11132
|
+
const callError = stallReason !== void 0 && providerSettlementSignal?.reason === stallReason ? new StallTimeoutError("ctx.ask()", stallTimeoutMs, agent2._name) : err;
|
|
11133
|
+
if (richRequest) this.recordRichFailure(callError);
|
|
11134
|
+
const failureTiming = callError instanceof ProviderError ? callError.timing : void 0;
|
|
10674
11135
|
this.emitEvent({
|
|
10675
11136
|
type: "agent_call_end",
|
|
10676
11137
|
agent: agent2._name,
|
|
10677
11138
|
model: effectiveModelUri,
|
|
10678
11139
|
promptVersion: agent2._config.version,
|
|
11140
|
+
// A dispatched request abandoned at the runtime boundary may still
|
|
11141
|
+
// be billed even though a non-cooperative provider supplied no
|
|
11142
|
+
// usage. Preserve budget honesty by marking the known total as a
|
|
11143
|
+
// lower bound rather than presenting an exact $0.
|
|
11144
|
+
...callError instanceof StallTimeoutError && requestDispatched ? { unpriced: true } : {},
|
|
10679
11145
|
duration: Date.now() - turnStart,
|
|
11146
|
+
// Top-level beside `duration`, exactly as on the success path.
|
|
11147
|
+
...failureTiming ? { timing: failureTiming } : {},
|
|
10680
11148
|
data: {
|
|
10681
11149
|
response: "",
|
|
10682
11150
|
turn: turns,
|
|
10683
11151
|
...retryReason ? { retryReason } : {},
|
|
10684
|
-
error: this._observerErrorProjection(
|
|
11152
|
+
error: this._observerErrorProjection(callError)?.message ?? legacyErrorMessage(callError),
|
|
10685
11153
|
// Enrich with typed-error metadata when available. `body` is
|
|
10686
11154
|
// deliberately NOT emitted — it's redaction-eligible (see
|
|
10687
11155
|
// docs/security.md). `data.error` already carries the message.
|
|
10688
|
-
...providerErrorEventMetadata(
|
|
11156
|
+
...providerErrorEventMetadata(callError, richRequest)
|
|
10689
11157
|
}
|
|
10690
11158
|
});
|
|
10691
|
-
throw
|
|
11159
|
+
throw callError;
|
|
11160
|
+
} finally {
|
|
11161
|
+
providerCallActive = false;
|
|
11162
|
+
clearStallTimer();
|
|
10692
11163
|
}
|
|
10693
11164
|
if (usageCapture && response.usage) {
|
|
10694
11165
|
usageCapture.value = response.usage;
|
|
10695
11166
|
}
|
|
11167
|
+
if (usageCapture && response.timing) {
|
|
11168
|
+
usageCapture.timing = response.timing;
|
|
11169
|
+
}
|
|
11170
|
+
if (chatOptions.promptCache && provider.realizesPromptCache?.(model) === true && response.usage && !this.promptCacheWarned) {
|
|
11171
|
+
const wrote = (response.usage.cache_write_tokens ?? 0) > 0;
|
|
11172
|
+
const read = (response.usage.cached_tokens ?? 0) > 0;
|
|
11173
|
+
if (read) {
|
|
11174
|
+
this.promptCacheColdWrites = 0;
|
|
11175
|
+
this.promptCacheInactive = 0;
|
|
11176
|
+
} else if (wrote) {
|
|
11177
|
+
this.promptCacheColdWrites += 1;
|
|
11178
|
+
this.promptCacheInactive = 0;
|
|
11179
|
+
} else {
|
|
11180
|
+
this.promptCacheInactive += 1;
|
|
11181
|
+
}
|
|
11182
|
+
let warning;
|
|
11183
|
+
if (this.promptCacheColdWrites >= PROMPT_CACHE_COLD_WRITE_WARN_AFTER) {
|
|
11184
|
+
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.`;
|
|
11185
|
+
} else if (this.promptCacheInactive >= PROMPT_CACHE_COLD_WRITE_WARN_AFTER) {
|
|
11186
|
+
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.`;
|
|
11187
|
+
}
|
|
11188
|
+
if (warning) {
|
|
11189
|
+
this.promptCacheWarned = true;
|
|
11190
|
+
this.emitEvent({ type: "log", agent: agent2._name, data: { warning } });
|
|
11191
|
+
}
|
|
11192
|
+
}
|
|
10696
11193
|
if (response.cost) {
|
|
10697
11194
|
this._accumulateBudgetCost(response.cost);
|
|
10698
11195
|
}
|
|
11196
|
+
if (response.timing) {
|
|
11197
|
+
timingTotals.turns += 1;
|
|
11198
|
+
timingTotals.queuedMs += response.timing.queuedMs;
|
|
11199
|
+
timingTotals.retryMs += response.timing.retryMs;
|
|
11200
|
+
timingTotals.wireMs += response.timing.wireMs;
|
|
11201
|
+
}
|
|
10699
11202
|
this.emitEvent({
|
|
10700
11203
|
type: "agent_call_end",
|
|
10701
11204
|
agent: agent2._name,
|
|
@@ -10710,6 +11213,8 @@ ${appendedSchemaText}`;
|
|
|
10710
11213
|
cacheWrite: response.usage.cache_write_tokens
|
|
10711
11214
|
} : void 0,
|
|
10712
11215
|
duration: Date.now() - turnStart,
|
|
11216
|
+
// Additive beside `duration`, which keeps its turn-wall-clock meaning.
|
|
11217
|
+
...response.timing ? { timing: response.timing } : {},
|
|
10713
11218
|
data: {
|
|
10714
11219
|
response: response.content,
|
|
10715
11220
|
...response.thinking_content ? { thinking: response.thinking_content } : {},
|
|
@@ -11428,6 +11933,7 @@ ${appendedSchemaText}`;
|
|
|
11428
11933
|
if (this.summaryCache) {
|
|
11429
11934
|
const summaryMsg2 = {
|
|
11430
11935
|
role: "system",
|
|
11936
|
+
origin: "runtime",
|
|
11431
11937
|
content: `Summary of earlier conversation:
|
|
11432
11938
|
${this.summaryCache}`
|
|
11433
11939
|
};
|
|
@@ -11441,6 +11947,7 @@ ${this.summaryCache}`
|
|
|
11441
11947
|
recentTokens2 += msgTokens;
|
|
11442
11948
|
splitIdx2 = i;
|
|
11443
11949
|
}
|
|
11950
|
+
splitIdx2 = nextUserTurn(history, splitIdx2) ?? history.length;
|
|
11444
11951
|
if (splitIdx2 < history.length) {
|
|
11445
11952
|
return [summaryMsg2, ...history.slice(splitIdx2)];
|
|
11446
11953
|
}
|
|
@@ -11454,6 +11961,19 @@ ${this.summaryCache}`
|
|
|
11454
11961
|
recentTokens += msgTokens;
|
|
11455
11962
|
splitIdx = i;
|
|
11456
11963
|
}
|
|
11964
|
+
const anchored = nextUserTurn(history, splitIdx);
|
|
11965
|
+
if (anchored !== void 0) {
|
|
11966
|
+
splitIdx = anchored;
|
|
11967
|
+
} else if (splitIdx === history.length && history.length > 1) {
|
|
11968
|
+
for (let i = history.length - 1; i > 0; i--) {
|
|
11969
|
+
if (history[i].role === "user") {
|
|
11970
|
+
splitIdx = i;
|
|
11971
|
+
break;
|
|
11972
|
+
}
|
|
11973
|
+
}
|
|
11974
|
+
} else {
|
|
11975
|
+
splitIdx = history.length;
|
|
11976
|
+
}
|
|
11457
11977
|
if (splitIdx === 0) return history;
|
|
11458
11978
|
const oldMessages = history.slice(0, splitIdx);
|
|
11459
11979
|
const summaryModelUri = this.config.contextManagement?.summaryModel;
|
|
@@ -11485,6 +12005,7 @@ ${this.summaryCache}`
|
|
|
11485
12005
|
}
|
|
11486
12006
|
const summaryMsg = {
|
|
11487
12007
|
role: "system",
|
|
12008
|
+
origin: "runtime",
|
|
11488
12009
|
content: `Summary of earlier conversation:
|
|
11489
12010
|
${summaryResponse.content}`
|
|
11490
12011
|
};
|
|
@@ -12163,46 +12684,52 @@ ${summaryResponse.content}`
|
|
|
12163
12684
|
}
|
|
12164
12685
|
// ── ctx.awaitHuman() ──────────────────────────────────────────────────
|
|
12165
12686
|
async awaitHuman(options) {
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
12687
|
+
const trackers = timeoutTrackerStorage.getStore();
|
|
12688
|
+
const startedAt = Date.now();
|
|
12689
|
+
for (const tracker of trackers ?? []) {
|
|
12690
|
+
if (tracker.activeHumanWaits++ === 0) tracker.humanWaitStartedAt = startedAt;
|
|
12691
|
+
}
|
|
12692
|
+
try {
|
|
12693
|
+
if (this.spanManager) {
|
|
12694
|
+
return await this.spanManager.withSpanAsync(
|
|
12695
|
+
"axl.ctx.awaitHuman",
|
|
12696
|
+
{
|
|
12697
|
+
"axl.awaitHuman.channel": options.channel
|
|
12698
|
+
},
|
|
12699
|
+
async (span) => {
|
|
12700
|
+
const start = Date.now();
|
|
12701
|
+
const result = await this._awaitHumanImpl(options);
|
|
12702
|
+
span.setAttribute("axl.awaitHuman.wait_duration", Date.now() - start);
|
|
12703
|
+
span.setAttribute("axl.awaitHuman.approved", result.approved);
|
|
12704
|
+
return result;
|
|
12705
|
+
}
|
|
12706
|
+
);
|
|
12707
|
+
}
|
|
12708
|
+
return await this._awaitHumanImpl(options);
|
|
12709
|
+
} finally {
|
|
12710
|
+
const finishedAt = Date.now();
|
|
12711
|
+
for (const tracker of trackers ?? []) {
|
|
12712
|
+
tracker.activeHumanWaits--;
|
|
12713
|
+
if (tracker.activeHumanWaits === 0 && tracker.humanWaitStartedAt !== void 0) {
|
|
12714
|
+
tracker.humanWaitMs += finishedAt - tracker.humanWaitStartedAt;
|
|
12715
|
+
tracker.humanWaitStartedAt = void 0;
|
|
12178
12716
|
}
|
|
12179
|
-
|
|
12717
|
+
}
|
|
12180
12718
|
}
|
|
12181
|
-
return this._awaitHumanImpl(options);
|
|
12182
12719
|
}
|
|
12183
12720
|
async _awaitHumanImpl(options) {
|
|
12184
12721
|
const branchSignal = this.currentSignal;
|
|
12185
|
-
const makeAbortError = (reason) => {
|
|
12186
|
-
if (typeof DOMException !== "undefined") {
|
|
12187
|
-
return new DOMException(
|
|
12188
|
-
typeof reason === "string" ? reason : "awaitHuman aborted",
|
|
12189
|
-
"AbortError"
|
|
12190
|
-
);
|
|
12191
|
-
}
|
|
12192
|
-
const err = new Error(typeof reason === "string" ? reason : "awaitHuman aborted");
|
|
12193
|
-
err.name = "AbortError";
|
|
12194
|
-
return err;
|
|
12195
|
-
};
|
|
12196
12722
|
const awaitWithAbort = async (promise, signal2 = branchSignal) => {
|
|
12197
12723
|
if (!signal2) return promise;
|
|
12198
|
-
|
|
12724
|
+
signal2.throwIfAborted();
|
|
12199
12725
|
let onAbort;
|
|
12200
12726
|
try {
|
|
12201
12727
|
return await Promise.race([
|
|
12202
12728
|
promise,
|
|
12203
12729
|
new Promise((_, reject) => {
|
|
12204
|
-
onAbort = () => reject(
|
|
12730
|
+
onAbort = () => reject(signal2.reason);
|
|
12205
12731
|
signal2.addEventListener("abort", onAbort, { once: true });
|
|
12732
|
+
if (signal2.aborted) onAbort();
|
|
12206
12733
|
})
|
|
12207
12734
|
]);
|
|
12208
12735
|
} finally {
|
|
@@ -12210,12 +12737,12 @@ ${summaryResponse.content}`
|
|
|
12210
12737
|
}
|
|
12211
12738
|
};
|
|
12212
12739
|
if (this.awaitHumanHandler) {
|
|
12213
|
-
|
|
12740
|
+
branchSignal?.throwIfAborted();
|
|
12214
12741
|
this.emitEvent({
|
|
12215
12742
|
type: "await_human",
|
|
12216
12743
|
data: { channel: options.channel, prompt: options.prompt }
|
|
12217
12744
|
});
|
|
12218
|
-
|
|
12745
|
+
branchSignal?.throwIfAborted();
|
|
12219
12746
|
const decision2 = parseHumanDecision(
|
|
12220
12747
|
await awaitWithAbort(Promise.resolve(this.awaitHumanHandler(options)))
|
|
12221
12748
|
);
|
|
@@ -12270,12 +12797,12 @@ ${summaryResponse.content}`
|
|
|
12270
12797
|
const abortListener = () => {
|
|
12271
12798
|
registeredResolver.cancelled = true;
|
|
12272
12799
|
markReady();
|
|
12273
|
-
rejectDecision(
|
|
12800
|
+
rejectDecision(signal.reason);
|
|
12274
12801
|
};
|
|
12275
12802
|
signal.addEventListener("abort", abortListener, { once: true });
|
|
12276
12803
|
let decision;
|
|
12277
12804
|
try {
|
|
12278
|
-
|
|
12805
|
+
signal.throwIfAborted();
|
|
12279
12806
|
if (this.stateStore) {
|
|
12280
12807
|
saveAttempted = true;
|
|
12281
12808
|
await this.stateStore.savePendingDecision(this.executionId, {
|
|
@@ -12286,7 +12813,7 @@ ${summaryResponse.content}`
|
|
|
12286
12813
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12287
12814
|
});
|
|
12288
12815
|
}
|
|
12289
|
-
|
|
12816
|
+
signal.throwIfAborted();
|
|
12290
12817
|
this.emitEvent({
|
|
12291
12818
|
type: "await_human",
|
|
12292
12819
|
data: { channel: options.channel, prompt: options.prompt }
|
|
@@ -12756,7 +13283,7 @@ ${summaryResponse.content}`
|
|
|
12756
13283
|
data: partial.data
|
|
12757
13284
|
});
|
|
12758
13285
|
};
|
|
12759
|
-
const
|
|
13286
|
+
const abortError2 = () => new DOMException("The transcription operation was aborted", "AbortError");
|
|
12760
13287
|
const cleanupStatus = (value) => value === "not_required" || value === "deleted" || value === "failed" || value === "timed_out" ? value : void 0;
|
|
12761
13288
|
const safeProviderFailure = (error, provider, model) => {
|
|
12762
13289
|
const details = error && typeof error === "object" ? error : void 0;
|
|
@@ -12807,7 +13334,7 @@ ${summaryResponse.content}`
|
|
|
12807
13334
|
hasWork: false
|
|
12808
13335
|
})
|
|
12809
13336
|
});
|
|
12810
|
-
throw
|
|
13337
|
+
throw abortError2();
|
|
12811
13338
|
}
|
|
12812
13339
|
if (this.budgetContext?.exceeded) {
|
|
12813
13340
|
const { limit, totalCost: spent, policy } = this.budgetContext;
|
|
@@ -12873,7 +13400,7 @@ ${summaryResponse.content}`
|
|
|
12873
13400
|
...failure.cleanup ? [{ cleanupStatus: failure.cleanup }] : []
|
|
12874
13401
|
)
|
|
12875
13402
|
});
|
|
12876
|
-
throw
|
|
13403
|
+
throw abortError2();
|
|
12877
13404
|
}
|
|
12878
13405
|
if (failure.accounting.cost !== void 0)
|
|
12879
13406
|
this._accumulateBudgetCost(failure.accounting.cost);
|
|
@@ -12916,7 +13443,7 @@ ${summaryResponse.content}`
|
|
|
12916
13443
|
...cleanupStatus(result.cleanupStatus) ? [{ cleanupStatus: cleanupStatus(result.cleanupStatus) }] : []
|
|
12917
13444
|
)
|
|
12918
13445
|
});
|
|
12919
|
-
throw
|
|
13446
|
+
throw abortError2();
|
|
12920
13447
|
}
|
|
12921
13448
|
let transcript;
|
|
12922
13449
|
try {
|
|
@@ -13002,9 +13529,9 @@ ${summaryResponse.content}`
|
|
|
13002
13529
|
const cost = partial.cost;
|
|
13003
13530
|
if (COST_BEARING_LEAF_TYPES.includes(partial.type)) {
|
|
13004
13531
|
const usable = isUsableCost(cost);
|
|
13005
|
-
const unpriced = !usable && hasPositiveBillableWork(
|
|
13532
|
+
const unpriced = !usable && (partial.unpriced === true || hasPositiveBillableWork(
|
|
13006
13533
|
partial
|
|
13007
|
-
);
|
|
13534
|
+
));
|
|
13008
13535
|
if (frame) {
|
|
13009
13536
|
if (usable) frame.askCost.value += cost;
|
|
13010
13537
|
else if (unpriced) frame.askUnpriced = true;
|
|
@@ -13015,7 +13542,7 @@ ${summaryResponse.content}`
|
|
|
13015
13542
|
if (!this.budgetContext.unpricedWarned && Number.isFinite(this.budgetContext.limit)) {
|
|
13016
13543
|
this.budgetContext.unpricedWarned = true;
|
|
13017
13544
|
console.warn(
|
|
13018
|
-
"Budget honesty:
|
|
13545
|
+
"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."
|
|
13019
13546
|
);
|
|
13020
13547
|
}
|
|
13021
13548
|
}
|
|
@@ -14984,12 +15511,13 @@ function forwardAbortSignal(external, internal) {
|
|
|
14984
15511
|
if (!external) return () => {
|
|
14985
15512
|
};
|
|
14986
15513
|
if (external.aborted) {
|
|
14987
|
-
internal.abort();
|
|
15514
|
+
internal.abort(external.reason);
|
|
14988
15515
|
return () => {
|
|
14989
15516
|
};
|
|
14990
15517
|
}
|
|
14991
|
-
const onAbort = () => internal.abort();
|
|
15518
|
+
const onAbort = () => internal.abort(external.reason);
|
|
14992
15519
|
external.addEventListener("abort", onAbort, { once: true });
|
|
15520
|
+
if (external.aborted) onAbort();
|
|
14993
15521
|
return () => external.removeEventListener("abort", onAbort);
|
|
14994
15522
|
}
|
|
14995
15523
|
var AxlRuntime = class extends EventEmitter2 {
|
|
@@ -16260,6 +16788,8 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
16260
16788
|
parent: parentScope
|
|
16261
16789
|
};
|
|
16262
16790
|
const modelCalls = /* @__PURE__ */ new Map();
|
|
16791
|
+
const modelTiming = /* @__PURE__ */ new Map();
|
|
16792
|
+
const captureTimingSamples = options?.captureTimingSamples === true;
|
|
16263
16793
|
const workflowCalls = /* @__PURE__ */ new Map();
|
|
16264
16794
|
const tokens = { input: 0, output: 0, reasoning: 0 };
|
|
16265
16795
|
let agentCalls = 0;
|
|
@@ -16277,6 +16807,30 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
16277
16807
|
tokens.output += event.tokens.output ?? 0;
|
|
16278
16808
|
tokens.reasoning += event.tokens.reasoning ?? 0;
|
|
16279
16809
|
}
|
|
16810
|
+
const failed2 = event.data?.error != null;
|
|
16811
|
+
if (event.model && event.timing && !failed2) {
|
|
16812
|
+
const t = event.timing;
|
|
16813
|
+
let bucket = modelTiming.get(event.model);
|
|
16814
|
+
if (!bucket) {
|
|
16815
|
+
bucket = {
|
|
16816
|
+
calls: 0,
|
|
16817
|
+
queuedMs: 0,
|
|
16818
|
+
retryMs: 0,
|
|
16819
|
+
wireMs: 0,
|
|
16820
|
+
...captureTimingSamples ? { samples: [] } : {}
|
|
16821
|
+
};
|
|
16822
|
+
modelTiming.set(event.model, bucket);
|
|
16823
|
+
}
|
|
16824
|
+
bucket.calls++;
|
|
16825
|
+
bucket.samples?.push(t);
|
|
16826
|
+
bucket.queuedMs += t.queuedMs;
|
|
16827
|
+
bucket.retryMs += t.retryMs;
|
|
16828
|
+
bucket.wireMs += t.wireMs;
|
|
16829
|
+
if (t.firstTokenMs != null) {
|
|
16830
|
+
bucket.firstTokenMs = (bucket.firstTokenMs ?? 0) + t.firstTokenMs;
|
|
16831
|
+
bucket.firstTokenCalls = (bucket.firstTokenCalls ?? 0) + 1;
|
|
16832
|
+
}
|
|
16833
|
+
}
|
|
16280
16834
|
}
|
|
16281
16835
|
if (event.type === "workflow_start" && event.workflow) {
|
|
16282
16836
|
workflowCalls.set(event.workflow, (workflowCalls.get(event.workflow) ?? 0) + 1);
|
|
@@ -16307,6 +16861,7 @@ var AxlRuntime = class extends EventEmitter2 {
|
|
|
16307
16861
|
result,
|
|
16308
16862
|
cost: scope.totalCost,
|
|
16309
16863
|
unpriced,
|
|
16864
|
+
...modelTiming.size > 0 ? { modelTiming: Object.fromEntries(modelTiming) } : {},
|
|
16310
16865
|
...capturedTraces ? { traces: capturedTraces } : {},
|
|
16311
16866
|
metadata: {
|
|
16312
16867
|
models: [...modelCalls.keys()],
|
|
@@ -17443,6 +17998,7 @@ export {
|
|
|
17443
17998
|
SQLiteStore,
|
|
17444
17999
|
Session,
|
|
17445
18000
|
SqliteVectorStore,
|
|
18001
|
+
StallTimeoutError,
|
|
17446
18002
|
ThinkTagScanner,
|
|
17447
18003
|
TimeoutError,
|
|
17448
18004
|
ToolFailure,
|