@zhivex-ai/core 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-handoff.d.ts +2 -0
- package/dist/agent-handoff.d.ts.map +1 -1
- package/dist/agent-handoff.js +8 -3
- package/dist/agent-handoff.js.map +1 -1
- package/dist/agent-state.d.ts +6 -0
- package/dist/agent-state.d.ts.map +1 -0
- package/dist/agent-state.js +374 -0
- package/dist/agent-state.js.map +1 -0
- package/dist/agent-store.d.ts +5 -3
- package/dist/agent-store.d.ts.map +1 -1
- package/dist/agent-store.js +1027 -145
- package/dist/agent-store.js.map +1 -1
- package/dist/agent.d.ts +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +698 -229
- package/dist/agent.js.map +1 -1
- package/dist/api-stability.d.ts.map +1 -1
- package/dist/api-stability.js +5 -0
- package/dist/api-stability.js.map +1 -1
- package/dist/artifact.d.ts.map +1 -1
- package/dist/artifact.js +2 -1
- package/dist/artifact.js.map +1 -1
- package/dist/bounded-broadcast.d.ts +45 -0
- package/dist/bounded-broadcast.d.ts.map +1 -0
- package/dist/bounded-broadcast.js +163 -0
- package/dist/bounded-broadcast.js.map +1 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +11 -0
- package/dist/catalog.js.map +1 -1
- package/dist/generate-object.d.ts.map +1 -1
- package/dist/generate-object.js +26 -88
- package/dist/generate-object.js.map +1 -1
- package/dist/generate-text.d.ts +9 -1
- package/dist/generate-text.d.ts.map +1 -1
- package/dist/generate-text.js +109 -80
- package/dist/generate-text.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/live-agent.d.ts.map +1 -1
- package/dist/live-agent.js +31 -54
- package/dist/live-agent.js.map +1 -1
- package/dist/messages.d.ts +4 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js.map +1 -1
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +39 -55
- package/dist/realtime.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +2 -1
- package/dist/runner.js.map +1 -1
- package/dist/safety-policy.d.ts +41 -1
- package/dist/safety-policy.d.ts.map +1 -1
- package/dist/safety-policy.js +114 -8
- package/dist/safety-policy.js.map +1 -1
- package/dist/secure-id.d.ts +2 -0
- package/dist/secure-id.d.ts.map +1 -0
- package/dist/secure-id.js +3 -0
- package/dist/secure-id.js.map +1 -0
- package/dist/types.d.ts +189 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +2 -1
- package/dist/ui.js.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +2 -1
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { createAgentApprovalMessage, getAgentApprovalRequests } from "./agent-approval.js";
|
|
2
3
|
import { createAgentHandoffMessage } from "./agent-handoff.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { AGENT_RUN_STATE_SCHEMA_VERSION, normalizeAgentRunState } from "./agent-state.js";
|
|
5
|
+
import { BoundedReplayBroadcast } from "./bounded-broadcast.js";
|
|
6
|
+
import { ConflictError, GuardrailTriggeredError, ValidationError } from "./errors.js";
|
|
7
|
+
import { aggregateTokenUsage, generateText, getGenerateTextStepTiming, normalizeMessages, streamText } from "./generate-text.js";
|
|
8
|
+
import { isCallableToolDefinition, serializeJsonValue } from "./messages.js";
|
|
6
9
|
import { mergeAbortSignals } from "./runtime.js";
|
|
10
|
+
import { evaluateAgentBudgetPreflight, getAgentBudgetStatus } from "./safety-policy.js";
|
|
11
|
+
import { createSecureId } from "./secure-id.js";
|
|
7
12
|
import { toToolSet } from "./tool-registry.js";
|
|
8
13
|
import { z } from "zod";
|
|
9
|
-
const randomId =
|
|
10
|
-
const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
14
|
+
const randomId = createSecureId;
|
|
11
15
|
const AGENT_GROUP_FAIL_FAST_ABORT_MESSAGE = "Agent group member aborted after fail-fast.";
|
|
16
|
+
const DEFAULT_AGENT_LEASE_TTL_MS = 30_000;
|
|
17
|
+
const DEFAULT_AGENT_CANCELLATION_POLL_MS = 1_000;
|
|
18
|
+
const DEFAULT_AGENT_MAX_STATE_BYTES = 4 * 1024 * 1024;
|
|
12
19
|
class AgentPolicyTimeoutError extends Error {
|
|
13
20
|
constructor(timeoutMs) {
|
|
14
21
|
super(`Agent run timed out after ${timeoutMs}ms.`);
|
|
@@ -20,8 +27,9 @@ const joinInstructions = (...parts) => {
|
|
|
20
27
|
return content.length ? content.join("\n\n") : undefined;
|
|
21
28
|
};
|
|
22
29
|
const hasToolCalls = (messages) => messages.some((message) => message.parts.some((part) => part.type === "tool-call"));
|
|
23
|
-
const snapshotRequest = (request) => ({
|
|
24
|
-
|
|
30
|
+
const snapshotRequest = (request, messageOffset = 0, messages = request.messages) => ({
|
|
31
|
+
messageOffset,
|
|
32
|
+
messages,
|
|
25
33
|
toolChoice: request.toolChoice,
|
|
26
34
|
toolExecution: request.toolExecution,
|
|
27
35
|
temperature: request.temperature,
|
|
@@ -42,18 +50,23 @@ const snapshotResponse = (response) => ({
|
|
|
42
50
|
const countToolCalls = (messages) => messages.reduce((total, message) => total + message.parts.filter((part) => part.type === "tool-call").length, 0);
|
|
43
51
|
const mapSteps = (steps, offset, toolResults) => {
|
|
44
52
|
let toolResultCursor = 0;
|
|
53
|
+
let previousMessageCount = 0;
|
|
45
54
|
return steps.map((step, index) => {
|
|
46
55
|
const response = snapshotResponse(step.response);
|
|
47
56
|
const toolCallCount = countToolCalls(response.messages);
|
|
48
57
|
const stepToolResults = toolResults.slice(toolResultCursor, toolResultCursor + toolCallCount);
|
|
49
58
|
toolResultCursor += toolCallCount;
|
|
50
|
-
const
|
|
59
|
+
const timing = getGenerateTextStepTiming(step.request);
|
|
60
|
+
const finishedAt = timing?.finishedAt ?? Date.now();
|
|
61
|
+
const messageOffset = index === 0 ? 0 : previousMessageCount;
|
|
62
|
+
const incrementalMessages = step.request.messages.slice(messageOffset);
|
|
63
|
+
previousMessageCount = step.request.messages.length;
|
|
51
64
|
return {
|
|
52
65
|
index: offset + index + 1,
|
|
53
66
|
status: "completed",
|
|
54
|
-
startedAt: finishedAt,
|
|
67
|
+
startedAt: timing?.startedAt ?? finishedAt,
|
|
55
68
|
finishedAt,
|
|
56
|
-
request: snapshotRequest(step.request),
|
|
69
|
+
request: snapshotRequest(step.request, messageOffset, incrementalMessages),
|
|
57
70
|
response,
|
|
58
71
|
toolResults: stepToolResults
|
|
59
72
|
};
|
|
@@ -75,17 +88,15 @@ const toOutput = (state) => ({
|
|
|
75
88
|
state,
|
|
76
89
|
error: state.error
|
|
77
90
|
});
|
|
78
|
-
const normalizeRunState = (state) => ({
|
|
79
|
-
...state,
|
|
80
|
-
schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION
|
|
81
|
-
});
|
|
82
91
|
const normalizeApprovalStatus = (status) => status === "suspended" ? "waiting_approval" : status;
|
|
83
|
-
const cloneState = (state) => JSON.parse(JSON.stringify(
|
|
84
|
-
const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata, agentId, runId, handoff, parentRunId, idempotencyKey) => {
|
|
92
|
+
const cloneState = (state) => JSON.parse(JSON.stringify(normalizeAgentRunState(state)));
|
|
93
|
+
const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata, agentId, runId, handoff, parentRunId, idempotencyKey, scope) => {
|
|
85
94
|
const startedAt = Date.now();
|
|
86
95
|
return {
|
|
87
96
|
schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
|
|
97
|
+
revision: 0,
|
|
88
98
|
runId,
|
|
99
|
+
scope,
|
|
89
100
|
idempotencyKey,
|
|
90
101
|
agentId,
|
|
91
102
|
parentRunId: parentRunId ?? handoff?.fromRunId,
|
|
@@ -115,6 +126,15 @@ const ensureValidStateInput = (input) => {
|
|
|
115
126
|
if (input.prompt !== undefined || input.messages !== undefined || input.system !== undefined || input.handoff !== undefined) {
|
|
116
127
|
throw new ValidationError('Pass either "state" or a fresh "prompt"/"messages" input, but not both.');
|
|
117
128
|
}
|
|
129
|
+
const stateScope = input.state?.scope;
|
|
130
|
+
const inputScope = input.scope;
|
|
131
|
+
if (stateScope &&
|
|
132
|
+
inputScope &&
|
|
133
|
+
(stateScope.tenantId !== inputScope.tenantId ||
|
|
134
|
+
stateScope.userId !== inputScope.userId ||
|
|
135
|
+
stateScope.namespace !== inputScope.namespace)) {
|
|
136
|
+
throw new ValidationError('The provided agent state belongs to a different tenant/user scope.');
|
|
137
|
+
}
|
|
118
138
|
};
|
|
119
139
|
const ensureValidIdempotencyInput = (input, store) => {
|
|
120
140
|
if (!input.idempotencyKey) {
|
|
@@ -123,8 +143,46 @@ const ensureValidIdempotencyInput = (input, store) => {
|
|
|
123
143
|
if (!store) {
|
|
124
144
|
throw new ValidationError('The "idempotencyKey" option requires an agent run "store".');
|
|
125
145
|
}
|
|
126
|
-
if (!store.
|
|
127
|
-
throw new ValidationError('The agent run "store" must implement "
|
|
146
|
+
if (!store.claimIdempotencyKey) {
|
|
147
|
+
throw new ValidationError('The agent run "store" must implement "claimIdempotencyKey()" to use "idempotencyKey" safely.');
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const ensureValidScope = (scope) => {
|
|
151
|
+
if (!scope)
|
|
152
|
+
return;
|
|
153
|
+
if (typeof scope.tenantId !== "string" || scope.tenantId.length === 0) {
|
|
154
|
+
throw new ValidationError('Agent scope "tenantId" must be a non-empty string.');
|
|
155
|
+
}
|
|
156
|
+
for (const field of ["userId", "namespace"]) {
|
|
157
|
+
if (scope[field] !== undefined && (typeof scope[field] !== "string" || scope[field].length === 0)) {
|
|
158
|
+
throw new ValidationError(`Agent scope "${field}" must be a non-empty string when provided.`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const invokeOperationalHook = async (agent, source, operation, runId, callback, fallback) => {
|
|
163
|
+
if (!callback) {
|
|
164
|
+
return fallback;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
return await callback();
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
171
|
+
try {
|
|
172
|
+
await agent.hookFailurePolicy?.onError?.({
|
|
173
|
+
source,
|
|
174
|
+
operation,
|
|
175
|
+
runId,
|
|
176
|
+
error: normalizedError
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Reporting an observer failure must never recursively fail the run.
|
|
181
|
+
}
|
|
182
|
+
if (agent.hookFailurePolicy?.[source] === "fail") {
|
|
183
|
+
throw normalizedError;
|
|
184
|
+
}
|
|
185
|
+
return fallback;
|
|
128
186
|
}
|
|
129
187
|
};
|
|
130
188
|
const injectContextMessages = (messages, extraMessages) => {
|
|
@@ -146,13 +204,14 @@ const prepareFreshMessages = async (agent, input, runId) => {
|
|
|
146
204
|
? [createAgentHandoffMessage(input.handoff), ...input.handoff.contextMessages.filter((message) => message.role !== "system")]
|
|
147
205
|
: [];
|
|
148
206
|
messages = injectContextMessages(messages, handoffMessages);
|
|
149
|
-
const memoryMessages = agent.memory
|
|
150
|
-
?
|
|
207
|
+
const memoryMessages = await invokeOperationalHook(agent, "memory", "load", runId, agent.memory
|
|
208
|
+
? () => agent.memory.load({
|
|
151
209
|
runId,
|
|
152
210
|
agentId: agent.id,
|
|
211
|
+
scope: input.scope ?? input.handoff?.scope,
|
|
153
212
|
metadata: cloneMetadata(agent.metadata, input.metadata)
|
|
154
213
|
})
|
|
155
|
-
: [];
|
|
214
|
+
: undefined, []);
|
|
156
215
|
messages = injectContextMessages(messages, memoryMessages);
|
|
157
216
|
return {
|
|
158
217
|
messages,
|
|
@@ -215,13 +274,13 @@ const finalizeState = (state, result, newSteps, newToolResults) => {
|
|
|
215
274
|
state.outputText = result.text;
|
|
216
275
|
state.finishReason = result.finishReason;
|
|
217
276
|
state.providerFinishReason = result.providerFinishReason;
|
|
218
|
-
state.usage = result.usage;
|
|
277
|
+
state.usage = aggregateTokenUsage([state.usage, result.usage]);
|
|
219
278
|
state.pendingApprovals = pendingApprovals;
|
|
220
279
|
state.updatedAt = Date.now();
|
|
221
280
|
return toOutput(state);
|
|
222
281
|
};
|
|
223
282
|
const emitTelemetryEvent = async (agent, event) => {
|
|
224
|
-
await agent.onTelemetryEvent
|
|
283
|
+
await invokeOperationalHook(agent, "telemetry", event.type, event.runId, agent.onTelemetryEvent ? () => agent.onTelemetryEvent(event) : undefined, undefined);
|
|
225
284
|
};
|
|
226
285
|
const subAgentToolInputSchema = z.object({
|
|
227
286
|
prompt: z.string().min(1),
|
|
@@ -273,6 +332,7 @@ export const createSubAgentTool = (options) => {
|
|
|
273
332
|
prompt: input.prompt,
|
|
274
333
|
system: joinInstructions(options.system, input.system),
|
|
275
334
|
parentRunId: options.parentRunId,
|
|
335
|
+
scope: options.scope,
|
|
276
336
|
maxSteps: options.maxSteps,
|
|
277
337
|
metadata: cloneMetadata(options.metadata, childMetadata)
|
|
278
338
|
});
|
|
@@ -311,21 +371,52 @@ export const createSubAgentTool = (options) => {
|
|
|
311
371
|
}
|
|
312
372
|
};
|
|
313
373
|
};
|
|
314
|
-
const
|
|
374
|
+
const saveStateWithRevision = async (store, state) => {
|
|
375
|
+
const expectedRevision = state.revision ?? 0;
|
|
376
|
+
const nextRevision = expectedRevision + 1;
|
|
377
|
+
const nextState = { ...state, revision: nextRevision };
|
|
378
|
+
await store.save(cloneState(nextState), { expectedRevision });
|
|
379
|
+
state.revision = nextRevision;
|
|
380
|
+
};
|
|
381
|
+
const claimAgentExecution = async (agent, state) => {
|
|
382
|
+
state.status = "running";
|
|
383
|
+
state.updatedAt = Date.now();
|
|
384
|
+
assertStateSize(agent, state);
|
|
385
|
+
if (agent.store) {
|
|
386
|
+
await saveStateWithRevision(agent.store, state);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const assertStateSize = (agent, state, policy) => {
|
|
390
|
+
const limit = policy?.maxStateBytes ?? agent.policy?.maxStateBytes ?? DEFAULT_AGENT_MAX_STATE_BYTES;
|
|
391
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
392
|
+
throw new ValidationError('Agent policy "maxStateBytes" must be a positive integer.');
|
|
393
|
+
}
|
|
394
|
+
const bytes = new TextEncoder().encode(JSON.stringify(state)).byteLength;
|
|
395
|
+
if (bytes > limit) {
|
|
396
|
+
throw new ValidationError(`Agent run state is ${bytes} bytes and exceeds maxStateBytes=${limit}. Offload large tool outputs to artifacts or raise the explicit limit.`);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const persistState = async (agent, state, policy) => {
|
|
315
400
|
state.updatedAt = Date.now();
|
|
316
|
-
|
|
401
|
+
assertStateSize(agent, state, policy);
|
|
402
|
+
if (agent.store) {
|
|
403
|
+
await saveStateWithRevision(agent.store, state);
|
|
404
|
+
}
|
|
317
405
|
await emitTelemetryEvent(agent, {
|
|
318
406
|
type: "state-saved",
|
|
319
407
|
runId: state.runId,
|
|
320
408
|
agentId: state.agentId,
|
|
321
409
|
status: state.status
|
|
322
410
|
});
|
|
323
|
-
await agent.memory?.save
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
411
|
+
await invokeOperationalHook(agent, "memory", "save", state.runId, agent.memory?.save
|
|
412
|
+
? () => agent.memory.save({
|
|
413
|
+
runId: state.runId,
|
|
414
|
+
agentId: state.agentId,
|
|
415
|
+
scope: state.scope,
|
|
416
|
+
state: cloneState(state),
|
|
417
|
+
metadata: state.metadata
|
|
418
|
+
})
|
|
419
|
+
: undefined, undefined);
|
|
329
420
|
};
|
|
330
421
|
const approvalsFromEvents = (messages) => getAgentApprovalRequests(messages);
|
|
331
422
|
const emitFinalizedStepTelemetry = async (agent, state, steps) => {
|
|
@@ -388,20 +479,34 @@ const runGuardrails = async (agent, state, stage, guardrails, requestFactory) =>
|
|
|
388
479
|
};
|
|
389
480
|
const resolveContext = async (agent, input) => {
|
|
390
481
|
ensureValidIdempotencyInput(input, agent.store);
|
|
391
|
-
|
|
482
|
+
const inputScope = input.scope ?? input.handoff?.scope;
|
|
483
|
+
ensureValidScope(inputScope);
|
|
484
|
+
let loadedState = input.state ? normalizeAgentRunState(input.state) : undefined;
|
|
392
485
|
let loadedByIdempotencyKey = false;
|
|
393
|
-
if (!loadedState && input.
|
|
394
|
-
loadedState = await agent.store
|
|
486
|
+
if (!loadedState && input.runId && agent.store) {
|
|
487
|
+
loadedState = await agent.store.load(input.runId, inputScope);
|
|
395
488
|
if (loadedState) {
|
|
396
|
-
loadedState =
|
|
397
|
-
loadedByIdempotencyKey = true;
|
|
489
|
+
loadedState = normalizeAgentRunState(loadedState);
|
|
398
490
|
}
|
|
399
491
|
}
|
|
400
|
-
if (!loadedState && input.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
492
|
+
if (!loadedState && input.idempotencyKey) {
|
|
493
|
+
const runId = input.runId ?? randomId("run");
|
|
494
|
+
const maxSteps = Math.max(1, input.maxSteps ?? agent.maxSteps ?? 1);
|
|
495
|
+
const metadata = cloneMetadata(agent.metadata, input.metadata, input.handoff?.metadata);
|
|
496
|
+
const prepared = await prepareFreshMessages(agent, input, runId);
|
|
497
|
+
const candidate = createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope);
|
|
498
|
+
const claim = await agent.store.claimIdempotencyKey(candidate);
|
|
499
|
+
if (claim.claimed) {
|
|
500
|
+
return {
|
|
501
|
+
state: normalizeAgentRunState(claim.state),
|
|
502
|
+
messages: prepared.messages,
|
|
503
|
+
remainingSteps: maxSteps,
|
|
504
|
+
memoryMessages: prepared.memoryMessages,
|
|
505
|
+
fresh: true
|
|
506
|
+
};
|
|
404
507
|
}
|
|
508
|
+
loadedState = normalizeAgentRunState(claim.state);
|
|
509
|
+
loadedByIdempotencyKey = true;
|
|
405
510
|
}
|
|
406
511
|
const normalizedInput = loadedState && loadedByIdempotencyKey
|
|
407
512
|
? { ...input, prompt: undefined, messages: undefined, system: undefined, handoff: undefined, state: loadedState }
|
|
@@ -418,6 +523,7 @@ const resolveContext = async (agent, input) => {
|
|
|
418
523
|
...loadedState,
|
|
419
524
|
schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
|
|
420
525
|
idempotencyKey: loadedState.idempotencyKey ?? input.idempotencyKey,
|
|
526
|
+
scope: loadedState.scope ?? inputScope,
|
|
421
527
|
agentId: loadedState.agentId ?? agent.id,
|
|
422
528
|
parentRunId: loadedState.parentRunId ?? input.parentRunId,
|
|
423
529
|
provider: agent.model.provider,
|
|
@@ -430,17 +536,105 @@ const resolveContext = async (agent, input) => {
|
|
|
430
536
|
},
|
|
431
537
|
messages: resumed.messages,
|
|
432
538
|
remainingSteps: Math.max(0, maxSteps - loadedState.currentStep),
|
|
433
|
-
memoryMessages: []
|
|
539
|
+
memoryMessages: [],
|
|
540
|
+
fresh: false
|
|
434
541
|
};
|
|
435
542
|
}
|
|
436
543
|
const runId = input.runId ?? randomId("run");
|
|
437
544
|
const maxSteps = Math.max(1, input.maxSteps ?? agent.maxSteps ?? 1);
|
|
438
545
|
const prepared = await prepareFreshMessages(agent, input, runId);
|
|
439
546
|
return {
|
|
440
|
-
state: createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey),
|
|
547
|
+
state: createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope),
|
|
441
548
|
messages: prepared.messages,
|
|
442
549
|
remainingSteps: maxSteps,
|
|
443
|
-
memoryMessages: prepared.memoryMessages
|
|
550
|
+
memoryMessages: prepared.memoryMessages,
|
|
551
|
+
fresh: true
|
|
552
|
+
};
|
|
553
|
+
};
|
|
554
|
+
const canonicalJson = (value) => {
|
|
555
|
+
if (value === null || typeof value !== "object") {
|
|
556
|
+
return JSON.stringify(value);
|
|
557
|
+
}
|
|
558
|
+
if (Array.isArray(value)) {
|
|
559
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
560
|
+
}
|
|
561
|
+
return `{${Object.keys(value)
|
|
562
|
+
.sort()
|
|
563
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
564
|
+
.join(",")}}`;
|
|
565
|
+
};
|
|
566
|
+
const durableToolCallId = (runId, step, providerToolCallId, toolName, input) => `tool_${createHash("sha256")
|
|
567
|
+
.update(`${runId}\0${step}\0${providerToolCallId}\0${toolName}\0${canonicalJson(input)}`)
|
|
568
|
+
.digest("hex")}`;
|
|
569
|
+
const wrapToolWithJournal = (agent, state, tool) => {
|
|
570
|
+
const store = agent.store;
|
|
571
|
+
if (!store?.claimToolExecution || !store.loadToolExecution || !store.completeToolExecution) {
|
|
572
|
+
return tool;
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
...tool,
|
|
576
|
+
execute: async (input, context) => {
|
|
577
|
+
if (!context) {
|
|
578
|
+
throw new ValidationError(`Durable tool "${tool.name}" requires an execution context.`);
|
|
579
|
+
}
|
|
580
|
+
const serializedInput = serializeJsonValue(input);
|
|
581
|
+
const step = context.step;
|
|
582
|
+
const toolCallId = durableToolCallId(state.runId, step, context.toolCall.id, tool.name, serializedInput);
|
|
583
|
+
const idempotencyKey = `${state.runId}:${toolCallId}`;
|
|
584
|
+
const now = Date.now();
|
|
585
|
+
const candidate = {
|
|
586
|
+
runId: state.runId,
|
|
587
|
+
scope: state.scope,
|
|
588
|
+
toolCallId,
|
|
589
|
+
toolName: tool.name,
|
|
590
|
+
status: "pending",
|
|
591
|
+
idempotencyKey,
|
|
592
|
+
revision: 0,
|
|
593
|
+
input: serializedInput,
|
|
594
|
+
updatedAt: now
|
|
595
|
+
};
|
|
596
|
+
const claim = await store.claimToolExecution(candidate);
|
|
597
|
+
if (!claim.claimed) {
|
|
598
|
+
if (claim.entry.status === "completed") {
|
|
599
|
+
return claim.entry.output ?? null;
|
|
600
|
+
}
|
|
601
|
+
if (claim.entry.status === "failed") {
|
|
602
|
+
throw new Error(claim.entry.error?.message ?? `Tool "${tool.name}" previously failed.`);
|
|
603
|
+
}
|
|
604
|
+
throw new ConflictError(`Tool "${tool.name}" has an indeterminate durable execution. Reconcile idempotency key "${claim.entry.idempotencyKey}" before retrying.`);
|
|
605
|
+
}
|
|
606
|
+
try {
|
|
607
|
+
const output = serializeJsonValue(await tool.execute(input, {
|
|
608
|
+
...context,
|
|
609
|
+
runId: state.runId,
|
|
610
|
+
idempotencyKey
|
|
611
|
+
}));
|
|
612
|
+
await store.completeToolExecution({
|
|
613
|
+
...claim.entry,
|
|
614
|
+
status: "completed",
|
|
615
|
+
output,
|
|
616
|
+
completedAt: Date.now(),
|
|
617
|
+
updatedAt: Date.now()
|
|
618
|
+
}, { expectedRevision: claim.entry.revision });
|
|
619
|
+
return output;
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
623
|
+
try {
|
|
624
|
+
await store.completeToolExecution({
|
|
625
|
+
...claim.entry,
|
|
626
|
+
status: "failed",
|
|
627
|
+
error: { message: normalizedError.message },
|
|
628
|
+
completedAt: Date.now(),
|
|
629
|
+
updatedAt: Date.now()
|
|
630
|
+
}, { expectedRevision: claim.entry.revision });
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// The original error is more useful; a running journal row blocks unsafe replay.
|
|
634
|
+
}
|
|
635
|
+
throw normalizedError;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
444
638
|
};
|
|
445
639
|
};
|
|
446
640
|
const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSignal = input.abortSignal) => {
|
|
@@ -450,6 +644,7 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
|
|
|
450
644
|
...subagent,
|
|
451
645
|
parentRunId: state.runId,
|
|
452
646
|
parentAgentId: state.agentId,
|
|
647
|
+
scope: state.scope,
|
|
453
648
|
onStart: async ({ toolName, childAgentId }) => {
|
|
454
649
|
await emitTelemetryEvent(agent, {
|
|
455
650
|
type: "subagent-start",
|
|
@@ -474,7 +669,24 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
|
|
|
474
669
|
}
|
|
475
670
|
tools[subagentTool.name] = subagentTool;
|
|
476
671
|
}
|
|
672
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
673
|
+
if (isCallableToolDefinition(tool)) {
|
|
674
|
+
tools[name] = wrapToolWithJournal(agent, state, tool);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
477
677
|
const finalTools = Object.keys(tools).length ? tools : undefined;
|
|
678
|
+
const budget = input.policy?.budget ?? agent.policy?.budget;
|
|
679
|
+
const runPolicy = resolveRunPolicy(agent, input);
|
|
680
|
+
let checkpointState = cloneState(state);
|
|
681
|
+
let reservedToolCalls = 0;
|
|
682
|
+
const requestedMaxTokens = input.maxTokens ?? agent.maxTokens;
|
|
683
|
+
const budgetStatus = budget ? getAgentBudgetStatus(state, budget) : undefined;
|
|
684
|
+
const tokenCeilings = [
|
|
685
|
+
requestedMaxTokens,
|
|
686
|
+
budgetStatus?.remaining.outputTokens,
|
|
687
|
+
budgetStatus?.remaining.totalTokens
|
|
688
|
+
].filter((value) => value !== undefined);
|
|
689
|
+
const maxTokens = tokenCeilings.length ? Math.min(...tokenCeilings) : undefined;
|
|
478
690
|
return {
|
|
479
691
|
model: agent.model,
|
|
480
692
|
messages,
|
|
@@ -485,9 +697,95 @@ const createGenerateOptions = (agent, state, input, messages, maxSteps, abortSig
|
|
|
485
697
|
onToolApprovalDecision: async (event) => {
|
|
486
698
|
await emitToolApprovalTelemetry(agent, state, event);
|
|
487
699
|
},
|
|
700
|
+
onBeforeModelStep: ({ step }) => {
|
|
701
|
+
if (!budget)
|
|
702
|
+
return;
|
|
703
|
+
const trigger = evaluateAgentBudgetPreflight(state, budget, {
|
|
704
|
+
operation: "model",
|
|
705
|
+
requiredSteps: Math.max(1, step - state.currentStep),
|
|
706
|
+
requestedOutputTokens: maxTokens
|
|
707
|
+
});
|
|
708
|
+
if (trigger) {
|
|
709
|
+
throw new GuardrailTriggeredError("input", trigger.reason ?? "Agent model budget preflight failed.", {
|
|
710
|
+
metadata: trigger.metadata
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
onModelStep: async ({ request, response, step, toolCalls }) => {
|
|
715
|
+
if (!agent.store)
|
|
716
|
+
return;
|
|
717
|
+
const responseSnapshot = snapshotResponse(response);
|
|
718
|
+
const approvals = getAgentApprovalRequests(responseSnapshot.messages);
|
|
719
|
+
const requestOffset = Math.min(checkpointState.messages.length, request.messages.length);
|
|
720
|
+
const timing = getGenerateTextStepTiming(request);
|
|
721
|
+
const finishedAt = timing?.finishedAt ?? Date.now();
|
|
722
|
+
const checkpointStep = {
|
|
723
|
+
index: step,
|
|
724
|
+
status: approvals.length ? "waiting_approval" : "completed",
|
|
725
|
+
startedAt: timing?.startedAt ?? finishedAt,
|
|
726
|
+
finishedAt,
|
|
727
|
+
request: snapshotRequest(request, requestOffset, request.messages.slice(requestOffset)),
|
|
728
|
+
response: responseSnapshot,
|
|
729
|
+
toolResults: []
|
|
730
|
+
};
|
|
731
|
+
checkpointState = {
|
|
732
|
+
...checkpointState,
|
|
733
|
+
status: approvals.length ? "waiting_approval" : toolCalls.length ? "running" : "completed",
|
|
734
|
+
messages: [...request.messages, ...responseSnapshot.messages],
|
|
735
|
+
steps: [...checkpointState.steps.filter((existing) => existing.index !== step), checkpointStep],
|
|
736
|
+
currentStep: step,
|
|
737
|
+
outputText: response.text ?? checkpointState.outputText,
|
|
738
|
+
finishReason: response.finishReason,
|
|
739
|
+
providerFinishReason: response.providerFinishReason,
|
|
740
|
+
usage: aggregateTokenUsage([checkpointState.usage, response.usage]),
|
|
741
|
+
pendingApprovals: approvals,
|
|
742
|
+
error: undefined,
|
|
743
|
+
updatedAt: Date.now()
|
|
744
|
+
};
|
|
745
|
+
await persistState(agent, checkpointState, runPolicy);
|
|
746
|
+
state.revision = checkpointState.revision;
|
|
747
|
+
},
|
|
748
|
+
onToolExecutionComplete: async ({ toolResults }) => {
|
|
749
|
+
if (!agent.store)
|
|
750
|
+
return;
|
|
751
|
+
const lastStep = checkpointState.steps.at(-1);
|
|
752
|
+
if (lastStep) {
|
|
753
|
+
lastStep.toolResults = [...lastStep.toolResults, ...toolResults];
|
|
754
|
+
}
|
|
755
|
+
checkpointState = {
|
|
756
|
+
...checkpointState,
|
|
757
|
+
status: "running",
|
|
758
|
+
messages: [
|
|
759
|
+
...checkpointState.messages,
|
|
760
|
+
...toolResults.map((toolResult) => ({
|
|
761
|
+
role: "tool",
|
|
762
|
+
parts: [{ type: "tool-result", toolResult }]
|
|
763
|
+
}))
|
|
764
|
+
],
|
|
765
|
+
toolResults: [...checkpointState.toolResults, ...toolResults],
|
|
766
|
+
updatedAt: Date.now()
|
|
767
|
+
};
|
|
768
|
+
await persistState(agent, checkpointState, runPolicy);
|
|
769
|
+
state.revision = checkpointState.revision;
|
|
770
|
+
},
|
|
771
|
+
stepOffset: state.currentStep,
|
|
772
|
+
onBeforeToolExecution: ({ toolCalls }) => {
|
|
773
|
+
if (!budget)
|
|
774
|
+
return;
|
|
775
|
+
reservedToolCalls += toolCalls.length;
|
|
776
|
+
const trigger = evaluateAgentBudgetPreflight(state, budget, {
|
|
777
|
+
operation: "tool",
|
|
778
|
+
requiredToolCalls: reservedToolCalls
|
|
779
|
+
});
|
|
780
|
+
if (trigger) {
|
|
781
|
+
throw new GuardrailTriggeredError("input", trigger.reason ?? "Agent tool budget preflight failed.", {
|
|
782
|
+
metadata: trigger.metadata
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
},
|
|
488
786
|
maxSteps,
|
|
489
787
|
temperature: input.temperature ?? agent.temperature,
|
|
490
|
-
maxTokens
|
|
788
|
+
maxTokens,
|
|
491
789
|
reasoning: input.reasoning ?? agent.reasoning,
|
|
492
790
|
providerOptions: input.providerOptions ?? agent.providerOptions,
|
|
493
791
|
abortSignal,
|
|
@@ -565,6 +863,89 @@ const createAgentAbortContext = (inputAbortSignal, policy) => {
|
|
|
565
863
|
isTimedOut: () => timedOut
|
|
566
864
|
};
|
|
567
865
|
};
|
|
866
|
+
const acquireAgentExecutionLease = async (agent, state, policy) => {
|
|
867
|
+
const store = agent.store;
|
|
868
|
+
if (policy?.leaseMode === "disabled" || !store?.acquireLease || !store.renewLease || !store.releaseLease) {
|
|
869
|
+
return {
|
|
870
|
+
supported: false,
|
|
871
|
+
cancelledState: () => undefined,
|
|
872
|
+
leaseLost: () => false,
|
|
873
|
+
release: async () => undefined
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
const ttlMs = policy?.leaseTtlMs ?? DEFAULT_AGENT_LEASE_TTL_MS;
|
|
877
|
+
const heartbeatMs = policy?.heartbeatMs ?? Math.max(250, Math.floor(ttlMs / 3));
|
|
878
|
+
const cancellationPollMs = policy?.cancellationPollMs ?? DEFAULT_AGENT_CANCELLATION_POLL_MS;
|
|
879
|
+
for (const [name, value] of [
|
|
880
|
+
["leaseTtlMs", ttlMs],
|
|
881
|
+
["heartbeatMs", heartbeatMs],
|
|
882
|
+
["cancellationPollMs", cancellationPollMs]
|
|
883
|
+
]) {
|
|
884
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
885
|
+
throw new ValidationError(`Agent policy "${name}" must be a positive integer.`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (heartbeatMs >= ttlMs) {
|
|
889
|
+
throw new ValidationError('Agent policy "heartbeatMs" must be less than "leaseTtlMs".');
|
|
890
|
+
}
|
|
891
|
+
const ownerId = randomId("worker");
|
|
892
|
+
const lease = await store.acquireLease(state.runId, { ownerId, ttlMs }, state.scope);
|
|
893
|
+
if (!lease) {
|
|
894
|
+
return undefined;
|
|
895
|
+
}
|
|
896
|
+
const controller = new AbortController();
|
|
897
|
+
let cancelled;
|
|
898
|
+
let lost = false;
|
|
899
|
+
let stopped = false;
|
|
900
|
+
let monitoring = false;
|
|
901
|
+
let lastHeartbeat = Date.now();
|
|
902
|
+
let lastCancellationPoll = 0;
|
|
903
|
+
const intervalMs = Math.max(25, Math.min(heartbeatMs, cancellationPollMs));
|
|
904
|
+
const timer = setInterval(async () => {
|
|
905
|
+
if (stopped || monitoring)
|
|
906
|
+
return;
|
|
907
|
+
monitoring = true;
|
|
908
|
+
const now = Date.now();
|
|
909
|
+
try {
|
|
910
|
+
if (now - lastHeartbeat >= heartbeatMs) {
|
|
911
|
+
const renewed = await store.renewLease?.(state.runId, { ownerId, ttlMs }, state.scope);
|
|
912
|
+
if (!renewed) {
|
|
913
|
+
lost = true;
|
|
914
|
+
controller.abort(new ConflictError(`Agent run "${state.runId}" lost its worker lease.`));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
lastHeartbeat = now;
|
|
918
|
+
}
|
|
919
|
+
if (now - lastCancellationPoll >= cancellationPollMs) {
|
|
920
|
+
const latest = await store.load(state.runId, state.scope);
|
|
921
|
+
lastCancellationPoll = now;
|
|
922
|
+
if (latest?.status === "cancel_requested" || latest?.status === "cancelled") {
|
|
923
|
+
cancelled = normalizeAgentRunState(latest);
|
|
924
|
+
controller.abort(new Error(latest.cancellationReason ?? "Agent run was cancelled."));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
lost = true;
|
|
930
|
+
controller.abort(error);
|
|
931
|
+
}
|
|
932
|
+
finally {
|
|
933
|
+
monitoring = false;
|
|
934
|
+
}
|
|
935
|
+
}, intervalMs);
|
|
936
|
+
timer.unref?.();
|
|
937
|
+
return {
|
|
938
|
+
supported: true,
|
|
939
|
+
signal: controller.signal,
|
|
940
|
+
cancelledState: () => cancelled,
|
|
941
|
+
leaseLost: () => lost,
|
|
942
|
+
release: async () => {
|
|
943
|
+
stopped = true;
|
|
944
|
+
clearInterval(timer);
|
|
945
|
+
await store.releaseLease?.(state.runId, ownerId, state.scope);
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
};
|
|
568
949
|
const emitRunStartTelemetry = async (agent, state, memoryMessages, approvals) => {
|
|
569
950
|
await emitTelemetryEvent(agent, {
|
|
570
951
|
type: "run-start",
|
|
@@ -632,6 +1013,7 @@ export class Agent {
|
|
|
632
1013
|
store;
|
|
633
1014
|
memory;
|
|
634
1015
|
onTelemetryEvent;
|
|
1016
|
+
hookFailurePolicy;
|
|
635
1017
|
constructor(definition) {
|
|
636
1018
|
Object.assign(this, createAgent(definition));
|
|
637
1019
|
this.model = definition.model;
|
|
@@ -656,7 +1038,8 @@ export class Agent {
|
|
|
656
1038
|
metadata: this.metadata,
|
|
657
1039
|
store: this.store,
|
|
658
1040
|
memory: this.memory,
|
|
659
|
-
onTelemetryEvent: this.onTelemetryEvent
|
|
1041
|
+
onTelemetryEvent: this.onTelemetryEvent,
|
|
1042
|
+
hookFailurePolicy: this.hookFailurePolicy
|
|
660
1043
|
});
|
|
661
1044
|
}
|
|
662
1045
|
run(input = {}) {
|
|
@@ -765,13 +1148,13 @@ export const runAgentGroup = async (agents, input = {}) => {
|
|
|
765
1148
|
};
|
|
766
1149
|
};
|
|
767
1150
|
export const cancelAgentRun = async (store, runId, options = {}) => {
|
|
768
|
-
const loadedState = await store.load(runId);
|
|
1151
|
+
const loadedState = await store.load(runId, options.scope);
|
|
769
1152
|
if (!loadedState) {
|
|
770
1153
|
return undefined;
|
|
771
1154
|
}
|
|
772
1155
|
const cancelledAt = Date.now();
|
|
773
1156
|
const status = options.mode === "final" ? "cancelled" : "cancel_requested";
|
|
774
|
-
const state =
|
|
1157
|
+
const state = normalizeAgentRunState({
|
|
775
1158
|
...loadedState,
|
|
776
1159
|
status,
|
|
777
1160
|
cancelledAt,
|
|
@@ -779,7 +1162,7 @@ export const cancelAgentRun = async (store, runId, options = {}) => {
|
|
|
779
1162
|
updatedAt: cancelledAt,
|
|
780
1163
|
error: undefined
|
|
781
1164
|
});
|
|
782
|
-
await store
|
|
1165
|
+
await saveStateWithRevision(store, state);
|
|
783
1166
|
return cloneState(state);
|
|
784
1167
|
};
|
|
785
1168
|
export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
@@ -788,7 +1171,7 @@ export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
|
788
1171
|
}
|
|
789
1172
|
const cancelledAt = Date.now();
|
|
790
1173
|
const status = options.mode === "final" ? "cancelled" : "cancel_requested";
|
|
791
|
-
const cancelState = (state) =>
|
|
1174
|
+
const cancelState = (state) => normalizeAgentRunState({
|
|
792
1175
|
...state,
|
|
793
1176
|
status,
|
|
794
1177
|
cancelledAt,
|
|
@@ -796,7 +1179,7 @@ export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
|
796
1179
|
updatedAt: cancelledAt,
|
|
797
1180
|
error: undefined
|
|
798
1181
|
});
|
|
799
|
-
const parent = await store.load(runId);
|
|
1182
|
+
const parent = await store.load(runId, options.scope);
|
|
800
1183
|
if (!parent) {
|
|
801
1184
|
return {
|
|
802
1185
|
parent: undefined,
|
|
@@ -806,7 +1189,7 @@ export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
|
806
1189
|
const visited = new Set([runId]);
|
|
807
1190
|
const children = [];
|
|
808
1191
|
const collectChildren = async (parentRunId) => {
|
|
809
|
-
const directChildren = await store.findByParentRunId?.(parentRunId);
|
|
1192
|
+
const directChildren = await store.findByParentRunId?.(parentRunId, options.scope);
|
|
810
1193
|
for (const child of directChildren ?? []) {
|
|
811
1194
|
if (visited.has(child.runId)) {
|
|
812
1195
|
continue;
|
|
@@ -819,9 +1202,9 @@ export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
|
819
1202
|
await collectChildren(runId);
|
|
820
1203
|
const cancelledParent = cancelState(parent);
|
|
821
1204
|
const cancelledChildren = children.map(cancelState);
|
|
822
|
-
await store
|
|
1205
|
+
await saveStateWithRevision(store, cancelledParent);
|
|
823
1206
|
for (const child of cancelledChildren) {
|
|
824
|
-
await store
|
|
1207
|
+
await saveStateWithRevision(store, child);
|
|
825
1208
|
}
|
|
826
1209
|
return {
|
|
827
1210
|
parent: cloneState(cancelledParent),
|
|
@@ -830,8 +1213,8 @@ export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
|
830
1213
|
};
|
|
831
1214
|
export const runAgent = async (agent, input = {}) => {
|
|
832
1215
|
const context = await resolveContext(agent, input);
|
|
833
|
-
await emitRunStartTelemetry(agent, context.state, context.memoryMessages, input.approvals);
|
|
834
1216
|
const currentStatus = normalizeApprovalStatus(context.state.status);
|
|
1217
|
+
const policy = resolveRunPolicy(agent, input);
|
|
835
1218
|
if (currentStatus === "completed" ||
|
|
836
1219
|
currentStatus === "cancelled" ||
|
|
837
1220
|
currentStatus === "cancel_requested" ||
|
|
@@ -843,34 +1226,83 @@ export const runAgent = async (agent, input = {}) => {
|
|
|
843
1226
|
context.state.status = currentStatus;
|
|
844
1227
|
return toOutput(context.state);
|
|
845
1228
|
}
|
|
1229
|
+
const supportsLeases = Boolean(agent.store?.acquireLease && agent.store.renewLease && agent.store.releaseLease);
|
|
1230
|
+
if (!context.fresh && currentStatus === "running" && !supportsLeases) {
|
|
1231
|
+
return toOutput(context.state);
|
|
1232
|
+
}
|
|
1233
|
+
const freshRequiresExistingClaim = context.fresh && Boolean(context.state.idempotencyKey);
|
|
1234
|
+
if (context.fresh && !freshRequiresExistingClaim) {
|
|
1235
|
+
await claimAgentExecution(agent, context.state);
|
|
1236
|
+
}
|
|
1237
|
+
const executionLease = await acquireAgentExecutionLease(agent, context.state, policy);
|
|
1238
|
+
if (!executionLease) {
|
|
1239
|
+
if (input.state) {
|
|
1240
|
+
throw new ConflictError(`Agent run "${context.state.runId}" is already owned by another worker.`);
|
|
1241
|
+
}
|
|
1242
|
+
const activeState = await agent.store?.load(context.state.runId, context.state.scope);
|
|
1243
|
+
return toOutput(activeState ? normalizeAgentRunState(activeState) : context.state);
|
|
1244
|
+
}
|
|
1245
|
+
try {
|
|
1246
|
+
if (!context.fresh || freshRequiresExistingClaim) {
|
|
1247
|
+
await claimAgentExecution(agent, context.state);
|
|
1248
|
+
}
|
|
1249
|
+
await emitRunStartTelemetry(agent, context.state, context.memoryMessages, input.approvals);
|
|
1250
|
+
}
|
|
1251
|
+
catch (error) {
|
|
1252
|
+
await executionLease.release();
|
|
1253
|
+
throw error;
|
|
1254
|
+
}
|
|
846
1255
|
if (context.remainingSteps === 0) {
|
|
847
1256
|
const state = createFailedState(context.state, "Agent exhausted maxSteps before reaching a terminal response.");
|
|
848
|
-
await persistState(agent, state);
|
|
1257
|
+
await persistState(agent, state, policy);
|
|
849
1258
|
await emitRunFinishTelemetry(agent, state);
|
|
1259
|
+
await executionLease.release();
|
|
850
1260
|
return toOutput(state);
|
|
851
1261
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1262
|
+
let inputGuardrail;
|
|
1263
|
+
try {
|
|
1264
|
+
inputGuardrail = await runGuardrails(agent, context.state, "input", agent.inputGuardrails, () => ({
|
|
1265
|
+
runId: context.state.runId,
|
|
1266
|
+
agentId: context.state.agentId,
|
|
1267
|
+
state: cloneState(context.state),
|
|
1268
|
+
messages: context.messages,
|
|
1269
|
+
metadata: context.state.metadata
|
|
1270
|
+
}));
|
|
1271
|
+
}
|
|
1272
|
+
catch (error) {
|
|
1273
|
+
await executionLease.release();
|
|
1274
|
+
throw error;
|
|
1275
|
+
}
|
|
858
1276
|
if (inputGuardrail) {
|
|
859
1277
|
const failedState = applyGuardrailFailure(context.state, "input", inputGuardrail);
|
|
860
|
-
await persistState(agent, failedState);
|
|
1278
|
+
await persistState(agent, failedState, policy);
|
|
861
1279
|
await emitRunFinishTelemetry(agent, failedState);
|
|
1280
|
+
await executionLease.release();
|
|
862
1281
|
return toOutput(failedState);
|
|
863
1282
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1283
|
+
try {
|
|
1284
|
+
await emitTelemetryEvent(agent, {
|
|
1285
|
+
type: "step-start",
|
|
1286
|
+
runId: context.state.runId,
|
|
1287
|
+
agentId: context.state.agentId,
|
|
1288
|
+
stepIndex: context.state.currentStep + 1
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
catch (error) {
|
|
1292
|
+
await executionLease.release();
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
const abortContext = createAgentAbortContext(mergeAbortSignals(input.abortSignal, executionLease.signal), policy);
|
|
872
1296
|
try {
|
|
873
1297
|
const result = await withAgentPolicyTimeout(generateText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, abortContext.signal)), abortContext);
|
|
1298
|
+
const cancelled = executionLease.cancelledState();
|
|
1299
|
+
if (cancelled) {
|
|
1300
|
+
await emitRunFinishTelemetry(agent, cancelled);
|
|
1301
|
+
return toOutput(cancelled);
|
|
1302
|
+
}
|
|
1303
|
+
if (executionLease.leaseLost()) {
|
|
1304
|
+
throw new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
1305
|
+
}
|
|
874
1306
|
const newSteps = mapSteps(result.steps, context.state.currentStep, result.toolResults);
|
|
875
1307
|
let output = finalizeState(context.state, result, newSteps, result.toolResults);
|
|
876
1308
|
const outputGuardrail = await runGuardrails(agent, output.state, "output", agent.outputGuardrails, () => ({
|
|
@@ -885,71 +1317,66 @@ export const runAgent = async (agent, input = {}) => {
|
|
|
885
1317
|
}
|
|
886
1318
|
await emitFinalizedStepTelemetry(agent, output.state, newSteps);
|
|
887
1319
|
await emitApprovalTelemetry(agent, output.state, approvalsFromEvents(newSteps.flatMap((step) => step.response?.messages ?? [])));
|
|
888
|
-
await persistState(agent, output.state);
|
|
1320
|
+
await persistState(agent, output.state, policy);
|
|
889
1321
|
await emitRunFinishTelemetry(agent, output.state);
|
|
890
1322
|
return output;
|
|
891
1323
|
}
|
|
892
1324
|
catch (error) {
|
|
1325
|
+
const cancelled = executionLease.cancelledState();
|
|
1326
|
+
if (cancelled) {
|
|
1327
|
+
await emitRunFinishTelemetry(agent, cancelled);
|
|
1328
|
+
return toOutput(cancelled);
|
|
1329
|
+
}
|
|
1330
|
+
if (executionLease.leaseLost()) {
|
|
1331
|
+
throw new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
1332
|
+
}
|
|
893
1333
|
if (error instanceof AgentPolicyTimeoutError || abortContext.isTimedOut()) {
|
|
894
1334
|
const status = policy?.onTimeout === "cancel-requested" ? "cancel_requested" : "timed_out";
|
|
895
1335
|
const message = error instanceof Error ? error.message : `Agent run timed out after ${policy?.timeoutMs}ms.`;
|
|
896
|
-
const
|
|
897
|
-
|
|
1336
|
+
const durableState = agent.store
|
|
1337
|
+
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
1338
|
+
: context.state;
|
|
1339
|
+
const timedOutState = createTerminalState(durableState, status, message);
|
|
1340
|
+
await persistState(agent, timedOutState, policy);
|
|
898
1341
|
await emitRunFinishTelemetry(agent, timedOutState);
|
|
899
1342
|
return toOutput(timedOutState);
|
|
900
1343
|
}
|
|
901
|
-
const
|
|
902
|
-
|
|
1344
|
+
const durableState = agent.store
|
|
1345
|
+
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
1346
|
+
: context.state;
|
|
1347
|
+
const failedState = createFailedState(durableState, error instanceof Error ? error.message : String(error));
|
|
1348
|
+
await persistState(agent, failedState, policy);
|
|
903
1349
|
await emitRunFinishTelemetry(agent, failedState);
|
|
904
1350
|
throw error;
|
|
905
1351
|
}
|
|
1352
|
+
finally {
|
|
1353
|
+
await executionLease.release();
|
|
1354
|
+
}
|
|
906
1355
|
};
|
|
907
1356
|
export const streamAgent = (agent, input = {}) => {
|
|
908
|
-
const
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
subscriber(value);
|
|
915
|
-
}
|
|
916
|
-
if (value.done) {
|
|
917
|
-
done = true;
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
const createEventStream = async function* () {
|
|
921
|
-
let cursor = 0;
|
|
922
|
-
while (true) {
|
|
923
|
-
while (cursor < history.length) {
|
|
924
|
-
const item = history[cursor];
|
|
925
|
-
cursor += 1;
|
|
926
|
-
if (item.done) {
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
yield item.value;
|
|
930
|
-
}
|
|
931
|
-
if (done) {
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
await new Promise((resolve) => {
|
|
935
|
-
const subscriber = (value) => {
|
|
936
|
-
subscribers.delete(subscriber);
|
|
937
|
-
resolve(value);
|
|
938
|
-
};
|
|
939
|
-
subscribers.add(subscriber);
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
};
|
|
1357
|
+
const policy = resolveRunPolicy(agent, input);
|
|
1358
|
+
const broadcast = new BoundedReplayBroadcast({
|
|
1359
|
+
maxHistory: policy?.maxStreamEvents ?? 4096
|
|
1360
|
+
});
|
|
1361
|
+
const publish = (event, terminal = false) => broadcast.publish(event, { terminal });
|
|
1362
|
+
let activeLease;
|
|
943
1363
|
const runner = (async () => {
|
|
944
1364
|
const context = await resolveContext(agent, input);
|
|
945
|
-
await emitRunStartTelemetry(agent, context.state, context.memoryMessages, input.approvals);
|
|
946
1365
|
const currentStatus = normalizeApprovalStatus(context.state.status);
|
|
1366
|
+
const supportsLeases = Boolean(agent.store?.acquireLease && agent.store.renewLease && agent.store.releaseLease);
|
|
1367
|
+
if (!context.fresh && currentStatus === "running" && !supportsLeases) {
|
|
1368
|
+
broadcast.close();
|
|
1369
|
+
return {
|
|
1370
|
+
output: toOutput(context.state),
|
|
1371
|
+
textStream: emptyAsyncIterable()
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
947
1374
|
if (currentStatus === "completed" ||
|
|
948
1375
|
currentStatus === "cancelled" ||
|
|
949
1376
|
currentStatus === "cancel_requested" ||
|
|
950
1377
|
currentStatus === "timed_out") {
|
|
951
1378
|
context.state.status = currentStatus;
|
|
952
|
-
|
|
1379
|
+
broadcast.close();
|
|
953
1380
|
return {
|
|
954
1381
|
output: toOutput(context.state),
|
|
955
1382
|
textStream: emptyAsyncIterable()
|
|
@@ -957,78 +1384,100 @@ export const streamAgent = (agent, input = {}) => {
|
|
|
957
1384
|
}
|
|
958
1385
|
if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0 && !input.approvals?.length) {
|
|
959
1386
|
context.state.status = currentStatus;
|
|
960
|
-
|
|
1387
|
+
broadcast.close();
|
|
961
1388
|
return {
|
|
962
1389
|
output: toOutput(context.state),
|
|
963
1390
|
textStream: emptyAsyncIterable()
|
|
964
1391
|
};
|
|
965
1392
|
}
|
|
1393
|
+
const freshRequiresExistingClaim = context.fresh && Boolean(context.state.idempotencyKey);
|
|
1394
|
+
if (context.fresh && !freshRequiresExistingClaim) {
|
|
1395
|
+
await claimAgentExecution(agent, context.state);
|
|
1396
|
+
}
|
|
1397
|
+
const executionLease = await acquireAgentExecutionLease(agent, context.state, policy);
|
|
1398
|
+
if (!executionLease) {
|
|
1399
|
+
if (input.state) {
|
|
1400
|
+
throw new ConflictError(`Agent run "${context.state.runId}" is already owned by another worker.`);
|
|
1401
|
+
}
|
|
1402
|
+
const activeState = await agent.store?.load(context.state.runId, context.state.scope);
|
|
1403
|
+
broadcast.close();
|
|
1404
|
+
return {
|
|
1405
|
+
output: toOutput(activeState ? normalizeAgentRunState(activeState) : context.state),
|
|
1406
|
+
textStream: emptyAsyncIterable()
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
activeLease = executionLease;
|
|
1410
|
+
try {
|
|
1411
|
+
if (!context.fresh || freshRequiresExistingClaim) {
|
|
1412
|
+
await claimAgentExecution(agent, context.state);
|
|
1413
|
+
}
|
|
1414
|
+
await emitRunStartTelemetry(agent, context.state, context.memoryMessages, input.approvals);
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
await executionLease.release();
|
|
1418
|
+
throw error;
|
|
1419
|
+
}
|
|
966
1420
|
if (context.remainingSteps === 0) {
|
|
967
1421
|
const state = createFailedState(context.state, "Agent exhausted maxSteps before reaching a terminal response.");
|
|
968
|
-
await persistState(agent, state);
|
|
1422
|
+
await persistState(agent, state, policy);
|
|
969
1423
|
await emitRunFinishTelemetry(agent, state);
|
|
970
|
-
|
|
1424
|
+
await executionLease.release();
|
|
1425
|
+
broadcast.close();
|
|
971
1426
|
return {
|
|
972
1427
|
output: toOutput(state),
|
|
973
1428
|
textStream: emptyAsyncIterable()
|
|
974
1429
|
};
|
|
975
1430
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1431
|
+
let inputGuardrail;
|
|
1432
|
+
try {
|
|
1433
|
+
inputGuardrail = await runGuardrails(agent, context.state, "input", agent.inputGuardrails, () => ({
|
|
1434
|
+
runId: context.state.runId,
|
|
1435
|
+
agentId: context.state.agentId,
|
|
1436
|
+
state: cloneState(context.state),
|
|
1437
|
+
messages: context.messages,
|
|
1438
|
+
metadata: context.state.metadata
|
|
1439
|
+
}));
|
|
1440
|
+
}
|
|
1441
|
+
catch (error) {
|
|
1442
|
+
await executionLease.release();
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
982
1445
|
if (inputGuardrail) {
|
|
983
1446
|
const failedState = applyGuardrailFailure(context.state, "input", inputGuardrail);
|
|
984
|
-
await persistState(agent, failedState);
|
|
1447
|
+
await persistState(agent, failedState, policy);
|
|
985
1448
|
await emitRunFinishTelemetry(agent, failedState);
|
|
986
|
-
publish({
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
status: failedState.status,
|
|
1000
|
-
state: failedState
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
publish({ done: true, value: undefined });
|
|
1449
|
+
await publish({
|
|
1450
|
+
type: "error",
|
|
1451
|
+
error: new GuardrailTriggeredError("input", failedState.error?.message ?? "Agent input guardrail triggered.", {
|
|
1452
|
+
metadata: inputGuardrail.metadata
|
|
1453
|
+
})
|
|
1454
|
+
}, true);
|
|
1455
|
+
await publish({
|
|
1456
|
+
type: "agent-run-finish",
|
|
1457
|
+
status: failedState.status,
|
|
1458
|
+
state: failedState
|
|
1459
|
+
}, true);
|
|
1460
|
+
broadcast.close();
|
|
1461
|
+
await executionLease.release();
|
|
1004
1462
|
return {
|
|
1005
1463
|
output: toOutput(failedState),
|
|
1006
1464
|
textStream: emptyAsyncIterable()
|
|
1007
1465
|
};
|
|
1008
1466
|
}
|
|
1009
|
-
publish({
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
currentStep: context.state.currentStep + 1,
|
|
1014
|
-
maxSteps: context.state.maxSteps
|
|
1015
|
-
}
|
|
1467
|
+
await publish({
|
|
1468
|
+
type: "agent-run-start",
|
|
1469
|
+
currentStep: context.state.currentStep + 1,
|
|
1470
|
+
maxSteps: context.state.maxSteps
|
|
1016
1471
|
});
|
|
1017
1472
|
for (const approval of input.approvals ?? []) {
|
|
1018
|
-
publish({
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
type: "agent-approval-resolved",
|
|
1022
|
-
approval
|
|
1023
|
-
}
|
|
1473
|
+
await publish({
|
|
1474
|
+
type: "agent-approval-resolved",
|
|
1475
|
+
approval
|
|
1024
1476
|
});
|
|
1025
1477
|
}
|
|
1026
|
-
publish({
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
type: "agent-step-start",
|
|
1030
|
-
stepIndex: context.state.currentStep + 1
|
|
1031
|
-
}
|
|
1478
|
+
await publish({
|
|
1479
|
+
type: "agent-step-start",
|
|
1480
|
+
stepIndex: context.state.currentStep + 1
|
|
1032
1481
|
});
|
|
1033
1482
|
await emitTelemetryEvent(agent, {
|
|
1034
1483
|
type: "step-start",
|
|
@@ -1036,13 +1485,12 @@ export const streamAgent = (agent, input = {}) => {
|
|
|
1036
1485
|
agentId: context.state.agentId,
|
|
1037
1486
|
stepIndex: context.state.currentStep + 1
|
|
1038
1487
|
});
|
|
1039
|
-
const
|
|
1040
|
-
const abortContext = createAgentAbortContext(input.abortSignal, policy);
|
|
1488
|
+
const abortContext = createAgentAbortContext(mergeAbortSignals(input.abortSignal, executionLease.signal), policy);
|
|
1041
1489
|
const streamResult = streamText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, abortContext.signal));
|
|
1042
1490
|
const approvalRequests = [];
|
|
1043
1491
|
const eventRelay = (async () => {
|
|
1044
1492
|
for await (const event of streamResult.eventStream) {
|
|
1045
|
-
publish(
|
|
1493
|
+
await publish(event);
|
|
1046
1494
|
if (event.type === "provider-data" &&
|
|
1047
1495
|
typeof event.data === "object" &&
|
|
1048
1496
|
event.data !== null &&
|
|
@@ -1060,12 +1508,9 @@ export const streamAgent = (agent, input = {}) => {
|
|
|
1060
1508
|
rawData: event.data
|
|
1061
1509
|
};
|
|
1062
1510
|
approvalRequests.push(approval);
|
|
1063
|
-
publish({
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
type: "agent-approval-request",
|
|
1067
|
-
approval
|
|
1068
|
-
}
|
|
1511
|
+
await publish({
|
|
1512
|
+
type: "agent-approval-request",
|
|
1513
|
+
approval
|
|
1069
1514
|
});
|
|
1070
1515
|
await emitTelemetryEvent(agent, {
|
|
1071
1516
|
type: "approval-request",
|
|
@@ -1079,6 +1524,22 @@ export const streamAgent = (agent, input = {}) => {
|
|
|
1079
1524
|
const output = (async () => {
|
|
1080
1525
|
try {
|
|
1081
1526
|
const final = await withAgentPolicyTimeout(eventRelay.then(() => streamResult.collect()), abortContext);
|
|
1527
|
+
const cancelled = executionLease.cancelledState();
|
|
1528
|
+
if (cancelled) {
|
|
1529
|
+
await emitRunFinishTelemetry(agent, cancelled);
|
|
1530
|
+
await publish({
|
|
1531
|
+
type: "agent-run-finish",
|
|
1532
|
+
status: cancelled.status,
|
|
1533
|
+
state: cancelled
|
|
1534
|
+
}, true);
|
|
1535
|
+
broadcast.close();
|
|
1536
|
+
return toOutput(cancelled);
|
|
1537
|
+
}
|
|
1538
|
+
if (executionLease.leaseLost()) {
|
|
1539
|
+
const conflict = new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
1540
|
+
broadcast.fail(conflict);
|
|
1541
|
+
throw conflict;
|
|
1542
|
+
}
|
|
1082
1543
|
const newSteps = mapSteps(final.steps, context.state.currentStep, final.toolResults);
|
|
1083
1544
|
let result = finalizeState(context.state, final, newSteps, final.toolResults);
|
|
1084
1545
|
const outputGuardrail = await runGuardrails(agent, result.state, "output", agent.outputGuardrails, () => ({
|
|
@@ -1090,94 +1551,102 @@ export const streamAgent = (agent, input = {}) => {
|
|
|
1090
1551
|
}));
|
|
1091
1552
|
if (outputGuardrail) {
|
|
1092
1553
|
result = toOutput(applyGuardrailFailure(result.state, "output", outputGuardrail));
|
|
1093
|
-
publish({
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
error: new GuardrailTriggeredError("output", result.state.error?.message ?? "Agent output guardrail triggered.", { metadata: outputGuardrail.metadata })
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1554
|
+
await publish({
|
|
1555
|
+
type: "error",
|
|
1556
|
+
error: new GuardrailTriggeredError("output", result.state.error?.message ?? "Agent output guardrail triggered.", { metadata: outputGuardrail.metadata })
|
|
1557
|
+
}, true);
|
|
1100
1558
|
}
|
|
1101
1559
|
for (const step of newSteps) {
|
|
1102
|
-
publish({
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
type: "agent-step-finish",
|
|
1106
|
-
step
|
|
1107
|
-
}
|
|
1560
|
+
await publish({
|
|
1561
|
+
type: "agent-step-finish",
|
|
1562
|
+
step
|
|
1108
1563
|
});
|
|
1109
1564
|
}
|
|
1110
1565
|
await emitFinalizedStepTelemetry(agent, result.state, newSteps);
|
|
1111
1566
|
if (!approvalRequests.length) {
|
|
1112
1567
|
await emitApprovalTelemetry(agent, result.state, approvalsFromEvents(newSteps.flatMap((step) => step.response?.messages ?? [])));
|
|
1113
1568
|
}
|
|
1114
|
-
await persistState(agent, result.state);
|
|
1569
|
+
await persistState(agent, result.state, policy);
|
|
1115
1570
|
await emitRunFinishTelemetry(agent, result.state);
|
|
1116
|
-
publish({
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
publish({ done: true, value: undefined });
|
|
1571
|
+
await publish({
|
|
1572
|
+
type: "agent-run-finish",
|
|
1573
|
+
status: result.status,
|
|
1574
|
+
state: result.state
|
|
1575
|
+
}, true);
|
|
1576
|
+
broadcast.close();
|
|
1125
1577
|
return result;
|
|
1126
1578
|
}
|
|
1127
1579
|
catch (error) {
|
|
1580
|
+
const cancelled = executionLease.cancelledState();
|
|
1581
|
+
if (cancelled) {
|
|
1582
|
+
await emitRunFinishTelemetry(agent, cancelled);
|
|
1583
|
+
await publish({
|
|
1584
|
+
type: "agent-run-finish",
|
|
1585
|
+
status: cancelled.status,
|
|
1586
|
+
state: cancelled
|
|
1587
|
+
}, true);
|
|
1588
|
+
broadcast.close();
|
|
1589
|
+
return toOutput(cancelled);
|
|
1590
|
+
}
|
|
1591
|
+
if (executionLease.leaseLost()) {
|
|
1592
|
+
const conflict = new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
1593
|
+
broadcast.fail(conflict);
|
|
1594
|
+
throw conflict;
|
|
1595
|
+
}
|
|
1128
1596
|
if (error instanceof AgentPolicyTimeoutError || abortContext.isTimedOut()) {
|
|
1129
1597
|
const status = policy?.onTimeout === "cancel-requested" ? "cancel_requested" : "timed_out";
|
|
1130
1598
|
const message = error instanceof Error ? error.message : `Agent run timed out after ${policy?.timeoutMs}ms.`;
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1599
|
+
const durableState = agent.store
|
|
1600
|
+
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
1601
|
+
: context.state;
|
|
1602
|
+
const timedOutState = createTerminalState(durableState, status, message);
|
|
1603
|
+
await persistState(agent, timedOutState, policy);
|
|
1133
1604
|
await emitRunFinishTelemetry(agent, timedOutState);
|
|
1134
|
-
publish({
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
type: "agent-run-finish",
|
|
1145
|
-
status: timedOutState.status,
|
|
1146
|
-
state: timedOutState
|
|
1147
|
-
}
|
|
1148
|
-
});
|
|
1149
|
-
publish({ done: true, value: undefined });
|
|
1605
|
+
await publish({
|
|
1606
|
+
type: "error",
|
|
1607
|
+
error: new AgentPolicyTimeoutError(policy?.timeoutMs ?? 0)
|
|
1608
|
+
}, true);
|
|
1609
|
+
await publish({
|
|
1610
|
+
type: "agent-run-finish",
|
|
1611
|
+
status: timedOutState.status,
|
|
1612
|
+
state: timedOutState
|
|
1613
|
+
}, true);
|
|
1614
|
+
broadcast.close();
|
|
1150
1615
|
return toOutput(timedOutState);
|
|
1151
1616
|
}
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1617
|
+
const durableState = agent.store
|
|
1618
|
+
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
1619
|
+
: context.state;
|
|
1620
|
+
const failedState = createFailedState(durableState, error instanceof Error ? error.message : String(error));
|
|
1621
|
+
await persistState(agent, failedState, policy);
|
|
1154
1622
|
await emitRunFinishTelemetry(agent, failedState);
|
|
1155
|
-
publish({
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
type: "agent-run-finish",
|
|
1166
|
-
status: failedState.status,
|
|
1167
|
-
state: failedState
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
publish({ done: true, value: undefined });
|
|
1623
|
+
await publish({
|
|
1624
|
+
type: "error",
|
|
1625
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1626
|
+
}, true);
|
|
1627
|
+
await publish({
|
|
1628
|
+
type: "agent-run-finish",
|
|
1629
|
+
status: failedState.status,
|
|
1630
|
+
state: failedState
|
|
1631
|
+
}, true);
|
|
1632
|
+
broadcast.close();
|
|
1171
1633
|
throw error;
|
|
1172
1634
|
}
|
|
1635
|
+
finally {
|
|
1636
|
+
await executionLease.release();
|
|
1637
|
+
}
|
|
1173
1638
|
})();
|
|
1174
1639
|
return {
|
|
1175
1640
|
output,
|
|
1176
1641
|
textStream: streamResult.textStream
|
|
1177
1642
|
};
|
|
1178
|
-
})()
|
|
1643
|
+
})().catch(async (error) => {
|
|
1644
|
+
await activeLease?.release();
|
|
1645
|
+
broadcast.fail(error);
|
|
1646
|
+
throw error;
|
|
1647
|
+
});
|
|
1179
1648
|
return {
|
|
1180
|
-
eventStream:
|
|
1649
|
+
eventStream: broadcast.stream(),
|
|
1181
1650
|
textStream: (async function* () {
|
|
1182
1651
|
const started = await runner;
|
|
1183
1652
|
for await (const chunk of started.textStream) {
|