@arnilo/prism 0.0.2 → 0.0.4

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +32 -20
  3. package/dist/agent-loops.d.ts +8 -1
  4. package/dist/agent-loops.js +57 -11
  5. package/dist/agents.js +70 -17
  6. package/dist/checkpoints.d.ts +11 -0
  7. package/dist/checkpoints.js +144 -0
  8. package/dist/compaction.js +9 -1
  9. package/dist/content.d.ts +102 -0
  10. package/dist/content.js +410 -0
  11. package/dist/contracts.d.ts +142 -2
  12. package/dist/event-multiplexer.d.ts +23 -0
  13. package/dist/event-multiplexer.js +136 -0
  14. package/dist/execution-policy.d.ts +28 -0
  15. package/dist/execution-policy.js +24 -0
  16. package/dist/index.d.ts +17 -5
  17. package/dist/index.js +11 -4
  18. package/dist/input.js +11 -1
  19. package/dist/leases.d.ts +8 -0
  20. package/dist/leases.js +111 -0
  21. package/dist/node/agent-definitions.js +3 -5
  22. package/dist/node/config.d.ts +1 -0
  23. package/dist/node/config.js +5 -3
  24. package/dist/node/contribution-discovery.js +5 -8
  25. package/dist/node/session-store-jsonl.js +8 -5
  26. package/dist/node/settings.js +2 -2
  27. package/dist/node/trust.js +2 -4
  28. package/dist/observability.d.ts +3 -0
  29. package/dist/observability.js +18 -0
  30. package/dist/providers/media.d.ts +42 -0
  31. package/dist/providers/media.js +116 -0
  32. package/dist/providers/openai-compatible.js +18 -119
  33. package/dist/providers/openai-primitives.d.ts +9 -0
  34. package/dist/providers/openai-primitives.js +129 -0
  35. package/dist/providers/transport.d.ts +40 -0
  36. package/dist/providers/transport.js +221 -0
  37. package/dist/redaction.js +40 -13
  38. package/dist/resources.d.ts +5 -0
  39. package/dist/resources.js +4 -0
  40. package/dist/structured-output.d.ts +11 -0
  41. package/dist/structured-output.js +59 -0
  42. package/dist/testing/persistence-schema.d.ts +102 -0
  43. package/dist/testing/persistence-schema.js +457 -0
  44. package/dist/testing/provider-conformance.js +10 -1
  45. package/dist/testing/run-ledger-conformance.d.ts +33 -0
  46. package/dist/testing/run-ledger-conformance.js +172 -0
  47. package/dist/testing/session-store-conformance.d.ts +16 -0
  48. package/dist/testing/session-store-conformance.js +73 -0
  49. package/dist/tools.d.ts +17 -0
  50. package/dist/tools.js +29 -2
  51. package/docs/agent-events.md +13 -4
  52. package/docs/agent-loops.md +10 -4
  53. package/docs/agent-session-runtime.md +1 -0
  54. package/docs/cli-rpc.md +3 -0
  55. package/docs/coding-agent-tools.md +242 -0
  56. package/docs/coding-security.md +84 -0
  57. package/docs/credential-storage.md +177 -0
  58. package/docs/credentials-and-redaction.md +2 -1
  59. package/docs/database-persistence.md +44 -2
  60. package/docs/host-security.md +15 -1
  61. package/docs/index.md +28 -11
  62. package/docs/input-and-prompt-assembly.md +6 -5
  63. package/docs/mcp-tools.md +139 -0
  64. package/docs/middleware-hooks.md +2 -0
  65. package/docs/migration.md +21 -28
  66. package/docs/model-registry.md +5 -3
  67. package/docs/multimodal-content.md +148 -0
  68. package/docs/observability.md +163 -0
  69. package/docs/performance.md +40 -1
  70. package/docs/persistence-credentials-multimodality-primitives.md +303 -0
  71. package/docs/postgres-persistence.md +141 -0
  72. package/docs/provider-conformance.md +17 -0
  73. package/docs/provider-layer.md +1 -1
  74. package/docs/provider-primitives.md +281 -0
  75. package/docs/providers/kimi.md +1 -0
  76. package/docs/providers/neuralwatt.md +1 -0
  77. package/docs/providers/openai-compatible.md +2 -1
  78. package/docs/providers/openai.md +8 -1
  79. package/docs/providers/opencode-go.md +1 -0
  80. package/docs/providers/openrouter.md +1 -0
  81. package/docs/providers/zai.md +1 -0
  82. package/docs/public-contracts.md +9 -2
  83. package/docs/release-and-install.md +209 -23
  84. package/docs/resource-loading.md +14 -4
  85. package/docs/review-coverage-2026-07-14.md +260 -0
  86. package/docs/run-ledger-conformance.md +96 -0
  87. package/docs/runs-and-usage.md +2 -0
  88. package/docs/session-store-conformance.md +16 -0
  89. package/docs/session-stores-and-branching.md +1 -0
  90. package/docs/settings-auth-trust-security.md +2 -1
  91. package/docs/sqlite-persistence.md +122 -0
  92. package/docs/structured-output.md +9 -0
  93. package/docs/tool-conformance.md +1 -0
  94. package/docs/tool-execution-primitives.md +374 -0
  95. package/docs/tools.md +40 -1
  96. package/docs/workflow-orchestration-primitives.md +565 -0
  97. package/docs/workflow-tui-primitives.md +5 -0
  98. package/docs/workflows.md +219 -0
  99. package/package.json +34 -5
@@ -0,0 +1,9 @@
1
+ import type { JsonObject, Message, ModelCapabilities, StructuredOutputOptions, ToolDefinition, Usage } from "../contracts.js";
2
+ export declare function assertOpenAIChatMessage(message: unknown, path: string): asserts message is Message;
3
+ export declare function serializeOpenAIChatStructuredOutput(options: StructuredOutputOptions): JsonObject;
4
+ export declare function serializeOpenAIResponsesStructuredOutput(options: StructuredOutputOptions): JsonObject;
5
+ export declare function applyOpenAIChatStructuredOutput(body: Record<string, unknown>, structuredOutput?: StructuredOutputOptions): void;
6
+ export declare function applyOpenAIResponsesStructuredOutput(body: Record<string, unknown>, structuredOutput?: StructuredOutputOptions): void;
7
+ export declare function serializeOpenAITool(tool: ToolDefinition): JsonObject;
8
+ export declare function serializeOpenAIChatMessage(message: Message, capabilities?: ModelCapabilities): JsonObject;
9
+ export declare function mapOpenAIChatUsage(usage: unknown): Usage | undefined;
@@ -0,0 +1,129 @@
1
+ export function assertOpenAIChatMessage(message, path) {
2
+ if (!message || typeof message !== "object") {
3
+ throw new Error(`Invalid provider message at ${path}: expected object`);
4
+ }
5
+ const candidate = message;
6
+ if (typeof candidate.role !== "string") {
7
+ throw new Error(`Invalid provider message at ${path}: expected role`);
8
+ }
9
+ if (!Array.isArray(candidate.content)) {
10
+ throw new Error(`Invalid provider message at ${path}: expected content array`);
11
+ }
12
+ }
13
+ export function serializeOpenAIChatStructuredOutput(options) {
14
+ return {
15
+ type: "json_schema",
16
+ json_schema: {
17
+ name: options.name,
18
+ schema: options.schema,
19
+ ...(options.strict === undefined ? {} : { strict: options.strict }),
20
+ },
21
+ };
22
+ }
23
+ export function serializeOpenAIResponsesStructuredOutput(options) {
24
+ return {
25
+ format: {
26
+ type: "json_schema",
27
+ name: options.name,
28
+ schema: options.schema,
29
+ ...(options.strict === undefined ? {} : { strict: options.strict }),
30
+ },
31
+ };
32
+ }
33
+ export function applyOpenAIChatStructuredOutput(body, structuredOutput) {
34
+ if (!structuredOutput)
35
+ return;
36
+ body.response_format = serializeOpenAIChatStructuredOutput(structuredOutput);
37
+ }
38
+ export function applyOpenAIResponsesStructuredOutput(body, structuredOutput) {
39
+ if (!structuredOutput)
40
+ return;
41
+ body.text = serializeOpenAIResponsesStructuredOutput(structuredOutput);
42
+ }
43
+ export function serializeOpenAITool(tool) {
44
+ return {
45
+ type: "function",
46
+ function: {
47
+ name: tool.name,
48
+ description: tool.description,
49
+ parameters: tool.parameters ?? { type: "object" },
50
+ },
51
+ };
52
+ }
53
+ export function serializeOpenAIChatMessage(message, capabilities = {}) {
54
+ if (message.role === "tool") {
55
+ const result = message.content.find((part) => part.type === "tool_result");
56
+ return {
57
+ role: "tool",
58
+ tool_call_id: result?.toolCallId ?? "",
59
+ content: result ? JSON.stringify(result.result ?? result.error ?? null) : "",
60
+ };
61
+ }
62
+ if (message.role === "assistant") {
63
+ const toolCalls = message.content.filter((part) => part.type === "tool_call");
64
+ const textParts = message.content.filter((part) => part.type === "text" || part.type === "thinking");
65
+ if (toolCalls.length > 0) {
66
+ return {
67
+ role: "assistant",
68
+ content: textParts.map((part) => part.text).join("\n") || null,
69
+ tool_calls: toolCalls.map((call) => ({
70
+ id: call.id,
71
+ type: "function",
72
+ function: { name: call.name, arguments: JSON.stringify(call.arguments) },
73
+ })),
74
+ };
75
+ }
76
+ }
77
+ const content = [];
78
+ for (const part of message.content) {
79
+ if (part.type === "text" || part.type === "thinking") {
80
+ content.push({ type: "text", text: part.text });
81
+ }
82
+ else if (part.type === "image") {
83
+ if (!capabilities.input?.includes("image")) {
84
+ throw new Error(`Provider ${message.role} message includes image but model does not declare image input capability`);
85
+ }
86
+ content.push(serializeOpenAIImage(part));
87
+ }
88
+ else if (part.type === "audio" || part.type === "file" || part.type === "document") {
89
+ throw new Error(`OpenAI Chat Completions does not support ${part.type} content blocks`);
90
+ }
91
+ else if (part.type === "tool_call") {
92
+ throw new Error("Provider assistant tool_call blocks must be the only content on the message");
93
+ }
94
+ else if (part.type === "tool_result") {
95
+ throw new Error("Provider tool_result blocks must appear in role=tool messages");
96
+ }
97
+ }
98
+ if (content.length === 1 && content[0].type === "text") {
99
+ return { role: message.role, content: content[0].text };
100
+ }
101
+ return { role: message.role, content };
102
+ }
103
+ function serializeOpenAIImage(part) {
104
+ const url = part.url ?? (part.data ? `data:${part.mimeType ?? "image/png"};base64,${part.data}` : undefined);
105
+ if (!url)
106
+ throw new Error("Provider image block missing url or data");
107
+ return { type: "image_url", image_url: { url } };
108
+ }
109
+ export function mapOpenAIChatUsage(usage) {
110
+ if (!usage || typeof usage !== "object" || Array.isArray(usage))
111
+ return undefined;
112
+ const wire = usage;
113
+ if (wire.prompt_tokens === undefined
114
+ && wire.completion_tokens === undefined
115
+ && wire.total_tokens === undefined
116
+ && wire.prompt_cache_hit_tokens === undefined
117
+ && wire.prompt_tokens_details?.cached_tokens === undefined
118
+ && wire.prompt_tokens_details?.cache_write_tokens === undefined) {
119
+ return undefined;
120
+ }
121
+ return {
122
+ inputTokens: wire.prompt_tokens,
123
+ outputTokens: wire.completion_tokens,
124
+ totalTokens: wire.total_tokens,
125
+ cacheReadTokens: wire.prompt_tokens_details?.cached_tokens ?? wire.prompt_cache_hit_tokens,
126
+ cacheWriteTokens: wire.prompt_tokens_details?.cache_write_tokens,
127
+ };
128
+ }
129
+ //# sourceMappingURL=openai-primitives.js.map
@@ -0,0 +1,40 @@
1
+ import type { JsonObject } from "../contracts.js";
2
+ export declare const DEFAULT_MAX_EVENT_BYTES = 262144;
3
+ export declare const DEFAULT_MAX_BUFFER_BYTES = 524288;
4
+ export declare const DEFAULT_MAX_RESPONSE_BODY_BYTES = 65536;
5
+ export declare const DEFAULT_MAX_ARGUMENT_BYTES = 262144;
6
+ export interface BoundedStreamLimits {
7
+ readonly maxEventBytes?: number;
8
+ readonly maxBufferBytes?: number;
9
+ readonly maxResponseBodyBytes?: number;
10
+ }
11
+ export interface SseEvent {
12
+ readonly id?: string;
13
+ readonly event?: string;
14
+ readonly data: string;
15
+ readonly comments?: readonly string[];
16
+ }
17
+ export type ProviderTransportErrorCode = "sse_buffer_overflow" | "sse_event_overflow" | "response_body_overflow" | "aborted" | "invalid_json_arguments";
18
+ export declare class ProviderTransportError extends Error {
19
+ readonly code: ProviderTransportErrorCode;
20
+ readonly limitBytes?: number;
21
+ constructor(code: ProviderTransportErrorCode, message: string, limitBytes?: number);
22
+ }
23
+ export interface ReadSseEventsOptions extends BoundedStreamLimits {
24
+ readonly signal?: AbortSignal;
25
+ }
26
+ export interface ReadBoundedResponseTextOptions extends BoundedStreamLimits {
27
+ readonly secrets?: readonly (string | undefined)[];
28
+ }
29
+ export interface ParseJsonObjectArgumentsOptions {
30
+ readonly toolName?: string;
31
+ readonly maxBytes?: number;
32
+ }
33
+ /** Incremental O(bytes) SSE parser with finite buffer/event limits. */
34
+ export declare function readSseEvents(body: ReadableStream<Uint8Array>, options?: ReadSseEventsOptions): AsyncGenerator<SseEvent>;
35
+ /** Yields joined `data:` payloads for each SSE event (migration helper over {@link readSseEvents}). */
36
+ export declare function readSseData(body: ReadableStream<Uint8Array>, options?: ReadSseEventsOptions): AsyncGenerator<string>;
37
+ /** Read a response body with a hard byte ceiling; redacts optional secrets. */
38
+ export declare function readBoundedResponseText(response: Response, options?: ReadBoundedResponseTextOptions): Promise<string>;
39
+ /** Parse streamed tool arguments as a JSON object; throws {@link ProviderTransportError} on invalid input. */
40
+ export declare function parseJsonObjectArguments(text: string, options?: ParseJsonObjectArgumentsOptions): JsonObject;
@@ -0,0 +1,221 @@
1
+ import { redactSecrets } from "../redaction.js";
2
+ export const DEFAULT_MAX_EVENT_BYTES = 262_144;
3
+ export const DEFAULT_MAX_BUFFER_BYTES = 524_288;
4
+ export const DEFAULT_MAX_RESPONSE_BODY_BYTES = 65_536;
5
+ export const DEFAULT_MAX_ARGUMENT_BYTES = 262_144;
6
+ export class ProviderTransportError extends Error {
7
+ code;
8
+ limitBytes;
9
+ constructor(code, message, limitBytes) {
10
+ super(message);
11
+ this.name = "ProviderTransportError";
12
+ this.code = code;
13
+ this.limitBytes = limitBytes;
14
+ }
15
+ }
16
+ function resolveLimits(options) {
17
+ return {
18
+ maxEventBytes: options?.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES,
19
+ maxBufferBytes: options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES,
20
+ maxResponseBodyBytes: options?.maxResponseBodyBytes ?? DEFAULT_MAX_RESPONSE_BODY_BYTES,
21
+ };
22
+ }
23
+ function byteLength(text) {
24
+ return new TextEncoder().encode(text).length;
25
+ }
26
+ function throwIfAborted(signal) {
27
+ if (signal?.aborted) {
28
+ throw new ProviderTransportError("aborted", "Transport operation aborted");
29
+ }
30
+ }
31
+ function parseSseEventBlock(block) {
32
+ const comments = [];
33
+ const dataLines = [];
34
+ let id;
35
+ let event;
36
+ for (const line of block.split(/\r?\n/)) {
37
+ if (!line)
38
+ continue;
39
+ if (line.startsWith(":")) {
40
+ comments.push(line.slice(1).trimStart());
41
+ continue;
42
+ }
43
+ const colon = line.indexOf(":");
44
+ const field = colon === -1 ? line : line.slice(0, colon);
45
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^\s/, "");
46
+ switch (field) {
47
+ case "data":
48
+ dataLines.push(value);
49
+ break;
50
+ case "id":
51
+ id = value;
52
+ break;
53
+ case "event":
54
+ event = value;
55
+ break;
56
+ default:
57
+ break;
58
+ }
59
+ }
60
+ const data = dataLines.join("\n");
61
+ if (!data && comments.length === 0)
62
+ return undefined;
63
+ return {
64
+ id,
65
+ event,
66
+ data,
67
+ ...(comments.length > 0 ? { comments } : {}),
68
+ };
69
+ }
70
+ function eventBlockBytes(block) {
71
+ return byteLength(block);
72
+ }
73
+ /** Incremental O(bytes) SSE parser with finite buffer/event limits. */
74
+ export async function* readSseEvents(body, options) {
75
+ const limits = resolveLimits(options);
76
+ const reader = body.getReader();
77
+ const decoder = new TextDecoder();
78
+ let buffer = "";
79
+ const ensureBufferLimit = () => {
80
+ if (byteLength(buffer) > limits.maxBufferBytes) {
81
+ throw new ProviderTransportError("sse_buffer_overflow", `SSE buffer exceeded ${limits.maxBufferBytes} bytes`, limits.maxBufferBytes);
82
+ }
83
+ };
84
+ const yieldCompleteEvents = function* () {
85
+ while (true) {
86
+ const match = buffer.match(/\r?\n\r?\n/);
87
+ if (!match || match.index === undefined)
88
+ break;
89
+ const block = buffer.slice(0, match.index);
90
+ buffer = buffer.slice(match.index + match[0].length);
91
+ const parsed = parseSseEventBlock(block);
92
+ if (!parsed)
93
+ continue;
94
+ const size = eventBlockBytes(block);
95
+ if (size > limits.maxEventBytes) {
96
+ throw new ProviderTransportError("sse_event_overflow", `SSE event exceeded ${limits.maxEventBytes} bytes`, limits.maxEventBytes);
97
+ }
98
+ yield parsed;
99
+ }
100
+ };
101
+ try {
102
+ while (true) {
103
+ throwIfAborted(options?.signal);
104
+ const { done, value } = await reader.read();
105
+ if (done)
106
+ break;
107
+ buffer += decoder.decode(value, { stream: true });
108
+ ensureBufferLimit();
109
+ yield* yieldCompleteEvents();
110
+ }
111
+ buffer += decoder.decode();
112
+ ensureBufferLimit();
113
+ yield* yieldCompleteEvents();
114
+ const tail = buffer.trim();
115
+ if (tail) {
116
+ const parsed = parseSseEventBlock(tail);
117
+ if (parsed) {
118
+ const size = eventBlockBytes(tail);
119
+ if (size > limits.maxEventBytes) {
120
+ throw new ProviderTransportError("sse_event_overflow", `SSE event exceeded ${limits.maxEventBytes} bytes`, limits.maxEventBytes);
121
+ }
122
+ yield parsed;
123
+ }
124
+ }
125
+ }
126
+ finally {
127
+ try {
128
+ reader.releaseLock();
129
+ }
130
+ catch {
131
+ // already released
132
+ }
133
+ try {
134
+ await reader.cancel();
135
+ }
136
+ catch {
137
+ // stream may already be closed
138
+ }
139
+ }
140
+ }
141
+ /** Yields joined `data:` payloads for each SSE event (migration helper over {@link readSseEvents}). */
142
+ export async function* readSseData(body, options) {
143
+ for await (const event of readSseEvents(body, options)) {
144
+ const data = event.data.trim();
145
+ if (data)
146
+ yield data;
147
+ }
148
+ }
149
+ /** Read a response body with a hard byte ceiling; redacts optional secrets. */
150
+ export async function readBoundedResponseText(response, options) {
151
+ const limits = resolveLimits(options);
152
+ const secrets = options?.secrets ?? [];
153
+ if (!response.body) {
154
+ try {
155
+ const text = await response.text();
156
+ if (byteLength(text) > limits.maxResponseBodyBytes) {
157
+ throw new ProviderTransportError("response_body_overflow", `Response body exceeded ${limits.maxResponseBodyBytes} bytes`, limits.maxResponseBodyBytes);
158
+ }
159
+ return redactSecrets(text, secrets);
160
+ }
161
+ catch (error) {
162
+ if (error instanceof ProviderTransportError)
163
+ throw error;
164
+ return "";
165
+ }
166
+ }
167
+ const reader = response.body.getReader();
168
+ const decoder = new TextDecoder();
169
+ const parts = [];
170
+ let totalBytes = 0;
171
+ try {
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done)
175
+ break;
176
+ totalBytes += value.byteLength;
177
+ if (totalBytes > limits.maxResponseBodyBytes) {
178
+ throw new ProviderTransportError("response_body_overflow", `Response body exceeded ${limits.maxResponseBodyBytes} bytes`, limits.maxResponseBodyBytes);
179
+ }
180
+ parts.push(decoder.decode(value, { stream: true }));
181
+ }
182
+ parts.push(decoder.decode());
183
+ return redactSecrets(parts.join(""), secrets);
184
+ }
185
+ finally {
186
+ try {
187
+ reader.releaseLock();
188
+ }
189
+ catch {
190
+ // already released
191
+ }
192
+ try {
193
+ await reader.cancel();
194
+ }
195
+ catch {
196
+ // stream may already be closed
197
+ }
198
+ }
199
+ }
200
+ /** Parse streamed tool arguments as a JSON object; throws {@link ProviderTransportError} on invalid input. */
201
+ export function parseJsonObjectArguments(text, options) {
202
+ const maxBytes = options?.maxBytes ?? DEFAULT_MAX_ARGUMENT_BYTES;
203
+ const suffix = options?.toolName ? ` for tool ${options.toolName}` : "";
204
+ if (!text)
205
+ return {};
206
+ if (byteLength(text) > maxBytes) {
207
+ throw new ProviderTransportError("invalid_json_arguments", `Tool arguments${suffix} exceeded ${maxBytes} bytes`, maxBytes);
208
+ }
209
+ let parsed;
210
+ try {
211
+ parsed = JSON.parse(text);
212
+ }
213
+ catch (error) {
214
+ throw new ProviderTransportError("invalid_json_arguments", `Invalid tool arguments JSON${suffix}`);
215
+ }
216
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
217
+ throw new ProviderTransportError("invalid_json_arguments", `Tool arguments${suffix} must be a JSON object`);
218
+ }
219
+ return parsed;
220
+ }
221
+ //# sourceMappingURL=transport.js.map
package/dist/redaction.js CHANGED
@@ -22,10 +22,25 @@ export function redactSecrets(value, secrets) {
22
22
  if (needles.length === 0)
23
23
  return value;
24
24
  const redactString = (text) => needles.reduce((current, secret) => current.split(secret).join(REDACTED), text);
25
- // ponytail: WeakSet cycle guard + leaf passthrough for Date/RegExp/ArrayBuffer/typed
26
- // arrays. Map/Set are normalized to plain object/array so output stays JSON-shaped.
27
- // Upgrade path: if callers need original Map/Set preserved, thread a mode flag.
28
- const redact = (input, seen = new WeakSet()) => {
25
+ const redactKey = (key) => {
26
+ if (typeof key === "string")
27
+ return redactString(key);
28
+ return String(key);
29
+ };
30
+ const assignKey = (target, key, value) => {
31
+ if (!Object.prototype.hasOwnProperty.call(target, key)) {
32
+ target[key] = value;
33
+ return;
34
+ }
35
+ let suffix = 2;
36
+ while (Object.prototype.hasOwnProperty.call(target, `${key}__${suffix}`))
37
+ suffix += 1;
38
+ target[`${key}__${suffix}`] = value;
39
+ };
40
+ // ponytail: active-path WeakSet marks only ancestor cycles as [Circular]; shared
41
+ // references (diamonds) are visited again on separate branches. Map/Set normalize
42
+ // to JSON-shaped output; string keys are redacted like values.
43
+ const redact = (input, active = new WeakSet()) => {
29
44
  if (typeof input === "string")
30
45
  return redactString(input);
31
46
  if (input === null || typeof input !== "object")
@@ -34,16 +49,28 @@ export function redactSecrets(value, secrets) {
34
49
  return input;
35
50
  if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer)
36
51
  return input;
37
- if (seen.has(input))
52
+ if (active.has(input))
38
53
  return "[Circular]";
39
- seen.add(input);
40
- if (Array.isArray(input))
41
- return input.map((item) => redact(item, seen));
42
- if (input instanceof Map)
43
- return Object.fromEntries([...input].map(([key, item]) => [key, redact(item, seen)]));
44
- if (input instanceof Set)
45
- return [...input].map((item) => redact(item, seen));
46
- return Object.fromEntries(Object.entries(input).map(([key, item]) => [key, redact(item, seen)]));
54
+ active.add(input);
55
+ try {
56
+ if (Array.isArray(input))
57
+ return input.map((item) => redact(item, active));
58
+ if (input instanceof Map) {
59
+ const out = {};
60
+ for (const [key, item] of input)
61
+ assignKey(out, redactKey(key), redact(item, active));
62
+ return out;
63
+ }
64
+ if (input instanceof Set)
65
+ return [...input].map((item) => redact(item, active));
66
+ const out = {};
67
+ for (const [key, item] of Object.entries(input))
68
+ assignKey(out, redactKey(key), redact(item, active));
69
+ return out;
70
+ }
71
+ finally {
72
+ active.delete(input);
73
+ }
47
74
  };
48
75
  return redact(value);
49
76
  }
@@ -1,5 +1,10 @@
1
1
  import { type PrismManifest } from "./manifests.js";
2
2
  import type { JsonObject, ResourceLoader, ResourceLoadContext } from "./contracts.js";
3
+ import { type MediaContentBounds } from "./content.js";
3
4
  export declare function loadTextResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<string>;
4
5
  export declare function loadJsonResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<JsonObject>;
5
6
  export declare function loadManifestResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext): Promise<PrismManifest>;
7
+ export interface LoadBinaryResourceOptions extends MediaContentBounds {
8
+ readonly signal?: AbortSignal;
9
+ }
10
+ export declare function loadBinaryResource(loader: ResourceLoader, uri: string, context?: ResourceLoadContext, options?: LoadBinaryResourceOptions): Promise<Uint8Array>;
package/dist/resources.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { parsePrismManifest } from "./manifests.js";
2
2
  import { assertJsonObject } from "./config.js";
3
+ import { DEFAULT_MAX_MEDIA_ITEM_BYTES, loadBoundedBinaryResource, } from "./content.js";
3
4
  import { assertPermission } from "./security.js";
4
5
  export async function loadTextResource(loader, uri, context) {
5
6
  await assertPermission(context?.permission, { kind: "resource", action: "load", target: uri, metadata: context?.metadata });
@@ -24,6 +25,9 @@ export async function loadJsonResource(loader, uri, context) {
24
25
  export async function loadManifestResource(loader, uri, context) {
25
26
  return parsePrismManifest(await loadJsonResource(loader, uri, context));
26
27
  }
28
+ export async function loadBinaryResource(loader, uri, context, options) {
29
+ return loadBoundedBinaryResource(loader, uri, context, options?.maxItemBytes ?? DEFAULT_MAX_MEDIA_ITEM_BYTES, options?.signal ?? context?.signal);
30
+ }
27
31
  function errorMessage(error) {
28
32
  return error instanceof Error ? error.message : String(error);
29
33
  }
@@ -0,0 +1,11 @@
1
+ import type { AgentConfig, ModelCapabilities, ModelConfig, ProviderRequestOptions, RunOptions, StructuredOutputOptions } from "./contracts.js";
2
+ export declare const DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES = 65536;
3
+ export declare const DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH = 128;
4
+ export declare class StructuredOutputError extends Error {
5
+ readonly code: "invalid_schema" | "unsupported_model" | "schema_too_large" | "invalid_name";
6
+ constructor(code: StructuredOutputError["code"], message: string);
7
+ }
8
+ export declare function modelSupportsStructuredOutput(capabilities?: ModelCapabilities): boolean;
9
+ export declare function validateStructuredOutputOptions(options: StructuredOutputOptions): StructuredOutputOptions;
10
+ export declare function assertStructuredOutputRequestSupported(model: ModelConfig, options?: ProviderRequestOptions): void;
11
+ export declare function resolveRunProviderOptions(runOptions: Pick<RunOptions, "providerOptions" | "loop">, config: Pick<AgentConfig, "providerOptions" | "loop">): ProviderRequestOptions | undefined;
@@ -0,0 +1,59 @@
1
+ import { assertJsonObject } from "./config.js";
2
+ import { mergeProviderRequestOptions } from "./provider-request-policy.js";
3
+ export const DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES = 65_536;
4
+ export const DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH = 128;
5
+ export class StructuredOutputError extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = "StructuredOutputError";
10
+ this.code = code;
11
+ }
12
+ }
13
+ export function modelSupportsStructuredOutput(capabilities) {
14
+ const mode = capabilities?.structuredOutput;
15
+ return mode === true || mode === "json_schema";
16
+ }
17
+ export function validateStructuredOutputOptions(options) {
18
+ const name = options.name.trim();
19
+ if (!name)
20
+ throw new StructuredOutputError("invalid_name", "structuredOutput.name is required");
21
+ if (name.length > DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH) {
22
+ throw new StructuredOutputError("invalid_name", `structuredOutput.name exceeded ${DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH} characters`);
23
+ }
24
+ try {
25
+ assertJsonObject(options.schema, "structuredOutput.schema");
26
+ }
27
+ catch (error) {
28
+ throw new StructuredOutputError("invalid_schema", error instanceof Error ? error.message : "structuredOutput.schema must be a JSON object");
29
+ }
30
+ const schemaBytes = new TextEncoder().encode(JSON.stringify(options.schema)).byteLength;
31
+ if (schemaBytes > DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES) {
32
+ throw new StructuredOutputError("schema_too_large", `structuredOutput.schema exceeded ${DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES} bytes`);
33
+ }
34
+ return { ...options, name, schema: options.schema };
35
+ }
36
+ export function assertStructuredOutputRequestSupported(model, options) {
37
+ if (!options?.structuredOutput)
38
+ return;
39
+ validateStructuredOutputOptions(options.structuredOutput);
40
+ if (!modelSupportsStructuredOutput(model.capabilities)) {
41
+ throw new StructuredOutputError("unsupported_model", `Model ${model.provider}/${model.model} does not declare structuredOutput capability; `
42
+ + `set loop.structuredOutputMode to "artifact-loop" or choose a capable model`);
43
+ }
44
+ }
45
+ function isGenerateValidateReviseLoopOptions(loop) {
46
+ return typeof loop === "object" && loop !== null && "strategy" in loop && loop.strategy === "generate-validate-revise";
47
+ }
48
+ export function resolveRunProviderOptions(runOptions, config) {
49
+ const merged = mergeProviderRequestOptions(config.providerOptions, runOptions.providerOptions);
50
+ const loop = runOptions.loop ?? config.loop;
51
+ if (!isGenerateValidateReviseLoopOptions(loop))
52
+ return merged;
53
+ if (loop.structuredOutputMode === "artifact-loop" || !loop.structuredOutput)
54
+ return merged;
55
+ return mergeProviderRequestOptions(merged, {
56
+ structuredOutput: validateStructuredOutputOptions(loop.structuredOutput),
57
+ });
58
+ }
59
+ //# sourceMappingURL=structured-output.js.map