@arnilo/prism 0.0.23 → 0.0.24
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 +19 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-run-state.js +6 -1
- package/dist/agents.js +7 -2
- package/dist/contracts.d.ts +157 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +4 -1
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +58 -21
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +338 -0
- package/dist/tools.d.ts +3 -1
- package/dist/tools.js +204 -9
- package/docs/0.1.0-readiness.md +9 -9
- package/docs/a2a.md +6 -2
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +39 -42
- package/docs/agent-events.md +5 -1
- package/docs/browser-automation.md +2 -0
- package/docs/coding-agent-tools.md +2 -0
- package/docs/database-persistence.md +2 -0
- package/docs/enterprise-postgres-state.md +5 -1
- package/docs/host-security.md +8 -1
- package/docs/index.md +11 -9
- package/docs/mcp-tools.md +17 -2
- package/docs/migration.md +22 -0
- package/docs/performance.md +24 -0
- package/docs/postgres-persistence.md +5 -2
- package/docs/public-contracts.md +2 -0
- package/docs/release-and-install.md +52 -693
- package/docs/server.md +9 -6
- package/docs/sqlite-persistence.md +10 -2
- package/docs/supervisors.md +2 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +4 -0
- package/docs/work-tools.md +4 -0
- package/package.json +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.24] - 2026-08-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Durable `AgentEventSource` with append/page/subscribe/resume and PostgreSQL LISTEN/NOTIFY wakeups (schema v6 streams + v7 retention index).
|
|
7
|
+
- Recoverable `ToolEffectStore` claim/CAS lifecycle; enterprise PostgreSQL `toolEffects`; coding/browser/work/MCP/supervisor effect classification.
|
|
8
|
+
- AG-UI 0.0.57 full input/event/interrupt compatibility plus MCP Apps and remote A2A adapters.
|
|
9
|
+
- Protected Phase 7 process conformance and `benchmark-0.0.24.json` evidence.
|
|
10
|
+
- Example `examples/distributed-events-and-tool-effects.ts`; docs `docs/tool-effects.md`.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Work mutations require core-derived idempotency keys; ambiguous outcomes stay `unknown` (not exactly-once).
|
|
14
|
+
- Publishable graph remains **47** manifests at **0.0.24**.
|
|
15
|
+
|
|
16
|
+
### Breaking (minor, pre-1.0)
|
|
17
|
+
- Hosts using approved work mutations must supply `effectStore` / core `idempotencyKey` (model keys ignored).
|
|
18
|
+
- Durable event reconnect uses `AgentEventSource` cursors / `Last-Event-ID`; sticky sessions are optional only.
|
|
19
|
+
|
|
20
|
+
See [docs/migration.md](docs/migration.md) for the 0.0.23 → 0.0.24 guide.
|
|
21
|
+
|
|
3
22
|
## [0.0.23] - 2026-08-03
|
|
4
23
|
|
|
5
24
|
### Added
|
|
@@ -17,7 +36,6 @@
|
|
|
17
36
|
|
|
18
37
|
See [docs/migration.md](docs/migration.md) for the 0.0.22 → 0.0.23 guide.
|
|
19
38
|
|
|
20
|
-
|
|
21
39
|
## [0.0.22] - 2026-07-31
|
|
22
40
|
|
|
23
41
|
### Added
|
|
@@ -45,7 +63,6 @@ See [docs/migration.md](docs/migration.md) for the full 0.0.21 → 0.0.22 notes.
|
|
|
45
63
|
|
|
46
64
|
See [docs/migration.md](docs/migration.md) for the full 0.0.20 → 0.0.21 notes.
|
|
47
65
|
|
|
48
|
-
|
|
49
66
|
## [0.0.20] - 2026-07-31
|
|
50
67
|
|
|
51
68
|
### Added
|
|
@@ -169,7 +186,6 @@ All notable changes to this project will be documented in this file.
|
|
|
169
186
|
- Versioned all 35 first-party manifests and exact internal ranges to `0.0.12`; `@arnilo/prism-all` includes AG-UI while `@arnilo/prism-code` and `@arnilo/prism-sdk` remain free of UI protocol dependencies.
|
|
170
187
|
- Added network-free interoperability/compaction evidence: `scripts/benchmark-0.0.12.mjs`.
|
|
171
188
|
|
|
172
|
-
|
|
173
189
|
## [0.0.11] - 2026-07-22
|
|
174
190
|
|
|
175
191
|
### Added
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentEventSource, AgentEventSourceOptions } from "./contracts.js";
|
|
2
|
+
export type AgentEventSourceErrorCode = "ERR_PRISM_AGENT_EVENT_SOURCE_INPUT" | "ERR_PRISM_AGENT_EVENT_SOURCE_CURSOR" | "ERR_PRISM_AGENT_EVENT_SOURCE_RETENTION" | "ERR_PRISM_AGENT_EVENT_SOURCE_OVERFLOW" | "ERR_PRISM_AGENT_EVENT_SOURCE_CLOSED";
|
|
3
|
+
export declare class AgentEventSourceError extends Error {
|
|
4
|
+
readonly code: AgentEventSourceErrorCode;
|
|
5
|
+
constructor(code: AgentEventSourceErrorCode, message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* In-process, non-production reference implementation. It has no cross-process
|
|
9
|
+
* wakeup or persistence; use a database-backed source for distributed delivery.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createMemoryAgentEventSource(options?: AgentEventSourceOptions): AgentEventSource;
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
export class AgentEventSourceError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(code, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "AgentEventSourceError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const DEFAULT_MAX_EVENT_BYTES = 64 * 1024;
|
|
11
|
+
const HARD_MAX_EVENT_BYTES = 1024 * 1024;
|
|
12
|
+
const DEFAULT_MAX_PAGE_SIZE = 100;
|
|
13
|
+
const HARD_MAX_PAGE_SIZE = 500;
|
|
14
|
+
const DEFAULT_MAX_CURSOR_BYTES = 4 * 1024;
|
|
15
|
+
const HARD_MAX_CURSOR_BYTES = 16 * 1024;
|
|
16
|
+
const DEFAULT_MAX_QUEUED_EVENTS = 128;
|
|
17
|
+
const HARD_MAX_QUEUED_EVENTS = 4096;
|
|
18
|
+
const DEFAULT_MAX_SUBSCRIBERS = 256;
|
|
19
|
+
const HARD_MAX_SUBSCRIBERS = 4096;
|
|
20
|
+
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
21
|
+
const HARD_POLL_INTERVAL_MS = 30_000;
|
|
22
|
+
const DEFAULT_RECONNECT_INITIAL_MS = 100;
|
|
23
|
+
const HARD_RECONNECT_INITIAL_MS = 5000;
|
|
24
|
+
const DEFAULT_RECONNECT_MAX_MS = 5000;
|
|
25
|
+
const HARD_RECONNECT_MAX_MS = 30_000;
|
|
26
|
+
const DEFAULT_MAX_RETAINED_EVENTS_PER_RUN = 10_000;
|
|
27
|
+
const HARD_MAX_RETAINED_EVENTS_PER_RUN = 100_000;
|
|
28
|
+
const DEFAULT_MAX_RETENTION_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
29
|
+
const HARD_MAX_RETENTION_AGE_MS = 365 * 24 * 60 * 60 * 1000;
|
|
30
|
+
const DEFAULT_CLEANUP_BATCH = 100;
|
|
31
|
+
const HARD_CLEANUP_BATCH = 500;
|
|
32
|
+
/**
|
|
33
|
+
* In-process, non-production reference implementation. It has no cross-process
|
|
34
|
+
* wakeup or persistence; use a database-backed source for distributed delivery.
|
|
35
|
+
*/
|
|
36
|
+
export function createMemoryAgentEventSource(options = {}) {
|
|
37
|
+
const limits = resolveOptions(options);
|
|
38
|
+
const streams = new Map();
|
|
39
|
+
const byId = new Map();
|
|
40
|
+
const subscribers = new Set();
|
|
41
|
+
const secret = randomBytes(32);
|
|
42
|
+
let closed = false;
|
|
43
|
+
const source = {
|
|
44
|
+
async append(input) {
|
|
45
|
+
assertOpen();
|
|
46
|
+
const record = normalizeRecord(input, limits.maxEventBytes);
|
|
47
|
+
const existing = byId.get(record.id);
|
|
48
|
+
if (existing) {
|
|
49
|
+
if (sameRecord(existing, record))
|
|
50
|
+
return clone(existing);
|
|
51
|
+
throw inputError();
|
|
52
|
+
}
|
|
53
|
+
const stream = getStream(record, true);
|
|
54
|
+
const sequence = record.sequence ?? stream.nextSequence;
|
|
55
|
+
if (!Number.isSafeInteger(sequence) || sequence < 1 || sequence !== stream.nextSequence)
|
|
56
|
+
throw inputError();
|
|
57
|
+
const stored = { ...record, sequence };
|
|
58
|
+
assertCursorFits(stored);
|
|
59
|
+
stream.records.set(stored.sequence, stored);
|
|
60
|
+
stream.nextSequence += 1;
|
|
61
|
+
byId.set(stored.id, stored);
|
|
62
|
+
while (stream.records.size > limits.maxRetainedEventsPerRun)
|
|
63
|
+
drop(stream, stream.records.values().next().value);
|
|
64
|
+
for (const subscriber of subscribers)
|
|
65
|
+
subscriber.notify();
|
|
66
|
+
return clone(stored);
|
|
67
|
+
},
|
|
68
|
+
async page(input) {
|
|
69
|
+
assertOpen();
|
|
70
|
+
throwIfAborted(input.signal);
|
|
71
|
+
const read = normalizeRead(input, limits.maxPageSize);
|
|
72
|
+
const stream = getStream({ ...read.ownership, sessionId: read.sessionId, runId: read.runId }, false);
|
|
73
|
+
const records = orderedRecords(stream);
|
|
74
|
+
const after = read.after === undefined ? undefined : decodeCursor(read.after, read, stream, records);
|
|
75
|
+
const start = after === undefined ? 0 : positionAfter(records, after, stream);
|
|
76
|
+
const selected = records.slice(start, start + read.limit);
|
|
77
|
+
const hasMore = start + selected.length < records.length;
|
|
78
|
+
return {
|
|
79
|
+
items: selected.map((record) => ({ record: clone(record), cursor: encodeCursor(record) })),
|
|
80
|
+
...(hasMore && selected.length > 0 ? { nextCursor: encodeCursor(selected.at(-1)) } : {}),
|
|
81
|
+
terminal: !hasMore && terminalAt(records, after),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
subscribe(input) {
|
|
85
|
+
return handoffAgentEvents((after) => source.page({ ...input, ...(after === undefined ? {} : { after }) }), () => createWake(input.signal), input.after, limits.maxRetainedEventsPerRun);
|
|
86
|
+
},
|
|
87
|
+
async cleanup(input) {
|
|
88
|
+
assertOpen();
|
|
89
|
+
throwIfAborted(input.signal);
|
|
90
|
+
const ownership = normalizeOwnership(input.ownership);
|
|
91
|
+
const before = parseTimestamp(input.before, "input");
|
|
92
|
+
const limit = bounded(input.limit, DEFAULT_CLEANUP_BATCH, HARD_CLEANUP_BATCH, "cleanup limit");
|
|
93
|
+
const matches = [];
|
|
94
|
+
for (const stream of streams.values()) {
|
|
95
|
+
if (!sameOwnership(ownership, stream.ownership))
|
|
96
|
+
continue;
|
|
97
|
+
for (const record of stream.records.values()) {
|
|
98
|
+
if (Date.parse(record.timestamp) < before)
|
|
99
|
+
matches.push({ stream, record });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
matches.sort((a, b) => a.record.timestamp.localeCompare(b.record.timestamp) ||
|
|
103
|
+
a.record.sequence - b.record.sequence ||
|
|
104
|
+
a.record.id.localeCompare(b.record.id));
|
|
105
|
+
for (const { stream, record } of matches.slice(0, limit))
|
|
106
|
+
drop(stream, record);
|
|
107
|
+
return { deleted: Math.min(matches.length, limit) };
|
|
108
|
+
},
|
|
109
|
+
close() {
|
|
110
|
+
if (closed)
|
|
111
|
+
return;
|
|
112
|
+
closed = true;
|
|
113
|
+
for (const subscriber of subscribers)
|
|
114
|
+
subscriber.fail(closedError());
|
|
115
|
+
subscribers.clear();
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
return source;
|
|
119
|
+
function getStream(input, create) {
|
|
120
|
+
const ownership = normalizeOwnership(input);
|
|
121
|
+
const sessionId = requiredText(input.sessionId);
|
|
122
|
+
const runId = requiredText(input.runId);
|
|
123
|
+
const key = streamKey(ownership, sessionId, runId);
|
|
124
|
+
let stream = streams.get(key);
|
|
125
|
+
if (!stream && create) {
|
|
126
|
+
stream = { ownership, sessionId, runId, records: new Map(), nextSequence: 1 };
|
|
127
|
+
streams.set(key, stream);
|
|
128
|
+
}
|
|
129
|
+
return stream;
|
|
130
|
+
}
|
|
131
|
+
function encodeCursor(record) {
|
|
132
|
+
const payload = {
|
|
133
|
+
v: 1,
|
|
134
|
+
tenantId: record.tenantId,
|
|
135
|
+
...(record.accountId === undefined ? {} : { accountId: record.accountId }),
|
|
136
|
+
...(record.userId === undefined ? {} : { userId: record.userId }),
|
|
137
|
+
sessionId: record.sessionId,
|
|
138
|
+
runId: record.runId,
|
|
139
|
+
sequence: record.sequence,
|
|
140
|
+
id: record.id,
|
|
141
|
+
};
|
|
142
|
+
const body = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
143
|
+
const signature = createHmac("sha256", secret).update(body).digest("base64url");
|
|
144
|
+
const encoded = `${body}.${signature}`;
|
|
145
|
+
if (Buffer.byteLength(encoded, "utf8") > limits.maxCursorBytes)
|
|
146
|
+
throw inputError();
|
|
147
|
+
return encoded;
|
|
148
|
+
}
|
|
149
|
+
function decodeCursor(encoded, input, stream, records) {
|
|
150
|
+
if (typeof encoded !== "string" || encoded.length === 0 || Buffer.byteLength(encoded, "utf8") > limits.maxCursorBytes) {
|
|
151
|
+
throw cursorError();
|
|
152
|
+
}
|
|
153
|
+
const [body, signature, extra] = encoded.split(".");
|
|
154
|
+
if (!body || !signature || extra !== undefined)
|
|
155
|
+
throw cursorError();
|
|
156
|
+
const expected = createHmac("sha256", secret).update(body).digest();
|
|
157
|
+
let actual;
|
|
158
|
+
try {
|
|
159
|
+
actual = Buffer.from(signature, "base64url");
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
throw cursorError();
|
|
163
|
+
}
|
|
164
|
+
if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
|
|
165
|
+
throw cursorError();
|
|
166
|
+
let value;
|
|
167
|
+
try {
|
|
168
|
+
value = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
throw cursorError();
|
|
172
|
+
}
|
|
173
|
+
if (!validCursor(value))
|
|
174
|
+
throw cursorError();
|
|
175
|
+
const cursor = value;
|
|
176
|
+
if (!sameOwnership(input.ownership, cursor) || cursor.sessionId !== input.sessionId || cursor.runId !== input.runId) {
|
|
177
|
+
throw cursorError();
|
|
178
|
+
}
|
|
179
|
+
if (!stream || positionOf(records, cursor) < 0)
|
|
180
|
+
throw retentionError();
|
|
181
|
+
return cursor;
|
|
182
|
+
}
|
|
183
|
+
function assertCursorFits(record) {
|
|
184
|
+
void encodeCursor(record);
|
|
185
|
+
}
|
|
186
|
+
function createWake(signal) {
|
|
187
|
+
throwIfAborted(signal);
|
|
188
|
+
if (subscribers.size >= limits.maxSubscribers)
|
|
189
|
+
throw overflowError();
|
|
190
|
+
const subscription = new WakeSubscription(limits.maxQueuedEvents, signal, () => subscribers.delete(subscription));
|
|
191
|
+
subscribers.add(subscription);
|
|
192
|
+
return subscription;
|
|
193
|
+
}
|
|
194
|
+
function drop(stream, record) {
|
|
195
|
+
if (stream.records.get(record.sequence) === record)
|
|
196
|
+
stream.records.delete(record.sequence);
|
|
197
|
+
if (byId.get(record.id) === record)
|
|
198
|
+
byId.delete(record.id);
|
|
199
|
+
}
|
|
200
|
+
function assertOpen() {
|
|
201
|
+
if (closed)
|
|
202
|
+
throw closedError();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** Shared replay/live loop. Its live iterator is started before every page, closing the query-to-wait race. */
|
|
206
|
+
function handoffAgentEvents(page, createWake, initialAfter, maxSeen) {
|
|
207
|
+
return {
|
|
208
|
+
[Symbol.asyncIterator]() {
|
|
209
|
+
const wake = createWake();
|
|
210
|
+
const iterator = run(wake);
|
|
211
|
+
return {
|
|
212
|
+
next: () => iterator.next(),
|
|
213
|
+
async return() {
|
|
214
|
+
await wake.return?.();
|
|
215
|
+
return (await iterator.return?.(undefined)) ?? { value: undefined, done: true };
|
|
216
|
+
},
|
|
217
|
+
async throw(error) {
|
|
218
|
+
await wake.return?.();
|
|
219
|
+
if (iterator.throw)
|
|
220
|
+
return iterator.throw(error);
|
|
221
|
+
throw error;
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
async function* run(wake) {
|
|
227
|
+
const seen = new Set();
|
|
228
|
+
let after = initialAfter;
|
|
229
|
+
let waiting = nextWake(wake); // Register live delivery before replaying durable rows.
|
|
230
|
+
try {
|
|
231
|
+
while (true) {
|
|
232
|
+
const replay = await page(after);
|
|
233
|
+
for (const item of replay.items) {
|
|
234
|
+
after = item.cursor;
|
|
235
|
+
if (seen.has(item.record.id))
|
|
236
|
+
continue;
|
|
237
|
+
seen.add(item.record.id);
|
|
238
|
+
if (seen.size > maxSeen)
|
|
239
|
+
seen.delete(seen.values().next().value);
|
|
240
|
+
yield item;
|
|
241
|
+
}
|
|
242
|
+
if (replay.terminal)
|
|
243
|
+
return;
|
|
244
|
+
const next = await waiting;
|
|
245
|
+
if (next.done)
|
|
246
|
+
return;
|
|
247
|
+
waiting = nextWake(wake);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
await wake.return?.();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function nextWake(wake) {
|
|
256
|
+
const next = wake.next();
|
|
257
|
+
// A slow subscriber can overflow while replay items are yielded. Keep the
|
|
258
|
+
// rejection observed until the generator reaches its next wait.
|
|
259
|
+
void next.catch(() => undefined);
|
|
260
|
+
return next;
|
|
261
|
+
}
|
|
262
|
+
class WakeSubscription {
|
|
263
|
+
maxQueuedEvents;
|
|
264
|
+
onFinish;
|
|
265
|
+
queued = 0;
|
|
266
|
+
waiting;
|
|
267
|
+
failure;
|
|
268
|
+
done = false;
|
|
269
|
+
abort;
|
|
270
|
+
constructor(maxQueuedEvents, signal, onFinish) {
|
|
271
|
+
this.maxQueuedEvents = maxQueuedEvents;
|
|
272
|
+
this.onFinish = onFinish;
|
|
273
|
+
this.abort = () => this.fail(signal?.reason ?? new DOMException("Aborted", "AbortError"));
|
|
274
|
+
if (signal?.aborted)
|
|
275
|
+
this.abort();
|
|
276
|
+
else
|
|
277
|
+
signal?.addEventListener("abort", this.abort, { once: true });
|
|
278
|
+
}
|
|
279
|
+
notify() {
|
|
280
|
+
if (this.done)
|
|
281
|
+
return;
|
|
282
|
+
if (this.waiting) {
|
|
283
|
+
const { resolve } = this.waiting;
|
|
284
|
+
this.waiting = undefined;
|
|
285
|
+
resolve({ value: undefined, done: false });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (this.queued >= this.maxQueuedEvents) {
|
|
289
|
+
this.fail(overflowError());
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.queued += 1;
|
|
293
|
+
}
|
|
294
|
+
fail(error) {
|
|
295
|
+
if (this.done)
|
|
296
|
+
return;
|
|
297
|
+
this.failure = error;
|
|
298
|
+
this.finish();
|
|
299
|
+
}
|
|
300
|
+
async next() {
|
|
301
|
+
if (this.failure !== undefined)
|
|
302
|
+
throw this.failure;
|
|
303
|
+
if (this.done)
|
|
304
|
+
return { value: undefined, done: true };
|
|
305
|
+
if (this.queued > 0) {
|
|
306
|
+
this.queued -= 1;
|
|
307
|
+
return { value: undefined, done: false };
|
|
308
|
+
}
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
this.waiting = { resolve, reject };
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
async return() {
|
|
314
|
+
this.finish();
|
|
315
|
+
return { value: undefined, done: true };
|
|
316
|
+
}
|
|
317
|
+
finish() {
|
|
318
|
+
if (this.done)
|
|
319
|
+
return;
|
|
320
|
+
this.done = true;
|
|
321
|
+
this.onFinish();
|
|
322
|
+
const waiting = this.waiting;
|
|
323
|
+
this.waiting = undefined;
|
|
324
|
+
if (this.failure !== undefined)
|
|
325
|
+
waiting?.reject(this.failure);
|
|
326
|
+
else
|
|
327
|
+
waiting?.resolve({ value: undefined, done: true });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function resolveOptions(options) {
|
|
331
|
+
const resolved = {
|
|
332
|
+
maxEventBytes: bounded(options.maxEventBytes, DEFAULT_MAX_EVENT_BYTES, HARD_MAX_EVENT_BYTES, "maxEventBytes"),
|
|
333
|
+
maxPageSize: bounded(options.maxPageSize, DEFAULT_MAX_PAGE_SIZE, HARD_MAX_PAGE_SIZE, "maxPageSize"),
|
|
334
|
+
maxCursorBytes: bounded(options.maxCursorBytes, DEFAULT_MAX_CURSOR_BYTES, HARD_MAX_CURSOR_BYTES, "maxCursorBytes"),
|
|
335
|
+
maxQueuedEvents: bounded(options.maxQueuedEvents, DEFAULT_MAX_QUEUED_EVENTS, HARD_MAX_QUEUED_EVENTS, "maxQueuedEvents"),
|
|
336
|
+
maxSubscribers: bounded(options.maxSubscribers, DEFAULT_MAX_SUBSCRIBERS, HARD_MAX_SUBSCRIBERS, "maxSubscribers"),
|
|
337
|
+
pollIntervalMs: bounded(options.pollIntervalMs, DEFAULT_POLL_INTERVAL_MS, HARD_POLL_INTERVAL_MS, "pollIntervalMs"),
|
|
338
|
+
reconnectInitialMs: bounded(options.reconnectInitialMs, DEFAULT_RECONNECT_INITIAL_MS, HARD_RECONNECT_INITIAL_MS, "reconnectInitialMs"),
|
|
339
|
+
reconnectMaxMs: bounded(options.reconnectMaxMs, DEFAULT_RECONNECT_MAX_MS, HARD_RECONNECT_MAX_MS, "reconnectMaxMs"),
|
|
340
|
+
maxRetainedEventsPerRun: bounded(options.maxRetainedEventsPerRun, DEFAULT_MAX_RETAINED_EVENTS_PER_RUN, HARD_MAX_RETAINED_EVENTS_PER_RUN, "maxRetainedEventsPerRun"),
|
|
341
|
+
maxRetentionAgeMs: bounded(options.maxRetentionAgeMs, DEFAULT_MAX_RETENTION_AGE_MS, HARD_MAX_RETENTION_AGE_MS, "maxRetentionAgeMs"),
|
|
342
|
+
};
|
|
343
|
+
if (resolved.reconnectInitialMs > resolved.reconnectMaxMs) {
|
|
344
|
+
throw new RangeError("reconnectInitialMs must not exceed reconnectMaxMs");
|
|
345
|
+
}
|
|
346
|
+
return resolved;
|
|
347
|
+
}
|
|
348
|
+
function normalizeRecord(input, maxEventBytes) {
|
|
349
|
+
if (!input || typeof input !== "object")
|
|
350
|
+
throw inputError();
|
|
351
|
+
const record = clone(input);
|
|
352
|
+
const ownership = normalizeOwnership(record);
|
|
353
|
+
const sessionId = requiredText(record.sessionId);
|
|
354
|
+
const runId = requiredText(record.runId);
|
|
355
|
+
const id = requiredText(record.id);
|
|
356
|
+
if (record.redacted !== true ||
|
|
357
|
+
record.type !== record.event?.type ||
|
|
358
|
+
record.event.sessionId !== sessionId ||
|
|
359
|
+
record.event.runId !== runId) {
|
|
360
|
+
throw inputError();
|
|
361
|
+
}
|
|
362
|
+
parseTimestamp(record.timestamp, "input");
|
|
363
|
+
if (record.sequence !== undefined && (!Number.isSafeInteger(record.sequence) || record.sequence < 1))
|
|
364
|
+
throw inputError();
|
|
365
|
+
if (Buffer.byteLength(JSON.stringify(record), "utf8") > maxEventBytes)
|
|
366
|
+
throw inputError();
|
|
367
|
+
return {
|
|
368
|
+
...record,
|
|
369
|
+
...ownership,
|
|
370
|
+
id,
|
|
371
|
+
sessionId,
|
|
372
|
+
runId,
|
|
373
|
+
...(record.sequence === undefined ? {} : { sequence: record.sequence }),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function normalizeRead(input, maxPageSize) {
|
|
377
|
+
if (!input || typeof input !== "object")
|
|
378
|
+
throw inputError();
|
|
379
|
+
return {
|
|
380
|
+
...input,
|
|
381
|
+
ownership: normalizeOwnership(input.ownership),
|
|
382
|
+
sessionId: requiredText(input.sessionId),
|
|
383
|
+
runId: requiredText(input.runId),
|
|
384
|
+
limit: bounded(input.limit, maxPageSize, maxPageSize, "page limit"),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function normalizeOwnership(input) {
|
|
388
|
+
if (!input || typeof input.tenantId !== "string" || input.tenantId.length === 0)
|
|
389
|
+
throw inputError();
|
|
390
|
+
if (input.accountId !== undefined && (typeof input.accountId !== "string" || input.accountId.length === 0))
|
|
391
|
+
throw inputError();
|
|
392
|
+
if (input.userId !== undefined && (typeof input.userId !== "string" || input.userId.length === 0))
|
|
393
|
+
throw inputError();
|
|
394
|
+
return {
|
|
395
|
+
tenantId: input.tenantId,
|
|
396
|
+
...(input.accountId === undefined ? {} : { accountId: input.accountId }),
|
|
397
|
+
...(input.userId === undefined ? {} : { userId: input.userId }),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function requiredText(value) {
|
|
401
|
+
if (typeof value !== "string" || value.length === 0)
|
|
402
|
+
throw inputError();
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
405
|
+
function parseTimestamp(value, kind) {
|
|
406
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) {
|
|
407
|
+
throw kind === "cursor" ? cursorError() : inputError();
|
|
408
|
+
}
|
|
409
|
+
const parsed = Date.parse(value);
|
|
410
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value)
|
|
411
|
+
throw kind === "cursor" ? cursorError() : inputError();
|
|
412
|
+
return parsed;
|
|
413
|
+
}
|
|
414
|
+
function bounded(value, fallback, hard, name) {
|
|
415
|
+
const resolved = value ?? fallback;
|
|
416
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > hard)
|
|
417
|
+
throw inputError(`${name} is outside its allowed range`);
|
|
418
|
+
return resolved;
|
|
419
|
+
}
|
|
420
|
+
function sameOwnership(a, b) {
|
|
421
|
+
return a.tenantId === b.tenantId && a.accountId === b.accountId && a.userId === b.userId;
|
|
422
|
+
}
|
|
423
|
+
function streamKey(ownership, sessionId, runId) {
|
|
424
|
+
return JSON.stringify([ownership.tenantId, ownership.accountId, ownership.userId, sessionId, runId]);
|
|
425
|
+
}
|
|
426
|
+
function orderedRecords(stream) {
|
|
427
|
+
return stream ? [...stream.records.values()] : [];
|
|
428
|
+
}
|
|
429
|
+
function positionOf(records, cursor) {
|
|
430
|
+
return records.findIndex((record) => record.sequence === cursor.sequence && record.id === cursor.id);
|
|
431
|
+
}
|
|
432
|
+
function positionAfter(records, cursor, stream) {
|
|
433
|
+
const position = positionOf(records, cursor);
|
|
434
|
+
if (position >= 0)
|
|
435
|
+
return position + 1;
|
|
436
|
+
if (!stream || cursor.sequence < stream.nextSequence)
|
|
437
|
+
throw retentionError();
|
|
438
|
+
throw cursorError();
|
|
439
|
+
}
|
|
440
|
+
function terminalAt(records, after) {
|
|
441
|
+
const last = records.at(-1);
|
|
442
|
+
if (!last || !isTerminal(last))
|
|
443
|
+
return false;
|
|
444
|
+
return after === undefined || after.sequence <= last.sequence;
|
|
445
|
+
}
|
|
446
|
+
function isTerminal(record) {
|
|
447
|
+
return (record.type === "agent_finished" || record.type === "agent_denied" || record.type === "run_limit_exceeded" || record.type === "error");
|
|
448
|
+
}
|
|
449
|
+
function sameRecord(existing, input) {
|
|
450
|
+
const { sequence: _existingSequence, ...existingWithoutSequence } = existing;
|
|
451
|
+
const { sequence: _inputSequence, ...inputWithoutSequence } = input;
|
|
452
|
+
return stableJson(existingWithoutSequence) === stableJson(inputWithoutSequence);
|
|
453
|
+
}
|
|
454
|
+
function stableJson(value) {
|
|
455
|
+
if (value === null || typeof value !== "object")
|
|
456
|
+
return JSON.stringify(value);
|
|
457
|
+
if (Array.isArray(value))
|
|
458
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
459
|
+
return `{${Object.entries(value)
|
|
460
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
461
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
|
|
462
|
+
.join(",")}}`;
|
|
463
|
+
}
|
|
464
|
+
function clone(value) {
|
|
465
|
+
try {
|
|
466
|
+
const encoded = JSON.stringify(value);
|
|
467
|
+
if (encoded === undefined)
|
|
468
|
+
throw new Error("not JSON serializable");
|
|
469
|
+
return JSON.parse(encoded);
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
throw inputError();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function validCursor(value) {
|
|
476
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
477
|
+
return false;
|
|
478
|
+
const cursor = value;
|
|
479
|
+
return (cursor.v === 1 &&
|
|
480
|
+
typeof cursor.tenantId === "string" &&
|
|
481
|
+
cursor.tenantId.length > 0 &&
|
|
482
|
+
(cursor.accountId === undefined || (typeof cursor.accountId === "string" && cursor.accountId.length > 0)) &&
|
|
483
|
+
(cursor.userId === undefined || (typeof cursor.userId === "string" && cursor.userId.length > 0)) &&
|
|
484
|
+
typeof cursor.sessionId === "string" &&
|
|
485
|
+
cursor.sessionId.length > 0 &&
|
|
486
|
+
typeof cursor.runId === "string" &&
|
|
487
|
+
cursor.runId.length > 0 &&
|
|
488
|
+
Number.isSafeInteger(cursor.sequence) &&
|
|
489
|
+
cursor.sequence > 0 &&
|
|
490
|
+
typeof cursor.id === "string" &&
|
|
491
|
+
cursor.id.length > 0);
|
|
492
|
+
}
|
|
493
|
+
function throwIfAborted(signal) {
|
|
494
|
+
if (signal?.aborted)
|
|
495
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
496
|
+
}
|
|
497
|
+
function inputError(message = "Invalid agent event source input") {
|
|
498
|
+
return new AgentEventSourceError("ERR_PRISM_AGENT_EVENT_SOURCE_INPUT", message);
|
|
499
|
+
}
|
|
500
|
+
function cursorError() {
|
|
501
|
+
return new AgentEventSourceError("ERR_PRISM_AGENT_EVENT_SOURCE_CURSOR", "Invalid agent event cursor");
|
|
502
|
+
}
|
|
503
|
+
function retentionError() {
|
|
504
|
+
return new AgentEventSourceError("ERR_PRISM_AGENT_EVENT_SOURCE_RETENTION", "Agent event history is unavailable");
|
|
505
|
+
}
|
|
506
|
+
function overflowError() {
|
|
507
|
+
return new AgentEventSourceError("ERR_PRISM_AGENT_EVENT_SOURCE_OVERFLOW", "Agent event subscription overflow");
|
|
508
|
+
}
|
|
509
|
+
function closedError() {
|
|
510
|
+
return new AgentEventSourceError("ERR_PRISM_AGENT_EVENT_SOURCE_CLOSED", "Agent event source is closed");
|
|
511
|
+
}
|
|
512
|
+
//# sourceMappingURL=agent-event-source.js.map
|
package/dist/agent-run-state.js
CHANGED
|
@@ -28,7 +28,12 @@ export function agentFingerprint(agent, revision) {
|
|
|
28
28
|
instructions: config.instructions ?? null,
|
|
29
29
|
systemPrompt,
|
|
30
30
|
skills: skills.map((skill) => ({ name: skill.name, instructions: skill.instructions, toolNames: skill.toolNames })),
|
|
31
|
-
tools: tools.map((tool) => ({
|
|
31
|
+
tools: tools.map((tool) => ({
|
|
32
|
+
name: tool.name,
|
|
33
|
+
parameters: tool.parameters,
|
|
34
|
+
exclusive: tool.exclusive,
|
|
35
|
+
effect: typeof tool.effect === "function" ? "classifier" : tool.effect,
|
|
36
|
+
})),
|
|
32
37
|
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
33
38
|
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
34
39
|
? config.loop.strategy
|
package/dist/agents.js
CHANGED
|
@@ -160,6 +160,7 @@ class RuntimeAgentSession {
|
|
|
160
160
|
activeRedactor;
|
|
161
161
|
activeProvider;
|
|
162
162
|
activeLedger;
|
|
163
|
+
activeEffectStore;
|
|
163
164
|
activeOwnership;
|
|
164
165
|
activeIdentity;
|
|
165
166
|
activeIdempotencyKey;
|
|
@@ -244,6 +245,7 @@ class RuntimeAgentSession {
|
|
|
244
245
|
(options.redactor !== undefined ||
|
|
245
246
|
options.ownership !== undefined ||
|
|
246
247
|
options.validate !== undefined ||
|
|
248
|
+
options.effectStore !== undefined ||
|
|
247
249
|
options.runState !== undefined)) {
|
|
248
250
|
throw new AgentRunStateError("Secure agent defaults cannot be replaced per run");
|
|
249
251
|
}
|
|
@@ -257,8 +259,8 @@ class RuntimeAgentSession {
|
|
|
257
259
|
}
|
|
258
260
|
if (durableOptions) {
|
|
259
261
|
validateRunStateOptions(durableOptions);
|
|
260
|
-
if (options.model || options.guardrails || options.loop)
|
|
261
|
-
throw new AgentRunStateError("Durable runs require model, guardrails, and
|
|
262
|
+
if (options.model || options.guardrails || options.loop || options.effectStore)
|
|
263
|
+
throw new AgentRunStateError("Durable runs require model, guardrails, loop, and effect store on AgentConfig for fingerprinting");
|
|
262
264
|
const configuredLoop = this.agent.config.loop;
|
|
263
265
|
if (configuredLoop && !isBuiltInLoop(configuredLoop))
|
|
264
266
|
throw new AgentRunStateError("Custom AgentLoopStrategy is not durable");
|
|
@@ -277,6 +279,7 @@ class RuntimeAgentSession {
|
|
|
277
279
|
this.pendingSoftInterrupt = false;
|
|
278
280
|
this.activeRedactor = options.redactor ?? this.agent.config.redactor;
|
|
279
281
|
this.activeLedger = options.runLedger ?? this.agent.config.runLedger;
|
|
282
|
+
this.activeEffectStore = options.effectStore ?? this.agent.config.effectStore;
|
|
280
283
|
this.activeOwnership = options.ownership ?? this.agent.config.ownership;
|
|
281
284
|
this.activeIdentity = resolveRunIdentity(options.identity, this.agent.config.identity, this.activeOwnership);
|
|
282
285
|
if (this.activeIdentity && !this.activeOwnership)
|
|
@@ -492,6 +495,7 @@ class RuntimeAgentSession {
|
|
|
492
495
|
trust: this.agent.config.trust,
|
|
493
496
|
redactor: this.activeRedactor,
|
|
494
497
|
ledger: this.activeLedger,
|
|
498
|
+
effectStore: this.activeEffectStore,
|
|
495
499
|
ownership: this.activeOwnership,
|
|
496
500
|
identity: this.activeIdentity,
|
|
497
501
|
guardrails: this.activeGuardrails,
|
|
@@ -644,6 +648,7 @@ class RuntimeAgentSession {
|
|
|
644
648
|
}
|
|
645
649
|
finally {
|
|
646
650
|
this.activeLedger = undefined;
|
|
651
|
+
this.activeEffectStore = undefined;
|
|
647
652
|
this.activeOwnership = undefined;
|
|
648
653
|
this.activeIdentity = undefined;
|
|
649
654
|
this.activeIdempotencyKey = undefined;
|