@arnilo/prism 0.6.0 → 0.8.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 (178) hide show
  1. package/CHANGELOG.md +79 -5
  2. package/README.md +12 -11
  3. package/dist/agent-approval.d.ts +4 -0
  4. package/dist/agent-approval.js +5 -1
  5. package/dist/agent-definitions.js +1 -0
  6. package/dist/agent-run-lifecycle.js +39 -4
  7. package/dist/agent-run-state.d.ts +18 -0
  8. package/dist/agent-run-state.js +39 -9
  9. package/dist/agent-session/helpers.js +6 -1
  10. package/dist/agent-session/session/assemble.js +159 -7
  11. package/dist/agent-session/session/persist.d.ts +16 -0
  12. package/dist/agent-session/session/persist.js +64 -4
  13. package/dist/agent-session/session/provider-round.d.ts +3 -3
  14. package/dist/agent-session/session/provider-round.js +12 -6
  15. package/dist/agent-session/session/tool-round.js +5 -1
  16. package/dist/agent-session/session/types.d.ts +22 -1
  17. package/dist/agent-session/session.d.ts +16 -0
  18. package/dist/agent-session/session.js +42 -3
  19. package/dist/artifacts.d.ts +39 -1
  20. package/dist/artifacts.js +73 -0
  21. package/dist/attention-compiler.d.ts +121 -0
  22. package/dist/attention-compiler.js +479 -0
  23. package/dist/checkpoints.js +7 -11
  24. package/dist/cli-init.js +20 -6
  25. package/dist/context-budget.d.ts +20 -1
  26. package/dist/context-budget.js +10 -1
  27. package/dist/contracts-core/agent.d.ts +7 -0
  28. package/dist/contracts-core/attention.d.ts +66 -0
  29. package/dist/contracts-core/attention.js +2 -0
  30. package/dist/contracts-core/compaction.d.ts +59 -0
  31. package/dist/contracts-core/compaction.js +77 -1
  32. package/dist/contracts-core/content.d.ts +5 -0
  33. package/dist/contracts-core/loop.d.ts +42 -0
  34. package/dist/contracts-core/provider.d.ts +4 -0
  35. package/dist/contracts-core/run-limits.d.ts +2 -0
  36. package/dist/contracts-core.d.ts +1 -0
  37. package/dist/contracts-core.js +1 -0
  38. package/dist/contracts-protocol.d.ts +44 -3
  39. package/dist/contracts-run-state.d.ts +32 -5
  40. package/dist/evidence-grounding.d.ts +29 -0
  41. package/dist/evidence-grounding.js +162 -0
  42. package/dist/host-composition.d.ts +91 -0
  43. package/dist/host-composition.js +279 -0
  44. package/dist/index.d.ts +13 -6
  45. package/dist/index.js +7 -4
  46. package/dist/input.d.ts +13 -1
  47. package/dist/input.js +40 -1
  48. package/dist/provider-events.d.ts +3 -1
  49. package/dist/provider-events.js +2 -2
  50. package/dist/providers/transport.d.ts +3 -1
  51. package/dist/providers/transport.js +36 -0
  52. package/dist/redaction.js +18 -2
  53. package/dist/run-bundle.d.ts +89 -0
  54. package/dist/run-bundle.js +149 -0
  55. package/dist/secure-agent.d.ts +2 -0
  56. package/dist/secure-agent.js +6 -1
  57. package/dist/testing/state-concurrency-conformance.js +5 -12
  58. package/dist/tool-result-fold.d.ts +12 -0
  59. package/dist/tool-result-fold.js +13 -6
  60. package/dist/tools.d.ts +10 -0
  61. package/dist/tools.js +41 -0
  62. package/docs/acp-agent.md +42 -11
  63. package/docs/acp.md +2 -1
  64. package/docs/ag-ui.md +10 -3
  65. package/docs/agent-definitions.md +9 -1
  66. package/docs/agent-events.md +4 -1
  67. package/docs/agent-loops.md +33 -0
  68. package/docs/agent-session-runtime.md +8 -7
  69. package/docs/attention-compiler.md +272 -0
  70. package/docs/cli-rpc.md +4 -2
  71. package/docs/coding-agent-tools.md +1 -1
  72. package/docs/coding-security.md +6 -3
  73. package/docs/coding-tools.md +0 -1
  74. package/docs/coding-workspaces.md +22 -0
  75. package/docs/compaction-and-retry.md +36 -4
  76. package/docs/compaction-observational-memory.md +63 -10
  77. package/docs/connected-apps.md +116 -0
  78. package/docs/context-and-skills.md +17 -2
  79. package/docs/conversations.md +1 -1
  80. package/docs/core.md +1 -1
  81. package/docs/dev-inspector.md +4 -0
  82. package/docs/device-adapters.md +1 -0
  83. package/docs/diagrams.md +6 -6
  84. package/docs/document-reader.md +18 -10
  85. package/docs/documents.md +40 -11
  86. package/docs/durable-runs.md +87 -0
  87. package/docs/enterprise-postgres-state.md +6 -2
  88. package/docs/evaluations.md +168 -4
  89. package/docs/execution-timeline.md +186 -0
  90. package/docs/guardrails.md +33 -0
  91. package/docs/history/0.7.0-primitive-review.md +254 -0
  92. package/docs/history/079-messaging-primitive-review.md +391 -0
  93. package/docs/history/080-messaging-followon-primitive-review.md +234 -0
  94. package/docs/history/081-connected-apps-primitive-review.md +74 -0
  95. package/docs/history/083-prism-work-primitive-review.md +84 -0
  96. package/docs/history/084-primitive-review.md +96 -0
  97. package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
  98. package/docs/history/README.md +5 -0
  99. package/docs/history/migration-0.0.md +2 -2
  100. package/docs/history/release-handoffs.md +75 -1
  101. package/docs/host-compositions.md +149 -0
  102. package/docs/host-security.md +2 -2
  103. package/docs/hosted-sandboxes.md +94 -0
  104. package/docs/index.md +82 -45
  105. package/docs/input-and-prompt-assembly.md +1 -0
  106. package/docs/knowledge-sync.md +84 -0
  107. package/docs/language-intelligence.md +1 -1
  108. package/docs/live-testing.md +8 -3
  109. package/docs/mcp-tools.md +3 -1
  110. package/docs/memory-fabric.md +416 -0
  111. package/docs/messaging-channel-operations.md +166 -0
  112. package/docs/messaging-channels.md +150 -0
  113. package/docs/migrate-to-0.5.md +1 -1
  114. package/docs/migrate-to-0.6.md +1 -0
  115. package/docs/migrate-to-0.7.md +345 -0
  116. package/docs/migrate-to-0.8.md +124 -0
  117. package/docs/migration.md +43 -1
  118. package/docs/model-registry.md +12 -2
  119. package/docs/model-routing.md +79 -4
  120. package/docs/multi-agent-patterns.md +20 -6
  121. package/docs/observability.md +52 -1
  122. package/docs/openapi-tools.md +1 -1
  123. package/docs/operations.md +14 -4
  124. package/docs/options-index.md +47 -3
  125. package/docs/peer-dependencies.md +12 -10
  126. package/docs/postgres-persistence.md +1 -1
  127. package/docs/process-sessions.md +3 -1
  128. package/docs/prompt-registry.md +1 -1
  129. package/docs/provider-caching.md +4 -2
  130. package/docs/provider-conformance.md +1 -1
  131. package/docs/provider-layer.md +2 -2
  132. package/docs/provider-packages.md +22 -22
  133. package/docs/providers/bedrock.md +71 -7
  134. package/docs/providers/neuralwatt.md +5 -1
  135. package/docs/providers/openai.md +1 -1
  136. package/docs/rag.md +24 -8
  137. package/docs/realtime-voice.md +87 -0
  138. package/docs/release-and-install.md +53 -45
  139. package/docs/run-bundle.md +92 -0
  140. package/docs/runs-and-usage.md +17 -2
  141. package/docs/server.md +7 -3
  142. package/docs/sheets.md +9 -9
  143. package/docs/signal-channel.md +112 -0
  144. package/docs/speech.md +7 -1
  145. package/docs/sqlite-persistence.md +1 -1
  146. package/docs/supervisors.md +33 -5
  147. package/docs/telegram-channel.md +157 -0
  148. package/docs/testing.md +2 -2
  149. package/docs/thinking-and-reasoning.md +3 -1
  150. package/docs/tools.md +6 -5
  151. package/docs/web-tools.md +2 -1
  152. package/docs/wiki.md +1 -1
  153. package/docs/work-artifacts-and-review.md +14 -4
  154. package/docs/work-connectors.md +12 -10
  155. package/docs/work-sandbox.md +115 -0
  156. package/docs/work-tools.md +50 -18
  157. package/docs/workflows.md +69 -1
  158. package/docs/working-and-semantic-memory.md +25 -14
  159. package/package.json +5 -3
  160. package/templates/README.md +2 -0
  161. package/templates/business-worker/README.md.tmpl +19 -0
  162. package/templates/business-worker/env.example.tmpl +1 -0
  163. package/templates/business-worker/gitignore.tmpl +11 -0
  164. package/templates/business-worker/manifest.json +12 -0
  165. package/templates/business-worker/package.json.tmpl +23 -0
  166. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  167. package/templates/business-worker/src/index.ts.tmpl +13 -0
  168. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  169. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  170. package/templates/personal-assistant/README.md.tmpl +18 -0
  171. package/templates/personal-assistant/env.example.tmpl +1 -0
  172. package/templates/personal-assistant/gitignore.tmpl +11 -0
  173. package/templates/personal-assistant/manifest.json +11 -0
  174. package/templates/personal-assistant/package.json.tmpl +23 -0
  175. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  176. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  177. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  178. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
@@ -44,7 +44,7 @@ Offline conformance is mandatory for every package; credentialed probes are not
44
44
  | Ollama | cloud/local preset, reasoning/image mapping, implicit-cache fixture | Protected cloud or host-local authenticated daemon probe; no daemon starts in tests |
45
45
  | NeuralWatt | stream/retry/quota/telemetry fixtures, implicit-cache usage, headers/redaction | Protected `NEURALWATT_API_KEY` smoke |
46
46
  | Azure | endpoint preservation, Entra/resource-key header and OpenAI-compatible stream fixture | Protected host workload-identity probe |
47
- | Bedrock | SigV4/region/PrivateLink and OpenAI-compatible stream fixture | Protected host IAM/IRSA probe |
47
+ | Bedrock | SigV4/region/PrivateLink, OpenAI-compatible stream fixture, and native Converse fixtures: body mapping (messages/system/tools/media/reasoning/structured output/cachePoint), canonical event-stream frame bytes with CRC/limit/truncation refusals, capability refusals before network I/O | Protected host IAM/IRSA probe for both routes |
48
48
  | Vertex | location/endpoint preservation, ADC header and OpenAI-compatible stream fixture | Protected host ADC/WIF probe |
49
49
 
50
50
  All rows must retain bounded request/response fixtures, abort propagation, provider-owned-header precedence, and fake-secret leak assertions where the package surfaces those values. A successful fake transport proves Prism mapping, not account entitlement or vendor availability.
@@ -127,7 +127,7 @@ const agent = createAgent({ model: { provider: own.id, model: "demo" }, provider
127
127
 
128
128
  - Registry `resolve()` returns the matching provider/model or throws before any provider `generate()` call.
129
129
  - Provider event helpers return plain `ProviderEvent` objects.
130
- - `providerError()` converts unknown errors to redacted `ErrorInfo` through `errorToErrorInfo()` and preserves safe string/number `code` fields for retry classification.
130
+ - `providerError()` converts unknown errors to redacted `ErrorInfo`, preserves safe string/number `code` fields for retry classification, and stamps advisory `failureClass` (`quota`, `auth`, `rate_limited`, `transient`, `permanent`, or `unknown`) from already-captured status/body evidence. It never changes retry behavior or exposes response bodies/headers; see [Runs and usage ledger](runs-and-usage.md#provider-failure-classes).
131
131
  - `createMockProvider()` returns an `AIProvider` whose `generate()` yields the scripted events in order and checks `request.signal?.aborted` before each event.
132
132
  - The agent/session runtime passes its per-run abort signal as `ProviderRequest.signal`. `ProviderRequestOptions.structuredOutput` requests provider-native JSON-schema output when the model declares `capabilities.structuredOutput`; unsupported models fail before fetch. Timeouts are host-owned: pass `RunOptions.signal`/host abort controllers; retries are runtime-owned via `AgentConfig.retry`/`RunOptions.retry`. Provider-level timeout/retry hints were removed in 0.1.5.
133
133
 
@@ -200,7 +200,7 @@ for await (const event of resolvedProvider.generate({
200
200
  - `createMockProvider()` uses scripted events only: no timers, credentials, SDKs, or network.
201
201
  - Do not hide real secrets in mock event fixtures. If an error event must include secret-like text, use fake placeholders and redaction helpers.
202
202
  - `providerError(error, secrets)` only redacts the provided secret values. It is not a general secret scanner.
203
- - Providers may set safe `ErrorInfo.code` values such as `429`, `503`, or `ETIMEDOUT`; retry policy code treats them as classification hints, not trusted provider metadata.
203
+ - Providers may set safe `ErrorInfo.code` values such as `429`, `503`, or `ETIMEDOUT`; retry policy code treats them as classification hints, not trusted provider metadata. The shared `classifyProviderFailure()` transport helper maps those already-captured values to advisory run outcome metadata; `unknown` is always the fallback.
204
204
 
205
205
  ## Related APIs
206
206
 
@@ -25,26 +25,26 @@ Do not use provider packages as a package manager, credential store, env loader,
25
25
 
26
26
  | adapter package | version |
27
27
  | --- | --- |
28
- | `@arnilo/prism-providers/ai-sdk` | 0.6.0 |
29
- | `@arnilo/prism-providers/alibaba` | 0.6.0 |
30
- | `@arnilo/prism-providers/anthropic` | 0.6.0 |
31
- | `@arnilo/prism-providers/azure` | 0.6.0 |
32
- | `@arnilo/prism-providers/bedrock` | 0.6.0 |
33
- | `@arnilo/prism-providers/clinepass` | 0.6.0 |
34
- | `@arnilo/prism-providers/commandcode` | 0.6.0 |
35
- | `@arnilo/prism-providers/deepseek` | 0.6.0 |
36
- | `@arnilo/prism-providers/google` | 0.6.0 |
37
- | `@arnilo/prism-providers/hyper` | 0.6.0 |
38
- | `@arnilo/prism-providers/kimi` | 0.6.0 |
39
- | `@arnilo/prism-providers/model-discovery` | 0.6.0 |
40
- | `@arnilo/prism-providers/neuralwatt` | 0.6.0 |
41
- | `@arnilo/prism-providers/ollama` | 0.6.0 |
42
- | `@arnilo/prism-providers/openai` | 0.6.0 |
43
- | `@arnilo/prism-providers/opencode-go` | 0.6.0 |
44
- | `@arnilo/prism-providers/openrouter` | 0.6.0 |
45
- | `@arnilo/prism-providers/vertex` | 0.6.0 |
46
- | `@arnilo/prism-providers/xai` | 0.6.0 |
47
- | `@arnilo/prism-providers/zai` | 0.6.0 |
28
+ | `@arnilo/prism-providers/ai-sdk` | 0.8.0 |
29
+ | `@arnilo/prism-providers/alibaba` | 0.8.0 |
30
+ | `@arnilo/prism-providers/anthropic` | 0.8.0 |
31
+ | `@arnilo/prism-providers/azure` | 0.8.0 |
32
+ | `@arnilo/prism-providers/bedrock` | 0.8.0 |
33
+ | `@arnilo/prism-providers/clinepass` | 0.8.0 |
34
+ | `@arnilo/prism-providers/commandcode` | 0.8.0 |
35
+ | `@arnilo/prism-providers/deepseek` | 0.8.0 |
36
+ | `@arnilo/prism-providers/google` | 0.8.0 |
37
+ | `@arnilo/prism-providers/hyper` | 0.8.0 |
38
+ | `@arnilo/prism-providers/kimi` | 0.8.0 |
39
+ | `@arnilo/prism-providers/model-discovery` | 0.8.0 |
40
+ | `@arnilo/prism-providers/neuralwatt` | 0.8.0 |
41
+ | `@arnilo/prism-providers/ollama` | 0.8.0 |
42
+ | `@arnilo/prism-providers/openai` | 0.8.0 |
43
+ | `@arnilo/prism-providers/opencode-go` | 0.8.0 |
44
+ | `@arnilo/prism-providers/openrouter` | 0.8.0 |
45
+ | `@arnilo/prism-providers/vertex` | 0.8.0 |
46
+ | `@arnilo/prism-providers/xai` | 0.8.0 |
47
+ | `@arnilo/prism-providers/zai` | 0.8.0 |
48
48
  <!-- generated:package-truth:providers end -->
49
49
 
50
50
 
@@ -61,7 +61,7 @@ Do not use provider packages as a package manager, credential store, env loader,
61
61
  | `@arnilo/prism-providers/hyper` | `api_key` only | No subscription OAuth — Charm Hyper is pay-per-use Hypercredits; host supplies `HYPER_API_KEY` (keys start `sk-hyper-`). |
62
62
  | `@arnilo/prism-providers/commandcode` | `api_key` only | No subscription OAuth — Command Code Go/GOAT/Pro/Max coding plans and the Provider plan all authenticate with the same Studio API key; host supplies `COMMAND_CODE_API_KEY`. |
63
63
  | `@arnilo/prism-providers/azure` | host Entra token or Azure resource key | Workload identity via `credential` callback; endpoint host preserved ([docs](providers/azure.md)). |
64
- | `@arnilo/prism-providers/bedrock` | host IAM/IRSA credentials | SigV4 over OpenAI-compatible Bedrock Runtime; region/PrivateLink preserved ([docs](providers/bedrock.md)). |
64
+ | `@arnilo/prism-providers/bedrock` | host IAM/IRSA credentials | SigV4 over either the OpenAI-compatible Bedrock Runtime route (default) or the native model-agnostic Converse/ConverseStream route (`api: "converse"`); region/PrivateLink preserved ([docs](providers/bedrock.md)). |
65
65
  | `@arnilo/prism-providers/vertex` | host ADC / workload token | OpenAPI-compatible Vertex endpoint; separate from consumer Google package ([docs](providers/vertex.md)). |
66
66
 
67
67
  A future provider-local OAuth package must first have explicit third-party permission and documented authorize/token/refresh flow. Before it registers an OAuth descriptor, it must add bounded request/response, abort, PKCE/state where required, expiry/refresh, secret-redaction, durable-store round-trip, and offline protocol tests. Do not add a generic OAuth framework, CLI credential scanner, automatic refresh timer, or success stub.
@@ -139,7 +139,7 @@ Every package remains explicit, setup-zero-fetch, and late-credential-bound. `Mo
139
139
  | xAI | OpenAI-compatible Completions; caller-gated list | text, image | tool deltas, `reasoning_content` replay | implicit + `x-grok-conv-id`; protected API-key smoke; SuperGrok login operator-only |
140
140
  | ClinePass | OpenAI-compatible stream-only; static `cline-pass/*` catalog | text | tool deltas, per-model `reasoning_effort` | implicit; protected API-key smoke |
141
141
  | Azure | Azure/Foundry OpenAI-compatible; host models | selected endpoint/model capability | normalized OpenAI-compatible tools | no Prism cache mapping; protected host workload-identity probe |
142
- | Bedrock | Bedrock OpenAI-compatible; host models | selected endpoint/model capability | normalized OpenAI-compatible tools | no Prism cache mapping; protected host IAM/IRSA probe |
142
+ | Bedrock | Bedrock OpenAI-compatible (default) or native Converse/ConverseStream; host models | selected endpoint/model capability; native route refuses denied/unknown capabilities before request | normalized OpenAI-compatible tools or Converse `toolSpec`/`toolUse` deltas | compatible route: no Prism cache mapping; native route maps `cachePoint` breakpoints; protected host IAM/IRSA probe |
143
143
  | Vertex | Vertex OpenAPI-compatible; host models | selected endpoint/model capability | normalized OpenAI-compatible tools | no Prism cache mapping; protected host ADC/WIF probe |
144
144
 
145
145
  ### First-party cache behavior
@@ -2,11 +2,54 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-providers/bedrock` registers an Amazon Bedrock Runtime OpenAI-compatible Chat Completions provider. Hosts supply IAM/IRSA/assumed-role credentials; the package signs requests with SigV4 (no AWS SDK). Region and optional PrivateLink endpoint URLs are preserved.
5
+ `@arnilo/prism-providers/bedrock` registers an Amazon Bedrock Runtime provider with two explicit routes:
6
+
7
+ | Route | Wire API | Select with |
8
+ | --- | --- | --- |
9
+ | `compatible` (default) | OpenAI-compatible Chat Completions at `/openai/v1/chat/completions` | `createBedrockProvider` / `api: "compatible"` |
10
+ | `converse` | Native model-agnostic `Converse` and `ConverseStream` | `createBedrockConverseProvider` / `api: "converse"` |
11
+
12
+ Hosts supply IAM/IRSA/assumed-role credentials; the package signs requests with SigV4 (no AWS SDK). Region and optional PrivateLink endpoint URLs are preserved.
6
13
 
7
14
  ## When to use it
8
15
 
9
- Use it for enterprise Bedrock access under workload identity. Do not embed long-lived keys in fixtures. Use model-router residency policy to deny disallowed regions.
16
+ Use it for enterprise Bedrock access under workload identity. Do not embed long-lived keys in fixtures. Use model-router residency policy to deny disallowed regions. Use the `converse` route when the model only exists on Converse (tool use, reasoning, prompt caching, and structured output for Anthropic/Nova/OpenAI families), and keep `compatible` when an OpenAI-shaped gateway is what the deployment standardizes on.
17
+
18
+ ## Route selection
19
+
20
+ ```ts
21
+ import { createBedrockConverseProvider, createBedrockProviderPackage } from "@arnilo/prism-providers/bedrock";
22
+
23
+ // Package form: one provider id, selected route, optional non-streaming mode.
24
+ createBedrockProviderPackage({
25
+ region: "eu-west-1",
26
+ credential: () => hostAwsCredentials(),
27
+ api: "converse", // or "compatible" (default)
28
+ stream: true, // native route only: ConverseStream (default) vs one Converse call
29
+ models: [{ provider: "bedrock", model: "eu.anthropic.claude-haiku-4-5-20251001-v1:0" }],
30
+ });
31
+
32
+ // Factory form (same options, no registry wiring):
33
+ const provider = createBedrockConverseProvider({ region: "us-east-1", credential });
34
+ ```
35
+
36
+ Routes are mutually exclusive per provider id, so a host that needs both registers the second provider under a different `id` and model bindings. The package records the selected route in `ProviderPackage.metadata.route` and in the registered auth method metadata.
37
+
38
+ ## Capability matrix
39
+
40
+ | Capability | `compatible` | `converse` |
41
+ | --- | --- | --- |
42
+ | Text streaming | yes (OpenAI SSE) | yes (`ConverseStream` event stream) |
43
+ | Non-streaming | n/a (always streams) | yes (`stream: false`, one `Converse` response mapped to deltas + done) |
44
+ | Images | model-dependent OpenAI image parts | `image` blocks (`png`/`jpeg`/`gif`/`webp`); unknown media types refuse before the request |
45
+ | PDF documents | n/a | `document` blocks (`format` from media type); non-PDF files refuse |
46
+ | Tools | OpenAI `tools` | `toolConfig.tools[].toolSpec`, streamed `toolUse` deltas, `toolResult` in the following user turn |
47
+ | Reasoning/thinking | sanitized `reasoning_effort` / `reasoning` object | Anthropic-family `thinking` (`enabled`/`disabled`/`adaptive`, budget validated) and OpenAI-family `reasoning_effort` in `additionalModelRequestFields`; reasoning deltas map to Prism thinking blocks with signatures |
48
+ | Prompt caching | none (no Prism cache fields emitted) | `cachePoint` blocks from Prism cache breakpoints, `ttl: "1h"` for long retention; usage reports `cacheReadTokens` / `cacheWriteTokens` |
49
+ | Structured output | body passthrough only | `outputConfig.textFormat` JSON schema (requires `capabilities.structuredOutput`) |
50
+ | Usage | OpenAI usage | `inputTokens`/`outputTokens`/`totalTokens` + cache read/write |
51
+
52
+ Every feature above is tested offline against recorded frame/body fixtures; the live probe below covers text, tools, and usage. Features are not inferred from compatible endpoints.
10
53
 
11
54
  ## Inputs / request
12
55
 
@@ -34,6 +77,8 @@ Default public base: `https://bedrock-runtime.{region}.amazonaws.com` → `/open
34
77
 
35
78
  OpenAI-compatible SSE mapped to Prism provider events. Missing credentials fail closed before network I/O.
36
79
 
80
+ Native route: `ConverseStream` frames are decoded from `application/vnd.amazon.eventstream` (prelude/header lengths and both CRC32 checksums validated, 1 MiB default frame ceiling, 24 MiB hard spec ceiling) and mapped to Prism provider events. `:message-type: exception` frames become `error` events with the exception name and message; a stream that ends without `messageStop` or with an incomplete tool block fails loudly instead of returning partial output as success. Credentials are resolved once per request and redacted from provider errors.
81
+
37
82
  ## Request/response example
38
83
 
39
84
  ```http
@@ -51,11 +96,26 @@ const provider = createBedrockProvider({
51
96
  });
52
97
  ```
53
98
 
99
+ Native route request:
100
+
101
+ ```http
102
+ POST https://bedrock-runtime.eu-west-1.amazonaws.com/model/eu.anthropic.claude-haiku-4-5-20251001-v1%3A0/converse-stream
103
+ Accept: application/vnd.amazon.eventstream
104
+ Authorization: AWS4-HMAC-SHA256 Credential=…/eu-west-1/bedrock/aws4_request, …
105
+
106
+ { "messages": [{ "role": "user", "content": [{ "text": "hi" }] }],
107
+ "inferenceConfig": { "maxTokens": 4096 } }
108
+ ```
109
+
110
+ The `Converse` and `ConverseStream` operations share one request body; `accept` and the URL suffix select the operation. Inference-profile model ids (`eu.`/`us.` prefixes) are percent-encoded into the path, and region/endpoint policy is unchanged from the compatible route.
111
+
54
112
  Live canaries stay opt-in behind host credentials; default tests are network-free.
55
113
 
56
114
  ## Extension and configuration notes
57
115
 
58
- Uses Bedrock’s OpenAI-compatible runtime route (not Converse eventstream). Hosts needing Converse-only models should supply a custom provider or AI SDK bridge.
116
+ The compatible route uses Bedrock’s OpenAI-compatible runtime route (not Converse eventstream). The native route (`api: "converse"`) covers Converse-only models and features; both stay explicit, and neither silently falls back to the other.
117
+
118
+ Model-specific fields (for example `top_k`) come from `ModelConfig.parameters` leftovers plus the sanitized `compat.thinking` / `compat.reasoning_effort` keys; opaque `compat` keys are not spread onto the Converse body, and `toolChoice` is only forwarded when it is `auto`/`any`/`required` or `{ tool: { name } }`.
59
119
 
60
120
  ## Request construction (0.5.1)
61
121
 
@@ -71,12 +131,14 @@ See [Provider request policies](../provider-request-policies.md).
71
131
 
72
132
  ## Security and performance notes
73
133
 
74
- - No AWS SDK; package-local SigV4 only for `bedrock` service.
134
+ - No AWS SDK; package-local SigV4 only for `bedrock` service on both routes.
75
135
  - Input headers are normalized once before signing: names are lowercased and duplicate-case keys merge last-wins, so the canonical request always matches the signed header list (no duplicate-case mismatch); query parameters are canonicalized sorted by encoded key then value.
76
136
  - Private endpoint hosts are not rewritten to public DNS.
77
137
  - Conformance-proven (Task 6): package `setup()` performs zero fetch and zero credential resolution; an already-aborted signal fails fast; a truncated SSE stream (no `data: [DONE]`) ends in an `error` event; native Bedrock caching (`Converse cachePoint`) is intentionally unsupported on the OpenAI-compatible route — no cache wire fields are emitted even when the request carries Prism cache hints.
138
+ - Native route: `ConverseStream` frames are capped at 1 MiB (24 MiB hard spec ceiling) and a non-streaming `Converse` body is read under a 4 MiB ceiling (`BEDROCK_CONVERSE_RESPONSE_MAX_BYTES`), so a hostile or runaway response cannot exhaust memory.
139
+ - Native route: denied/unknown capabilities (`streaming: false` with the streaming route, `tools: false` with tools, `structuredOutput` undeclared, `reasoning: false` with a thinking/effort request, unsupported media types) refuse before any request is sent; corrupt or oversized event-stream frames terminate the stream rather than resyncing.
78
140
  - Credential secrets are redacted from provider errors.
79
- - No credential prefetch at import.
141
+ - No credential prefetch at import on either route.
80
142
 
81
143
  ## Live probe
82
144
 
@@ -87,11 +149,13 @@ PRISM_LIVE_PROVIDER_TESTS=1 AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_
87
149
  node --test packages/prism-providers/dist/bedrock/__tests__/live.test.js
88
150
  ```
89
151
 
90
- `PRISM_LIVE_BEDROCK_MODEL` overrides the probed model (default `us.anthropic.claude-haiku-4-5-20251001-v1:0`). Without credentials the suite skips.
152
+ `PRISM_LIVE_BEDROCK_MODEL` overrides the probed model (default `us.anthropic.claude-haiku-4-5-20251001-v1:0`). The same suite also probes the native route (streaming text/tools/usage and one non-streaming `Converse` call). Without credentials the suite skips.
91
153
 
92
154
  ## Thinking and reasoning
93
155
 
94
- Bedrock OpenAI-compat chat expects snake_case `reasoning_effort` (with `effort`/`reasoningEffort` aliases) or a sanitized `reasoning` object. OpenAI-family models on Bedrock snap effort to their declared levels (gpt-5.1 → `none/low/medium/high`); non-OpenAI models pass through untouched. See [Thinking and reasoning](../thinking-and-reasoning.md).
156
+ Compatible route: Bedrock OpenAI-compat chat expects snake_case `reasoning_effort` (with `effort`/`reasoningEffort` aliases) or a sanitized `reasoning` object. OpenAI-family models on Bedrock snap effort to their declared levels (gpt-5.1 → `none/low/medium/high`); non-OpenAI models pass through untouched.
157
+
158
+ Native route: Anthropic-family models take `additionalModelRequestFields.thinking` (`{type: "enabled"|"disabled"|"adaptive", budget_tokens?}`); a bare `enabled` gets a default budget so it can never reach the wire without one, and historical thinking blocks replay with signatures when `compat.preserveThinking` is on (default: when the model declares `capabilities.reasoning`). OpenAI-family models take `additionalModelRequestFields.reasoning_effort`, snapped to declared levels. See [Thinking and reasoning](../thinking-and-reasoning.md).
95
159
 
96
160
  ## Related APIs
97
161
 
@@ -95,7 +95,7 @@ after resolved fields so per-call values and overrides win.
95
95
  | --- | --- |
96
96
  | Provider stream | Prism text, thinking (`delta.reasoning_content` → `providerThinkingDelta`), tool-call delta/final, `usage`, `done`, redacted `error` with HTTP-status `code` for retry classification. |
97
97
  | Block preservation | Text, thinking, assistant `tool_call` → `tool_calls`, `tool_result` → role `tool` messages, images when `capabilities.input` includes `"image"`. |
98
- | Model catalog | Featured aliases declare provider id, display name, context limit, text/image input support, tools, reasoning/fast variants, streaming, implicit cache, and NeuralWatt JSON-mode compat metadata where documented. |
98
+ | Model catalog | Featured aliases declare provider id, display name, context limit, text/image input support, tools, reasoning/fast variants, streaming, implicit cache, NeuralWatt JSON-mode compat metadata where documented, and conformance-derived `toolCallStrictness: "strict"`. |
99
99
  | Pricing | Static aliases do not guess rates. Exact per-alias input/output/cache-read prices are advertised by NeuralWatt's `/v1/models` response and mapped by `listNeuralWattModels()` when present. |
100
100
  | SSE comments | `: energy` / `: cost` comment lines are parsed by `neuralWattEventsWithTelemetry()` into `neuralwatt:telemetry` events; the standard `neuralWattEvents()` stream (used by `generate()`) tolerates them without spurious events. |
101
101
  | `[DONE]` | Terminates the stream; final `providerDone(usage)` always emitted on a clean stream. |
@@ -292,6 +292,10 @@ prior tool turns through a multi-turn loop:
292
292
  the stringified `tool_result` — matching the OpenAI requirement that a tool result
293
293
  follows the call that produced it. `tool_result` blocks must appear in `role: "tool"
294
294
  messages; `tool_call` blocks must be the only content on their assistant message.
295
+ - **Catalog evidence.** Curated aliases carry `capabilities.toolCallStrictness: "strict"`
296
+ because the network-free conformance fixtures cover parallel indexed calls,
297
+ schema-shaped arguments, and empty `{}` arguments. This is adapter evidence, not a
298
+ provider SLA: hosts still validate every call. See [tool-call coverage](../_evidence/toolcall-coverage-2026-09-17.md).
295
299
 
296
300
  ### Energy and cost telemetry
297
301
 
@@ -52,7 +52,7 @@ uses official Responses `reasoning: { effort, summary? }` via
52
52
  | --- | --- |
53
53
  | Provider stream | Prism text, thinking (downgraded to text), host `tool_call` deltas/finals, provider-hosted `tool_call` events (`authority: "provider-hosted"`), `continuation_required`, `usage`, `done`, and redacted `error` events. |
54
54
  | Continuation | An incomplete Responses stream self-resumes at most eight HTTP hops using opaque `previous_response_id`; a cursor is at most 4 KiB, is never replayed, and is observable as `continuation_required`. |
55
- | Realtime | `createOpenAIRealtimeSession()` exposes server-session creation, audio in/out, transcript deltas, provider-hosted calls, interrupt, and idempotent close through the neutral `RealtimeSession` seam. |
55
+ | Realtime | `createOpenAIRealtimeSession()` exposes server-session creation, audio in/out, transcript deltas, host `function_call` items, provider-hosted calls, `usage`, `completeTool`, interrupt, and idempotent close through the neutral `RealtimeSession` seam. Host orchestration is [Realtime voice](../realtime-voice.md). |
56
56
  | Block preservation | User/system text → `input_text`; assistant text → `output_text`; assistant host `tool_call` → top-level `function_call` with `call_id`; provider-hosted calls are not replayed; `tool_result` → top-level `function_call_output`; images/files/audio when declared on the model. Bare thinking without an encrypted Responses reasoning item is omitted on replay. |
57
57
  | Auth methods | `api_key` for `openai`; host-invoked subscription `oauth` for `openai-codex`. xAI SuperGrok is the other first-party subscription OAuth flow ([xAI](xai.md)). |
58
58
 
package/docs/rag.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- The `@arnilo/prism-memory/rag` subpath is an optional surface for deterministic text/Markdown chunking (with ATX heading-stack metadata), bounded embedding/vector indexing with embedder-identity drift guards, atomic scoped source replacement/deletion with content-hash skip and generation visibility, hybrid vector+lexical retrieval with reciprocal-rank fusion (one embed / one RRF / one rerank across one or many exact scopes), focused text/Markdown/HTML/PDF parsing, bounded reranking (host seam plus a TEI REST adapter), ingestion status, attributable citations, content-trust metadata, and explicit `ContextProvider` injection. It reuses `Embedder` and `VectorStore` from the memory root entry; Prism core input assembly is unchanged.
5
+ The `@arnilo/prism-memory/rag` subpath is an optional surface for deterministic text/Markdown chunking (with ATX heading-stack metadata), bounded embedding/vector indexing with embedder-identity drift guards, atomic scoped source replacement/deletion with content-hash skip and generation visibility, hybrid vector+lexical retrieval with reciprocal-rank fusion (one embed / one RRF / one rerank across one or many exact scopes), host-verified document authorization on both query legs, paged knowledge-source synchronization with a Drive connector, focused text/Markdown/HTML/PDF parsing, bounded reranking (host seam plus a TEI REST adapter), ingestion status, attributable citations, content-trust metadata, and explicit `ContextProvider` injection. It reuses `Embedder` and `VectorStore` from the memory root entry; Prism core input assembly is unchanged.
6
6
 
7
7
  ## When to use it
8
8
 
9
- Use it when a host needs bounded replacement of one owned source, focused parsing after a host-authorized resource or host-selected web fetch, or a host-selected reranker over a finite candidate set. Do not use it for LaTeX parsing, semantic chunking, metadata extraction agents, a hosted reranker implementation, GraphRAG, crawling, URL fetching outside `@arnilo/prism-web-tools`, or filesystem discovery.
9
+ Use it when a host needs bounded replacement of one owned source, focused parsing after a host-authorized resource or host-selected web fetch, a host-selected reranker over a finite candidate set, or incremental Drive knowledge sync into that same source lifecycle. Do not use it for LaTeX parsing, semantic chunking, metadata extraction agents, a hosted reranker implementation, GraphRAG, crawling, URL fetching outside `@arnilo/prism-web-tools`, or filesystem discovery.
10
10
 
11
11
  ## Inputs / request
12
12
 
@@ -24,9 +24,11 @@ Document lifecycle:
24
24
 
25
25
  | API/field | Meaning |
26
26
  | --- | --- |
27
- | `replaceSource({ sourceId, chunks, store, scope, ... })` | Atomically replaces one source after all bounded embedding succeeds; the store must implement scoped `getBySource()` and `transaction()`. |
27
+ | `replaceSource({ sourceId, chunks, store, scope, ... })` | Atomically replaces one source after all bounded embedding succeeds; the store must implement scoped `getBySource()` and `transaction()`. `advanceGeneration: false` stamps the current generation without moving the scope pointer (multi-source sync). |
28
28
  | `deleteSource({ sourceId, store, scope })` | Deletes only matching IDs under exact tenant/resource/corpus scope. |
29
29
  | `replaceDocument({ uri, loader, parser, store, scope, ... })` | Loads through a host seam, parses, chunks, and atomically replaces. `sourceId` is required unless loader supplies one. |
30
+ | `syncKnowledge({ connector, checkpoints, checkpoint, store, embedder, scope })` | Paged connector import; cursor CAS only after each committed page. See [Knowledge synchronization](knowledge-sync.md). |
31
+ | `createGoogleDriveConnector({ tokenProvider, resolveAccess })` | Drive `files.list` + `changes.list` connector. Host maps permissions; watch payloads are not authorization. |
30
32
  | `DocumentLoader` / `Parser` | Small host-replaceable seams. `@arnilo/prism-memory/rag/loaders` and `/rag/parsers` export reference adapters. |
31
33
  | `textParser` / `markdownParser` / `htmlParser` / `pdfParser` | UTF-8 text, Markdown, script/style-stripping HTML, and uncompressed-text PDF parsers. |
32
34
 
@@ -40,7 +42,8 @@ Index/retrieve:
40
42
  | `topK` / `queryCandidates` | retrieval | Returned result count and bounded pre-filter candidates (`queryCandidates` is **per scope**) |
41
43
  | `lexical` | no | `"fts"` \| `"bm25"` \| `"off"` (default `"off"`); enables the lexical retrieval leg when the store advertises it |
42
44
  | `fusion` / `rrfK` | no | `"rrf"` fusion of vector+lexical legs (default `"rrf"` when `lexical` is on; `rrfK` default 60, hard cap 1,000) |
43
- | `filter` | no | Shallow JSON metadata equality filter |
45
+ | `filter` | no | Shallow JSON metadata equality filter. **Not authorization.** |
46
+ | `authorization` | no | Host-verified `{ principalId, tenantId, groupIds?, accessVersion? }`. Injected into both vector and lexical legs before ranking; rechecked before rerank and injection. Stores without `authorization: "acl"` fail closed. |
44
47
  | `reranker` | no | Host-owned `Reranker` receives redacted bounded `RagHit[]` and must return the same IDs once each, in preferred order. |
45
48
  | `maxRerankBytes` / `maxRerankMs` / `rerankConcurrency` | no | Reranker caps; defaults/hard limits are 64/256 KiB, 2/10 s, and 2/8 active calls per reranker object. |
46
49
  | `statusStore` | no | `IngestionStatusStore` records per-source pending/indexed/failed/partial byte/chunk progress; use `listIngestionStatus()` for capped exact-scope pages. |
@@ -56,9 +59,10 @@ Index/retrieve:
56
59
  - `indexChunks()` returns `{ indexed, sourceIds }` after bounded batch upserts.
57
60
  - `replaceSource()` / `deleteSource()` return `{ sourceId, deleted, indexed }`.
58
61
  - `replaceDocument()` carries loader parser metadata into chunk metadata; the web loader preserves web-tools citation ID and `untrusted: true`.
59
- - `retrieveContext()` returns `{ query, trust, text, hits, citations, truncated }`. Every hit/citation carries `{ provenance: { sourceId, chunkId, citationId, provider, tenantId, resourceId, corpusId, retrieval: "vector" | "lexical" | "hybrid", retrievedAt }, trust: { untrusted: true, inert: true, injectionCapable: true } }`; `retrieval` labels the leg(s) that surfaced the hit after RRF fusion, and `retrievalRank` preserves pre-rerank order. Rendered text uses `[citation-id] text` blocks.
62
+ - `retrieveContext()` returns `{ query, trust, text, hits, citations, truncated }`. Every hit/citation carries `{ provenance: { sourceId, chunkId, citationId, provider, tenantId, resourceId, corpusId, retrieval: "vector" | "lexical" | "hybrid", retrievedAt }, trust: { untrusted: true, inert: true, injectionCapable: true } }`; `retrieval` labels the leg(s) that surfaced the hit after RRF fusion, and `retrievalRank` preserves pre-rerank order. Rendered text uses `[citation-id] text` blocks. `evidenceFromRagCitation(citation, { contentHash, revision, excerpt? })` projects a hit into the shared `ArtifactCitation` evidence shape without refetching.
60
63
  - `replaceSource()` returns `{ sourceId, deleted, indexed, skipped? }` (skipped when the stored `contentHash` matched and no writes occurred). Records carry `embedderId` (from `Embedder.id`, the Task 2 identity contract) and `generation` (scope-level monotonically bumped index per replacement; `_rag` metadata carries `contentHash` when supplied). `store.getCurrentGeneration(scope)` / `store.setCurrentGeneration(scope, n)` let hosts read and roll back the visible generation; retrieval filters to the current generation while legacy generation-less rows stay visible.
61
- - `createMemoryIngestionStatusStore()` is a bounded in-memory reference adapter. `listIngestionStatus({ store, scope, limit, cursor })` returns capped status pages; hosts supply durable stores when status must survive process restart.
64
+ - `createMemoryIngestionStatusStore()` is a bounded in-memory reference adapter. `listIngestionStatus({ store, scope, limit, cursor })` returns capped status pages; hosts supply durable stores when status must survive process restart. Optional `freshness` is `current` / `stale` / `unavailable` for synchronized sources.
65
+ - `syncKnowledge()` returns `{ pages, upserted, deleted, skipped, withheld, cursor?, exhausted }`. Unchanged `contentHash` values skip embedding. Invalid Drive page tokens throw `RagSyncCursorError` (default: one bootstrap resync).
62
66
  - `createRagContextProvider()` returns one ordinary context provider. Empty queries/results contribute no block.
63
67
  - No events, tools, permissions, provider calls, loaders, or network requests are added.
64
68
 
@@ -97,12 +101,17 @@ const statusStore = createMemoryIngestionStatusStore();
97
101
  await indexChunks({ chunks, embedder, store, scope, statusStore });
98
102
  // For a replaceable source use `replaceSource`; it keeps previous chunks until embedding succeeds.
99
103
 
104
+ await store.setSourceAccess(
105
+ { tenantId: scope.tenantId, resourceId: scope.resourceId, threadId: scope.corpusId },
106
+ [{ sourceId: "security-guide", principalIds: ["alice"], groupIds: ["eng"], accessVersion: 1 }],
107
+ );
100
108
  const found = await retrieveContext("approval policy", {
101
109
  embedder,
102
110
  store,
103
111
  scopes: [scope], // or `scope` for one corpus
104
112
  topK: 4,
105
113
  filter: { category: "security" },
114
+ authorization: { principalId: "alice", tenantId: scope.tenantId, groupIds: ["eng"] },
106
115
  reranker: { rerank: async ({ hits }) => [...hits].sort((a, b) => b.score - a.score) },
107
116
  });
108
117
  console.log(await listIngestionStatus({ store: statusStore, scope }));
@@ -149,7 +158,8 @@ const found = await retrieveContext("leave balance", {
149
158
  ## Extension and configuration notes
150
159
 
151
160
  - Supply any Phase 7-conforming embedder/vector store, including the in-memory reference or PostgreSQL/pgvector adapter.
152
- - Metadata filtering is package-local after a bounded candidate query so existing vector contracts/adapters remain unchanged. Increase `queryCandidates` only when selective filters measurably need it.
161
+ - Metadata filtering is package-local after a bounded candidate query so existing vector contracts/adapters remain unchanged. Increase `queryCandidates` only when selective filters measurably need it. `filter` never grants document access.
162
+ - Document ACL is opt-in via `authorization` on `retrieveContext` / `store.query` / `store.lexicalQuery`. Reference memory and PostgreSQL adapters declare `authorization: "acl"` and apply principal/group predicates **before** top-K. `setSourceAccess` replaces grants per source (empty principal+group lists revoke). Access version is independent of embedding generation; an unresolved `accessVersion` denies. Missing grants deny. Stores that omit the capability throw rather than claim protection. Group lists cap at 32.
153
163
  - `Reranker` is a host seam, not a provider integration. Return each redacted candidate ID exactly once; Prism retains canonical hit/provenance/trust fields and exposes `retrievalRank` for diagnostics. Add a hosted reranker only when a host owns its credentials, quota, and retry policy.
154
164
  - `createTeiReranker({ baseUrl, model?, timeoutMs?, maxResponseBytes?, ssrf?, allowLoopback?, fetch? })` (`CreateTeiRerankerOptions`) adapts a Hugging Face TEI `POST <baseUrl>/rerank` endpoint (`{query, texts, raw_scores:false}` → `{results:[{index,score}]}`) into the `Reranker` seam. It returns a permutation-only reorder of the same hit objects, so provenance/trust move untouched. Response parsing is strict — short/duplicate/out-of-range indices, non-finite scores, HTTP errors, timeouts, and oversized bodies all fail closed; the `rerankHits` caps (`maxRerankBytes`, `maxRerankMs`, `rerankConcurrency`) still apply around it. The default transport is the core DNS-pinned `pinnedFetch` (redirect-free, byte-bounded to 65,536 by default); HTTPS is required unless `allowLoopback: true` (loopback dev/test) or the host supplies `ssrf`/`fetch` for cluster networking. The adapter validates URL shape only — SSRF policy enforcement stays host-side. No credentials are ever sent; there is no SaaS default URL.
155
165
  - Hosted rerank adapters over the same seam (plan 062): `createOpenAiCompatibleReranker({ baseUrl, model?, apiKey?, timeoutMs?, maxResponseBytes?, ssrf?, allowLoopback?, fetch? })` speaks the OpenAI-compatible `POST <baseUrl>/rerank` route (`{model, query, documents}` → `{results:[{index,relevance_score}]}`; pass the version segment in `baseUrl`, e.g. `https://api.jina.ai/v1`), and `createVoyageReranker({ baseUrl, model?, apiKey, … })` adapts Voyage AI (`…/v1/rerank` → `{data:[{index,relevance_score}]}`; `apiKey` required). Both send one request per rerank — no adapter-side batching — never send `top_k` (the retrieval seam owns top-K), return the same permutation-only reorder, and fail closed on the same malformed-response/HTTP/timeout/byte-bound cases. `apiKey` rides as `Authorization: Bearer …` and is never logged; errors carry status/host only. No SaaS default URL — hosts own credentials, quota, and retry policy.
@@ -162,12 +172,13 @@ const found = await retrieveContext("leave balance", {
162
172
  - `createRagContextProvider()` derives its query from latest user text by default; pass a fixed string or callback for host-controlled query generation.
163
173
  - `createResourceDocumentLoader({ loader })` calls one host-owned `ResourceLoader`; it scans nothing and performs no filesystem or network I/O itself. Pass the host's permission/trust context to that loader.
164
174
  - `createWebFetchDocumentLoader({ fetcher })` accepts an already-configured `@arnilo/prism-web-tools` fetch adapter. It never opens a socket, rejects file/local/private/IP-literal URLs, and carries normalized citation/trust metadata forward. The fetch adapter still owns DNS/SSRF policy.
165
- - `pdfParser` is deliberately limited to bounded, uncompressed PDF text. Provide a host parser through `Parser` for compressed, scanned, or complex PDFs; do not silently index partial text.
175
+ - `pdfParser` is deliberately limited to bounded, uncompressed PDF text. Provide a host parser through `Parser` for compressed, scanned, or complex PDFs; do not silently index partial text. Hosts that need OCR wrap `createMistralOcrParser` from `@arnilo/prism-work/document-reader` — it is never the default parser and never runs unless the host passes it to `replaceDocument({ parser })`.
166
176
  - Package is available directly or via the `@arnilo/prism-memory` family tarball; installation does not create an embedder, vector store, loader, parser, or context provider.
167
177
 
168
178
  ## Security and performance notes
169
179
 
170
180
  - Every index/query includes exact tenant/resource/corpus scope; returned records are rechecked and malformed/foreign records fail closed. `retrieveContext` accepts `scope` or `scopes` (never both, never neither). Empty `scopes` is the host “no allowed corpora” path — no embed, no search, no rerank. A hit whose stored scope is not in the requested list fails closed. Generation filters stay per scope.
181
+ - When `authorization` is set, unauthorized text, titles, citations, counts, and reranker payloads never leave the store. Recheck runs after fusion (before rerank) and again after rerank before injection, so revocation between those steps drops the candidate. `authorization.tenantId` must match every retrieve scope.
171
182
  - Embedding identity is a privacy/consistency boundary: records from a different embedder (or dimension) never silently mingle with new ones — retrieval fails closed and names the re-index path. Generation pointers are scope-scoped: a pointer row belongs to exactly one scope, and visibility is computed inside the store (SQL), never by post-filtering in JS.
172
183
  - Source IDs become citation/storage IDs and must be stable non-secret identifiers. Text and user metadata can be redacted before external embedding and persistence.
173
184
  - Heading metadata is document text only — it passes through the existing `maxMetadataBytes` cap as chunk metadata; no new content path is introduced.
@@ -200,12 +211,17 @@ PRISM_TEST_TEI_RERANKER_URL=http://tei.svc:8080 \
200
211
  | `PRISM_TEST_HOSTED_RERANK_URL` | OpenAI-compatible rerank base URL (`/rerank` appended, include `/v1`) |
201
212
  | `PRISM_TEST_HOSTED_RERANK_KEY` | Bearer credential for the hosted endpoint |
202
213
  | `PRISM_LIVE_HOSTED_RERANK_MODEL` | optional hosted model name |
214
+ | `PRISM_TEST_DRIVE_ACCESS_TOKEN` | delegated Drive readonly token for `memory/drive-sync-live` |
215
+ | `PRISM_TEST_DRIVE_FOLDER_ID` | optional folder scope |
216
+ | `PRISM_TEST_DRIVE_SHARED_DRIVE_ID` | optional shared drive |
203
217
 
204
218
  Probes send one non-sensitive rerank request per configured endpoint and assert the live response conforms (permutation-only reorder, scores non-increasing, credential never in error transcripts). Registered in `scripts/live-matrix.json` as `memory/rag-rerankers-live`.
205
219
 
206
220
  ## Related APIs
207
221
 
222
+ - [Knowledge synchronization](knowledge-sync.md): paged connector sync, Drive adapter, cursor CAS, source freshness.
208
223
  - [Working and semantic memory](working-and-semantic-memory.md): shared `Embedder`/`VectorStore` contracts and adapters.
224
+ - [Work artifacts and review](work-artifacts-and-review.md): `evidenceFromRagCitation` projects retrieved hits into shared citation evidence.
209
225
  - [Context and skills](context-and-skills.md): explicit `ContextProvider` injection and inert context semantics.
210
226
  - [Resource loading](resource-loading.md): host-owned trusted source loading.
211
227
  - [Multimodal content](multimodal-content.md): remote media SSRF/MIME/byte policies before text extraction.
@@ -0,0 +1,87 @@
1
+ # Realtime voice
2
+
3
+ ## What it does
4
+
5
+ `createRealtimeVoiceBridge` in `@arnilo/prism-core/runtime/realtime` runs an existing `RealtimeSession` through ordinary host tool dispatch, device admission, barge-in, reconnect dedupe, and transcript privacy. OpenAI Realtime (`createOpenAIRealtimeSession`) maps host `function_call` items onto `RealtimeEvent.tool_call`, emits `usage`, and returns results with `completeTool`.
6
+
7
+ ## When to use it
8
+
9
+ Use it when microphone audio should drive the same tools, approvals, usage ledger, and memory consent as a text run. Do not use it as a second agent loop or a voice-specific policy engine. One-shot TTS/STT stays on [Speech and transcription](speech.md).
10
+
11
+ ## Inputs / request
12
+
13
+ ```ts
14
+ import { resolveDevicePolicy } from "@arnilo/prism";
15
+ import { createOpenAIRealtimeSession } from "@arnilo/prism-providers/openai";
16
+ import { createRealtimeVoiceBridge } from "@arnilo/prism-core/runtime/realtime";
17
+
18
+ const policy = resolveDevicePolicy(
19
+ { kind: "voice", enabled: true, requireApproval: true, sandbox: "voice" },
20
+ { runLimits: { maxTurns: 8, maxToolCalls: 32 } },
21
+ );
22
+ const session = createOpenAIRealtimeSession({
23
+ ownerId: "user-1",
24
+ model: { provider: "openai", model: "gpt-realtime" },
25
+ apiKey,
26
+ tools: [{ name: "lookup", parameters: { type: "object" } }],
27
+ });
28
+ const bridge = createRealtimeVoiceBridge({
29
+ session,
30
+ policy,
31
+ admit: { approved: true, activeSessions: 0 },
32
+ toolNames: ["lookup"],
33
+ strictGovernance: true,
34
+ retainTranscripts: false,
35
+ execute: (call, ctx) => dispatchToolCall({ call, signal: ctx.signal }),
36
+ recordUsage: (usage) => router.recordUsage({ identity, provider: "openai", model, tokens: usage.totalTokens, kind: "generation" }),
37
+ });
38
+ ```
39
+
40
+ | Field | Meaning |
41
+ | --- | --- |
42
+ | `session` | Existing `RealtimeSession` (`sendAudio` / `events` / `interrupt` / `close`). |
43
+ | `policy` + `admit` | `assertDeviceAdmit` on construct, each `sendAudio`, and reconnect (new bridge). |
44
+ | `execute` | Host dispatch (approvals, `toolNames`, effect store). Not provider-hosted calls. |
45
+ | `toolNames` | Same names-only grant as `RunOptions.toolNames`. Omitted = all host tools; `[]` = none. |
46
+ | `strictGovernance` | Provider-hosted tools are unknown, never dispatched. |
47
+ | `seenCallIds` | Reconnect skip list. Duplicate ids are not replayed. |
48
+ | `retainTranscripts` | Default `false`. Audio is never retained. |
49
+
50
+ ## Outputs / response / events
51
+
52
+ `bridge.run()` consumes `session.events()` until close. `snapshot()` reports pending/completed/cancelled/unknown call ids, `interrupted`, `consent`, `effectAfterInterrupt`, and `usageMissing`. Barge-in aborts queued calls before `execute`; an execute that still succeeds after interrupt sets `effectAfterInterrupt` (072 invariant 0). Outbound `audio_delta` is dropped after interrupt until the next `sendAudio`.
53
+
54
+ ## Request/response example
55
+
56
+ ```json
57
+ {
58
+ "type": "response.function_call_arguments.done",
59
+ "call_id": "call_001",
60
+ "name": "lookup",
61
+ "arguments": "{\"q\":\"x\"}"
62
+ }
63
+ ```
64
+
65
+ ## Implementation example
66
+
67
+ See `examples/realtime-voice-host.ts` (network-free mock session, keyboard-free barge-in). Hosts that need a text approval UI call `execute` through the same `dispatchToolCall` / durable-approval path as a text run.
68
+
69
+ ## Extension and configuration notes
70
+
71
+ OpenAI `session.update` advertises at most 32 host function tools. `completeTool` sends `conversation.item.create` (`function_call_output`) then `response.create`. Optional `completeTool` on `RealtimeSession` is the generic gap; transports that cannot complete tools omit it. Transcript memory uses Task 16 `remember` only when `retainTranscripts` is true and the host passes `onTranscript`.
72
+
73
+ ## Security and performance notes
74
+
75
+ - Microphone consent is not tool approval. `revokeConsent` closes the session; later `sendAudio` throws `ERR_PRISM_REALTIME_CONSENT`.
76
+ - Re-admit on every reconnect. Side effects never replay from `seenCallIds`.
77
+ - Raw audio is not made safe by text redaction and is not uploaded or stored by the bridge.
78
+ - Ambiguous outcomes after interrupt stay `unknown`, never fabricated success.
79
+ - Event/audio caps stay on the Realtime session (`maxAudioEventsPerSecond` / `maxBytesPerSecond` / `maxWallMs`). Pending host calls cap at 32.
80
+
81
+ ## Related APIs
82
+
83
+ - [Device adapters](device-adapters.md): deny-by-default voice admission.
84
+ - [Speech and transcription](speech.md): one-shot TTS/STT.
85
+ - [Tools](tools.md): `RunOptions.toolNames` grant used by the bridge.
86
+ - [Runs and usage ledger](runs-and-usage.md): voice tokens settle as `kind: "generation"`.
87
+ - [OpenAI provider](providers/openai.md): `createOpenAIRealtimeSession`.