@gajae-code/agent-core 0.12.4 → 0.12.6
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/CHANGELOG.md +7 -0
- package/dist/types/agent-loop.d.ts +3 -2
- package/dist/types/agent.d.ts +16 -4
- package/dist/types/attempt-scope.d.ts +84 -0
- package/dist/types/types.d.ts +111 -2
- package/package.json +4 -4
- package/src/agent-loop.ts +394 -104
- package/src/agent.ts +164 -31
- package/src/attempt-scope.ts +195 -0
- package/src/proxy.ts +1 -1
- package/src/run-resource-ledger.ts +233 -101
- package/src/telemetry.ts +2 -2
- package/src/types.ts +139 -15
package/src/agent-loop.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "@gajae-code/ai";
|
|
23
23
|
import { isInvalidPromptError, neutralizeReservedControlTokens } from "@gajae-code/ai/utils";
|
|
24
24
|
import { sanitizeText } from "@gajae-code/utils";
|
|
25
|
+
import type { AttemptScope } from "./attempt-scope";
|
|
25
26
|
import {
|
|
26
27
|
createHarmonyAuditEvent,
|
|
27
28
|
detectHarmonyLeakInAssistantMessage,
|
|
@@ -57,8 +58,10 @@ import type {
|
|
|
57
58
|
AgentLoopConfig,
|
|
58
59
|
AgentMessage,
|
|
59
60
|
AgentTool,
|
|
61
|
+
AgentToolContext,
|
|
60
62
|
AgentToolResult,
|
|
61
63
|
ManagedAttemptOutcome,
|
|
64
|
+
StandaloneRunOwnership,
|
|
62
65
|
StreamFn,
|
|
63
66
|
} from "./types";
|
|
64
67
|
|
|
@@ -102,6 +105,13 @@ class ManagedAttemptSnapshotError extends Error {
|
|
|
102
105
|
const managedAttemptTextEncoder = new TextEncoder();
|
|
103
106
|
|
|
104
107
|
const ABORTED: unique symbol = Symbol("agent-loop-aborted");
|
|
108
|
+
interface StandaloneOwnershipState {
|
|
109
|
+
continuationAvailable: boolean;
|
|
110
|
+
continuationClaimed: boolean;
|
|
111
|
+
terminal: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const standaloneOwnershipStates = new WeakMap<StandaloneRunOwnership, StandaloneOwnershipState>();
|
|
105
115
|
|
|
106
116
|
/**
|
|
107
117
|
* Terminal bound for argument-validation loops: how many CONSECUTIVE turns may
|
|
@@ -181,15 +191,16 @@ function repairInvalidPromptHistory(messages: AgentMessage[]): boolean {
|
|
|
181
191
|
return changed;
|
|
182
192
|
}
|
|
183
193
|
|
|
184
|
-
function managedFailureOutcome(message: AssistantMessage): ManagedAttemptOutcome {
|
|
194
|
+
function managedFailureOutcome(message: AssistantMessage, scope?: AttemptScope): ManagedAttemptOutcome {
|
|
185
195
|
return {
|
|
186
196
|
type: "retryable_discarded",
|
|
187
197
|
failure: { message, transportFailure: managedTransportFailure(message) },
|
|
198
|
+
scope,
|
|
188
199
|
};
|
|
189
200
|
}
|
|
190
201
|
|
|
191
|
-
function managedContextOverflowOutcome(message: AssistantMessage): ManagedAttemptOutcome {
|
|
192
|
-
return { type: "context_overflow_discarded", message };
|
|
202
|
+
function managedContextOverflowOutcome(message: AssistantMessage, scope?: AttemptScope): ManagedAttemptOutcome {
|
|
203
|
+
return { type: "context_overflow_discarded", message, scope };
|
|
193
204
|
}
|
|
194
205
|
|
|
195
206
|
function managedFailureMessage(error: unknown, config: AgentLoopConfig): AssistantMessage {
|
|
@@ -291,7 +302,8 @@ export function agentLoop(
|
|
|
291
302
|
config: AgentLoopConfig,
|
|
292
303
|
signal?: AbortSignal,
|
|
293
304
|
streamFn?: StreamFn,
|
|
294
|
-
|
|
305
|
+
emitAgentStart = true,
|
|
306
|
+
initialScope?: AttemptScope,
|
|
295
307
|
): EventStream<AgentEvent, AgentMessage[]> {
|
|
296
308
|
const stream = createAgentStream();
|
|
297
309
|
|
|
@@ -301,22 +313,24 @@ export function agentLoop(
|
|
|
301
313
|
...context,
|
|
302
314
|
messages: [...context.messages, ...prompts],
|
|
303
315
|
};
|
|
316
|
+
// Allocate before constructing the provisional transaction so every first turn
|
|
317
|
+
// has one stable scope for lifecycle events, transform hooks, and transport.
|
|
318
|
+
const scope = initialScope ?? config.initialScope ?? config.attemptMinter?.mint("main");
|
|
304
319
|
const transaction = config.fallbackManaged
|
|
305
|
-
? new ManagedAttemptTransaction(stream, config.onAssistantMessageEvent, config.model)
|
|
320
|
+
? new ManagedAttemptTransaction(stream, config.onAssistantMessageEvent, config.model, scope)
|
|
306
321
|
: undefined;
|
|
307
322
|
const attemptStream = transaction ?? stream;
|
|
308
|
-
openResourceRun(config);
|
|
309
|
-
if (!config.fallbackManaged || emitManagedAgentStart) stream.push({ type: "agent_start" });
|
|
310
|
-
attemptStream.push({ type: "turn_start" });
|
|
311
|
-
for (const prompt of prompts) {
|
|
312
|
-
stream.push({ type: "message_start", message: prompt });
|
|
313
|
-
stream.push({ type: "message_end", message: prompt });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
323
|
try {
|
|
317
|
-
|
|
324
|
+
prepareResourceOwnership(config, false);
|
|
325
|
+
if (emitAgentStart) stream.push({ type: "agent_start", ...(scope ? { scope } : {}) });
|
|
326
|
+
attemptStream.push({ type: "turn_start", ...(scope ? { scope } : {}) });
|
|
327
|
+
for (const prompt of prompts) {
|
|
328
|
+
stream.push({ type: "message_start", message: prompt, scope });
|
|
329
|
+
stream.push({ type: "message_end", message: prompt, scope });
|
|
330
|
+
}
|
|
331
|
+
await runLoop(currentContext, newMessages, config, signal, stream, streamFn, transaction, scope);
|
|
318
332
|
} catch (err) {
|
|
319
|
-
|
|
333
|
+
sealStandaloneOnError(config);
|
|
320
334
|
stream.fail(err);
|
|
321
335
|
}
|
|
322
336
|
})();
|
|
@@ -337,7 +351,8 @@ export function agentLoopContinue(
|
|
|
337
351
|
config: AgentLoopConfig,
|
|
338
352
|
signal?: AbortSignal,
|
|
339
353
|
streamFn?: StreamFn,
|
|
340
|
-
|
|
354
|
+
emitAgentStart = true,
|
|
355
|
+
initialScope?: AttemptScope,
|
|
341
356
|
): EventStream<AgentEvent, AgentMessage[]> {
|
|
342
357
|
if (context.messages.length === 0) {
|
|
343
358
|
throw new Error("Cannot continue: no messages in context");
|
|
@@ -352,18 +367,20 @@ export function agentLoopContinue(
|
|
|
352
367
|
(async () => {
|
|
353
368
|
const newMessages: AgentMessage[] = [];
|
|
354
369
|
const currentContext: AgentContext = { ...context };
|
|
370
|
+
// Allocate before constructing the provisional transaction so every first turn
|
|
371
|
+
// has one stable scope for lifecycle events, transform hooks, and transport.
|
|
372
|
+
const scope = initialScope ?? config.initialScope ?? config.attemptMinter?.mint("main");
|
|
355
373
|
const transaction = config.fallbackManaged
|
|
356
|
-
? new ManagedAttemptTransaction(stream, config.onAssistantMessageEvent, config.model)
|
|
374
|
+
? new ManagedAttemptTransaction(stream, config.onAssistantMessageEvent, config.model, scope)
|
|
357
375
|
: undefined;
|
|
358
376
|
const attemptStream = transaction ?? stream;
|
|
359
|
-
openResourceRun(config);
|
|
360
|
-
if (!config.fallbackManaged || emitManagedAgentStart) stream.push({ type: "agent_start" });
|
|
361
|
-
attemptStream.push({ type: "turn_start" });
|
|
362
|
-
|
|
363
377
|
try {
|
|
364
|
-
|
|
378
|
+
prepareResourceOwnership(config, true);
|
|
379
|
+
if (emitAgentStart) stream.push({ type: "agent_start", ...(scope ? { scope } : {}) });
|
|
380
|
+
attemptStream.push({ type: "turn_start", ...(scope ? { scope } : {}) });
|
|
381
|
+
await runLoop(currentContext, newMessages, config, signal, stream, streamFn, transaction, scope);
|
|
365
382
|
} catch (err) {
|
|
366
|
-
|
|
383
|
+
sealStandaloneOnError(config);
|
|
367
384
|
stream.fail(err);
|
|
368
385
|
}
|
|
369
386
|
})();
|
|
@@ -378,17 +395,121 @@ function createAgentStream(): EventStream<AgentEvent, AgentMessage[]> {
|
|
|
378
395
|
);
|
|
379
396
|
}
|
|
380
397
|
|
|
381
|
-
function
|
|
382
|
-
if (config.resourceLedger
|
|
398
|
+
function prepareResourceOwnership(config: AgentLoopConfig, continuation: boolean): void {
|
|
399
|
+
if (!config.resourceLedger || !config.resourceRunId) return;
|
|
400
|
+
if (config.resourceSealOwner === "caller") {
|
|
401
|
+
const existing = config.resourceLedger.lookupDomain(config.resourceRunId);
|
|
402
|
+
if (config.resourceCancellationDomain && existing && config.resourceCancellationDomain !== existing) {
|
|
403
|
+
config.resourceLedger.quarantine(config.resourceRunId);
|
|
404
|
+
throw new Error("Prompt resource cancellation domain is unavailable");
|
|
405
|
+
}
|
|
406
|
+
const domain = config.resourceCancellationDomain ?? existing ?? config.resourceLedger.open(config.resourceRunId);
|
|
407
|
+
if (!domain) throw new Error("Prompt resource cancellation domain is unavailable");
|
|
408
|
+
config.resourceCancellationDomain = domain;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const existing = config.resourceLedger.lookupDomain(config.resourceRunId);
|
|
413
|
+
const supplied = config.standaloneRunOwnership;
|
|
414
|
+
if (supplied) {
|
|
415
|
+
if (!continuation && existing) {
|
|
416
|
+
config.resourceLedger.quarantine(config.resourceRunId);
|
|
417
|
+
throw new Error("Standalone prompt continuation ownership is unavailable");
|
|
418
|
+
}
|
|
419
|
+
const state = standaloneOwnershipStates.get(supplied);
|
|
420
|
+
if (
|
|
421
|
+
!state ||
|
|
422
|
+
supplied.resourceRunId !== config.resourceRunId ||
|
|
423
|
+
supplied.domain !== existing ||
|
|
424
|
+
(config.resourceCancellationDomain !== undefined && config.resourceCancellationDomain !== existing)
|
|
425
|
+
) {
|
|
426
|
+
if (existing) config.resourceLedger.quarantine(config.resourceRunId);
|
|
427
|
+
throw new Error("Standalone prompt ownership is unavailable");
|
|
428
|
+
}
|
|
429
|
+
if (continuation && (!state.continuationClaimed || state.terminal)) {
|
|
430
|
+
config.resourceLedger.quarantine(config.resourceRunId);
|
|
431
|
+
throw new Error("Standalone prompt continuation ownership is unavailable");
|
|
432
|
+
}
|
|
433
|
+
if (continuation) {
|
|
434
|
+
state.continuationClaimed = false;
|
|
435
|
+
state.continuationAvailable = false;
|
|
436
|
+
}
|
|
437
|
+
config.resourceCancellationDomain = existing;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (existing) {
|
|
441
|
+
config.resourceLedger.quarantine(config.resourceRunId);
|
|
442
|
+
throw new Error("Standalone prompt continuation ownership is unavailable");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const domain = config.resourceLedger.open(config.resourceRunId);
|
|
446
|
+
if (!domain) throw new Error("Prompt resource cancellation domain is unavailable");
|
|
447
|
+
config.resourceCancellationDomain = domain;
|
|
448
|
+
const state: StandaloneOwnershipState = {
|
|
449
|
+
continuationAvailable: false,
|
|
450
|
+
continuationClaimed: false,
|
|
451
|
+
terminal: false,
|
|
452
|
+
};
|
|
453
|
+
const ownership: StandaloneRunOwnership = {
|
|
454
|
+
resourceRunId: config.resourceRunId,
|
|
455
|
+
domain,
|
|
456
|
+
claimContinuation: () => {
|
|
457
|
+
if (domain.signal.aborted) {
|
|
458
|
+
state.terminal = true;
|
|
459
|
+
return { ok: false, reason: "quarantined" };
|
|
460
|
+
}
|
|
461
|
+
if (state.terminal) return { ok: false, reason: "terminal" };
|
|
462
|
+
if (!state.continuationAvailable || state.continuationClaimed) return { ok: false, reason: "already_claimed" };
|
|
463
|
+
state.continuationClaimed = true;
|
|
464
|
+
return { ok: true, ownership };
|
|
465
|
+
},
|
|
466
|
+
abandon: reason => {
|
|
467
|
+
if (state.terminal) return;
|
|
468
|
+
state.terminal = true;
|
|
469
|
+
config.resourceLedger?.quarantine(config.resourceRunId!);
|
|
470
|
+
void reason;
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
standaloneOwnershipStates.set(ownership, state);
|
|
474
|
+
config.standaloneRunOwnership = ownership;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function sealStandaloneOnError(config: AgentLoopConfig): void {
|
|
478
|
+
const standalone = config.standaloneRunOwnership
|
|
479
|
+
? standaloneOwnershipStates.get(config.standaloneRunOwnership)
|
|
480
|
+
: undefined;
|
|
481
|
+
if (standalone) standalone.terminal = true;
|
|
482
|
+
if (config.resourceSealOwner !== "caller" && config.resourceLedger && config.resourceRunId) {
|
|
483
|
+
config.resourceLedger.seal(config.resourceRunId);
|
|
484
|
+
}
|
|
383
485
|
}
|
|
384
486
|
|
|
385
487
|
function publishAgentEnd(
|
|
386
488
|
stream: EventStream<AgentEvent, AgentMessage[]>,
|
|
387
489
|
config: AgentLoopConfig,
|
|
388
490
|
event: Extract<AgentEvent, { type: "agent_end" }>,
|
|
491
|
+
scope?: AttemptScope,
|
|
389
492
|
): void {
|
|
390
|
-
|
|
391
|
-
|
|
493
|
+
// Aborted maintenance yields no continuation, so it is terminal for standalone
|
|
494
|
+
// ownership and resource sealing. The event itself keeps its `maintenance`
|
|
495
|
+
// stopReason so AgentSession can still report the aborted maintenance
|
|
496
|
+
// settlement to its consumers.
|
|
497
|
+
const publishedEvent = scope ? { ...event, scope } : event;
|
|
498
|
+
const maintenanceContinues =
|
|
499
|
+
publishedEvent.stopReason === "maintenance" && publishedEvent.maintenanceOutcome !== "aborted";
|
|
500
|
+
stream.push(publishedEvent);
|
|
501
|
+
const standalone = config.standaloneRunOwnership
|
|
502
|
+
? standaloneOwnershipStates.get(config.standaloneRunOwnership)
|
|
503
|
+
: undefined;
|
|
504
|
+
if (maintenanceContinues) {
|
|
505
|
+
if (standalone) {
|
|
506
|
+
standalone.continuationAvailable = true;
|
|
507
|
+
standalone.continuationClaimed = false;
|
|
508
|
+
}
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (standalone) standalone.terminal = true;
|
|
512
|
+
if (config.resourceSealOwner !== "caller" && config.resourceLedger && config.resourceRunId) {
|
|
392
513
|
config.resourceLedger.seal(config.resourceRunId);
|
|
393
514
|
}
|
|
394
515
|
}
|
|
@@ -741,6 +862,7 @@ class ManagedAttemptTransaction {
|
|
|
741
862
|
| ((message: AssistantMessage, event: AssistantMessageEvent) => void)
|
|
742
863
|
| undefined,
|
|
743
864
|
private readonly model: AgentLoopConfig["model"],
|
|
865
|
+
readonly scope?: AttemptScope,
|
|
744
866
|
) {}
|
|
745
867
|
|
|
746
868
|
push(event: AgentEvent): void {
|
|
@@ -883,8 +1005,9 @@ function buildAgentEndEvent(
|
|
|
883
1005
|
telemetry: AgentTelemetry | undefined,
|
|
884
1006
|
stepCount: number,
|
|
885
1007
|
stopReason: "completed" | "paused" = "completed",
|
|
1008
|
+
scope?: AttemptScope,
|
|
886
1009
|
): Extract<AgentEvent, { type: "agent_end" }> {
|
|
887
|
-
const base = { type: "agent_end" as const, messages, stopReason };
|
|
1010
|
+
const base = { type: "agent_end" as const, messages, stopReason, ...(scope ? { scope } : {}) };
|
|
888
1011
|
if (!telemetry) return base;
|
|
889
1012
|
const snapshot = telemetry.collector.snapshot({ stepCount });
|
|
890
1013
|
if (telemetry.collector.markRunEnded()) {
|
|
@@ -1215,6 +1338,7 @@ async function runLoop(
|
|
|
1215
1338
|
stream: EventStream<AgentEvent, AgentMessage[]>,
|
|
1216
1339
|
streamFn?: StreamFn,
|
|
1217
1340
|
initialTransaction?: ManagedAttemptTransaction,
|
|
1341
|
+
initialScope?: AttemptScope,
|
|
1218
1342
|
): Promise<void> {
|
|
1219
1343
|
const loopSignal = signal ?? new AbortController().signal;
|
|
1220
1344
|
|
|
@@ -1236,6 +1360,7 @@ async function runLoop(
|
|
|
1236
1360
|
stepCounter,
|
|
1237
1361
|
streamFn,
|
|
1238
1362
|
initialTransaction,
|
|
1363
|
+
initialScope,
|
|
1239
1364
|
),
|
|
1240
1365
|
);
|
|
1241
1366
|
} catch (err) {
|
|
@@ -1265,8 +1390,10 @@ async function runLoopBody(
|
|
|
1265
1390
|
stepCounter: StepCounter,
|
|
1266
1391
|
streamFn?: StreamFn,
|
|
1267
1392
|
initialTransaction?: ManagedAttemptTransaction,
|
|
1393
|
+
initialScope?: AttemptScope,
|
|
1268
1394
|
): Promise<void> {
|
|
1269
1395
|
let firstTurn = true;
|
|
1396
|
+
let lastAttemptScope: AttemptScope | undefined;
|
|
1270
1397
|
// Check for steering messages at start (user may have typed while waiting)
|
|
1271
1398
|
let pendingMessages: AgentMessage[] = (await config.getSteeringMessages?.()) || [];
|
|
1272
1399
|
let harmonyRetryAttempt = 0;
|
|
@@ -1301,15 +1428,20 @@ async function runLoopBody(
|
|
|
1301
1428
|
|
|
1302
1429
|
// Inner loop: process tool calls and steering messages
|
|
1303
1430
|
while (hasMoreToolCalls || pendingMessages.length > 0) {
|
|
1431
|
+
const scope =
|
|
1432
|
+
initialScope ?? (firstTurn ? config.initialScope : undefined) ?? config.attemptMinter?.mint("main");
|
|
1433
|
+
initialScope = undefined;
|
|
1304
1434
|
const transaction =
|
|
1305
1435
|
initialTransaction ??
|
|
1306
1436
|
(config.fallbackManaged
|
|
1307
|
-
? new ManagedAttemptTransaction(stream, config.onAssistantMessageEvent, config.model)
|
|
1437
|
+
? new ManagedAttemptTransaction(stream, config.onAssistantMessageEvent, config.model, scope)
|
|
1308
1438
|
: undefined);
|
|
1309
1439
|
initialTransaction = undefined;
|
|
1440
|
+
const attemptScope = transaction?.scope ?? scope;
|
|
1441
|
+
lastAttemptScope = attemptScope;
|
|
1310
1442
|
const attemptStream = transaction ?? stream;
|
|
1311
1443
|
if (!firstTurn) {
|
|
1312
|
-
attemptStream.push({ type: "turn_start" });
|
|
1444
|
+
attemptStream.push({ type: "turn_start", ...(attemptScope ? { scope: attemptScope } : {}) });
|
|
1313
1445
|
} else {
|
|
1314
1446
|
firstTurn = false;
|
|
1315
1447
|
}
|
|
@@ -1318,8 +1450,8 @@ async function runLoopBody(
|
|
|
1318
1450
|
// discarded managed attempt cannot lose it before its retry continuation.
|
|
1319
1451
|
if (pendingMessages.length > 0) {
|
|
1320
1452
|
for (const message of pendingMessages) {
|
|
1321
|
-
stream.push({ type: "message_start", message });
|
|
1322
|
-
stream.push({ type: "message_end", message });
|
|
1453
|
+
stream.push({ type: "message_start", message, scope: attemptScope });
|
|
1454
|
+
stream.push({ type: "message_end", message, scope: attemptScope });
|
|
1323
1455
|
currentContext.messages.push(message);
|
|
1324
1456
|
newMessages.push(message);
|
|
1325
1457
|
}
|
|
@@ -1347,12 +1479,17 @@ async function runLoopBody(
|
|
|
1347
1479
|
const outcome = loopSignal.aborted ? "aborted" : maintenanceOutcome;
|
|
1348
1480
|
|
|
1349
1481
|
if (outcome !== "not-needed") {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1482
|
+
publishAgentEnd(
|
|
1483
|
+
stream,
|
|
1484
|
+
config,
|
|
1485
|
+
{
|
|
1486
|
+
type: "agent_end",
|
|
1487
|
+
messages: newMessages,
|
|
1488
|
+
stopReason: "maintenance",
|
|
1489
|
+
maintenanceOutcome: outcome,
|
|
1490
|
+
},
|
|
1491
|
+
attemptScope,
|
|
1492
|
+
);
|
|
1356
1493
|
stream.end(newMessages);
|
|
1357
1494
|
return;
|
|
1358
1495
|
}
|
|
@@ -1395,6 +1532,7 @@ async function runLoopBody(
|
|
|
1395
1532
|
telemetry,
|
|
1396
1533
|
invokeAgentSpan,
|
|
1397
1534
|
stepCounter,
|
|
1535
|
+
attemptScope,
|
|
1398
1536
|
streamFn,
|
|
1399
1537
|
harmonyRetryAttempt,
|
|
1400
1538
|
recoveryState.pending && recoveryState.syntheticMessage
|
|
@@ -1416,7 +1554,9 @@ async function runLoopBody(
|
|
|
1416
1554
|
transaction.discard();
|
|
1417
1555
|
currentContext.messages.splice(contextMessageCount);
|
|
1418
1556
|
newMessages.splice(newMessageCount);
|
|
1419
|
-
await config.onManagedAttemptOutcome?.(
|
|
1557
|
+
await config.onManagedAttemptOutcome?.(
|
|
1558
|
+
managedContextOverflowOutcome(failureMessage, transaction.scope),
|
|
1559
|
+
);
|
|
1420
1560
|
stream.end(newMessages);
|
|
1421
1561
|
return;
|
|
1422
1562
|
}
|
|
@@ -1424,7 +1564,7 @@ async function runLoopBody(
|
|
|
1424
1564
|
transaction.discard();
|
|
1425
1565
|
currentContext.messages.splice(contextMessageCount);
|
|
1426
1566
|
newMessages.splice(newMessageCount);
|
|
1427
|
-
await config.onManagedAttemptOutcome?.(managedFailureOutcome(failureMessage));
|
|
1567
|
+
await config.onManagedAttemptOutcome?.(managedFailureOutcome(failureMessage, transaction.scope));
|
|
1428
1568
|
stream.end(newMessages);
|
|
1429
1569
|
return;
|
|
1430
1570
|
}
|
|
@@ -1516,7 +1656,7 @@ async function runLoopBody(
|
|
|
1516
1656
|
transaction?.discard();
|
|
1517
1657
|
currentContext.messages.splice(contextMessageCount);
|
|
1518
1658
|
newMessages.splice(newMessageCount);
|
|
1519
|
-
await config.onManagedAttemptOutcome?.(managedContextOverflowOutcome(message));
|
|
1659
|
+
await config.onManagedAttemptOutcome?.(managedContextOverflowOutcome(message, transaction?.scope));
|
|
1520
1660
|
stream.end(newMessages);
|
|
1521
1661
|
return;
|
|
1522
1662
|
}
|
|
@@ -1537,7 +1677,7 @@ async function runLoopBody(
|
|
|
1537
1677
|
transaction?.discard();
|
|
1538
1678
|
currentContext.messages.splice(contextMessageCount);
|
|
1539
1679
|
newMessages.splice(newMessageCount);
|
|
1540
|
-
await config.onManagedAttemptOutcome?.(managedFailureOutcome(message));
|
|
1680
|
+
await config.onManagedAttemptOutcome?.(managedFailureOutcome(message, transaction?.scope));
|
|
1541
1681
|
stream.end(newMessages);
|
|
1542
1682
|
return;
|
|
1543
1683
|
}
|
|
@@ -1546,7 +1686,11 @@ async function runLoopBody(
|
|
|
1546
1686
|
transaction?.discard();
|
|
1547
1687
|
currentContext.messages.splice(contextMessageCount);
|
|
1548
1688
|
newMessages.splice(newMessageCount);
|
|
1549
|
-
await config.onManagedAttemptOutcome?.({
|
|
1689
|
+
await config.onManagedAttemptOutcome?.({
|
|
1690
|
+
type: "run_terminal",
|
|
1691
|
+
reason: "cancelled",
|
|
1692
|
+
scope: transaction?.scope,
|
|
1693
|
+
});
|
|
1550
1694
|
stream.end(newMessages);
|
|
1551
1695
|
return;
|
|
1552
1696
|
}
|
|
@@ -1588,8 +1732,13 @@ async function runLoopBody(
|
|
|
1588
1732
|
status: message.stopReason === "aborted" ? "aborted" : "error",
|
|
1589
1733
|
});
|
|
1590
1734
|
}
|
|
1591
|
-
stream.push({ type: "turn_end", message, toolResults });
|
|
1592
|
-
publishAgentEnd(
|
|
1735
|
+
stream.push({ type: "turn_end", message, toolResults, scope: attemptScope });
|
|
1736
|
+
publishAgentEnd(
|
|
1737
|
+
stream,
|
|
1738
|
+
config,
|
|
1739
|
+
buildAgentEndEvent(newMessages, telemetry, stepCounter.count, "completed", attemptScope),
|
|
1740
|
+
attemptScope,
|
|
1741
|
+
);
|
|
1593
1742
|
stream.end(newMessages);
|
|
1594
1743
|
return;
|
|
1595
1744
|
}
|
|
@@ -1627,6 +1776,7 @@ async function runLoopBody(
|
|
|
1627
1776
|
config,
|
|
1628
1777
|
telemetry,
|
|
1629
1778
|
invokeAgentSpan,
|
|
1779
|
+
attemptScope,
|
|
1630
1780
|
);
|
|
1631
1781
|
|
|
1632
1782
|
toolResults.push(...executionResult.toolResults);
|
|
@@ -1660,7 +1810,7 @@ async function runLoopBody(
|
|
|
1660
1810
|
recoveryState.syntheticMessage = undefined;
|
|
1661
1811
|
}
|
|
1662
1812
|
|
|
1663
|
-
stream.push({ type: "turn_end", message, toolResults });
|
|
1813
|
+
stream.push({ type: "turn_end", message, toolResults, scope: attemptScope });
|
|
1664
1814
|
|
|
1665
1815
|
if (steeringMessagesFromExecution && steeringMessagesFromExecution.length > 0) {
|
|
1666
1816
|
pendingMessages = steeringMessagesFromExecution;
|
|
@@ -1669,7 +1819,12 @@ async function runLoopBody(
|
|
|
1669
1819
|
pendingMessages = (await config.getSteeringMessages?.()) || [];
|
|
1670
1820
|
if (pendingMessages.length > 0) continue;
|
|
1671
1821
|
if (config.shouldPause?.()) {
|
|
1672
|
-
publishAgentEnd(
|
|
1822
|
+
publishAgentEnd(
|
|
1823
|
+
stream,
|
|
1824
|
+
config,
|
|
1825
|
+
buildAgentEndEvent(newMessages, telemetry, stepCounter.count, "paused", attemptScope),
|
|
1826
|
+
attemptScope,
|
|
1827
|
+
);
|
|
1673
1828
|
stream.end(newMessages);
|
|
1674
1829
|
return;
|
|
1675
1830
|
}
|
|
@@ -1690,7 +1845,12 @@ async function runLoopBody(
|
|
|
1690
1845
|
message.errorMessage = message.errorMessage
|
|
1691
1846
|
? `${message.errorMessage} | ${breakerMessage}`
|
|
1692
1847
|
: breakerMessage;
|
|
1693
|
-
publishAgentEnd(
|
|
1848
|
+
publishAgentEnd(
|
|
1849
|
+
stream,
|
|
1850
|
+
config,
|
|
1851
|
+
buildAgentEndEvent(newMessages, telemetry, stepCounter.count, "completed", attemptScope),
|
|
1852
|
+
attemptScope,
|
|
1853
|
+
);
|
|
1694
1854
|
stream.end(newMessages);
|
|
1695
1855
|
return;
|
|
1696
1856
|
}
|
|
@@ -1699,7 +1859,12 @@ async function runLoopBody(
|
|
|
1699
1859
|
// Agent would stop here. Check for follow-up messages.
|
|
1700
1860
|
await config.onBeforeYield?.();
|
|
1701
1861
|
if (config.shouldPause?.()) {
|
|
1702
|
-
publishAgentEnd(
|
|
1862
|
+
publishAgentEnd(
|
|
1863
|
+
stream,
|
|
1864
|
+
config,
|
|
1865
|
+
buildAgentEndEvent(newMessages, telemetry, stepCounter.count, "paused", lastAttemptScope),
|
|
1866
|
+
lastAttemptScope,
|
|
1867
|
+
);
|
|
1703
1868
|
stream.end(newMessages);
|
|
1704
1869
|
return;
|
|
1705
1870
|
}
|
|
@@ -1714,7 +1879,12 @@ async function runLoopBody(
|
|
|
1714
1879
|
break;
|
|
1715
1880
|
}
|
|
1716
1881
|
|
|
1717
|
-
publishAgentEnd(
|
|
1882
|
+
publishAgentEnd(
|
|
1883
|
+
stream,
|
|
1884
|
+
config,
|
|
1885
|
+
buildAgentEndEvent(newMessages, telemetry, stepCounter.count, "completed", lastAttemptScope),
|
|
1886
|
+
lastAttemptScope,
|
|
1887
|
+
);
|
|
1718
1888
|
stream.end(newMessages);
|
|
1719
1889
|
}
|
|
1720
1890
|
|
|
@@ -1747,6 +1917,7 @@ async function streamAssistantResponse(
|
|
|
1747
1917
|
telemetry: AgentTelemetry | undefined,
|
|
1748
1918
|
invokeAgentSpan: Span | undefined,
|
|
1749
1919
|
stepCounter: StepCounter,
|
|
1920
|
+
scope?: AttemptScope,
|
|
1750
1921
|
streamFn?: StreamFn,
|
|
1751
1922
|
harmonyRetryAttempt = 0,
|
|
1752
1923
|
recoveryMode?: { syntheticMessage: UserMessage },
|
|
@@ -1754,7 +1925,7 @@ async function streamAssistantResponse(
|
|
|
1754
1925
|
// Apply context transform if configured (AgentMessage[] → AgentMessage[])
|
|
1755
1926
|
let messages = context.messages;
|
|
1756
1927
|
if (config.transformContext) {
|
|
1757
|
-
messages = await config.transformContext(messages, signal);
|
|
1928
|
+
messages = await config.transformContext(messages, signal, scope);
|
|
1758
1929
|
}
|
|
1759
1930
|
|
|
1760
1931
|
// Convert to LLM-compatible messages (AgentMessage[] → Message[]) and normalize at the LLM boundary.
|
|
@@ -1810,11 +1981,17 @@ async function streamAssistantResponse(
|
|
|
1810
1981
|
const dynamicReasoning = config.getReasoning?.();
|
|
1811
1982
|
const harmonyMitigationEnabled = isHarmonyLeakMitigationTarget(config.model);
|
|
1812
1983
|
const harmonyAbortController = harmonyMitigationEnabled ? new AbortController() : undefined;
|
|
1813
|
-
const
|
|
1814
|
-
? signal
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1984
|
+
const requestSignals = [
|
|
1985
|
+
...(signal ? [signal] : []),
|
|
1986
|
+
...(config.resourceCancellationDomain ? [config.resourceCancellationDomain.signal] : []),
|
|
1987
|
+
...(harmonyAbortController ? [harmonyAbortController.signal] : []),
|
|
1988
|
+
];
|
|
1989
|
+
const requestSignal =
|
|
1990
|
+
requestSignals.length === 0
|
|
1991
|
+
? undefined
|
|
1992
|
+
: requestSignals.length === 1
|
|
1993
|
+
? requestSignals[0]
|
|
1994
|
+
: AbortSignal.any(requestSignals);
|
|
1818
1995
|
const effectiveTemperature =
|
|
1819
1996
|
harmonyRetryAttempt > 0 && config.temperature !== undefined ? config.temperature + 0.05 : config.temperature;
|
|
1820
1997
|
const effectiveToolChoice = recoveryMode ? "none" : (dynamicToolChoice ?? config.toolChoice);
|
|
@@ -1845,9 +2022,9 @@ async function streamAssistantResponse(
|
|
|
1845
2022
|
// stealing them from the configured hook.
|
|
1846
2023
|
let capturedHeaders: Readonly<Record<string, string>> | undefined;
|
|
1847
2024
|
const userOnResponse = config.onResponse;
|
|
1848
|
-
const captureOnResponse: AgentLoopConfig["onResponse"] = (response, modelInfo) => {
|
|
2025
|
+
const captureOnResponse: AgentLoopConfig["onResponse"] = (response, modelInfo, scope) => {
|
|
1849
2026
|
capturedHeaders = response.headers;
|
|
1850
|
-
return userOnResponse?.(response, modelInfo);
|
|
2027
|
+
return userOnResponse?.(response, modelInfo, scope);
|
|
1851
2028
|
};
|
|
1852
2029
|
|
|
1853
2030
|
const finishChat = async (message: AssistantMessage): Promise<void> => {
|
|
@@ -1862,21 +2039,45 @@ async function streamAssistantResponse(
|
|
|
1862
2039
|
try {
|
|
1863
2040
|
return await runInActiveSpan(chatSpan, async () => {
|
|
1864
2041
|
const fallbackAttempt = config.fallbackManaged ? config.nextFallbackAttempt?.(config.model) : undefined;
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2042
|
+
const providerReservation =
|
|
2043
|
+
config.resourceLedger && config.resourceRunId
|
|
2044
|
+
? config.resourceLedger.reserveProducer(
|
|
2045
|
+
config.resourceRunId,
|
|
2046
|
+
config.resourceCancellationDomain,
|
|
2047
|
+
"provider_factory",
|
|
2048
|
+
`${config.model.provider}/${config.model.id}`,
|
|
2049
|
+
)
|
|
2050
|
+
: undefined;
|
|
2051
|
+
if (providerReservation && !providerReservation.ok)
|
|
2052
|
+
throw new Error("Prompt resource ownership is unavailable");
|
|
2053
|
+
if (requestSignal?.aborted) {
|
|
2054
|
+
providerReservation?.ok && providerReservation.lease.closeDiscovery();
|
|
2055
|
+
const aborted = emitAbortedAssistantMessage(null, false, context, config, stream, scope);
|
|
2056
|
+
await finishChat(aborted);
|
|
2057
|
+
return aborted;
|
|
2058
|
+
}
|
|
2059
|
+
let responsePromise: Promise<Awaited<ReturnType<StreamFn>>>;
|
|
2060
|
+
try {
|
|
2061
|
+
responsePromise = Promise.resolve(
|
|
2062
|
+
streamFunction(config.model, llmContext, {
|
|
2063
|
+
...config,
|
|
2064
|
+
attemptScope: scope,
|
|
2065
|
+
fallbackAttempt,
|
|
2066
|
+
apiKey: resolvedApiKey,
|
|
2067
|
+
authCredentialType,
|
|
2068
|
+
metadata: resolvedMetadata,
|
|
2069
|
+
sessionId: config.providerSessionId ?? config.sessionId,
|
|
2070
|
+
toolChoice: effectiveToolChoice,
|
|
2071
|
+
reasoning: effectiveReasoning,
|
|
2072
|
+
temperature: effectiveTemperature,
|
|
2073
|
+
signal: requestSignal,
|
|
2074
|
+
onResponse: captureOnResponse,
|
|
2075
|
+
}),
|
|
2076
|
+
);
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
providerReservation?.ok && providerReservation.lease.closeDiscovery();
|
|
2079
|
+
throw error;
|
|
2080
|
+
}
|
|
1880
2081
|
const { promise: iteratorSettled, resolve: settleIterator } = Promise.withResolvers<void>();
|
|
1881
2082
|
let responseResultPromise: Promise<AssistantMessage> | undefined;
|
|
1882
2083
|
let responseForResult: { result(): Promise<AssistantMessage> } | undefined;
|
|
@@ -1887,16 +2088,55 @@ async function streamAssistantResponse(
|
|
|
1887
2088
|
await iteratorSettled;
|
|
1888
2089
|
await Promise.allSettled([getResponseResult()]);
|
|
1889
2090
|
});
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2091
|
+
const closeLateFactoryResponse = (): void => {
|
|
2092
|
+
void responsePromise.then(
|
|
2093
|
+
response => {
|
|
2094
|
+
responseForResult = response;
|
|
2095
|
+
try {
|
|
2096
|
+
const iterator = response[Symbol.asyncIterator]();
|
|
2097
|
+
try {
|
|
2098
|
+
const returned = iterator.return?.();
|
|
2099
|
+
void Promise.resolve(returned).then(
|
|
2100
|
+
() => settleIterator(),
|
|
2101
|
+
() => settleIterator(),
|
|
2102
|
+
);
|
|
2103
|
+
} catch {
|
|
2104
|
+
settleIterator();
|
|
2105
|
+
}
|
|
2106
|
+
} catch {
|
|
2107
|
+
settleIterator();
|
|
2108
|
+
}
|
|
2109
|
+
},
|
|
2110
|
+
() => settleIterator(),
|
|
2111
|
+
);
|
|
2112
|
+
};
|
|
2113
|
+
if (providerReservation?.ok) {
|
|
2114
|
+
providerReservation.lease.track("provider_iterator", "provider-lifecycle", providerLifecycle);
|
|
2115
|
+
void providerLifecycle.then(
|
|
2116
|
+
() => providerReservation.lease.closeDiscovery(),
|
|
2117
|
+
() => providerReservation.lease.closeDiscovery(),
|
|
1897
2118
|
);
|
|
1898
2119
|
}
|
|
1899
|
-
|
|
2120
|
+
let response: Awaited<typeof responsePromise>;
|
|
2121
|
+
if (requestSignal) {
|
|
2122
|
+
const { promise: factoryAbort, resolve: resolveFactoryAbort } = Promise.withResolvers<typeof ABORTED>();
|
|
2123
|
+
const onFactoryAbort = () => resolveFactoryAbort(ABORTED);
|
|
2124
|
+
requestSignal.addEventListener("abort", onFactoryAbort, { once: true });
|
|
2125
|
+
try {
|
|
2126
|
+
const responseOrAbort = await Promise.race([responsePromise, factoryAbort]);
|
|
2127
|
+
if (responseOrAbort === ABORTED) {
|
|
2128
|
+
const aborted = emitAbortedAssistantMessage(null, false, context, config, stream, scope);
|
|
2129
|
+
await finishChat(aborted);
|
|
2130
|
+
closeLateFactoryResponse();
|
|
2131
|
+
return aborted;
|
|
2132
|
+
}
|
|
2133
|
+
response = responseOrAbort;
|
|
2134
|
+
} finally {
|
|
2135
|
+
requestSignal.removeEventListener("abort", onFactoryAbort);
|
|
2136
|
+
}
|
|
2137
|
+
} else {
|
|
2138
|
+
response = await responsePromise;
|
|
2139
|
+
}
|
|
1900
2140
|
responseForResult = response;
|
|
1901
2141
|
|
|
1902
2142
|
let partialMessage: AssistantMessage | null = null;
|
|
@@ -1929,7 +2169,14 @@ async function streamAssistantResponse(
|
|
|
1929
2169
|
if (requestSignal) {
|
|
1930
2170
|
if (requestSignal.aborted) {
|
|
1931
2171
|
closeIterator();
|
|
1932
|
-
const aborted = emitAbortedAssistantMessage(
|
|
2172
|
+
const aborted = emitAbortedAssistantMessage(
|
|
2173
|
+
partialMessage,
|
|
2174
|
+
addedPartial,
|
|
2175
|
+
context,
|
|
2176
|
+
config,
|
|
2177
|
+
stream,
|
|
2178
|
+
scope,
|
|
2179
|
+
);
|
|
1933
2180
|
await finishChat(aborted);
|
|
1934
2181
|
return aborted;
|
|
1935
2182
|
}
|
|
@@ -1947,7 +2194,14 @@ async function streamAssistantResponse(
|
|
|
1947
2194
|
const result = await Promise.race([responseIterator.next(), abortRacePromise]);
|
|
1948
2195
|
if (result === ABORTED) {
|
|
1949
2196
|
closeIterator();
|
|
1950
|
-
const aborted = emitAbortedAssistantMessage(
|
|
2197
|
+
const aborted = emitAbortedAssistantMessage(
|
|
2198
|
+
partialMessage,
|
|
2199
|
+
addedPartial,
|
|
2200
|
+
context,
|
|
2201
|
+
config,
|
|
2202
|
+
stream,
|
|
2203
|
+
scope,
|
|
2204
|
+
);
|
|
1951
2205
|
await finishChat(aborted);
|
|
1952
2206
|
return aborted;
|
|
1953
2207
|
}
|
|
@@ -1956,7 +2210,14 @@ async function streamAssistantResponse(
|
|
|
1956
2210
|
next = await responseIterator.next();
|
|
1957
2211
|
}
|
|
1958
2212
|
if (requestSignal?.aborted) {
|
|
1959
|
-
const aborted = emitAbortedAssistantMessage(
|
|
2213
|
+
const aborted = emitAbortedAssistantMessage(
|
|
2214
|
+
partialMessage,
|
|
2215
|
+
addedPartial,
|
|
2216
|
+
context,
|
|
2217
|
+
config,
|
|
2218
|
+
stream,
|
|
2219
|
+
scope,
|
|
2220
|
+
);
|
|
1960
2221
|
await finishChat(aborted);
|
|
1961
2222
|
return aborted;
|
|
1962
2223
|
}
|
|
@@ -1975,7 +2236,7 @@ async function streamAssistantResponse(
|
|
|
1975
2236
|
: event.partial;
|
|
1976
2237
|
context.messages.push(partialMessage);
|
|
1977
2238
|
addedPartial = true;
|
|
1978
|
-
stream.push({ type: "message_start", message: { ...partialMessage } });
|
|
2239
|
+
stream.push({ type: "message_start", message: { ...partialMessage }, scope });
|
|
1979
2240
|
break;
|
|
1980
2241
|
|
|
1981
2242
|
case "toolChoiceIncapability":
|
|
@@ -2006,6 +2267,7 @@ async function streamAssistantResponse(
|
|
|
2006
2267
|
type: "message_update",
|
|
2007
2268
|
assistantMessageEvent: partialEvent,
|
|
2008
2269
|
message: { ...partialMessage },
|
|
2270
|
+
scope,
|
|
2009
2271
|
});
|
|
2010
2272
|
}
|
|
2011
2273
|
break;
|
|
@@ -2021,9 +2283,9 @@ async function streamAssistantResponse(
|
|
|
2021
2283
|
context.messages.push(finalMessage);
|
|
2022
2284
|
}
|
|
2023
2285
|
if (!addedPartial) {
|
|
2024
|
-
stream.push({ type: "message_start", message: { ...finalMessage } });
|
|
2286
|
+
stream.push({ type: "message_start", message: { ...finalMessage }, scope });
|
|
2025
2287
|
}
|
|
2026
|
-
stream.push({ type: "message_end", message: finalMessage });
|
|
2288
|
+
stream.push({ type: "message_end", message: finalMessage, scope });
|
|
2027
2289
|
await finishChat(finalMessage);
|
|
2028
2290
|
return finalMessage;
|
|
2029
2291
|
}
|
|
@@ -2056,6 +2318,7 @@ function emitAbortedAssistantMessage(
|
|
|
2056
2318
|
context: AgentContext,
|
|
2057
2319
|
config: AgentLoopConfig,
|
|
2058
2320
|
stream: EventStream<AgentEvent, AgentMessage[]>,
|
|
2321
|
+
scope?: AttemptScope,
|
|
2059
2322
|
): AssistantMessage {
|
|
2060
2323
|
const errorMessage = "Request was aborted";
|
|
2061
2324
|
const now = Date.now();
|
|
@@ -2080,9 +2343,9 @@ function emitAbortedAssistantMessage(
|
|
|
2080
2343
|
if (addedPartial) {
|
|
2081
2344
|
context.messages.pop();
|
|
2082
2345
|
} else {
|
|
2083
|
-
stream.push({ type: "message_start", message: { ...abortedMessage } });
|
|
2346
|
+
stream.push({ type: "message_start", message: { ...abortedMessage }, scope });
|
|
2084
2347
|
}
|
|
2085
|
-
stream.push({ type: "message_end", message: abortedMessage });
|
|
2348
|
+
stream.push({ type: "message_end", message: abortedMessage, scope });
|
|
2086
2349
|
return abortedMessage;
|
|
2087
2350
|
}
|
|
2088
2351
|
|
|
@@ -2108,6 +2371,7 @@ async function executeToolCalls(
|
|
|
2108
2371
|
config: AgentLoopConfig,
|
|
2109
2372
|
telemetry: AgentTelemetry | undefined,
|
|
2110
2373
|
invokeAgentSpan: Span | undefined,
|
|
2374
|
+
scope?: AttemptScope,
|
|
2111
2375
|
): Promise<{
|
|
2112
2376
|
toolResults: ToolResultMessage[];
|
|
2113
2377
|
steeringMessages?: AgentMessage[];
|
|
@@ -2130,9 +2394,12 @@ async function executeToolCalls(
|
|
|
2130
2394
|
const batchId = `${assistantMessage.timestamp ?? Date.now()}_${toolCalls[0]?.id ?? "batch"}`;
|
|
2131
2395
|
const shouldInterruptImmediately = interruptMode !== "wait";
|
|
2132
2396
|
const steeringAbortController = new AbortController();
|
|
2133
|
-
const
|
|
2134
|
-
?
|
|
2135
|
-
|
|
2397
|
+
const toolSignals = [
|
|
2398
|
+
...(signal ? [signal] : []),
|
|
2399
|
+
...(config.resourceCancellationDomain ? [config.resourceCancellationDomain.signal] : []),
|
|
2400
|
+
steeringAbortController.signal,
|
|
2401
|
+
];
|
|
2402
|
+
const toolSignal = toolSignals.length === 1 ? toolSignals[0] : AbortSignal.any(toolSignals);
|
|
2136
2403
|
const interruptState = { triggered: false };
|
|
2137
2404
|
let steeringMessages: AgentMessage[] | undefined;
|
|
2138
2405
|
let steeringCheck: Promise<void> | null = null;
|
|
@@ -2187,6 +2454,7 @@ async function executeToolCalls(
|
|
|
2187
2454
|
toolName: toolCall.name,
|
|
2188
2455
|
args: record.args,
|
|
2189
2456
|
intent: toolCall.intent,
|
|
2457
|
+
scope,
|
|
2190
2458
|
});
|
|
2191
2459
|
}
|
|
2192
2460
|
stream.push({
|
|
@@ -2195,6 +2463,7 @@ async function executeToolCalls(
|
|
|
2195
2463
|
toolName: toolCall.name,
|
|
2196
2464
|
result,
|
|
2197
2465
|
isError,
|
|
2466
|
+
scope,
|
|
2198
2467
|
});
|
|
2199
2468
|
|
|
2200
2469
|
const toolResultMessage: ToolResultMessage = {
|
|
@@ -2212,8 +2481,8 @@ async function executeToolCalls(
|
|
|
2212
2481
|
record.resultEmitted = true;
|
|
2213
2482
|
emittedToolResults.push(toolResultMessage);
|
|
2214
2483
|
|
|
2215
|
-
stream.push({ type: "message_start", message: toolResultMessage });
|
|
2216
|
-
stream.push({ type: "message_end", message: toolResultMessage });
|
|
2484
|
+
stream.push({ type: "message_start", message: toolResultMessage, scope });
|
|
2485
|
+
stream.push({ type: "message_end", message: toolResultMessage, scope });
|
|
2217
2486
|
};
|
|
2218
2487
|
|
|
2219
2488
|
const runTool = async (record: (typeof records)[number], index: number): Promise<void> => {
|
|
@@ -2251,6 +2520,7 @@ async function executeToolCalls(
|
|
|
2251
2520
|
toolName: toolCall.name,
|
|
2252
2521
|
args: argsForExecution,
|
|
2253
2522
|
intent: toolCall.intent,
|
|
2523
|
+
scope,
|
|
2254
2524
|
});
|
|
2255
2525
|
|
|
2256
2526
|
const toolSpan = startExecuteToolSpan(telemetry, {
|
|
@@ -2328,7 +2598,7 @@ async function executeToolCalls(
|
|
|
2328
2598
|
// Reflect post-hook args so emitted tool results / afterToolCall see what actually executed.
|
|
2329
2599
|
record.args = effectiveArgs;
|
|
2330
2600
|
|
|
2331
|
-
const
|
|
2601
|
+
const baseToolContext = getToolContext
|
|
2332
2602
|
? getToolContext({
|
|
2333
2603
|
batchId,
|
|
2334
2604
|
index,
|
|
@@ -2336,6 +2606,9 @@ async function executeToolCalls(
|
|
|
2336
2606
|
toolCalls: toolCallInfos,
|
|
2337
2607
|
})
|
|
2338
2608
|
: undefined;
|
|
2609
|
+
const toolContext = scope
|
|
2610
|
+
? (Object.assign(baseToolContext ?? {}, { attemptScope: scope }) as AgentToolContext)
|
|
2611
|
+
: baseToolContext;
|
|
2339
2612
|
const execution = tool.execute(
|
|
2340
2613
|
toolCall.id,
|
|
2341
2614
|
transformToolCallArguments ? transformToolCallArguments(effectiveArgs, toolCall.name) : effectiveArgs,
|
|
@@ -2347,6 +2620,7 @@ async function executeToolCalls(
|
|
|
2347
2620
|
toolName: toolCall.name,
|
|
2348
2621
|
args: effectiveArgs,
|
|
2349
2622
|
partialResult: coerceToolResult(partialResult).result,
|
|
2623
|
+
scope,
|
|
2350
2624
|
});
|
|
2351
2625
|
},
|
|
2352
2626
|
toolContext,
|
|
@@ -2435,11 +2709,28 @@ async function executeToolCalls(
|
|
|
2435
2709
|
const record = records[index];
|
|
2436
2710
|
const concurrency = record.tool?.concurrency ?? "shared";
|
|
2437
2711
|
const start = concurrency === "exclusive" ? Promise.all([lastExclusive, ...sharedTasks]) : lastExclusive;
|
|
2712
|
+
const reservation =
|
|
2713
|
+
config.resourceLedger && config.resourceRunId
|
|
2714
|
+
? config.resourceLedger.reserveProducer(
|
|
2715
|
+
config.resourceRunId,
|
|
2716
|
+
config.resourceCancellationDomain,
|
|
2717
|
+
"tool",
|
|
2718
|
+
`${record.toolCall.name}:${record.toolCall.id}`,
|
|
2719
|
+
)
|
|
2720
|
+
: undefined;
|
|
2721
|
+
if (reservation && !reservation.ok) {
|
|
2722
|
+
record.skipped = true;
|
|
2723
|
+
recordSkippedTool(telemetry, {
|
|
2724
|
+
toolCallId: record.toolCall.id,
|
|
2725
|
+
toolName: record.toolCall.name,
|
|
2726
|
+
status: "skipped",
|
|
2727
|
+
});
|
|
2728
|
+
emitToolResult(record, createSkippedToolResult(), true);
|
|
2729
|
+
continue;
|
|
2730
|
+
}
|
|
2438
2731
|
const task = start
|
|
2439
2732
|
.then(() => runTool(record, index))
|
|
2440
2733
|
.finally(() => {
|
|
2441
|
-
// Scheduler ownership includes dependency waits and the fallback skip
|
|
2442
|
-
// emission, not only tool.execute().
|
|
2443
2734
|
if (!record.toolResultMessage) {
|
|
2444
2735
|
record.skipped = true;
|
|
2445
2736
|
recordSkippedTool(telemetry, {
|
|
@@ -2450,15 +2741,14 @@ async function executeToolCalls(
|
|
|
2450
2741
|
emitToolResult(record, createSkippedToolResult(), true);
|
|
2451
2742
|
}
|
|
2452
2743
|
});
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
`${record.toolCall.name}:${record.toolCall.id}`,
|
|
2459
|
-
task,
|
|
2744
|
+
if (reservation?.ok) {
|
|
2745
|
+
reservation.lease.track("tool", `${record.toolCall.name}:${record.toolCall.id}`, task);
|
|
2746
|
+
void task.then(
|
|
2747
|
+
() => reservation.lease.closeDiscovery(),
|
|
2748
|
+
() => reservation.lease.closeDiscovery(),
|
|
2460
2749
|
);
|
|
2461
2750
|
}
|
|
2751
|
+
tasks.push(task);
|
|
2462
2752
|
if (concurrency === "exclusive") {
|
|
2463
2753
|
lastExclusive = task;
|
|
2464
2754
|
sharedTasks = [];
|