@arnilo/prism 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +62 -26
- package/dist/agent-loops.d.ts +8 -1
- package/dist/agent-loops.js +57 -11
- package/dist/agents.js +212 -32
- package/dist/checkpoints.d.ts +11 -0
- package/dist/checkpoints.js +144 -0
- package/dist/cli-init.d.ts +41 -0
- package/dist/cli-init.js +390 -0
- package/dist/cli-runner.d.ts +7 -1
- package/dist/cli-runner.js +13 -1
- package/dist/compaction.js +9 -1
- package/dist/content.d.ts +121 -0
- package/dist/content.js +538 -0
- package/dist/contracts.d.ts +236 -11
- package/dist/contracts.js +8 -0
- package/dist/event-multiplexer.d.ts +23 -0
- package/dist/event-multiplexer.js +136 -0
- package/dist/execution-policy.d.ts +28 -0
- package/dist/execution-policy.js +24 -0
- package/dist/feedback.d.ts +48 -0
- package/dist/feedback.js +230 -0
- package/dist/index.d.ts +20 -6
- package/dist/index.js +13 -5
- package/dist/input.js +11 -1
- package/dist/leases.d.ts +8 -0
- package/dist/leases.js +111 -0
- package/dist/node/agent-definitions.js +3 -5
- package/dist/node/config.d.ts +1 -0
- package/dist/node/config.js +5 -3
- package/dist/node/contribution-discovery.js +5 -8
- package/dist/node/session-store-jsonl.js +8 -5
- package/dist/node/settings.js +2 -2
- package/dist/node/trust.js +2 -4
- package/dist/observability.d.ts +3 -0
- package/dist/observability.js +18 -0
- package/dist/providers/media.d.ts +44 -0
- package/dist/providers/media.js +126 -0
- package/dist/providers/openai-compatible.js +18 -119
- package/dist/providers/openai-primitives.d.ts +9 -0
- package/dist/providers/openai-primitives.js +129 -0
- package/dist/providers/transport.d.ts +40 -0
- package/dist/providers/transport.js +221 -0
- package/dist/redaction.js +40 -13
- package/dist/resources.d.ts +5 -0
- package/dist/resources.js +4 -0
- package/dist/structured-output.d.ts +11 -0
- package/dist/structured-output.js +59 -0
- package/dist/testing/feedback.d.ts +6 -0
- package/dist/testing/feedback.js +37 -0
- package/dist/testing/persistence-schema.d.ts +102 -0
- package/dist/testing/persistence-schema.js +487 -0
- package/dist/testing/provider-conformance.js +10 -1
- package/dist/testing/run-ledger-conformance.d.ts +33 -0
- package/dist/testing/run-ledger-conformance.js +178 -0
- package/dist/testing/session-store-conformance.d.ts +16 -0
- package/dist/testing/session-store-conformance.js +73 -0
- package/dist/tools.d.ts +17 -0
- package/dist/tools.js +29 -2
- package/docs/a2a.md +73 -0
- package/docs/agent-events.md +17 -10
- package/docs/agent-loops.md +11 -5
- package/docs/agent-session-runtime.md +15 -16
- package/docs/cli-rpc.md +36 -5
- package/docs/coding-agent-tools.md +43 -9
- package/docs/coding-security.md +88 -0
- package/docs/compaction-observational-memory.md +2 -0
- package/docs/context-and-skills.md +1 -0
- package/docs/credential-storage.md +177 -0
- package/docs/credentials-and-redaction.md +4 -3
- package/docs/database-persistence.md +52 -7
- package/docs/evaluations.md +122 -0
- package/docs/extensions.md +2 -2
- package/docs/host-security.md +33 -2
- package/docs/index.md +46 -18
- package/docs/input-and-prompt-assembly.md +6 -5
- package/docs/mcp-tools.md +184 -0
- package/docs/middleware-hooks.md +2 -0
- package/docs/migration.md +51 -28
- package/docs/model-registry.md +5 -3
- package/docs/multimodal-content.md +156 -0
- package/docs/observability.md +171 -0
- package/docs/performance.md +249 -1
- package/docs/persistence-credentials-multimodality-primitives.md +303 -0
- package/docs/postgres-persistence.md +143 -0
- package/docs/provider-conformance.md +18 -0
- package/docs/provider-layer.md +1 -1
- package/docs/provider-packages.md +2 -0
- package/docs/provider-primitives.md +281 -0
- package/docs/providers/ai-sdk.md +113 -0
- package/docs/providers/kimi.md +1 -0
- package/docs/providers/neuralwatt.md +1 -0
- package/docs/providers/openai-compatible.md +2 -1
- package/docs/providers/openai.md +8 -1
- package/docs/providers/opencode-go.md +1 -0
- package/docs/providers/openrouter.md +1 -0
- package/docs/providers/zai.md +1 -0
- package/docs/public-contracts.md +13 -5
- package/docs/rag.md +113 -0
- package/docs/release-and-install.md +237 -30
- package/docs/resource-loading.md +14 -4
- package/docs/review-coverage-2026-07-14.md +260 -0
- package/docs/review-coverage-2026-07-15.md +193 -0
- package/docs/run-ledger-conformance.md +96 -0
- package/docs/runs-and-usage.md +43 -4
- package/docs/server.md +139 -0
- package/docs/session-store-conformance.md +16 -0
- package/docs/session-stores-and-branching.md +1 -0
- package/docs/settings-auth-trust-security.md +6 -5
- package/docs/sqlite-persistence.md +123 -0
- package/docs/structured-output.md +9 -0
- package/docs/supervisors.md +71 -0
- package/docs/tool-conformance.md +1 -0
- package/docs/tool-execution-primitives.md +374 -0
- package/docs/tools.md +39 -1
- package/docs/workflow-orchestration-primitives.md +581 -0
- package/docs/workflow-tui-primitives.md +5 -0
- package/docs/workflows.md +293 -0
- package/docs/working-and-semantic-memory.md +169 -0
- package/package.json +43 -5
- package/templates/init/README.md.tmpl +28 -0
- package/templates/init/env.example.tmpl +1 -0
- package/templates/init/gitignore.tmpl +11 -0
- package/templates/init/optional/evals-example.ts.tmpl +17 -0
- package/templates/init/optional/workflows-example.ts.tmpl +27 -0
- package/templates/init/package.json.tmpl +22 -0
- package/templates/init/providers.json +76 -0
- package/templates/init/src/agent.ts.tmpl +10 -0
- package/templates/init/src/index.ts.tmpl +12 -0
- package/templates/init/src/tests/agent.test.ts.tmpl +24 -0
- package/templates/init/tsconfig.json.tmpl +15 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# MCP client bridge and server exposure
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
`@arnilo/prism-mcp` has two explicit directions. Its client bridge connects hosts to remote [Model Context Protocol](https://modelcontextprotocol.io) servers and maps discovered tools to ordinary `ToolDefinition`s. Its server API registers selected Prism `ToolDefinition` and `CommandDefinition` values on the official SDK `McpServer`, with required authorization and a bounded optional Web-standard Streamable HTTP handler. The package wraps `@modelcontextprotocol/sdk` v1.29+ and adds no MCP branch to core Prism.
|
|
6
|
+
|
|
7
|
+
Primary API:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { connectMcpTools } from "@arnilo/prism-mcp";
|
|
11
|
+
|
|
12
|
+
const bridge = await connectMcpTools({
|
|
13
|
+
serverId: "fs",
|
|
14
|
+
transport: { type: "stdio", command: "node", args: ["server.js"] },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// bridge.tools are ToolDefinition[] — register with createToolRegistry / createAgent
|
|
18
|
+
await bridge.refresh(); // re-list after notifications or TTL expiry
|
|
19
|
+
await bridge.close(); // close client + transport
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Advanced hosts that manage their own `Client` + `Transport` can call `attachMcpToolBridge(client, transport, options)` after `client.connect(transport)`.
|
|
23
|
+
|
|
24
|
+
Server direction:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createPrismMcpServer, createPrismMcpWebHandler } from "@arnilo/prism-mcp";
|
|
28
|
+
|
|
29
|
+
const server = createPrismMcpServer({
|
|
30
|
+
tools: [approvedTool],
|
|
31
|
+
commands: [approvedWorkflowStatusCommand],
|
|
32
|
+
authorize: async ({ authInfo, kind, name }) => hostPolicy(authInfo, kind, name)
|
|
33
|
+
? { allowed: true, ownership: { tenantId: "tenant-1" } }
|
|
34
|
+
: false,
|
|
35
|
+
validate,
|
|
36
|
+
permission,
|
|
37
|
+
redactor,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const handleMcp = await createPrismMcpWebHandler(server, {
|
|
41
|
+
resolveAuthInfo: authenticateRequest,
|
|
42
|
+
allowedHosts: ["api.example.test"],
|
|
43
|
+
allowedOrigins: ["https://app.example.test"],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`McpServer.connect(transport)` remains available for SDK stdio or in-memory transports. The helper uses SDK `WebStandardStreamableHTTPServerTransport` in bounded stateless JSON-response mode; it does not start a listener.
|
|
48
|
+
|
|
49
|
+
## When to use it
|
|
50
|
+
|
|
51
|
+
- **Integrate external MCP tool servers** (filesystem, databases, SaaS adapters) without reimplementing JSON-RPC transports in your app.
|
|
52
|
+
- **Keep core dispatch gates** — register returned tools and let `dispatchToolCall` enforce permission, JSON Schema validation (`ToolValidator`), middleware, abort, and parallel execution (Plan 055 Tasks 1–2).
|
|
53
|
+
- **Explicit lifecycle** — connect, refresh on `notifications/tools/list_changed`, and `close()` when the session ends.
|
|
54
|
+
- **Expose selected capabilities** — register a reviewed tool/command allow-list for MCP clients without a custom JSON-RPC server.
|
|
55
|
+
|
|
56
|
+
Do **not** use this package as a sandbox, permission engine, or auto-discovery loader. Hosts must trust configured commands/URLs and gate registration.
|
|
57
|
+
|
|
58
|
+
## Inputs / request
|
|
59
|
+
|
|
60
|
+
`connectMcpTools()` requires a stable `serverId` plus an explicit stdio or Streamable HTTP transport. Optional bounds control list caching, call timeout, result bytes, name prefix, and abort behavior; defaults are listed below.
|
|
61
|
+
|
|
62
|
+
## Outputs / response / events
|
|
63
|
+
|
|
64
|
+
The resolved `McpToolBridge` exposes `tools`, `refresh()`, and `close()`. Each discovered MCP tool becomes a normal Prism `ToolDefinition`; calls return `ToolResult`, with remote `isError` mapped to `ToolResult.error`. List-change notifications invalidate the cache but register nothing automatically.
|
|
65
|
+
|
|
66
|
+
`createPrismMcpServer()` returns the SDK `McpServer`. It lists only passed tools/commands; JSON Schema parameters are converted through installed Zod v4 for SDK validation, then Prism tool calls still pass through `dispatchToolCall` permission/validator/redactor gates. Command definitions support explicitly selected direct/background/replay workflow operations and optional ownership-scoped schedule operations from `createWorkflowCommands()`; none are registered unless the host passes those command definitions. Calls return bounded MCP text content and `isError` on denial/failure. `createPrismMcpWebHandler()` returns `(Request) => Promise<Response>`.
|
|
67
|
+
|
|
68
|
+
## Request/response example
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"request": { "serverId": "docs", "transport": { "type": "stdio", "command": "node", "args": ["server.js"] } },
|
|
73
|
+
"mappedTool": { "name": "mcp:docs:search", "parameters": { "type": "object" } }
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Implementation example
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { createToolRegistry } from "@arnilo/prism";
|
|
81
|
+
import { connectMcpTools } from "@arnilo/prism-mcp";
|
|
82
|
+
|
|
83
|
+
const bridge = await connectMcpTools({
|
|
84
|
+
serverId: "docs",
|
|
85
|
+
transport: { type: "stdio", command: "node", args: ["server.js"] },
|
|
86
|
+
callTimeoutMs: 30_000,
|
|
87
|
+
});
|
|
88
|
+
const registry = createToolRegistry({ duplicate: "error" });
|
|
89
|
+
for (const tool of bridge.tools) registry.register(tool);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Tool naming and mapping
|
|
93
|
+
|
|
94
|
+
| MCP | Prism |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `tools/list` `inputSchema` | `ToolDefinition.parameters` |
|
|
97
|
+
| `tools/call` arguments | Parsed `ToolCallContent.arguments` |
|
|
98
|
+
| `tools/call` content blocks | `ToolResult.content` (`text`, `image`; resource/audio/link → descriptive `text`) |
|
|
99
|
+
| Tool `name` | Prefixed `mcp:<serverId>:<name>` (override with `namePrefix`) |
|
|
100
|
+
| `isError` results | `ToolResult.error` with summarized text |
|
|
101
|
+
| `structuredContent` | `ToolResult.value` / metadata |
|
|
102
|
+
|
|
103
|
+
Duplicate prefixed names throw `McpToolNameCollisionError` at refresh time.
|
|
104
|
+
|
|
105
|
+
## Extension and configuration notes
|
|
106
|
+
|
|
107
|
+
| Option | Default | Purpose |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| `serverId` | required | Stable identifier used in default name prefix |
|
|
110
|
+
| `transport` | required | `stdio` or `streamable-http` config |
|
|
111
|
+
| `namePrefix` | `mcp:<serverId>:` | Registry namespace for remote tools |
|
|
112
|
+
| `listCacheTtlMs` | `30000` | Skip re-listing until TTL expires (invalidated on list-changed) |
|
|
113
|
+
| `callTimeoutMs` | `60000` | Per-call MCP request timeout |
|
|
114
|
+
| `maxResultBytes` | `10000000` | Bound mapped result content |
|
|
115
|
+
| `signal` | none | Abort connect and trigger close on abort |
|
|
116
|
+
|
|
117
|
+
### Stdio transport
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
{
|
|
121
|
+
type: "stdio",
|
|
122
|
+
command: "node",
|
|
123
|
+
args: ["path/to/server.js"],
|
|
124
|
+
env?: Record<string, string>,
|
|
125
|
+
cwd?: string,
|
|
126
|
+
stderr?: "inherit" | "pipe" | "ignore" | "overlapped",
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The host explicitly chooses the executable, arguments, environment, and working directory. Prism does not search `PATH` for unknown servers or inject credentials.
|
|
131
|
+
|
|
132
|
+
### Streamable HTTP transport
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
{
|
|
136
|
+
type: "streamable-http",
|
|
137
|
+
url: "https://mcp.example.com/mcp",
|
|
138
|
+
requestInit?: RequestInit,
|
|
139
|
+
sessionId?: string,
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Only `http:` and `https:` URLs are accepted. Authentication (Bearer tokens, cookies) is supplied through `requestInit.headers` by the host.
|
|
144
|
+
|
|
145
|
+
### MCP server options
|
|
146
|
+
|
|
147
|
+
| Option | Default | Purpose |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `tools` / `commands` | empty | Explicit allow-list; zero default exposure |
|
|
150
|
+
| `authorize` | required | Per-call host authz using SDK auth/session metadata |
|
|
151
|
+
| `permission` / `validate` / `redactor` | none | Core tool-dispatch gates and known-secret redaction |
|
|
152
|
+
| `maxResultBytes` | 1 MiB (8 MiB hard) | Bound mapped MCP call output |
|
|
153
|
+
| `maxConcurrentCalls` | 16 (256 hard) | Bound active tool/command execution |
|
|
154
|
+
| `callTimeoutMs` | 60 s (30 min hard) | Abort and return timed-out calls |
|
|
155
|
+
|
|
156
|
+
Web handler defaults: 1 MiB request (8 MiB hard), 2 MiB response (16 MiB hard), 32 concurrent requests (512 hard), and 60 s timeout (30 min hard). It parses bounded JSON before passing `parsedBody` to the SDK transport. `allowedHosts`/`allowedOrigins` activate SDK DNS-rebinding checks only when explicitly configured. Authentication data comes only from host `resolveAuthInfo()`.
|
|
157
|
+
|
|
158
|
+
## Security and performance notes
|
|
159
|
+
|
|
160
|
+
| Risk | Mitigation |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| Untrusted subprocess (stdio) | Explicit `command` / `args` / `env` / `cwd`; review before deploy |
|
|
163
|
+
| SSRF / open redirects (HTTP) | Host URL allow-lists, network policy, no implicit discovery |
|
|
164
|
+
| Tool-name shadowing | Prefixed names + `createToolRegistry({ duplicate: "error" })` |
|
|
165
|
+
| Oversized server output | `maxResultBytes` on content mapping |
|
|
166
|
+
| Unvalidated arguments | Register tools with `createJsonSchemaToolArgumentValidator()` at dispatch |
|
|
167
|
+
| Missing permission gate | Client direction: `PermissionPolicy` on `tool:mcp:<serverId>:<name>:execute`; server direction: required MCP `authorize` plus optional core `PermissionPolicy` |
|
|
168
|
+
| Accidental server exposure | Empty default arrays, duplicate-name rejection, explicit tools/commands only |
|
|
169
|
+
| Unbounded MCP HTTP | Bounded pre-parsed JSON, response bytes, concurrent requests, call timeout, SDK web-standard transport |
|
|
170
|
+
| Cross-tenant operation | Authorizer derives ownership from validated auth and passes it to tool dispatch/selected workflow commands; never trust arguments as identity |
|
|
171
|
+
|
|
172
|
+
MCP output is untrusted. Apply `SecretRedactor` and host logging policy to `ToolResult` before persisting or displaying. MCP server authorization does not replace tool `PermissionPolicy`, argument validation, coding `ExecutionPolicy`, workflow ownership checks, TLS, rate limiting, or sandboxing. A timed-out tool must cooperate with `AbortSignal` to stop side effects; the response is bounded even when untrusted code ignores abort.
|
|
173
|
+
|
|
174
|
+
## Related APIs
|
|
175
|
+
|
|
176
|
+
- [Tools](tools.md): registry, dispatch, validation
|
|
177
|
+
- [Tool execution primitives](tool-execution-primitives.md): Plan 055 design and conformance matrix
|
|
178
|
+
- [Host security guide](host-security.md): permission, trust, validation checklist
|
|
179
|
+
- [Web-standard server handler](server.md): agent/workflow HTTP routes and shared remote-boundary rules
|
|
180
|
+
- Package README: [`@arnilo/prism-mcp`](../packages/mcp/README.md)
|
|
181
|
+
|
|
182
|
+
## Testing
|
|
183
|
+
|
|
184
|
+
Package tests use in-memory MCP transports (no network). Hosts should integration-test their configured stdio commands and HTTP endpoints in staging before production registration.
|
package/docs/middleware-hooks.md
CHANGED
|
@@ -101,6 +101,7 @@ export const extension: Extension = {
|
|
|
101
101
|
- `retry` middleware may stop retrying or adjust delay, but runtime still owns retry event emission, abort-aware waiting, and provider-turn boundaries.
|
|
102
102
|
- The registry does not discover packages, read manifests, load config, call providers, execute tools, read resources, or start sessions.
|
|
103
103
|
- Hosts may pass a middleware registry into `createExtensionKernel({ middleware })` to share it with direct host code.
|
|
104
|
+
- For OpenTelemetry export, prefer `session.subscribe()` + `@arnilo/prism-observability-opentelemetry` (see [Observability](observability.md)) rather than adding a parallel event bus. Middleware hooks remain for transforming payloads at named boundaries.
|
|
104
105
|
|
|
105
106
|
## Security and performance notes
|
|
106
107
|
|
|
@@ -118,6 +119,7 @@ export const extension: Extension = {
|
|
|
118
119
|
- [Input and prompt assembly](input-and-prompt-assembly.md): `input_assembly` and `prompt_build` helper call sites.
|
|
119
120
|
- [Compaction and retry policies](compaction-and-retry.md): compaction/retry middleware payloads and runtime timing.
|
|
120
121
|
- [Context and skills](context-and-skills.md): `context` helper call site.
|
|
122
|
+
- [Observability](observability.md): optional OpenTelemetry adapter over `AgentEvent` streams.
|
|
121
123
|
- [Public contracts](public-contracts.md): provider, tool, context, session, and extension contracts that runtimes can pass through hooks.
|
|
122
124
|
|
|
123
125
|
Permission checks for tools, extensions, and resources are hard guards; middleware can transform payloads but cannot bypass a denied `PermissionPolicy`.
|
package/docs/migration.md
CHANGED
|
@@ -2,12 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## What it does
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Prism 0.0.5 preserves documented 0.0.3 agent construction except for two intentional Phase 3 public-API cleanups:
|
|
6
6
|
|
|
7
|
-
1.
|
|
8
|
-
2.
|
|
7
|
+
1. **`session.run()` / `session.prompt()` return `AgentRunResult`** and `session.stream()` starts one owned run after subscribing. Callers that ignored the previous `Promise<void>` keep working; failed/aborted runs reject with `AgentRunError` (`.result` attached).
|
|
8
|
+
2. **`AgentConfig.extensions` / `settings` / `credentials` are removed.** Wire extensions through `createExtensionKernel()`, read settings in the host, and pass credential resolvers to the provider edge.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Phase 4 adds optional `@arnilo/prism-evals` for deterministic scorers/datasets/experiments. It is not a core dependency; install it directly or through `@arnilo/prism-all`.
|
|
11
|
+
|
|
12
|
+
Phase 5 adds `prism init <dir>` to the existing CLI. It scaffolds a tiny TypeScript project with one selected provider and an offline mock test. Optional `--with-workflows` / `--with-evals` flags add only those packages; storage and telemetry stay opt-in elsewhere.
|
|
13
|
+
|
|
14
|
+
Phase 6 adds optional `@arnilo/prism-provider-ai-sdk` for AI SDK `LanguageModelV4` interoperability. Install it with `@ai-sdk/provider@^4`, through `@arnilo/prism-providers`, or through `@arnilo/prism-all`; it is not a core dependency.
|
|
15
|
+
|
|
16
|
+
Phase 7 adds optional `@arnilo/prism-memory` for schema/template-backed working memory and embedding-based semantic recall. Install it directly or through `@arnilo/prism-all`; in-memory adapters are default, and PostgreSQL/pgvector is opt-in. It is not a core dependency.
|
|
17
|
+
|
|
18
|
+
Phase 8 extends `@arnilo/prism-workflows` compatibly. Nodes may return `suspend()`, and opted-in tool nodes may declare `approval`. Resuming a suspended run requires `{ decision, input?, expectedVersion }`; ordinary failed/aborted recovery resume remains unchanged. `WorkflowRunStatus` adds `suspended` and terminal `denied`. Suspension/resume records remain bounded checkpoint JSON, so SQLite/PostgreSQL require no migration.
|
|
19
|
+
|
|
20
|
+
Phase 9 adds optional `@arnilo/prism-rag` for bounded plain-text/Markdown chunking, Phase 7 vector indexing/retrieval, stable citations, and explicit context injection. Existing agents and memory stores are unchanged; install and attach its context provider explicitly. No database migration is required.
|
|
21
|
+
|
|
22
|
+
Phase 10 adds optional `@arnilo/prism-server` and extends `@arnilo/prism-mcp` with server-direction APIs. Existing agent/workflow/MCP client behavior is unchanged. Install the server package explicitly, pass selected capability maps plus required host authorization, and adapt its Web handler in the deployment host. MCP servers likewise register only passed tools/commands and require authorization. No listener, route, credential source, profile package, or database migration activates automatically.
|
|
23
|
+
|
|
24
|
+
Phase 11 compatibly extends `@arnilo/prism-workflows` with `workflowNode`, shared state fields/context updates, replay lineage, explicit background enqueue, and ownership-scoped schedules. Existing workflow definitions and direct runs remain valid; `WorkflowRunResult` now always includes `state`. State schemas require a host `validateState` callback. Schedules reuse generic checkpoint/lease stores, so SQLite/PostgreSQL need no migration and no scheduler starts automatically.
|
|
25
|
+
|
|
26
|
+
This page also covers two optional adoption paths:
|
|
27
|
+
|
|
28
|
+
1. **In-memory / JSONL → database-backed persistence** — replace the single-process development `SessionStore` with `@arnilo/prism-session-store-sqlite`, `@arnilo/prism-session-store-postgres`, or a host implementation, and optionally attach its durable `RunLedger`.
|
|
29
|
+
2. **Legacy permissive capability configuration → explicit activation** — name tools/skills and keep omitted capabilities fail-closed.
|
|
30
|
+
|
|
31
|
+
It states before/after shapes and links detailed schema, redaction, branch, capability, and security guidance.
|
|
11
32
|
|
|
12
33
|
## When to use it
|
|
13
34
|
|
|
@@ -15,7 +36,7 @@ Read this page when:
|
|
|
15
36
|
|
|
16
37
|
- you are taking an app from the `createMemorySessionStore()` / `createJsonlSessionStore()` path to a multi-process, multi-tenant, or durable database backend;
|
|
17
38
|
- you are hardening an agent that previously relied on "every scoped tool/skill is active" and need to name capabilities explicitly;
|
|
18
|
-
- you are adopting
|
|
39
|
+
- you are adopting 0.0.5 persistence, checkpoints/leases, workflows, structured output, multimodality, or explicit tool safety for the first time.
|
|
19
40
|
|
|
20
41
|
If you are new to Prism, start at [Session stores](session-stores.md) and [Agent/session runtime](agent-session-runtime.md) instead.
|
|
21
42
|
|
|
@@ -26,6 +47,8 @@ There is no runtime import for this page. The migrations below use these surface
|
|
|
26
47
|
| Surface | Where | Migration role |
|
|
27
48
|
| --- | --- | --- |
|
|
28
49
|
| `SessionStore` | `@arnilo/prism` | Runtime seam swapped from memory/JSONL to DB. |
|
|
50
|
+
| `createSqlitePersistence` | `@arnilo/prism-session-store-sqlite` | Local durable session, ledger, query, checkpoint, and lease adapter. |
|
|
51
|
+
| `createPostgresPersistence` | `@arnilo/prism-session-store-postgres` | Multi-process pooled persistence with advisory-lock migrations. |
|
|
29
52
|
| `ProductionPersistenceStore` | `@arnilo/prism` | Adapter-facing contract for paginated, multi-tenant reads (`query*`, optional `readBranchPath`). |
|
|
30
53
|
| `RunLedger` / `RunLedgerRecord` | `@arnilo/prism` | Durable run/event/tool-call/usage ledger attached via `AgentConfig.runLedger` / `RunOptions.runLedger`. |
|
|
31
54
|
| `SessionAppendOptions` / `SessionAppendConflictError` / `SessionBranchHandle` | `@arnilo/prism` | Atomic append, retry dedup, durable branch handles. |
|
|
@@ -77,34 +100,23 @@ Capability migration (before/after):
|
|
|
77
100
|
|
|
78
101
|
### Migration 1 — in-memory / JSONL → database-backed persistence
|
|
79
102
|
|
|
80
|
-
|
|
103
|
+
Runnable references: [`examples/workflow-sqlite-resume.ts`](../examples/workflow-sqlite-resume.ts), credential-gated [`examples/workflow-postgres-resume.ts`](../examples/workflow-postgres-resume.ts), and the network-free custom-adapter example [`examples/external-app-db-backed.ts`](../examples/external-app-db-backed.ts).
|
|
81
104
|
|
|
82
|
-
Step 1:
|
|
105
|
+
Step 1: replace the development store with a first-party adapter. Use PostgreSQL instead when multiple processes or sustained concurrent writers matter.
|
|
83
106
|
|
|
84
107
|
```ts
|
|
85
108
|
// Before: development store, single process.
|
|
86
109
|
import { createJsonlSessionStore } from "@arnilo/prism/node/session-store-jsonl";
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
// After:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const store
|
|
93
|
-
async append(entry: SessionEntry, options?: SessionAppendOptions) {
|
|
94
|
-
// 1. idempotency dedup: insert (session_id, expected_parent_id, idempotency_key, entry_id)
|
|
95
|
-
// into prism_session_append_idempotency; unique hit => SessionAppendConflictError { idempotencyDuplicate: true }
|
|
96
|
-
// 2. expectedParentId existence check => SessionAppendConflictError { expectedParentId } if missing
|
|
97
|
-
// 3. insert prism_session_entries row; duplicate id fails the transaction
|
|
98
|
-
// 4. optionally compare-and-swap prism_branches.leaf_entry_id
|
|
99
|
-
},
|
|
100
|
-
async list(sessionId: string) { /* O(n) development fallback only */ return []; },
|
|
101
|
-
async readBranchPath(query: SessionBranchRead): Promise<PersistencePage<SessionEntry>> {
|
|
102
|
-
// one recursive CTE / ancestor query — do NOT list(sessionId)+in-memory walk for long sessions
|
|
103
|
-
return { items: [] };
|
|
104
|
-
},
|
|
105
|
-
};
|
|
110
|
+
const oldStore = createJsonlSessionStore("./sessions.jsonl");
|
|
111
|
+
|
|
112
|
+
// After: local durable adapter. The same object implements SessionStore,
|
|
113
|
+
// RunLedger, ProductionPersistenceStore, checkpoints, and leases.
|
|
114
|
+
import { createSqlitePersistence } from "@arnilo/prism-session-store-sqlite";
|
|
115
|
+
const store = createSqlitePersistence({ filename: "./prism.db" });
|
|
106
116
|
```
|
|
107
117
|
|
|
118
|
+
Custom adapters remain supported through `SessionStore` / `ProductionPersistenceStore`; implement indexed `readBranchPath()` rather than full-session scans.
|
|
119
|
+
|
|
108
120
|
Step 2: optionally attach a durable run/event/tool/usage ledger and ownership scope so a process exit leaves enough to resume and bill:
|
|
109
121
|
|
|
110
122
|
```ts
|
|
@@ -134,6 +146,8 @@ What you leave behind and why:
|
|
|
134
146
|
- `createMemorySessionStore()` — process-local maps; lost on restart, no cross-process locking. Keep for tests.
|
|
135
147
|
- `createJsonlSessionStore()` — single-process file adapter; reads are linear in file size, no cross-process lock, no durable idempotency table, two writers to the same file can race. Keep for local/dev only.
|
|
136
148
|
|
|
149
|
+
Prism 0.0.5 persistence adapters automatically apply additive schema step `002_usage_scope`, then `003_run_feedback`. Migration 003 creates immutable owned run/trace feedback with a run FK, cascade deletion, JSON tag/scorer/evaluation ID lists, and owner/run/trace cursor indexes. Existing rows are unchanged. Custom adapters may omit optional `ProductionPersistenceStore.feedback`; adopters implement `RunFeedbackStore` append/query/delete semantics and must verify exact linked-run ownership before insert.
|
|
150
|
+
|
|
137
151
|
See [Database persistence](database-persistence.md) for the full reference schema, indexes, conditional-append transaction pattern, retention, and NoSQL mapping; [Session stores](session-stores.md) for the `SessionStore` contract and branch helpers; [Session stores and branching](session-stores-and-branching.md) for branch semantics; [Runs and usage ledger](runs-and-usage.md) for the `RunLedger` record shapes and ordering rules.
|
|
138
152
|
|
|
139
153
|
### Migration 2 — permissive capability defaults → explicit capability activation
|
|
@@ -174,7 +188,7 @@ Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per
|
|
|
174
188
|
|
|
175
189
|
## Extension and configuration notes
|
|
176
190
|
|
|
177
|
-
- **Persistence
|
|
191
|
+
- **Persistence remains host-configured.** Optional SQLite/PostgreSQL packages ship adapters and versioned setup, but hosts choose connection paths/pools, TLS, credentials, retention, tenant policy, and lifecycle. Core only consumes `SessionStore`, `RunLedger`, feedback, checkpoint, and lease contracts.
|
|
178
192
|
- **`RunLedger` is not a `SessionStore` replacement.** Messages, branches, and session entries still flow through `SessionStore.append()`; the ledger records run/event/tool/usage facts. See [Runs and usage ledger](runs-and-usage.md).
|
|
179
193
|
- **Capability activation is config over code.** Every seam lives on `AgentDefinition` / `AgentDefinitionResolutionContext` / `RunOptions`; no auto-activation, no privilege grant. A declaration cannot grant permissions or bypass `toolNames`.
|
|
180
194
|
- **Migration order is decoupled.** You can adopt database persistence without changing capability activation, and vice versa. Both migrations are independent config swaps.
|
|
@@ -190,7 +204,16 @@ Runtime skill activation remains explicit: `RunOptions.activeSkills` narrows per
|
|
|
190
204
|
|
|
191
205
|
## Related APIs
|
|
192
206
|
|
|
193
|
-
- [
|
|
207
|
+
- [Evaluations](evaluations.md): optional `@arnilo/prism-evals` scorers/datasets/experiments over `AgentRunResult`.
|
|
208
|
+
- [AI SDK provider adapter](providers/ai-sdk.md): optional `@arnilo/prism-provider-ai-sdk` `LanguageModelV4` bridge.
|
|
209
|
+
- [Working and semantic memory](working-and-semantic-memory.md): optional `@arnilo/prism-memory` working/semantic recall primitives.
|
|
210
|
+
- [Retrieval-augmented generation](rag.md): optional text/Markdown chunk, index, retrieval, and citation helpers.
|
|
211
|
+
- [Web-standard server handler](server.md): optional authorized agent/workflow HTTP routes.
|
|
212
|
+
- [Supervisor delegation](supervisors.md) and [A2A interoperability](a2a.md): optional install only; core agent/workflow behavior is unchanged. Child factories now receive package-derived memory IDs and narrowing permission, while remote endpoints require exact HTTPS origin allow-lists.
|
|
213
|
+
- [MCP client/server exposure](mcp-tools.md): selected MCP tools/commands and bounded Web transport.
|
|
214
|
+
- [Database persistence](database-persistence.md): production contracts, reference schema, indexes, conditional append, retention, migrations, and custom adapters.
|
|
215
|
+
- [SQLite persistence](sqlite-persistence.md): local durable first-party adapter and writer ceiling.
|
|
216
|
+
- [PostgreSQL persistence](postgres-persistence.md): pooled multi-process adapter, TLS/pool ownership, and live gate.
|
|
194
217
|
- [Session stores](session-stores.md): `SessionStore` contract, `SessionAppendOptions`, `SessionAppendConflictError`, branch handles, `readBranchPath`.
|
|
195
218
|
- [Session stores and branching](session-stores-and-branching.md): detailed branch semantics and helper reference.
|
|
196
219
|
- [Runs and usage ledger](runs-and-usage.md): `RunLedger` record shapes, redaction, and event/usage ordering.
|
package/docs/model-registry.md
CHANGED
|
@@ -36,7 +36,7 @@ import { createModelRegistry, type ModelConfig } from "@arnilo/prism";
|
|
|
36
36
|
| --- | --- |
|
|
37
37
|
| `provider` / `model` | Required registry key. |
|
|
38
38
|
| `displayName` | Human-readable label. |
|
|
39
|
-
| `capabilities` | Input/output modes plus reasoning/tools/streaming booleans. |
|
|
39
|
+
| `capabilities` | Input/output modes (`text`, `image`, `audio`, `file`, `document`) plus reasoning/tools/streaming booleans and optional `structuredOutput` (`true` or `"json_schema"`) for native JSON-schema requests. |
|
|
40
40
|
| `limits` | Context and output-token limits. |
|
|
41
41
|
| `cost` | Input/output/cache read/cache write pricing. |
|
|
42
42
|
| `cache` | Generic `ModelCacheCapabilities`. |
|
|
@@ -74,7 +74,7 @@ The registry emits no events and performs no I/O.
|
|
|
74
74
|
"model": {
|
|
75
75
|
"provider": "demo",
|
|
76
76
|
"model": "demo-large",
|
|
77
|
-
"capabilities": { "input": ["text"], "tools": true, "streaming": true },
|
|
77
|
+
"capabilities": { "input": ["text", "image", "audio", "file", "document"], "tools": true, "streaming": true },
|
|
78
78
|
"limits": { "contextWindow": 128000, "maxOutputTokens": 8192 },
|
|
79
79
|
"cost": { "input": 10, "output": 30, "cacheRead": 2, "currency": "USD", "unit": "1M tokens" },
|
|
80
80
|
"cache": { "kind": "cache_control", "maxBreakpoints": 4, "longRetention": true }
|
|
@@ -91,7 +91,7 @@ const model: ModelConfig = {
|
|
|
91
91
|
provider: "demo",
|
|
92
92
|
model: "demo-large",
|
|
93
93
|
displayName: "Demo Large",
|
|
94
|
-
capabilities: { input: ["text"], output: ["text"], tools: true, streaming: true },
|
|
94
|
+
capabilities: { input: ["text", "document"], output: ["text"], tools: true, streaming: true },
|
|
95
95
|
limits: { contextWindow: 128_000, maxOutputTokens: 8_192 },
|
|
96
96
|
cost: { input: 10, output: 30, cacheRead: 2, cacheWrite: 12, currency: "USD", unit: "1M tokens" },
|
|
97
97
|
cache: { kind: "cache_control", maxBreakpoints: 4, minCacheableTokens: 1024, longRetention: true },
|
|
@@ -110,12 +110,14 @@ Provider packages register models through `ProviderPackageAPI.registerModel(mode
|
|
|
110
110
|
## Security and performance notes
|
|
111
111
|
|
|
112
112
|
- Model metadata must not contain credentials or secrets.
|
|
113
|
+
- Declare truthful `capabilities.input` tags. Prism core rejects undeclared modalities in `assembleProviderInput()` when the list is present.
|
|
113
114
|
- Registration is in-memory and O(1) by provider/model key.
|
|
114
115
|
- `ModelConfig.cache` is declarative capability info only; it does not grant permissions, select tools, or bypass auth.
|
|
115
116
|
- Provider-specific behavior belongs in provider packages, not Prism core.
|
|
116
117
|
|
|
117
118
|
## Related APIs
|
|
118
119
|
|
|
120
|
+
- [Multimodal content](multimodal-content.md): `audio`/`file`/`document` blocks and `MODEL_INPUT_CAPABILITIES`.
|
|
119
121
|
- [Provider layer](provider-layer.md): provider/model registry overview.
|
|
120
122
|
- [Provider caching](provider-caching.md): `ModelCacheCapabilities` and cache helpers.
|
|
121
123
|
- [Provider packages](provider-packages.md): package registration of model metadata.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Multimodal content
|
|
2
|
+
|
|
3
|
+
## What it does
|
|
4
|
+
|
|
5
|
+
Prism core ships generic `audio`, `file`, and `document` `ContentBlock` types plus bounded media resolution helpers. Blocks carry MIME type, optional name, and exactly one source: inline base64 `data`, remote `url`, or host `resourceUri`. Optional `transcript` metadata can accompany audio/document blocks.
|
|
6
|
+
|
|
7
|
+
`assembleProviderInput()` calls `assertMessagesSupportModelCapabilities()` so declared `ModelCapabilities.input` tags are enforced before provider calls. First-party provider packages map supported blocks locally; unsupported combinations fail closed with `UnsupportedModalityError` or an explicit provider error.
|
|
8
|
+
|
|
9
|
+
## When to use it
|
|
10
|
+
|
|
11
|
+
- **Host apps** attaching PDFs, audio clips, or generic files to user messages before a provider turn.
|
|
12
|
+
- **Resource loaders** returning binary payloads for `resourceUri` references under trust/permission policy.
|
|
13
|
+
- **Provider authors** reading truthful `ModelCapabilities.input` tags (`text`, `image`, `audio`, `file`, `document`) before mapping wire formats.
|
|
14
|
+
|
|
15
|
+
Do not embed provider upload IDs, tenant-scoped remote file IDs, or API-specific handles in core content blocks.
|
|
16
|
+
|
|
17
|
+
## Inputs / request
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import {
|
|
21
|
+
resolveMediaContentBlock,
|
|
22
|
+
resolveMediaContentBlocks,
|
|
23
|
+
assertSsrfAllowedUrl,
|
|
24
|
+
type AudioContent,
|
|
25
|
+
type FileContent,
|
|
26
|
+
type DocumentContent,
|
|
27
|
+
} from "@arnilo/prism";
|
|
28
|
+
|
|
29
|
+
const file: FileContent = {
|
|
30
|
+
type: "file",
|
|
31
|
+
mediaType: "application/pdf",
|
|
32
|
+
name: "report.pdf",
|
|
33
|
+
data: base64Pdf,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const audio: AudioContent = {
|
|
37
|
+
type: "audio",
|
|
38
|
+
mediaType: "audio/wav",
|
|
39
|
+
resourceUri: "package://demo/sample.wav",
|
|
40
|
+
durationMs: 12_000,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await resolveMediaContentBlock(file, { bounds: { maxItemBytes: 10_000_000 } });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`ResolveMediaContentOptions` also exposes `ssrf`, `fetch`, `resolveHostname`, `requestUrl`, `loader`, `loadContext`, and `signal`. `MediaHostnameResolver`, `MediaHostAddress`, `MediaUrlRequester`, and `MediaUrlRequest` are exported for typed custom/test transports; ordinary callers need none of them.
|
|
47
|
+
|
|
48
|
+
Known `ModelCapabilities.input` tags are exported as `MODEL_INPUT_CAPABILITIES`:
|
|
49
|
+
|
|
50
|
+
| Tag | Block type | First-party mapping (declared capability required) |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `text` | `text` (default) | All providers |
|
|
53
|
+
| `image` | `image` | OpenAI Responses, OpenRouter, OpenCode Go Anthropic route, Kimi, NeuralWatt |
|
|
54
|
+
| `audio` | `audio` | OpenAI Responses (`input_audio`) |
|
|
55
|
+
| `file` | `file` | OpenAI Responses (`input_file`); Anthropic routes map PDF only |
|
|
56
|
+
| `document` | `document` | OpenAI Responses (`input_file`); OpenCode Go Anthropic route; Kimi |
|
|
57
|
+
|
|
58
|
+
## Outputs / response / events
|
|
59
|
+
|
|
60
|
+
- `resolveMediaContentBlock()` resolves one item and returns `{ mediaType, bytes, name?, durationMs?, transcript?, metadata? }`.
|
|
61
|
+
- `resolveMediaContentBlocks()` is the complete-request path: it rejects item count/inline estimates before I/O, resolves each item within its bound, then enforces exact aggregate decoded bytes.
|
|
62
|
+
- `assertModelSupportsContentBlocks()` / `assertMessagesSupportModelCapabilities()` throw `UnsupportedModalityError` when a declared capability list omits the block modality.
|
|
63
|
+
- `assertMediaBlocksWithinBounds()` enforces per-item bytes, total request bytes, item count, and audio duration ceilings.
|
|
64
|
+
- `assertSsrfAllowedUrl()` rejects loopback, private, link-local, unspecified, multicast, IPv4-mapped private, and metadata literals/hosts unless explicitly allow-listed.
|
|
65
|
+
- Default URL resolution performs one bounded `dns.lookup(..., { all: true })`, rejects the whole hostname if any answer is non-public, then pins the selected public address into `http.request()`/`https.request()` so a second DNS lookup cannot rebind the connection.
|
|
66
|
+
- `sniffMediaMimeType()` / `assertDeclaredMediaTypeMatches()` compare declared MIME types to magic bytes.
|
|
67
|
+
- No events are emitted and no provider calls occur in these helpers.
|
|
68
|
+
|
|
69
|
+
Default ceilings:
|
|
70
|
+
|
|
71
|
+
| Constant | Value |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `DEFAULT_MAX_MEDIA_ITEM_BYTES` | 10 MB |
|
|
74
|
+
| `DEFAULT_MAX_MEDIA_REQUEST_BYTES` | 32 MiB |
|
|
75
|
+
| `DEFAULT_MAX_AUDIO_DURATION_MS` | 5 minutes |
|
|
76
|
+
| `DEFAULT_MEDIA_FETCH_TIMEOUT_MS` | 30 seconds |
|
|
77
|
+
| `DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST` | 32 items |
|
|
78
|
+
|
|
79
|
+
## Request/response example
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"block": {
|
|
84
|
+
"type": "file",
|
|
85
|
+
"mediaType": "application/pdf",
|
|
86
|
+
"name": "report.pdf",
|
|
87
|
+
"resourceUri": "package://demo/report.pdf"
|
|
88
|
+
},
|
|
89
|
+
"resolved": {
|
|
90
|
+
"mediaType": "application/pdf",
|
|
91
|
+
"bytes": "<Uint8Array>",
|
|
92
|
+
"name": "report.pdf"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Implementation example
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import {
|
|
101
|
+
assembleProviderInput,
|
|
102
|
+
loadBinaryResource,
|
|
103
|
+
resolveMediaContentBlock,
|
|
104
|
+
UnsupportedModalityError,
|
|
105
|
+
} from "@arnilo/prism";
|
|
106
|
+
|
|
107
|
+
const loader = {
|
|
108
|
+
async load(uri) {
|
|
109
|
+
return { uri, mediaType: "application/pdf", data: pdfBytes };
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const bytes = await loadBinaryResource(loader, "package://demo/report.pdf");
|
|
114
|
+
const resolved = await resolveMediaContentBlock(
|
|
115
|
+
{ type: "document", mediaType: "application/pdf", resourceUri: "package://demo/report.pdf" },
|
|
116
|
+
{ loader },
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await assembleProviderInput({
|
|
121
|
+
model: { provider: "demo", model: "text-only", capabilities: { input: ["text"] } },
|
|
122
|
+
input: [{ role: "user", content: [{ type: "file", mediaType: "application/pdf", data: "..." }] }],
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error instanceof UnsupportedModalityError) {
|
|
126
|
+
// Host-visible reject before provider HTTP.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Extension and configuration notes
|
|
132
|
+
|
|
133
|
+
- URL fetches use the DNS-classifying, address-pinned Node transport by default. `resolveHostname` and `requestUrl` are paired test/custom seams; `requestUrl` must connect to the supplied validated address while preserving the original URL hostname for HTTP Host/TLS verification.
|
|
134
|
+
- Supplying `fetch` is a trusted compatibility/custom-transport escape hatch: Prism still checks URL literals and host allow-lists, but the host-provided fetch owns DNS resolution, rebinding protection, redirects, proxies, TLS, auth, and logging.
|
|
135
|
+
- `resourceUri` resolution requires a caller-provided `ResourceLoader` and optional `ResourceLoadContext.permission` check.
|
|
136
|
+
- Local filesystem paths should use trust policies such as `createPathTrustPolicy()` before exposing URIs to loaders.
|
|
137
|
+
- Provider upload/create/delete lifecycles are provider-package-local. `@arnilo/prism-provider-openai` inlines files under 4 MiB as `data:<mediaType>;base64,...` `file_data`, otherwise uses a bounded per-run upload cache and best-effort `DELETE /v1/files` cleanup after each stream.
|
|
138
|
+
- Shared wire helpers live in `@arnilo/prism/providers/media` (`resolveProviderMediaMessages`, `serializeOpenAIResponsesInputFile`, `serializePdfDocumentWireBlock`, `createBoundedUploadCache`). OpenAI Responses, Kimi, and OpenCode Go Anthropic routes resolve their complete media collection once before serialization or upload.
|
|
139
|
+
|
|
140
|
+
## Security and performance notes
|
|
141
|
+
|
|
142
|
+
- SSRF deny-by-default blocks IPv4/IPv6 loopback, private/unique-local, link-local, unspecified, multicast, IPv4-mapped private, and cloud metadata targets. DNS answers are all classified before one public address is pinned; mixed public/private answers fail closed.
|
|
143
|
+
- `allowedHostnames` is an explicit trust override and may permit a private destination. `denyPrivateHosts: false` is broader and should be reserved for hosts that intentionally own private-network access.
|
|
144
|
+
- DNS lookup, connection, and body streaming share `fetchTimeoutMs` and caller abort; more than 32 resolved addresses, redirects, and oversized response bodies are rejected.
|
|
145
|
+
- MIME validation rejects common magic-byte spoofing; extensions alone are never trusted.
|
|
146
|
+
- Byte budgets use base64 size estimates before decode and re-check decoded bytes after every read/fetch. Complete-request resolution keeps at most the configured request budget plus one bounded item in memory and performs no provider upload/request until validation succeeds.
|
|
147
|
+
- Media errors omit raw bytes/base64 payloads from messages.
|
|
148
|
+
- Fetch readers are cancelled promptly after bound violations or abort signals.
|
|
149
|
+
|
|
150
|
+
## Related APIs
|
|
151
|
+
|
|
152
|
+
- [Input and prompt assembly](input-and-prompt-assembly.md): attachments and `assembleProviderInput()` capability checks.
|
|
153
|
+
- [Resource loading](resource-loading.md): `loadBinaryResource()` and text/JSON helpers.
|
|
154
|
+
- [Model registry](model-registry.md): `ModelCapabilities.input` metadata.
|
|
155
|
+
- [Provider conformance](provider-conformance.md): serialized request coverage for content blocks.
|
|
156
|
+
- [Persistence, credentials, and multimodality primitives](persistence-credentials-multimodality-primitives.md): Plan 056 inventory and threat model.
|