@arnilo/prism 0.4.0 → 0.5.0

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 (171) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +23 -20
  3. package/dist/agent-run-state.d.ts +1 -2
  4. package/dist/agent-run-state.js +0 -3
  5. package/dist/agent-session/session/assemble.d.ts +6 -0
  6. package/dist/agent-session/session/assemble.js +391 -0
  7. package/dist/agent-session/session/persist.d.ts +28 -0
  8. package/dist/agent-session/session/persist.js +166 -0
  9. package/dist/agent-session/session/provider-round.d.ts +6 -0
  10. package/dist/agent-session/session/provider-round.js +231 -0
  11. package/dist/agent-session/session/tool-round.d.ts +31 -0
  12. package/dist/agent-session/session/tool-round.js +473 -0
  13. package/dist/agent-session/session/types.d.ts +115 -0
  14. package/dist/agent-session/session/types.js +5 -0
  15. package/dist/agent-session/session.d.ts +49 -43
  16. package/dist/agent-session/session.js +11 -1177
  17. package/dist/capture.d.ts +63 -0
  18. package/dist/capture.js +67 -0
  19. package/dist/cli-init.d.ts +18 -2
  20. package/dist/cli-init.js +2 -7
  21. package/dist/cli-runner.d.ts +2 -2
  22. package/dist/cli-runner.js +45 -9
  23. package/dist/content.d.ts +3 -3
  24. package/dist/content.js +3 -1
  25. package/dist/contracts-core/agent.d.ts +2 -0
  26. package/dist/contracts-core/batch.d.ts +97 -0
  27. package/dist/contracts-core/batch.js +65 -0
  28. package/dist/contracts-core/content.d.ts +72 -1
  29. package/dist/contracts-core/embeddings.d.ts +30 -0
  30. package/dist/contracts-core/embeddings.js +17 -0
  31. package/dist/contracts-core/images.d.ts +60 -0
  32. package/dist/contracts-core/images.js +17 -0
  33. package/dist/contracts-core/moderation.d.ts +46 -0
  34. package/dist/contracts-core/moderation.js +34 -0
  35. package/dist/contracts-core/speech.d.ts +39 -0
  36. package/dist/contracts-core/speech.js +17 -0
  37. package/dist/contracts-core/transcription.d.ts +48 -0
  38. package/dist/contracts-core/transcription.js +17 -0
  39. package/dist/contracts-core/video.d.ts +61 -0
  40. package/dist/contracts-core/video.js +17 -0
  41. package/dist/contracts-core.d.ts +7 -0
  42. package/dist/contracts-core.js +7 -0
  43. package/dist/index.d.ts +5 -3
  44. package/dist/index.js +4 -3
  45. package/dist/node/agent-definitions.d.ts +1 -8
  46. package/dist/node/agent-definitions.js +0 -34
  47. package/dist/node/settings.d.ts +0 -1
  48. package/dist/node/settings.js +0 -5
  49. package/dist/pinned-fetch.js +29 -3
  50. package/dist/provider-events.js +3 -4
  51. package/dist/providers/media.d.ts +1 -2
  52. package/dist/providers/media.js +1 -4
  53. package/dist/rpc.d.ts +1 -1
  54. package/dist/rpc.js +4 -4
  55. package/dist/testing/provider-conformance.d.ts +114 -5
  56. package/dist/testing/provider-conformance.js +342 -0
  57. package/dist/testing/tool-effect-store-conformance.d.ts +0 -1
  58. package/dist/testing/tool-effect-store-conformance.js +0 -3
  59. package/dist/thinking.d.ts +48 -9
  60. package/dist/thinking.js +134 -8
  61. package/docs/0.1.0-readiness.md +3 -3
  62. package/docs/a2a.md +2 -2
  63. package/docs/acp.md +3 -3
  64. package/docs/ag-ui-adoption.md +1 -1
  65. package/docs/ag-ui.md +1 -2
  66. package/docs/agent-definitions.md +1 -1
  67. package/docs/agent-events.md +5 -5
  68. package/docs/agent-identity.md +13 -2
  69. package/docs/audit-export.md +3 -3
  70. package/docs/batch-jobs.md +120 -0
  71. package/docs/cli-rpc.md +20 -9
  72. package/docs/coding-agent-tools.md +19 -19
  73. package/docs/coding-review-and-diagnostics.md +2 -2
  74. package/docs/coding-security.md +4 -4
  75. package/docs/coding-workspaces.md +2 -2
  76. package/docs/computer-use-linux.md +13 -2
  77. package/docs/context-and-skills.md +1 -1
  78. package/docs/conversations.md +4 -4
  79. package/docs/credential-storage.md +11 -7
  80. package/docs/credentials-and-redaction.md +1 -1
  81. package/docs/data-classification.md +1 -1
  82. package/docs/database-persistence.md +4 -4
  83. package/docs/dev-inspector.md +6 -6
  84. package/docs/device-adapters.md +2 -2
  85. package/docs/diagrams.md +1 -1
  86. package/docs/document-reader.md +6 -6
  87. package/docs/documents.md +5 -4
  88. package/docs/embeddings.md +112 -0
  89. package/docs/enterprise-postgres-state.md +7 -7
  90. package/docs/evaluations.md +8 -8
  91. package/docs/extensions.md +3 -3
  92. package/docs/forge-integration.md +3 -3
  93. package/docs/graft.md +2 -2
  94. package/docs/guardrails.md +1 -1
  95. package/docs/host-security.md +15 -15
  96. package/docs/image-generation.md +129 -0
  97. package/docs/impeccable.md +5 -3
  98. package/docs/index.md +60 -33
  99. package/docs/indexed-code-search.md +2 -2
  100. package/docs/language-intelligence.md +4 -4
  101. package/docs/live-testing.md +126 -0
  102. package/docs/mcp-tools.md +43 -12
  103. package/docs/middleware-hooks.md +1 -1
  104. package/docs/migrate-to-0.4.md +3 -3
  105. package/docs/migrate-to-0.5.md +122 -0
  106. package/docs/migration.md +29 -1
  107. package/docs/model-registry.md +38 -0
  108. package/docs/model-routing.md +5 -5
  109. package/docs/moderation.md +117 -0
  110. package/docs/multi-agent-patterns.md +4 -4
  111. package/docs/multimodal-content.md +26 -2
  112. package/docs/obscura.md +2 -2
  113. package/docs/observability.md +32 -7
  114. package/docs/openapi-tools.md +13 -3
  115. package/docs/operations.md +11 -0
  116. package/docs/performance.md +7 -7
  117. package/docs/persistence-credentials-multimodality-primitives.md +6 -6
  118. package/docs/policy-and-audit.md +17 -7
  119. package/docs/ponytail.md +1 -1
  120. package/docs/postgres-persistence.md +5 -5
  121. package/docs/process-sessions.md +2 -2
  122. package/docs/prompt-registry.md +7 -7
  123. package/docs/provider-caching.md +4 -0
  124. package/docs/provider-conformance.md +23 -1
  125. package/docs/provider-packages.md +39 -3
  126. package/docs/provider-primitives.md +1 -1
  127. package/docs/provider-request-policies.md +1 -1
  128. package/docs/providers/ai-sdk.md +15 -3
  129. package/docs/providers/alibaba.md +5 -1
  130. package/docs/providers/anthropic.md +4 -0
  131. package/docs/providers/azure.md +17 -1
  132. package/docs/providers/bedrock.md +15 -0
  133. package/docs/providers/clinepass.md +4 -0
  134. package/docs/providers/commandcode.md +253 -0
  135. package/docs/providers/deepseek.md +4 -0
  136. package/docs/providers/google.md +4 -0
  137. package/docs/providers/hyper.md +284 -0
  138. package/docs/providers/kimi.md +4 -0
  139. package/docs/providers/neuralwatt.md +4 -0
  140. package/docs/providers/ollama.md +15 -0
  141. package/docs/providers/openai-compatible.md +4 -0
  142. package/docs/providers/openai.md +4 -0
  143. package/docs/providers/opencode-go.md +4 -0
  144. package/docs/providers/openrouter.md +5 -1
  145. package/docs/providers/vertex.md +16 -0
  146. package/docs/providers/xai.md +4 -0
  147. package/docs/providers/zai.md +4 -0
  148. package/docs/rag.md +26 -4
  149. package/docs/release-and-install.md +103 -46
  150. package/docs/resource-loading.md +1 -1
  151. package/docs/runs-and-usage.md +14 -2
  152. package/docs/server.md +5 -5
  153. package/docs/settings-auth-trust-security.md +7 -5
  154. package/docs/sheets.md +2 -2
  155. package/docs/speech.md +126 -0
  156. package/docs/sqlite-persistence.md +4 -4
  157. package/docs/supervisors.md +3 -3
  158. package/docs/thinking-and-reasoning.md +93 -60
  159. package/docs/tool-conformance.md +1 -1
  160. package/docs/tool-execution-primitives.md +8 -8
  161. package/docs/tools.md +4 -4
  162. package/docs/web-tools.md +1 -1
  163. package/docs/wiki.md +1 -1
  164. package/docs/work-artifacts-and-review.md +17 -6
  165. package/docs/work-connectors.md +4 -4
  166. package/docs/work-tools.md +5 -5
  167. package/docs/workflow-orchestration-primitives.md +11 -11
  168. package/docs/workflows.md +5 -5
  169. package/package.json +11 -8
  170. package/templates/init/providers.json +24 -8
  171. package/docs/antigravity-agent.md +0 -207
@@ -9,8 +9,16 @@ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
9
9
  * Compat mapping families used by ≥2 packages, or explicit no-op for host-owned adapters.
10
10
  * Provider packages keep unique escape hatches (budgets, keep/all, tool_stream) local.
11
11
  */
12
- export type ThinkingCompatFamily = "openai_reasoning" | "reasoning_effort" | "thinking_type" | "noop";
12
+ export type ThinkingCompatFamily = "openai_reasoning" | "reasoning_effort" | "thinking_type" | "google" | "output_config_effort" | "noop";
13
13
  export declare function isThinkingLevel(value: unknown): value is ThinkingLevel;
14
+ /**
15
+ * Parse a host thinking-level value without guessing: known levels canonicalize to
16
+ * `ThinkingLevel`, unknown non-empty strings pass through as opaque `{ opaque }`
17
+ * (forward-compat passthrough), invalid/empty/non-string input fails closed.
18
+ */
19
+ export declare function parseThinkingLevel(value: unknown): ThinkingLevel | {
20
+ readonly opaque: string;
21
+ } | undefined;
14
22
  /**
15
23
  * Normalize a host thinkingLevel string. Known levels are lowercased; other non-empty
16
24
  * strings pass through as opaque effort values for forward-compatible provider fields.
@@ -26,17 +34,48 @@ export declare function thinkingCompatFor(family: ThinkingCompatFamily, level: T
26
34
  * Per-turn patches win over prior compat via {@link mergeProviderRequestOptions}.
27
35
  */
28
36
  export declare function applyThinkingLevel(options: ProviderRequestOptions | undefined, level: ThinkingLevel | string, family?: ThinkingCompatFamily): ProviderRequestOptions;
37
+ /**
38
+ * Declared portable thinking levels for a model, if any (ascending ladder order).
39
+ * `undefined` means the provider declares no subset — forward-compat passthrough.
40
+ */
41
+ export declare function thinkingLevelsForModel(model: Pick<ModelConfig, "provider" | "compat" | "capabilities">): readonly string[] | undefined;
42
+ /**
43
+ * Strict declared-set membership (hosts fail closed on unknown levels).
44
+ * A model that declares no levels supports any value (forward-compat passthrough).
45
+ */
46
+ export declare function isSupportedThinkingLevel(model: Pick<ModelConfig, "provider" | "compat" | "capabilities">, level: unknown): boolean;
47
+ /**
48
+ * Snap a portable level to a model's declared set (design record §2):
49
+ * in-set → unchanged; below the declared minimum → up to the minimum
50
+ * (never silently disable what cannot be disabled); otherwise nearest declared
51
+ * level by ladder distance with ties breaking up; undeclared levels and
52
+ * undeclared sets pass through. Provider-documented snap tables
53
+ * (deepseek, Z.AI GLM-5.2, clinepass slots) override this generic fallback
54
+ * inside their own resolvers.
55
+ */
56
+ export declare function snapThinkingLevel(model: Pick<ModelConfig, "provider" | "compat" | "capabilities">, level: ThinkingLevel | string): ThinkingLevel | string;
57
+ /**
58
+ * Model-aware thinking-level application (design record §5). Resolves the family
59
+ * stamp-first (`compat.thinkingFamily` → inference → `capabilities.reasoning`),
60
+ * snaps the level to the model's declared set, and merges the compat patch
61
+ * per-turn-wins. Returns options unchanged for non-reasoning models — never
62
+ * invents a field where the model declares no thinking support.
63
+ */
64
+ export declare function applyThinkingLevelForModel(options: ProviderRequestOptions | undefined, level: ThinkingLevel | string, model: Pick<ModelConfig, "provider" | "compat" | "capabilities">): ProviderRequestOptions;
29
65
  /**
30
66
  * Best-effort family inference from model metadata without a second options tree.
31
- * Prefer an explicit family in hosts/use-case workers when the provider is known.
67
+ * Prefer an explicit `compat.thinkingFamily` stamp in host/use-case workers when
68
+ * the provider is known; inference is the fallback (stamp-first).
32
69
  *
33
70
  * Heuristics (ordered):
34
- * 1. Existing `compat.thinking` object → `thinking_type`
35
- * 2. Existing `compat.reasoning` → `openai_reasoning`
36
- * 3. Existing `compat.reasoning_effort` → `reasoning_effort`
37
- * 4. Provider id starting with `openai` → `openai_reasoning`
38
- * 5. Provider id `neuralwatt` → `reasoning_effort`
39
- * 6. `capabilities.reasoning` → `reasoning_effort` (portable string field)
40
- * 7. Else `noop`
71
+ * 1. `compat.thinkingFamily` stamp → itself
72
+ * 2. Existing `compat.thinking` object → `thinking_type`
73
+ * 3. Existing `compat.thinkingConfig` object/boolean → `google`
74
+ * 4. Existing `compat.reasoning` → `openai_reasoning`
75
+ * 5. Existing `compat.reasoning_effort` → `reasoning_effort`
76
+ * 6. Provider id starting with `openai` → `openai_reasoning`
77
+ * 7. Provider id `neuralwatt` → `reasoning_effort`
78
+ * 8. `capabilities.reasoning` → `reasoning_effort` (portable string field)
79
+ * 9. Else `noop`
41
80
  */
42
81
  export declare function thinkingFamilyForModel(model: Pick<ModelConfig, "provider" | "compat" | "capabilities">): ThinkingCompatFamily;
package/dist/thinking.js CHANGED
@@ -7,6 +7,40 @@ export const THINKING_LEVELS = ["none", "minimal", "low", "medium", "high", "xhi
7
7
  export function isThinkingLevel(value) {
8
8
  return typeof value === "string" && THINKING_LEVELS.includes(value);
9
9
  }
10
+ const LEVEL_RANK = {
11
+ none: 0,
12
+ minimal: 1,
13
+ low: 2,
14
+ medium: 3,
15
+ high: 4,
16
+ xhigh: 5,
17
+ max: 6,
18
+ };
19
+ function isThinkingFamily(value) {
20
+ return (typeof value === "string" &&
21
+ (value === "openai_reasoning" ||
22
+ value === "reasoning_effort" ||
23
+ value === "thinking_type" ||
24
+ value === "google" ||
25
+ value === "output_config_effort" ||
26
+ value === "noop"));
27
+ }
28
+ function thinkingLevelRank(level) {
29
+ return isThinkingLevel(level) ? LEVEL_RANK[level] : undefined;
30
+ }
31
+ /**
32
+ * Parse a host thinking-level value without guessing: known levels canonicalize to
33
+ * `ThinkingLevel`, unknown non-empty strings pass through as opaque `{ opaque }`
34
+ * (forward-compat passthrough), invalid/empty/non-string input fails closed.
35
+ */
36
+ export function parseThinkingLevel(value) {
37
+ if (typeof value !== "string")
38
+ return undefined;
39
+ const normalized = normalizeThinkingLevel(value);
40
+ if (!normalized)
41
+ return undefined;
42
+ return isThinkingLevel(normalized) ? normalized : { opaque: normalized };
43
+ }
10
44
  /**
11
45
  * Normalize a host thinkingLevel string. Known levels are lowercased; other non-empty
12
46
  * strings pass through as opaque effort values for forward-compatible provider fields.
@@ -32,6 +66,10 @@ export function thinkingCompatFor(family, level) {
32
66
  return { reasoning_effort: normalized };
33
67
  case "thinking_type":
34
68
  return { thinking: { type: normalized === "none" ? "disabled" : "enabled" } };
69
+ case "google":
70
+ return { thinkingLevel: normalized };
71
+ case "output_config_effort":
72
+ return { output_config: { effort: normalized } };
35
73
  default: {
36
74
  const _exhaustive = family;
37
75
  return _exhaustive;
@@ -62,23 +100,111 @@ export function applyThinkingLevel(options, level, family = "reasoning_effort")
62
100
  }
63
101
  return mergeProviderRequestOptions(options, { compat: patch });
64
102
  }
103
+ /**
104
+ * Declared portable thinking levels for a model, if any (ascending ladder order).
105
+ * `undefined` means the provider declares no subset — forward-compat passthrough.
106
+ */
107
+ export function thinkingLevelsForModel(model) {
108
+ return model.capabilities?.thinkingLevels;
109
+ }
110
+ /**
111
+ * Strict declared-set membership (hosts fail closed on unknown levels).
112
+ * A model that declares no levels supports any value (forward-compat passthrough).
113
+ */
114
+ export function isSupportedThinkingLevel(model, level) {
115
+ const parsed = parseThinkingLevel(level);
116
+ if (!parsed)
117
+ return false;
118
+ const declared = thinkingLevelsForModel(model);
119
+ if (!declared || declared.length === 0)
120
+ return true;
121
+ const value = typeof parsed === "string" ? parsed : parsed.opaque;
122
+ return declared.includes(value);
123
+ }
124
+ /**
125
+ * Snap a portable level to a model's declared set (design record §2):
126
+ * in-set → unchanged; below the declared minimum → up to the minimum
127
+ * (never silently disable what cannot be disabled); otherwise nearest declared
128
+ * level by ladder distance with ties breaking up; undeclared levels and
129
+ * undeclared sets pass through. Provider-documented snap tables
130
+ * (deepseek, Z.AI GLM-5.2, clinepass slots) override this generic fallback
131
+ * inside their own resolvers.
132
+ */
133
+ export function snapThinkingLevel(model, level) {
134
+ const normalized = normalizeThinkingLevel(String(level));
135
+ if (!normalized)
136
+ return String(level);
137
+ const declared = thinkingLevelsForModel(model);
138
+ if (!declared || declared.length === 0)
139
+ return normalized;
140
+ if (declared.includes(normalized))
141
+ return normalized;
142
+ const rank = thinkingLevelRank(normalized);
143
+ const ranked = declared
144
+ .map((entry) => ({ entry, rank: thinkingLevelRank(entry) }))
145
+ .filter((entry) => entry.rank != null);
146
+ if (rank == null || ranked.length === 0)
147
+ return normalized;
148
+ const minRank = Math.min(...ranked.map(({ rank: r }) => r));
149
+ if (rank < minRank)
150
+ return ranked.find(({ rank: r }) => r === minRank).entry;
151
+ let best = ranked[0].entry;
152
+ let bestDistance = Number.POSITIVE_INFINITY;
153
+ let bestRank = Number.NEGATIVE_INFINITY;
154
+ for (const { entry, rank: candidateRank } of ranked) {
155
+ const distance = Math.abs(candidateRank - rank);
156
+ if (distance < bestDistance || (distance === bestDistance && candidateRank > bestRank)) {
157
+ best = entry;
158
+ bestDistance = distance;
159
+ bestRank = candidateRank;
160
+ }
161
+ }
162
+ return best;
163
+ }
164
+ /**
165
+ * Model-aware thinking-level application (design record §5). Resolves the family
166
+ * stamp-first (`compat.thinkingFamily` → inference → `capabilities.reasoning`),
167
+ * snaps the level to the model's declared set, and merges the compat patch
168
+ * per-turn-wins. Returns options unchanged for non-reasoning models — never
169
+ * invents a field where the model declares no thinking support.
170
+ */
171
+ export function applyThinkingLevelForModel(options, level, model) {
172
+ const normalized = normalizeThinkingLevel(String(level));
173
+ if (!normalized)
174
+ return options ?? {};
175
+ const family = model.compat?.thinkingFamily != null && isThinkingFamily(model.compat.thinkingFamily)
176
+ ? model.compat.thinkingFamily
177
+ : thinkingFamilyForModel(model);
178
+ if (family === "noop")
179
+ return options ?? {};
180
+ return applyThinkingLevel(options, snapThinkingLevel(model, normalized), family);
181
+ }
65
182
  /**
66
183
  * Best-effort family inference from model metadata without a second options tree.
67
- * Prefer an explicit family in hosts/use-case workers when the provider is known.
184
+ * Prefer an explicit `compat.thinkingFamily` stamp in host/use-case workers when
185
+ * the provider is known; inference is the fallback (stamp-first).
68
186
  *
69
187
  * Heuristics (ordered):
70
- * 1. Existing `compat.thinking` object → `thinking_type`
71
- * 2. Existing `compat.reasoning` → `openai_reasoning`
72
- * 3. Existing `compat.reasoning_effort` → `reasoning_effort`
73
- * 4. Provider id starting with `openai` → `openai_reasoning`
74
- * 5. Provider id `neuralwatt` → `reasoning_effort`
75
- * 6. `capabilities.reasoning` → `reasoning_effort` (portable string field)
76
- * 7. Else `noop`
188
+ * 1. `compat.thinkingFamily` stamp → itself
189
+ * 2. Existing `compat.thinking` object → `thinking_type`
190
+ * 3. Existing `compat.thinkingConfig` object/boolean → `google`
191
+ * 4. Existing `compat.reasoning` → `openai_reasoning`
192
+ * 5. Existing `compat.reasoning_effort` → `reasoning_effort`
193
+ * 6. Provider id starting with `openai` → `openai_reasoning`
194
+ * 7. Provider id `neuralwatt` → `reasoning_effort`
195
+ * 8. `capabilities.reasoning` → `reasoning_effort` (portable string field)
196
+ * 9. Else `noop`
77
197
  */
78
198
  export function thinkingFamilyForModel(model) {
199
+ const stamp = model.compat?.thinkingFamily;
200
+ if (isThinkingFamily(stamp))
201
+ return stamp;
79
202
  const compat = model.compat ?? {};
80
203
  if (compat.thinking != null && typeof compat.thinking === "object")
81
204
  return "thinking_type";
205
+ if (compat.thinkingConfig != null && (typeof compat.thinkingConfig === "object" || typeof compat.thinkingConfig === "boolean")) {
206
+ return "google";
207
+ }
82
208
  if (compat.reasoning != null)
83
209
  return "openai_reasoning";
84
210
  if (compat.reasoning_effort != null)
@@ -1,6 +1,6 @@
1
1
  # 0.1.0 / 1.0 Readiness Gates
2
2
 
3
- Status: **0.4.0** is the current release line (plan 054 package consolidation: 11 active packages, family subpaths, `^0.4.0` peers); **0.3.3** was the terminal 0.3.x cut; **0.1.7** was the terminal 0.1.x baseline; **1.0** readiness remains operator-gated, not automatic.
3
+ Status: **0.5.0** is the current release line (dead-export removal cut: 10 active packages, family subpaths, `^0.5.0` peers); **0.3.3** was the terminal 0.3.x cut; **0.1.7** was the terminal 0.1.x baseline; **1.0** readiness remains operator-gated, not automatic.
4
4
 
5
5
  This page distills runnable readiness gates into one command-per-gate table.
6
6
  The **Last evidence** column records the 0.1.0-tree snapshot (plan 012 Tasks
@@ -20,7 +20,7 @@ Historical release lines (0.0.16 floor → 0.0.27 Phase 10 ACP interop → 0.1.0
20
20
  keep their per-phase evidence in the pages above; this page records the 0.2.6
21
21
  snapshot (plan 026) with the 0.1.x tables below as the historical record.
22
22
 
23
- ## Current line (0.4.0)
23
+ ## Current line (0.5.0)
24
24
 
25
25
  | Item | Status |
26
26
  |---|---|
@@ -66,7 +66,7 @@ snapshot (plan 026) with the 0.1.x tables below as the historical record.
66
66
  | Publish order + tarball validation | `node scripts/release.mjs publish --version 0.1.0 --dry-run --allow-dirty --allow-untagged` | 49/49 packages `dry-run` twice with byte-identical reports, deterministic dependency order, no failures (Task 7) | Operator (dry-run), CI |
67
67
  | Node 20 compatibility | CI `node20-compat` (build + public-import smoke) | all 21 root exports import cleanly on Node 20.20.2 | CI |
68
68
  | PostgreSQL suite | `PRISM_TEST_POSTGRES_URL="$DATABASE_URL" npm run test:postgres` | 0.1.0: Phase 7 conformance + Phase 12 restart-recovery + 74 workspace checks green against PostgreSQL 16 (Task 4 recording, re-run green at 0.1.0 on 2026-08-09); operator-gated | Operator |
69
- | Keychain suite | `PRISM_TEST_KEYCHAIN=1 npm test --workspace @arnilo/prism-credentials-node` (protected) | 28/28 green incl. native keychain round-trip against the OS secret-service backend (gnome-keyring, 2026-08-09) | Operator host |
69
+ | Keychain suite | `PRISM_TEST_KEYCHAIN=1 npm test --workspace @arnilo/prism-core/credentials/node` (protected) | 28/28 green incl. native keychain round-trip against the OS secret-service backend (gnome-keyring, 2026-08-09) | Operator host |
70
70
  | Live-provider suites | `npm run test:live` (protected) | **operator-gated** (requires credentials; `live-canaries.yml` blocked gate, `canary-report.json` retained) | Operator |
71
71
  | SAST | GitHub CodeQL | **operator-gated** (runs in CI workflow) | CI |
72
72
  | Signed, provenance publication | `npm run release:publish` (clean tagged tree, OIDC) | **operator-gated** (see "Remaining for 1.0") | Operator |
package/docs/a2a.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-supervisor` implements bounded A2A 1.0 over the JSON-RPC/HTTPS binding. Supported operations: `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, push-notification-config create/get/list/delete, and `GetExtendedAgentCard`. `client.streamMessage()` additionally exposes verified rich task/message events for frontend adapters while legacy `stream()` remains text-compatible. Agent Cards retain explicit ES256 verification. gRPC, HTTP+JSON, discovery registries, automatic JWK/OAuth fetching, and an internal task worker/store are absent.
5
+ `@arnilo/prism-core/runtime/supervisor` implements bounded A2A 1.0 over the JSON-RPC/HTTPS binding. Supported operations: `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, push-notification-config create/get/list/delete, and `GetExtendedAgentCard`. `client.streamMessage()` additionally exposes verified rich task/message events for frontend adapters while legacy `stream()` remains text-compatible. Agent Cards retain explicit ES256 verification. gRPC, HTTP+JSON, discovery registries, automatic JWK/OAuth fetching, and an internal task worker/store are absent.
6
6
 
7
7
  ## When to use it
8
8
 
@@ -41,7 +41,7 @@ Parts, messages, artifacts, histories, metadata, and aggregate responses are unt
41
41
 
42
42
  ## AG-UI server-side exposure (Task 13, 0.0.26)
43
43
 
44
- `createAgUiA2AServer()` in `@arnilo/prism-ag-ui` fronts one host-selected **local AG-UI agent** as an A2A 1.0 server, the reverse direction of `createAgUiA2AAdapter()`: remote A2A clients start and stream local runs through the same AG-UI input allow-list and event mapper as the AG-UI SSE path (same projection, redaction, and byte caps). It reuses this package's `createA2AHandler` transport/lifecycle; it creates no second runtime, task store, or worker. Requires the optional `@arnilo/prism-supervisor` peer (imported lazily; plain `@arnilo/prism-ag-ui` imports keep working without it).
44
+ `createAgUiA2AServer()` in `@arnilo/prism-ag-ui` fronts one host-selected **local AG-UI agent** as an A2A 1.0 server, the reverse direction of `createAgUiA2AAdapter()`: remote A2A clients start and stream local runs through the same AG-UI input allow-list and event mapper as the AG-UI SSE path (same projection, redaction, and byte caps). It reuses this package's `createA2AHandler` transport/lifecycle; it creates no second runtime, task store, or worker. Requires the optional `@arnilo/prism-core/runtime/supervisor` peer (imported lazily; plain `@arnilo/prism-ag-ui` imports keep working without it).
45
45
 
46
46
  ```ts
47
47
  import { createAgentEventSourceAgUiReplay, createAgUiA2AServer } from "@arnilo/prism-ag-ui";
package/docs/acp.md CHANGED
@@ -112,7 +112,7 @@ const agent = createPrismAcpAgent({
112
112
 
113
113
  - **Seam = capability.** Wiring `sessions.load` advertises `loadSession`; removing it withdraws the method. There is no separate capability flag to keep in sync — the freeze manifest's advertise-when matrix is enforced by construction and asserted by `scripts/phase10-conformance.test.mjs`.
114
114
  - **Transcript replay (F2).** When `sessions.transcript` is wired, `session/load` and `session/resume` replay `user_message_chunk`/`agent_message_chunk` text chunks (from `SessionEntry`s with `kind: "message"` and a user/assistant role, text blocks only) before returning `sessionState`. Each chunk passes the shared redactor and is truncated at `maxTextBytes`; replay stops at `maxReplayEvents` chunks and counts against the stream event/byte caps (an oversized transcript fails the load/resume request closed). Absent seam = no replay, behavior unchanged.
115
- - **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. `createAcpFilesystemOperations` from `@arnilo/prism-coding-agent` maps that filesystem seam onto the coding tools' `read`/`write`/`edit` operations. This editor-buffer mode is intentionally hybrid: `repo_list`, `repo_search`, `glob`, `delete`, and `move` remain disk-backed unless the host supplies separate operations; binary/image/document handling never falls back to local disk. Host repo operations remain default when the client fs is absent.
115
+ - **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. `createAcpFilesystemOperations` from `@arnilo/prism-coding-tools/agent` maps that filesystem seam onto the coding tools' `read`/`write`/`edit` operations. This editor-buffer mode is intentionally hybrid: `repo_list`, `repo_search`, `glob`, `delete`, and `move` remain disk-backed unless the host supplies separate operations; binary/image/document handling never falls back to local disk. Host repo operations remain default when the client fs is absent.
116
116
  - **Spawnable ACP coding registry (Task 6).** `@arnilo/prism-acp-agent` wires `createAcpClientFilesystem` and creates a separate coding tool registry per ACP session when the client advertises `fs/read_text_file` or `fs/write_text_file`. That session's `read`/`write`/`edit` operations use editor buffers; without fs advertisement, the existing disk registry is used. `shell`, repository search/list/glob, `delete`, and `move` remain disk-backed in this hybrid mode. Durable approvals resolve the same per-session agent, so one session cannot resume through another session's buffer adapter.
117
117
  - **Modes and config options are a pure host overlay.** The agent stores only a thin per-session registry; `apply`/`onChange` hooks narrow the host's own behavior. Mode switches can narrow or host-authorized widen — never a parallel policy evaluator, never a client-enabled tool.
118
118
  - **Lifecycle wiring.** Pass your `createCodingLifecycleEmitter()` as `coding.lifecycle`; `file_changed` etc. then flow to streaming sessions. `configuration_changed` broadcasts `config_option_update` (agent-message fallback if the SDK rejects the kind).
@@ -152,14 +152,14 @@ const agent = createPrismAcpAgent({
152
152
  - **Deny-closed by default.** Unknown mode ids, unadvertised methods, unprojected lifecycle events, oversize diffs/locations/media, thrown projection hooks, and failed elicitation all fail closed. Raw tool arguments/results are never sent unless a projection allow-list says otherwise.
153
153
  - **Slash commands (F9).** `commands.list` is a host-owned slash-command list (not derived from the tool registry). The agent emits `available_commands_update` on session start (`session/new`, `session/load`, `session/resume`). Mid-session refresh is not in this release — re-list by starting a session. Names, descriptions, and input hints pass the shared redactor; the list is sliced at `acpCommandsPerUpdate`. Absent seam or a thrown list ⇒ no update.
154
154
  - **Projected images (F8).** `AgUiProjection.toolResult` may return `{ type: "image", data, mimeType }` (return-type widening — existing string returns stay valid). The mapper emits `{ type: "content", content: { type: "image", data, mimeType } }` (SDK v1 `ToolCallContent` has no top-level image variant). `data` is the host-supplied base64; it is not redacted and not truncated — payloads over `acpImageBytes` are dropped. Default (no hook / non-image return) emits no image.
155
- - **Coding-tool projection (F7).** `createCodingToolProjection({ maxDiffBytes? })` is an opt-in `AgUiProjection` for first-party `@arnilo/prism-coding-agent` results: `edit` → `toolDiff` (`path` + unified `patch` as `newText`) and `toolLocations` (`path` + `firstChangedLine`); `write` and `delete` → `toolLocations` (`path` only); `move` → destination `toolLocations` (`metadata.to`, with `from` fallback). No delete/move diff is fabricated. Pass as `projection: createCodingToolProjection()` on the agent/mapper. Mapper still redacts and enforces `acpDiffBytes` / `acpLocationsPerUpdate`; optional `maxDiffBytes` pre-truncates the patch so a slightly-oversize edit is shortened instead of dropped. Without the factory, behavior is unchanged (deny-by-default).
155
+ - **Coding-tool projection (F7).** `createCodingToolProjection({ maxDiffBytes? })` is an opt-in `AgUiProjection` for first-party `@arnilo/prism-coding-tools/agent` results: `edit` → `toolDiff` (`path` + unified `patch` as `newText`) and `toolLocations` (`path` + `firstChangedLine`); `write` and `delete` → `toolLocations` (`path` only); `move` → destination `toolLocations` (`metadata.to`, with `from` fallback). No delete/move diff is fabricated. Pass as `projection: createCodingToolProjection()` on the agent/mapper. Mapper still redacts and enforces `acpDiffBytes` / `acpLocationsPerUpdate`; optional `maxDiffBytes` pre-truncates the patch so a slightly-oversize edit is shortened instead of dropped. Without the factory, behavior is unchanged (deny-by-default).
156
156
  - **No secrets.** Updates carry no raw file bodies, terminal output is capped by the Phase 9 chunk budget, and the shared redactor is applied before anything leaves the host. `permission_denied` never includes raw args.
157
157
  - **Performance.** The adapter is O(1) per update with no unbounded buffering; p95 targets (fs round trip 250 ms, mode switch 250 ms, terminal chunk ack 1000 ms, prompt first update 2000 ms, prompt end 30 s) are recorded by `scripts/benchmark-0.0.27.mjs` and gated in `scripts/budgets.json` `phase10`.
158
158
 
159
159
  ## Related APIs
160
160
 
161
161
  - [AG-UI](ag-ui.md): sibling frontend protocol; shared projection/redaction/caps and the same pending-decision model. This page is the full ACP reference.
162
- - [Coding agent tools](coding-agent-tools.md): the `CodingLifecycleEvent` source mapped here; `@arnilo/prism-coding-agent` `process.outputChunkBytes` caps terminal chunks.
162
+ - [Coding agent tools](coding-agent-tools.md): the `CodingLifecycleEvent` source mapped here; `@arnilo/prism-coding-tools/agent` `process.outputChunkBytes` caps terminal chunks.
163
163
  - [Agent events](agent-events.md): the durable `AgentEventSource`/replay story behind `session/load` and `session/resume`.
164
164
  - [Host security guide](host-security.md): fail-closed checklist rows for ACP boundaries (authorize, ownership, redaction, untrusted MCP).
165
165
  - [Migration guide](migration.md): 0.0.26 → 0.0.27 advertise/surface changes for hosts that parsed the old `initialize`.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- This page records Prism's compatibility review against official AG-UI `@ag-ui/core` **0.0.57** and the official repository at commit [`a40b5c0`](https://github.com/ag-ui-protocol/ag-ui/commit/a40b5c0824564eb2f9ab9edf2be43f355f42a3b8). It separates shipped transport/replay support from remaining work needed to claim full AG-UI support, including AG-UI fronting MCP and A2A agents.
5
+ This page records Prism's compatibility review against official AG-UI `@ag-ui/core` **0.0.59** and the official repository at commit [`a40b5c0`](https://github.com/ag-ui-protocol/ag-ui/commit/a40b5c0824564eb2f9ab9edf2be43f355f42a3b8). It separates shipped transport/replay support from remaining work needed to claim full AG-UI support, including AG-UI fronting MCP and A2A agents.
6
6
 
7
7
  Official material reviewed:
8
8
 
package/docs/ag-ui.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  `@arnilo/prism-ag-ui` is an optional, framework-free protocol adapter over Prism's existing redacted `AgentEvent`, session, durable-run, and persistence seams.
6
6
 
7
- - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.57** events and offers `createAgUiHandler()` (`Request` → SSE `Response`), compatible `createPersistenceAgUiReplay()` pages, distributed `createAgentEventSourceAgUiReplay()` follow, and explicit `createAgUiMcpAdapter()` / `createAgUiMcpAppHandler()` / `createAgUiA2AAdapter()` protocol handshakes.
7
+ - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.59** events and offers `createAgUiHandler()` (`Request` → SSE `Response`), compatible `createPersistenceAgUiReplay()` pages, distributed `createAgentEventSourceAgUiReplay()` follow, and explicit `createAgUiMcpAdapter()` / `createAgUiMcpAppHandler()` / `createAgUiA2AAdapter()` protocol handshakes.
8
8
  - `@arnilo/prism-ag-ui/acp` is the stable ACP **v1** sibling: `createAcpEventMapper()` and `createPrismAcpAgent()` over `@agentclientprotocol/sdk` **1.3.0** root exports. ACP is a protocol adapter — sessions, modes, MCP, fs/terminal, lifecycle mapping, and caps live on the host seams. See [ACP coding-host interop](acp.md) for the full reference; this page covers AG-UI only.
9
9
  - Core remains protocol-free. `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()` are generic durable-resume streams shared by adapters.
10
10
 
@@ -225,5 +225,4 @@ Defaults / hard caps: request 64 KiB / 1 MiB; input 128 / 1024 messages, 32 / 25
225
225
  - [MCP bridge/server](mcp-tools.md): `mcpApps` negotiation, bounded resources, and remote tool trust.
226
226
  - [A2A interoperability](a2a.md): verified rich task client and remote task lifecycle.
227
227
  - [Host security guide](host-security.md): authorization, ownership, redaction, and credential boundaries.
228
- - [Antigravity delegated agent](antigravity-agent.md): delegated Antigravity CLI execution with timeline step projection.
229
228
  - [Work artifacts and review](work-artifacts-and-review.md): durable artifact service that produces the co-work approval/progress/download-link events projected here.
@@ -224,7 +224,7 @@ Use `activateAllCapabilities: true` only while migrating old configs. It intenti
224
224
  - `ExtensionAPI.registerAgent(agent)` contributes an inert `AgentDefinition` programmatically; its `create()` (if present) is only invoked when the host runs it through `resolveAgentDefinition`. See [Extensions](extensions.md).
225
225
  - Bundle resolution is config over code: every seam lives on `AgentDefinition`, `AgentDefinitionResolutionContext`, or `ResolveAgentBundleOptions`. `systemPrompt` and `loop` are passed via `context.overrides` / `create()` rather than frontmatter.
226
226
  - Migration note: before Phase 38, a definition that omitted `tools` but had a tool scope could receive every scoped tool. Now omitted `tools`/`skills` activates none. Add explicit names to `tools` / `skills`; use `activateAllCapabilities: true` only while migrating old configs.
227
- - `parseAgentFile(text, path)` (re-exported from `@arnilo/prism`) is the stdlib-only frontmatter parser for `AGENT.md`. `parseContextFile` and `parseToolFile` parse colocated `CONTEXT.md` / tool descriptors inside the Node subpath.
227
+ - `parseAgentFile(text, path)` (re-exported from `@arnilo/prism`) is the stdlib-only frontmatter parser for `AGENT.md`. Colocated `CONTEXT.md` / tool-descriptor parsing is host-owned (the thin `parseContextFile`/`parseToolFile` helpers were removed in 0.5.0 — see `docs/migrate-to-0.5.md`).
228
228
  - Repo contributions (`<workspaceRoot>/.agents/{skills,tools}/`) are scanned by `discoverContributions` and passed via `repoContributions`. Repo `.agents/` is preserved as a shared contribution surface across every agent that operates on the same repository; multiple agents from different apps can work the same repo, and all share its repo-level skills.
229
229
 
230
230
  ## Security and performance notes
@@ -24,10 +24,10 @@ Event records preserve emission order within a run because the runtime drains pe
24
24
 
25
25
  ### Placement (FR-7 answer, 0.0.26)
26
26
 
27
- The durable `AgentEventSource` **stays in `@arnilo/prism-session-store-postgres`** for the 0.0.26 line and is importable from the package root (FR-6):
27
+ The durable `AgentEventSource` **stays in `@arnilo/prism-core/sessions/postgres`** for the 0.0.26 line and is importable from the package root (FR-6):
28
28
 
29
29
  ```ts
30
- import { createPostgresAgentEventSource } from "@arnilo/prism-session-store-postgres";
30
+ import { createPostgresAgentEventSource } from "@arnilo/prism-core/sessions/postgres";
31
31
  const source = createPostgresAgentEventSource({ pool, schema: "prism", cursorSecret });
32
32
  ```
33
33
 
@@ -35,11 +35,11 @@ PostgreSQL `LISTEN`/`NOTIFY` remains the **reference durable implementation**; `
35
35
 
36
36
  ### NATS JetStream adapter (FR-5)
37
37
 
38
- `@arnilo/prism-session-store-nats` ships a sibling durable `AgentEventSource` over NATS JetStream for JetStream backbones (Postgres remains the reference implementation):
38
+ `@arnilo/prism-core/sessions/nats` ships a sibling durable `AgentEventSource` over NATS JetStream for JetStream backbones (Postgres remains the reference implementation):
39
39
 
40
40
  ```ts
41
41
  import { connect } from "@nats-io/transport-node";
42
- import { createNatsAgentEventSource, createNatsJetStream } from "@arnilo/prism-session-store-nats";
42
+ import { createNatsAgentEventSource, createNatsJetStream } from "@arnilo/prism-core/sessions/nats";
43
43
 
44
44
  const nc = await connect({ servers: process.env.NATS_URL });
45
45
  const source = createNatsAgentEventSource({ connection: await createNatsJetStream(nc), stream: "prism_agent_events" });
@@ -249,4 +249,4 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
249
249
  - [Observability](observability.md): `ProviderTurnMetadata`; optional adapter builds one parented GenAI span tree from metadata-only lifecycle events and ignores message/progress deltas.
250
250
  - [Tools](tools.md): `tool_execution_*` variants.
251
251
  - [Compaction and retry policies](compaction-and-retry.md): `compaction_*` and `retry_scheduled` variants.
252
- - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`, `plan_changed`, `plan_removed`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes`, plan updates only to clients that advertised the UNSTABLE `plan` capability.
252
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-tools/agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`, `plan_changed`, `plan_removed`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes`, plan updates only to clients that advertised the UNSTABLE `plan` capability.
@@ -87,7 +87,7 @@ await agent.createSession().run("Summarize inbox", {
87
87
 
88
88
  Server / MCP / A2A authorize callbacks may include the same `identity` beside `ownership`. Handlers assert activity and ownership match before admitting work.
89
89
 
90
- ## OIDC/JWKS verifier adapter (`@arnilo/prism-credentials-node/oidc`)
90
+ ## OIDC/JWKS verifier adapter (`@arnilo/prism-core/credentials/node/oidc`)
91
91
 
92
92
  Optional `createOidcIdentityVerifier` turns a pinned issuer/audience and pinned JWKS URL into a core `IdentityVerifier` — one bounded reference adapter for hosts that already authenticate callers with OIDC JWTs (Entra, Keycloak, Auth0, …). Native `fetch` + WebCrypto only; no JOSE dependency.
93
93
 
@@ -102,7 +102,7 @@ Optional `createOidcIdentityVerifier` turns a pinned issuer/audience and pinned
102
102
  | `limits` | Bounded JWKS/claims knobs; `identity` reuses core identity caps |
103
103
 
104
104
  ```ts
105
- import { createOidcIdentityVerifier } from "@arnilo/prism-credentials-node/oidc";
105
+ import { createOidcIdentityVerifier } from "@arnilo/prism-core/credentials/node/oidc";
106
106
 
107
107
  const verifier = createOidcIdentityVerifier({
108
108
  issuer: "https://id.example.com/tenant",
@@ -132,6 +132,17 @@ Identity is optional. Hosts that only set `ownership` keep prior behavior. When
132
132
  - Checks are O(fields) and network-free in core; remote auth stays in the host verifier.
133
133
  - Raising hard caps requires updating `docs/_evidence/review-coverage-2026-07-23-phase-8.md`, tests, and docs.
134
134
 
135
+ ## Live probe (plans/064 Task 9)
136
+
137
+ The OIDC/JWKS identity verifier has an operator-gated live probe against a real issuer:
138
+
139
+ ```bash
140
+ PRISM_TEST_OIDC_ISSUER=https://id.example.com/tenant PRISM_TEST_OIDC_AUDIENCE=prism-api \
141
+ PRISM_TEST_OIDC_TOKEN=<real bearer token> npm test -w @arnilo/prism-core -- oidc-live
142
+ ```
143
+
144
+ Set `PRISM_TEST_OIDC_JWKS_URL` to pin a non-default JWKS URL (default `<issuer>/.well-known/jwks.json`). Probes: a valid token verifies against the live JWKS (1 fetch), a tampered token fails closed with `ERR_PRISM_OIDC_SIGNATURE` (error text never echoes the token), and a garbage token fails closed without JWKS traffic. Bounded to 1 real request. Registered in `scripts/live-matrix.json` as `core/oidc-live`.
145
+
135
146
  ## Related APIs
136
147
 
137
148
  - [Policy and audit](policy-and-audit.md)
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-policy` exports tenant-scoped audit records as signed, hash-chained
5
+ `@arnilo/prism-core/governance/policy` exports tenant-scoped audit records as signed, hash-chained
6
6
  batches: each record envelope is canonicalized (RFC 8785 semantics), hashed with
7
7
  SHA-256 including the prior digest, so records form a tamper-evident chain. A
8
8
  batch of chained records is wrapped in a manifest that a host-provided
@@ -69,7 +69,7 @@ canonical `record` payload. `verifyAuditBatch` returns `{ ok, errors, batch }`.
69
69
  ## Request/response example
70
70
 
71
71
  ```ts
72
- import { createAuditExporter, createMemoryAuditCursorStore } from "@arnilo/prism-policy";
72
+ import { createAuditExporter, createMemoryAuditCursorStore } from "@arnilo/prism-core/governance/policy";
73
73
 
74
74
  const exporter = createAuditExporter({
75
75
  source, // host: tenant-scoped record pages
@@ -87,7 +87,7 @@ const result = await exporter.exportNext({ tenantId: "acme", maxRecords: 1000 })
87
87
 
88
88
  ```ts
89
89
  import { readFileSync } from "node:fs";
90
- import { verifyAuditBatch } from "@arnilo/prism-policy";
90
+ import { verifyAuditBatch } from "@arnilo/prism-core/governance/policy";
91
91
 
92
92
  const artifactBytes = new Uint8Array(readFileSync("./acme-000001.json"));
93
93
  const verified = verifyAuditBatch({
@@ -0,0 +1,120 @@
1
+ # Batch jobs
2
+
3
+ ## What it does
4
+
5
+ `BatchJobsProvider` is the provider-neutral async batch contract (plan 061
6
+ Task 7): `submit` (requests + metadata → opaque job id), `status`, `cancel`, and
7
+ paged `results`, with a typed job-state union and a `pollBatch` backoff utility
8
+ exported standalone — core never loops or awaits completion for you. The
9
+ first-party adapter is [`createOpenAIBatchJobsProvider`](providers/openai.md)
10
+ (Files API JSONL upload + `/v1/batches` lifecycle). Offline conformance runs via
11
+ `runBatchJobsConformance` from `@arnilo/prism/testing/provider-conformance`.
12
+
13
+ ## When to use it
14
+
15
+ Use it for large asynchronous workloads that tolerate 24-hour completion
16
+ windows — offline scoring, backfills, bulk classification. Do not use it as a
17
+ scheduler or an orchestration saga: the contract is standalone by decision
18
+ (integration with the workflows saga is deferred — see plan 061 Further
19
+ Actions). Interactive low-latency requests belong on the normal provider path.
20
+
21
+ ## Inputs / request
22
+
23
+ | Call | Inputs | Meaning |
24
+ | --- | --- | --- |
25
+ | `submit` | `{ model, requests, metadata? }` | `requests` are provider-native `{ customId?, body: JsonObject }` items; bodies are opaque to the contract and inherit provider caps. |
26
+ | `status` | opaque job id | Current `BatchJob` snapshot (state union + provider counts + `raw`). |
27
+ | `cancel` | opaque job id | Best-effort cancellation; returns the transitioning job (`cancelling`). |
28
+ | `results` | job id + `{ cursor?, pageSize? }` | Page of per-request outcomes; cursor is an opaque continuation token. |
29
+ | `pollBatch` | provider + job id + `{ intervalMs?, backoffMultiplier?, maxIntervalMs?, maxAttempts? }` | Utility only — resolves on `completed`, throws typed on `failed`/`cancelled`/`expired`. |
30
+
31
+ Adapter options: `apiKey` (`CredentialValueSource` — resolved per call,
32
+ redacted from errors), `baseUrl`, `fetch` (fake transport for offline tests),
33
+ `headers`, `endpoint` (defaults to `/v1/chat/completions`),
34
+ `completionWindow` (defaults `24h`).
35
+
36
+ ## Outputs / response / events
37
+
38
+ | Field | Type | Meaning |
39
+ | --- | --- | --- |
40
+ | `job.state` | `"queued" \| "running" \| "cancelling" \| "completed" \| "failed" \| "cancelled" \| "expired"` | Neutral union; adapters map provider states (OpenAI `validating`→`queued`, `in_progress`/`finalizing`→`running`, …). `isBatchJobTerminal` classifies. |
41
+ | `job.id` | `string` | Opaque provider id — the contract never parses or scopes it. |
42
+ | `job.requestCounts` | `{ total, completed, failed }?` | Provider progress counts. |
43
+ | `job.raw` | `JsonObject?` | Provider-native job fields, unmodified, for audits. |
44
+ | `result.items[n]` | `{ customId, response?, error?, raw? }` | Per-request outcome; per-item failures ride alongside a `completed` job. |
45
+
46
+ Failures throw `BatchJobsError` with a stable `code`: `empty_requests`,
47
+ `too_many_requests` (adapter cap `OPENAI_BATCH_MAX_REQUESTS` = 50,000, OpenAI's
48
+ documented limit), `unsupported_model` (via `assertBatchJobsSupported` when the
49
+ host checks `ModelCapabilities.batchJobs`), `job_not_found`, `invalid_cursor`,
50
+ `request_failed` (non-2xx, secret-redacted), `response_malformed`, and — from
51
+ `pollBatch` on terminal states — `job_failed`, `job_cancelled`, `job_expired`.
52
+
53
+ ## Request/response example
54
+
55
+ ```json
56
+ { "input_file_id": "file-X123", "endpoint": "/v1/chat/completions", "completion_window": "24h" }
57
+ ```
58
+
59
+ ## Implementation example
60
+
61
+ ```ts
62
+ import { createOpenAIBatchJobsProvider } from "@arnilo/prism-providers/openai";
63
+ import { pollBatch, runBatchJobsConformance } from "@arnilo/prism";
64
+ import { runBatchJobsConformance } from "@arnilo/prism/testing/provider-conformance";
65
+
66
+ const batch = createOpenAIBatchJobsProvider({ apiKey: process.env.OPENAI_API_KEY });
67
+ const job = await batch.submit({
68
+ model: "gpt-4o-mini",
69
+ requests: [{ customId: "doc-1", body: { messages: [{ role: "user", content: "summarize" }] } }],
70
+ });
71
+ // pollBatch is a plain utility — host owns scheduling and persistence:
72
+ const done = await pollBatch(batch, job.id, { intervalMs: 30_000, backoffMultiplier: 1.5, maxIntervalMs: 300_000 });
73
+ let cursor: string | null | undefined = null;
74
+ do {
75
+ const page = await batch.results(done.id, { cursor });
76
+ for (const item of page.items) { /* host owns per-item handling */ }
77
+ cursor = page.nextCursor ?? null;
78
+ } while (cursor !== null);
79
+
80
+ // Offline conformance (fake transport, no network):
81
+ await runBatchJobsConformance({
82
+ provider: createOpenAIBatchJobsProvider({ apiKey: "sk-test", fetch: fakeFetch }),
83
+ maxRequests: 50_000,
84
+ sample: { model: "gpt-4o-mini", requests: [{ body: { messages: [] } }] },
85
+ });
86
+ ```
87
+
88
+ ## Extension and configuration notes
89
+
90
+ - Implement `BatchJobsProvider` for other vendors; the contract is structural —
91
+ no base class, no registry. Map your provider's states onto the neutral union
92
+ and surface your raw job payload on `job.raw`.
93
+ - Models declare support with `capabilities.batchJobs`; hosts gate with
94
+ `modelSupportsBatchJobs` / `assertBatchJobsSupported`, mirroring the
95
+ embeddings/speech/image/video/moderation guard pattern.
96
+ - Workflow-saga integration (auto-submission from run failure recovery) is
97
+ intentionally out of scope for v1 — see plan 061 Further Actions.
98
+
99
+ ## Security and performance notes
100
+
101
+ - Job ids are opaque strings; results cursors are opaque continuation tokens
102
+ (adapter: line offsets — never parsed as authorization).
103
+ - API keys resolve through the existing `CredentialValueSource` seam and are
104
+ redacted from every thrown error; no new secret paths.
105
+ - All responses read through the bounded readers
106
+ (`OPENAI_BATCH_MAX_RESPONSE_BYTES`, 256 MiB for JSONL downloads — batches are
107
+ large by design); oversized payloads reject instead of buffering.
108
+ - One provider request per call; paging is client-side over the downloaded
109
+ output/error file — no network per page boundary.
110
+ - Inputs, payloads, and raw files are never logged by core; error messages
111
+ carry status and a redacted body only.
112
+
113
+ ## Related APIs
114
+
115
+ - [Provider conformance](provider-conformance.md): `runBatchJobsConformance`
116
+ and the offline conformance matrix.
117
+ - [Provider packages](provider-packages.md): subpath import rules for
118
+ `@arnilo/prism-providers/openai`.
119
+ - [Multimodal content](multimodal-content.md): sibling one-shot modality
120
+ contracts sharing the same capability-flag and guard pattern.