@zhivex-ai/core 1.17.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/agent/approvals.d.ts +7 -0
- package/dist/agent/approvals.d.ts.map +1 -0
- package/dist/agent/approvals.js +97 -0
- package/dist/agent/approvals.js.map +1 -0
- package/dist/agent/cancellation.d.ts +4 -0
- package/dist/agent/cancellation.d.ts.map +1 -0
- package/dist/agent/cancellation.js +69 -0
- package/dist/agent/cancellation.js.map +1 -0
- package/dist/agent/common.d.ts +20 -0
- package/dist/agent/common.d.ts.map +1 -0
- package/dist/agent/common.js +150 -0
- package/dist/agent/common.js.map +1 -0
- package/dist/agent/compaction.d.ts +6 -0
- package/dist/agent/compaction.d.ts.map +1 -0
- package/dist/agent/compaction.js +156 -0
- package/dist/agent/compaction.js.map +1 -0
- package/dist/agent/context.d.ts +58 -0
- package/dist/agent/context.d.ts.map +1 -0
- package/dist/agent/context.js +267 -0
- package/dist/agent/context.js.map +1 -0
- package/dist/agent/definition.d.ts +4 -0
- package/dist/agent/definition.d.ts.map +1 -0
- package/dist/agent/definition.js +35 -0
- package/dist/agent/definition.js.map +1 -0
- package/dist/agent/execution.d.ts +14 -0
- package/dist/agent/execution.d.ts.map +1 -0
- package/dist/agent/execution.js +1062 -0
- package/dist/agent/execution.js.map +1 -0
- package/dist/agent/groups.d.ts +3 -0
- package/dist/agent/groups.d.ts.map +1 -0
- package/dist/agent/groups.js +130 -0
- package/dist/agent/groups.js.map +1 -0
- package/dist/agent/guardrails.d.ts +4 -0
- package/dist/agent/guardrails.d.ts.map +1 -0
- package/dist/agent/guardrails.js +29 -0
- package/dist/agent/guardrails.js.map +1 -0
- package/dist/agent/instance.d.ts +42 -0
- package/dist/agent/instance.d.ts.map +1 -0
- package/dist/agent/instance.js +82 -0
- package/dist/agent/instance.js.map +1 -0
- package/dist/agent/lifecycle.d.ts +31 -0
- package/dist/agent/lifecycle.d.ts.map +1 -0
- package/dist/agent/lifecycle.js +184 -0
- package/dist/agent/lifecycle.js.map +1 -0
- package/dist/agent/state.d.ts +6 -0
- package/dist/agent/state.d.ts.map +1 -0
- package/dist/agent/state.js +119 -0
- package/dist/agent/state.js.map +1 -0
- package/dist/agent/telemetry.d.ts +12 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +176 -0
- package/dist/agent/telemetry.js.map +1 -0
- package/dist/agent/tool-execution.d.ts +6 -0
- package/dist/agent/tool-execution.d.ts.map +1 -0
- package/dist/agent/tool-execution.js +176 -0
- package/dist/agent/tool-execution.js.map +1 -0
- package/dist/agent-evaluation.d.ts.map +1 -1
- package/dist/agent-evaluation.js +2 -0
- package/dist/agent-evaluation.js.map +1 -1
- package/dist/agent-reconciliation.d.ts +12 -0
- package/dist/agent-reconciliation.d.ts.map +1 -0
- package/dist/agent-reconciliation.js +87 -0
- package/dist/agent-reconciliation.js.map +1 -0
- package/dist/agent-run-view.d.ts +9 -0
- package/dist/agent-run-view.d.ts.map +1 -0
- package/dist/agent-run-view.js +42 -0
- package/dist/agent-run-view.js.map +1 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +34 -0
- package/dist/agent-state.js.map +1 -1
- package/dist/agent-store/file.d.ts +11 -0
- package/dist/agent-store/file.d.ts.map +1 -0
- package/dist/agent-store/file.js +534 -0
- package/dist/agent-store/file.js.map +1 -0
- package/dist/agent-store/memory.d.ts +9 -0
- package/dist/agent-store/memory.d.ts.map +1 -0
- package/dist/agent-store/memory.js +198 -0
- package/dist/agent-store/memory.js.map +1 -0
- package/dist/agent-store/postgres.d.ts +4 -0
- package/dist/agent-store/postgres.d.ts.map +1 -0
- package/dist/agent-store/postgres.js +374 -0
- package/dist/agent-store/postgres.js.map +1 -0
- package/dist/agent-store/shared.d.ts +19 -0
- package/dist/agent-store/shared.d.ts.map +1 -0
- package/dist/agent-store/shared.js +137 -0
- package/dist/agent-store/shared.js.map +1 -0
- package/dist/agent-store/sqlite.d.ts +4 -0
- package/dist/agent-store/sqlite.d.ts.map +1 -0
- package/dist/agent-store/sqlite.js +362 -0
- package/dist/agent-store/sqlite.js.map +1 -0
- package/dist/agent-store.d.ts +5 -21
- package/dist/agent-store.d.ts.map +1 -1
- package/dist/agent-store.js +5 -1540
- package/dist/agent-store.js.map +1 -1
- package/dist/agent.d.ts +6 -57
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +6 -2622
- package/dist/agent.js.map +1 -1
- package/dist/agents-entry.d.ts +19 -0
- package/dist/agents-entry.d.ts.map +1 -0
- package/dist/agents-entry.js +14 -0
- package/dist/agents-entry.js.map +1 -0
- package/dist/api-stability.d.ts.map +1 -1
- package/dist/api-stability.js +1 -0
- package/dist/api-stability.js.map +1 -1
- package/dist/artifact/file.d.ts +6 -0
- package/dist/artifact/file.d.ts.map +1 -0
- package/dist/artifact/file.js +380 -0
- package/dist/artifact/file.js.map +1 -0
- package/dist/artifact/memory.d.ts +3 -0
- package/dist/artifact/memory.d.ts.map +1 -0
- package/dist/artifact/memory.js +80 -0
- package/dist/artifact/memory.js.map +1 -0
- package/dist/artifact/postgres.d.ts +3 -0
- package/dist/artifact/postgres.d.ts.map +1 -0
- package/dist/artifact/postgres.js +283 -0
- package/dist/artifact/postgres.js.map +1 -0
- package/dist/artifact/shared.d.ts +208 -0
- package/dist/artifact/shared.d.ts.map +1 -0
- package/dist/artifact/shared.js +523 -0
- package/dist/artifact/shared.js.map +1 -0
- package/dist/artifact/sqlite.d.ts +3 -0
- package/dist/artifact/sqlite.d.ts.map +1 -0
- package/dist/artifact/sqlite.js +283 -0
- package/dist/artifact/sqlite.js.map +1 -0
- package/dist/artifact.d.ts +6 -186
- package/dist/artifact.d.ts.map +1 -1
- package/dist/artifact.js +6 -1536
- package/dist/artifact.js.map +1 -1
- package/dist/catalog-contracts.d.ts +52 -0
- package/dist/catalog-contracts.d.ts.map +1 -0
- package/dist/catalog-contracts.js +250 -0
- package/dist/catalog-contracts.js.map +1 -0
- package/dist/catalog.d.ts +3 -52
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +3 -249
- package/dist/catalog.js.map +1 -1
- package/dist/generate-cache-file.d.ts +11 -0
- package/dist/generate-cache-file.d.ts.map +1 -0
- package/dist/generate-cache-file.js +124 -0
- package/dist/generate-cache-file.js.map +1 -0
- package/dist/generate-text.d.ts +2 -5
- package/dist/generate-text.d.ts.map +1 -1
- package/dist/generate-text.js +3 -18
- package/dist/generate-text.js.map +1 -1
- package/dist/generation-entry.d.ts +6 -0
- package/dist/generation-entry.d.ts.map +1 -0
- package/dist/generation-entry.js +6 -0
- package/dist/generation-entry.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware-runtime.d.ts +35 -0
- package/dist/middleware-runtime.d.ts.map +1 -0
- package/dist/middleware-runtime.js +532 -0
- package/dist/middleware-runtime.js.map +1 -0
- package/dist/middleware.d.ts +3 -43
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +3 -653
- package/dist/middleware.js.map +1 -1
- package/dist/normalize-messages.d.ts +3 -0
- package/dist/normalize-messages.d.ts.map +1 -0
- package/dist/normalize-messages.js +16 -0
- package/dist/normalize-messages.js.map +1 -0
- package/dist/provider-entry.d.ts +17 -0
- package/dist/provider-entry.d.ts.map +1 -0
- package/dist/provider-entry.js +13 -0
- package/dist/provider-entry.js.map +1 -0
- package/dist/realtime.d.ts +2 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +6 -0
- package/dist/realtime.js.map +1 -1
- package/dist/store-key.d.ts +3 -0
- package/dist/store-key.d.ts.map +1 -0
- package/dist/store-key.js +5 -0
- package/dist/store-key.js.map +1 -0
- package/dist/store-security.d.ts +1 -2
- package/dist/store-security.d.ts.map +1 -1
- package/dist/store-security.js +2 -4
- package/dist/store-security.js.map +1 -1
- package/dist/types/agent-state.d.ts +236 -0
- package/dist/types/agent-state.d.ts.map +1 -0
- package/dist/types/agent-state.js +2 -0
- package/dist/types/agent-state.js.map +1 -0
- package/dist/types/agents.d.ts +429 -0
- package/dist/types/agents.d.ts.map +1 -0
- package/dist/types/agents.js +2 -0
- package/dist/types/agents.js.map +1 -0
- package/dist/types/common.d.ts +179 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/generation.d.ts +109 -0
- package/dist/types/generation.d.ts.map +1 -0
- package/dist/types/generation.js +2 -0
- package/dist/types/generation.js.map +1 -0
- package/dist/types/media-data.d.ts +65 -0
- package/dist/types/media-data.d.ts.map +1 -0
- package/dist/types/media-data.js +2 -0
- package/dist/types/media-data.js.map +1 -0
- package/dist/types/media.d.ts +141 -0
- package/dist/types/media.d.ts.map +1 -0
- package/dist/types/media.js +2 -0
- package/dist/types/media.js.map +1 -0
- package/dist/types/messages.d.ts +82 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +2 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/middleware.d.ts +130 -0
- package/dist/types/middleware.d.ts.map +1 -0
- package/dist/types/middleware.js +2 -0
- package/dist/types/middleware.js.map +1 -0
- package/dist/types/model-tools.d.ts +409 -0
- package/dist/types/model-tools.d.ts.map +1 -0
- package/dist/types/model-tools.js +2 -0
- package/dist/types/model-tools.js.map +1 -0
- package/dist/types/persistence.d.ts +168 -0
- package/dist/types/persistence.d.ts.map +1 -0
- package/dist/types/persistence.js +2 -0
- package/dist/types/persistence.js.map +1 -0
- package/dist/types/provider-resources.d.ts +336 -0
- package/dist/types/provider-resources.d.ts.map +1 -0
- package/dist/types/provider-resources.js +2 -0
- package/dist/types/provider-resources.js.map +1 -0
- package/dist/types/provider.d.ts +23 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +2 -0
- package/dist/types/provider.js.map +1 -0
- package/dist/types/resource-options.d.ts +88 -0
- package/dist/types/resource-options.d.ts.map +1 -0
- package/dist/types/resource-options.js +2 -0
- package/dist/types/resource-options.js.map +1 -0
- package/dist/types/stream.d.ts +70 -0
- package/dist/types/stream.d.ts.map +1 -0
- package/dist/types/stream.js +2 -0
- package/dist/types/stream.js.map +1 -0
- package/dist/types/ui.d.ts +110 -0
- package/dist/types/ui.d.ts.map +1 -0
- package/dist/types/ui.js +2 -0
- package/dist/types/ui.js.map +1 -0
- package/dist/types.d.ts +16 -2442
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +2 -0
- package/dist/ui.js.map +1 -1
- package/dist/workflow-state-service/file.d.ts +5 -0
- package/dist/workflow-state-service/file.d.ts.map +1 -0
- package/dist/workflow-state-service/file.js +142 -0
- package/dist/workflow-state-service/file.js.map +1 -0
- package/dist/workflow-state-service/memory.d.ts +3 -0
- package/dist/workflow-state-service/memory.d.ts.map +1 -0
- package/dist/workflow-state-service/memory.js +27 -0
- package/dist/workflow-state-service/memory.js.map +1 -0
- package/dist/workflow-state-service/postgres.d.ts +4 -0
- package/dist/workflow-state-service/postgres.d.ts.map +1 -0
- package/dist/workflow-state-service/postgres.js +179 -0
- package/dist/workflow-state-service/postgres.js.map +1 -0
- package/dist/workflow-state-service/shared.d.ts +43 -0
- package/dist/workflow-state-service/shared.d.ts.map +1 -0
- package/dist/workflow-state-service/shared.js +107 -0
- package/dist/workflow-state-service/shared.js.map +1 -0
- package/dist/workflow-state-service/sqlite.d.ts +4 -0
- package/dist/workflow-state-service/sqlite.d.ts.map +1 -0
- package/dist/workflow-state-service/sqlite.js +183 -0
- package/dist/workflow-state-service/sqlite.js.map +1 -0
- package/dist/workflow-state-service.d.ts +6 -33
- package/dist/workflow-state-service.d.ts.map +1 -1
- package/dist/workflow-state-service.js +6 -626
- package/dist/workflow-state-service.js.map +1 -1
- package/package.json +17 -1
package/dist/agent.js
CHANGED
|
@@ -1,2623 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { ConflictError, GuardrailTriggeredError, ProviderToolCallError, ToolNotRegisteredError, UnsupportedFeatureError, ValidationError } from "./errors.js";
|
|
8
|
-
import { aggregateTokenUsage, generateText, getGenerateTextStepTiming, normalizeMessages, streamText } from "./generate-text.js";
|
|
9
|
-
import { createTextMessage, isCallableToolDefinition, serializeJsonValue } from "./messages.js";
|
|
10
|
-
import { createMergedAbortSignal } from "./runtime.js";
|
|
11
|
-
import { evaluateAgentBudgetPreflight, getAgentBudgetStatus } from "./safety-policy.js";
|
|
12
|
-
import { createSecureId } from "#secure-id";
|
|
13
|
-
import { createStructuredOutputPrompt } from "./structured-output-prompt.js";
|
|
14
|
-
import { ToolExecutionSuspendedError } from "./tool-execution-suspension.js";
|
|
15
|
-
import { toToolSet } from "./tool-registry.js";
|
|
16
|
-
import { z } from "zod";
|
|
17
|
-
const randomId = createSecureId;
|
|
18
|
-
const AGENT_GROUP_FAIL_FAST_ABORT_MESSAGE = "Agent group member aborted after fail-fast.";
|
|
19
|
-
const DEFAULT_AGENT_LEASE_TTL_MS = 30_000;
|
|
20
|
-
const DEFAULT_AGENT_CANCELLATION_POLL_MS = 1_000;
|
|
21
|
-
const DEFAULT_AGENT_MONITOR_SHUTDOWN_TIMEOUT_MS = 1_000;
|
|
22
|
-
const DEFAULT_AGENT_MAX_STATE_BYTES = 4 * 1024 * 1024;
|
|
23
|
-
class AgentPolicyTimeoutError extends Error {
|
|
24
|
-
constructor(timeoutMs) {
|
|
25
|
-
super(`Agent run timed out after ${timeoutMs}ms.`);
|
|
26
|
-
this.name = "AgentPolicyTimeoutError";
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
const joinInstructions = (...parts) => {
|
|
30
|
-
const content = parts.map((part) => part?.trim()).filter((part) => Boolean(part));
|
|
31
|
-
return content.length ? content.join("\n\n") : undefined;
|
|
32
|
-
};
|
|
33
|
-
const resolveAgentOutputMode = (agent) => {
|
|
34
|
-
if (!agent.outputSchema) {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
const requested = agent.outputMode ?? "auto";
|
|
38
|
-
if (requested === "native" && !agent.model.capabilities.structuredOutput) {
|
|
39
|
-
throw new UnsupportedFeatureError(`Model "${agent.model.provider}/${agent.model.modelId}" does not support native structured output.`);
|
|
40
|
-
}
|
|
41
|
-
return requested === "auto"
|
|
42
|
-
? agent.model.capabilities.structuredOutput
|
|
43
|
-
? "native"
|
|
44
|
-
: "prompted"
|
|
45
|
-
: requested;
|
|
46
|
-
};
|
|
47
|
-
const promptedOutputInstruction = (agent) => agent.outputSchema && resolveAgentOutputMode(agent) === "prompted"
|
|
48
|
-
? createStructuredOutputPrompt(agent.outputSchema, {
|
|
49
|
-
name: agent.outputName,
|
|
50
|
-
description: agent.outputDescription
|
|
51
|
-
})
|
|
52
|
-
: undefined;
|
|
53
|
-
const hasToolCalls = (messages) => messages.some((message) => message.parts.some((part) => part.type === "tool-call"));
|
|
54
|
-
const snapshotRequest = (request, messageOffset = 0, messages = request.messages) => ({
|
|
55
|
-
messageOffset,
|
|
56
|
-
messages,
|
|
57
|
-
toolChoice: request.toolChoice,
|
|
58
|
-
toolExecution: request.toolExecution,
|
|
59
|
-
temperature: request.temperature,
|
|
60
|
-
maxTokens: request.maxTokens,
|
|
61
|
-
reasoning: request.reasoning,
|
|
62
|
-
providerOptions: request.providerOptions,
|
|
63
|
-
timeoutMs: request.timeoutMs,
|
|
64
|
-
maxRetries: request.maxRetries,
|
|
65
|
-
retryBackoffMs: request.retryBackoffMs
|
|
66
|
-
});
|
|
67
|
-
const snapshotResponse = (response) => ({
|
|
68
|
-
messages: response.messages ?? (response.message ? [response.message] : []),
|
|
69
|
-
text: response.text,
|
|
70
|
-
finishReason: response.finishReason,
|
|
71
|
-
providerFinishReason: response.providerFinishReason,
|
|
72
|
-
usage: response.usage
|
|
73
|
-
});
|
|
74
|
-
const countToolCalls = (messages) => messages.reduce((total, message) => total + message.parts.filter((part) => part.type === "tool-call").length, 0);
|
|
75
|
-
const messagePrefixLength = (prefix, messages) => {
|
|
76
|
-
if (prefix.length > messages.length) {
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
|
79
|
-
for (let index = 0; index < prefix.length; index += 1) {
|
|
80
|
-
if (JSON.stringify(prefix[index]) !== JSON.stringify(messages[index])) {
|
|
81
|
-
return 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return prefix.length;
|
|
85
|
-
};
|
|
86
|
-
const mapSteps = (steps, offset, toolResults) => {
|
|
87
|
-
let toolResultCursor = 0;
|
|
88
|
-
let previousMessages = [];
|
|
89
|
-
return steps.map((step, index) => {
|
|
90
|
-
const response = snapshotResponse(step.response);
|
|
91
|
-
const toolCallCount = countToolCalls(response.messages);
|
|
92
|
-
const stepToolResults = toolResults.slice(toolResultCursor, toolResultCursor + toolCallCount);
|
|
93
|
-
toolResultCursor += toolCallCount;
|
|
94
|
-
const timing = getGenerateTextStepTiming(step.request);
|
|
95
|
-
const finishedAt = timing?.finishedAt ?? Date.now();
|
|
96
|
-
const messageOffset = index === 0 ? 0 : messagePrefixLength(previousMessages, step.request.messages);
|
|
97
|
-
const incrementalMessages = step.request.messages.slice(messageOffset);
|
|
98
|
-
previousMessages = step.request.messages;
|
|
99
|
-
return {
|
|
100
|
-
index: offset + index + 1,
|
|
101
|
-
status: "completed",
|
|
102
|
-
startedAt: timing?.startedAt ?? finishedAt,
|
|
103
|
-
finishedAt,
|
|
104
|
-
request: snapshotRequest(step.request, messageOffset, incrementalMessages),
|
|
105
|
-
response,
|
|
106
|
-
toolResults: stepToolResults
|
|
107
|
-
};
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
const cloneMetadata = (...values) => {
|
|
111
|
-
const merged = Object.assign({}, ...values.filter(Boolean));
|
|
112
|
-
return Object.keys(merged).length ? merged : undefined;
|
|
113
|
-
};
|
|
114
|
-
const toOutput = (state) => ({
|
|
115
|
-
status: state.status,
|
|
116
|
-
outputText: state.outputText,
|
|
117
|
-
finalOutput: state.status === "completed" && state.finalOutput !== undefined
|
|
118
|
-
? state.finalOutput
|
|
119
|
-
: undefined,
|
|
120
|
-
finishReason: state.finishReason,
|
|
121
|
-
providerFinishReason: state.providerFinishReason,
|
|
122
|
-
usage: state.usage,
|
|
123
|
-
messages: state.messages,
|
|
124
|
-
steps: state.steps,
|
|
125
|
-
toolResults: state.toolResults,
|
|
126
|
-
state,
|
|
127
|
-
error: state.error
|
|
128
|
-
});
|
|
129
|
-
const normalizeApprovalStatus = (status) => status === "suspended" ? "waiting_approval" : status;
|
|
130
|
-
const cloneState = (state) => JSON.parse(JSON.stringify(normalizeAgentRunState(state)));
|
|
131
|
-
const createBaseState = (provider, modelId, initialMessages, maxSteps, metadata, agentId, runId, handoff, parentRunId, idempotencyKey, scope, outputMode, harness, executionEnvironment) => {
|
|
132
|
-
const startedAt = Date.now();
|
|
133
|
-
return {
|
|
134
|
-
schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
|
|
135
|
-
revision: 0,
|
|
136
|
-
runId,
|
|
137
|
-
scope,
|
|
138
|
-
idempotencyKey,
|
|
139
|
-
agentId,
|
|
140
|
-
parentRunId: parentRunId ?? handoff?.fromRunId,
|
|
141
|
-
provider,
|
|
142
|
-
modelId,
|
|
143
|
-
harness,
|
|
144
|
-
executionEnvironment,
|
|
145
|
-
status: "running",
|
|
146
|
-
messages: initialMessages,
|
|
147
|
-
steps: [],
|
|
148
|
-
toolResults: [],
|
|
149
|
-
currentStep: 0,
|
|
150
|
-
maxSteps,
|
|
151
|
-
outputText: "",
|
|
152
|
-
outputMode,
|
|
153
|
-
pendingApprovals: [],
|
|
154
|
-
approvalHistory: [],
|
|
155
|
-
compactions: [],
|
|
156
|
-
metadata,
|
|
157
|
-
handoff,
|
|
158
|
-
startedAt,
|
|
159
|
-
updatedAt: startedAt
|
|
160
|
-
};
|
|
161
|
-
};
|
|
162
|
-
const ensureValidStateInput = (input) => {
|
|
163
|
-
if (input.approvals?.length && !input.state) {
|
|
164
|
-
throw new ValidationError('The "approvals" option requires an existing agent "state".');
|
|
165
|
-
}
|
|
166
|
-
if (!input.state) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (input.prompt !== undefined || input.messages !== undefined || input.system !== undefined || input.handoff !== undefined) {
|
|
170
|
-
throw new ValidationError('Pass either "state" or a fresh "prompt"/"messages" input, but not both.');
|
|
171
|
-
}
|
|
172
|
-
const stateScope = input.state?.scope;
|
|
173
|
-
const inputScope = input.scope;
|
|
174
|
-
if (stateScope &&
|
|
175
|
-
inputScope &&
|
|
176
|
-
(stateScope.tenantId !== inputScope.tenantId ||
|
|
177
|
-
stateScope.userId !== inputScope.userId ||
|
|
178
|
-
stateScope.namespace !== inputScope.namespace)) {
|
|
179
|
-
throw new ValidationError('The provided agent state belongs to a different tenant/user scope.');
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
const ensureValidIdempotencyInput = (input, store) => {
|
|
183
|
-
if (!input.idempotencyKey) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (!store) {
|
|
187
|
-
throw new ValidationError('The "idempotencyKey" option requires an agent run "store".');
|
|
188
|
-
}
|
|
189
|
-
if (!store.claimIdempotencyKey) {
|
|
190
|
-
throw new ValidationError('The agent run "store" must implement "claimIdempotencyKey()" to use "idempotencyKey" safely.');
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
const ensureValidScope = (scope) => {
|
|
194
|
-
if (!scope)
|
|
195
|
-
return;
|
|
196
|
-
if (typeof scope.tenantId !== "string" || scope.tenantId.length === 0) {
|
|
197
|
-
throw new ValidationError('Agent scope "tenantId" must be a non-empty string.');
|
|
198
|
-
}
|
|
199
|
-
for (const field of ["userId", "namespace"]) {
|
|
200
|
-
if (scope[field] !== undefined && (typeof scope[field] !== "string" || scope[field].length === 0)) {
|
|
201
|
-
throw new ValidationError(`Agent scope "${field}" must be a non-empty string when provided.`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
const invokeOperationalHook = async (agent, source, operation, runId, callback, fallback) => {
|
|
206
|
-
if (!callback) {
|
|
207
|
-
return fallback;
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
return await callback();
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
214
|
-
try {
|
|
215
|
-
await agent.hookFailurePolicy?.onError?.({
|
|
216
|
-
source,
|
|
217
|
-
operation,
|
|
218
|
-
runId,
|
|
219
|
-
error: normalizedError
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
// Reporting an observer failure must never recursively fail the run.
|
|
224
|
-
}
|
|
225
|
-
if (agent.hookFailurePolicy?.[source] === "fail") {
|
|
226
|
-
throw normalizedError;
|
|
227
|
-
}
|
|
228
|
-
return fallback;
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
const injectContextMessages = (messages, extraMessages) => {
|
|
232
|
-
if (!extraMessages.length) {
|
|
233
|
-
return messages;
|
|
234
|
-
}
|
|
235
|
-
if (messages[0]?.role === "system") {
|
|
236
|
-
return [messages[0], ...extraMessages, ...messages.slice(1)];
|
|
237
|
-
}
|
|
238
|
-
return [...extraMessages, ...messages];
|
|
239
|
-
};
|
|
240
|
-
const prepareFreshMessages = async (agent, input, runId) => {
|
|
241
|
-
let messages = normalizeMessages({
|
|
242
|
-
prompt: input.prompt,
|
|
243
|
-
messages: input.messages,
|
|
244
|
-
system: joinInstructions(agent.instructions, input.system, promptedOutputInstruction(agent))
|
|
245
|
-
});
|
|
246
|
-
const handoffMessages = input.handoff
|
|
247
|
-
? [createAgentHandoffMessage(input.handoff), ...input.handoff.contextMessages.filter((message) => message.role !== "system")]
|
|
248
|
-
: [];
|
|
249
|
-
messages = injectContextMessages(messages, handoffMessages);
|
|
250
|
-
const memoryMessages = await invokeOperationalHook(agent, "memory", "load", runId, agent.memory
|
|
251
|
-
? () => agent.memory.load({
|
|
252
|
-
runId,
|
|
253
|
-
agentId: agent.id,
|
|
254
|
-
scope: input.scope ?? input.handoff?.scope,
|
|
255
|
-
metadata: cloneMetadata(agent.metadata, input.metadata)
|
|
256
|
-
})
|
|
257
|
-
: undefined, []);
|
|
258
|
-
messages = injectContextMessages(messages, memoryMessages);
|
|
259
|
-
return {
|
|
260
|
-
messages,
|
|
261
|
-
memoryMessages
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
const localApprovalResolutionPayload = (inputDigest, approve, reason) => JSON.stringify({
|
|
265
|
-
inputDigest,
|
|
266
|
-
approve,
|
|
267
|
-
reason: reason ?? null
|
|
268
|
-
});
|
|
269
|
-
const approvalKey = (provider, requestId) => `${provider}\0${requestId}`;
|
|
270
|
-
const applyApprovalResponses = async (messages, approvals, pendingApprovals, approvalHistory = [], signer) => {
|
|
271
|
-
if (!approvals?.length) {
|
|
272
|
-
return {
|
|
273
|
-
messages,
|
|
274
|
-
pendingApprovals,
|
|
275
|
-
approvalHistory
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
const pendingById = new Map(pendingApprovals.map((approval) => [approvalKey(approval.provider, approval.id), approval]));
|
|
279
|
-
for (const approval of approvals) {
|
|
280
|
-
const pending = pendingById.get(approvalKey(approval.provider, approval.approvalRequestId));
|
|
281
|
-
if (!pending) {
|
|
282
|
-
throw new ValidationError(`Unknown approval request "${approval.approvalRequestId}" for provider "${approval.provider}".`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
const providerApprovals = approvals.filter((approval) => {
|
|
286
|
-
const pending = pendingById.get(approvalKey(approval.provider, approval.approvalRequestId));
|
|
287
|
-
return pending?.kind === undefined || pending.kind === "provider";
|
|
288
|
-
});
|
|
289
|
-
const localResolutions = [];
|
|
290
|
-
const subagentResolutions = [];
|
|
291
|
-
for (const approval of approvals) {
|
|
292
|
-
const pending = pendingById.get(approvalKey(approval.provider, approval.approvalRequestId));
|
|
293
|
-
if (!pending) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (pending.kind === "subagent") {
|
|
297
|
-
subagentResolutions.push({
|
|
298
|
-
requestId: pending.id,
|
|
299
|
-
kind: "subagent",
|
|
300
|
-
provider: pending.provider,
|
|
301
|
-
approve: approval.approve,
|
|
302
|
-
reason: approval.reason,
|
|
303
|
-
toolCallId: pending.toolCallId,
|
|
304
|
-
childRunId: pending.childRunId,
|
|
305
|
-
childAgentId: pending.childAgentId,
|
|
306
|
-
childApprovalRequestId: pending.childApprovalRequestId,
|
|
307
|
-
resolvedAt: Date.now()
|
|
308
|
-
});
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
if (pending.kind !== "local-tool") {
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (signer) {
|
|
315
|
-
if (!pending.inputDigest || !pending.signature) {
|
|
316
|
-
throw new ValidationError(`Approval request "${pending.id}" is missing its required signature.`);
|
|
317
|
-
}
|
|
318
|
-
const requestSignatureValid = signer.verify
|
|
319
|
-
? await signer.verify(pending.inputDigest, pending.signature)
|
|
320
|
-
: (await signer.sign(pending.inputDigest)) === pending.signature;
|
|
321
|
-
if (!requestSignatureValid) {
|
|
322
|
-
throw new ValidationError(`Approval request "${pending.id}" has an invalid signature.`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const resolutionSignature = signer && pending.inputDigest
|
|
326
|
-
? await signer.sign(localApprovalResolutionPayload(pending.inputDigest, approval.approve, approval.reason))
|
|
327
|
-
: undefined;
|
|
328
|
-
localResolutions.push({
|
|
329
|
-
requestId: pending.id,
|
|
330
|
-
kind: "local-tool",
|
|
331
|
-
provider: pending.provider,
|
|
332
|
-
approve: approval.approve,
|
|
333
|
-
reason: approval.reason,
|
|
334
|
-
toolCallId: pending.toolCallId,
|
|
335
|
-
step: pending.step,
|
|
336
|
-
inputDigest: pending.inputDigest,
|
|
337
|
-
toolVersion: pending.toolVersion,
|
|
338
|
-
signature: resolutionSignature,
|
|
339
|
-
resolvedAt: Date.now()
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
return {
|
|
343
|
-
messages: providerApprovals.length
|
|
344
|
-
? [...messages, createAgentApprovalMessage(providerApprovals)]
|
|
345
|
-
: messages,
|
|
346
|
-
pendingApprovals: pendingApprovals.filter((pending) => !approvals.some((approval) => approval.approvalRequestId === pending.id &&
|
|
347
|
-
approval.provider === pending.provider)),
|
|
348
|
-
approvalHistory: [
|
|
349
|
-
...approvalHistory.filter((existing) => !localResolutions.some((resolution) => resolution.requestId === existing.requestId &&
|
|
350
|
-
resolution.provider === existing.provider) &&
|
|
351
|
-
!subagentResolutions.some((resolution) => resolution.requestId === existing.requestId &&
|
|
352
|
-
resolution.provider === existing.provider)),
|
|
353
|
-
...localResolutions,
|
|
354
|
-
...subagentResolutions
|
|
355
|
-
]
|
|
356
|
-
};
|
|
357
|
-
};
|
|
358
|
-
const finalizeState = (agent, state, result, newSteps, newToolResults) => {
|
|
359
|
-
const nextCurrentStep = state.currentStep + newSteps.length;
|
|
360
|
-
const exhausted = nextCurrentStep >= state.maxSteps;
|
|
361
|
-
const lastStep = newSteps.at(-1);
|
|
362
|
-
const unresolvedToolCalls = lastStep?.response ? hasToolCalls(lastStep.response.messages) : false;
|
|
363
|
-
const pendingApprovals = [
|
|
364
|
-
...(result.approvalRequests ?? []),
|
|
365
|
-
...getAgentApprovalRequests(newSteps.flatMap((step) => step.response?.messages ?? []))
|
|
366
|
-
];
|
|
367
|
-
if (pendingApprovals.length) {
|
|
368
|
-
state.status = "waiting_approval";
|
|
369
|
-
state.error = undefined;
|
|
370
|
-
if (lastStep) {
|
|
371
|
-
lastStep.status = "waiting_approval";
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
else if (exhausted && unresolvedToolCalls) {
|
|
375
|
-
state.status = "failed";
|
|
376
|
-
state.error = {
|
|
377
|
-
message: "Agent exhausted maxSteps before reaching a terminal response."
|
|
378
|
-
};
|
|
379
|
-
if (lastStep) {
|
|
380
|
-
lastStep.status = "failed";
|
|
381
|
-
lastStep.error = state.error;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
state.status = "completed";
|
|
386
|
-
state.error = undefined;
|
|
387
|
-
}
|
|
388
|
-
state.messages = result.messages;
|
|
389
|
-
state.steps = [...state.steps, ...newSteps];
|
|
390
|
-
state.toolResults = [...state.toolResults, ...newToolResults];
|
|
391
|
-
state.currentStep = nextCurrentStep;
|
|
392
|
-
state.outputText = result.text;
|
|
393
|
-
if (state.status === "completed") {
|
|
394
|
-
const terminalText = result.steps.at(-1)?.response.text ?? result.text;
|
|
395
|
-
if (agent.outputSchema) {
|
|
396
|
-
let parsedJson;
|
|
397
|
-
try {
|
|
398
|
-
parsedJson = JSON.parse(terminalText);
|
|
399
|
-
}
|
|
400
|
-
catch (error) {
|
|
401
|
-
throw new ValidationError("Agent final output is not valid JSON.", { cause: error });
|
|
402
|
-
}
|
|
403
|
-
const parsedOutput = agent.outputSchema.safeParse(parsedJson);
|
|
404
|
-
if (!parsedOutput.success) {
|
|
405
|
-
throw new ValidationError(`Agent final output validation failed: ${parsedOutput.error.message}`);
|
|
406
|
-
}
|
|
407
|
-
state.finalOutput = serializeJsonValue(parsedOutput.data);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
state.finishReason = result.finishReason;
|
|
411
|
-
state.providerFinishReason = result.providerFinishReason;
|
|
412
|
-
state.usage = aggregateTokenUsage([state.usage, result.usage]);
|
|
413
|
-
state.pendingApprovals = pendingApprovals;
|
|
414
|
-
state.updatedAt = Date.now();
|
|
415
|
-
return toOutput(state);
|
|
416
|
-
};
|
|
417
|
-
const emitTelemetryEvent = async (agent, event) => {
|
|
418
|
-
await invokeOperationalHook(agent, "telemetry", event.type, event.runId, agent.onTelemetryEvent ? () => agent.onTelemetryEvent(event) : undefined, undefined);
|
|
419
|
-
};
|
|
420
|
-
const emitInvocationStartTelemetry = async (agent, runId, startedAt, maxSteps) => {
|
|
421
|
-
try {
|
|
422
|
-
await agent.onTelemetryEvent?.startInvocation?.({
|
|
423
|
-
runId,
|
|
424
|
-
agentId: agent.id,
|
|
425
|
-
agentName: agent.name,
|
|
426
|
-
provider: agent.model.provider,
|
|
427
|
-
modelId: agent.model.modelId,
|
|
428
|
-
maxSteps,
|
|
429
|
-
startedAt
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
catch {
|
|
433
|
-
// Invocation telemetry is best-effort and cannot replace setup/business errors.
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
const emitInvocationFinishTelemetry = async (agent, runId, status, error) => {
|
|
437
|
-
try {
|
|
438
|
-
await agent.onTelemetryEvent?.finishInvocation?.({
|
|
439
|
-
runId,
|
|
440
|
-
agentId: agent.id,
|
|
441
|
-
agentName: agent.name,
|
|
442
|
-
status,
|
|
443
|
-
error,
|
|
444
|
-
finishedAt: Date.now()
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
catch {
|
|
448
|
-
// Invocation telemetry is best-effort and cannot replace setup/business errors.
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
const withAgentTelemetryRunContext = (agent, runId, callback) => {
|
|
452
|
-
const telemetryObserver = agent.onTelemetryEvent;
|
|
453
|
-
const wrapper = telemetryObserver?.withRunContext;
|
|
454
|
-
if (!wrapper)
|
|
455
|
-
return Promise.resolve().then(callback);
|
|
456
|
-
let execution;
|
|
457
|
-
let callbackError;
|
|
458
|
-
let callbackFailed = false;
|
|
459
|
-
const executeOnce = () => {
|
|
460
|
-
execution ??= Promise.resolve()
|
|
461
|
-
.then(callback)
|
|
462
|
-
.catch((error) => {
|
|
463
|
-
callbackFailed = true;
|
|
464
|
-
callbackError = error;
|
|
465
|
-
throw error;
|
|
466
|
-
});
|
|
467
|
-
return execution;
|
|
468
|
-
};
|
|
469
|
-
return Promise.resolve()
|
|
470
|
-
.then(async () => {
|
|
471
|
-
await wrapper.call(telemetryObserver, runId, executeOnce);
|
|
472
|
-
return executeOnce();
|
|
473
|
-
})
|
|
474
|
-
.catch(async (wrapperError) => {
|
|
475
|
-
if (callbackFailed)
|
|
476
|
-
throw callbackError;
|
|
477
|
-
try {
|
|
478
|
-
return await executeOnce();
|
|
479
|
-
}
|
|
480
|
-
catch (executionError) {
|
|
481
|
-
throw callbackFailed ? callbackError : executionError ?? wrapperError;
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
};
|
|
485
|
-
const subAgentToolInputSchema = z.object({
|
|
486
|
-
prompt: z.string().min(1),
|
|
487
|
-
system: z.string().optional()
|
|
488
|
-
});
|
|
489
|
-
const defaultSubAgentToolName = (agent) => {
|
|
490
|
-
const id = agent.id ?? `${agent.model.provider}_${agent.model.modelId}`;
|
|
491
|
-
return `subagent_${id.replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "agent"}`;
|
|
492
|
-
};
|
|
493
|
-
const countToolCallsInSteps = (steps) => steps.reduce((total, step) => total + countToolCalls(step.response?.messages ?? []), 0);
|
|
494
|
-
const countToolErrors = (toolResults) => toolResults.filter((result) => result.isError).length;
|
|
495
|
-
export const createSubAgentTool = (options) => {
|
|
496
|
-
const runtimeState = options.runtimeState;
|
|
497
|
-
const toolName = options.toolName ?? options.name ?? defaultSubAgentToolName(options.agent);
|
|
498
|
-
const metadata = {
|
|
499
|
-
type: "subagent"
|
|
500
|
-
};
|
|
501
|
-
if (options.agent.id) {
|
|
502
|
-
metadata.childAgentId = options.agent.id;
|
|
503
|
-
}
|
|
504
|
-
if (options.parentRunId) {
|
|
505
|
-
metadata.parentRunId = options.parentRunId;
|
|
506
|
-
}
|
|
507
|
-
if (options.parentAgentId) {
|
|
508
|
-
metadata.parentAgentId = options.parentAgentId;
|
|
509
|
-
}
|
|
510
|
-
return {
|
|
511
|
-
name: toolName,
|
|
512
|
-
description: options.description ??
|
|
513
|
-
`Delegate the task to ${options.agent.id ? `subagent "${options.agent.id}"` : "a subagent"} and return its result.`,
|
|
514
|
-
schema: subAgentToolInputSchema,
|
|
515
|
-
requiresApproval: options.requiresApproval,
|
|
516
|
-
metadata: cloneMetadata(metadata, options.metadata),
|
|
517
|
-
execute: async (input, executionContext) => {
|
|
518
|
-
await options.onStart?.({
|
|
519
|
-
toolName,
|
|
520
|
-
childAgentId: options.agent.id,
|
|
521
|
-
parentRunId: options.parentRunId
|
|
522
|
-
});
|
|
523
|
-
const childMetadata = {
|
|
524
|
-
subagentToolName: toolName
|
|
525
|
-
};
|
|
526
|
-
if (options.parentRunId) {
|
|
527
|
-
childMetadata.parentRunId = options.parentRunId;
|
|
528
|
-
}
|
|
529
|
-
if (options.parentAgentId) {
|
|
530
|
-
childMetadata.parentAgentId = options.parentAgentId;
|
|
531
|
-
}
|
|
532
|
-
const toolCallId = executionContext?.toolCall.id;
|
|
533
|
-
const childIdempotencyKey = options.parentRunId &&
|
|
534
|
-
toolCallId &&
|
|
535
|
-
executionContext &&
|
|
536
|
-
options.agent.store?.claimIdempotencyKey
|
|
537
|
-
? `subagent:${durableToolCallId(options.parentRunId, executionContext.step, toolCallId, toolName, serializeJsonValue(input))}`
|
|
538
|
-
: undefined;
|
|
539
|
-
const checkpoint = runtimeState?.childRuns?.find((childRun) => childRun.toolCallId === toolCallId && childRun.resumeState);
|
|
540
|
-
const childApprovalResponses = checkpoint
|
|
541
|
-
? (runtimeState?.approvalHistory ?? [])
|
|
542
|
-
.filter((resolution) => resolution.kind === "subagent" &&
|
|
543
|
-
resolution.toolCallId === toolCallId &&
|
|
544
|
-
resolution.childRunId === checkpoint.runId &&
|
|
545
|
-
resolution.childApprovalRequestId)
|
|
546
|
-
.map((resolution) => ({
|
|
547
|
-
provider: resolution.provider,
|
|
548
|
-
approvalRequestId: resolution.childApprovalRequestId,
|
|
549
|
-
approve: resolution.approve,
|
|
550
|
-
reason: resolution.reason
|
|
551
|
-
}))
|
|
552
|
-
: [];
|
|
553
|
-
const output = checkpoint?.resumeState
|
|
554
|
-
? await resumeAgent(options.agent, {
|
|
555
|
-
state: checkpoint.resumeState,
|
|
556
|
-
approvals: childApprovalResponses,
|
|
557
|
-
scope: options.scope,
|
|
558
|
-
context: executionContext?.context,
|
|
559
|
-
maxSteps: options.maxSteps
|
|
560
|
-
})
|
|
561
|
-
: await runAgent(options.agent, {
|
|
562
|
-
prompt: input.prompt,
|
|
563
|
-
system: joinInstructions(options.system, input.system),
|
|
564
|
-
parentRunId: options.parentRunId,
|
|
565
|
-
idempotencyKey: childIdempotencyKey,
|
|
566
|
-
scope: options.scope,
|
|
567
|
-
context: executionContext?.context,
|
|
568
|
-
maxSteps: options.maxSteps,
|
|
569
|
-
metadata: cloneMetadata(options.metadata, childMetadata)
|
|
570
|
-
});
|
|
571
|
-
const childRun = {
|
|
572
|
-
runId: output.state.runId,
|
|
573
|
-
status: output.status,
|
|
574
|
-
outputText: output.outputText,
|
|
575
|
-
steps: output.state.currentStep,
|
|
576
|
-
toolCalls: countToolCallsInSteps(output.steps),
|
|
577
|
-
toolErrors: countToolErrors(output.toolResults)
|
|
578
|
-
};
|
|
579
|
-
if (toolCallId) {
|
|
580
|
-
childRun.toolCallId = toolCallId;
|
|
581
|
-
}
|
|
582
|
-
if (output.state.agentId) {
|
|
583
|
-
childRun.agentId = output.state.agentId;
|
|
584
|
-
}
|
|
585
|
-
if (options.parentRunId) {
|
|
586
|
-
childRun.parentRunId = options.parentRunId;
|
|
587
|
-
}
|
|
588
|
-
childRun.toolName = toolName;
|
|
589
|
-
if (output.usage) {
|
|
590
|
-
childRun.usage = output.usage;
|
|
591
|
-
}
|
|
592
|
-
if (output.state.startedAt !== undefined) {
|
|
593
|
-
childRun.startedAt = output.state.startedAt;
|
|
594
|
-
}
|
|
595
|
-
if (output.state.updatedAt !== undefined) {
|
|
596
|
-
childRun.updatedAt = output.state.updatedAt;
|
|
597
|
-
}
|
|
598
|
-
if (output.error) {
|
|
599
|
-
childRun.error = output.error;
|
|
600
|
-
}
|
|
601
|
-
if (output.state.metadata) {
|
|
602
|
-
childRun.metadata = output.state.metadata;
|
|
603
|
-
}
|
|
604
|
-
if (output.status === "waiting_approval" && output.state.pendingApprovals.length) {
|
|
605
|
-
childRun.resumeState = output.state;
|
|
606
|
-
}
|
|
607
|
-
await options.onFinish?.(childRun);
|
|
608
|
-
if (childRun.resumeState) {
|
|
609
|
-
const approvals = childRun.resumeState.pendingApprovals.map((approval) => {
|
|
610
|
-
const id = `subapproval_${createHash("sha256")
|
|
611
|
-
.update(`${options.parentRunId ?? ""}\0${toolCallId ?? ""}\0${childRun.runId}\0${approval.id}`)
|
|
612
|
-
.digest("hex")}`;
|
|
613
|
-
return {
|
|
614
|
-
kind: "subagent",
|
|
615
|
-
provider: approval.provider,
|
|
616
|
-
id,
|
|
617
|
-
name: approval.name,
|
|
618
|
-
arguments: approval.arguments,
|
|
619
|
-
serverLabel: approval.serverLabel,
|
|
620
|
-
toolCallId,
|
|
621
|
-
step: executionContext?.step,
|
|
622
|
-
childRunId: childRun.runId,
|
|
623
|
-
childAgentId: childRun.agentId,
|
|
624
|
-
childApprovalRequestId: approval.id,
|
|
625
|
-
rawData: {
|
|
626
|
-
type: "subagent_approval_request",
|
|
627
|
-
childRunId: childRun.runId,
|
|
628
|
-
childAgentId: childRun.agentId ?? null,
|
|
629
|
-
childApprovalRequestId: approval.id,
|
|
630
|
-
approval: approval.rawData
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
});
|
|
634
|
-
throw new ToolExecutionSuspendedError(approvals);
|
|
635
|
-
}
|
|
636
|
-
return serializeJsonValue(childRun);
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
};
|
|
640
|
-
const saveStateWithRevision = async (store, state, serializedNextState) => {
|
|
641
|
-
const expectedRevision = state.revision ?? 0;
|
|
642
|
-
const nextRevision = expectedRevision + 1;
|
|
643
|
-
const nextState = { ...state, revision: nextRevision };
|
|
644
|
-
await store.save(serializedNextState === undefined ? cloneState(nextState) : JSON.parse(serializedNextState), { expectedRevision });
|
|
645
|
-
state.revision = nextRevision;
|
|
646
|
-
};
|
|
647
|
-
const claimAgentExecution = async (agent, state) => {
|
|
648
|
-
state.status = "running";
|
|
649
|
-
state.updatedAt = Date.now();
|
|
650
|
-
const serialized = assertStateSize(agent, agent.store ? normalizeAgentRunState({ ...state, revision: (state.revision ?? 0) + 1 }) : state);
|
|
651
|
-
if (agent.store) {
|
|
652
|
-
await saveStateWithRevision(agent.store, state, serialized);
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
const assertStateSize = (agent, state, policy) => {
|
|
656
|
-
const limit = policy?.maxStateBytes ?? agent.policy?.maxStateBytes ?? DEFAULT_AGENT_MAX_STATE_BYTES;
|
|
657
|
-
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
658
|
-
throw new ValidationError('Agent policy "maxStateBytes" must be a positive integer.');
|
|
659
|
-
}
|
|
660
|
-
const serialized = JSON.stringify(state);
|
|
661
|
-
const bytes = new TextEncoder().encode(serialized).byteLength;
|
|
662
|
-
if (bytes > limit) {
|
|
663
|
-
throw new ValidationError(`Agent run state is ${bytes} bytes and exceeds maxStateBytes=${limit}. Offload large tool outputs to artifacts or raise the explicit limit.`);
|
|
664
|
-
}
|
|
665
|
-
return serialized;
|
|
666
|
-
};
|
|
667
|
-
const persistState = async (agent, state, policy) => {
|
|
668
|
-
state.updatedAt = Date.now();
|
|
669
|
-
const serialized = assertStateSize(agent, agent.store ? normalizeAgentRunState({ ...state, revision: (state.revision ?? 0) + 1 }) : state, policy);
|
|
670
|
-
if (agent.store) {
|
|
671
|
-
await saveStateWithRevision(agent.store, state, serialized);
|
|
672
|
-
}
|
|
673
|
-
await emitTelemetryEvent(agent, {
|
|
674
|
-
type: "state-saved",
|
|
675
|
-
runId: state.runId,
|
|
676
|
-
agentId: state.agentId,
|
|
677
|
-
status: state.status
|
|
678
|
-
});
|
|
679
|
-
await invokeOperationalHook(agent, "memory", "save", state.runId, agent.memory?.save
|
|
680
|
-
? () => agent.memory.save({
|
|
681
|
-
runId: state.runId,
|
|
682
|
-
agentId: state.agentId,
|
|
683
|
-
scope: state.scope,
|
|
684
|
-
state: cloneState(state),
|
|
685
|
-
metadata: state.metadata
|
|
686
|
-
})
|
|
687
|
-
: undefined, undefined);
|
|
688
|
-
};
|
|
689
|
-
const approvalsFromEvents = (messages) => getAgentApprovalRequests(messages);
|
|
690
|
-
const emitFinalizedStepTelemetry = async (agent, state, steps) => {
|
|
691
|
-
for (const step of steps) {
|
|
692
|
-
await emitTelemetryEvent(agent, {
|
|
693
|
-
type: "step-finish",
|
|
694
|
-
runId: state.runId,
|
|
695
|
-
agentId: state.agentId,
|
|
696
|
-
step
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
};
|
|
700
|
-
const emitApprovalTelemetry = async (agent, state, approvals) => {
|
|
701
|
-
for (const approval of approvals) {
|
|
702
|
-
await emitTelemetryEvent(agent, {
|
|
703
|
-
type: "approval-request",
|
|
704
|
-
runId: state.runId,
|
|
705
|
-
agentId: state.agentId,
|
|
706
|
-
approval
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
const emitToolApprovalTelemetry = async (agent, state, event) => {
|
|
711
|
-
await emitTelemetryEvent(agent, {
|
|
712
|
-
type: "tool-approval",
|
|
713
|
-
runId: state.runId,
|
|
714
|
-
agentId: state.agentId,
|
|
715
|
-
toolCall: event.request.toolCall,
|
|
716
|
-
approved: event.decision.approved,
|
|
717
|
-
reason: event.decision.reason,
|
|
718
|
-
metadata: event.decision.metadata
|
|
719
|
-
});
|
|
720
|
-
};
|
|
721
|
-
const normalizeGuardrailTrigger = (value) => value?.triggered ? value : undefined;
|
|
722
|
-
const applyGuardrailFailure = (state, stage, trigger) => ({
|
|
723
|
-
...state,
|
|
724
|
-
status: "failed",
|
|
725
|
-
error: {
|
|
726
|
-
message: trigger.reason ?? `Agent ${stage} guardrail triggered.`
|
|
727
|
-
},
|
|
728
|
-
updatedAt: Date.now()
|
|
729
|
-
});
|
|
730
|
-
const runGuardrails = async (agent, state, stage, guardrails, requestFactory) => {
|
|
731
|
-
for (const [index, guardrail] of (guardrails ?? []).entries()) {
|
|
732
|
-
const trigger = normalizeGuardrailTrigger(await guardrail(requestFactory(index)));
|
|
733
|
-
if (!trigger) {
|
|
734
|
-
continue;
|
|
735
|
-
}
|
|
736
|
-
await emitTelemetryEvent(agent, {
|
|
737
|
-
type: "guardrail-triggered",
|
|
738
|
-
runId: state.runId,
|
|
739
|
-
agentId: state.agentId,
|
|
740
|
-
stage,
|
|
741
|
-
reason: trigger.reason ?? `Agent ${stage} guardrail #${index + 1} triggered.`,
|
|
742
|
-
metadata: trigger.metadata
|
|
743
|
-
});
|
|
744
|
-
return trigger;
|
|
745
|
-
}
|
|
746
|
-
return undefined;
|
|
747
|
-
};
|
|
748
|
-
const bindDurableRuntime = (agent, input, state, executionEnvironment) => {
|
|
749
|
-
const policy = {
|
|
750
|
-
...(agent.policy ?? {}),
|
|
751
|
-
...(input.policy ?? {})
|
|
752
|
-
};
|
|
753
|
-
if (state.harness && !agent.harness) {
|
|
754
|
-
throw new ConflictError(`Agent run "${state.runId}" is bound to harness "${state.harness.id}", but the current agent has no harness binding.`);
|
|
755
|
-
}
|
|
756
|
-
if (state.harness && agent.harness) {
|
|
757
|
-
if (state.harness.id !== agent.harness.id ||
|
|
758
|
-
state.harness.version !== agent.harness.version ||
|
|
759
|
-
state.harness.fingerprint !== agent.harness.fingerprint) {
|
|
760
|
-
throw new ConflictError(`Agent run "${state.runId}" was created by a different harness fingerprint.`);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
else if (!state.harness && agent.harness) {
|
|
764
|
-
if (!policy?.allowLegacyHarnessResume) {
|
|
765
|
-
throw new ConflictError(`Agent run "${state.runId}" predates harness binding; set allowLegacyHarnessResume only for an explicit migration.`);
|
|
766
|
-
}
|
|
767
|
-
state.harness = agent.harness;
|
|
768
|
-
}
|
|
769
|
-
if (state.executionEnvironment && !executionEnvironment) {
|
|
770
|
-
throw new ConflictError(`Agent run "${state.runId}" is bound to execution environment "${state.executionEnvironment.environmentId}".`);
|
|
771
|
-
}
|
|
772
|
-
if (state.executionEnvironment && executionEnvironment) {
|
|
773
|
-
if (state.executionEnvironment.environmentId !== executionEnvironment.environmentId ||
|
|
774
|
-
state.executionEnvironment.environmentVersion !== executionEnvironment.environmentVersion ||
|
|
775
|
-
state.executionEnvironment.fingerprint !== executionEnvironment.fingerprint ||
|
|
776
|
-
state.executionEnvironment.workspaceId !== executionEnvironment.workspaceId) {
|
|
777
|
-
throw new ConflictError(`Agent run "${state.runId}" was created in a different execution environment.`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
else if (!state.executionEnvironment && executionEnvironment) {
|
|
781
|
-
if (!policy?.allowLegacyExecutionEnvironmentResume) {
|
|
782
|
-
throw new ConflictError(`Agent run "${state.runId}" predates execution-environment binding; enable the explicit migration policy to resume it.`);
|
|
783
|
-
}
|
|
784
|
-
state.executionEnvironment = executionEnvironment;
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
const validateHarnessBinding = (binding) => {
|
|
788
|
-
if (!binding) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
if (binding.schemaVersion !== 1 ||
|
|
792
|
-
binding.algorithm !== "sha256" ||
|
|
793
|
-
!binding.id ||
|
|
794
|
-
!binding.version ||
|
|
795
|
-
!/^sha256:[0-9a-f]{64}$/.test(binding.fingerprint)) {
|
|
796
|
-
throw new ValidationError("Agent harness binding is invalid.");
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
const resolveContext = async (agent, input) => {
|
|
800
|
-
validateHarnessBinding(agent.harness);
|
|
801
|
-
const executionEnvironment = input.executionEnvironment ?? agent.executionEnvironment;
|
|
802
|
-
const executionEnvironmentBinding = executionEnvironment
|
|
803
|
-
? createAgentExecutionEnvironmentBinding(executionEnvironment.manifest)
|
|
804
|
-
: undefined;
|
|
805
|
-
let parsedContext = input.context;
|
|
806
|
-
if (agent.contextSchema) {
|
|
807
|
-
const result = await agent.contextSchema.safeParseAsync(input.context);
|
|
808
|
-
if (!result.success) {
|
|
809
|
-
throw new ValidationError(`Invalid agent context: ${result.error.message}`);
|
|
810
|
-
}
|
|
811
|
-
parsedContext = result.data;
|
|
812
|
-
}
|
|
813
|
-
ensureValidIdempotencyInput(input, agent.store);
|
|
814
|
-
const inputScope = input.scope ?? input.handoff?.scope;
|
|
815
|
-
ensureValidScope(inputScope);
|
|
816
|
-
let loadedState = input.state ? normalizeAgentRunState(input.state) : undefined;
|
|
817
|
-
let loadedByIdempotencyKey = false;
|
|
818
|
-
if (!loadedState && input.runId && agent.store) {
|
|
819
|
-
loadedState = await agent.store.load(input.runId, inputScope);
|
|
820
|
-
if (loadedState) {
|
|
821
|
-
loadedState = normalizeAgentRunState(loadedState);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
if (!loadedState && input.idempotencyKey) {
|
|
825
|
-
const runId = input.runId ?? randomId("run");
|
|
826
|
-
const maxSteps = Math.max(1, input.maxSteps ?? agent.maxSteps ?? 1);
|
|
827
|
-
const metadata = cloneMetadata(agent.metadata, input.metadata, input.handoff?.metadata);
|
|
828
|
-
const prepared = await prepareFreshMessages(agent, input, runId);
|
|
829
|
-
const candidate = createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope, resolveAgentOutputMode(agent), agent.harness, executionEnvironmentBinding);
|
|
830
|
-
const claim = await agent.store.claimIdempotencyKey(candidate);
|
|
831
|
-
if (claim.claimed) {
|
|
832
|
-
return {
|
|
833
|
-
state: normalizeAgentRunState(claim.state),
|
|
834
|
-
messages: prepared.messages,
|
|
835
|
-
remainingSteps: maxSteps,
|
|
836
|
-
memoryMessages: prepared.memoryMessages,
|
|
837
|
-
context: parsedContext,
|
|
838
|
-
executionEnvironment,
|
|
839
|
-
fresh: true
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
loadedState = normalizeAgentRunState(claim.state);
|
|
843
|
-
loadedByIdempotencyKey = true;
|
|
844
|
-
}
|
|
845
|
-
const normalizedInput = loadedState && loadedByIdempotencyKey
|
|
846
|
-
? { ...input, prompt: undefined, messages: undefined, system: undefined, handoff: undefined, state: loadedState }
|
|
847
|
-
: loadedState
|
|
848
|
-
? { ...input, state: loadedState }
|
|
849
|
-
: input;
|
|
850
|
-
ensureValidStateInput(normalizedInput);
|
|
851
|
-
const metadata = cloneMetadata(agent.metadata, loadedState?.metadata, input.metadata, input.handoff?.metadata);
|
|
852
|
-
if (loadedState) {
|
|
853
|
-
const groupIdentity = input.metadata?.agentGroupIdentity;
|
|
854
|
-
if (groupIdentity !== undefined && (loadedState.metadata?.agentGroupIdentity !== groupIdentity ||
|
|
855
|
-
loadedState.agentId !== agent.id)) {
|
|
856
|
-
throw new ConflictError("Agent group idempotency key belongs to a different member or agent.");
|
|
857
|
-
}
|
|
858
|
-
bindDurableRuntime(agent, input, loadedState, executionEnvironmentBinding);
|
|
859
|
-
const maxSteps = input.maxSteps ?? loadedState.maxSteps;
|
|
860
|
-
const resumed = await applyApprovalResponses(loadedState.messages, input.approvals, loadedState.pendingApprovals, loadedState.approvalHistory, agent.toolApprovalSigner);
|
|
861
|
-
return {
|
|
862
|
-
state: {
|
|
863
|
-
...loadedState,
|
|
864
|
-
schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
|
|
865
|
-
idempotencyKey: loadedState.idempotencyKey ?? input.idempotencyKey,
|
|
866
|
-
scope: loadedState.scope ?? inputScope,
|
|
867
|
-
agentId: loadedState.agentId ?? agent.id,
|
|
868
|
-
parentRunId: loadedState.parentRunId ?? input.parentRunId,
|
|
869
|
-
provider: agent.model.provider,
|
|
870
|
-
modelId: agent.model.modelId,
|
|
871
|
-
maxSteps,
|
|
872
|
-
messages: resumed.messages,
|
|
873
|
-
pendingApprovals: resumed.pendingApprovals,
|
|
874
|
-
approvalHistory: resumed.approvalHistory,
|
|
875
|
-
metadata,
|
|
876
|
-
updatedAt: Date.now()
|
|
877
|
-
},
|
|
878
|
-
messages: resumed.messages,
|
|
879
|
-
remainingSteps: Math.max(0, maxSteps - loadedState.currentStep),
|
|
880
|
-
memoryMessages: [],
|
|
881
|
-
context: parsedContext,
|
|
882
|
-
executionEnvironment,
|
|
883
|
-
fresh: false
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
const runId = input.runId ?? randomId("run");
|
|
887
|
-
const maxSteps = Math.max(1, input.maxSteps ?? agent.maxSteps ?? 1);
|
|
888
|
-
const prepared = await prepareFreshMessages(agent, input, runId);
|
|
889
|
-
return {
|
|
890
|
-
state: createBaseState(agent.model.provider, agent.model.modelId, prepared.messages, maxSteps, metadata, agent.id, runId, input.handoff, input.parentRunId, input.idempotencyKey, inputScope, resolveAgentOutputMode(agent), agent.harness, executionEnvironmentBinding),
|
|
891
|
-
messages: prepared.messages,
|
|
892
|
-
remainingSteps: maxSteps,
|
|
893
|
-
memoryMessages: prepared.memoryMessages,
|
|
894
|
-
context: parsedContext,
|
|
895
|
-
executionEnvironment,
|
|
896
|
-
fresh: true
|
|
897
|
-
};
|
|
898
|
-
};
|
|
899
|
-
const canonicalJson = (value) => {
|
|
900
|
-
if (value === null || typeof value !== "object") {
|
|
901
|
-
return JSON.stringify(value);
|
|
902
|
-
}
|
|
903
|
-
if (Array.isArray(value)) {
|
|
904
|
-
return `[${value.map(canonicalJson).join(",")}]`;
|
|
905
|
-
}
|
|
906
|
-
return `{${Object.keys(value)
|
|
907
|
-
.sort()
|
|
908
|
-
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
909
|
-
.join(",")}}`;
|
|
910
|
-
};
|
|
911
|
-
const durableToolCallId = (runId, step, providerToolCallId, toolName, input) => `tool_${createHash("sha256")
|
|
912
|
-
.update(`${runId}\0${step}\0${providerToolCallId}\0${toolName}\0${canonicalJson(input)}`)
|
|
913
|
-
.digest("hex")}`;
|
|
914
|
-
const wrapToolWithExecutionEnvironment = (tool, session) => {
|
|
915
|
-
const authorize = async (input, context, phase) => session.authorize({
|
|
916
|
-
manifest: session.manifest,
|
|
917
|
-
binding: session.binding,
|
|
918
|
-
tool,
|
|
919
|
-
toolCall: context.toolCall,
|
|
920
|
-
input,
|
|
921
|
-
context,
|
|
922
|
-
phase
|
|
923
|
-
});
|
|
924
|
-
const environmentGuardrail = async ({ input, context }) => {
|
|
925
|
-
const decision = await authorize(input, context, "preflight");
|
|
926
|
-
return decision.decision === "deny"
|
|
927
|
-
? {
|
|
928
|
-
triggered: true,
|
|
929
|
-
reason: decision.reason,
|
|
930
|
-
metadata: decision.metadata
|
|
931
|
-
}
|
|
932
|
-
: undefined;
|
|
933
|
-
};
|
|
934
|
-
return {
|
|
935
|
-
...tool,
|
|
936
|
-
approvalVersion: [
|
|
937
|
-
tool.approvalVersion,
|
|
938
|
-
`environment:${session.binding.fingerprint}`
|
|
939
|
-
].filter(Boolean).join("|"),
|
|
940
|
-
inputGuardrails: [
|
|
941
|
-
environmentGuardrail,
|
|
942
|
-
...(tool.inputGuardrails ?? [])
|
|
943
|
-
],
|
|
944
|
-
execute: async (input, context) => {
|
|
945
|
-
if (!context) {
|
|
946
|
-
throw new ValidationError(`Tool "${tool.name}" requires an execution environment context.`);
|
|
947
|
-
}
|
|
948
|
-
const decision = await authorize(input, context, "execute");
|
|
949
|
-
if (decision.decision === "deny") {
|
|
950
|
-
throw new GuardrailTriggeredError("tool-input", decision.reason, { metadata: decision.metadata });
|
|
951
|
-
}
|
|
952
|
-
const request = {
|
|
953
|
-
manifest: session.manifest,
|
|
954
|
-
binding: session.binding,
|
|
955
|
-
tool,
|
|
956
|
-
toolCall: context.toolCall,
|
|
957
|
-
input,
|
|
958
|
-
context,
|
|
959
|
-
phase: "execute"
|
|
960
|
-
};
|
|
961
|
-
return session.execute(request, () => tool.execute(input, context));
|
|
962
|
-
}
|
|
963
|
-
};
|
|
964
|
-
};
|
|
965
|
-
const wrapToolWithJournal = (agent, state, tool) => {
|
|
966
|
-
const store = agent.store;
|
|
967
|
-
if (!store?.claimToolExecution || !store.loadToolExecution || !store.completeToolExecution) {
|
|
968
|
-
return tool;
|
|
969
|
-
}
|
|
970
|
-
return {
|
|
971
|
-
...tool,
|
|
972
|
-
execute: async (input, context) => {
|
|
973
|
-
if (!context) {
|
|
974
|
-
throw new ValidationError(`Durable tool "${tool.name}" requires an execution context.`);
|
|
975
|
-
}
|
|
976
|
-
const serializedInput = serializeJsonValue(input);
|
|
977
|
-
const step = context.step;
|
|
978
|
-
const toolCallId = durableToolCallId(state.runId, step, context.toolCall.id, tool.name, serializedInput);
|
|
979
|
-
const idempotencyKey = `${state.runId}:${toolCallId}`;
|
|
980
|
-
const now = Date.now();
|
|
981
|
-
const candidate = {
|
|
982
|
-
runId: state.runId,
|
|
983
|
-
scope: state.scope,
|
|
984
|
-
toolCallId,
|
|
985
|
-
toolName: tool.name,
|
|
986
|
-
status: "pending",
|
|
987
|
-
idempotencyKey,
|
|
988
|
-
revision: 0,
|
|
989
|
-
input: serializedInput,
|
|
990
|
-
updatedAt: now
|
|
991
|
-
};
|
|
992
|
-
const claim = await store.claimToolExecution(candidate);
|
|
993
|
-
if (!claim.claimed) {
|
|
994
|
-
if (claim.entry.status === "completed") {
|
|
995
|
-
return claim.entry.output ?? null;
|
|
996
|
-
}
|
|
997
|
-
if (claim.entry.status === "failed") {
|
|
998
|
-
throw new Error(claim.entry.error?.message ?? `Tool "${tool.name}" previously failed.`);
|
|
999
|
-
}
|
|
1000
|
-
throw new ConflictError(`Tool "${tool.name}" has an indeterminate durable execution. Reconcile idempotency key "${claim.entry.idempotencyKey}" before retrying.`);
|
|
1001
|
-
}
|
|
1002
|
-
try {
|
|
1003
|
-
const output = serializeJsonValue(await tool.execute(input, {
|
|
1004
|
-
...context,
|
|
1005
|
-
runId: state.runId,
|
|
1006
|
-
idempotencyKey
|
|
1007
|
-
}));
|
|
1008
|
-
await store.completeToolExecution({
|
|
1009
|
-
...claim.entry,
|
|
1010
|
-
status: "completed",
|
|
1011
|
-
output,
|
|
1012
|
-
completedAt: Date.now(),
|
|
1013
|
-
updatedAt: Date.now()
|
|
1014
|
-
}, { expectedRevision: claim.entry.revision });
|
|
1015
|
-
return output;
|
|
1016
|
-
}
|
|
1017
|
-
catch (error) {
|
|
1018
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
1019
|
-
try {
|
|
1020
|
-
await store.completeToolExecution({
|
|
1021
|
-
...claim.entry,
|
|
1022
|
-
status: "failed",
|
|
1023
|
-
error: { message: normalizedError.message },
|
|
1024
|
-
completedAt: Date.now(),
|
|
1025
|
-
updatedAt: Date.now()
|
|
1026
|
-
}, { expectedRevision: claim.entry.revision });
|
|
1027
|
-
}
|
|
1028
|
-
catch {
|
|
1029
|
-
// The original error is more useful; a running journal row blocks unsafe replay.
|
|
1030
|
-
}
|
|
1031
|
-
throw normalizedError;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
};
|
|
1036
|
-
const defaultEstimateInputTokens = (messages) => Math.ceil(new TextEncoder().encode(JSON.stringify(messages)).byteLength / 4);
|
|
1037
|
-
const validateCompactionOptions = (options) => {
|
|
1038
|
-
if (options.maxMessages !== undefined &&
|
|
1039
|
-
(!Number.isSafeInteger(options.maxMessages) || options.maxMessages < 2)) {
|
|
1040
|
-
throw new ValidationError("Agent compaction maxMessages must be an integer greater than or equal to 2.");
|
|
1041
|
-
}
|
|
1042
|
-
if (options.maxEstimatedInputTokens !== undefined &&
|
|
1043
|
-
(!Number.isSafeInteger(options.maxEstimatedInputTokens) || options.maxEstimatedInputTokens < 1)) {
|
|
1044
|
-
throw new ValidationError("Agent compaction maxEstimatedInputTokens must be a positive integer.");
|
|
1045
|
-
}
|
|
1046
|
-
if (options.keepRecentMessages !== undefined &&
|
|
1047
|
-
(!Number.isSafeInteger(options.keepRecentMessages) || options.keepRecentMessages < 1)) {
|
|
1048
|
-
throw new ValidationError("Agent compaction keepRecentMessages must be a positive integer.");
|
|
1049
|
-
}
|
|
1050
|
-
if (options.maxMessages === undefined && options.maxEstimatedInputTokens === undefined) {
|
|
1051
|
-
throw new ValidationError("Agent compaction requires maxMessages or maxEstimatedInputTokens.");
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
const compactAgentMessages = async (options, state, messages, beforeStep, context, abortSignal) => {
|
|
1055
|
-
validateCompactionOptions(options);
|
|
1056
|
-
if (state.pendingApprovals.length) {
|
|
1057
|
-
return undefined;
|
|
1058
|
-
}
|
|
1059
|
-
const estimateTokens = options.estimateTokens ?? defaultEstimateInputTokens;
|
|
1060
|
-
const estimatedTokensBefore = estimateTokens(messages);
|
|
1061
|
-
const reasons = [];
|
|
1062
|
-
if (options.maxMessages !== undefined && messages.length > options.maxMessages) {
|
|
1063
|
-
reasons.push("message-count");
|
|
1064
|
-
}
|
|
1065
|
-
if (options.maxEstimatedInputTokens !== undefined &&
|
|
1066
|
-
estimatedTokensBefore > options.maxEstimatedInputTokens) {
|
|
1067
|
-
reasons.push("estimated-input-tokens");
|
|
1068
|
-
}
|
|
1069
|
-
if (!reasons.length) {
|
|
1070
|
-
return undefined;
|
|
1071
|
-
}
|
|
1072
|
-
let systemCount = 0;
|
|
1073
|
-
while (messages[systemCount]?.role === "system") {
|
|
1074
|
-
systemCount += 1;
|
|
1075
|
-
}
|
|
1076
|
-
const maxTailForMessageLimit = options.maxMessages === undefined
|
|
1077
|
-
? Number.POSITIVE_INFINITY
|
|
1078
|
-
: Math.max(1, options.maxMessages - systemCount - 1);
|
|
1079
|
-
const keepRecentMessages = Math.min(options.keepRecentMessages ?? 8, maxTailForMessageLimit);
|
|
1080
|
-
let cut = Math.max(systemCount, messages.length - keepRecentMessages);
|
|
1081
|
-
while (cut > systemCount && messages[cut]?.role === "tool") {
|
|
1082
|
-
cut -= 1;
|
|
1083
|
-
}
|
|
1084
|
-
// Preserve whole correlated groups even when approval messages or parallel
|
|
1085
|
-
// tool results separate a call from its result. Moving the boundary can expose
|
|
1086
|
-
// another crossing dependency, so resolve it to a fixed point.
|
|
1087
|
-
const origins = new Map();
|
|
1088
|
-
const dependencies = [];
|
|
1089
|
-
messages.forEach((message, index) => {
|
|
1090
|
-
for (const part of message.parts) {
|
|
1091
|
-
let origin;
|
|
1092
|
-
let reference;
|
|
1093
|
-
if (part.type === "tool-call")
|
|
1094
|
-
origin = `tool:${part.toolCall.id}`;
|
|
1095
|
-
if (part.type === "tool-result")
|
|
1096
|
-
reference = `tool:${part.toolResult.toolCallId}`;
|
|
1097
|
-
if (part.type === "provider-data" && typeof part.data === "object" && part.data !== null && !Array.isArray(part.data)) {
|
|
1098
|
-
if (part.data.type === "mcp_approval_request" && typeof part.data.id === "string") {
|
|
1099
|
-
origin = `approval:${part.provider}:${part.data.id}`;
|
|
1100
|
-
}
|
|
1101
|
-
if (part.data.type === "mcp_approval_response" && typeof part.data.approval_request_id === "string") {
|
|
1102
|
-
reference = `approval:${part.provider}:${part.data.approval_request_id}`;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
if (origin)
|
|
1106
|
-
origins.set(origin, index);
|
|
1107
|
-
const from = reference ? origins.get(reference) : undefined;
|
|
1108
|
-
if (from !== undefined)
|
|
1109
|
-
dependencies.push({ from, to: index });
|
|
1110
|
-
}
|
|
1111
|
-
});
|
|
1112
|
-
let previousCut;
|
|
1113
|
-
do {
|
|
1114
|
-
previousCut = cut;
|
|
1115
|
-
for (const { from, to } of dependencies) {
|
|
1116
|
-
if (from < cut && to >= cut)
|
|
1117
|
-
cut = from;
|
|
1118
|
-
}
|
|
1119
|
-
} while (cut !== previousCut);
|
|
1120
|
-
if (cut <= systemCount) {
|
|
1121
|
-
throw new ValidationError("Agent compaction cannot satisfy its limits without removing protected messages.");
|
|
1122
|
-
}
|
|
1123
|
-
const compactedMessages = structuredClone(messages.slice(systemCount, cut));
|
|
1124
|
-
const retainedMessages = structuredClone(messages.slice(cut));
|
|
1125
|
-
const sourceDigest = fingerprintAgentHarness(messages);
|
|
1126
|
-
const id = `cmp_${createHash("sha256")
|
|
1127
|
-
.update(`${state.runId}\0${beforeStep}\0${sourceDigest}`)
|
|
1128
|
-
.digest("hex")}`;
|
|
1129
|
-
const result = await options.compactor({
|
|
1130
|
-
runId: state.runId,
|
|
1131
|
-
agentId: state.agentId,
|
|
1132
|
-
scope: state.scope,
|
|
1133
|
-
beforeStep,
|
|
1134
|
-
context,
|
|
1135
|
-
messages: compactedMessages,
|
|
1136
|
-
retainedMessages,
|
|
1137
|
-
reasons,
|
|
1138
|
-
estimatedTokensBefore,
|
|
1139
|
-
sourceDigest,
|
|
1140
|
-
idempotencyKey: id,
|
|
1141
|
-
metadata: state.metadata,
|
|
1142
|
-
abortSignal
|
|
1143
|
-
});
|
|
1144
|
-
const summary = result.summary.trim();
|
|
1145
|
-
if (!summary) {
|
|
1146
|
-
throw new ValidationError("Agent compactor returned an empty summary.");
|
|
1147
|
-
}
|
|
1148
|
-
const compacted = [
|
|
1149
|
-
...structuredClone(messages.slice(0, systemCount)),
|
|
1150
|
-
createTextMessage("assistant", `[Compacted prior conversation]\n${summary}`),
|
|
1151
|
-
...retainedMessages
|
|
1152
|
-
];
|
|
1153
|
-
const estimatedTokensAfter = estimateTokens(compacted);
|
|
1154
|
-
if (compacted.length >= messages.length || estimatedTokensAfter >= estimatedTokensBefore) {
|
|
1155
|
-
throw new ValidationError("Agent compaction must reduce both message count and estimated input tokens.");
|
|
1156
|
-
}
|
|
1157
|
-
if (options.maxMessages !== undefined && compacted.length > options.maxMessages) {
|
|
1158
|
-
throw new ValidationError("Agent compaction result still exceeds maxMessages.");
|
|
1159
|
-
}
|
|
1160
|
-
if (options.maxEstimatedInputTokens !== undefined &&
|
|
1161
|
-
estimatedTokensAfter > options.maxEstimatedInputTokens) {
|
|
1162
|
-
throw new ValidationError("Agent compaction result still exceeds maxEstimatedInputTokens.");
|
|
1163
|
-
}
|
|
1164
|
-
const resultDigest = fingerprintAgentHarness(compacted);
|
|
1165
|
-
return {
|
|
1166
|
-
messages: compacted,
|
|
1167
|
-
record: {
|
|
1168
|
-
id,
|
|
1169
|
-
beforeStep,
|
|
1170
|
-
createdAt: Date.now(),
|
|
1171
|
-
reasons,
|
|
1172
|
-
sourceDigest,
|
|
1173
|
-
resultDigest,
|
|
1174
|
-
summaryDigest: fingerprintAgentHarness(summary),
|
|
1175
|
-
summary,
|
|
1176
|
-
messageCountBefore: messages.length,
|
|
1177
|
-
messageCountAfter: compacted.length,
|
|
1178
|
-
compactedMessageCount: compactedMessages.length,
|
|
1179
|
-
retainedMessageCount: retainedMessages.length,
|
|
1180
|
-
estimatedTokensBefore,
|
|
1181
|
-
estimatedTokensAfter,
|
|
1182
|
-
usage: result.usage,
|
|
1183
|
-
metadata: result.metadata
|
|
1184
|
-
}
|
|
1185
|
-
};
|
|
1186
|
-
};
|
|
1187
|
-
const createGenerateOptions = (agent, state, input, messages, maxSteps, context, executionEnvironmentSession, abortSignal = input.abortSignal, onCompaction) => {
|
|
1188
|
-
const tools = { ...(toToolSet(input.tools ?? agent.tools) ?? {}) };
|
|
1189
|
-
for (const subagent of agent.subagents ?? []) {
|
|
1190
|
-
const subagentTool = createSubAgentTool({
|
|
1191
|
-
...subagent,
|
|
1192
|
-
agent: {
|
|
1193
|
-
...subagent.agent,
|
|
1194
|
-
store: subagent.agent.store ?? agent.store,
|
|
1195
|
-
memory: subagent.agent.memory ?? agent.memory,
|
|
1196
|
-
executionEnvironment: subagent.agent.executionEnvironment ??
|
|
1197
|
-
input.executionEnvironment ??
|
|
1198
|
-
agent.executionEnvironment,
|
|
1199
|
-
compaction: subagent.agent.compaction ?? (input.compaction === false
|
|
1200
|
-
? undefined
|
|
1201
|
-
: input.compaction ?? agent.compaction)
|
|
1202
|
-
},
|
|
1203
|
-
parentRunId: state.runId,
|
|
1204
|
-
parentAgentId: state.agentId,
|
|
1205
|
-
scope: state.scope,
|
|
1206
|
-
runtimeState: state,
|
|
1207
|
-
onStart: async ({ toolName, childAgentId }) => {
|
|
1208
|
-
await emitTelemetryEvent(agent, {
|
|
1209
|
-
type: "subagent-start",
|
|
1210
|
-
runId: state.runId,
|
|
1211
|
-
agentId: state.agentId,
|
|
1212
|
-
childAgentId,
|
|
1213
|
-
toolName
|
|
1214
|
-
});
|
|
1215
|
-
},
|
|
1216
|
-
onFinish: async (childRun) => {
|
|
1217
|
-
state.childRuns = [
|
|
1218
|
-
...(state.childRuns ?? []).filter((existing) => childRun.toolCallId
|
|
1219
|
-
? existing.toolCallId !== childRun.toolCallId
|
|
1220
|
-
: existing.runId !== childRun.runId),
|
|
1221
|
-
childRun
|
|
1222
|
-
];
|
|
1223
|
-
await emitTelemetryEvent(agent, {
|
|
1224
|
-
type: "subagent-finish",
|
|
1225
|
-
runId: state.runId,
|
|
1226
|
-
agentId: state.agentId,
|
|
1227
|
-
childRun
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
});
|
|
1231
|
-
if (tools[subagentTool.name]) {
|
|
1232
|
-
throw new ValidationError(`Subagent tool "${subagentTool.name}" conflicts with an existing tool.`);
|
|
1233
|
-
}
|
|
1234
|
-
tools[subagentTool.name] = subagentTool;
|
|
1235
|
-
}
|
|
1236
|
-
for (const [name, tool] of Object.entries(tools)) {
|
|
1237
|
-
if (isCallableToolDefinition(tool)) {
|
|
1238
|
-
const environmentTool = executionEnvironmentSession
|
|
1239
|
-
? wrapToolWithExecutionEnvironment(tool, executionEnvironmentSession)
|
|
1240
|
-
: tool;
|
|
1241
|
-
tools[name] = tool.metadata?.type === "subagent"
|
|
1242
|
-
? environmentTool
|
|
1243
|
-
: wrapToolWithJournal(agent, state, environmentTool);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
const finalTools = Object.keys(tools).length ? tools : undefined;
|
|
1247
|
-
const budget = input.policy?.budget ?? agent.policy?.budget;
|
|
1248
|
-
const runPolicy = resolveRunPolicy(agent, input);
|
|
1249
|
-
const compaction = input.compaction === false
|
|
1250
|
-
? undefined
|
|
1251
|
-
: input.compaction ?? agent.compaction;
|
|
1252
|
-
let checkpointState = cloneState(state);
|
|
1253
|
-
let liveUsage = state.usage;
|
|
1254
|
-
const liveToolResults = [...state.toolResults];
|
|
1255
|
-
let reservedToolCalls = 0;
|
|
1256
|
-
const requestedMaxTokens = input.maxTokens ?? agent.maxTokens;
|
|
1257
|
-
const budgetStatus = budget ? getAgentBudgetStatus(state, budget) : undefined;
|
|
1258
|
-
const tokenCeilings = [
|
|
1259
|
-
requestedMaxTokens,
|
|
1260
|
-
budgetStatus?.remaining.outputTokens,
|
|
1261
|
-
budgetStatus?.remaining.totalTokens
|
|
1262
|
-
].filter((value) => value !== undefined);
|
|
1263
|
-
const maxTokens = tokenCeilings.length ? Math.min(...tokenCeilings) : undefined;
|
|
1264
|
-
const requestedToolExecution = input.toolExecution ?? agent.toolExecution;
|
|
1265
|
-
const toolExecution = agent.subagents?.length && !(requestedToolExecution?.parallel && requestedToolExecution.independentOnly)
|
|
1266
|
-
? {
|
|
1267
|
-
...requestedToolExecution,
|
|
1268
|
-
parallel: false,
|
|
1269
|
-
maxConcurrency: 1
|
|
1270
|
-
}
|
|
1271
|
-
: requestedToolExecution;
|
|
1272
|
-
return {
|
|
1273
|
-
model: agent.model,
|
|
1274
|
-
messages,
|
|
1275
|
-
tools: finalTools,
|
|
1276
|
-
toolChoice: input.toolChoice,
|
|
1277
|
-
toolExecution,
|
|
1278
|
-
toolApprovalPolicy: input.toolApprovalPolicy ?? agent.toolApprovalPolicy,
|
|
1279
|
-
toolApprovalSigner: agent.toolApprovalSigner,
|
|
1280
|
-
toolApprovalResolutions: state.approvalHistory,
|
|
1281
|
-
toolContext: {
|
|
1282
|
-
context,
|
|
1283
|
-
runId: state.runId,
|
|
1284
|
-
agentId: state.agentId,
|
|
1285
|
-
agentName: agent.name,
|
|
1286
|
-
scope: state.scope,
|
|
1287
|
-
metadata: state.metadata,
|
|
1288
|
-
executionEnvironment: executionEnvironmentSession
|
|
1289
|
-
},
|
|
1290
|
-
onToolApprovalDecision: async (event) => {
|
|
1291
|
-
await emitToolApprovalTelemetry(agent, state, event);
|
|
1292
|
-
},
|
|
1293
|
-
prepareModelMessages: compaction
|
|
1294
|
-
? async ({ messages: activeMessages, step }) => {
|
|
1295
|
-
const compacted = await compactAgentMessages(compaction, checkpointState, activeMessages, step, context, abortSignal);
|
|
1296
|
-
if (!compacted) {
|
|
1297
|
-
return undefined;
|
|
1298
|
-
}
|
|
1299
|
-
checkpointState = {
|
|
1300
|
-
...checkpointState,
|
|
1301
|
-
messages: compacted.messages,
|
|
1302
|
-
usage: aggregateTokenUsage([
|
|
1303
|
-
checkpointState.usage,
|
|
1304
|
-
compacted.record.usage
|
|
1305
|
-
]),
|
|
1306
|
-
compactions: [
|
|
1307
|
-
...(checkpointState.compactions ?? []).filter((existing) => existing.id !== compacted.record.id),
|
|
1308
|
-
compacted.record
|
|
1309
|
-
],
|
|
1310
|
-
updatedAt: Date.now()
|
|
1311
|
-
};
|
|
1312
|
-
state.messages = compacted.messages;
|
|
1313
|
-
// Finalization adds this invocation's model usage. Only add the
|
|
1314
|
-
// compactor here; checkpoint usage already includes model responses.
|
|
1315
|
-
state.usage = aggregateTokenUsage([state.usage, compacted.record.usage]);
|
|
1316
|
-
liveUsage = aggregateTokenUsage([liveUsage, compacted.record.usage]);
|
|
1317
|
-
state.compactions = checkpointState.compactions;
|
|
1318
|
-
await persistState(agent, checkpointState, runPolicy);
|
|
1319
|
-
state.revision = checkpointState.revision;
|
|
1320
|
-
await onCompaction?.(compacted.record);
|
|
1321
|
-
return compacted.messages;
|
|
1322
|
-
}
|
|
1323
|
-
: undefined,
|
|
1324
|
-
onBeforeModelStep: async ({ step }) => {
|
|
1325
|
-
if (budget) {
|
|
1326
|
-
const trigger = evaluateAgentBudgetPreflight({ ...state, usage: liveUsage, toolResults: liveToolResults }, budget, {
|
|
1327
|
-
operation: "model",
|
|
1328
|
-
requiredSteps: Math.max(1, step - state.currentStep),
|
|
1329
|
-
requestedOutputTokens: maxTokens
|
|
1330
|
-
});
|
|
1331
|
-
if (trigger) {
|
|
1332
|
-
throw new GuardrailTriggeredError("input", trigger.reason ?? "Agent model budget preflight failed.", {
|
|
1333
|
-
metadata: trigger.metadata
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
await emitTelemetryEvent(agent, {
|
|
1338
|
-
type: "step-start",
|
|
1339
|
-
runId: state.runId,
|
|
1340
|
-
agentId: state.agentId,
|
|
1341
|
-
agentName: agent.name,
|
|
1342
|
-
stepIndex: step,
|
|
1343
|
-
startedAt: Date.now()
|
|
1344
|
-
});
|
|
1345
|
-
},
|
|
1346
|
-
onModelStep: async ({ request, response, step, toolCalls, approvalRequests, failedToolResults }) => {
|
|
1347
|
-
liveUsage = aggregateTokenUsage([liveUsage, response.usage]);
|
|
1348
|
-
const responseSnapshot = snapshotResponse(response);
|
|
1349
|
-
const approvals = [
|
|
1350
|
-
...approvalRequests,
|
|
1351
|
-
...getAgentApprovalRequests(responseSnapshot.messages)
|
|
1352
|
-
];
|
|
1353
|
-
const crossedCompactionBoundary = checkpointState.compactions?.some((record) => record.beforeStep === step &&
|
|
1354
|
-
record.resultDigest === fingerprintAgentHarness(request.messages));
|
|
1355
|
-
const requestOffset = crossedCompactionBoundary
|
|
1356
|
-
? 0
|
|
1357
|
-
: messagePrefixLength(checkpointState.messages, request.messages);
|
|
1358
|
-
const timing = getGenerateTextStepTiming(request);
|
|
1359
|
-
const finishedAt = timing?.finishedAt ?? Date.now();
|
|
1360
|
-
const checkpointStep = {
|
|
1361
|
-
index: step,
|
|
1362
|
-
status: failedToolResults ? "failed" : approvals.length ? "waiting_approval" : "completed",
|
|
1363
|
-
startedAt: timing?.startedAt ?? finishedAt,
|
|
1364
|
-
finishedAt,
|
|
1365
|
-
request: snapshotRequest(request, requestOffset, request.messages.slice(requestOffset)),
|
|
1366
|
-
response: responseSnapshot,
|
|
1367
|
-
toolResults: failedToolResults ?? []
|
|
1368
|
-
};
|
|
1369
|
-
checkpointState = {
|
|
1370
|
-
...checkpointState,
|
|
1371
|
-
status: approvals.length ? "waiting_approval" : toolCalls.length ? "running" : "completed",
|
|
1372
|
-
messages: [...request.messages, ...responseSnapshot.messages,
|
|
1373
|
-
...(failedToolResults ?? []).map(toolResult => ({ role: "tool", parts: [{ type: "tool-result", toolResult }] }))],
|
|
1374
|
-
toolResults: [...checkpointState.toolResults, ...(failedToolResults ?? [])],
|
|
1375
|
-
steps: [...checkpointState.steps.filter((existing) => existing.index !== step), checkpointStep],
|
|
1376
|
-
currentStep: step,
|
|
1377
|
-
outputText: response.text ?? checkpointState.outputText,
|
|
1378
|
-
finishReason: response.finishReason,
|
|
1379
|
-
providerFinishReason: response.providerFinishReason,
|
|
1380
|
-
usage: aggregateTokenUsage([checkpointState.usage, response.usage]),
|
|
1381
|
-
pendingApprovals: approvals,
|
|
1382
|
-
error: undefined,
|
|
1383
|
-
updatedAt: Date.now()
|
|
1384
|
-
};
|
|
1385
|
-
if (agent.store) {
|
|
1386
|
-
await persistState(agent, checkpointState, runPolicy);
|
|
1387
|
-
state.revision = checkpointState.revision;
|
|
1388
|
-
}
|
|
1389
|
-
if (failedToolResults)
|
|
1390
|
-
Object.assign(state, checkpointState);
|
|
1391
|
-
},
|
|
1392
|
-
onToolExecutionComplete: async ({ toolResults }) => {
|
|
1393
|
-
liveToolResults.push(...toolResults);
|
|
1394
|
-
const lastStep = checkpointState.steps.at(-1);
|
|
1395
|
-
if (lastStep) {
|
|
1396
|
-
lastStep.toolResults = [...lastStep.toolResults, ...toolResults];
|
|
1397
|
-
}
|
|
1398
|
-
checkpointState = {
|
|
1399
|
-
...checkpointState,
|
|
1400
|
-
status: "running",
|
|
1401
|
-
messages: [
|
|
1402
|
-
...checkpointState.messages,
|
|
1403
|
-
...toolResults.map((toolResult) => ({
|
|
1404
|
-
role: "tool",
|
|
1405
|
-
parts: [{ type: "tool-result", toolResult }]
|
|
1406
|
-
}))
|
|
1407
|
-
],
|
|
1408
|
-
toolResults: [...checkpointState.toolResults, ...toolResults],
|
|
1409
|
-
updatedAt: Date.now()
|
|
1410
|
-
};
|
|
1411
|
-
if (agent.store) {
|
|
1412
|
-
await persistState(agent, checkpointState, runPolicy);
|
|
1413
|
-
state.revision = checkpointState.revision;
|
|
1414
|
-
}
|
|
1415
|
-
},
|
|
1416
|
-
stepOffset: state.currentStep,
|
|
1417
|
-
onBeforeToolExecution: async ({ step, toolCalls }) => {
|
|
1418
|
-
if (budget) {
|
|
1419
|
-
reservedToolCalls += toolCalls.length;
|
|
1420
|
-
const trigger = evaluateAgentBudgetPreflight({ ...state, usage: liveUsage, toolResults: liveToolResults }, budget, {
|
|
1421
|
-
operation: "tool",
|
|
1422
|
-
requiredToolCalls: reservedToolCalls
|
|
1423
|
-
});
|
|
1424
|
-
if (trigger) {
|
|
1425
|
-
throw new GuardrailTriggeredError("input", trigger.reason ?? "Agent tool budget preflight failed.", {
|
|
1426
|
-
metadata: trigger.metadata
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
for (const toolCall of toolCalls) {
|
|
1431
|
-
await emitTelemetryEvent(agent, {
|
|
1432
|
-
type: "tool-start",
|
|
1433
|
-
runId: state.runId,
|
|
1434
|
-
agentId: state.agentId,
|
|
1435
|
-
agentName: agent.name,
|
|
1436
|
-
stepIndex: step,
|
|
1437
|
-
toolCall,
|
|
1438
|
-
startedAt: Date.now()
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
},
|
|
1442
|
-
maxSteps,
|
|
1443
|
-
temperature: input.temperature ?? agent.temperature,
|
|
1444
|
-
maxTokens,
|
|
1445
|
-
reasoning: input.reasoning ?? agent.reasoning,
|
|
1446
|
-
structuredOutput: agent.outputSchema && resolveAgentOutputMode(agent) === "native"
|
|
1447
|
-
? {
|
|
1448
|
-
schema: agent.outputSchema,
|
|
1449
|
-
mode: "native",
|
|
1450
|
-
name: agent.outputName,
|
|
1451
|
-
description: agent.outputDescription
|
|
1452
|
-
}
|
|
1453
|
-
: undefined,
|
|
1454
|
-
providerOptions: input.providerOptions ?? agent.providerOptions,
|
|
1455
|
-
abortSignal,
|
|
1456
|
-
timeoutMs: input.timeoutMs,
|
|
1457
|
-
maxRetries: input.maxRetries,
|
|
1458
|
-
retryBackoffMs: input.retryBackoffMs
|
|
1459
|
-
};
|
|
1460
|
-
};
|
|
1461
|
-
const emptyAsyncIterable = async function* () {
|
|
1462
|
-
return;
|
|
1463
|
-
};
|
|
1464
|
-
const toAgentRunError = (error) => {
|
|
1465
|
-
if (error instanceof ToolNotRegisteredError) {
|
|
1466
|
-
return { message: error.message, diagnosticCode: error.code };
|
|
1467
|
-
}
|
|
1468
|
-
if (error instanceof ProviderToolCallError) {
|
|
1469
|
-
return {
|
|
1470
|
-
message: error.message,
|
|
1471
|
-
diagnosticCode: error.diagnosticCode,
|
|
1472
|
-
category: error.category,
|
|
1473
|
-
provider: error.provider,
|
|
1474
|
-
...(error.transport ? { transport: error.transport } : {}),
|
|
1475
|
-
reason: error.reason,
|
|
1476
|
-
retryable: error.retryable,
|
|
1477
|
-
effectsPossible: error.effectsPossible
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
return {
|
|
1481
|
-
message: error instanceof Error ? error.message : String(error)
|
|
1482
|
-
};
|
|
1483
|
-
};
|
|
1484
|
-
const createFailedState = (state, error) => ({
|
|
1485
|
-
...state,
|
|
1486
|
-
status: "failed",
|
|
1487
|
-
error: toAgentRunError(error),
|
|
1488
|
-
updatedAt: Date.now()
|
|
1489
|
-
});
|
|
1490
|
-
const createTerminalState = (state, status, message) => ({
|
|
1491
|
-
...state,
|
|
1492
|
-
status,
|
|
1493
|
-
error: {
|
|
1494
|
-
message
|
|
1495
|
-
},
|
|
1496
|
-
cancellationReason: status === "cancel_requested" ? message : state.cancellationReason,
|
|
1497
|
-
cancelledAt: status === "cancel_requested" ? Date.now() : state.cancelledAt,
|
|
1498
|
-
updatedAt: Date.now()
|
|
1499
|
-
});
|
|
1500
|
-
const resolveRunPolicy = (agent, input) => {
|
|
1501
|
-
const policy = {
|
|
1502
|
-
...(agent.policy ?? {}),
|
|
1503
|
-
...(input.policy ?? {})
|
|
1504
|
-
};
|
|
1505
|
-
return Object.keys(policy).length ? policy : undefined;
|
|
1506
|
-
};
|
|
1507
|
-
const withAgentPolicyTimeout = async (operation, timeout) => {
|
|
1508
|
-
try {
|
|
1509
|
-
return timeout.timeoutPromise
|
|
1510
|
-
? await Promise.race([operation, timeout.timeoutPromise])
|
|
1511
|
-
: await operation;
|
|
1512
|
-
}
|
|
1513
|
-
finally {
|
|
1514
|
-
timeout.cleanup();
|
|
1515
|
-
}
|
|
1516
|
-
};
|
|
1517
|
-
const createAgentAbortContext = (inputAbortSignal, policy, ...additionalSignals) => {
|
|
1518
|
-
if (!policy?.timeoutMs) {
|
|
1519
|
-
const merged = createMergedAbortSignal(inputAbortSignal, ...additionalSignals);
|
|
1520
|
-
return {
|
|
1521
|
-
signal: merged.signal,
|
|
1522
|
-
timeoutPromise: undefined,
|
|
1523
|
-
cleanup: merged.cleanup,
|
|
1524
|
-
isTimedOut: () => false
|
|
1525
|
-
};
|
|
1526
|
-
}
|
|
1527
|
-
const controller = new AbortController();
|
|
1528
|
-
let timedOut = false;
|
|
1529
|
-
let timeout;
|
|
1530
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
1531
|
-
timeout = setTimeout(() => {
|
|
1532
|
-
timedOut = true;
|
|
1533
|
-
controller.abort();
|
|
1534
|
-
reject(new AgentPolicyTimeoutError(policy.timeoutMs));
|
|
1535
|
-
}, policy.timeoutMs);
|
|
1536
|
-
});
|
|
1537
|
-
const merged = createMergedAbortSignal(inputAbortSignal, ...additionalSignals, controller.signal);
|
|
1538
|
-
return {
|
|
1539
|
-
signal: merged.signal,
|
|
1540
|
-
timeoutPromise,
|
|
1541
|
-
cleanup: () => {
|
|
1542
|
-
merged.cleanup();
|
|
1543
|
-
if (timeout) {
|
|
1544
|
-
clearTimeout(timeout);
|
|
1545
|
-
}
|
|
1546
|
-
},
|
|
1547
|
-
isTimedOut: () => timedOut
|
|
1548
|
-
};
|
|
1549
|
-
};
|
|
1550
|
-
const acquireExecutionEnvironment = async (environment, state, context, abortSignal) => {
|
|
1551
|
-
if (!environment) {
|
|
1552
|
-
return undefined;
|
|
1553
|
-
}
|
|
1554
|
-
const expected = state.executionEnvironment;
|
|
1555
|
-
if (!expected) {
|
|
1556
|
-
throw new ConflictError(`Agent run "${state.runId}" has no durable execution-environment binding.`);
|
|
1557
|
-
}
|
|
1558
|
-
const session = await environment.acquire({
|
|
1559
|
-
runId: state.runId,
|
|
1560
|
-
agentId: state.agentId,
|
|
1561
|
-
scope: state.scope,
|
|
1562
|
-
context,
|
|
1563
|
-
metadata: state.metadata,
|
|
1564
|
-
abortSignal
|
|
1565
|
-
});
|
|
1566
|
-
const manifestBinding = createAgentExecutionEnvironmentBinding(session.manifest);
|
|
1567
|
-
if (session.binding.environmentId !== expected.environmentId ||
|
|
1568
|
-
session.binding.environmentVersion !== expected.environmentVersion ||
|
|
1569
|
-
session.binding.fingerprint !== expected.fingerprint ||
|
|
1570
|
-
session.binding.workspaceId !== expected.workspaceId ||
|
|
1571
|
-
manifestBinding.fingerprint !== expected.fingerprint) {
|
|
1572
|
-
await session.release?.({
|
|
1573
|
-
status: "failed",
|
|
1574
|
-
error: { message: "Execution environment returned a binding that differs from the durable run." }
|
|
1575
|
-
});
|
|
1576
|
-
throw new ConflictError(`Execution environment binding changed for agent run "${state.runId}".`);
|
|
1577
|
-
}
|
|
1578
|
-
return session;
|
|
1579
|
-
};
|
|
1580
|
-
const waitForAgentMonitorShutdown = async (monitor, timeoutMs) => {
|
|
1581
|
-
let timeout;
|
|
1582
|
-
const deadline = new Promise((resolve) => {
|
|
1583
|
-
timeout = setTimeout(resolve, timeoutMs);
|
|
1584
|
-
});
|
|
1585
|
-
try {
|
|
1586
|
-
await Promise.race([monitor, deadline]);
|
|
1587
|
-
}
|
|
1588
|
-
finally {
|
|
1589
|
-
if (timeout)
|
|
1590
|
-
clearTimeout(timeout);
|
|
1591
|
-
}
|
|
1592
|
-
};
|
|
1593
|
-
const acquireAgentExecutionLease = async (agent, state, policy) => {
|
|
1594
|
-
const store = agent.store;
|
|
1595
|
-
if (policy?.leaseMode === "disabled" || !store?.acquireLease || !store.renewLease || !store.releaseLease) {
|
|
1596
|
-
return {
|
|
1597
|
-
supported: false,
|
|
1598
|
-
cancelledState: () => undefined,
|
|
1599
|
-
leaseLost: () => false,
|
|
1600
|
-
release: async () => undefined
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
1603
|
-
const ttlMs = policy?.leaseTtlMs ?? DEFAULT_AGENT_LEASE_TTL_MS;
|
|
1604
|
-
const heartbeatMs = policy?.heartbeatMs ?? Math.max(250, Math.floor(ttlMs / 3));
|
|
1605
|
-
const cancellationPollMs = policy?.cancellationPollMs ?? DEFAULT_AGENT_CANCELLATION_POLL_MS;
|
|
1606
|
-
for (const [name, value] of [
|
|
1607
|
-
["leaseTtlMs", ttlMs],
|
|
1608
|
-
["heartbeatMs", heartbeatMs],
|
|
1609
|
-
["cancellationPollMs", cancellationPollMs]
|
|
1610
|
-
]) {
|
|
1611
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
1612
|
-
throw new ValidationError(`Agent policy "${name}" must be a positive integer.`);
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
if (heartbeatMs >= ttlMs) {
|
|
1616
|
-
throw new ValidationError('Agent policy "heartbeatMs" must be less than "leaseTtlMs".');
|
|
1617
|
-
}
|
|
1618
|
-
const ownerId = randomId("worker");
|
|
1619
|
-
const lease = await store.acquireLease(state.runId, { ownerId, ttlMs }, state.scope);
|
|
1620
|
-
if (!lease) {
|
|
1621
|
-
return undefined;
|
|
1622
|
-
}
|
|
1623
|
-
const controller = new AbortController();
|
|
1624
|
-
let cancelled;
|
|
1625
|
-
let lost = false;
|
|
1626
|
-
let stopped = false;
|
|
1627
|
-
let activeMonitor;
|
|
1628
|
-
let lastHeartbeat = Date.now();
|
|
1629
|
-
let lastCancellationPoll = 0;
|
|
1630
|
-
const intervalMs = Math.max(25, Math.min(heartbeatMs, cancellationPollMs));
|
|
1631
|
-
const monitorShutdownTimeoutMs = Math.min(ttlMs, DEFAULT_AGENT_MONITOR_SHUTDOWN_TIMEOUT_MS);
|
|
1632
|
-
const cleanupStoppedMonitorLease = async () => {
|
|
1633
|
-
try {
|
|
1634
|
-
await store.releaseLease?.(state.runId, ownerId, state.scope);
|
|
1635
|
-
}
|
|
1636
|
-
catch {
|
|
1637
|
-
// The foreground release remains authoritative; late monitor cleanup is best effort.
|
|
1638
|
-
}
|
|
1639
|
-
};
|
|
1640
|
-
const monitorLease = async () => {
|
|
1641
|
-
const now = Date.now();
|
|
1642
|
-
try {
|
|
1643
|
-
if (now - lastHeartbeat >= heartbeatMs) {
|
|
1644
|
-
const renewed = await store.renewLease?.(state.runId, { ownerId, ttlMs }, state.scope);
|
|
1645
|
-
if (stopped) {
|
|
1646
|
-
await cleanupStoppedMonitorLease();
|
|
1647
|
-
return;
|
|
1648
|
-
}
|
|
1649
|
-
if (!renewed) {
|
|
1650
|
-
lost = true;
|
|
1651
|
-
controller.abort(new ConflictError(`Agent run "${state.runId}" lost its worker lease.`));
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
lastHeartbeat = now;
|
|
1655
|
-
}
|
|
1656
|
-
if (now - lastCancellationPoll >= cancellationPollMs) {
|
|
1657
|
-
const latest = await store.load(state.runId, state.scope);
|
|
1658
|
-
if (stopped)
|
|
1659
|
-
return;
|
|
1660
|
-
lastCancellationPoll = now;
|
|
1661
|
-
if (latest?.status === "cancel_requested" || latest?.status === "cancelled") {
|
|
1662
|
-
cancelled = normalizeAgentRunState(latest);
|
|
1663
|
-
controller.abort(new Error(latest.cancellationReason ?? "Agent run was cancelled."));
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
catch (error) {
|
|
1668
|
-
if (stopped) {
|
|
1669
|
-
await cleanupStoppedMonitorLease();
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
lost = true;
|
|
1673
|
-
controller.abort(error);
|
|
1674
|
-
}
|
|
1675
|
-
};
|
|
1676
|
-
const timer = setInterval(() => {
|
|
1677
|
-
if (stopped || activeMonitor)
|
|
1678
|
-
return;
|
|
1679
|
-
activeMonitor = monitorLease().finally(() => {
|
|
1680
|
-
activeMonitor = undefined;
|
|
1681
|
-
});
|
|
1682
|
-
}, intervalMs);
|
|
1683
|
-
timer.unref?.();
|
|
1684
|
-
return {
|
|
1685
|
-
supported: true,
|
|
1686
|
-
signal: controller.signal,
|
|
1687
|
-
cancelledState: () => cancelled,
|
|
1688
|
-
leaseLost: () => lost,
|
|
1689
|
-
release: async () => {
|
|
1690
|
-
stopped = true;
|
|
1691
|
-
clearInterval(timer);
|
|
1692
|
-
const monitor = activeMonitor;
|
|
1693
|
-
if (monitor) {
|
|
1694
|
-
await waitForAgentMonitorShutdown(monitor, monitorShutdownTimeoutMs);
|
|
1695
|
-
}
|
|
1696
|
-
await store.releaseLease?.(state.runId, ownerId, state.scope);
|
|
1697
|
-
}
|
|
1698
|
-
};
|
|
1699
|
-
};
|
|
1700
|
-
const emitRunStartTelemetry = async (agent, state, memoryMessages, approvals, invocationStartedAt) => {
|
|
1701
|
-
await emitTelemetryEvent(agent, {
|
|
1702
|
-
type: "run-start",
|
|
1703
|
-
runId: state.runId,
|
|
1704
|
-
agentId: state.agentId,
|
|
1705
|
-
agentName: agent.name,
|
|
1706
|
-
provider: state.provider,
|
|
1707
|
-
modelId: state.modelId,
|
|
1708
|
-
maxSteps: state.maxSteps,
|
|
1709
|
-
startedAt: invocationStartedAt
|
|
1710
|
-
});
|
|
1711
|
-
if (state.handoff) {
|
|
1712
|
-
await emitTelemetryEvent(agent, {
|
|
1713
|
-
type: "handoff",
|
|
1714
|
-
runId: state.runId,
|
|
1715
|
-
agentId: state.agentId,
|
|
1716
|
-
handoff: state.handoff
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
if (memoryMessages.length) {
|
|
1720
|
-
await emitTelemetryEvent(agent, {
|
|
1721
|
-
type: "memory-loaded",
|
|
1722
|
-
runId: state.runId,
|
|
1723
|
-
agentId: state.agentId,
|
|
1724
|
-
messageCount: memoryMessages.length
|
|
1725
|
-
});
|
|
1726
|
-
}
|
|
1727
|
-
for (const approval of approvals ?? []) {
|
|
1728
|
-
await emitTelemetryEvent(agent, {
|
|
1729
|
-
type: "approval-resolved",
|
|
1730
|
-
runId: state.runId,
|
|
1731
|
-
agentId: state.agentId,
|
|
1732
|
-
approval
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
};
|
|
1736
|
-
const emitRunFinishTelemetry = async (agent, state) => {
|
|
1737
|
-
await emitTelemetryEvent(agent, {
|
|
1738
|
-
type: "run-finish",
|
|
1739
|
-
runId: state.runId,
|
|
1740
|
-
agentId: state.agentId,
|
|
1741
|
-
agentName: agent.name,
|
|
1742
|
-
status: state.status,
|
|
1743
|
-
state: cloneState(state),
|
|
1744
|
-
finishedAt: Date.now()
|
|
1745
|
-
});
|
|
1746
|
-
};
|
|
1747
|
-
export const createAgent = (definition) => ({
|
|
1748
|
-
...definition,
|
|
1749
|
-
metadata: cloneMetadata(definition.metadata)
|
|
1750
|
-
});
|
|
1751
|
-
export class Agent {
|
|
1752
|
-
id;
|
|
1753
|
-
name;
|
|
1754
|
-
model;
|
|
1755
|
-
instructions;
|
|
1756
|
-
contextSchema;
|
|
1757
|
-
tools;
|
|
1758
|
-
maxSteps;
|
|
1759
|
-
temperature;
|
|
1760
|
-
maxTokens;
|
|
1761
|
-
reasoning;
|
|
1762
|
-
outputSchema;
|
|
1763
|
-
outputMode;
|
|
1764
|
-
outputName;
|
|
1765
|
-
outputDescription;
|
|
1766
|
-
toolExecution;
|
|
1767
|
-
toolApprovalPolicy;
|
|
1768
|
-
toolApprovalSigner;
|
|
1769
|
-
inputGuardrails;
|
|
1770
|
-
outputGuardrails;
|
|
1771
|
-
providerOptions;
|
|
1772
|
-
subagents;
|
|
1773
|
-
harness;
|
|
1774
|
-
executionEnvironment;
|
|
1775
|
-
compaction;
|
|
1776
|
-
policy;
|
|
1777
|
-
metadata;
|
|
1778
|
-
store;
|
|
1779
|
-
memory;
|
|
1780
|
-
onTelemetryEvent;
|
|
1781
|
-
hookFailurePolicy;
|
|
1782
|
-
constructor(definition) {
|
|
1783
|
-
Object.assign(this, createAgent(definition));
|
|
1784
|
-
this.model = definition.model;
|
|
1785
|
-
}
|
|
1786
|
-
toDefinition() {
|
|
1787
|
-
return createAgent({
|
|
1788
|
-
id: this.id,
|
|
1789
|
-
name: this.name,
|
|
1790
|
-
model: this.model,
|
|
1791
|
-
instructions: this.instructions,
|
|
1792
|
-
contextSchema: this.contextSchema,
|
|
1793
|
-
tools: this.tools,
|
|
1794
|
-
maxSteps: this.maxSteps,
|
|
1795
|
-
temperature: this.temperature,
|
|
1796
|
-
maxTokens: this.maxTokens,
|
|
1797
|
-
reasoning: this.reasoning,
|
|
1798
|
-
outputSchema: this.outputSchema,
|
|
1799
|
-
outputMode: this.outputMode,
|
|
1800
|
-
outputName: this.outputName,
|
|
1801
|
-
outputDescription: this.outputDescription,
|
|
1802
|
-
toolExecution: this.toolExecution,
|
|
1803
|
-
toolApprovalPolicy: this.toolApprovalPolicy,
|
|
1804
|
-
toolApprovalSigner: this.toolApprovalSigner,
|
|
1805
|
-
inputGuardrails: this.inputGuardrails,
|
|
1806
|
-
outputGuardrails: this.outputGuardrails,
|
|
1807
|
-
providerOptions: this.providerOptions,
|
|
1808
|
-
subagents: this.subagents,
|
|
1809
|
-
harness: this.harness,
|
|
1810
|
-
executionEnvironment: this.executionEnvironment,
|
|
1811
|
-
compaction: this.compaction,
|
|
1812
|
-
policy: this.policy,
|
|
1813
|
-
metadata: this.metadata,
|
|
1814
|
-
store: this.store,
|
|
1815
|
-
memory: this.memory,
|
|
1816
|
-
onTelemetryEvent: this.onTelemetryEvent,
|
|
1817
|
-
hookFailurePolicy: this.hookFailurePolicy
|
|
1818
|
-
});
|
|
1819
|
-
}
|
|
1820
|
-
run(input = {}) {
|
|
1821
|
-
return runAgent(this.toDefinition(), input);
|
|
1822
|
-
}
|
|
1823
|
-
resume(input) {
|
|
1824
|
-
return resumeAgent(this.toDefinition(), input);
|
|
1825
|
-
}
|
|
1826
|
-
stream(input = {}) {
|
|
1827
|
-
return streamAgent(this.toDefinition(), input);
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
export const prepareSubagentsForAgent = (agent, options = {}) => {
|
|
1831
|
-
const store = options.store ?? agent.store;
|
|
1832
|
-
const memory = options.memory ?? agent.memory;
|
|
1833
|
-
const onTelemetryEvent = options.onTelemetryEvent ?? agent.onTelemetryEvent;
|
|
1834
|
-
const toolApprovalPolicy = options.toolApprovalPolicy ?? agent.toolApprovalPolicy;
|
|
1835
|
-
const toolExecution = options.toolExecution ?? agent.toolExecution;
|
|
1836
|
-
const executionEnvironment = options.executionEnvironment ?? agent.executionEnvironment;
|
|
1837
|
-
const compaction = options.compaction ?? agent.compaction;
|
|
1838
|
-
const defaultMetadata = cloneMetadata(agent.metadata, options.metadata);
|
|
1839
|
-
return {
|
|
1840
|
-
...agent,
|
|
1841
|
-
metadata: cloneMetadata(agent.metadata),
|
|
1842
|
-
subagents: (agent.subagents ?? []).map((subagent) => ({
|
|
1843
|
-
...subagent,
|
|
1844
|
-
metadata: cloneMetadata(defaultMetadata, subagent.metadata),
|
|
1845
|
-
agent: {
|
|
1846
|
-
...subagent.agent,
|
|
1847
|
-
store: subagent.agent.store ?? store,
|
|
1848
|
-
memory: subagent.agent.memory ?? memory,
|
|
1849
|
-
onTelemetryEvent: subagent.agent.onTelemetryEvent ?? onTelemetryEvent,
|
|
1850
|
-
toolApprovalPolicy: subagent.agent.toolApprovalPolicy ?? toolApprovalPolicy,
|
|
1851
|
-
toolExecution: subagent.agent.toolExecution ?? toolExecution,
|
|
1852
|
-
executionEnvironment: subagent.agent.executionEnvironment ?? executionEnvironment,
|
|
1853
|
-
compaction: subagent.agent.compaction ?? compaction,
|
|
1854
|
-
metadata: cloneMetadata(defaultMetadata, subagent.agent.metadata)
|
|
1855
|
-
}
|
|
1856
|
-
}))
|
|
1857
|
-
};
|
|
1858
|
-
};
|
|
1859
|
-
export const runAgentGroup = async (agents, input = {}) => {
|
|
1860
|
-
const { stopOnError, maxConcurrency, runId: _runId, state: _state, approvals: _approvals, handoff: _handoff, ...sharedInput } = input;
|
|
1861
|
-
if (maxConcurrency !== undefined && (!Number.isSafeInteger(maxConcurrency) || maxConcurrency < 1)) {
|
|
1862
|
-
throw new ValidationError('Agent group "maxConcurrency" must be a positive safe integer.');
|
|
1863
|
-
}
|
|
1864
|
-
// Validate the complete group before any member can claim a key or invoke a model.
|
|
1865
|
-
const identities = new Set();
|
|
1866
|
-
const claims = [];
|
|
1867
|
-
const memberInputs = agents.map((member) => {
|
|
1868
|
-
const merged = { ...sharedInput, ...member.input };
|
|
1869
|
-
ensureValidScope(merged.scope);
|
|
1870
|
-
ensureValidIdempotencyInput(merged, member.agent.store);
|
|
1871
|
-
if (!merged.idempotencyKey)
|
|
1872
|
-
return merged;
|
|
1873
|
-
const identity = member.name ?? member.agent.id;
|
|
1874
|
-
if (!identity || identities.has(identity)) {
|
|
1875
|
-
throw new ValidationError("Idempotent agent group members require unique names or agent IDs.");
|
|
1876
|
-
}
|
|
1877
|
-
identities.add(identity);
|
|
1878
|
-
const key = member.input?.idempotencyKey ?? `agent-group:${JSON.stringify([input.idempotencyKey, identity])}`;
|
|
1879
|
-
const scope = merged.scope;
|
|
1880
|
-
const scopedKey = JSON.stringify([scope?.namespace ?? "default", scope?.tenantId, scope?.userId, key]);
|
|
1881
|
-
if (claims.some((claim) => claim.store === member.agent.store && claim.key === scopedKey)) {
|
|
1882
|
-
throw new ConflictError("Agent group members cannot share an explicit idempotency key in the same store and scope.");
|
|
1883
|
-
}
|
|
1884
|
-
claims.push({ store: member.agent.store, key: scopedKey });
|
|
1885
|
-
return {
|
|
1886
|
-
...merged,
|
|
1887
|
-
idempotencyKey: key,
|
|
1888
|
-
metadata: cloneMetadata(input.metadata, member.input?.metadata, {
|
|
1889
|
-
agentGroupIdentity: JSON.stringify([input.idempotencyKey ?? null, identity])
|
|
1890
|
-
})
|
|
1891
|
-
};
|
|
1892
|
-
});
|
|
1893
|
-
const parentRunId = input.parentRunId;
|
|
1894
|
-
const controllers = agents.map(() => new AbortController());
|
|
1895
|
-
let failFastTriggered = false;
|
|
1896
|
-
const isFailingOutput = (output) => output.status === "failed" || output.status === "timed_out";
|
|
1897
|
-
const abortPending = (currentIndex) => {
|
|
1898
|
-
if (!stopOnError || failFastTriggered) {
|
|
1899
|
-
return;
|
|
1900
|
-
}
|
|
1901
|
-
failFastTriggered = true;
|
|
1902
|
-
controllers.forEach((controller, index) => {
|
|
1903
|
-
if (index !== currentIndex) {
|
|
1904
|
-
controller.abort();
|
|
1905
|
-
}
|
|
1906
|
-
});
|
|
1907
|
-
};
|
|
1908
|
-
const runMember = async (member, index) => {
|
|
1909
|
-
const merged = createMergedAbortSignal(input.abortSignal, member.input?.abortSignal, controllers[index].signal);
|
|
1910
|
-
try {
|
|
1911
|
-
if (merged.signal?.aborted) {
|
|
1912
|
-
throw new DOMException("Agent group member aborted before execution.", "AbortError");
|
|
1913
|
-
}
|
|
1914
|
-
const runInput = {
|
|
1915
|
-
...memberInputs[index],
|
|
1916
|
-
parentRunId: member.input?.parentRunId ?? parentRunId,
|
|
1917
|
-
abortSignal: merged.signal,
|
|
1918
|
-
metadata: cloneMetadata(input.metadata, memberInputs[index]?.metadata, {
|
|
1919
|
-
...(member.name ? { agentGroupMember: member.name } : {})
|
|
1920
|
-
})
|
|
1921
|
-
};
|
|
1922
|
-
const output = await runAgent(member.agent, runInput);
|
|
1923
|
-
if (isFailingOutput(output)) {
|
|
1924
|
-
abortPending(index);
|
|
1925
|
-
}
|
|
1926
|
-
return output;
|
|
1927
|
-
}
|
|
1928
|
-
catch (error) {
|
|
1929
|
-
abortPending(index);
|
|
1930
|
-
throw error;
|
|
1931
|
-
}
|
|
1932
|
-
finally {
|
|
1933
|
-
merged.cleanup();
|
|
1934
|
-
}
|
|
1935
|
-
};
|
|
1936
|
-
const settled = new Array(agents.length);
|
|
1937
|
-
let nextIndex = 0;
|
|
1938
|
-
await Promise.all(Array.from({ length: Math.min(maxConcurrency ?? agents.length, agents.length) }, async () => {
|
|
1939
|
-
while (nextIndex < agents.length) {
|
|
1940
|
-
const index = nextIndex++;
|
|
1941
|
-
try {
|
|
1942
|
-
settled[index] = { status: "fulfilled", value: await runMember(agents[index], index) };
|
|
1943
|
-
}
|
|
1944
|
-
catch (reason) {
|
|
1945
|
-
settled[index] = { status: "rejected", reason };
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
}));
|
|
1949
|
-
const outputs = settled.map((result, index) => {
|
|
1950
|
-
const member = agents[index];
|
|
1951
|
-
if (result.status === "fulfilled") {
|
|
1952
|
-
return {
|
|
1953
|
-
name: member.name,
|
|
1954
|
-
agentId: result.value.state.agentId ?? member.agent.id,
|
|
1955
|
-
status: "fulfilled",
|
|
1956
|
-
output: result.value
|
|
1957
|
-
};
|
|
1958
|
-
}
|
|
1959
|
-
return {
|
|
1960
|
-
name: member.name,
|
|
1961
|
-
agentId: member.agent.id,
|
|
1962
|
-
status: "rejected",
|
|
1963
|
-
error: {
|
|
1964
|
-
message: stopOnError && failFastTriggered && controllers[index].signal.aborted
|
|
1965
|
-
? AGENT_GROUP_FAIL_FAST_ABORT_MESSAGE
|
|
1966
|
-
: result.reason instanceof Error
|
|
1967
|
-
? result.reason.message
|
|
1968
|
-
: String(result.reason)
|
|
1969
|
-
}
|
|
1970
|
-
};
|
|
1971
|
-
});
|
|
1972
|
-
const precedence = [
|
|
1973
|
-
"failed", "timed_out", "cancel_requested", "running", "queued", "waiting_approval", "suspended", "cancelled", "completed"
|
|
1974
|
-
];
|
|
1975
|
-
const status = precedence.find((status) => outputs.some((output) => output.status === "rejected" ? status === "failed" : output.output?.status === status)) ?? "completed";
|
|
1976
|
-
return {
|
|
1977
|
-
status,
|
|
1978
|
-
parentRunId,
|
|
1979
|
-
outputs
|
|
1980
|
-
};
|
|
1981
|
-
};
|
|
1982
|
-
export const cancelAgentRun = async (store, runId, options = {}) => {
|
|
1983
|
-
const loadedState = await store.load(runId, options.scope);
|
|
1984
|
-
if (!loadedState) {
|
|
1985
|
-
return undefined;
|
|
1986
|
-
}
|
|
1987
|
-
const cancelledAt = Date.now();
|
|
1988
|
-
const status = options.mode === "final" ? "cancelled" : "cancel_requested";
|
|
1989
|
-
const state = normalizeAgentRunState({
|
|
1990
|
-
...loadedState,
|
|
1991
|
-
status,
|
|
1992
|
-
cancelledAt,
|
|
1993
|
-
cancellationReason: options.reason,
|
|
1994
|
-
updatedAt: cancelledAt,
|
|
1995
|
-
error: undefined
|
|
1996
|
-
});
|
|
1997
|
-
await saveStateWithRevision(store, state);
|
|
1998
|
-
return cloneState(state);
|
|
1999
|
-
};
|
|
2000
|
-
export const cancelAgentRunTree = async (store, runId, options = {}) => {
|
|
2001
|
-
if (!store.findByParentRunId) {
|
|
2002
|
-
throw new ValidationError('The agent run "store" must implement "findByParentRunId()" to cancel an agent run tree.');
|
|
2003
|
-
}
|
|
2004
|
-
const cancelledAt = Date.now();
|
|
2005
|
-
const status = options.mode === "final" ? "cancelled" : "cancel_requested";
|
|
2006
|
-
const cancelState = (state) => normalizeAgentRunState({
|
|
2007
|
-
...state,
|
|
2008
|
-
status,
|
|
2009
|
-
cancelledAt,
|
|
2010
|
-
cancellationReason: options.reason,
|
|
2011
|
-
updatedAt: cancelledAt,
|
|
2012
|
-
error: undefined
|
|
2013
|
-
});
|
|
2014
|
-
const parent = await store.load(runId, options.scope);
|
|
2015
|
-
if (!parent) {
|
|
2016
|
-
return {
|
|
2017
|
-
parent: undefined,
|
|
2018
|
-
children: []
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
2021
|
-
const visited = new Set([runId]);
|
|
2022
|
-
const children = [];
|
|
2023
|
-
const collectChildren = async (parentRunId) => {
|
|
2024
|
-
const directChildren = await store.findByParentRunId?.(parentRunId, options.scope);
|
|
2025
|
-
for (const child of directChildren ?? []) {
|
|
2026
|
-
if (visited.has(child.runId)) {
|
|
2027
|
-
continue;
|
|
2028
|
-
}
|
|
2029
|
-
visited.add(child.runId);
|
|
2030
|
-
children.push(child);
|
|
2031
|
-
await collectChildren(child.runId);
|
|
2032
|
-
}
|
|
2033
|
-
};
|
|
2034
|
-
await collectChildren(runId);
|
|
2035
|
-
const cancelledParent = cancelState(parent);
|
|
2036
|
-
const cancelledChildren = children.map(cancelState);
|
|
2037
|
-
await saveStateWithRevision(store, cancelledParent);
|
|
2038
|
-
for (const child of cancelledChildren) {
|
|
2039
|
-
await saveStateWithRevision(store, child);
|
|
2040
|
-
}
|
|
2041
|
-
return {
|
|
2042
|
-
parent: cloneState(cancelledParent),
|
|
2043
|
-
children: cancelledChildren.map(cloneState)
|
|
2044
|
-
};
|
|
2045
|
-
};
|
|
2046
|
-
export const runAgent = async (agent, input = {}) => {
|
|
2047
|
-
const invocationStartedAt = Date.now();
|
|
2048
|
-
const telemetryRunId = input.runId ?? input.state?.runId ?? randomId("run");
|
|
2049
|
-
const invocationInput = input.runId || input.state
|
|
2050
|
-
? input
|
|
2051
|
-
: { ...input, runId: telemetryRunId };
|
|
2052
|
-
let invocationStatus = "completed";
|
|
2053
|
-
let invocationError;
|
|
2054
|
-
const returnInvocationOutput = (output) => {
|
|
2055
|
-
invocationStatus = output.status;
|
|
2056
|
-
if (output.status === "failed" || output.status === "timed_out") {
|
|
2057
|
-
invocationError = new Error(output.error?.message ?? `Agent invocation ${output.status}.`);
|
|
2058
|
-
if (output.status === "timed_out")
|
|
2059
|
-
invocationError.name = "TimeoutError";
|
|
2060
|
-
}
|
|
2061
|
-
else {
|
|
2062
|
-
invocationError = undefined;
|
|
2063
|
-
}
|
|
2064
|
-
return output;
|
|
2065
|
-
};
|
|
2066
|
-
await emitInvocationStartTelemetry(agent, telemetryRunId, invocationStartedAt, Math.max(1, input.maxSteps ?? input.state?.maxSteps ?? agent.maxSteps ?? 1));
|
|
2067
|
-
try {
|
|
2068
|
-
const context = await resolveContext(agent, invocationInput);
|
|
2069
|
-
const currentStatus = normalizeApprovalStatus(context.state.status);
|
|
2070
|
-
const policy = resolveRunPolicy(agent, input);
|
|
2071
|
-
if (currentStatus === "completed" ||
|
|
2072
|
-
currentStatus === "cancelled" ||
|
|
2073
|
-
currentStatus === "cancel_requested" ||
|
|
2074
|
-
currentStatus === "timed_out") {
|
|
2075
|
-
context.state.status = currentStatus;
|
|
2076
|
-
invocationStatus = currentStatus;
|
|
2077
|
-
return returnInvocationOutput(toOutput(context.state));
|
|
2078
|
-
}
|
|
2079
|
-
if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0) {
|
|
2080
|
-
context.state.status = currentStatus;
|
|
2081
|
-
invocationStatus = currentStatus;
|
|
2082
|
-
return returnInvocationOutput(toOutput(context.state));
|
|
2083
|
-
}
|
|
2084
|
-
const supportsLeases = Boolean(agent.store?.acquireLease && agent.store.renewLease && agent.store.releaseLease);
|
|
2085
|
-
if (!context.fresh && currentStatus === "running" && !supportsLeases) {
|
|
2086
|
-
invocationStatus = currentStatus;
|
|
2087
|
-
return returnInvocationOutput(toOutput(context.state));
|
|
2088
|
-
}
|
|
2089
|
-
const freshRequiresExistingClaim = context.fresh && Boolean(context.state.idempotencyKey);
|
|
2090
|
-
if (context.fresh && !freshRequiresExistingClaim) {
|
|
2091
|
-
await claimAgentExecution(agent, context.state);
|
|
2092
|
-
}
|
|
2093
|
-
const executionLease = await acquireAgentExecutionLease(agent, context.state, policy);
|
|
2094
|
-
if (!executionLease) {
|
|
2095
|
-
if (input.state) {
|
|
2096
|
-
throw new ConflictError(`Agent run "${context.state.runId}" is already owned by another worker.`);
|
|
2097
|
-
}
|
|
2098
|
-
const activeState = await agent.store?.load(context.state.runId, context.state.scope);
|
|
2099
|
-
const outputState = activeState ? normalizeAgentRunState(activeState) : context.state;
|
|
2100
|
-
invocationStatus = outputState.status;
|
|
2101
|
-
return returnInvocationOutput(toOutput(outputState));
|
|
2102
|
-
}
|
|
2103
|
-
try {
|
|
2104
|
-
if (!context.fresh || freshRequiresExistingClaim) {
|
|
2105
|
-
await claimAgentExecution(agent, context.state);
|
|
2106
|
-
}
|
|
2107
|
-
await emitRunStartTelemetry(agent, context.state, context.memoryMessages, input.approvals, invocationStartedAt);
|
|
2108
|
-
}
|
|
2109
|
-
catch (error) {
|
|
2110
|
-
await executionLease.release();
|
|
2111
|
-
throw error;
|
|
2112
|
-
}
|
|
2113
|
-
if (context.remainingSteps === 0) {
|
|
2114
|
-
const state = createFailedState(context.state, "Agent exhausted maxSteps before reaching a terminal response.");
|
|
2115
|
-
await persistState(agent, state, policy);
|
|
2116
|
-
await emitRunFinishTelemetry(agent, state);
|
|
2117
|
-
await executionLease.release();
|
|
2118
|
-
return returnInvocationOutput(toOutput(state));
|
|
2119
|
-
}
|
|
2120
|
-
let inputGuardrail;
|
|
2121
|
-
try {
|
|
2122
|
-
inputGuardrail = await runGuardrails(agent, context.state, "input", agent.inputGuardrails, () => ({
|
|
2123
|
-
runId: context.state.runId,
|
|
2124
|
-
agentId: context.state.agentId,
|
|
2125
|
-
context: context.context,
|
|
2126
|
-
state: cloneState(context.state),
|
|
2127
|
-
messages: context.messages,
|
|
2128
|
-
metadata: context.state.metadata
|
|
2129
|
-
}));
|
|
2130
|
-
}
|
|
2131
|
-
catch (error) {
|
|
2132
|
-
await executionLease.release();
|
|
2133
|
-
throw error;
|
|
2134
|
-
}
|
|
2135
|
-
if (inputGuardrail) {
|
|
2136
|
-
const failedState = applyGuardrailFailure(context.state, "input", inputGuardrail);
|
|
2137
|
-
await persistState(agent, failedState, policy);
|
|
2138
|
-
await emitRunFinishTelemetry(agent, failedState);
|
|
2139
|
-
await executionLease.release();
|
|
2140
|
-
return returnInvocationOutput(toOutput(failedState));
|
|
2141
|
-
}
|
|
2142
|
-
const abortContext = createAgentAbortContext(input.abortSignal, policy, executionLease.signal);
|
|
2143
|
-
let executionEnvironmentSession;
|
|
2144
|
-
let executionEnvironmentStatus = "failed";
|
|
2145
|
-
let executionEnvironmentError;
|
|
2146
|
-
try {
|
|
2147
|
-
executionEnvironmentSession = await acquireExecutionEnvironment(context.executionEnvironment, context.state, context.context, abortContext.signal);
|
|
2148
|
-
}
|
|
2149
|
-
catch (error) {
|
|
2150
|
-
abortContext.cleanup();
|
|
2151
|
-
await executionLease.release();
|
|
2152
|
-
throw error;
|
|
2153
|
-
}
|
|
2154
|
-
try {
|
|
2155
|
-
const result = await withAgentTelemetryRunContext(agent, context.state.runId, () => withAgentPolicyTimeout(generateText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, context.context, executionEnvironmentSession, abortContext.signal)), abortContext));
|
|
2156
|
-
const cancelled = executionLease.cancelledState();
|
|
2157
|
-
if (cancelled) {
|
|
2158
|
-
executionEnvironmentStatus = cancelled.status;
|
|
2159
|
-
await emitRunFinishTelemetry(agent, cancelled);
|
|
2160
|
-
return returnInvocationOutput(toOutput(cancelled));
|
|
2161
|
-
}
|
|
2162
|
-
if (executionLease.leaseLost()) {
|
|
2163
|
-
throw new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
2164
|
-
}
|
|
2165
|
-
const newSteps = mapSteps(result.steps, context.state.currentStep, result.toolResults);
|
|
2166
|
-
let output = finalizeState(agent, context.state, result, newSteps, result.toolResults);
|
|
2167
|
-
const outputGuardrail = await runGuardrails(agent, output.state, "output", agent.outputGuardrails, () => ({
|
|
2168
|
-
runId: output.state.runId,
|
|
2169
|
-
agentId: output.state.agentId,
|
|
2170
|
-
context: context.context,
|
|
2171
|
-
state: cloneState(output.state),
|
|
2172
|
-
output,
|
|
2173
|
-
metadata: output.state.metadata
|
|
2174
|
-
}));
|
|
2175
|
-
if (outputGuardrail) {
|
|
2176
|
-
output = toOutput(applyGuardrailFailure(output.state, "output", outputGuardrail));
|
|
2177
|
-
}
|
|
2178
|
-
await emitFinalizedStepTelemetry(agent, output.state, newSteps);
|
|
2179
|
-
await emitApprovalTelemetry(agent, output.state, [
|
|
2180
|
-
...(result.approvalRequests ?? []),
|
|
2181
|
-
...approvalsFromEvents(newSteps.flatMap((step) => step.response?.messages ?? []))
|
|
2182
|
-
]);
|
|
2183
|
-
await persistState(agent, output.state, policy);
|
|
2184
|
-
await emitRunFinishTelemetry(agent, output.state);
|
|
2185
|
-
executionEnvironmentStatus = output.status;
|
|
2186
|
-
return returnInvocationOutput(output);
|
|
2187
|
-
}
|
|
2188
|
-
catch (error) {
|
|
2189
|
-
executionEnvironmentError = {
|
|
2190
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2191
|
-
};
|
|
2192
|
-
const cancelled = executionLease.cancelledState();
|
|
2193
|
-
if (cancelled) {
|
|
2194
|
-
executionEnvironmentStatus = cancelled.status;
|
|
2195
|
-
await emitRunFinishTelemetry(agent, cancelled);
|
|
2196
|
-
return returnInvocationOutput(toOutput(cancelled));
|
|
2197
|
-
}
|
|
2198
|
-
if (executionLease.leaseLost()) {
|
|
2199
|
-
throw new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
2200
|
-
}
|
|
2201
|
-
if (error instanceof AgentPolicyTimeoutError || abortContext.isTimedOut()) {
|
|
2202
|
-
const status = policy?.onTimeout === "cancel-requested" ? "cancel_requested" : "timed_out";
|
|
2203
|
-
const message = error instanceof Error ? error.message : `Agent run timed out after ${policy?.timeoutMs}ms.`;
|
|
2204
|
-
const durableState = agent.store
|
|
2205
|
-
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
2206
|
-
: context.state;
|
|
2207
|
-
const timedOutState = createTerminalState(durableState, status, message);
|
|
2208
|
-
await persistState(agent, timedOutState, policy);
|
|
2209
|
-
await emitRunFinishTelemetry(agent, timedOutState);
|
|
2210
|
-
executionEnvironmentStatus = timedOutState.status;
|
|
2211
|
-
return returnInvocationOutput(toOutput(timedOutState));
|
|
2212
|
-
}
|
|
2213
|
-
const durableState = agent.store
|
|
2214
|
-
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
2215
|
-
: context.state;
|
|
2216
|
-
const failedState = createFailedState(durableState, error);
|
|
2217
|
-
await persistState(agent, failedState, policy);
|
|
2218
|
-
await emitRunFinishTelemetry(agent, failedState);
|
|
2219
|
-
executionEnvironmentStatus = failedState.status;
|
|
2220
|
-
throw error;
|
|
2221
|
-
}
|
|
2222
|
-
finally {
|
|
2223
|
-
await executionEnvironmentSession?.release?.({
|
|
2224
|
-
status: executionEnvironmentStatus,
|
|
2225
|
-
error: executionEnvironmentError
|
|
2226
|
-
});
|
|
2227
|
-
await executionLease.release();
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
catch (error) {
|
|
2231
|
-
invocationStatus = "failed";
|
|
2232
|
-
invocationError = error instanceof Error ? error : new Error(String(error));
|
|
2233
|
-
throw error;
|
|
2234
|
-
}
|
|
2235
|
-
finally {
|
|
2236
|
-
await emitInvocationFinishTelemetry(agent, telemetryRunId, invocationStatus, invocationError);
|
|
2237
|
-
}
|
|
2238
|
-
};
|
|
2239
|
-
export const streamAgent = (agent, input = {}) => {
|
|
2240
|
-
const invocationStartedAt = Date.now();
|
|
2241
|
-
const telemetryRunId = input.runId ?? input.state?.runId ?? randomId("run");
|
|
2242
|
-
const invocationInput = input.runId || input.state
|
|
2243
|
-
? input
|
|
2244
|
-
: { ...input, runId: telemetryRunId };
|
|
2245
|
-
const policy = resolveRunPolicy(agent, input);
|
|
2246
|
-
const broadcast = new BoundedReplayBroadcast({
|
|
2247
|
-
maxHistory: policy?.maxStreamEvents ?? 4096
|
|
2248
|
-
});
|
|
2249
|
-
const publish = (event, terminal = false) => broadcast.publish(event, { terminal });
|
|
2250
|
-
let activeLease;
|
|
2251
|
-
let activeExecutionEnvironment;
|
|
2252
|
-
let invocationFinishPromise;
|
|
2253
|
-
const finishInvocation = (status, error) => {
|
|
2254
|
-
invocationFinishPromise ??= emitInvocationFinishTelemetry(agent, telemetryRunId, status, error);
|
|
2255
|
-
return invocationFinishPromise;
|
|
2256
|
-
};
|
|
2257
|
-
const runner = (async () => {
|
|
2258
|
-
await emitInvocationStartTelemetry(agent, telemetryRunId, invocationStartedAt, Math.max(1, input.maxSteps ?? input.state?.maxSteps ?? agent.maxSteps ?? 1));
|
|
2259
|
-
const context = await resolveContext(agent, invocationInput);
|
|
2260
|
-
const currentStatus = normalizeApprovalStatus(context.state.status);
|
|
2261
|
-
const supportsLeases = Boolean(agent.store?.acquireLease && agent.store.renewLease && agent.store.releaseLease);
|
|
2262
|
-
if (!context.fresh && currentStatus === "running" && !supportsLeases) {
|
|
2263
|
-
broadcast.close();
|
|
2264
|
-
await finishInvocation(currentStatus);
|
|
2265
|
-
return {
|
|
2266
|
-
output: toOutput(context.state),
|
|
2267
|
-
textStream: emptyAsyncIterable()
|
|
2268
|
-
};
|
|
2269
|
-
}
|
|
2270
|
-
if (currentStatus === "completed" ||
|
|
2271
|
-
currentStatus === "cancelled" ||
|
|
2272
|
-
currentStatus === "cancel_requested" ||
|
|
2273
|
-
currentStatus === "timed_out") {
|
|
2274
|
-
context.state.status = currentStatus;
|
|
2275
|
-
broadcast.close();
|
|
2276
|
-
await finishInvocation(currentStatus);
|
|
2277
|
-
return {
|
|
2278
|
-
output: toOutput(context.state),
|
|
2279
|
-
textStream: emptyAsyncIterable()
|
|
2280
|
-
};
|
|
2281
|
-
}
|
|
2282
|
-
if (currentStatus === "waiting_approval" && context.state.pendingApprovals.length > 0) {
|
|
2283
|
-
context.state.status = currentStatus;
|
|
2284
|
-
broadcast.close();
|
|
2285
|
-
await finishInvocation(currentStatus);
|
|
2286
|
-
return {
|
|
2287
|
-
output: toOutput(context.state),
|
|
2288
|
-
textStream: emptyAsyncIterable()
|
|
2289
|
-
};
|
|
2290
|
-
}
|
|
2291
|
-
const freshRequiresExistingClaim = context.fresh && Boolean(context.state.idempotencyKey);
|
|
2292
|
-
if (context.fresh && !freshRequiresExistingClaim) {
|
|
2293
|
-
await claimAgentExecution(agent, context.state);
|
|
2294
|
-
}
|
|
2295
|
-
const executionLease = await acquireAgentExecutionLease(agent, context.state, policy);
|
|
2296
|
-
if (!executionLease) {
|
|
2297
|
-
if (input.state) {
|
|
2298
|
-
throw new ConflictError(`Agent run "${context.state.runId}" is already owned by another worker.`);
|
|
2299
|
-
}
|
|
2300
|
-
const activeState = await agent.store?.load(context.state.runId, context.state.scope);
|
|
2301
|
-
broadcast.close();
|
|
2302
|
-
const outputState = activeState ? normalizeAgentRunState(activeState) : context.state;
|
|
2303
|
-
await finishInvocation(outputState.status);
|
|
2304
|
-
return {
|
|
2305
|
-
output: toOutput(outputState),
|
|
2306
|
-
textStream: emptyAsyncIterable()
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
activeLease = executionLease;
|
|
2310
|
-
try {
|
|
2311
|
-
if (!context.fresh || freshRequiresExistingClaim) {
|
|
2312
|
-
await claimAgentExecution(agent, context.state);
|
|
2313
|
-
}
|
|
2314
|
-
await emitRunStartTelemetry(agent, context.state, context.memoryMessages, input.approvals, invocationStartedAt);
|
|
2315
|
-
}
|
|
2316
|
-
catch (error) {
|
|
2317
|
-
await executionLease.release();
|
|
2318
|
-
throw error;
|
|
2319
|
-
}
|
|
2320
|
-
if (context.remainingSteps === 0) {
|
|
2321
|
-
const state = createFailedState(context.state, "Agent exhausted maxSteps before reaching a terminal response.");
|
|
2322
|
-
await persistState(agent, state, policy);
|
|
2323
|
-
await emitRunFinishTelemetry(agent, state);
|
|
2324
|
-
await executionLease.release();
|
|
2325
|
-
broadcast.close();
|
|
2326
|
-
await finishInvocation(state.status);
|
|
2327
|
-
return {
|
|
2328
|
-
output: toOutput(state),
|
|
2329
|
-
textStream: emptyAsyncIterable()
|
|
2330
|
-
};
|
|
2331
|
-
}
|
|
2332
|
-
let inputGuardrail;
|
|
2333
|
-
try {
|
|
2334
|
-
inputGuardrail = await runGuardrails(agent, context.state, "input", agent.inputGuardrails, () => ({
|
|
2335
|
-
runId: context.state.runId,
|
|
2336
|
-
agentId: context.state.agentId,
|
|
2337
|
-
context: context.context,
|
|
2338
|
-
state: cloneState(context.state),
|
|
2339
|
-
messages: context.messages,
|
|
2340
|
-
metadata: context.state.metadata
|
|
2341
|
-
}));
|
|
2342
|
-
}
|
|
2343
|
-
catch (error) {
|
|
2344
|
-
await executionLease.release();
|
|
2345
|
-
throw error;
|
|
2346
|
-
}
|
|
2347
|
-
if (inputGuardrail) {
|
|
2348
|
-
const failedState = applyGuardrailFailure(context.state, "input", inputGuardrail);
|
|
2349
|
-
await persistState(agent, failedState, policy);
|
|
2350
|
-
await emitRunFinishTelemetry(agent, failedState);
|
|
2351
|
-
await publish({
|
|
2352
|
-
type: "error",
|
|
2353
|
-
error: new GuardrailTriggeredError("input", failedState.error?.message ?? "Agent input guardrail triggered.", {
|
|
2354
|
-
metadata: inputGuardrail.metadata
|
|
2355
|
-
})
|
|
2356
|
-
}, true);
|
|
2357
|
-
await publish({
|
|
2358
|
-
type: "agent-run-finish",
|
|
2359
|
-
status: failedState.status,
|
|
2360
|
-
state: failedState
|
|
2361
|
-
}, true);
|
|
2362
|
-
broadcast.close();
|
|
2363
|
-
await executionLease.release();
|
|
2364
|
-
await finishInvocation(failedState.status);
|
|
2365
|
-
return {
|
|
2366
|
-
output: toOutput(failedState),
|
|
2367
|
-
textStream: emptyAsyncIterable()
|
|
2368
|
-
};
|
|
2369
|
-
}
|
|
2370
|
-
await publish({
|
|
2371
|
-
type: "agent-run-start",
|
|
2372
|
-
currentStep: context.state.currentStep + 1,
|
|
2373
|
-
maxSteps: context.state.maxSteps
|
|
2374
|
-
});
|
|
2375
|
-
for (const approval of input.approvals ?? []) {
|
|
2376
|
-
await publish({
|
|
2377
|
-
type: "agent-approval-resolved",
|
|
2378
|
-
approval
|
|
2379
|
-
});
|
|
2380
|
-
}
|
|
2381
|
-
await publish({
|
|
2382
|
-
type: "agent-step-start",
|
|
2383
|
-
stepIndex: context.state.currentStep + 1
|
|
2384
|
-
});
|
|
2385
|
-
const abortContext = createAgentAbortContext(input.abortSignal, policy, executionLease.signal);
|
|
2386
|
-
let executionEnvironmentSession;
|
|
2387
|
-
try {
|
|
2388
|
-
executionEnvironmentSession = await acquireExecutionEnvironment(context.executionEnvironment, context.state, context.context, abortContext.signal);
|
|
2389
|
-
}
|
|
2390
|
-
catch (error) {
|
|
2391
|
-
abortContext.cleanup();
|
|
2392
|
-
await executionLease.release();
|
|
2393
|
-
throw error;
|
|
2394
|
-
}
|
|
2395
|
-
activeExecutionEnvironment = executionEnvironmentSession;
|
|
2396
|
-
let executionEnvironmentStatus = "failed";
|
|
2397
|
-
let executionEnvironmentError;
|
|
2398
|
-
let streamResult;
|
|
2399
|
-
try {
|
|
2400
|
-
streamResult = streamText(createGenerateOptions(agent, context.state, input, context.messages, context.remainingSteps, context.context, executionEnvironmentSession, abortContext.signal, async (record) => {
|
|
2401
|
-
await publish({
|
|
2402
|
-
type: "agent-compaction",
|
|
2403
|
-
compaction: record
|
|
2404
|
-
});
|
|
2405
|
-
}));
|
|
2406
|
-
}
|
|
2407
|
-
catch (error) {
|
|
2408
|
-
abortContext.cleanup();
|
|
2409
|
-
executionEnvironmentError = {
|
|
2410
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2411
|
-
};
|
|
2412
|
-
await executionEnvironmentSession?.release?.({
|
|
2413
|
-
status: "failed",
|
|
2414
|
-
error: executionEnvironmentError
|
|
2415
|
-
});
|
|
2416
|
-
activeExecutionEnvironment = undefined;
|
|
2417
|
-
await executionLease.release();
|
|
2418
|
-
throw error;
|
|
2419
|
-
}
|
|
2420
|
-
const approvalRequests = [];
|
|
2421
|
-
const eventRelay = withAgentTelemetryRunContext(agent, context.state.runId, async () => {
|
|
2422
|
-
for await (const event of streamResult.eventStream) {
|
|
2423
|
-
await publish(event);
|
|
2424
|
-
if (event.type === "tool-approval-request") {
|
|
2425
|
-
approvalRequests.push(event.approval);
|
|
2426
|
-
await publish({
|
|
2427
|
-
type: "agent-approval-request",
|
|
2428
|
-
approval: event.approval
|
|
2429
|
-
});
|
|
2430
|
-
await emitTelemetryEvent(agent, {
|
|
2431
|
-
type: "approval-request",
|
|
2432
|
-
runId: context.state.runId,
|
|
2433
|
-
agentId: context.state.agentId,
|
|
2434
|
-
approval: event.approval
|
|
2435
|
-
});
|
|
2436
|
-
}
|
|
2437
|
-
if (event.type === "provider-data" &&
|
|
2438
|
-
typeof event.data === "object" &&
|
|
2439
|
-
event.data !== null &&
|
|
2440
|
-
!Array.isArray(event.data) &&
|
|
2441
|
-
event.data.type === "mcp_approval_request" &&
|
|
2442
|
-
typeof event.data.id === "string" &&
|
|
2443
|
-
typeof event.data.name === "string" &&
|
|
2444
|
-
typeof event.data.arguments === "string") {
|
|
2445
|
-
const approval = {
|
|
2446
|
-
provider: event.provider,
|
|
2447
|
-
id: event.data.id,
|
|
2448
|
-
name: event.data.name,
|
|
2449
|
-
arguments: event.data.arguments,
|
|
2450
|
-
serverLabel: typeof event.data.server_label === "string" ? event.data.server_label : undefined,
|
|
2451
|
-
rawData: event.data
|
|
2452
|
-
};
|
|
2453
|
-
approvalRequests.push(approval);
|
|
2454
|
-
await publish({
|
|
2455
|
-
type: "agent-approval-request",
|
|
2456
|
-
approval
|
|
2457
|
-
});
|
|
2458
|
-
await emitTelemetryEvent(agent, {
|
|
2459
|
-
type: "approval-request",
|
|
2460
|
-
runId: context.state.runId,
|
|
2461
|
-
agentId: context.state.agentId,
|
|
2462
|
-
approval
|
|
2463
|
-
});
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
});
|
|
2467
|
-
const output = (async () => {
|
|
2468
|
-
try {
|
|
2469
|
-
const final = await withAgentPolicyTimeout(eventRelay.then(() => streamResult.collect()), abortContext);
|
|
2470
|
-
const cancelled = executionLease.cancelledState();
|
|
2471
|
-
if (cancelled) {
|
|
2472
|
-
executionEnvironmentStatus = cancelled.status;
|
|
2473
|
-
await emitRunFinishTelemetry(agent, cancelled);
|
|
2474
|
-
await publish({
|
|
2475
|
-
type: "agent-run-finish",
|
|
2476
|
-
status: cancelled.status,
|
|
2477
|
-
state: cancelled
|
|
2478
|
-
}, true);
|
|
2479
|
-
broadcast.close();
|
|
2480
|
-
return toOutput(cancelled);
|
|
2481
|
-
}
|
|
2482
|
-
if (executionLease.leaseLost()) {
|
|
2483
|
-
const conflict = new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
2484
|
-
broadcast.fail(conflict);
|
|
2485
|
-
throw conflict;
|
|
2486
|
-
}
|
|
2487
|
-
const newSteps = mapSteps(final.steps, context.state.currentStep, final.toolResults);
|
|
2488
|
-
let result = finalizeState(agent, context.state, final, newSteps, final.toolResults);
|
|
2489
|
-
const outputGuardrail = await runGuardrails(agent, result.state, "output", agent.outputGuardrails, () => ({
|
|
2490
|
-
runId: result.state.runId,
|
|
2491
|
-
agentId: result.state.agentId,
|
|
2492
|
-
context: context.context,
|
|
2493
|
-
state: cloneState(result.state),
|
|
2494
|
-
output: result,
|
|
2495
|
-
metadata: result.state.metadata
|
|
2496
|
-
}));
|
|
2497
|
-
if (outputGuardrail) {
|
|
2498
|
-
result = toOutput(applyGuardrailFailure(result.state, "output", outputGuardrail));
|
|
2499
|
-
await publish({
|
|
2500
|
-
type: "error",
|
|
2501
|
-
error: new GuardrailTriggeredError("output", result.state.error?.message ?? "Agent output guardrail triggered.", { metadata: outputGuardrail.metadata })
|
|
2502
|
-
}, true);
|
|
2503
|
-
}
|
|
2504
|
-
for (const step of newSteps) {
|
|
2505
|
-
await publish({
|
|
2506
|
-
type: "agent-step-finish",
|
|
2507
|
-
step
|
|
2508
|
-
});
|
|
2509
|
-
}
|
|
2510
|
-
await emitFinalizedStepTelemetry(agent, result.state, newSteps);
|
|
2511
|
-
if (!approvalRequests.length) {
|
|
2512
|
-
await emitApprovalTelemetry(agent, result.state, approvalsFromEvents(newSteps.flatMap((step) => step.response?.messages ?? [])));
|
|
2513
|
-
}
|
|
2514
|
-
await persistState(agent, result.state, policy);
|
|
2515
|
-
await emitRunFinishTelemetry(agent, result.state);
|
|
2516
|
-
await publish({
|
|
2517
|
-
type: "agent-run-finish",
|
|
2518
|
-
status: result.status,
|
|
2519
|
-
state: result.state
|
|
2520
|
-
}, true);
|
|
2521
|
-
broadcast.close();
|
|
2522
|
-
executionEnvironmentStatus = result.status;
|
|
2523
|
-
return result;
|
|
2524
|
-
}
|
|
2525
|
-
catch (error) {
|
|
2526
|
-
executionEnvironmentError = {
|
|
2527
|
-
message: error instanceof Error ? error.message : String(error)
|
|
2528
|
-
};
|
|
2529
|
-
const cancelled = executionLease.cancelledState();
|
|
2530
|
-
if (cancelled) {
|
|
2531
|
-
executionEnvironmentStatus = cancelled.status;
|
|
2532
|
-
await emitRunFinishTelemetry(agent, cancelled);
|
|
2533
|
-
await publish({
|
|
2534
|
-
type: "agent-run-finish",
|
|
2535
|
-
status: cancelled.status,
|
|
2536
|
-
state: cancelled
|
|
2537
|
-
}, true);
|
|
2538
|
-
broadcast.close();
|
|
2539
|
-
return toOutput(cancelled);
|
|
2540
|
-
}
|
|
2541
|
-
if (executionLease.leaseLost()) {
|
|
2542
|
-
const conflict = new ConflictError(`Agent run "${context.state.runId}" lost its worker lease.`);
|
|
2543
|
-
broadcast.fail(conflict);
|
|
2544
|
-
throw conflict;
|
|
2545
|
-
}
|
|
2546
|
-
if (error instanceof AgentPolicyTimeoutError || abortContext.isTimedOut()) {
|
|
2547
|
-
const status = policy?.onTimeout === "cancel-requested" ? "cancel_requested" : "timed_out";
|
|
2548
|
-
const message = error instanceof Error ? error.message : `Agent run timed out after ${policy?.timeoutMs}ms.`;
|
|
2549
|
-
const durableState = agent.store
|
|
2550
|
-
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
2551
|
-
: context.state;
|
|
2552
|
-
const timedOutState = createTerminalState(durableState, status, message);
|
|
2553
|
-
await persistState(agent, timedOutState, policy);
|
|
2554
|
-
await emitRunFinishTelemetry(agent, timedOutState);
|
|
2555
|
-
await publish({
|
|
2556
|
-
type: "error",
|
|
2557
|
-
error: new AgentPolicyTimeoutError(policy?.timeoutMs ?? 0)
|
|
2558
|
-
}, true);
|
|
2559
|
-
await publish({
|
|
2560
|
-
type: "agent-run-finish",
|
|
2561
|
-
status: timedOutState.status,
|
|
2562
|
-
state: timedOutState
|
|
2563
|
-
}, true);
|
|
2564
|
-
broadcast.close();
|
|
2565
|
-
executionEnvironmentStatus = timedOutState.status;
|
|
2566
|
-
return toOutput(timedOutState);
|
|
2567
|
-
}
|
|
2568
|
-
const durableState = agent.store
|
|
2569
|
-
? normalizeAgentRunState((await agent.store.load(context.state.runId, context.state.scope)) ?? context.state)
|
|
2570
|
-
: context.state;
|
|
2571
|
-
const failedState = createFailedState(durableState, error);
|
|
2572
|
-
await persistState(agent, failedState, policy);
|
|
2573
|
-
await emitRunFinishTelemetry(agent, failedState);
|
|
2574
|
-
await publish({
|
|
2575
|
-
type: "error",
|
|
2576
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
2577
|
-
}, true);
|
|
2578
|
-
await publish({
|
|
2579
|
-
type: "agent-run-finish",
|
|
2580
|
-
status: failedState.status,
|
|
2581
|
-
state: failedState
|
|
2582
|
-
}, true);
|
|
2583
|
-
broadcast.close();
|
|
2584
|
-
executionEnvironmentStatus = failedState.status;
|
|
2585
|
-
throw error;
|
|
2586
|
-
}
|
|
2587
|
-
finally {
|
|
2588
|
-
await executionEnvironmentSession?.release?.({
|
|
2589
|
-
status: executionEnvironmentStatus,
|
|
2590
|
-
error: executionEnvironmentError
|
|
2591
|
-
});
|
|
2592
|
-
activeExecutionEnvironment = undefined;
|
|
2593
|
-
await executionLease.release();
|
|
2594
|
-
}
|
|
2595
|
-
})().finally(() => finishInvocation(executionEnvironmentStatus, executionEnvironmentError ? new Error(executionEnvironmentError.message) : undefined));
|
|
2596
|
-
return {
|
|
2597
|
-
output,
|
|
2598
|
-
textStream: streamResult.textStream
|
|
2599
|
-
};
|
|
2600
|
-
})().catch(async (error) => {
|
|
2601
|
-
await finishInvocation("failed", error instanceof Error ? error : new Error(String(error)));
|
|
2602
|
-
await activeExecutionEnvironment?.release?.({
|
|
2603
|
-
status: "failed",
|
|
2604
|
-
error: { message: error instanceof Error ? error.message : String(error) }
|
|
2605
|
-
});
|
|
2606
|
-
activeExecutionEnvironment = undefined;
|
|
2607
|
-
await activeLease?.release();
|
|
2608
|
-
broadcast.fail(error);
|
|
2609
|
-
throw error;
|
|
2610
|
-
});
|
|
2611
|
-
return {
|
|
2612
|
-
eventStream: broadcast.stream(),
|
|
2613
|
-
textStream: (async function* () {
|
|
2614
|
-
const started = await runner;
|
|
2615
|
-
for await (const chunk of started.textStream) {
|
|
2616
|
-
yield chunk;
|
|
2617
|
-
}
|
|
2618
|
-
})(),
|
|
2619
|
-
collect: async () => (await runner).output
|
|
2620
|
-
};
|
|
2621
|
-
};
|
|
2622
|
-
export const resumeAgent = async (agent, input) => runAgent(agent, input);
|
|
1
|
+
/** Compatibility facade. Implementations are organized by responsibility internally. */
|
|
2
|
+
export { runAgentGroup } from "./agent/groups.js";
|
|
3
|
+
export { createSubAgentTool, runAgent, streamAgent, resumeAgent } from "./agent/execution.js";
|
|
4
|
+
export { createAgent, prepareSubagentsForAgent } from "./agent/definition.js";
|
|
5
|
+
export { Agent } from "./agent/instance.js";
|
|
6
|
+
export { cancelAgentRun, cancelAgentRunTree } from "./agent/cancellation.js";
|
|
2623
7
|
//# sourceMappingURL=agent.js.map
|