@arnilo/prism 0.0.6 → 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 +11 -0
- package/dist/agent-loops.js +1 -0
- 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 +335 -43
- package/dist/contracts.d.ts +203 -3
- package/dist/contracts.js +4 -0
- package/dist/guardrails.d.ts +25 -0
- package/dist/guardrails.js +133 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +8 -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/tools.d.ts +10 -2
- package/dist/tools.js +54 -4
- package/docs/agent-events.md +13 -1
- package/docs/agent-loops.md +11 -3
- package/docs/agent-session-runtime.md +33 -1
- package/docs/guardrails.md +75 -0
- package/docs/host-security.md +6 -2
- package/docs/index.md +5 -4
- package/docs/mcp-tools.md +7 -3
- package/docs/migration.md +18 -0
- package/docs/release-and-install.md +40 -40
- package/docs/runs-and-usage.md +29 -2
- package/docs/server.md +5 -2
- package/docs/tools.md +6 -1
- package/docs/workflows.md +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.7] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Typed `Guardrails` for input, provider output, tool input, and tool output. Guardrail decisions are bounded/redacted `guardrail_decision` events; provider output is buffered before exposure when output checks are configured.
|
|
13
|
+
- Workflow tool nodes and MCP server tool registrations now route optional tool guardrails through shared `dispatchToolCall()`.
|
|
14
|
+
- `RunLimits` adds validated, narrowing-only budgets for turns, provider attempts, tool rounds/calls, wall time, request/response bytes, token usage, and optional single-currency cost. Breaches emit one `run_limit_exceeded` event and return `AgentRunError.result.limit`.
|
|
15
|
+
- Opt-in durable built-in agent runs can suspend before a tool side effect and resume through versioned, bounded, redacted checkpoint state with CAS approval, ownership/fingerprint checks, and no automatic replay of an ambiguous dispatched tool.
|
|
16
|
+
- `createSecureAgent()` composes strict tool schemas/validation, trust and permission gates, redaction, finite limits, exact ownership, and durable pre-tool approval without changing low-level `createAgent()` defaults.
|
|
17
|
+
- `createAgentRunLifecycle()` adds explicit, ownership-scoped durable agent status/resume capability for selected server and MCP exposures; no lifecycle route/tool is enabled by default.
|
|
18
|
+
|
|
8
19
|
## [0.0.6] - 2026-07-19
|
|
9
20
|
|
|
10
21
|
### Added
|
package/dist/agent-loops.js
CHANGED
|
@@ -159,6 +159,7 @@ export function resolveToolConcurrency(options, config) {
|
|
|
159
159
|
export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
160
160
|
if (calls.length === 0)
|
|
161
161
|
return;
|
|
162
|
+
ctx.chargeToolRound?.(calls);
|
|
162
163
|
const concurrency = calls.some((call) => ctx.isToolCallExclusive?.(call))
|
|
163
164
|
? 1
|
|
164
165
|
: Math.max(1, Math.min(ctx.toolConcurrency, calls.length));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Agent, AgentRunRef, AgentRunResult, AgentRunResume, AgentRunStatusResult, OwnershipScope } from "./contracts.js";
|
|
2
|
+
import type { CheckpointStore } from "./contracts.js";
|
|
3
|
+
export interface AgentRunLifecycleAgent {
|
|
4
|
+
readonly agent: Agent;
|
|
5
|
+
/** Current host-authored revision; it must match the stored revision. */
|
|
6
|
+
readonly definitionRevision: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AgentRunLifecycleOptions {
|
|
9
|
+
readonly checkpoints: CheckpointStore;
|
|
10
|
+
readonly resolveAgent: (input: {
|
|
11
|
+
readonly agentId: string;
|
|
12
|
+
readonly ownership?: OwnershipScope;
|
|
13
|
+
readonly signal?: AbortSignal;
|
|
14
|
+
}) => AgentRunLifecycleAgent | Promise<AgentRunLifecycleAgent>;
|
|
15
|
+
readonly fencingToken?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface AgentRunLifecycleRequest {
|
|
18
|
+
readonly ownership?: OwnershipScope;
|
|
19
|
+
readonly signal?: AbortSignal;
|
|
20
|
+
/** Adapter-selected capability; stored runs for another agent are non-enumerable. */
|
|
21
|
+
readonly agentId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface AgentRunLifecycle {
|
|
24
|
+
status(ref: AgentRunRef, options?: AgentRunLifecycleRequest): Promise<AgentRunStatusResult>;
|
|
25
|
+
resume(ref: AgentRunRef, resume: AgentRunResume, options?: AgentRunLifecycleRequest): Promise<AgentRunResult>;
|
|
26
|
+
}
|
|
27
|
+
/** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
|
|
28
|
+
export declare function createAgentRunLifecycle(options: AgentRunLifecycleOptions): AgentRunLifecycle;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AgentRunStateError } from "./contracts.js";
|
|
2
|
+
import { loadAgentRunState, publicState } from "./agent-run-state.js";
|
|
3
|
+
import { resumeAgentRun } from "./agents.js";
|
|
4
|
+
function assertAgentId(actual, expected) {
|
|
5
|
+
if (expected !== undefined && actual !== expected)
|
|
6
|
+
throw new AgentRunStateError("Agent run capability mismatch");
|
|
7
|
+
}
|
|
8
|
+
/** Host capability for durable agent status/resume. Adapters supply authorized ownership only. */
|
|
9
|
+
export function createAgentRunLifecycle(options) {
|
|
10
|
+
return {
|
|
11
|
+
async status(ref, request = {}) {
|
|
12
|
+
request.signal?.throwIfAborted();
|
|
13
|
+
const { state, record } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
|
|
14
|
+
assertAgentId(state.agentId, request.agentId);
|
|
15
|
+
request.signal?.throwIfAborted();
|
|
16
|
+
return { state: publicState({ ...state, version: record.version }), version: record.version };
|
|
17
|
+
},
|
|
18
|
+
async resume(ref, resume, request = {}) {
|
|
19
|
+
request.signal?.throwIfAborted();
|
|
20
|
+
const { state } = await loadAgentRunState(options.checkpoints, ref, request.ownership);
|
|
21
|
+
assertAgentId(state.agentId, request.agentId);
|
|
22
|
+
const resolved = await options.resolveAgent({ agentId: state.agentId, ownership: request.ownership, signal: request.signal });
|
|
23
|
+
request.signal?.throwIfAborted();
|
|
24
|
+
return resumeAgentRun(resolved.agent, ref, resume, {
|
|
25
|
+
checkpoints: options.checkpoints,
|
|
26
|
+
ownership: request.ownership,
|
|
27
|
+
fencingToken: options.fencingToken,
|
|
28
|
+
definitionRevision: resolved.definitionRevision,
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=agent-run-lifecycle.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Agent, AgentRunInterruption, AgentRunRef, AgentRunState, AgentRunStateOptions, AgentRunStatusResult, CheckpointRecord, CheckpointStore, Message, ModelConfig, OwnershipScope, RunLimitCounters, ToolCallContent } from "./contracts.js";
|
|
2
|
+
import type { SecretRedactor } from "./redaction.js";
|
|
3
|
+
export declare const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
4
|
+
export declare const AGENT_RUN_STATE_SCHEMA_VERSION: 1;
|
|
5
|
+
export declare const DEFAULT_MAX_AGENT_RUN_STATE_BYTES: number;
|
|
6
|
+
export declare const HARD_MAX_AGENT_RUN_STATE_BYTES: number;
|
|
7
|
+
export interface StoredAgentRunState extends AgentRunState {
|
|
8
|
+
readonly input?: readonly Message[];
|
|
9
|
+
readonly pending?: {
|
|
10
|
+
readonly call: ToolCallContent;
|
|
11
|
+
readonly status: "ready" | "dispatched";
|
|
12
|
+
};
|
|
13
|
+
readonly interruptBeforeTool?: boolean;
|
|
14
|
+
readonly counters: RunLimitCounters;
|
|
15
|
+
readonly deadlineAt: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function agentFingerprint(agent: Agent, revision: string): string;
|
|
18
|
+
export declare function agentId(agent: Agent): string;
|
|
19
|
+
export declare function validateRunStateOptions(options: AgentRunStateOptions): void;
|
|
20
|
+
export declare function loadAgentRunState(checkpoints: CheckpointStore, ref: AgentRunRef, ownership?: OwnershipScope): Promise<{
|
|
21
|
+
readonly record: CheckpointRecord;
|
|
22
|
+
readonly state: StoredAgentRunState;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function saveAgentRunState(input: {
|
|
25
|
+
readonly checkpoints: CheckpointStore;
|
|
26
|
+
readonly state: StoredAgentRunState;
|
|
27
|
+
readonly expectedVersion: number;
|
|
28
|
+
readonly ownership?: OwnershipScope;
|
|
29
|
+
readonly fencingToken?: number;
|
|
30
|
+
readonly redactor?: SecretRedactor;
|
|
31
|
+
readonly maxStateBytes?: number;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
readonly record: CheckpointRecord;
|
|
34
|
+
readonly state: StoredAgentRunState;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function statusFromState(state: StoredAgentRunState, version: number): AgentRunStatusResult;
|
|
37
|
+
export declare function publicState(state: StoredAgentRunState): AgentRunState;
|
|
38
|
+
export declare function initialAgentRunState(input: {
|
|
39
|
+
readonly agent: Agent;
|
|
40
|
+
readonly options: AgentRunStateOptions;
|
|
41
|
+
readonly runId: string;
|
|
42
|
+
readonly sessionId: string;
|
|
43
|
+
readonly leafId?: string;
|
|
44
|
+
readonly model: ModelConfig;
|
|
45
|
+
readonly counters: RunLimitCounters;
|
|
46
|
+
readonly deadlineAt: string;
|
|
47
|
+
readonly status: "suspended" | "running";
|
|
48
|
+
readonly interruption?: AgentRunInterruption;
|
|
49
|
+
readonly messages?: readonly Message[];
|
|
50
|
+
readonly pending?: StoredAgentRunState["pending"];
|
|
51
|
+
readonly interruptBeforeTool?: boolean;
|
|
52
|
+
}): StoredAgentRunState;
|
|
53
|
+
export declare function parseAgentRunState(value: unknown, version?: number): StoredAgentRunState;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AgentRunStateError } from "./contracts.js";
|
|
3
|
+
export const AGENT_RUN_STATE_NAMESPACE = "prism.agent-run";
|
|
4
|
+
export const AGENT_RUN_STATE_SCHEMA_VERSION = 1;
|
|
5
|
+
export const DEFAULT_MAX_AGENT_RUN_STATE_BYTES = 256 * 1024;
|
|
6
|
+
export const HARD_MAX_AGENT_RUN_STATE_BYTES = 1024 * 1024;
|
|
7
|
+
const MAX_DEPTH = 32;
|
|
8
|
+
const MAX_PROPERTIES = 256;
|
|
9
|
+
export function agentFingerprint(agent, revision) {
|
|
10
|
+
const config = agent.config;
|
|
11
|
+
const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
|
|
12
|
+
const guardrails = [
|
|
13
|
+
...(config.guardrails?.input ?? []),
|
|
14
|
+
...(config.guardrails?.output ?? []),
|
|
15
|
+
...(config.guardrails?.toolInput ?? []),
|
|
16
|
+
...(config.guardrails?.toolOutput ?? []),
|
|
17
|
+
];
|
|
18
|
+
const value = JSON.stringify({
|
|
19
|
+
id: config.id ?? config.name ?? "agent",
|
|
20
|
+
revision,
|
|
21
|
+
model: config.model,
|
|
22
|
+
tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
|
|
23
|
+
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
24
|
+
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop ? config.loop.strategy : config.loop?.name ?? "single-shot",
|
|
25
|
+
});
|
|
26
|
+
return createHash("sha256").update(value).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
export function agentId(agent) {
|
|
29
|
+
const id = agent.config.id ?? agent.config.name;
|
|
30
|
+
if (!id?.trim())
|
|
31
|
+
throw new AgentRunStateError("Durable agent runs require AgentConfig.id or name");
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
export function validateRunStateOptions(options) {
|
|
35
|
+
if (!options.definitionRevision.trim())
|
|
36
|
+
throw new AgentRunStateError("Durable agent runs require definitionRevision");
|
|
37
|
+
const bytes = options.maxStateBytes ?? DEFAULT_MAX_AGENT_RUN_STATE_BYTES;
|
|
38
|
+
if (!Number.isSafeInteger(bytes) || bytes < 1 || bytes > HARD_MAX_AGENT_RUN_STATE_BYTES) {
|
|
39
|
+
throw new AgentRunStateError(`maxStateBytes must be a positive safe integer at most ${HARD_MAX_AGENT_RUN_STATE_BYTES}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function loadAgentRunState(checkpoints, ref, ownership) {
|
|
43
|
+
const record = await checkpoints.loadCheckpoint({ namespace: AGENT_RUN_STATE_NAMESPACE, key: ref.runId, ...ownership });
|
|
44
|
+
if (!record)
|
|
45
|
+
throw new AgentRunStateError(`No durable agent run ${ref.runId}`);
|
|
46
|
+
if (ref.sessionId && record.value && typeof record.value === "object" && record.value.sessionId !== ref.sessionId) {
|
|
47
|
+
throw new AgentRunStateError("Agent run session mismatch");
|
|
48
|
+
}
|
|
49
|
+
return { record, state: parseAgentRunState(record.value, record.version) };
|
|
50
|
+
}
|
|
51
|
+
export async function saveAgentRunState(input) {
|
|
52
|
+
const bounded = boundState(input.redactor?.redact(input.state) ?? input.state, input.maxStateBytes ?? DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
|
|
53
|
+
const record = await input.checkpoints.saveCheckpoint({
|
|
54
|
+
namespace: AGENT_RUN_STATE_NAMESPACE,
|
|
55
|
+
key: bounded.runId,
|
|
56
|
+
version: input.expectedVersion + 1,
|
|
57
|
+
expectedVersion: input.expectedVersion,
|
|
58
|
+
fencingToken: input.fencingToken,
|
|
59
|
+
value: bounded,
|
|
60
|
+
category: "agent-run",
|
|
61
|
+
...input.ownership,
|
|
62
|
+
});
|
|
63
|
+
return { record, state: { ...bounded, version: record.version } };
|
|
64
|
+
}
|
|
65
|
+
export function statusFromState(state, version) {
|
|
66
|
+
return { state: publicState({ ...state, version }), version };
|
|
67
|
+
}
|
|
68
|
+
export function publicState(state) {
|
|
69
|
+
const { input: _input, pending: _pending, interruptBeforeTool: _interruptBeforeTool, counters: _counters, deadlineAt: _deadlineAt, ...publicValue } = state;
|
|
70
|
+
return publicValue;
|
|
71
|
+
}
|
|
72
|
+
export function initialAgentRunState(input) {
|
|
73
|
+
validateRunStateOptions(input.options);
|
|
74
|
+
return {
|
|
75
|
+
schemaVersion: AGENT_RUN_STATE_SCHEMA_VERSION,
|
|
76
|
+
agentId: agentId(input.agent),
|
|
77
|
+
definitionRevision: input.options.definitionRevision,
|
|
78
|
+
fingerprint: agentFingerprint(input.agent, input.options.definitionRevision),
|
|
79
|
+
runId: input.runId,
|
|
80
|
+
sessionId: input.sessionId,
|
|
81
|
+
...(input.leafId ? { leafId: input.leafId } : {}),
|
|
82
|
+
model: input.model,
|
|
83
|
+
status: input.status,
|
|
84
|
+
interruption: input.interruption,
|
|
85
|
+
input: input.messages,
|
|
86
|
+
pending: input.pending,
|
|
87
|
+
interruptBeforeTool: input.interruptBeforeTool,
|
|
88
|
+
counters: input.counters,
|
|
89
|
+
deadlineAt: input.deadlineAt,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function parseAgentRunState(value, version) {
|
|
93
|
+
if (!value || typeof value !== "object")
|
|
94
|
+
throw new AgentRunStateError("Agent run state must be an object");
|
|
95
|
+
const state = value;
|
|
96
|
+
if (state.schemaVersion !== AGENT_RUN_STATE_SCHEMA_VERSION)
|
|
97
|
+
throw new AgentRunStateError(`Unsupported agent run state schemaVersion ${String(state.schemaVersion)}`);
|
|
98
|
+
if (!state.agentId || !state.definitionRevision || !state.fingerprint || !state.runId || !state.sessionId || !state.model || !state.status || !state.counters || !state.deadlineAt) {
|
|
99
|
+
throw new AgentRunStateError("Malformed agent run state");
|
|
100
|
+
}
|
|
101
|
+
return boundState({ ...state, version }, DEFAULT_MAX_AGENT_RUN_STATE_BYTES);
|
|
102
|
+
}
|
|
103
|
+
function boundState(state, maxBytes) {
|
|
104
|
+
checkShape(state, 0);
|
|
105
|
+
let text;
|
|
106
|
+
try {
|
|
107
|
+
text = JSON.stringify(state);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new AgentRunStateError("Agent run state must be JSON serializable");
|
|
111
|
+
}
|
|
112
|
+
if (Buffer.byteLength(text) > maxBytes)
|
|
113
|
+
throw new AgentRunStateError(`Agent run state exceeds ${maxBytes} bytes`);
|
|
114
|
+
return JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
function checkShape(value, depth) {
|
|
117
|
+
if (depth > MAX_DEPTH)
|
|
118
|
+
throw new AgentRunStateError(`Agent run state exceeds depth ${MAX_DEPTH}`);
|
|
119
|
+
if (!value || typeof value !== "object")
|
|
120
|
+
return;
|
|
121
|
+
const entries = Array.isArray(value) ? value : Object.values(value);
|
|
122
|
+
if (!Array.isArray(value) && entries.length > MAX_PROPERTIES)
|
|
123
|
+
throw new AgentRunStateError(`Agent run state exceeds ${MAX_PROPERTIES} properties`);
|
|
124
|
+
for (const item of entries)
|
|
125
|
+
checkShape(item, depth + 1);
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=agent-run-state.js.map
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { Agent, AgentConfig, AgentSession, AgentSessionConfig } from "./contracts.js";
|
|
1
|
+
import type { Agent, AgentConfig, AgentRunResult, AgentRunResume, AgentRunResumeOptions, AgentRunRef, 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
|
+
/** Resume a persisted built-in run. A claimed/dispatched tool is never replayed automatically. */
|
|
7
|
+
export declare function resumeAgentRun(agent: Agent, ref: AgentRunRef, resume: AgentRunResume, options: AgentRunResumeOptions): Promise<AgentRunResult>;
|