@arnilo/prism 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -1
- package/dist/agent-loops.d.ts +1 -0
- package/dist/agent-loops.js +27 -16
- package/dist/agent-run-lifecycle.d.ts +28 -0
- package/dist/agent-run-lifecycle.js +33 -0
- package/dist/agent-run-state.d.ts +53 -0
- package/dist/agent-run-state.js +127 -0
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +337 -46
- package/dist/contracts.d.ts +205 -3
- package/dist/contracts.js +4 -0
- package/dist/guardrails.d.ts +25 -0
- package/dist/guardrails.js +133 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +6 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.js +10 -3
- package/dist/input.js +2 -0
- package/dist/resources.js +2 -1
- package/dist/run-limits.d.ts +34 -0
- package/dist/run-limits.js +163 -0
- package/dist/secure-agent.d.ts +3 -0
- package/dist/secure-agent.js +63 -0
- package/dist/session-stores.js +2 -3
- package/dist/testing/persistence-schema.d.ts +45 -7
- package/dist/testing/persistence-schema.js +138 -24
- package/dist/thinking.d.ts +42 -0
- package/dist/thinking.js +92 -0
- package/dist/tools.d.ts +10 -2
- package/dist/tools.js +56 -7
- package/dist/use-case-model.d.ts +63 -0
- package/dist/use-case-model.js +52 -0
- package/docs/a2a.md +4 -2
- package/docs/agent-events.md +23 -16
- package/docs/agent-loops.md +19 -8
- package/docs/agent-session-runtime.md +33 -1
- package/docs/coding-agent-tools.md +33 -12
- package/docs/coding-security.md +2 -2
- package/docs/compaction-llm.md +17 -7
- package/docs/compaction-observational-memory.md +28 -4
- package/docs/credential-storage.md +58 -9
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/database-persistence.md +8 -3
- package/docs/guardrails.md +75 -0
- package/docs/host-security.md +16 -8
- package/docs/index.md +26 -22
- package/docs/mcp-tools.md +32 -12
- package/docs/migration.md +164 -2
- package/docs/node-filesystem-config.md +1 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/postgres-persistence.md +3 -3
- package/docs/provider-caching.md +16 -4
- package/docs/provider-conformance.md +39 -1
- package/docs/provider-packages.md +60 -3
- package/docs/providers/ai-sdk.md +36 -0
- package/docs/providers/kimi.md +124 -61
- package/docs/providers/neuralwatt.md +19 -13
- package/docs/providers/openai.md +56 -13
- package/docs/providers/opencode-go.md +118 -30
- package/docs/providers/openrouter.md +105 -35
- package/docs/providers/zai.md +94 -45
- package/docs/release-and-install.md +47 -49
- package/docs/review-coverage-2026-07-17-provider-validation.md +192 -0
- package/docs/runs-and-usage.md +30 -3
- package/docs/server.md +5 -2
- package/docs/sqlite-persistence.md +2 -2
- package/docs/structured-output.md +1 -1
- package/docs/thinking-and-reasoning.md +98 -0
- package/docs/tool-execution-primitives.md +3 -3
- package/docs/tools.md +21 -1
- package/docs/use-case-model-selection.md +109 -0
- package/docs/workflow-orchestration-primitives.md +1 -0
- package/docs/workflows.md +18 -10
- package/docs/working-and-semantic-memory.md +1 -0
- package/package.json +2 -2
package/dist/agents.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { AgentRunError } from "./contracts.js";
|
|
1
|
+
import { AgentRunError, AgentRunStateError } from "./contracts.js";
|
|
2
2
|
import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
|
|
3
|
+
import { createId } from "./ids.js";
|
|
4
|
+
import { assertGuardrailsAllowed, GuardrailError, runGuardrails } from "./guardrails.js";
|
|
3
5
|
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
4
6
|
import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
|
|
5
7
|
import { assembleProviderInput } from "./input.js";
|
|
@@ -11,6 +13,8 @@ import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.j
|
|
|
11
13
|
import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
|
|
12
14
|
import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext } from "./session-stores.js";
|
|
13
15
|
import { createToolRegistry, dispatchToolCall } from "./tools.js";
|
|
16
|
+
import { RunLimitError, RunLimitTracker, resolveRunLimits } from "./run-limits.js";
|
|
17
|
+
import { agentFingerprint, initialAgentRunState, loadAgentRunState, publicState, saveAgentRunState, validateRunStateOptions } from "./agent-run-state.js";
|
|
14
18
|
import { resolveActiveSkills } from "./skills.js";
|
|
15
19
|
export function createAgent(config) {
|
|
16
20
|
return {
|
|
@@ -23,6 +27,60 @@ export function createAgent(config) {
|
|
|
23
27
|
export function createAgentSession(config) {
|
|
24
28
|
return new RuntimeAgentSession(config);
|
|
25
29
|
}
|
|
30
|
+
/** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
|
|
31
|
+
export async function resumeAgentRun(agent, ref, resume, options) {
|
|
32
|
+
const { record, state } = await loadAgentRunState(options.checkpoints, ref, options.ownership);
|
|
33
|
+
if (state.definitionRevision !== options.definitionRevision || state.agentId !== (agent.config.id ?? agent.config.name) || state.fingerprint !== agentFingerprint(agent, options.definitionRevision)) {
|
|
34
|
+
throw new AgentRunStateError("Agent definition revision or fingerprint mismatch on resume");
|
|
35
|
+
}
|
|
36
|
+
if (record.version !== resume.expectedVersion || state.status !== "suspended") {
|
|
37
|
+
throw new AgentRunStateError("Stale or non-suspended agent run resume");
|
|
38
|
+
}
|
|
39
|
+
if (resume.decision === "deny") {
|
|
40
|
+
const denied = await saveAgentRunState({
|
|
41
|
+
checkpoints: options.checkpoints,
|
|
42
|
+
state: { ...state, status: "denied" },
|
|
43
|
+
expectedVersion: record.version,
|
|
44
|
+
ownership: options.ownership,
|
|
45
|
+
fencingToken: options.fencingToken,
|
|
46
|
+
});
|
|
47
|
+
const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
|
|
48
|
+
await session.recordDurableDenial(state.runId, state.interruption, denied.record.version, options.ownership);
|
|
49
|
+
const runState = publicState(denied.state);
|
|
50
|
+
return { sessionId: state.sessionId, runId: state.runId, status: "denied", leafId: state.leafId, text: "", content: [], runState, interruption: state.interruption };
|
|
51
|
+
}
|
|
52
|
+
if (state.pending?.status === "dispatched")
|
|
53
|
+
throw new AgentRunStateError("Ambiguous dispatched tool requires operator resolution");
|
|
54
|
+
const configured = agent.config.runState;
|
|
55
|
+
if (configured && (configured.checkpoints !== options.checkpoints || configured.definitionRevision !== options.definitionRevision)) {
|
|
56
|
+
throw new AgentRunStateError("Agent durable run-state configuration mismatch on resume");
|
|
57
|
+
}
|
|
58
|
+
const claimed = await saveAgentRunState({
|
|
59
|
+
checkpoints: options.checkpoints,
|
|
60
|
+
state: { ...state, status: "running", interruption: undefined },
|
|
61
|
+
expectedVersion: record.version,
|
|
62
|
+
ownership: options.ownership,
|
|
63
|
+
fencingToken: options.fencingToken,
|
|
64
|
+
});
|
|
65
|
+
const session = new RuntimeAgentSession({ agent, id: state.sessionId, leafId: state.leafId });
|
|
66
|
+
return session.resumeDurable(claimed.state, configured ?? {
|
|
67
|
+
checkpoints: options.checkpoints,
|
|
68
|
+
definitionRevision: options.definitionRevision,
|
|
69
|
+
interruptBeforeTool: state.interruptBeforeTool,
|
|
70
|
+
fencingToken: options.fencingToken,
|
|
71
|
+
}, options.ownership);
|
|
72
|
+
}
|
|
73
|
+
class AgentRunSuspended extends Error {
|
|
74
|
+
state;
|
|
75
|
+
interruption;
|
|
76
|
+
code = "ERR_PRISM_AGENT_RUN_SUSPENDED";
|
|
77
|
+
constructor(state, interruption) {
|
|
78
|
+
super("Agent run suspended");
|
|
79
|
+
this.state = state;
|
|
80
|
+
this.interruption = interruption;
|
|
81
|
+
this.name = "AgentRunSuspended";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
26
84
|
class RuntimeAgentSession {
|
|
27
85
|
id;
|
|
28
86
|
agent;
|
|
@@ -37,6 +95,11 @@ class RuntimeAgentSession {
|
|
|
37
95
|
activeLedger;
|
|
38
96
|
activeOwnership;
|
|
39
97
|
activeIdempotencyKey;
|
|
98
|
+
activeGuardrails;
|
|
99
|
+
activeMetadata;
|
|
100
|
+
activeLimits;
|
|
101
|
+
activeLimitOutputBuffer = false;
|
|
102
|
+
activeDurable;
|
|
40
103
|
activeLoopTurn = 1;
|
|
41
104
|
ledgerChain = Promise.resolve();
|
|
42
105
|
ledgerFailure;
|
|
@@ -56,7 +119,41 @@ class RuntimeAgentSession {
|
|
|
56
119
|
return subscriber;
|
|
57
120
|
}
|
|
58
121
|
async run(input, options = {}) {
|
|
59
|
-
|
|
122
|
+
return this.runInternal(input, options, randomId("run"));
|
|
123
|
+
}
|
|
124
|
+
async resumeDurable(state, runState, ownership) {
|
|
125
|
+
return this.runInternal(state.input ?? [], { runState, ownership }, state.runId, { options: runState, state, version: state.version });
|
|
126
|
+
}
|
|
127
|
+
async recordDurableDenial(runId, interruption, version, ownership) {
|
|
128
|
+
this.activeLedger = this.agent.config.runLedger;
|
|
129
|
+
this.activeOwnership = ownership ?? this.agent.config.ownership;
|
|
130
|
+
this.activeRedactor = this.agent.config.redactor;
|
|
131
|
+
this.emit({ type: "agent_denied", sessionId: this.id, runId, interruption, version });
|
|
132
|
+
await this.drainLedger();
|
|
133
|
+
this.activeLedger = undefined;
|
|
134
|
+
this.activeOwnership = undefined;
|
|
135
|
+
this.activeRedactor = undefined;
|
|
136
|
+
}
|
|
137
|
+
async runInternal(input, options, runId, resumed) {
|
|
138
|
+
if (this.agent.config.secure && (options.redactor !== undefined || options.ownership !== undefined || options.validate !== undefined || options.runState !== undefined)) {
|
|
139
|
+
throw new AgentRunStateError("Secure agent defaults cannot be replaced per run");
|
|
140
|
+
}
|
|
141
|
+
const requestedLimits = options.maxToolRounds === undefined
|
|
142
|
+
? options.limits
|
|
143
|
+
: { ...options.limits, maxToolRounds: Math.min(options.maxToolRounds, options.limits?.maxToolRounds ?? options.maxToolRounds) };
|
|
144
|
+
const resolvedLimits = resolveRunLimits(this.agent.config.limits, requestedLimits);
|
|
145
|
+
const durableOptions = options.runState ?? this.agent.config.runState;
|
|
146
|
+
if (this.agent.config.runState && options.runState && this.agent.config.runState !== options.runState) {
|
|
147
|
+
throw new AgentRunStateError("RunOptions cannot replace agent durable run-state configuration");
|
|
148
|
+
}
|
|
149
|
+
if (durableOptions) {
|
|
150
|
+
validateRunStateOptions(durableOptions);
|
|
151
|
+
if (options.model || options.guardrails || options.loop)
|
|
152
|
+
throw new AgentRunStateError("Durable runs require model, guardrails, and loop on AgentConfig for fingerprinting");
|
|
153
|
+
const configuredLoop = this.agent.config.loop;
|
|
154
|
+
if (configuredLoop && !isBuiltInLoop(configuredLoop))
|
|
155
|
+
throw new AgentRunStateError("Custom AgentLoopStrategy is not durable");
|
|
156
|
+
}
|
|
60
157
|
if (this.activeRun) {
|
|
61
158
|
const error = new Error("Agent session already has an active run");
|
|
62
159
|
this.emit({ type: "error", sessionId: this.id, runId, error: errorToErrorInfo(error) });
|
|
@@ -69,15 +166,32 @@ class RuntimeAgentSession {
|
|
|
69
166
|
this.activeLedger = options.runLedger ?? this.agent.config.runLedger;
|
|
70
167
|
this.activeOwnership = options.ownership ?? this.agent.config.ownership;
|
|
71
168
|
this.activeIdempotencyKey = options.idempotencyKey ?? this.agent.config.idempotencyKey;
|
|
169
|
+
this.activeGuardrails = mergeGuardrails(this.agent.config.guardrails, options.guardrails);
|
|
170
|
+
this.activeDurable = resumed ?? (durableOptions ? { options: durableOptions, version: 0 } : undefined);
|
|
72
171
|
const model = options.model ?? this.agent.config.model;
|
|
73
172
|
const startedAt = new Date().toISOString();
|
|
74
173
|
let runError;
|
|
174
|
+
let runStatus = "succeeded";
|
|
75
175
|
const runUsage = createUsageAccumulator();
|
|
76
176
|
let usage;
|
|
177
|
+
const metadata = { ...this.agent.config.metadata, ...this.metadata, ...options.metadata };
|
|
178
|
+
this.activeMetadata = metadata;
|
|
179
|
+
const limits = new RunLimitTracker(resolvedLimits, {
|
|
180
|
+
onExceeded: (breach) => {
|
|
181
|
+
this.emit({ type: "run_limit_exceeded", sessionId: this.id, runId, breach });
|
|
182
|
+
controller.abort(new RunLimitError(breach));
|
|
183
|
+
},
|
|
184
|
+
snapshot: resumed?.state?.counters,
|
|
185
|
+
deadlineAt: resumed?.state?.deadlineAt,
|
|
186
|
+
});
|
|
187
|
+
this.activeLimits = limits;
|
|
188
|
+
this.activeLimitOutputBuffer = [this.agent.config.limits, requestedLimits].some((value) => value?.maxOutputTokens !== undefined || value?.maxTotalTokens !== undefined || value?.maxCost !== undefined);
|
|
77
189
|
try {
|
|
78
190
|
this.resolveRunProvider(options);
|
|
79
191
|
throwIfAborted(controller.signal);
|
|
80
192
|
this.emit({ type: "agent_started", sessionId: this.id, runId });
|
|
193
|
+
if (resumed)
|
|
194
|
+
this.emit({ type: "agent_resumed", sessionId: this.id, runId, version: resumed.version });
|
|
81
195
|
const startRecord = {
|
|
82
196
|
id: runId,
|
|
83
197
|
sessionId: this.id,
|
|
@@ -97,11 +211,27 @@ class RuntimeAgentSession {
|
|
|
97
211
|
await this.appendEntry(createSessionEntry({ sessionId: this.id, parentId: this.currentLeafId, runId, kind: "model_change", previousModel: this.agent.config.model, model: options.model }));
|
|
98
212
|
}
|
|
99
213
|
const inputMessages = inputToMessages(input).map((message) => this.redact(message));
|
|
214
|
+
const inputGuardrails = await runGuardrails({
|
|
215
|
+
stage: "input",
|
|
216
|
+
guardrails: this.activeGuardrails,
|
|
217
|
+
value: inputMessages,
|
|
218
|
+
context: { sessionId: this.id, runId, metadata, signal: controller.signal },
|
|
219
|
+
redactor: this.activeRedactor,
|
|
220
|
+
emit: (event) => this.emit(event),
|
|
221
|
+
});
|
|
222
|
+
if (inputGuardrails.terminal?.action === "interrupt" && this.activeDurable) {
|
|
223
|
+
if (!resumed) {
|
|
224
|
+
const interruption = { kind: "input_guardrail", reason: inputGuardrails.terminal.reason ?? "Input requires approval" };
|
|
225
|
+
throw new AgentRunSuspended(await this.suspendDurable({ runId, model, limits, interruption, messages: inputMessages }), interruption);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
assertGuardrailsAllowed(inputGuardrails);
|
|
230
|
+
}
|
|
100
231
|
for (const message of inputMessages)
|
|
101
232
|
await this.appendMessage(message, runId);
|
|
102
233
|
await this.autoCompact(runId, options, controller.signal, inputMessages);
|
|
103
|
-
const
|
|
104
|
-
const maxToolRounds = options.maxToolRounds ?? 1;
|
|
234
|
+
const maxToolRounds = resolvedLimits.maxToolRounds;
|
|
105
235
|
const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), { base: this.agent.config.instructions });
|
|
106
236
|
const contextProviders = [
|
|
107
237
|
...(this.agent.config.context ?? []),
|
|
@@ -118,6 +248,9 @@ class RuntimeAgentSession {
|
|
|
118
248
|
const toolConcurrency = resolveToolConcurrency(options, this.agent.config);
|
|
119
249
|
this.activeLoopTurn = 1;
|
|
120
250
|
const recordProviderUsage = async (turnUsage, turn, attempt) => {
|
|
251
|
+
limits.recordUsage(turnUsage);
|
|
252
|
+
if (!turnUsage)
|
|
253
|
+
return;
|
|
121
254
|
runUsage.add(turnUsage);
|
|
122
255
|
if (!this.activeLedger)
|
|
123
256
|
return;
|
|
@@ -135,6 +268,7 @@ class RuntimeAgentSession {
|
|
|
135
268
|
await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
|
|
136
269
|
};
|
|
137
270
|
// ponytail: LoopContext binds existing private helpers; loop orchestrates only.
|
|
271
|
+
let assembledTurn = false;
|
|
138
272
|
const ctx = {
|
|
139
273
|
sessionId: this.id,
|
|
140
274
|
runId,
|
|
@@ -145,31 +279,45 @@ class RuntimeAgentSession {
|
|
|
145
279
|
inputMessages,
|
|
146
280
|
maxToolRounds,
|
|
147
281
|
toolConcurrency,
|
|
148
|
-
assemble: async (nextInput, toolResults, turn) =>
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
282
|
+
assemble: async (nextInput, toolResults, turn) => {
|
|
283
|
+
limits.charge("maxTurns");
|
|
284
|
+
const request = await assembleProviderInput({
|
|
285
|
+
model: options.model ?? this.agent.config.model,
|
|
286
|
+
input: nextInput,
|
|
287
|
+
history: this.history,
|
|
288
|
+
summaries: (await this.snapshot()).summaries,
|
|
289
|
+
toolResults: toolResults ?? [],
|
|
290
|
+
turn,
|
|
291
|
+
instructionInjectors,
|
|
292
|
+
inputLayout,
|
|
293
|
+
systemInstructions,
|
|
294
|
+
inputBuilder: this.agent.config.inputBuilder,
|
|
295
|
+
promptBuilder: this.agent.config.promptBuilder,
|
|
296
|
+
contextProviders,
|
|
297
|
+
skills: activeSkills,
|
|
298
|
+
tools,
|
|
299
|
+
resourceLoader: this.agent.config.resourceLoader,
|
|
300
|
+
permission: this.agent.config.permission,
|
|
301
|
+
trust: this.agent.config.trust,
|
|
302
|
+
providerOptions,
|
|
303
|
+
redactor: this.activeRedactor,
|
|
304
|
+
middleware: this.agent.config.middleware,
|
|
305
|
+
sessionId: this.id,
|
|
306
|
+
runId,
|
|
307
|
+
metadata,
|
|
308
|
+
signal: controller.signal,
|
|
309
|
+
});
|
|
310
|
+
assembledTurn = true;
|
|
311
|
+
return request;
|
|
312
|
+
},
|
|
313
|
+
chargeToolRound: (calls) => {
|
|
314
|
+
if (calls.length > 0)
|
|
315
|
+
limits.charge("maxToolRounds");
|
|
316
|
+
},
|
|
172
317
|
generate: async (request) => {
|
|
318
|
+
if (!assembledTurn)
|
|
319
|
+
limits.charge("maxTurns");
|
|
320
|
+
assembledTurn = false;
|
|
173
321
|
const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
|
|
174
322
|
const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
|
|
175
323
|
return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
|
|
@@ -182,9 +330,32 @@ class RuntimeAgentSession {
|
|
|
182
330
|
middleware: this.agent.config.middleware,
|
|
183
331
|
emit: (event) => this.emit(event),
|
|
184
332
|
permission: this.agent.config.permission,
|
|
333
|
+
trust: this.agent.config.trust,
|
|
185
334
|
redactor: this.activeRedactor,
|
|
186
335
|
ledger: this.activeLedger,
|
|
187
336
|
ownership: this.activeOwnership,
|
|
337
|
+
guardrails: this.activeGuardrails,
|
|
338
|
+
limitTracker: limits,
|
|
339
|
+
beforeExecute: async (mediatedCall) => {
|
|
340
|
+
const durable = this.activeDurable;
|
|
341
|
+
if (!durable)
|
|
342
|
+
return;
|
|
343
|
+
const pending = durable.state?.pending;
|
|
344
|
+
if (pending?.call.id === mediatedCall.id && pending.status === "ready") {
|
|
345
|
+
await this.persistDurable({ ...durable.state, status: "running", pending: { ...pending, status: "dispatched" }, interruption: undefined });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!durable.options.interruptBeforeTool)
|
|
349
|
+
return;
|
|
350
|
+
const interruption = { kind: "tool_approval", reason: "Tool side effect requires approval", toolCallId: mediatedCall.id, toolName: mediatedCall.name };
|
|
351
|
+
throw new AgentRunSuspended(await this.suspendDurable({
|
|
352
|
+
runId,
|
|
353
|
+
model,
|
|
354
|
+
limits,
|
|
355
|
+
interruption,
|
|
356
|
+
pending: { call: mediatedCall, status: "ready" },
|
|
357
|
+
}), interruption);
|
|
358
|
+
},
|
|
188
359
|
// ponytail: RunOptions wins; array-compose deferred (roadmap: compose-later).
|
|
189
360
|
validate,
|
|
190
361
|
}),
|
|
@@ -195,6 +366,14 @@ class RuntimeAgentSession {
|
|
|
195
366
|
this.emit(event);
|
|
196
367
|
},
|
|
197
368
|
};
|
|
369
|
+
if (resumed?.state?.pending?.status === "ready") {
|
|
370
|
+
const result = await ctx.dispatchToolCall(resumed.state.pending.call);
|
|
371
|
+
await ctx.appendMessage({
|
|
372
|
+
role: "tool",
|
|
373
|
+
content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
|
|
374
|
+
metadata: result.metadata,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
198
377
|
const loopUsage = await loop.run(ctx);
|
|
199
378
|
usage = runUsage.value() ?? loopUsage;
|
|
200
379
|
if (usage && this.activeLedger) {
|
|
@@ -210,22 +389,34 @@ class RuntimeAgentSession {
|
|
|
210
389
|
await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
|
|
211
390
|
}
|
|
212
391
|
await this.drainLedger();
|
|
392
|
+
const runState = this.activeDurable?.state
|
|
393
|
+
? await this.persistDurable({ ...this.activeDurable.state, status: "succeeded", pending: undefined, interruption: undefined })
|
|
394
|
+
: undefined;
|
|
213
395
|
this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
|
|
214
|
-
return this.buildRunResult({
|
|
215
|
-
runId,
|
|
216
|
-
status: "succeeded",
|
|
217
|
-
usage,
|
|
218
|
-
});
|
|
396
|
+
return this.buildRunResult({ runId, status: "succeeded", usage, runState });
|
|
219
397
|
}
|
|
220
398
|
catch (error) {
|
|
399
|
+
if (error instanceof AgentRunSuspended) {
|
|
400
|
+
runStatus = "suspended";
|
|
401
|
+
const version = error.state.version;
|
|
402
|
+
this.emit({ type: "agent_suspended", sessionId: this.id, runId, interruption: error.interruption, version });
|
|
403
|
+
return this.buildRunResult({ runId, status: "suspended", runState: error.state, interruption: error.interruption });
|
|
404
|
+
}
|
|
221
405
|
runError = errorToErrorInfo(error);
|
|
222
406
|
this.emit({ type: "error", sessionId: this.id, runId, error: runError });
|
|
407
|
+
const breach = error instanceof RunLimitError ? error.breach : limits.breach;
|
|
408
|
+
runStatus = breach ? "failed" : controller.signal.aborted ? "aborted" : "failed";
|
|
409
|
+
const runState = this.activeDurable?.state
|
|
410
|
+
? await this.persistDurable({ ...this.activeDurable.state, status: runStatus, interruption: undefined })
|
|
411
|
+
: undefined;
|
|
223
412
|
const result = this.buildRunResult({
|
|
224
413
|
runId,
|
|
225
|
-
status:
|
|
414
|
+
status: runStatus,
|
|
226
415
|
usage: runUsage.value() ?? usage,
|
|
416
|
+
limit: breach,
|
|
227
417
|
error: runError,
|
|
228
|
-
abortReason: controller.signal.aborted ? String(controller.signal.reason) : undefined,
|
|
418
|
+
abortReason: !breach && controller.signal.aborted ? String(controller.signal.reason) : undefined,
|
|
419
|
+
runState,
|
|
229
420
|
});
|
|
230
421
|
throw new AgentRunError(result, { cause: error });
|
|
231
422
|
}
|
|
@@ -235,7 +426,7 @@ class RuntimeAgentSession {
|
|
|
235
426
|
try {
|
|
236
427
|
await this.drainLedger();
|
|
237
428
|
if (this.activeLedger) {
|
|
238
|
-
const status =
|
|
429
|
+
const status = runStatus;
|
|
239
430
|
const finishRecord = {
|
|
240
431
|
id: runId,
|
|
241
432
|
sessionId: this.id,
|
|
@@ -257,6 +448,11 @@ class RuntimeAgentSession {
|
|
|
257
448
|
this.activeLedger = undefined;
|
|
258
449
|
this.activeOwnership = undefined;
|
|
259
450
|
this.activeIdempotencyKey = undefined;
|
|
451
|
+
this.activeGuardrails = undefined;
|
|
452
|
+
this.activeMetadata = undefined;
|
|
453
|
+
this.activeLimits?.dispose();
|
|
454
|
+
this.activeLimits = undefined;
|
|
455
|
+
this.activeLimitOutputBuffer = false;
|
|
260
456
|
this.activeRedactor = undefined;
|
|
261
457
|
this.activeProvider = undefined;
|
|
262
458
|
cleanupSignal();
|
|
@@ -305,10 +501,59 @@ class RuntimeAgentSession {
|
|
|
305
501
|
content: final.content,
|
|
306
502
|
message: final.message,
|
|
307
503
|
usage: input.usage,
|
|
504
|
+
limit: input.limit,
|
|
308
505
|
error: input.error,
|
|
309
506
|
abortReason: input.abortReason,
|
|
507
|
+
runState: input.runState,
|
|
508
|
+
interruption: input.interruption,
|
|
310
509
|
};
|
|
311
510
|
}
|
|
511
|
+
async suspendDurable(input) {
|
|
512
|
+
const durable = this.activeDurable;
|
|
513
|
+
if (!durable)
|
|
514
|
+
throw new AgentRunStateError("Durable interruption is not configured");
|
|
515
|
+
const state = durable.state ?? initialAgentRunState({
|
|
516
|
+
agent: this.agent,
|
|
517
|
+
options: durable.options,
|
|
518
|
+
runId: input.runId,
|
|
519
|
+
sessionId: this.id,
|
|
520
|
+
leafId: this.currentLeafId,
|
|
521
|
+
model: input.model,
|
|
522
|
+
counters: input.limits.snapshot(),
|
|
523
|
+
deadlineAt: input.limits.deadlineAt,
|
|
524
|
+
status: "suspended",
|
|
525
|
+
interruption: input.interruption,
|
|
526
|
+
messages: input.messages,
|
|
527
|
+
pending: input.pending,
|
|
528
|
+
interruptBeforeTool: durable.options.interruptBeforeTool,
|
|
529
|
+
});
|
|
530
|
+
return this.persistDurable({
|
|
531
|
+
...state,
|
|
532
|
+
leafId: this.currentLeafId,
|
|
533
|
+
status: "suspended",
|
|
534
|
+
interruption: input.interruption,
|
|
535
|
+
...(input.messages ? { input: input.messages } : {}),
|
|
536
|
+
...(input.pending ? { pending: input.pending } : {}),
|
|
537
|
+
counters: input.limits.snapshot(),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
async persistDurable(state) {
|
|
541
|
+
const durable = this.activeDurable;
|
|
542
|
+
if (!durable)
|
|
543
|
+
throw new AgentRunStateError("Durable run state is not configured");
|
|
544
|
+
const saved = await saveAgentRunState({
|
|
545
|
+
checkpoints: durable.options.checkpoints,
|
|
546
|
+
state,
|
|
547
|
+
expectedVersion: durable.version,
|
|
548
|
+
ownership: this.activeOwnership,
|
|
549
|
+
fencingToken: durable.options.fencingToken,
|
|
550
|
+
redactor: this.activeRedactor,
|
|
551
|
+
maxStateBytes: durable.options.maxStateBytes,
|
|
552
|
+
});
|
|
553
|
+
durable.state = saved.state;
|
|
554
|
+
durable.version = saved.record.version;
|
|
555
|
+
return publicState(saved.state);
|
|
556
|
+
}
|
|
312
557
|
async compact(options = {}) {
|
|
313
558
|
if (this.activeRun)
|
|
314
559
|
throw new Error("Agent session already has an active run");
|
|
@@ -425,6 +670,8 @@ class RuntimeAgentSession {
|
|
|
425
670
|
return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt, recordUsage);
|
|
426
671
|
}
|
|
427
672
|
catch (error) {
|
|
673
|
+
if (error instanceof GuardrailError)
|
|
674
|
+
throw error;
|
|
428
675
|
const failure = error instanceof ProviderTurnFailure ? error : undefined;
|
|
429
676
|
const info = failure ? redactSecrets(failure.info, secrets) : errorToErrorInfo(error, secrets);
|
|
430
677
|
if (!policy || failure?.observable)
|
|
@@ -442,6 +689,8 @@ class RuntimeAgentSession {
|
|
|
442
689
|
}
|
|
443
690
|
}
|
|
444
691
|
async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage) {
|
|
692
|
+
this.activeLimits.charge("maxProviderAttempts");
|
|
693
|
+
this.activeLimits.charge("maxRequestBytes", jsonBytes(request));
|
|
445
694
|
const startedAt = performance.now();
|
|
446
695
|
const providerId = this.activeProvider?.id ?? request.model.provider;
|
|
447
696
|
const buildMetadata = (extra = {}) => createProviderTurnMetadata(request, providerId, { attempt, ...extra });
|
|
@@ -459,8 +708,16 @@ class RuntimeAgentSession {
|
|
|
459
708
|
let started = false;
|
|
460
709
|
let usage;
|
|
461
710
|
let usageRecorded = false;
|
|
711
|
+
const bufferedOutput = [];
|
|
712
|
+
const bufferOutput = Boolean(this.activeGuardrails?.output?.length || this.activeLimitOutputBuffer);
|
|
713
|
+
const emitOutput = (event) => {
|
|
714
|
+
if (bufferOutput)
|
|
715
|
+
bufferedOutput.push(event);
|
|
716
|
+
else
|
|
717
|
+
this.emit(event);
|
|
718
|
+
};
|
|
462
719
|
const recordTurnUsage = async () => {
|
|
463
|
-
if (
|
|
720
|
+
if (usageRecorded)
|
|
464
721
|
return;
|
|
465
722
|
usageRecorded = true;
|
|
466
723
|
await recordUsage?.(usage, turn, attempt);
|
|
@@ -468,6 +725,7 @@ class RuntimeAgentSession {
|
|
|
468
725
|
try {
|
|
469
726
|
for await (const event of this.activeProvider.generate(request)) {
|
|
470
727
|
throwIfAborted(signal);
|
|
728
|
+
this.activeLimits.charge("maxResponseBytes", jsonBytes(event));
|
|
471
729
|
if (event.type === "error")
|
|
472
730
|
throw new ProviderTurnFailure(event.error, started);
|
|
473
731
|
if (event.type === "usage")
|
|
@@ -479,32 +737,45 @@ class RuntimeAgentSession {
|
|
|
479
737
|
if (event.type === "message_start") {
|
|
480
738
|
started = true;
|
|
481
739
|
messageId = event.messageId;
|
|
482
|
-
|
|
740
|
+
emitOutput({ type: "message_started", sessionId: this.id, runId, message: { id: messageId, role: "assistant", content: [] } });
|
|
483
741
|
continue;
|
|
484
742
|
}
|
|
485
743
|
if (event.type === "content_delta" || event.type === "tool_call" || event.type === "tool_call_delta") {
|
|
486
744
|
if (!started) {
|
|
487
745
|
started = true;
|
|
488
|
-
|
|
746
|
+
emitOutput({ type: "message_started", sessionId: this.id, runId, message: { role: "assistant", content: [] } });
|
|
489
747
|
}
|
|
490
748
|
if (event.type === "tool_call_delta") {
|
|
491
749
|
toolDeltas.push(event);
|
|
492
|
-
|
|
750
|
+
emitOutput({ type: "message_delta", sessionId: this.id, runId, content: providerToolCallDeltaContent(event) });
|
|
493
751
|
continue;
|
|
494
752
|
}
|
|
495
753
|
const block = providerContent(event);
|
|
496
754
|
content.push(block);
|
|
497
755
|
if (block.type === "tool_call")
|
|
498
756
|
calls.push(block);
|
|
499
|
-
|
|
757
|
+
emitOutput({ type: "message_delta", sessionId: this.id, runId, content: block });
|
|
500
758
|
}
|
|
501
759
|
}
|
|
502
760
|
for (const call of reconstructMissingToolCalls(toolDeltas, calls)) {
|
|
503
761
|
content.push(call);
|
|
504
762
|
calls.push(call);
|
|
505
|
-
|
|
763
|
+
emitOutput({ type: "message_delta", sessionId: this.id, runId, content: call });
|
|
506
764
|
}
|
|
507
765
|
await recordTurnUsage();
|
|
766
|
+
if (this.activeGuardrails?.output?.length) {
|
|
767
|
+
assertGuardrailsAllowed(await runGuardrails({
|
|
768
|
+
stage: "output",
|
|
769
|
+
guardrails: this.activeGuardrails,
|
|
770
|
+
value: { content, calls, messageId, started, usage },
|
|
771
|
+
context: { sessionId: this.id, runId, metadata: this.activeMetadata ?? {}, signal },
|
|
772
|
+
redactor: this.activeRedactor,
|
|
773
|
+
emit: (event) => this.emit(event),
|
|
774
|
+
}));
|
|
775
|
+
}
|
|
776
|
+
if (bufferOutput)
|
|
777
|
+
for (const event of bufferedOutput)
|
|
778
|
+
this.emit(event);
|
|
508
779
|
const latencyMs = Math.round(performance.now() - startedAt);
|
|
509
780
|
this.emit({
|
|
510
781
|
type: "provider_turn_finished",
|
|
@@ -529,7 +800,7 @@ class RuntimeAgentSession {
|
|
|
529
800
|
usage,
|
|
530
801
|
error: info,
|
|
531
802
|
});
|
|
532
|
-
if (error instanceof ProviderTurnFailure)
|
|
803
|
+
if (error instanceof GuardrailError || error instanceof ProviderTurnFailure)
|
|
533
804
|
throw error;
|
|
534
805
|
throw new ProviderTurnFailure(info, started);
|
|
535
806
|
}
|
|
@@ -734,6 +1005,20 @@ function mergeCompaction(agent, run) {
|
|
|
734
1005
|
return { ...(agent || {}), ...run };
|
|
735
1006
|
return agent || undefined;
|
|
736
1007
|
}
|
|
1008
|
+
function isBuiltInLoop(loop) {
|
|
1009
|
+
return typeof loop === "object" && loop !== null && "strategy" in loop;
|
|
1010
|
+
}
|
|
1011
|
+
function mergeGuardrails(agent, run) {
|
|
1012
|
+
if (!agent && !run)
|
|
1013
|
+
return undefined;
|
|
1014
|
+
return {
|
|
1015
|
+
input: [...(agent?.input ?? []), ...(run?.input ?? [])],
|
|
1016
|
+
output: [...(agent?.output ?? []), ...(run?.output ?? [])],
|
|
1017
|
+
toolInput: [...(agent?.toolInput ?? []), ...(run?.toolInput ?? [])],
|
|
1018
|
+
toolOutput: [...(agent?.toolOutput ?? []), ...(run?.toolOutput ?? [])],
|
|
1019
|
+
maxConcurrency: run?.maxConcurrency ?? agent?.maxConcurrency,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
737
1022
|
function withoutTrailingInput(messages, input) {
|
|
738
1023
|
const next = [...messages];
|
|
739
1024
|
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
@@ -760,6 +1045,14 @@ function throwIfAbortedSignal(signal) {
|
|
|
760
1045
|
if (signal?.aborted)
|
|
761
1046
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
762
1047
|
}
|
|
1048
|
+
function jsonBytes(value) {
|
|
1049
|
+
try {
|
|
1050
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
1051
|
+
}
|
|
1052
|
+
catch {
|
|
1053
|
+
throw new TypeError("Provider request or event must be JSON-serializable for run limits");
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
763
1056
|
function createUsageAccumulator() {
|
|
764
1057
|
const sums = new Map();
|
|
765
1058
|
let costCurrency;
|
|
@@ -800,7 +1093,5 @@ function createUsageAccumulator() {
|
|
|
800
1093
|
},
|
|
801
1094
|
};
|
|
802
1095
|
}
|
|
803
|
-
|
|
804
|
-
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
805
|
-
}
|
|
1096
|
+
const randomId = createId;
|
|
806
1097
|
//# sourceMappingURL=agents.js.map
|