@arnilo/prism 0.0.96 → 0.1.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/CHANGELOG.md +290 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +215 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +152 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +427 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +68 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +362 -208
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -7
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
package/dist/agent-loops.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AgentLoopStateError } from "./contracts.js";
|
|
2
2
|
import { createId } from "./ids.js";
|
|
3
|
+
import { inputMessages } from "./input.js";
|
|
4
|
+
import { artifactStructuredOutputRequest, withoutStructuredOutput } from "./structured-output.js";
|
|
3
5
|
function throwIfAborted(signal) {
|
|
4
6
|
if (signal.aborted)
|
|
5
7
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
@@ -7,7 +9,10 @@ function throwIfAborted(signal) {
|
|
|
7
9
|
function toolResultMessage(result) {
|
|
8
10
|
return {
|
|
9
11
|
role: "tool",
|
|
10
|
-
content: [
|
|
12
|
+
content: [
|
|
13
|
+
{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error },
|
|
14
|
+
...(result.content ?? []),
|
|
15
|
+
],
|
|
11
16
|
metadata: result.metadata,
|
|
12
17
|
};
|
|
13
18
|
}
|
|
@@ -17,12 +22,15 @@ function toolResultMessage(result) {
|
|
|
17
22
|
// fires artifact_* events here as a noop seam — single-shot emits zero.
|
|
18
23
|
export const singleShotLoop = {
|
|
19
24
|
name: "single-shot",
|
|
25
|
+
// Durable via the runtime's pending-call mechanism; no loop-local state to snapshot.
|
|
26
|
+
revision: "1",
|
|
20
27
|
async run(ctx) {
|
|
21
28
|
let usage;
|
|
22
29
|
let toolRounds = 0;
|
|
23
30
|
let nextInput = ctx.input;
|
|
24
31
|
for (let turn = 1;; turn += 1) {
|
|
25
32
|
throwIfAborted(ctx.signal);
|
|
33
|
+
await ctx.applyPendingSteers?.();
|
|
26
34
|
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
27
35
|
const request = await ctx.assemble(nextInput, undefined, turn);
|
|
28
36
|
throwIfAborted(ctx.signal);
|
|
@@ -37,10 +45,17 @@ export const singleShotLoop = {
|
|
|
37
45
|
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
38
46
|
}
|
|
39
47
|
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
40
|
-
|
|
48
|
+
const dispatchable = dispatchableToolCalls(calls);
|
|
49
|
+
if (dispatchable.length === 0 || toolRounds >= ctx.maxToolRounds) {
|
|
50
|
+
// Soft-interrupt / late steer: keep same run going when queue still has text.
|
|
51
|
+
if (await ctx.applyPendingSteers?.()) {
|
|
52
|
+
nextInput = [];
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
41
55
|
break;
|
|
56
|
+
}
|
|
42
57
|
toolRounds += 1;
|
|
43
|
-
await dispatchToolCallsInOrder(
|
|
58
|
+
await dispatchToolCallsInOrder(dispatchable, ctx);
|
|
44
59
|
nextInput = [];
|
|
45
60
|
}
|
|
46
61
|
return usage;
|
|
@@ -61,18 +76,56 @@ function defaultRepairer() {
|
|
|
61
76
|
export function generateValidateReviseLoop(opts) {
|
|
62
77
|
const max = opts.maxRevisions ?? 3;
|
|
63
78
|
const repairer = opts.repairer ?? defaultRepairer();
|
|
79
|
+
const finalOnly = opts.structuredOutputTiming === "final-turn-only" && opts.toolCalls === "bounded";
|
|
80
|
+
// ponytail: per-run state hoisted to factory scope so snapshot/restore can capture it;
|
|
81
|
+
// resolveLoop invokes this factory once per run, so there is no cross-run leakage.
|
|
82
|
+
let attempts = 0;
|
|
83
|
+
let artifactPhase = !finalOnly;
|
|
84
|
+
let savedSchema;
|
|
85
|
+
let pendingHistory = [];
|
|
64
86
|
return {
|
|
65
87
|
name: "generate-validate-revise",
|
|
88
|
+
revision: "1",
|
|
89
|
+
snapshot() {
|
|
90
|
+
return {
|
|
91
|
+
attempts,
|
|
92
|
+
artifactPhase,
|
|
93
|
+
savedSchema: savedSchema ?? null,
|
|
94
|
+
pendingHistory,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
restore(snapshot) {
|
|
98
|
+
const state = snapshot;
|
|
99
|
+
if (typeof state.attempts !== "number" || !Number.isInteger(state.attempts) || typeof state.artifactPhase !== "boolean") {
|
|
100
|
+
throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "generate-validate-revise snapshot drift");
|
|
101
|
+
}
|
|
102
|
+
attempts = state.attempts;
|
|
103
|
+
artifactPhase = state.artifactPhase;
|
|
104
|
+
savedSchema = (state.savedSchema ?? undefined);
|
|
105
|
+
// Repair messages were appended to the session before suspension, so the rebuilt
|
|
106
|
+
// history already carries them; re-applying pendingHistory would duplicate them.
|
|
107
|
+
pendingHistory = [];
|
|
108
|
+
},
|
|
66
109
|
async run(ctx) {
|
|
67
110
|
let usage;
|
|
68
111
|
let nextInput = ctx.input;
|
|
69
|
-
let pendingHistory = [];
|
|
70
112
|
let toolRounds = 0;
|
|
71
|
-
let attempts = 0;
|
|
72
113
|
for (let turn = 1; attempts <= max; turn += 1) {
|
|
73
114
|
throwIfAborted(ctx.signal);
|
|
115
|
+
await ctx.applyPendingSteers?.();
|
|
74
116
|
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
75
|
-
|
|
117
|
+
let request = await ctx.assemble(nextInput, undefined, turn);
|
|
118
|
+
if (request.options?.structuredOutput)
|
|
119
|
+
savedSchema ??= request.options.structuredOutput;
|
|
120
|
+
if (finalOnly) {
|
|
121
|
+
if (!artifactPhase && toolRounds < ctx.maxToolRounds) {
|
|
122
|
+
request = withoutStructuredOutput(request);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
artifactPhase = true;
|
|
126
|
+
request = artifactStructuredOutputRequest(request, savedSchema);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
76
129
|
throwIfAborted(ctx.signal);
|
|
77
130
|
const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
78
131
|
usage = turnUsage ?? usage;
|
|
@@ -90,13 +143,34 @@ export function generateValidateReviseLoop(opts) {
|
|
|
90
143
|
}
|
|
91
144
|
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
92
145
|
if (opts.toolCalls === "bounded" && calls.length > 0) {
|
|
146
|
+
const dispatchable = dispatchableToolCalls(calls);
|
|
147
|
+
if (dispatchable.length === 0) {
|
|
148
|
+
// Only provider-hosted calls; no host tool to run. Continue without charging a round.
|
|
149
|
+
nextInput = [];
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
93
152
|
if (toolRounds >= ctx.maxToolRounds) {
|
|
94
|
-
const result = {
|
|
153
|
+
const result = {
|
|
154
|
+
ok: false,
|
|
155
|
+
errors: [{ message: "maximum tool rounds exceeded" }],
|
|
156
|
+
metadata: { reason: "tool_round_limit" },
|
|
157
|
+
};
|
|
95
158
|
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
|
|
96
159
|
return usage;
|
|
97
160
|
}
|
|
98
161
|
toolRounds += 1;
|
|
99
|
-
await dispatchToolCallsInOrder(
|
|
162
|
+
await dispatchToolCallsInOrder(dispatchable, { ...ctx, toolConcurrency: 1 });
|
|
163
|
+
nextInput = [];
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// Soft-interrupt / late steer before treating empty/final output as artifact work.
|
|
167
|
+
if (await ctx.applyPendingSteers?.()) {
|
|
168
|
+
nextInput = [];
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Call-free during tool phase → one more turn with schema on / tools off.
|
|
172
|
+
if (finalOnly && !artifactPhase) {
|
|
173
|
+
artifactPhase = true;
|
|
100
174
|
nextInput = [];
|
|
101
175
|
continue;
|
|
102
176
|
}
|
|
@@ -125,7 +199,7 @@ export function generateValidateReviseLoop(opts) {
|
|
|
125
199
|
: undefined;
|
|
126
200
|
const attempt = ++attempts;
|
|
127
201
|
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
128
|
-
const result = parseFailure ?? await opts.validator(parsed.value, artifactCtx);
|
|
202
|
+
const result = parseFailure ?? (await opts.validator(parsed.value, artifactCtx));
|
|
129
203
|
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
130
204
|
if (result.ok) {
|
|
131
205
|
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
@@ -161,14 +235,18 @@ export function resolveToolConcurrency(options, config) {
|
|
|
161
235
|
return 1;
|
|
162
236
|
return Math.floor(value);
|
|
163
237
|
}
|
|
238
|
+
/** Calls the host must dispatch. Provider-hosted calls (`authority: "provider-hosted"`)
|
|
239
|
+
* were already executed server-side; the assistant response text carries their effect, so
|
|
240
|
+
* the host neither dispatches them nor appends a `tool_result`. */
|
|
241
|
+
export function dispatchableToolCalls(calls) {
|
|
242
|
+
return calls.filter((call) => call.authority !== "provider-hosted");
|
|
243
|
+
}
|
|
164
244
|
/** Dispatch tool calls with bounded concurrency; append transcript rows in call order. */
|
|
165
245
|
export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
166
246
|
if (calls.length === 0)
|
|
167
247
|
return;
|
|
168
|
-
ctx.chargeToolRound?.(calls);
|
|
169
|
-
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call))
|
|
170
|
-
? 1
|
|
171
|
-
: Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
248
|
+
await ctx.chargeToolRound?.(calls);
|
|
249
|
+
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call)) ? 1 : Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
172
250
|
if (concurrency === 1) {
|
|
173
251
|
for (const call of calls) {
|
|
174
252
|
const result = await ctx.dispatchToolCall(call);
|
|
@@ -191,10 +269,16 @@ export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
|
191
269
|
await Promise.all(workers);
|
|
192
270
|
for (const result of results) {
|
|
193
271
|
throwIfAborted(ctx.signal);
|
|
272
|
+
if (!result)
|
|
273
|
+
continue;
|
|
194
274
|
await appendToolResultMessage(result, ctx);
|
|
195
275
|
}
|
|
196
276
|
}
|
|
197
277
|
async function appendToolResultMessage(result, ctx) {
|
|
278
|
+
// Approval-gated calls return a marker instead of a real result; the transcript must not
|
|
279
|
+
// record a phantom tool_result for a call that never dispatched.
|
|
280
|
+
if (result.metadata?.approvalPending === true)
|
|
281
|
+
return;
|
|
198
282
|
const message = toolResultMessage(result);
|
|
199
283
|
ctx.history.push(message);
|
|
200
284
|
await ctx.appendMessage(message);
|
|
@@ -214,6 +298,7 @@ export function resolveLoop(options, config) {
|
|
|
214
298
|
repairer: loop.repairer,
|
|
215
299
|
maxRevisions: loop.maxRevisions,
|
|
216
300
|
toolCalls: loop.toolCalls,
|
|
301
|
+
structuredOutputTiming: loop.structuredOutputTiming,
|
|
217
302
|
});
|
|
218
303
|
}
|
|
219
304
|
throw new Error(`Unknown agent loop strategy: ${strategy}`);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { Agent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope } from "./contracts.js";
|
|
2
|
-
import type { CheckpointStore } from "./contracts.js";
|
|
1
|
+
import type { Agent, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, CheckpointStore, OwnershipScope, SubscribeOptions } from "./contracts.js";
|
|
3
2
|
export interface AgentRunLifecycleAgent {
|
|
4
3
|
readonly agent: Agent;
|
|
5
4
|
/** Current host-authored revision; it must match the stored revision. */
|
|
@@ -20,9 +19,13 @@ export interface AgentRunLifecycleRequest {
|
|
|
20
19
|
/** Adapter-selected capability; stored runs for another agent are non-enumerable. */
|
|
21
20
|
readonly agentId?: string;
|
|
22
21
|
}
|
|
22
|
+
/** Bounded live-event options for a durable lifecycle resume. */
|
|
23
|
+
export interface AgentRunLifecycleStreamRequest extends AgentRunLifecycleRequest, SubscribeOptions {
|
|
24
|
+
}
|
|
23
25
|
export interface AgentRunLifecycle {
|
|
24
26
|
status(ref: AgentRunRef, options?: AgentRunLifecycleRequest): Promise<AgentRunStatusResult>;
|
|
25
27
|
resume(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleRequest): Promise<AgentRunResult>;
|
|
28
|
+
resumeStream(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleStreamRequest): AsyncIterable<AgentEvent>;
|
|
26
29
|
}
|
|
27
30
|
/** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
|
|
28
31
|
export declare function createAgentRunLifecycle(options: AgentRunLifecycleOptions): AgentRunLifecycle;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AgentRunStateError } from "./contracts.js";
|
|
2
1
|
import { loadAgentRunState, publicState } from "./agent-run-state.js";
|
|
3
|
-
import { resumeAgentRun } from "./agents.js";
|
|
2
|
+
import { resumeAgentRun, resumeAgentRunStream } from "./agents.js";
|
|
3
|
+
import { AgentRunStateError } from "./contracts.js";
|
|
4
4
|
function assertAgentId(actual, expected) {
|
|
5
5
|
if (expected !== undefined && actual !== expected)
|
|
6
6
|
throw new AgentRunStateError("Agent run capability mismatch");
|
|
@@ -28,6 +28,22 @@ export function createAgentRunLifecycle(options) {
|
|
|
28
28
|
definitionRevision: resolved.definitionRevision,
|
|
29
29
|
});
|
|
30
30
|
},
|
|
31
|
+
async *resumeStream(ref, resume, request = {}) {
|
|
32
|
+
request.signal?.throwIfAborted();
|
|
33
|
+
const { state } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
|
|
34
|
+
assertAgentId(state.agentId, request.agentId);
|
|
35
|
+
const resolved = await options.resolveAgent({ agentId: state.agentId, ownership: request.ownership, signal: request.signal });
|
|
36
|
+
request.signal?.throwIfAborted();
|
|
37
|
+
yield* resumeAgentRunStream(resolved.agent, ref, resume, {
|
|
38
|
+
checkpoints: options.checkpoints,
|
|
39
|
+
ownership: request.ownership,
|
|
40
|
+
fencingToken: options.fencingToken,
|
|
41
|
+
definitionRevision: resolved.definitionRevision,
|
|
42
|
+
signal: request.signal,
|
|
43
|
+
maxQueuedEvents: request.maxQueuedEvents,
|
|
44
|
+
overflow: request.overflow,
|
|
45
|
+
});
|
|
46
|
+
},
|
|
31
47
|
};
|
|
32
48
|
}
|
|
33
49
|
//# sourceMappingURL=agent-run-lifecycle.js.map
|
|
@@ -1,19 +1,44 @@
|
|
|
1
|
-
import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, Message, ModelConfig, OwnershipScope, RunLimitCounters, ToolCallContent } from "./contracts.js";
|
|
1
|
+
import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, JsonValue, Message, ModelConfig, NestedRunRef, OwnershipScope, RunDecision, RunLimitCounters, StickyDecision, ToolCallContent } from "./contracts.js";
|
|
2
2
|
import type { SecretRedactor } from "./redaction.js";
|
|
3
3
|
export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
4
4
|
export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
|
|
5
5
|
export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
|
|
6
6
|
export declare const HARD_MAX_AGENT_RUN_STATE_BYTES: number;
|
|
7
|
+
/** One gated tool call awaiting or holding a decision inside a suspended durable run. */
|
|
8
|
+
export interface PendingToolCall {
|
|
9
|
+
readonly call: ToolCallContent;
|
|
10
|
+
readonly status: "ready" | "dispatched";
|
|
11
|
+
readonly approvalId: string;
|
|
12
|
+
/** Decision persisted by a partial batch; applied when the run finally resumes. */
|
|
13
|
+
readonly decision?: RunDecision;
|
|
14
|
+
}
|
|
7
15
|
export interface StoredAgentRunState extends AgentRunState {
|
|
8
16
|
readonly input?: readonly Message[];
|
|
17
|
+
/** Legacy single gated call (pre-0.0.25 checkpoints). New states write `pendingCalls`. */
|
|
9
18
|
readonly pending?: {
|
|
10
19
|
readonly call: ToolCallContent;
|
|
11
20
|
readonly status: "ready" | "dispatched";
|
|
12
21
|
};
|
|
22
|
+
/** Gated calls of the current suspension, in provider-turn order. */
|
|
23
|
+
readonly pendingCalls?: readonly PendingToolCall[];
|
|
24
|
+
/** Suspended nested runs (supervisor children) whose pending decisions surface at this root. */
|
|
25
|
+
readonly nestedRuns?: readonly NestedRunRef[];
|
|
26
|
+
/** Run-scoped sticky decisions; exact scope match, dropped at any terminal status. */
|
|
27
|
+
readonly stickyDecisions?: readonly StickyDecision[];
|
|
13
28
|
readonly interruptBeforeTool?: boolean;
|
|
14
29
|
readonly counters: RunLimitCounters;
|
|
15
30
|
readonly deadlineAt: string;
|
|
31
|
+
/** Loop-local durable state captured by the strategy's snapshot hook at suspension. */
|
|
32
|
+
readonly loopState?: {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly revision: string;
|
|
35
|
+
readonly snapshot: JsonValue;
|
|
36
|
+
};
|
|
16
37
|
}
|
|
38
|
+
/** Revision stamps of the built-in loops; custom strategies declare their own `revision`. */
|
|
39
|
+
export declare const BUILT_IN_LOOP_REVISIONS: Readonly<Record<string, string>>;
|
|
40
|
+
/** Validate a strategy snapshot as JSON-compatible and package it for the durable envelope. */
|
|
41
|
+
export declare function boundedLoopSnapshot(name: string, revision: string, snapshot: JsonValue): StoredAgentRunState["loopState"];
|
|
17
42
|
export declare function agentFingerprint(agent: Agent, revision: string): string;
|
|
18
43
|
export declare function agentId(agent: Agent): string;
|
|
19
44
|
export declare function validateRunStateOptions(options: AgentRunStateOptions): void;
|
|
@@ -48,6 +73,7 @@ export declare function initialAgentRunState(input: {
|
|
|
48
73
|
readonly interruption?: AgentRunInterruption;
|
|
49
74
|
readonly messages?: readonly Message[];
|
|
50
75
|
readonly pending?: StoredAgentRunState["pending"];
|
|
76
|
+
readonly pendingCalls?: StoredAgentRunState["pendingCalls"];
|
|
51
77
|
readonly interruptBeforeTool?: boolean;
|
|
52
78
|
}): StoredAgentRunState;
|
|
53
79
|
export declare function parseAgentRunState(value: unknown, version?: number): StoredAgentRunState;
|
package/dist/agent-run-state.js
CHANGED
|
@@ -1,27 +1,83 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { AgentRunStateError } from "./contracts.js";
|
|
2
|
+
import { AgentLoopStateError, AgentRunStateError } from "./contracts.js";
|
|
3
3
|
export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
4
4
|
export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
5
5
|
export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
|
|
6
6
|
export const HARD_MAX_AGENT_RUN_STATE_BYTES = 1024 * 1024;
|
|
7
7
|
const MAX_DEPTH = 32;
|
|
8
8
|
const MAX_PROPERTIES = 256;
|
|
9
|
+
/** Revision stamps of the built-in loops; custom strategies declare their own `revision`. */
|
|
10
|
+
export const BUILT_IN_LOOP_REVISIONS = {
|
|
11
|
+
"single-shot": "1",
|
|
12
|
+
"generate-validate-revise": "1",
|
|
13
|
+
};
|
|
14
|
+
/** Validate a strategy snapshot as JSON-compatible and package it for the durable envelope. */
|
|
15
|
+
export function boundedLoopSnapshot(name, revision, snapshot) {
|
|
16
|
+
try {
|
|
17
|
+
assertJsonValue(snapshot, 0);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error instanceof AgentLoopStateError)
|
|
21
|
+
throw error;
|
|
22
|
+
throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "Loop snapshot must be JSON-compatible", { cause: error });
|
|
23
|
+
}
|
|
24
|
+
return { name, revision, snapshot };
|
|
25
|
+
}
|
|
26
|
+
function assertJsonValue(value, depth) {
|
|
27
|
+
if (depth > MAX_DEPTH)
|
|
28
|
+
throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", `Loop snapshot exceeds depth ${MAX_DEPTH}`);
|
|
29
|
+
switch (typeof value) {
|
|
30
|
+
case "string":
|
|
31
|
+
case "boolean":
|
|
32
|
+
return;
|
|
33
|
+
case "number":
|
|
34
|
+
if (!Number.isFinite(value))
|
|
35
|
+
throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "Loop snapshot numbers must be finite");
|
|
36
|
+
return;
|
|
37
|
+
case "object": {
|
|
38
|
+
if (value === null)
|
|
39
|
+
return;
|
|
40
|
+
for (const item of Object.values(value))
|
|
41
|
+
assertJsonValue(item, depth + 1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
throw new AgentLoopStateError("ERR_PRISM_LOOP_SNAPSHOT", "Loop snapshot must be JSON-compatible");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
9
48
|
export function agentFingerprint(agent, revision) {
|
|
10
49
|
const config = agent.config;
|
|
11
50
|
const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
|
|
51
|
+
const skills = !config.skills ? [] : "list" in config.skills ? config.skills.list() : config.skills;
|
|
12
52
|
const guardrails = [
|
|
13
53
|
...(config.guardrails?.input ?? []),
|
|
14
54
|
...(config.guardrails?.output ?? []),
|
|
15
55
|
...(config.guardrails?.toolInput ?? []),
|
|
16
56
|
...(config.guardrails?.toolOutput ?? []),
|
|
17
57
|
];
|
|
58
|
+
const systemPrompt = config.systemPrompt === false || config.systemPrompt === undefined
|
|
59
|
+
? (config.systemPrompt ?? null)
|
|
60
|
+
: (Array.isArray(config.systemPrompt) ? config.systemPrompt : [config.systemPrompt]).map((c) => ({ id: c.id, text: c.text }));
|
|
18
61
|
const value = JSON.stringify({
|
|
19
62
|
id: config.id ?? config.name ?? "agent",
|
|
20
63
|
revision,
|
|
21
64
|
model: config.model,
|
|
22
|
-
|
|
65
|
+
// Instructions/prompt text shapes agent behavior as much as the tool set; a change
|
|
66
|
+
// without a definitionRevision bump must not resume stale durable runs silently.
|
|
67
|
+
instructions: config.instructions ?? null,
|
|
68
|
+
systemPrompt,
|
|
69
|
+
skills: skills.map((skill) => ({ name: skill.name, instructions: skill.instructions, toolNames: skill.toolNames })),
|
|
70
|
+
tools: tools.map((tool) => ({
|
|
71
|
+
name: tool.name,
|
|
72
|
+
parameters: tool.parameters,
|
|
73
|
+
exclusive: tool.exclusive,
|
|
74
|
+
effect: typeof tool.effect === "function" ? "classifier" : tool.effect,
|
|
75
|
+
})),
|
|
23
76
|
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
24
|
-
|
|
77
|
+
// Loop revision participates so a loop change without a definitionRevision bump fails closed.
|
|
78
|
+
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
79
|
+
? { name: config.loop.strategy, revision: BUILT_IN_LOOP_REVISIONS[config.loop.strategy] ?? null }
|
|
80
|
+
: { name: config.loop?.name ?? "single-shot", revision: config.loop?.revision ?? BUILT_IN_LOOP_REVISIONS["single-shot"] },
|
|
25
81
|
});
|
|
26
82
|
return createHash("sha256").update(value).digest("hex");
|
|
27
83
|
}
|
|
@@ -43,7 +99,10 @@ export async function loadAgentRunState(checkpoints, ref, ownership) {
|
|
|
43
99
|
const record = await checkpoints.loadCheckpoint({ namespace: AGENT_RUN_STATE_NAMESPACE, key: ref.runId, ...ownership });
|
|
44
100
|
if (!record)
|
|
45
101
|
throw new AgentRunStateError(`No durable agent run ${ref.runId}`);
|
|
46
|
-
if (ref.sessionId &&
|
|
102
|
+
if (ref.sessionId &&
|
|
103
|
+
record.value &&
|
|
104
|
+
typeof record.value === "object" &&
|
|
105
|
+
record.value.sessionId !== ref.sessionId) {
|
|
47
106
|
throw new AgentRunStateError("Agent run session mismatch");
|
|
48
107
|
}
|
|
49
108
|
return { record, state: parseAgentRunState(record.value, record.version) };
|
|
@@ -66,7 +125,7 @@ export function statusFromState(state, version) {
|
|
|
66
125
|
return { state: publicState({ ...state, version }), version };
|
|
67
126
|
}
|
|
68
127
|
export function publicState(state) {
|
|
69
|
-
const { input: _input, pending: _pending, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
|
|
128
|
+
const { input: _input, pending: _pending, pendingCalls: _pendingCalls, nestedRuns: _nestedRuns, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
|
|
70
129
|
return publicValue;
|
|
71
130
|
}
|
|
72
131
|
export function initialAgentRunState(input) {
|
|
@@ -84,6 +143,7 @@ export function initialAgentRunState(input) {
|
|
|
84
143
|
interruption: input.interruption,
|
|
85
144
|
input: input.messages,
|
|
86
145
|
pending: input.pending,
|
|
146
|
+
pendingCalls: input.pendingCalls,
|
|
87
147
|
interruptBeforeTool: input.interruptBeforeTool,
|
|
88
148
|
counters: input.counters,
|
|
89
149
|
deadlineAt: input.deadlineAt,
|
|
@@ -95,10 +155,56 @@ export function parseAgentRunState(value, version) {
|
|
|
95
155
|
const state = value;
|
|
96
156
|
if (state.schemaVersion !== AGENT_RUN_STATE_SCHEMA_VERSION)
|
|
97
157
|
throw new AgentRunStateError(`Unsupported agent run state schemaVersion ${String(state.schemaVersion)}`);
|
|
98
|
-
if (!state.agentId ||
|
|
158
|
+
if (!state.agentId ||
|
|
159
|
+
!state.definitionRevision ||
|
|
160
|
+
!state.fingerprint ||
|
|
161
|
+
!state.runId ||
|
|
162
|
+
!state.sessionId ||
|
|
163
|
+
!state.model ||
|
|
164
|
+
!state.status ||
|
|
165
|
+
!state.counters ||
|
|
166
|
+
!state.deadlineAt) {
|
|
99
167
|
throw new AgentRunStateError("Malformed agent run state");
|
|
100
168
|
}
|
|
101
|
-
|
|
169
|
+
if (state.pendingCalls !== undefined &&
|
|
170
|
+
(!Array.isArray(state.pendingCalls) ||
|
|
171
|
+
state.pendingCalls.some((entry) => !entry ||
|
|
172
|
+
typeof entry !== "object" ||
|
|
173
|
+
!entry.call ||
|
|
174
|
+
typeof entry.approvalId !== "string" ||
|
|
175
|
+
(entry.status !== "ready" && entry.status !== "dispatched")))) {
|
|
176
|
+
throw new AgentRunStateError("Malformed agent run pending calls");
|
|
177
|
+
}
|
|
178
|
+
if (state.stickyDecisions !== undefined &&
|
|
179
|
+
(!Array.isArray(state.stickyDecisions) ||
|
|
180
|
+
state.stickyDecisions.some((entry) => !entry ||
|
|
181
|
+
typeof entry !== "object" ||
|
|
182
|
+
!entry.scope ||
|
|
183
|
+
(entry.outcome !== "allow_for_run" && entry.outcome !== "reject_for_run")))) {
|
|
184
|
+
throw new AgentRunStateError("Malformed agent run sticky decisions");
|
|
185
|
+
}
|
|
186
|
+
if (state.nestedRuns !== undefined &&
|
|
187
|
+
(!Array.isArray(state.nestedRuns) ||
|
|
188
|
+
state.nestedRuns.some((entry) => !entry ||
|
|
189
|
+
typeof entry !== "object" ||
|
|
190
|
+
typeof entry.runId !== "string" ||
|
|
191
|
+
typeof entry.toolCallId !== "string" ||
|
|
192
|
+
!Array.isArray(entry.path) ||
|
|
193
|
+
!Array.isArray(entry.approvals) ||
|
|
194
|
+
entry.approvals.some((approval) => typeof approval?.id !== "string" || typeof approval?.childApprovalId !== "string")))) {
|
|
195
|
+
throw new AgentRunStateError("Malformed agent run nested runs");
|
|
196
|
+
}
|
|
197
|
+
if (state.loopState !== undefined &&
|
|
198
|
+
(typeof state.loopState !== "object" ||
|
|
199
|
+
typeof state.loopState.name !== "string" ||
|
|
200
|
+
typeof state.loopState.revision !== "string" ||
|
|
201
|
+
!("snapshot" in state.loopState))) {
|
|
202
|
+
throw new AgentRunStateError("Malformed agent run loop state");
|
|
203
|
+
}
|
|
204
|
+
// Load bounds against the hard cap, not the default: the configured maxStateBytes is a
|
|
205
|
+
// save-side policy knob, while the load-side bound is only a DoS ceiling. States saved
|
|
206
|
+
// with a raised maxStateBytes must remain resumable.
|
|
207
|
+
return boundState({ ...state, version }, HARD_MAX_AGENT_RUN_STATE_BYTES);
|
|
102
208
|
}
|
|
103
209
|
function boundState(state, maxBytes) {
|
|
104
210
|
checkShape(state, 0);
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { Agent, AgentConfig, AgentRunResult, AgentRunResume, AgentRunResumeOptions,
|
|
1
|
+
import type { Agent, AgentConfig, AgentEvent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunResumeStreamOptions, AgentSession, AgentSessionConfig } from "./contracts.js";
|
|
2
2
|
export declare function createAgent(config: AgentConfig): Agent;
|
|
3
3
|
export declare function createAgentSession(config: AgentSessionConfig & {
|
|
4
4
|
readonly agent: Agent;
|
|
5
5
|
}): AgentSession;
|
|
6
6
|
/** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
|
|
7
7
|
export declare function resumeAgentRun(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeOptions): Promise<AgentRunResult>;
|
|
8
|
+
/** Subscribe before resuming one durable run. Early consumer return aborts that resumed execution. */
|
|
9
|
+
export declare function resumeAgentRunStream(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeStreamOptions): AsyncGenerator<AgentEvent>;
|