@arnilo/prism 0.0.4 → 0.0.5
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 +18 -0
- package/README.md +34 -10
- package/dist/agents.js +146 -19
- package/dist/cli-init.d.ts +41 -0
- package/dist/cli-init.js +390 -0
- package/dist/cli-runner.d.ts +7 -1
- package/dist/cli-runner.js +13 -1
- package/dist/content.d.ts +19 -0
- package/dist/content.js +197 -69
- package/dist/contracts.d.ts +94 -9
- package/dist/contracts.js +8 -0
- package/dist/feedback.d.ts +48 -0
- package/dist/feedback.js +230 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -3
- package/dist/providers/media.d.ts +3 -1
- package/dist/providers/media.js +11 -1
- package/dist/testing/feedback.d.ts +6 -0
- package/dist/testing/feedback.js +37 -0
- package/dist/testing/persistence-schema.d.ts +3 -3
- package/dist/testing/persistence-schema.js +32 -2
- package/dist/testing/run-ledger-conformance.js +7 -1
- package/docs/a2a.md +73 -0
- package/docs/agent-events.md +4 -6
- package/docs/agent-loops.md +1 -1
- package/docs/agent-session-runtime.md +14 -16
- package/docs/cli-rpc.md +35 -7
- package/docs/coding-agent-tools.md +2 -2
- package/docs/coding-security.md +7 -3
- package/docs/compaction-observational-memory.md +2 -0
- package/docs/context-and-skills.md +1 -0
- package/docs/credentials-and-redaction.md +2 -2
- package/docs/database-persistence.md +9 -6
- package/docs/evaluations.md +122 -0
- package/docs/extensions.md +2 -2
- package/docs/host-security.md +20 -3
- package/docs/index.md +29 -17
- package/docs/mcp-tools.md +49 -4
- package/docs/migration.md +33 -3
- package/docs/multimodal-content.md +14 -6
- package/docs/observability.md +14 -6
- package/docs/performance.md +209 -0
- package/docs/postgres-persistence.md +6 -4
- package/docs/provider-conformance.md +1 -0
- package/docs/provider-packages.md +2 -0
- package/docs/providers/ai-sdk.md +113 -0
- package/docs/public-contracts.md +6 -5
- package/docs/rag.md +113 -0
- package/docs/release-and-install.md +100 -77
- package/docs/review-coverage-2026-07-15.md +193 -0
- package/docs/runs-and-usage.md +41 -4
- package/docs/server.md +139 -0
- package/docs/settings-auth-trust-security.md +5 -5
- package/docs/sqlite-persistence.md +4 -3
- package/docs/supervisors.md +71 -0
- package/docs/workflow-orchestration-primitives.md +19 -3
- package/docs/workflows.md +97 -23
- package/docs/working-and-semantic-memory.md +169 -0
- package/package.json +12 -2
- package/templates/init/README.md.tmpl +28 -0
- package/templates/init/env.example.tmpl +1 -0
- package/templates/init/gitignore.tmpl +11 -0
- package/templates/init/optional/evals-example.ts.tmpl +17 -0
- package/templates/init/optional/workflows-example.ts.tmpl +27 -0
- package/templates/init/package.json.tmpl +22 -0
- package/templates/init/providers.json +76 -0
- package/templates/init/src/agent.ts.tmpl +10 -0
- package/templates/init/src/index.ts.tmpl +12 -0
- package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
- package/templates/init/tsconfig.json.tmpl +15 -0
package/dist/feedback.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
export const DEFAULT_MAX_FEEDBACK_COMMENT_BYTES = 4096;
|
|
2
|
+
export const HARD_MAX_FEEDBACK_COMMENT_BYTES = 16384;
|
|
3
|
+
export const DEFAULT_MAX_FEEDBACK_TAGS = 16;
|
|
4
|
+
export const HARD_MAX_FEEDBACK_TAGS = 64;
|
|
5
|
+
export const DEFAULT_MAX_FEEDBACK_LINKS = 16;
|
|
6
|
+
export const HARD_MAX_FEEDBACK_LINKS = 64;
|
|
7
|
+
export const DEFAULT_MAX_FEEDBACK_METADATA_BYTES = 16384;
|
|
8
|
+
export const HARD_MAX_FEEDBACK_METADATA_BYTES = 65536;
|
|
9
|
+
export const DEFAULT_FEEDBACK_PAGE_SIZE = 100;
|
|
10
|
+
export const HARD_FEEDBACK_PAGE_SIZE = 500;
|
|
11
|
+
export const MAX_FEEDBACK_TAG_LENGTH = 64;
|
|
12
|
+
export const MAX_FEEDBACK_ID_LENGTH = 128;
|
|
13
|
+
export class RunFeedbackError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
constructor(message, code = "ERR_PRISM_RUN_FEEDBACK") {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "RunFeedbackError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Validate, ownership-check, redact, bound, and freeze one feedback record. */
|
|
22
|
+
export async function prepareRunFeedback(input, options) {
|
|
23
|
+
input.signal?.throwIfAborted();
|
|
24
|
+
const ownership = requireOwnership(input);
|
|
25
|
+
const id = requireId(input.id, "feedback id");
|
|
26
|
+
const runId = requireId(input.runId, "runId");
|
|
27
|
+
const run = await options.resolveRun({ runId, ownership, signal: input.signal });
|
|
28
|
+
input.signal?.throwIfAborted();
|
|
29
|
+
if (!run || run.runId !== runId || !sameOwnership(ownership, run)) {
|
|
30
|
+
throw new RunFeedbackError("Run not found", "ERR_PRISM_RUN_FEEDBACK_RUN_NOT_FOUND");
|
|
31
|
+
}
|
|
32
|
+
if (input.sessionId !== undefined && input.sessionId !== run.sessionId) {
|
|
33
|
+
throw new RunFeedbackError("Run not found", "ERR_PRISM_RUN_FEEDBACK_RUN_NOT_FOUND");
|
|
34
|
+
}
|
|
35
|
+
if (input.traceId !== undefined && run.traceId !== undefined && input.traceId !== run.traceId) {
|
|
36
|
+
throw new RunFeedbackError("Run not found", "ERR_PRISM_RUN_FEEDBACK_RUN_NOT_FOUND");
|
|
37
|
+
}
|
|
38
|
+
if (input.comment !== undefined && !input.comment.trim()) {
|
|
39
|
+
throw new RunFeedbackError("comment must not be empty", "ERR_PRISM_RUN_FEEDBACK_VALIDATION");
|
|
40
|
+
}
|
|
41
|
+
if (input.rating !== undefined && (!Number.isFinite(input.rating) || input.rating < -1 || input.rating > 1)) {
|
|
42
|
+
throw new RunFeedbackError("rating must be a finite number in [-1, 1]", "ERR_PRISM_RUN_FEEDBACK_BOUNDS");
|
|
43
|
+
}
|
|
44
|
+
const maxCommentBytes = boundedLimit(options.maxCommentBytes, DEFAULT_MAX_FEEDBACK_COMMENT_BYTES, HARD_MAX_FEEDBACK_COMMENT_BYTES, "maxCommentBytes");
|
|
45
|
+
const maxTags = boundedLimit(options.maxTags, DEFAULT_MAX_FEEDBACK_TAGS, HARD_MAX_FEEDBACK_TAGS, "maxTags");
|
|
46
|
+
const maxLinks = boundedLimit(options.maxLinks, DEFAULT_MAX_FEEDBACK_LINKS, HARD_MAX_FEEDBACK_LINKS, "maxLinks");
|
|
47
|
+
const maxMetadataBytes = boundedLimit(options.maxMetadataBytes, DEFAULT_MAX_FEEDBACK_METADATA_BYTES, HARD_MAX_FEEDBACK_METADATA_BYTES, "maxMetadataBytes");
|
|
48
|
+
const tags = normalizeList(input.tags, maxTags, MAX_FEEDBACK_TAG_LENGTH, "tag");
|
|
49
|
+
const scorerIds = normalizeList(input.scorerIds, maxLinks, MAX_FEEDBACK_ID_LENGTH, "scorer id", true);
|
|
50
|
+
const evaluationIds = normalizeList(input.evaluationIds, maxLinks, MAX_FEEDBACK_ID_LENGTH, "evaluation id", true);
|
|
51
|
+
if (input.rating === undefined && input.comment === undefined && tags.length === 0 && scorerIds.length === 0 && evaluationIds.length === 0) {
|
|
52
|
+
throw new RunFeedbackError("feedback requires rating, comment, tag, scorer, or evaluation", "ERR_PRISM_RUN_FEEDBACK_EMPTY");
|
|
53
|
+
}
|
|
54
|
+
const redact = (value) => options.redactor ? options.redactor.redact(value) : value;
|
|
55
|
+
const comment = input.comment === undefined ? undefined : redact(input.comment);
|
|
56
|
+
const safeTags = Object.freeze(normalizeList(redact(tags), maxTags, MAX_FEEDBACK_TAG_LENGTH, "tag"));
|
|
57
|
+
const metadata = input.metadata === undefined ? undefined : redact(input.metadata);
|
|
58
|
+
assertBytes(comment, maxCommentBytes, "comment");
|
|
59
|
+
assertBytes(metadata, maxMetadataBytes, "metadata");
|
|
60
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
61
|
+
if (!Number.isFinite(Date.parse(createdAt)))
|
|
62
|
+
throw new RunFeedbackError("createdAt must be an ISO timestamp", "ERR_PRISM_RUN_FEEDBACK_VALIDATION");
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
id,
|
|
65
|
+
runId,
|
|
66
|
+
sessionId: run.sessionId,
|
|
67
|
+
traceId: input.traceId ?? run.traceId,
|
|
68
|
+
rating: input.rating,
|
|
69
|
+
comment,
|
|
70
|
+
tags: safeTags,
|
|
71
|
+
scorerIds: Object.freeze(scorerIds),
|
|
72
|
+
evaluationIds: Object.freeze(evaluationIds),
|
|
73
|
+
createdAt,
|
|
74
|
+
createdBy: input.createdBy === undefined ? undefined : requireId(input.createdBy, "createdBy"),
|
|
75
|
+
metadata: metadata === undefined ? undefined : cloneFrozenJsonObject(metadata),
|
|
76
|
+
...ownership,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** In-memory implementation with the same validation/ownership semantics as durable adapters. */
|
|
80
|
+
export function createMemoryRunFeedbackStore(options) {
|
|
81
|
+
const records = new Map();
|
|
82
|
+
for (const record of options.initial ?? [])
|
|
83
|
+
records.set(record.id, freezeRecord(record));
|
|
84
|
+
const maxPageSize = boundedLimit(options.maxPageSize, DEFAULT_FEEDBACK_PAGE_SIZE, HARD_FEEDBACK_PAGE_SIZE, "maxPageSize");
|
|
85
|
+
return {
|
|
86
|
+
async append(input) {
|
|
87
|
+
if (records.has(input.id))
|
|
88
|
+
throw new RunFeedbackError("Duplicate feedback id", "ERR_PRISM_RUN_FEEDBACK_DUPLICATE");
|
|
89
|
+
const record = await prepareRunFeedback(input, options);
|
|
90
|
+
records.set(record.id, record);
|
|
91
|
+
return record;
|
|
92
|
+
},
|
|
93
|
+
async query(query) {
|
|
94
|
+
query.signal?.throwIfAborted();
|
|
95
|
+
const ownership = requireOwnership(query);
|
|
96
|
+
const limit = pageLimit(query.limit, maxPageSize);
|
|
97
|
+
const order = query.order === "desc" ? -1 : 1;
|
|
98
|
+
const sorted = [...records.values()]
|
|
99
|
+
.filter((record) => sameOwnership(ownership, record) && matchesQuery(record, query))
|
|
100
|
+
.sort((a, b) => order * (a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id)));
|
|
101
|
+
const start = query.cursor ? sorted.findIndex((record) => record.id === query.cursor) + 1 : 0;
|
|
102
|
+
if (query.cursor && start === 0)
|
|
103
|
+
throw new RunFeedbackError("Unknown feedback cursor", "ERR_PRISM_RUN_FEEDBACK_CURSOR");
|
|
104
|
+
const items = sorted.slice(start, start + limit);
|
|
105
|
+
return { items, nextCursor: start + items.length < sorted.length ? items.at(-1)?.id : undefined, total: sorted.length };
|
|
106
|
+
},
|
|
107
|
+
async delete(input) {
|
|
108
|
+
input.signal?.throwIfAborted();
|
|
109
|
+
const ownership = requireOwnership(input);
|
|
110
|
+
const current = records.get(input.id);
|
|
111
|
+
if (!current || !sameOwnership(ownership, current))
|
|
112
|
+
return false;
|
|
113
|
+
return records.delete(input.id);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function requireRunFeedbackOwnership(input) {
|
|
118
|
+
return requireOwnership(input);
|
|
119
|
+
}
|
|
120
|
+
export function runFeedbackPageLimit(limit, maximum = HARD_FEEDBACK_PAGE_SIZE) {
|
|
121
|
+
return pageLimit(limit, maximum);
|
|
122
|
+
}
|
|
123
|
+
function requireOwnership(input) {
|
|
124
|
+
if (!input.tenantId?.trim()
|
|
125
|
+
|| (input.accountId !== undefined && !input.accountId.trim())
|
|
126
|
+
|| (input.userId !== undefined && !input.userId.trim())
|
|
127
|
+
|| (!input.accountId && !input.userId)) {
|
|
128
|
+
throw new RunFeedbackError("tenantId and non-empty accountId or userId are required", "ERR_PRISM_RUN_FEEDBACK_OWNERSHIP");
|
|
129
|
+
}
|
|
130
|
+
return { tenantId: input.tenantId, accountId: input.accountId, userId: input.userId };
|
|
131
|
+
}
|
|
132
|
+
function sameOwnership(expected, actual) {
|
|
133
|
+
return expected.tenantId === actual.tenantId
|
|
134
|
+
&& expected.accountId === actual.accountId
|
|
135
|
+
&& expected.userId === actual.userId;
|
|
136
|
+
}
|
|
137
|
+
function requireId(value, label) {
|
|
138
|
+
if (!value || value.length > MAX_FEEDBACK_ID_LENGTH || !/^[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value)) {
|
|
139
|
+
throw new RunFeedbackError(`${label} is invalid`, "ERR_PRISM_RUN_FEEDBACK_VALIDATION");
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
function normalizeList(values, maximum, maxLength, label, ids = false) {
|
|
144
|
+
if (!values)
|
|
145
|
+
return [];
|
|
146
|
+
if (values.length > maximum)
|
|
147
|
+
throw new RunFeedbackError(`Too many ${label}s`, "ERR_PRISM_RUN_FEEDBACK_BOUNDS");
|
|
148
|
+
const result = values.map((value) => {
|
|
149
|
+
if (typeof value !== "string" || !value.trim() || value.length > maxLength || /[\r\n]/.test(value)) {
|
|
150
|
+
throw new RunFeedbackError(`${label} is invalid`, "ERR_PRISM_RUN_FEEDBACK_VALIDATION");
|
|
151
|
+
}
|
|
152
|
+
return ids ? requireId(value, label) : value;
|
|
153
|
+
});
|
|
154
|
+
if (new Set(result).size !== result.length)
|
|
155
|
+
throw new RunFeedbackError(`Duplicate ${label}`, "ERR_PRISM_RUN_FEEDBACK_VALIDATION");
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
function boundedLimit(value, fallback, hard, label) {
|
|
159
|
+
const resolved = value ?? fallback;
|
|
160
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > hard) {
|
|
161
|
+
throw new RunFeedbackError(`${label} must be an integer in [1, ${hard}]`, "ERR_PRISM_RUN_FEEDBACK_BOUNDS");
|
|
162
|
+
}
|
|
163
|
+
return resolved;
|
|
164
|
+
}
|
|
165
|
+
function pageLimit(value, maximum) {
|
|
166
|
+
if (value === undefined)
|
|
167
|
+
return Math.min(DEFAULT_FEEDBACK_PAGE_SIZE, maximum);
|
|
168
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
169
|
+
throw new RunFeedbackError("limit must be a positive integer", "ERR_PRISM_RUN_FEEDBACK_BOUNDS");
|
|
170
|
+
return Math.min(value, maximum);
|
|
171
|
+
}
|
|
172
|
+
function assertBytes(value, maximum, label) {
|
|
173
|
+
if (value === undefined)
|
|
174
|
+
return;
|
|
175
|
+
let encoded;
|
|
176
|
+
try {
|
|
177
|
+
encoded = typeof value === "string" ? value : JSON.stringify(value);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
throw new RunFeedbackError(`${label} must be JSON serializable`, "ERR_PRISM_RUN_FEEDBACK_VALIDATION");
|
|
181
|
+
}
|
|
182
|
+
if (new TextEncoder().encode(encoded).byteLength > maximum) {
|
|
183
|
+
throw new RunFeedbackError(`${label} exceeds ${maximum} bytes`, "ERR_PRISM_RUN_FEEDBACK_BOUNDS");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function matchesQuery(record, query) {
|
|
187
|
+
if (query.runId !== undefined && record.runId !== query.runId)
|
|
188
|
+
return false;
|
|
189
|
+
if (query.sessionId !== undefined && record.sessionId !== query.sessionId)
|
|
190
|
+
return false;
|
|
191
|
+
if (query.traceId !== undefined && record.traceId !== query.traceId)
|
|
192
|
+
return false;
|
|
193
|
+
if (query.rating !== undefined && record.rating !== query.rating)
|
|
194
|
+
return false;
|
|
195
|
+
if (query.scorerId !== undefined && !record.scorerIds.includes(query.scorerId))
|
|
196
|
+
return false;
|
|
197
|
+
if (query.evaluationId !== undefined && !record.evaluationIds.includes(query.evaluationId))
|
|
198
|
+
return false;
|
|
199
|
+
if (query.tag !== undefined && !record.tags.includes(query.tag))
|
|
200
|
+
return false;
|
|
201
|
+
if (query.fromCreatedAt !== undefined && record.createdAt < query.fromCreatedAt)
|
|
202
|
+
return false;
|
|
203
|
+
if (query.toCreatedAt !== undefined && record.createdAt > query.toCreatedAt)
|
|
204
|
+
return false;
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
function freezeRecord(record) {
|
|
208
|
+
return Object.freeze({
|
|
209
|
+
...record,
|
|
210
|
+
tags: Object.freeze([...record.tags]),
|
|
211
|
+
scorerIds: Object.freeze([...record.scorerIds]),
|
|
212
|
+
evaluationIds: Object.freeze([...record.evaluationIds]),
|
|
213
|
+
metadata: record.metadata ? cloneFrozenJsonObject(record.metadata) : undefined,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function cloneFrozenJsonObject(value) {
|
|
217
|
+
const cloned = JSON.parse(JSON.stringify(value));
|
|
218
|
+
if (!cloned || typeof cloned !== "object" || Array.isArray(cloned))
|
|
219
|
+
throw new RunFeedbackError("metadata must be a JSON object");
|
|
220
|
+
return deepFreeze(cloned);
|
|
221
|
+
}
|
|
222
|
+
function deepFreeze(value) {
|
|
223
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
224
|
+
for (const child of Object.values(value))
|
|
225
|
+
deepFreeze(child);
|
|
226
|
+
Object.freeze(value);
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=feedback.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type * from "./contracts.js";
|
|
2
|
-
export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict } from "./contracts.js";
|
|
2
|
+
export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict, AgentRunError } from "./contracts.js";
|
|
3
3
|
export { createAgent, createAgentSession } from "./agents.js";
|
|
4
|
+
export { createMemoryRunFeedbackStore, prepareRunFeedback, requireRunFeedbackOwnership, runFeedbackPageLimit, RunFeedbackError, } from "./feedback.js";
|
|
5
|
+
export type { MemoryRunFeedbackStoreOptions, PrepareRunFeedbackOptions, RunFeedbackLimits, RunFeedbackRun, RunFeedbackRunResolver, } from "./feedback.js";
|
|
4
6
|
export { CHECKPOINT_CONFLICT_CODE, CheckpointConflictError, createMemoryCheckpointStore } from "./checkpoints.js";
|
|
5
7
|
export type { MemoryCheckpointStoreOptions } from "./checkpoints.js";
|
|
6
8
|
export { LEASE_CONFLICT_CODE, LeaseConflictError, createMemoryLeaseStore } from "./leases.js";
|
|
@@ -33,8 +35,8 @@ export type { ComposeSystemPromptOptions } from "./system-prompts.js";
|
|
|
33
35
|
export type { ModelRegistry, ModelRegistryOptions } from "./models.js";
|
|
34
36
|
export { definePrismManifest, parsePrismManifest } from "./manifests.js";
|
|
35
37
|
export type { ManifestContributionDeclaration, ManifestContributionKind, ManifestResourceDeclaration, PrismManifest } from "./manifests.js";
|
|
36
|
-
export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMessagesSupportModelCapabilities, assertModelSupportsContentBlocks, assertSsrfAllowedUrl, collectMessageContentBlocks, contentBlockInputModality, DEFAULT_MAX_AUDIO_DURATION_MS, DEFAULT_MAX_MEDIA_ITEM_BYTES, DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST, DEFAULT_MAX_MEDIA_REQUEST_BYTES, DEFAULT_MEDIA_FETCH_TIMEOUT_MS, loadBoundedBinaryResource, MediaContentError, MODEL_INPUT_CAPABILITIES, resolveMediaContentBlock, sniffMediaMimeType, UnsupportedModalityError, } from "./content.js";
|
|
37
|
-
export type { AudioContent, DocumentContent, FileContent, MediaContentBlock, MediaContentBounds, MediaMimePolicy, ModelInputCapability, ResolvedMediaContent, ResolveMediaContentOptions, SsrfPolicy, } from "./content.js";
|
|
38
|
+
export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMessagesSupportModelCapabilities, assertModelSupportsContentBlocks, assertSsrfAllowedUrl, collectMessageContentBlocks, contentBlockInputModality, DEFAULT_MAX_AUDIO_DURATION_MS, DEFAULT_MAX_MEDIA_ITEM_BYTES, DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST, DEFAULT_MAX_MEDIA_REQUEST_BYTES, DEFAULT_MEDIA_FETCH_TIMEOUT_MS, loadBoundedBinaryResource, MediaContentError, MODEL_INPUT_CAPABILITIES, resolveMediaContentBlock, resolveMediaContentBlocks, sniffMediaMimeType, UnsupportedModalityError, } from "./content.js";
|
|
39
|
+
export type { AudioContent, DocumentContent, FileContent, MediaContentBlock, MediaContentBounds, MediaHostAddress, MediaHostnameResolver, MediaMimePolicy, MediaUrlRequest, MediaUrlRequester, ModelInputCapability, ResolvedMediaContent, ResolveMediaContentOptions, SsrfPolicy, } from "./content.js";
|
|
38
40
|
export { loadBinaryResource, loadJsonResource, loadManifestResource, loadTextResource } from "./resources.js";
|
|
39
41
|
export type { LoadBinaryResourceOptions } from "./resources.js";
|
|
40
42
|
export { createChainedSettingsProvider, createStaticSettingsProvider } from "./settings.js";
|
|
@@ -65,5 +67,5 @@ export type { DispatchToolCallOptions, ToolArgumentValidationError, ToolArgument
|
|
|
65
67
|
export type { DuplicateRegistrationOptions, DuplicateRegistrationPolicy } from "./registry-options.js";
|
|
66
68
|
export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
|
|
67
69
|
export declare const name = "prism";
|
|
68
|
-
export declare const version = "0.0.
|
|
70
|
+
export declare const version = "0.0.5";
|
|
69
71
|
export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict } from "./contracts.js";
|
|
1
|
+
export { isSessionEntryKind, SESSION_APPEND_CONFLICT_CODE, SESSION_ENTRY_KINDS, SESSION_ENTRY_SCHEMA_VERSION, SessionAppendConflictError, isSessionAppendConflict, AgentRunError } from "./contracts.js";
|
|
2
2
|
export { createAgent, createAgentSession } from "./agents.js";
|
|
3
|
+
export { createMemoryRunFeedbackStore, prepareRunFeedback, requireRunFeedbackOwnership, runFeedbackPageLimit, RunFeedbackError, } from "./feedback.js";
|
|
3
4
|
export { CHECKPOINT_CONFLICT_CODE, CheckpointConflictError, createMemoryCheckpointStore } from "./checkpoints.js";
|
|
4
5
|
export { LEASE_CONFLICT_CODE, LeaseConflictError, createMemoryLeaseStore } from "./leases.js";
|
|
5
6
|
export { createEventMultiplexer } from "./event-multiplexer.js";
|
|
@@ -19,7 +20,7 @@ export { createProviderTurnMetadata, readProviderHttpStatus } from "./observabil
|
|
|
19
20
|
export { createProviderRequestPolicyChain, createSessionCachePolicy, mergeProviderRequestOptions } from "./provider-request-policy.js";
|
|
20
21
|
export { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
21
22
|
export { definePrismManifest, parsePrismManifest } from "./manifests.js";
|
|
22
|
-
export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMessagesSupportModelCapabilities, assertModelSupportsContentBlocks, assertSsrfAllowedUrl, collectMessageContentBlocks, contentBlockInputModality, DEFAULT_MAX_AUDIO_DURATION_MS, DEFAULT_MAX_MEDIA_ITEM_BYTES, DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST, DEFAULT_MAX_MEDIA_REQUEST_BYTES, DEFAULT_MEDIA_FETCH_TIMEOUT_MS, loadBoundedBinaryResource, MediaContentError, MODEL_INPUT_CAPABILITIES, resolveMediaContentBlock, sniffMediaMimeType, UnsupportedModalityError, } from "./content.js";
|
|
23
|
+
export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMessagesSupportModelCapabilities, assertModelSupportsContentBlocks, assertSsrfAllowedUrl, collectMessageContentBlocks, contentBlockInputModality, DEFAULT_MAX_AUDIO_DURATION_MS, DEFAULT_MAX_MEDIA_ITEM_BYTES, DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST, DEFAULT_MAX_MEDIA_REQUEST_BYTES, DEFAULT_MEDIA_FETCH_TIMEOUT_MS, loadBoundedBinaryResource, MediaContentError, MODEL_INPUT_CAPABILITIES, resolveMediaContentBlock, resolveMediaContentBlocks, sniffMediaMimeType, UnsupportedModalityError, } from "./content.js";
|
|
23
24
|
export { loadBinaryResource, loadJsonResource, loadManifestResource, loadTextResource } from "./resources.js";
|
|
24
25
|
export { createChainedSettingsProvider, createStaticSettingsProvider } from "./settings.js";
|
|
25
26
|
export { createMiddlewareRegistry } from "./middleware.js";
|
|
@@ -36,6 +37,6 @@ export { resolveInstructionInjectors, runInstructionInjectors } from "./instruct
|
|
|
36
37
|
export { createToolRegistry, dispatchToolCall, filterTools, createToolParameterValidator } from "./tools.js";
|
|
37
38
|
export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
|
|
38
39
|
export const name = "prism";
|
|
39
|
-
export const version = "0.0.
|
|
40
|
+
export const version = "0.0.5";
|
|
40
41
|
export const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
|
41
42
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AudioContent, ContentBlock, DocumentContent, FileContent, JsonObject, ModelCapabilities, ModelConfig } from "../contracts.js";
|
|
1
|
+
import type { AudioContent, ContentBlock, DocumentContent, FileContent, JsonObject, Message, ModelCapabilities, ModelConfig } from "../contracts.js";
|
|
2
2
|
import { type MediaContentBlock, type ModelInputCapability, type ResolveMediaContentOptions, type ResolvedMediaContent } from "../content.js";
|
|
3
3
|
/** Default upload-cache entry cap per provider media session. */
|
|
4
4
|
export declare const DEFAULT_PROVIDER_UPLOAD_CACHE_ENTRIES = 32;
|
|
@@ -38,5 +38,7 @@ export declare function serializePdfDocumentWireBlock(options: {
|
|
|
38
38
|
readonly title?: string;
|
|
39
39
|
}): JsonObject;
|
|
40
40
|
export declare function resolveProviderMediaBlock(block: AudioContent | FileContent | DocumentContent, options?: ResolveMediaContentOptions): Promise<ResolvedMediaContent>;
|
|
41
|
+
/** Resolve every media block once and enforce aggregate request bounds before provider I/O. */
|
|
42
|
+
export declare function resolveProviderMediaMessages(messages: readonly Message[], model: ModelConfig, options?: ResolveMediaContentOptions): Promise<ReadonlyMap<MediaContentBlock, ResolvedMediaContent>>;
|
|
41
43
|
export declare function defaultProviderFilename(block: MediaContentBlock, fallback: string): string;
|
|
42
44
|
export declare const DEFAULT_PROVIDER_MEDIA_ITEM_BYTES = 10000000;
|
package/dist/providers/media.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { contentBlockInputModality, DEFAULT_MAX_MEDIA_ITEM_BYTES, resolveMediaContentBlock, UnsupportedModalityError, } from "../content.js";
|
|
2
|
+
import { assertMessagesSupportModelCapabilities, contentBlockInputModality, DEFAULT_MAX_MEDIA_ITEM_BYTES, resolveMediaContentBlock, resolveMediaContentBlocks, UnsupportedModalityError, } from "../content.js";
|
|
3
3
|
/** Default upload-cache entry cap per provider media session. */
|
|
4
4
|
export const DEFAULT_PROVIDER_UPLOAD_CACHE_ENTRIES = 32;
|
|
5
5
|
/** Inline OpenAI file_data ceiling before preferring Files API upload. */
|
|
@@ -96,6 +96,16 @@ export function serializePdfDocumentWireBlock(options) {
|
|
|
96
96
|
export async function resolveProviderMediaBlock(block, options = {}) {
|
|
97
97
|
return resolveMediaContentBlock(block, options);
|
|
98
98
|
}
|
|
99
|
+
/** Resolve every media block once and enforce aggregate request bounds before provider I/O. */
|
|
100
|
+
export async function resolveProviderMediaMessages(messages, model, options = {}) {
|
|
101
|
+
assertMessagesSupportModelCapabilities(model, messages);
|
|
102
|
+
const blocks = messages.flatMap((message) => message.content.filter(isMediaContentBlock));
|
|
103
|
+
const resolved = await resolveMediaContentBlocks(blocks, options);
|
|
104
|
+
return new Map(blocks.map((block, index) => [block, resolved[index]]));
|
|
105
|
+
}
|
|
106
|
+
function isMediaContentBlock(block) {
|
|
107
|
+
return contentBlockInputModality(block) !== undefined;
|
|
108
|
+
}
|
|
99
109
|
export function defaultProviderFilename(block, fallback) {
|
|
100
110
|
if ("name" in block && block.name)
|
|
101
111
|
return block.name;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RunFeedbackStore } from "../contracts.js";
|
|
2
|
+
export interface RunFeedbackConformanceFactory {
|
|
3
|
+
(): RunFeedbackStore | Promise<RunFeedbackStore>;
|
|
4
|
+
}
|
|
5
|
+
/** Shared minimum behavior for memory and production feedback stores. */
|
|
6
|
+
export declare function runFeedbackConformance(factory: RunFeedbackConformanceFactory): Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Shared minimum behavior for memory and production feedback stores. */
|
|
2
|
+
export async function runFeedbackConformance(factory) {
|
|
3
|
+
const store = await factory();
|
|
4
|
+
const owner = { tenantId: "feedback-tenant", userId: "feedback-user" };
|
|
5
|
+
await store.append({
|
|
6
|
+
id: "feedback-1",
|
|
7
|
+
runId: "feedback-run-a",
|
|
8
|
+
rating: 1,
|
|
9
|
+
comment: "useful",
|
|
10
|
+
tags: ["reviewed"],
|
|
11
|
+
evaluationIds: ["eval-1"],
|
|
12
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
13
|
+
...owner,
|
|
14
|
+
});
|
|
15
|
+
await store.append({
|
|
16
|
+
id: "feedback-2",
|
|
17
|
+
runId: "feedback-run-a",
|
|
18
|
+
rating: 0,
|
|
19
|
+
createdAt: "2026-01-01T00:00:01.000Z",
|
|
20
|
+
...owner,
|
|
21
|
+
});
|
|
22
|
+
const first = await store.query({ ...owner, runId: "feedback-run-a", limit: 1 });
|
|
23
|
+
if (first.items.length !== 1 || !first.nextCursor)
|
|
24
|
+
throw new Error("feedback first page is invalid");
|
|
25
|
+
const second = await store.query({ ...owner, runId: "feedback-run-a", cursor: first.nextCursor, limit: 1 });
|
|
26
|
+
if (second.items.length !== 1 || second.items[0]?.id === first.items[0]?.id)
|
|
27
|
+
throw new Error("feedback cursor did not advance");
|
|
28
|
+
const linked = await store.query({ ...owner, evaluationId: "eval-1" });
|
|
29
|
+
if (linked.items.length !== 1 || linked.items[0]?.id !== "feedback-1")
|
|
30
|
+
throw new Error("feedback evaluation filter failed");
|
|
31
|
+
if (await store.delete({ id: "feedback-1", tenantId: "feedback-tenant", userId: "other" })) {
|
|
32
|
+
throw new Error("cross-owner feedback deletion succeeded");
|
|
33
|
+
}
|
|
34
|
+
if (!await store.delete({ id: "feedback-1", ...owner }))
|
|
35
|
+
throw new Error("owned feedback deletion failed");
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PersistencePage, SessionEntry, SessionEntryQuery } from "../contracts.js";
|
|
2
2
|
/** Current shared persistence schema version for production database adapters. */
|
|
3
|
-
export declare const PERSISTENCE_SCHEMA_VERSION =
|
|
4
|
-
export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_tool_calls" | "prism_usage" | "prism_retention_policies" | "prism_migrations";
|
|
5
|
-
export type PersistenceColumnType = "text" | "integer" | "boolean" | "json" | "timestamp";
|
|
3
|
+
export declare const PERSISTENCE_SCHEMA_VERSION = 3;
|
|
4
|
+
export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_tool_calls" | "prism_usage" | "prism_run_feedback" | "prism_retention_policies" | "prism_migrations";
|
|
5
|
+
export type PersistenceColumnType = "text" | "integer" | "number" | "boolean" | "json" | "timestamp";
|
|
6
6
|
export interface PersistenceColumnDefinition {
|
|
7
7
|
readonly name: string;
|
|
8
8
|
readonly type: PersistenceColumnType;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// this module defines the shared table/index/pagination/migration expectations
|
|
4
4
|
// adapter authors implement and test against before shipping dialect-specific DDL.
|
|
5
5
|
/** Current shared persistence schema version for production database adapters. */
|
|
6
|
-
export const PERSISTENCE_SCHEMA_VERSION =
|
|
6
|
+
export const PERSISTENCE_SCHEMA_VERSION = 3;
|
|
7
7
|
/** Guidance adapters must follow: values are bound parameters, never interpolated. */
|
|
8
8
|
export const PARAMETERIZED_QUERY_GUIDANCE = "Bind every user-supplied value (session ids, idempotency keys, tenant ids, timestamps, JSON payloads) as a query parameter. Quote/validate schema and table identifiers only; never interpolate untrusted strings into SQL text.";
|
|
9
9
|
const TENANT_COLUMNS = [
|
|
@@ -210,6 +210,9 @@ export function createPersistenceSchemaModel() {
|
|
|
210
210
|
{ name: "session_id", type: "text" },
|
|
211
211
|
{ name: "run_id", type: "text", nullable: true },
|
|
212
212
|
{ name: "entry_id", type: "text", nullable: true },
|
|
213
|
+
{ name: "scope", type: "text" },
|
|
214
|
+
{ name: "turn", type: "integer", nullable: true },
|
|
215
|
+
{ name: "attempt", type: "integer", nullable: true },
|
|
213
216
|
{ name: "usage", type: "json" },
|
|
214
217
|
{ name: "recorded_at", type: "timestamp" },
|
|
215
218
|
...TENANT_COLUMNS,
|
|
@@ -217,6 +220,26 @@ export function createPersistenceSchemaModel() {
|
|
|
217
220
|
],
|
|
218
221
|
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
219
222
|
},
|
|
223
|
+
{
|
|
224
|
+
name: "prism_run_feedback",
|
|
225
|
+
primaryKey: ["id"],
|
|
226
|
+
columns: [
|
|
227
|
+
{ name: "id", type: "text" },
|
|
228
|
+
{ name: "run_id", type: "text" },
|
|
229
|
+
{ name: "session_id", type: "text" },
|
|
230
|
+
{ name: "trace_id", type: "text", nullable: true },
|
|
231
|
+
{ name: "rating", type: "number", nullable: true },
|
|
232
|
+
{ name: "comment", type: "text", nullable: true },
|
|
233
|
+
{ name: "tags", type: "json" },
|
|
234
|
+
{ name: "scorer_ids", type: "json" },
|
|
235
|
+
{ name: "evaluation_ids", type: "json" },
|
|
236
|
+
{ name: "created_at", type: "timestamp" },
|
|
237
|
+
{ name: "created_by", type: "text", nullable: true },
|
|
238
|
+
...TENANT_COLUMNS,
|
|
239
|
+
{ name: "metadata", type: "json", nullable: true },
|
|
240
|
+
],
|
|
241
|
+
foreignKeys: [{ columns: ["run_id"], referencesTable: "prism_runs", referencesColumns: ["id"] }],
|
|
242
|
+
},
|
|
220
243
|
{
|
|
221
244
|
name: "prism_retention_policies",
|
|
222
245
|
primaryKey: ["id"],
|
|
@@ -268,6 +291,10 @@ export function createPersistenceSchemaModel() {
|
|
|
268
291
|
{ name: "prism_tool_calls_run_started_idx", table: "prism_tool_calls", columns: ["run_id", "started_at"], purpose: "run tool-call listing" },
|
|
269
292
|
{ name: "prism_usage_run_recorded_idx", table: "prism_usage", columns: ["run_id", "recorded_at", "id"], purpose: "run usage pagination" },
|
|
270
293
|
{ name: "prism_usage_session_recorded_idx", table: "prism_usage", columns: ["session_id", "recorded_at"], purpose: "usage aggregation" },
|
|
294
|
+
{ name: "prism_usage_session_scope_recorded_idx", table: "prism_usage", columns: ["session_id", "scope", "recorded_at"], purpose: "scope-safe usage aggregation" },
|
|
295
|
+
{ name: "prism_run_feedback_owner_created_idx", table: "prism_run_feedback", columns: ["tenant_id", "account_id", "user_id", "created_at", "id"], purpose: "ownership-scoped feedback pagination" },
|
|
296
|
+
{ name: "prism_run_feedback_run_created_idx", table: "prism_run_feedback", columns: ["run_id", "created_at", "id"], purpose: "run feedback lookup" },
|
|
297
|
+
{ name: "prism_run_feedback_trace_created_idx", table: "prism_run_feedback", columns: ["trace_id", "created_at", "id"], purpose: "trace feedback lookup" },
|
|
271
298
|
{ name: "prism_agent_definitions_name_version_idx", table: "prism_agent_definitions", columns: ["name", "version"], purpose: "definition lookup" },
|
|
272
299
|
{ name: "prism_migrations_name_version_idx", table: "prism_migrations", columns: ["name", "version"], unique: true, purpose: "applied-migration uniqueness" },
|
|
273
300
|
],
|
|
@@ -280,6 +307,8 @@ export function createPersistenceMigrationContract() {
|
|
|
280
307
|
appliedMigrationsTable: "prism_migrations",
|
|
281
308
|
steps: [
|
|
282
309
|
{ version: 1, name: "001_init", description: "Create core session, branch, entry, idempotency, run, ledger, and migration tables." },
|
|
310
|
+
{ version: 2, name: "002_usage_scope", description: "Distinguish provider-turn usage from aggregate run totals." },
|
|
311
|
+
{ version: 3, name: "003_run_feedback", description: "Add immutable ownership-scoped run/trace feedback and evaluation links." },
|
|
283
312
|
],
|
|
284
313
|
lockGuidance: "Acquire a dialect-specific migration lock before applying steps (PostgreSQL advisory lock; SQLite exclusive transaction). Only one process should migrate at a time.",
|
|
285
314
|
leastPrivilegeGuidance: "Run migrations with a DDL-capable role; use a separate least-privilege runtime role limited to INSERT/SELECT/UPDATE on adapter tables. Never grant migration credentials to the agent runtime.",
|
|
@@ -294,6 +323,7 @@ export function getPersistencePaginationCursors() {
|
|
|
294
323
|
{ table: "prism_agent_events", columns: ["session_id", "timestamp", "id"], supportsOrder: ["asc", "desc"], purpose: "session event stream" },
|
|
295
324
|
{ table: "prism_usage", columns: ["run_id", "recorded_at", "id"], supportsOrder: ["asc", "desc"], purpose: "run usage totals" },
|
|
296
325
|
{ table: "prism_tool_calls", columns: ["run_id", "started_at"], supportsOrder: ["asc", "desc"], purpose: "run tool-call listing" },
|
|
326
|
+
{ table: "prism_run_feedback", columns: ["tenant_id", "account_id", "user_id", "created_at", "id"], supportsOrder: ["asc", "desc"], purpose: "owned feedback listing" },
|
|
297
327
|
];
|
|
298
328
|
}
|
|
299
329
|
/** Build a tenant-scoped unique key column list for adapter DDL. */
|
|
@@ -326,7 +356,7 @@ export function assertPersistenceSchemaModel(model) {
|
|
|
326
356
|
throw new Error("Runs table must include tenant-scoped tenant_id");
|
|
327
357
|
}
|
|
328
358
|
const indexTables = new Set(model.indexes.map((index) => index.table));
|
|
329
|
-
for (const requiredIndex of ["prism_session_append_idempotency", "prism_session_entries", "prism_agent_events", "prism_runs"]) {
|
|
359
|
+
for (const requiredIndex of ["prism_session_append_idempotency", "prism_session_entries", "prism_agent_events", "prism_runs", "prism_run_feedback"]) {
|
|
330
360
|
if (!indexTables.has(requiredIndex)) {
|
|
331
361
|
throw new Error(`Persistence schema missing indexes for ${requiredIndex}`);
|
|
332
362
|
}
|
|
@@ -71,6 +71,9 @@ export async function assertRunLedgerConforms(fixture, options = {}) {
|
|
|
71
71
|
id: "usage-1",
|
|
72
72
|
sessionId,
|
|
73
73
|
runId,
|
|
74
|
+
scope: "provider_turn",
|
|
75
|
+
turn: 1,
|
|
76
|
+
attempt: 2,
|
|
74
77
|
usage: { inputTokens: 3, outputTokens: 5, totalTokens: 8 },
|
|
75
78
|
recordedAt: "2026-01-01T00:00:01.000Z",
|
|
76
79
|
...scope,
|
|
@@ -114,8 +117,11 @@ export async function assertRunLedgerConforms(fixture, options = {}) {
|
|
|
114
117
|
}
|
|
115
118
|
if (fixture.readUsage) {
|
|
116
119
|
const usageRows = await fixture.readUsage();
|
|
117
|
-
|
|
120
|
+
const storedUsage = usageRows.find((row) => row.id === "usage-1");
|
|
121
|
+
if (!storedUsage)
|
|
118
122
|
throw new Error("RunLedger must persist UsageRecord rows");
|
|
123
|
+
if (storedUsage.scope !== "provider_turn" || storedUsage.turn !== 1 || storedUsage.attempt !== 2) {
|
|
124
|
+
throw new Error("RunLedger must preserve UsageRecord scope, turn, and attempt");
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
if (options.exerciseTenantIsolation && fixture.readRuns) {
|
package/docs/a2a.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# A2A interoperability
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-supervisor` implements a bounded text-only subset of Agent2Agent (A2A) protocol 1.0: Agent Cards, JSON-RPC `SendMessage`, `SendStreamingMessage`, `GetExtendedAgentCard`, SSE task updates, ES256 JWS card signatures, and an explicit remote client.
|
|
6
|
+
|
|
7
|
+
## When to use it
|
|
8
|
+
|
|
9
|
+
Use it to expose one explicitly selected Prism agent at an A2A endpoint or call a known remote A2A agent. Do not use it as endpoint discovery, a generic proxy, credential forwarding, or a replacement for local workflows.
|
|
10
|
+
|
|
11
|
+
## Inputs / request
|
|
12
|
+
|
|
13
|
+
| API/field | Meaning |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `createA2AAgentCard(card)` | Validates/freeze a JSONRPC protocol-1.0 HTTPS text card. |
|
|
16
|
+
| `signA2AAgentCard(card, { privateKey, keyId, expiresAt })` | Adds detached-payload ES256 JWS signature using WebCrypto. |
|
|
17
|
+
| `verifyA2AAgentCard(card, { publicKey, keyId?, now?, maxAgeMs? })` | Pins ES256/key/expiry and verifies canonical unsigned card. |
|
|
18
|
+
| `createA2AHandler({ card, exposure, authorize })` | Web-standard card/JSON-RPC/SSE `Request` to `Response` handler. |
|
|
19
|
+
| `createA2AClient({ endpoint, allowedOrigins })` | Explicit HTTPS remote client with optional card verifier/auth callback. |
|
|
20
|
+
| `A2ALimits` | Request 64 KiB, response 1 MiB, event 64 KiB, stream 10 MiB/10k events, concurrency 16, timeout 120s, card 64 KiB defaults; finite hard caps apply. |
|
|
21
|
+
|
|
22
|
+
## Outputs / response / events
|
|
23
|
+
|
|
24
|
+
The handler serves `GET /.well-known/agent-card.json` and its configured POST endpoint. JSON-RPC returns `{ result: { task } }` or a bounded error. Streaming returns backpressure-driven SSE task envelopes. Client `send()` maps a terminal remote task to `AgentRunResult`; `stream()` yields validated/redacted text artifacts.
|
|
25
|
+
|
|
26
|
+
## Request/response example
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"role":"user","messageId":"m1","parts":[{"text":"Check sources"}]}}}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Implementation example
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { createA2AClient, createA2AHandler, verifyA2AAgentCard } from "@arnilo/prism-supervisor";
|
|
36
|
+
|
|
37
|
+
const handler = createA2AHandler({
|
|
38
|
+
card,
|
|
39
|
+
exposure: { sessionFactory: ({ ownership }) => agent.createSession({ metadata: ownership }) },
|
|
40
|
+
authorize: ({ request }) => authenticate(request),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const client = createA2AClient({
|
|
44
|
+
endpoint: "https://agent.example/a2a/v1",
|
|
45
|
+
allowedOrigins: ["https://agent.example"],
|
|
46
|
+
authorize: () => ({ authorization: `Bearer ${resolveOwnedToken()}` }),
|
|
47
|
+
verifyCard: (remoteCard) => verifyA2AAgentCard(remoteCard, { publicKey, keyId: "agent-key" }),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const result = await client.send("Check sources");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Extension and configuration notes
|
|
54
|
+
|
|
55
|
+
The package owns no listener or credential store. Mount the handler in a host server and resolve authentication/authorization on every request. Client auth executes only after body/card validation and serialization. Injectable `fetch` supports host transports/tests; redirects are disabled.
|
|
56
|
+
|
|
57
|
+
Only `text` parts are accepted. File/data parts, push notifications, task persistence/query/cancel, gRPC, HTTP+JSON binding, automatic JWK fetching, and endpoint discovery are intentionally absent.
|
|
58
|
+
|
|
59
|
+
## Security and performance notes
|
|
60
|
+
|
|
61
|
+
- Endpoints and card URLs must be HTTPS and exactly origin-allow-listed before fetch; `redirect: "error"` prevents redirect SSRF.
|
|
62
|
+
- Treat every remote card, error, task, status, artifact, and SSE frame as untrusted. Shape/count/byte/time limits apply before mapping.
|
|
63
|
+
- Card verification pins `alg=ES256`, optional key ID, issue/expiry, optional maximum age, and canonical unsigned-card payload. Hosts provision trusted public keys; remote `jku` is never fetched automatically.
|
|
64
|
+
- Card discovery is public; extended-card and invoke methods call host authorization. Use TLS, rate limits, and replay controls at the host edge.
|
|
65
|
+
- Credentials remain in the client auth callback or server authorizer and never enter cards, messages, events, or metrics.
|
|
66
|
+
- Offline conformance is authoritative. Live endpoints are optional operator smoke tests.
|
|
67
|
+
|
|
68
|
+
## Related APIs
|
|
69
|
+
|
|
70
|
+
- [Supervisor delegation](supervisors.md): local child boundary.
|
|
71
|
+
- [Web-standard server](server.md): non-A2A Prism routes.
|
|
72
|
+
- [Host security](host-security.md): authentication, SSRF, and untrusted-output policy.
|
|
73
|
+
- [Agent/session runtime](agent-session-runtime.md): mapped local execution/result.
|
package/docs/agent-events.md
CHANGED
|
@@ -8,7 +8,7 @@ Events are emitted by the runtime and by loops through `LoopContext.emit`, both
|
|
|
8
8
|
|
|
9
9
|
## When to use it
|
|
10
10
|
|
|
11
|
-
Subscribe via `session.subscribe()`
|
|
11
|
+
Subscribe via `session.stream()` for a single owned run, or `session.subscribe()` when a host needs a long-lived observer across runs: render streamed assistant text in a UI, react to tool execution, drive observability/telemetry, or audit artifact validation outcomes. Do not parse provider stream events directly for these — `AgentEvent` is the stable, normalized surface across providers and loops.
|
|
12
12
|
|
|
13
13
|
Do not use `AgentEvent` for durable replay (use a `SessionStore`) or for cross-session coordination (the broadcaster is per-session and live-only).
|
|
14
14
|
|
|
@@ -58,7 +58,7 @@ Agent / turn / message events:
|
|
|
58
58
|
| Variant | Fields |
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| `agent_started` | `sessionId`, `runId` |
|
|
61
|
-
| `agent_finished` | `sessionId`, `runId`, `usage?: Usage` |
|
|
61
|
+
| `agent_finished` | `sessionId`, `runId`, `usage?: Usage` (aggregate of all usage-bearing provider turns) |
|
|
62
62
|
| `turn_started` / `turn_finished` | `sessionId`, `runId`, `turn: number` |
|
|
63
63
|
| `message_started` / `message_finished` | `sessionId`, `runId`, `message: Message` |
|
|
64
64
|
| `message_delta` | `sessionId`, `runId`, `content: ContentBlock` (`tool_call_delta` fragments may appear here for live UI streaming; stored messages use final `tool_call` blocks) |
|
|
@@ -173,12 +173,10 @@ const session = createAgent({
|
|
|
173
173
|
provider: createMockProvider([providerTextDelta("ok"), providerDone()]),
|
|
174
174
|
}).createSession();
|
|
175
175
|
|
|
176
|
-
for await (const event of session.
|
|
176
|
+
for await (const event of session.stream("draft", { loop: { strategy: "generate-validate-revise", validator, maxRevisions: 3 } })) {
|
|
177
177
|
if (event.type === "artifact_finished") console.log("artifact ok", event.attempt);
|
|
178
178
|
if (event.type === "artifact_failed") console.log("artifact exhausted", event.attempt, event.result.errors);
|
|
179
179
|
}
|
|
180
|
-
|
|
181
|
-
await session.run("draft", { loop: { strategy: "generate-validate-revise", validator, maxRevisions: 3 } });
|
|
182
180
|
```
|
|
183
181
|
|
|
184
182
|
## Extension and configuration notes
|
|
@@ -199,7 +197,7 @@ await session.run("draft", { loop: { strategy: "generate-validate-revise", valid
|
|
|
199
197
|
- Runtime events contain messages/content only; do not put secrets in prompts, metadata, provider events, session entries, tool results, or artifact validation payloads.
|
|
200
198
|
|
|
201
199
|
## Related APIs
|
|
202
|
-
- [Agent/session runtime](agent-session-runtime.md): `session.subscribe()
|
|
200
|
+
- [Agent/session runtime](agent-session-runtime.md): `session.stream()`, `session.subscribe()`, and the live event broadcaster.
|
|
203
201
|
- [Agent loops](agent-loops.md): `singleShotLoop` and `generateValidateReviseLoop` emit the artifact events.
|
|
204
202
|
- [Structured output](structured-output.md): `ArtifactValidation` shape threaded through parser/validator/repairer.
|
|
205
203
|
- [Public contracts](public-contracts.md): full `AgentEvent` union and `ArtifactValidation` contract.
|
package/docs/agent-loops.md
CHANGED
|
@@ -108,7 +108,7 @@ Host callback contracts (all generic over host `T`):
|
|
|
108
108
|
|
|
109
109
|
## Outputs / response / events
|
|
110
110
|
|
|
111
|
-
`AgentLoopStrategy.run(ctx)` returns `Promise<Usage | undefined>`
|
|
111
|
+
`AgentLoopStrategy.run(ctx)` returns `Promise<Usage | undefined>` as a fallback for custom loops. Core runtime independently accumulates every usage-bearing provider turn in O(turns), persists scoped turn/run rows, and emits `agent_finished` with the aggregate.
|
|
112
112
|
|
|
113
113
|
Events during a loop run are the existing `AgentEvent`s (`turn_started`, `message_started`, `message_delta`, `message_finished`, `turn_finished`, tool-execution events when the loop dispatches tools, `error` on real failures). Both built-in loops emit `turn_started` before each provider turn, `message_finished` for every assistant draft, and `turn_finished` after the assistant draft is appended. First-turn input is appended to live history once, matching the already-persisted user message.
|
|
114
114
|
|