@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/content.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
2
|
+
import { request as httpRequest } from "node:http";
|
|
3
|
+
import { request as httpsRequest } from "node:https";
|
|
4
|
+
import { isIP } from "node:net";
|
|
1
5
|
import { assertPermission } from "./security.js";
|
|
2
6
|
/** Known model input capability tags for `ModelCapabilities.input`. */
|
|
3
7
|
export const MODEL_INPUT_CAPABILITIES = ["text", "image", "audio", "file", "document"];
|
|
@@ -99,9 +103,9 @@ export function assertSsrfAllowedUrl(url, policy = {}) {
|
|
|
99
103
|
if (parsed.username || parsed.password) {
|
|
100
104
|
throw new MediaContentError("ssrf_denied", "Media URL must not embed credentials");
|
|
101
105
|
}
|
|
102
|
-
const hostname = parsed.hostname
|
|
106
|
+
const hostname = normalizeHostname(parsed.hostname);
|
|
103
107
|
if (policy.allowedHostnames?.length) {
|
|
104
|
-
if (!policy.allowedHostnames.some((allowed) => hostname === allowed
|
|
108
|
+
if (!policy.allowedHostnames.some((allowed) => hostname === normalizeHostname(allowed))) {
|
|
105
109
|
throw new MediaContentError("ssrf_denied", `Media URL host ${hostname} is not allow-listed`);
|
|
106
110
|
}
|
|
107
111
|
return;
|
|
@@ -158,10 +162,28 @@ export function assertDeclaredMediaTypeMatches(declaredMediaType, bytes, policy
|
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
export async function resolveMediaContentBlock(block, options = {}) {
|
|
165
|
+
return (await resolveMediaContentBlocks([block], options))[0];
|
|
166
|
+
}
|
|
167
|
+
/** Resolve and validate one complete media request before provider serialization/upload. */
|
|
168
|
+
export async function resolveMediaContentBlocks(blocks, options = {}) {
|
|
169
|
+
const bounds = options.bounds ?? {};
|
|
170
|
+
const maxRequestBytes = bounds.maxRequestBytes ?? DEFAULT_MAX_MEDIA_REQUEST_BYTES;
|
|
171
|
+
assertMediaBlocksWithinBounds(blocks, bounds);
|
|
172
|
+
const resolved = [];
|
|
173
|
+
let requestBytes = 0;
|
|
174
|
+
for (const block of blocks) {
|
|
175
|
+
const item = await resolveMediaBlock(block, options);
|
|
176
|
+
requestBytes += item.bytes.byteLength;
|
|
177
|
+
if (requestBytes > maxRequestBytes) {
|
|
178
|
+
throw new MediaContentError("request_too_large", `Media request budget exceeded ${maxRequestBytes} bytes`);
|
|
179
|
+
}
|
|
180
|
+
resolved.push(item);
|
|
181
|
+
}
|
|
182
|
+
return resolved;
|
|
183
|
+
}
|
|
184
|
+
async function resolveMediaBlock(block, options) {
|
|
161
185
|
const bounds = options.bounds ?? {};
|
|
162
186
|
const maxItemBytes = bounds.maxItemBytes ?? DEFAULT_MAX_MEDIA_ITEM_BYTES;
|
|
163
|
-
const maxAudioDurationMs = bounds.maxAudioDurationMs ?? DEFAULT_MAX_AUDIO_DURATION_MS;
|
|
164
|
-
assertMediaBlocksWithinBounds([block], bounds);
|
|
165
187
|
const source = mediaSourceKind(block);
|
|
166
188
|
let bytes;
|
|
167
189
|
let mediaType = declaredMediaType(block);
|
|
@@ -180,6 +202,8 @@ export async function resolveMediaContentBlock(block, options = {}) {
|
|
|
180
202
|
timeoutMs: bounds.fetchTimeoutMs ?? DEFAULT_MEDIA_FETCH_TIMEOUT_MS,
|
|
181
203
|
ssrf: options.ssrf,
|
|
182
204
|
fetch: options.fetch,
|
|
205
|
+
resolveHostname: options.resolveHostname,
|
|
206
|
+
requestUrl: options.requestUrl,
|
|
183
207
|
signal: options.signal,
|
|
184
208
|
});
|
|
185
209
|
}
|
|
@@ -189,9 +213,6 @@ export async function resolveMediaContentBlock(block, options = {}) {
|
|
|
189
213
|
mediaType = sniffed;
|
|
190
214
|
}
|
|
191
215
|
const durationMs = block.type === "audio" ? block.durationMs : undefined;
|
|
192
|
-
if (durationMs !== undefined && durationMs > maxAudioDurationMs) {
|
|
193
|
-
throw new MediaContentError("audio_too_long", `Audio duration ${durationMs}ms exceeded ${maxAudioDurationMs}ms`);
|
|
194
|
-
}
|
|
195
216
|
return {
|
|
196
217
|
mediaType,
|
|
197
218
|
bytes,
|
|
@@ -283,54 +304,28 @@ function decodeBase64Bounded(data, maxBytes) {
|
|
|
283
304
|
}
|
|
284
305
|
async function fetchBoundedMediaUrl(url, options) {
|
|
285
306
|
assertSsrfAllowedUrl(url, options.ssrf);
|
|
286
|
-
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
287
|
-
if (!fetchFn) {
|
|
288
|
-
throw new MediaContentError("fetch_failed", "No fetch implementation available for media URL resolution");
|
|
289
|
-
}
|
|
290
307
|
const timeoutController = new AbortController();
|
|
291
308
|
const timeout = setTimeout(() => timeoutController.abort(), options.timeoutMs);
|
|
292
309
|
const signal = options.signal
|
|
293
310
|
? AbortSignal.any([options.signal, timeoutController.signal])
|
|
294
311
|
: timeoutController.signal;
|
|
295
312
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
continue;
|
|
313
|
-
total += value.byteLength;
|
|
314
|
-
if (total > options.maxBytes) {
|
|
315
|
-
throw new MediaContentError("item_too_large", `Fetched media exceeded ${options.maxBytes} bytes`);
|
|
316
|
-
}
|
|
317
|
-
chunks.push(value);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
finally {
|
|
321
|
-
try {
|
|
322
|
-
await reader.cancel();
|
|
323
|
-
}
|
|
324
|
-
catch {
|
|
325
|
-
// Reader may already be closed after abort/complete.
|
|
326
|
-
}
|
|
327
|
-
reader.releaseLock();
|
|
328
|
-
}
|
|
329
|
-
const bytes = new Uint8Array(total);
|
|
330
|
-
let offset = 0;
|
|
331
|
-
for (const chunk of chunks) {
|
|
332
|
-
bytes.set(chunk, offset);
|
|
333
|
-
offset += chunk.byteLength;
|
|
313
|
+
if (options.fetch)
|
|
314
|
+
return await readFetchResponse(await options.fetch(url, { signal, redirect: "error" }), options.maxBytes);
|
|
315
|
+
const parsed = new URL(url);
|
|
316
|
+
const hostname = normalizeHostname(parsed.hostname);
|
|
317
|
+
const family = isIP(hostname);
|
|
318
|
+
const address = family
|
|
319
|
+
? { address: hostname, family: family }
|
|
320
|
+
: await resolvePublicAddress(hostname, options.resolveHostname ?? defaultMediaHostnameResolver, signal, options.ssrf);
|
|
321
|
+
const bytes = await (options.requestUrl ?? requestPinnedMediaUrl)({
|
|
322
|
+
url: parsed,
|
|
323
|
+
address,
|
|
324
|
+
maxBytes: options.maxBytes,
|
|
325
|
+
signal,
|
|
326
|
+
});
|
|
327
|
+
if (bytes.byteLength > options.maxBytes) {
|
|
328
|
+
throw new MediaContentError("item_too_large", `Fetched media exceeded ${options.maxBytes} bytes`);
|
|
334
329
|
}
|
|
335
330
|
return bytes;
|
|
336
331
|
}
|
|
@@ -348,6 +343,110 @@ async function fetchBoundedMediaUrl(url, options) {
|
|
|
348
343
|
clearTimeout(timeout);
|
|
349
344
|
}
|
|
350
345
|
}
|
|
346
|
+
async function defaultMediaHostnameResolver(hostname) {
|
|
347
|
+
return dnsLookup(hostname, { all: true, verbatim: true });
|
|
348
|
+
}
|
|
349
|
+
async function resolvePublicAddress(hostname, resolver, signal, policy) {
|
|
350
|
+
const addresses = await raceAbort(resolver(hostname, signal), signal);
|
|
351
|
+
if (addresses.length === 0)
|
|
352
|
+
throw new MediaContentError("fetch_failed", "Media hostname resolved to no addresses");
|
|
353
|
+
if (addresses.length > 32)
|
|
354
|
+
throw new MediaContentError("fetch_failed", "Media hostname resolved to too many addresses");
|
|
355
|
+
if (policy?.denyPrivateHosts !== false && !policy?.allowedHostnames?.length) {
|
|
356
|
+
if (addresses.some(({ address }) => isBlockedIp(normalizeHostname(address)))) {
|
|
357
|
+
throw new MediaContentError("ssrf_denied", `Media URL host ${hostname} resolved to a private address`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const selected = addresses[0];
|
|
361
|
+
if (isIP(normalizeHostname(selected.address)) !== selected.family) {
|
|
362
|
+
throw new MediaContentError("fetch_failed", "Media hostname resolver returned an invalid address");
|
|
363
|
+
}
|
|
364
|
+
return { address: normalizeHostname(selected.address), family: selected.family };
|
|
365
|
+
}
|
|
366
|
+
async function raceAbort(promise, signal) {
|
|
367
|
+
signal.throwIfAborted();
|
|
368
|
+
return new Promise((resolve, reject) => {
|
|
369
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
370
|
+
const onAbort = () => {
|
|
371
|
+
cleanup();
|
|
372
|
+
reject(signal.reason);
|
|
373
|
+
};
|
|
374
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
375
|
+
promise.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); });
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
async function readFetchResponse(response, maxBytes) {
|
|
379
|
+
if (!response.ok)
|
|
380
|
+
throw new MediaContentError("fetch_failed", `Media fetch failed with status ${response.status}`);
|
|
381
|
+
const reader = response.body?.getReader();
|
|
382
|
+
if (!reader)
|
|
383
|
+
throw new MediaContentError("fetch_failed", "Media fetch returned no response body");
|
|
384
|
+
const chunks = [];
|
|
385
|
+
let total = 0;
|
|
386
|
+
try {
|
|
387
|
+
while (true) {
|
|
388
|
+
const { done, value } = await reader.read();
|
|
389
|
+
if (done)
|
|
390
|
+
break;
|
|
391
|
+
if (!value)
|
|
392
|
+
continue;
|
|
393
|
+
total += value.byteLength;
|
|
394
|
+
if (total > maxBytes)
|
|
395
|
+
throw new MediaContentError("item_too_large", `Fetched media exceeded ${maxBytes} bytes`);
|
|
396
|
+
chunks.push(value);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
finally {
|
|
400
|
+
try {
|
|
401
|
+
await reader.cancel();
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
// Reader may already be closed after abort/complete.
|
|
405
|
+
}
|
|
406
|
+
reader.releaseLock();
|
|
407
|
+
}
|
|
408
|
+
return joinChunks(chunks, total);
|
|
409
|
+
}
|
|
410
|
+
function requestPinnedMediaUrl({ url, address, maxBytes, signal }) {
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
|
|
413
|
+
agent: false,
|
|
414
|
+
family: address.family,
|
|
415
|
+
signal,
|
|
416
|
+
lookup: (_hostname, _options, callback) => callback(null, address.address, address.family),
|
|
417
|
+
}, (response) => readIncomingMessage(response, maxBytes).then(resolve, reject));
|
|
418
|
+
request.on("error", reject);
|
|
419
|
+
request.end();
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async function readIncomingMessage(response, maxBytes) {
|
|
423
|
+
const status = response.statusCode ?? 0;
|
|
424
|
+
if (status < 200 || status >= 300) {
|
|
425
|
+
response.resume();
|
|
426
|
+
throw new MediaContentError("fetch_failed", `Media fetch failed with status ${status}`);
|
|
427
|
+
}
|
|
428
|
+
const chunks = [];
|
|
429
|
+
let total = 0;
|
|
430
|
+
for await (const chunk of response) {
|
|
431
|
+
const bytes = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
|
|
432
|
+
total += bytes.byteLength;
|
|
433
|
+
if (total > maxBytes) {
|
|
434
|
+
response.destroy();
|
|
435
|
+
throw new MediaContentError("item_too_large", `Fetched media exceeded ${maxBytes} bytes`);
|
|
436
|
+
}
|
|
437
|
+
chunks.push(bytes);
|
|
438
|
+
}
|
|
439
|
+
return joinChunks(chunks, total);
|
|
440
|
+
}
|
|
441
|
+
function joinChunks(chunks, total) {
|
|
442
|
+
const bytes = new Uint8Array(total);
|
|
443
|
+
let offset = 0;
|
|
444
|
+
for (const chunk of chunks) {
|
|
445
|
+
bytes.set(chunk, offset);
|
|
446
|
+
offset += chunk.byteLength;
|
|
447
|
+
}
|
|
448
|
+
return bytes;
|
|
449
|
+
}
|
|
351
450
|
function mediaTypesCompatible(declared, sniffed) {
|
|
352
451
|
const normalizedDeclared = declared.toLowerCase();
|
|
353
452
|
const normalizedSniffed = sniffed.toLowerCase();
|
|
@@ -365,33 +464,62 @@ function mediaTypesCompatible(declared, sniffed) {
|
|
|
365
464
|
return true;
|
|
366
465
|
return false;
|
|
367
466
|
}
|
|
467
|
+
function normalizeHostname(hostname) {
|
|
468
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
469
|
+
return normalized.endsWith(".") ? normalized.slice(0, -1) : normalized;
|
|
470
|
+
}
|
|
368
471
|
function isBlockedIp(hostname) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
472
|
+
const normalized = normalizeHostname(hostname);
|
|
473
|
+
const family = isIP(normalized);
|
|
474
|
+
if (family === 4)
|
|
475
|
+
return isBlockedIpv4(normalized);
|
|
476
|
+
if (family === 6)
|
|
477
|
+
return isBlockedIpv6(normalized);
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
function isBlockedIpv4(address) {
|
|
481
|
+
const [a, b] = address.split(".").map(Number);
|
|
482
|
+
return a === 0
|
|
483
|
+
|| a === 10
|
|
484
|
+
|| a === 127
|
|
485
|
+
|| (a === 100 && b >= 64 && b <= 127)
|
|
486
|
+
|| (a === 169 && b === 254)
|
|
487
|
+
|| (a === 172 && b >= 16 && b <= 31)
|
|
488
|
+
|| (a === 192 && (b === 0 || b === 168))
|
|
489
|
+
|| (a === 198 && (b === 18 || b === 19 || b === 51))
|
|
490
|
+
|| (a === 203 && b === 0)
|
|
491
|
+
|| a >= 224;
|
|
492
|
+
}
|
|
493
|
+
function isBlockedIpv6(address) {
|
|
494
|
+
const words = parseIpv6Words(address);
|
|
495
|
+
if (!words)
|
|
379
496
|
return true;
|
|
380
|
-
if (
|
|
497
|
+
if (words.every((word) => word === 0) || words.slice(0, 7).every((word) => word === 0) && words[7] === 1)
|
|
381
498
|
return true;
|
|
382
|
-
if (
|
|
499
|
+
if ((words[0] & 0xfe00) === 0xfc00)
|
|
383
500
|
return true;
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
function isBlockedIpv6(hostname) {
|
|
387
|
-
const normalized = hostname.toLowerCase();
|
|
388
|
-
if (normalized === "::1")
|
|
501
|
+
if ((words[0] & 0xffc0) === 0xfe80 || (words[0] & 0xffc0) === 0xfec0)
|
|
389
502
|
return true;
|
|
390
|
-
if (
|
|
503
|
+
if ((words[0] & 0xff00) === 0xff00)
|
|
391
504
|
return true;
|
|
392
|
-
if (
|
|
505
|
+
if (words[0] === 0x2001 && words[1] === 0x0db8)
|
|
393
506
|
return true;
|
|
394
|
-
|
|
507
|
+
const mapped = words.slice(0, 5).every((word) => word === 0) && (words[5] === 0 || words[5] === 0xffff);
|
|
508
|
+
return mapped && isBlockedIpv4(`${words[6] >> 8}.${words[6] & 0xff}.${words[7] >> 8}.${words[7] & 0xff}`);
|
|
509
|
+
}
|
|
510
|
+
function parseIpv6Words(address) {
|
|
511
|
+
const parts = address.split("::");
|
|
512
|
+
if (parts.length > 2)
|
|
513
|
+
return undefined;
|
|
514
|
+
const left = parts[0] ? parts[0].split(":") : [];
|
|
515
|
+
const right = parts[1] ? parts[1].split(":") : [];
|
|
516
|
+
const missing = 8 - left.length - right.length;
|
|
517
|
+
if (missing < 0 || (parts.length === 1 && missing !== 0))
|
|
518
|
+
return undefined;
|
|
519
|
+
const words = [...left, ...Array.from({ length: missing }, () => "0"), ...right].map((part) => Number.parseInt(part, 16));
|
|
520
|
+
return words.length === 8 && words.every((word) => Number.isInteger(word) && word >= 0 && word <= 0xffff)
|
|
521
|
+
? words
|
|
522
|
+
: undefined;
|
|
395
523
|
}
|
|
396
524
|
function startsWith(bytes, prefix) {
|
|
397
525
|
if (bytes.length < prefix.length)
|
package/dist/contracts.d.ts
CHANGED
|
@@ -256,13 +256,7 @@ export interface AgentConfig {
|
|
|
256
256
|
readonly promptBuilder?: PromptBuilder;
|
|
257
257
|
readonly middleware?: MiddlewareRegistry;
|
|
258
258
|
readonly resourceLoader?: ResourceLoader;
|
|
259
|
-
/** Host-owned metadata only: createAgent/session.run do not load or run these extensions. */
|
|
260
|
-
readonly extensions?: readonly Extension[];
|
|
261
259
|
readonly store?: SessionStore;
|
|
262
|
-
/** Host-owned metadata only: createAgent/session.run do not read settings. */
|
|
263
|
-
readonly settings?: SettingsProvider;
|
|
264
|
-
/** Host-owned metadata only: createAgent/session.run do not resolve credentials. Pass resolvers to provider edges explicitly. */
|
|
265
|
-
readonly credentials?: CredentialResolver;
|
|
266
260
|
readonly permission?: PermissionPolicy;
|
|
267
261
|
readonly providerOptions?: ProviderRequestOptions;
|
|
268
262
|
readonly providerRequestPolicies?: ProviderRequestPolicy | readonly ProviderRequestPolicy[];
|
|
@@ -304,13 +298,42 @@ export interface SubscribeOptions {
|
|
|
304
298
|
/** What to do when `maxQueuedEvents` is reached. Defaults to `close`. */
|
|
305
299
|
readonly overflow?: SubscriberOverflowPolicy;
|
|
306
300
|
}
|
|
301
|
+
export type AgentRunStatus = "succeeded" | "failed" | "aborted";
|
|
302
|
+
/** Terminal result of `session.run()` / `session.prompt()`. Failed and aborted runs throw {@link AgentRunError} with this shape attached. */
|
|
303
|
+
export interface AgentRunResult {
|
|
304
|
+
readonly sessionId: string;
|
|
305
|
+
readonly runId: string;
|
|
306
|
+
readonly status: AgentRunStatus;
|
|
307
|
+
/** Branch leaf after the run settles. */
|
|
308
|
+
readonly leafId?: string;
|
|
309
|
+
/** Concatenated text blocks from the final assistant message, or `""` when none. */
|
|
310
|
+
readonly text: string;
|
|
311
|
+
/** Content blocks from the final assistant message, or `[]` when none. */
|
|
312
|
+
readonly content: readonly ContentBlock[];
|
|
313
|
+
/** Final assistant message when the run produced one. */
|
|
314
|
+
readonly message?: Message;
|
|
315
|
+
/** Aggregate usage across provider turns (`run_total` scope). */
|
|
316
|
+
readonly usage?: Usage;
|
|
317
|
+
/** Present when `status` is `"failed"` or when a failed attempt still produced partial output. */
|
|
318
|
+
readonly error?: ErrorInfo;
|
|
319
|
+
/** String form of the abort reason when `status` is `"aborted"`. */
|
|
320
|
+
readonly abortReason?: string;
|
|
321
|
+
}
|
|
322
|
+
export declare class AgentRunError extends Error {
|
|
323
|
+
readonly result: AgentRunResult;
|
|
324
|
+
constructor(result: AgentRunResult, options?: {
|
|
325
|
+
readonly cause?: unknown;
|
|
326
|
+
});
|
|
327
|
+
}
|
|
307
328
|
export interface AgentSession {
|
|
308
329
|
readonly id: string;
|
|
309
330
|
/** Current branch leaf entry id; advances on every append/run and is re-pointed by `checkout`.
|
|
310
331
|
* Undefined until the first entry lands (a fresh session with no history). */
|
|
311
332
|
readonly leafId: string | undefined;
|
|
312
|
-
run(input: string | Message | readonly Message[], options?: RunOptions): Promise<
|
|
313
|
-
prompt(input: string, options?: RunOptions): Promise<
|
|
333
|
+
run(input: string | Message | readonly Message[], options?: RunOptions): Promise<AgentRunResult>;
|
|
334
|
+
prompt(input: string, options?: RunOptions): Promise<AgentRunResult>;
|
|
335
|
+
/** Subscribe first, then start exactly one run and yield only that run's events until it terminates. */
|
|
336
|
+
stream(input: string | Message | readonly Message[], options?: RunOptions & SubscribeOptions): AsyncIterable<AgentEvent>;
|
|
314
337
|
compact(options?: CompactionOptions): Promise<CompactionResult>;
|
|
315
338
|
subscribe(options?: SubscribeOptions): AsyncIterable<AgentEvent>;
|
|
316
339
|
abort(reason?: unknown): void;
|
|
@@ -1035,12 +1058,16 @@ export interface ToolCallRecord extends OwnershipScope {
|
|
|
1035
1058
|
readonly redacted: boolean;
|
|
1036
1059
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
1037
1060
|
}
|
|
1038
|
-
|
|
1061
|
+
export type UsageScope = "provider_turn" | "run_total";
|
|
1062
|
+
/** Stored usage row. `scope` prevents provider-turn and aggregate totals from being summed together. */
|
|
1039
1063
|
export interface UsageRecord extends OwnershipScope {
|
|
1040
1064
|
readonly id: string;
|
|
1041
1065
|
readonly sessionId: string;
|
|
1042
1066
|
readonly runId?: string;
|
|
1043
1067
|
readonly entryId?: string;
|
|
1068
|
+
readonly scope: UsageScope;
|
|
1069
|
+
readonly turn?: number;
|
|
1070
|
+
readonly attempt?: number;
|
|
1044
1071
|
readonly usage: Usage;
|
|
1045
1072
|
readonly recordedAt: string;
|
|
1046
1073
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
@@ -1054,6 +1081,59 @@ export interface RunLedger {
|
|
|
1054
1081
|
}
|
|
1055
1082
|
/** Union of records that may be handed to a {@link RunLedger}. */
|
|
1056
1083
|
export type RunLedgerRecord = RunRecord | AgentEventRecord | ToolCallRecord | UsageRecord;
|
|
1084
|
+
/** Immutable human feedback linked to an existing owned run/trace and optional evaluations. */
|
|
1085
|
+
export interface RunFeedbackRecord extends OwnershipScope {
|
|
1086
|
+
readonly id: string;
|
|
1087
|
+
readonly runId: string;
|
|
1088
|
+
readonly sessionId: string;
|
|
1089
|
+
readonly traceId?: string;
|
|
1090
|
+
readonly rating?: number;
|
|
1091
|
+
readonly comment?: string;
|
|
1092
|
+
readonly tags: readonly string[];
|
|
1093
|
+
readonly scorerIds: readonly string[];
|
|
1094
|
+
readonly evaluationIds: readonly string[];
|
|
1095
|
+
readonly createdAt: string;
|
|
1096
|
+
readonly createdBy?: string;
|
|
1097
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
1098
|
+
}
|
|
1099
|
+
export interface AppendRunFeedbackInput extends OwnershipScope {
|
|
1100
|
+
readonly id: string;
|
|
1101
|
+
readonly runId: string;
|
|
1102
|
+
readonly sessionId?: string;
|
|
1103
|
+
readonly traceId?: string;
|
|
1104
|
+
readonly rating?: number;
|
|
1105
|
+
readonly comment?: string;
|
|
1106
|
+
readonly tags?: readonly string[];
|
|
1107
|
+
readonly scorerIds?: readonly string[];
|
|
1108
|
+
readonly evaluationIds?: readonly string[];
|
|
1109
|
+
readonly createdAt?: string;
|
|
1110
|
+
readonly createdBy?: string;
|
|
1111
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
1112
|
+
readonly signal?: AbortSignal;
|
|
1113
|
+
}
|
|
1114
|
+
/** Cursor-paginated, ownership-scoped feedback query. */
|
|
1115
|
+
export interface RunFeedbackQuery extends PersistenceQuery, OwnershipScope {
|
|
1116
|
+
readonly runId?: string;
|
|
1117
|
+
readonly sessionId?: string;
|
|
1118
|
+
readonly traceId?: string;
|
|
1119
|
+
readonly rating?: number;
|
|
1120
|
+
readonly scorerId?: string;
|
|
1121
|
+
readonly evaluationId?: string;
|
|
1122
|
+
readonly tag?: string;
|
|
1123
|
+
readonly fromCreatedAt?: string;
|
|
1124
|
+
readonly toCreatedAt?: string;
|
|
1125
|
+
readonly signal?: AbortSignal;
|
|
1126
|
+
}
|
|
1127
|
+
export interface DeleteRunFeedbackInput extends OwnershipScope {
|
|
1128
|
+
readonly id: string;
|
|
1129
|
+
readonly signal?: AbortSignal;
|
|
1130
|
+
}
|
|
1131
|
+
/** Feedback storage seam. Records are append-only; correction uses a new record and deletion is explicit. */
|
|
1132
|
+
export interface RunFeedbackStore {
|
|
1133
|
+
append(input: AppendRunFeedbackInput): Promise<RunFeedbackRecord>;
|
|
1134
|
+
query(query: RunFeedbackQuery): Promise<PersistencePage<RunFeedbackRecord>>;
|
|
1135
|
+
delete(input: DeleteRunFeedbackInput): Promise<boolean>;
|
|
1136
|
+
}
|
|
1057
1137
|
/** Stored agent definition version. Does not include provider credentials/resolvers/provider instances. */
|
|
1058
1138
|
export interface AgentDefinitionRecord extends OwnershipScope {
|
|
1059
1139
|
readonly id: string;
|
|
@@ -1158,6 +1238,9 @@ export interface UsageQuery extends PersistenceQuery, OwnershipScope {
|
|
|
1158
1238
|
readonly sessionId?: string;
|
|
1159
1239
|
readonly runId?: string;
|
|
1160
1240
|
readonly entryId?: string;
|
|
1241
|
+
readonly scope?: UsageScope;
|
|
1242
|
+
readonly turn?: number;
|
|
1243
|
+
readonly attempt?: number;
|
|
1161
1244
|
readonly fromRecordedAt?: string;
|
|
1162
1245
|
readonly toRecordedAt?: string;
|
|
1163
1246
|
}
|
|
@@ -1194,6 +1277,8 @@ export interface ProductionPersistenceStore {
|
|
|
1194
1277
|
readonly checkpoints?: CheckpointStore;
|
|
1195
1278
|
/** Optional atomic distributed lease capability for coordinators and workers. */
|
|
1196
1279
|
readonly leases?: LeaseStore;
|
|
1280
|
+
/** Optional immutable run/trace feedback storage capability. */
|
|
1281
|
+
readonly feedback?: RunFeedbackStore;
|
|
1197
1282
|
querySessions(query: SessionQuery): Promise<PersistencePage<SessionRecord>>;
|
|
1198
1283
|
queryBranches(query: BranchQuery): Promise<PersistencePage<BranchRecord>>;
|
|
1199
1284
|
queryEntries(query: SessionEntryQuery): Promise<PersistencePage<SessionEntry>>;
|
package/dist/contracts.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export class AgentRunError extends Error {
|
|
2
|
+
result;
|
|
3
|
+
constructor(result, options) {
|
|
4
|
+
super(result.error?.message ?? (result.status === "aborted" ? "Agent run aborted" : "Agent run failed"), options);
|
|
5
|
+
this.name = "AgentRunError";
|
|
6
|
+
this.result = result;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
1
9
|
export const SESSION_ENTRY_KINDS = [
|
|
2
10
|
"message",
|
|
3
11
|
"event",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AppendRunFeedbackInput, OwnershipScope, RunFeedbackRecord, RunFeedbackStore } from "./contracts.js";
|
|
2
|
+
import type { SecretRedactor } from "./redaction.js";
|
|
3
|
+
export declare const DEFAULT_MAX_FEEDBACK_COMMENT_BYTES = 4096;
|
|
4
|
+
export declare const HARD_MAX_FEEDBACK_COMMENT_BYTES = 16384;
|
|
5
|
+
export declare const DEFAULT_MAX_FEEDBACK_TAGS = 16;
|
|
6
|
+
export declare const HARD_MAX_FEEDBACK_TAGS = 64;
|
|
7
|
+
export declare const DEFAULT_MAX_FEEDBACK_LINKS = 16;
|
|
8
|
+
export declare const HARD_MAX_FEEDBACK_LINKS = 64;
|
|
9
|
+
export declare const DEFAULT_MAX_FEEDBACK_METADATA_BYTES = 16384;
|
|
10
|
+
export declare const HARD_MAX_FEEDBACK_METADATA_BYTES = 65536;
|
|
11
|
+
export declare const DEFAULT_FEEDBACK_PAGE_SIZE = 100;
|
|
12
|
+
export declare const HARD_FEEDBACK_PAGE_SIZE = 500;
|
|
13
|
+
export declare const MAX_FEEDBACK_TAG_LENGTH = 64;
|
|
14
|
+
export declare const MAX_FEEDBACK_ID_LENGTH = 128;
|
|
15
|
+
export declare class RunFeedbackError extends Error {
|
|
16
|
+
readonly code: string;
|
|
17
|
+
constructor(message: string, code?: string);
|
|
18
|
+
}
|
|
19
|
+
export interface RunFeedbackRun extends OwnershipScope {
|
|
20
|
+
readonly runId: string;
|
|
21
|
+
readonly sessionId: string;
|
|
22
|
+
readonly traceId?: string;
|
|
23
|
+
}
|
|
24
|
+
export type RunFeedbackRunResolver = (input: Readonly<{
|
|
25
|
+
runId: string;
|
|
26
|
+
ownership: OwnershipScope;
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
}>) => RunFeedbackRun | false | undefined | Promise<RunFeedbackRun | false | undefined>;
|
|
29
|
+
export interface RunFeedbackLimits {
|
|
30
|
+
readonly maxCommentBytes?: number;
|
|
31
|
+
readonly maxTags?: number;
|
|
32
|
+
readonly maxLinks?: number;
|
|
33
|
+
readonly maxMetadataBytes?: number;
|
|
34
|
+
readonly maxPageSize?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface PrepareRunFeedbackOptions extends RunFeedbackLimits {
|
|
37
|
+
readonly resolveRun: RunFeedbackRunResolver;
|
|
38
|
+
readonly redactor?: SecretRedactor;
|
|
39
|
+
}
|
|
40
|
+
export interface MemoryRunFeedbackStoreOptions extends PrepareRunFeedbackOptions {
|
|
41
|
+
readonly initial?: readonly RunFeedbackRecord[];
|
|
42
|
+
}
|
|
43
|
+
/** Validate, ownership-check, redact, bound, and freeze one feedback record. */
|
|
44
|
+
export declare function prepareRunFeedback(input: AppendRunFeedbackInput, options: PrepareRunFeedbackOptions): Promise<RunFeedbackRecord>;
|
|
45
|
+
/** In-memory implementation with the same validation/ownership semantics as durable adapters. */
|
|
46
|
+
export declare function createMemoryRunFeedbackStore(options: MemoryRunFeedbackStoreOptions): RunFeedbackStore;
|
|
47
|
+
export declare function requireRunFeedbackOwnership(input: OwnershipScope): Required<Pick<OwnershipScope, "tenantId">> & OwnershipScope;
|
|
48
|
+
export declare function runFeedbackPageLimit(limit: number | undefined, maximum?: number): number;
|