@arnilo/prism 0.0.3 → 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.
Files changed (131) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +62 -26
  3. package/dist/agent-loops.d.ts +8 -1
  4. package/dist/agent-loops.js +57 -11
  5. package/dist/agents.js +212 -32
  6. package/dist/checkpoints.d.ts +11 -0
  7. package/dist/checkpoints.js +144 -0
  8. package/dist/cli-init.d.ts +41 -0
  9. package/dist/cli-init.js +390 -0
  10. package/dist/cli-runner.d.ts +7 -1
  11. package/dist/cli-runner.js +13 -1
  12. package/dist/compaction.js +9 -1
  13. package/dist/content.d.ts +121 -0
  14. package/dist/content.js +538 -0
  15. package/dist/contracts.d.ts +236 -11
  16. package/dist/contracts.js +8 -0
  17. package/dist/event-multiplexer.d.ts +23 -0
  18. package/dist/event-multiplexer.js +136 -0
  19. package/dist/execution-policy.d.ts +28 -0
  20. package/dist/execution-policy.js +24 -0
  21. package/dist/feedback.d.ts +48 -0
  22. package/dist/feedback.js +230 -0
  23. package/dist/index.d.ts +20 -6
  24. package/dist/index.js +13 -5
  25. package/dist/input.js +11 -1
  26. package/dist/leases.d.ts +8 -0
  27. package/dist/leases.js +111 -0
  28. package/dist/node/agent-definitions.js +3 -5
  29. package/dist/node/config.d.ts +1 -0
  30. package/dist/node/config.js +5 -3
  31. package/dist/node/contribution-discovery.js +5 -8
  32. package/dist/node/session-store-jsonl.js +8 -5
  33. package/dist/node/settings.js +2 -2
  34. package/dist/node/trust.js +2 -4
  35. package/dist/observability.d.ts +3 -0
  36. package/dist/observability.js +18 -0
  37. package/dist/providers/media.d.ts +44 -0
  38. package/dist/providers/media.js +126 -0
  39. package/dist/providers/openai-compatible.js +18 -119
  40. package/dist/providers/openai-primitives.d.ts +9 -0
  41. package/dist/providers/openai-primitives.js +129 -0
  42. package/dist/providers/transport.d.ts +40 -0
  43. package/dist/providers/transport.js +221 -0
  44. package/dist/redaction.js +40 -13
  45. package/dist/resources.d.ts +5 -0
  46. package/dist/resources.js +4 -0
  47. package/dist/structured-output.d.ts +11 -0
  48. package/dist/structured-output.js +59 -0
  49. package/dist/testing/feedback.d.ts +6 -0
  50. package/dist/testing/feedback.js +37 -0
  51. package/dist/testing/persistence-schema.d.ts +102 -0
  52. package/dist/testing/persistence-schema.js +487 -0
  53. package/dist/testing/provider-conformance.js +10 -1
  54. package/dist/testing/run-ledger-conformance.d.ts +33 -0
  55. package/dist/testing/run-ledger-conformance.js +178 -0
  56. package/dist/testing/session-store-conformance.d.ts +16 -0
  57. package/dist/testing/session-store-conformance.js +73 -0
  58. package/dist/tools.d.ts +17 -0
  59. package/dist/tools.js +29 -2
  60. package/docs/a2a.md +73 -0
  61. package/docs/agent-events.md +17 -10
  62. package/docs/agent-loops.md +11 -5
  63. package/docs/agent-session-runtime.md +15 -16
  64. package/docs/cli-rpc.md +36 -5
  65. package/docs/coding-agent-tools.md +43 -9
  66. package/docs/coding-security.md +88 -0
  67. package/docs/compaction-observational-memory.md +2 -0
  68. package/docs/context-and-skills.md +1 -0
  69. package/docs/credential-storage.md +177 -0
  70. package/docs/credentials-and-redaction.md +4 -3
  71. package/docs/database-persistence.md +52 -7
  72. package/docs/evaluations.md +122 -0
  73. package/docs/extensions.md +2 -2
  74. package/docs/host-security.md +33 -2
  75. package/docs/index.md +46 -18
  76. package/docs/input-and-prompt-assembly.md +6 -5
  77. package/docs/mcp-tools.md +184 -0
  78. package/docs/middleware-hooks.md +2 -0
  79. package/docs/migration.md +51 -28
  80. package/docs/model-registry.md +5 -3
  81. package/docs/multimodal-content.md +156 -0
  82. package/docs/observability.md +171 -0
  83. package/docs/performance.md +249 -1
  84. package/docs/persistence-credentials-multimodality-primitives.md +303 -0
  85. package/docs/postgres-persistence.md +143 -0
  86. package/docs/provider-conformance.md +18 -0
  87. package/docs/provider-layer.md +1 -1
  88. package/docs/provider-packages.md +2 -0
  89. package/docs/provider-primitives.md +281 -0
  90. package/docs/providers/ai-sdk.md +113 -0
  91. package/docs/providers/kimi.md +1 -0
  92. package/docs/providers/neuralwatt.md +1 -0
  93. package/docs/providers/openai-compatible.md +2 -1
  94. package/docs/providers/openai.md +8 -1
  95. package/docs/providers/opencode-go.md +1 -0
  96. package/docs/providers/openrouter.md +1 -0
  97. package/docs/providers/zai.md +1 -0
  98. package/docs/public-contracts.md +13 -5
  99. package/docs/rag.md +113 -0
  100. package/docs/release-and-install.md +237 -30
  101. package/docs/resource-loading.md +14 -4
  102. package/docs/review-coverage-2026-07-14.md +260 -0
  103. package/docs/review-coverage-2026-07-15.md +193 -0
  104. package/docs/run-ledger-conformance.md +96 -0
  105. package/docs/runs-and-usage.md +43 -4
  106. package/docs/server.md +139 -0
  107. package/docs/session-store-conformance.md +16 -0
  108. package/docs/session-stores-and-branching.md +1 -0
  109. package/docs/settings-auth-trust-security.md +6 -5
  110. package/docs/sqlite-persistence.md +123 -0
  111. package/docs/structured-output.md +9 -0
  112. package/docs/supervisors.md +71 -0
  113. package/docs/tool-conformance.md +1 -0
  114. package/docs/tool-execution-primitives.md +374 -0
  115. package/docs/tools.md +39 -1
  116. package/docs/workflow-orchestration-primitives.md +581 -0
  117. package/docs/workflow-tui-primitives.md +5 -0
  118. package/docs/workflows.md +293 -0
  119. package/docs/working-and-semantic-memory.md +169 -0
  120. package/package.json +43 -5
  121. package/templates/init/README.md.tmpl +28 -0
  122. package/templates/init/env.example.tmpl +1 -0
  123. package/templates/init/gitignore.tmpl +11 -0
  124. package/templates/init/optional/evals-example.ts.tmpl +17 -0
  125. package/templates/init/optional/workflows-example.ts.tmpl +27 -0
  126. package/templates/init/package.json.tmpl +22 -0
  127. package/templates/init/providers.json +76 -0
  128. package/templates/init/src/agent.ts.tmpl +10 -0
  129. package/templates/init/src/index.ts.tmpl +12 -0
  130. package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
  131. package/templates/init/tsconfig.json.tmpl +15 -0
@@ -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
@@ -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
@@ -0,0 +1,102 @@
1
+ import type { PersistencePage, SessionEntry, SessionEntryQuery } from "../contracts.js";
2
+ /** Current shared persistence schema version for production database adapters. */
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
+ export interface PersistenceColumnDefinition {
7
+ readonly name: string;
8
+ readonly type: PersistenceColumnType;
9
+ readonly nullable?: boolean;
10
+ /** Column participates in tenant isolation boundaries when true. */
11
+ readonly tenantScoped?: boolean;
12
+ }
13
+ export interface PersistenceForeignKeyDefinition {
14
+ readonly columns: readonly string[];
15
+ readonly referencesTable: PersistenceTableName;
16
+ readonly referencesColumns: readonly string[];
17
+ /** When true, tenant_id must participate in the FK boundary for scoped tables. */
18
+ readonly tenantBound?: boolean;
19
+ }
20
+ export interface PersistenceTableDefinition {
21
+ readonly name: PersistenceTableName;
22
+ readonly columns: readonly PersistenceColumnDefinition[];
23
+ readonly primaryKey: readonly string[];
24
+ readonly uniqueKeys?: readonly (readonly string[])[];
25
+ readonly foreignKeys?: readonly PersistenceForeignKeyDefinition[];
26
+ }
27
+ export interface PersistenceIndexDefinition {
28
+ readonly name: string;
29
+ readonly table: PersistenceTableName;
30
+ readonly columns: readonly string[];
31
+ readonly unique?: boolean;
32
+ /** Human-readable query-plan purpose for adapter authors and tests. */
33
+ readonly purpose: string;
34
+ }
35
+ /** Dialect-neutral schema model shared by SQLite and PostgreSQL adapters. */
36
+ export interface PersistenceSchemaModel {
37
+ readonly version: number;
38
+ readonly tables: readonly PersistenceTableDefinition[];
39
+ readonly indexes: readonly PersistenceIndexDefinition[];
40
+ }
41
+ export interface PersistenceMigrationStep {
42
+ readonly version: number;
43
+ readonly name: string;
44
+ readonly description?: string;
45
+ }
46
+ /** Versioned migration expectations shared by production database adapters. */
47
+ export interface PersistenceMigrationContract {
48
+ readonly targetSchemaVersion: number;
49
+ readonly appliedMigrationsTable: PersistenceTableName;
50
+ readonly steps: readonly PersistenceMigrationStep[];
51
+ /** Advisory-lock or equivalent guidance for concurrent migration setup. */
52
+ readonly lockGuidance: string;
53
+ /** Least-privilege role guidance for migration vs runtime credentials. */
54
+ readonly leastPrivilegeGuidance: string;
55
+ }
56
+ /** Cursor key shapes adapters must index so pagination avoids full scans. */
57
+ export interface PersistencePaginationCursor {
58
+ readonly table: PersistenceTableName;
59
+ readonly columns: readonly string[];
60
+ readonly supportsOrder: readonly ("asc" | "desc")[];
61
+ readonly purpose: string;
62
+ }
63
+ /** Guidance adapters must follow: values are bound parameters, never interpolated. */
64
+ export declare 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.";
65
+ /** Canonical shared schema model for Tasks 2–3 adapter packages. */
66
+ export declare function createPersistenceSchemaModel(): PersistenceSchemaModel;
67
+ /** Canonical migration contract for production adapters. */
68
+ export declare function createPersistenceMigrationContract(): PersistenceMigrationContract;
69
+ /** Indexed cursor columns adapters must support for paginated reads. */
70
+ export declare function getPersistencePaginationCursors(): readonly PersistencePaginationCursor[];
71
+ /** Build a tenant-scoped unique key column list for adapter DDL. */
72
+ export declare function tenantScopedUniqueKey(baseColumns: readonly string[], tenantColumn?: "tenant_id" | "account_id" | "user_id"): readonly string[];
73
+ /** Assert a schema model includes required tables, tenant boundaries, and indexes. */
74
+ export declare function assertPersistenceSchemaModel(model: PersistenceSchemaModel): void;
75
+ /** Assert migration steps are strictly increasing and end at the target schema version. */
76
+ export declare function assertPersistenceMigrationContract(contract: PersistenceMigrationContract): void;
77
+ /**
78
+ * Assert a dialect-local adapter exposes the canonical table and index names.
79
+ * Adapters pass the table/index names their migration runner created.
80
+ */
81
+ export declare function assertAdapterSchemaMatchesModel(adapterTables: readonly string[], adapterIndexes: readonly string[], model?: PersistenceSchemaModel): void;
82
+ /** Guard adapter SQL tests: reject obvious value interpolation into statement text. */
83
+ export declare function assertParameterizedQuery(sql: string, boundValues: readonly unknown[]): void;
84
+ /** Simulate migration up + reopen: applied steps must match the contract in order. */
85
+ export declare function assertMigrationUpAndReopen(contract: PersistenceMigrationContract, appliedAfterUp: readonly {
86
+ readonly name: string;
87
+ readonly version: string;
88
+ }[], appliedAfterReopen: readonly {
89
+ readonly name: string;
90
+ readonly version: string;
91
+ }[]): void;
92
+ export interface PersistenceQueryConformanceFixture {
93
+ readonly seedEntries: (entries: readonly SessionEntry[]) => Promise<void> | void;
94
+ readonly queryEntries: (query: SessionEntryQuery) => Promise<PersistencePage<SessionEntry>>;
95
+ }
96
+ /** Assert cursor pagination returns stable pages without repeating rows. */
97
+ export declare function assertPersistenceQueryPaginationConforms(fixture: PersistenceQueryConformanceFixture, sessionId?: string): Promise<void>;
98
+ /** Assert tenant-filtered queries do not return rows from another tenant. */
99
+ export declare function assertTenantScopedQueryIsolation(queryByTenant: (tenantId: string) => Promise<readonly {
100
+ readonly tenantId?: string;
101
+ readonly id: string;
102
+ }[]>): Promise<void>;