@arnilo/prism 0.0.1

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 (106) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +139 -0
  4. package/dist/agents.d.ts +5 -0
  5. package/dist/agents.js +439 -0
  6. package/dist/cli-runner.d.ts +33 -0
  7. package/dist/cli-runner.js +167 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +10 -0
  10. package/dist/compaction.d.ts +9 -0
  11. package/dist/compaction.js +67 -0
  12. package/dist/config.d.ts +17 -0
  13. package/dist/config.js +69 -0
  14. package/dist/contracts.d.ts +670 -0
  15. package/dist/contracts.js +2 -0
  16. package/dist/contributions.d.ts +35 -0
  17. package/dist/contributions.js +47 -0
  18. package/dist/credentials.d.ts +22 -0
  19. package/dist/credentials.js +63 -0
  20. package/dist/extensions.d.ts +25 -0
  21. package/dist/extensions.js +131 -0
  22. package/dist/index.d.ts +47 -0
  23. package/dist/index.js +28 -0
  24. package/dist/input.d.ts +57 -0
  25. package/dist/input.js +225 -0
  26. package/dist/manifests.d.ts +28 -0
  27. package/dist/manifests.js +108 -0
  28. package/dist/middleware.d.ts +15 -0
  29. package/dist/middleware.js +49 -0
  30. package/dist/mock-provider.d.ts +6 -0
  31. package/dist/mock-provider.js +14 -0
  32. package/dist/models.d.ts +8 -0
  33. package/dist/models.js +25 -0
  34. package/dist/node/config.d.ts +9 -0
  35. package/dist/node/config.js +51 -0
  36. package/dist/node/session-store-jsonl.d.ts +17 -0
  37. package/dist/node/session-store-jsonl.js +134 -0
  38. package/dist/node/settings.d.ts +7 -0
  39. package/dist/node/settings.js +26 -0
  40. package/dist/node/trust.d.ts +13 -0
  41. package/dist/node/trust.js +56 -0
  42. package/dist/provider-events.d.ts +15 -0
  43. package/dist/provider-events.js +30 -0
  44. package/dist/provider-packages.d.ts +4 -0
  45. package/dist/provider-packages.js +12 -0
  46. package/dist/provider-request-policy.d.ts +9 -0
  47. package/dist/provider-request-policy.js +49 -0
  48. package/dist/providers/openai-compatible.d.ts +9 -0
  49. package/dist/providers/openai-compatible.js +197 -0
  50. package/dist/providers.d.ts +8 -0
  51. package/dist/providers.js +25 -0
  52. package/dist/redaction.d.ts +11 -0
  53. package/dist/redaction.js +68 -0
  54. package/dist/resources.d.ts +5 -0
  55. package/dist/resources.js +30 -0
  56. package/dist/retry.d.ts +11 -0
  57. package/dist/retry.js +48 -0
  58. package/dist/rpc.d.ts +18 -0
  59. package/dist/rpc.js +187 -0
  60. package/dist/security.d.ts +51 -0
  61. package/dist/security.js +60 -0
  62. package/dist/session-stores.d.ts +25 -0
  63. package/dist/session-stores.js +116 -0
  64. package/dist/settings.d.ts +3 -0
  65. package/dist/settings.js +26 -0
  66. package/dist/skills.d.ts +8 -0
  67. package/dist/skills.js +34 -0
  68. package/dist/system-prompts.d.ts +6 -0
  69. package/dist/system-prompts.js +47 -0
  70. package/dist/testing/provider-conformance.d.ts +36 -0
  71. package/dist/testing/provider-conformance.js +164 -0
  72. package/dist/tools.d.ts +25 -0
  73. package/dist/tools.js +109 -0
  74. package/docs/agent-session-runtime.md +167 -0
  75. package/docs/api-page-template.md +32 -0
  76. package/docs/cli-rpc.md +140 -0
  77. package/docs/compaction-and-retry.md +177 -0
  78. package/docs/compaction-llm.md +108 -0
  79. package/docs/compaction-observational-memory.md +123 -0
  80. package/docs/configuration-and-manifests.md +142 -0
  81. package/docs/context-and-skills.md +113 -0
  82. package/docs/contribution-registries.md +118 -0
  83. package/docs/credentials-and-redaction.md +122 -0
  84. package/docs/extensions.md +139 -0
  85. package/docs/index.md +56 -0
  86. package/docs/input-and-prompt-assembly.md +168 -0
  87. package/docs/middleware-hooks.md +123 -0
  88. package/docs/node-filesystem-config.md +85 -0
  89. package/docs/node-jsonl-session-store.md +81 -0
  90. package/docs/provider-conformance.md +109 -0
  91. package/docs/provider-layer.md +172 -0
  92. package/docs/provider-packages.md +171 -0
  93. package/docs/providers/kimi.md +110 -0
  94. package/docs/providers/openai-compatible.md +125 -0
  95. package/docs/providers/openai.md +131 -0
  96. package/docs/providers/opencode-go.md +103 -0
  97. package/docs/providers/openrouter.md +105 -0
  98. package/docs/providers/zai.md +108 -0
  99. package/docs/public-contracts.md +375 -0
  100. package/docs/release-and-install.md +141 -0
  101. package/docs/resource-loading.md +97 -0
  102. package/docs/session-stores-and-branching.md +119 -0
  103. package/docs/settings-auth-trust-security.md +73 -0
  104. package/docs/system-prompts.md +116 -0
  105. package/docs/tools.md +151 -0
  106. package/package.json +93 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Added `LICENSE` (MIT) and `CHANGELOG.md` to the published `prism` package.
13
+ - Added npm package metadata: `license`, `repository`, `bugs`, `homepage`,
14
+ `keywords`, and `sideEffects`.
15
+
16
+ ### Changed
17
+
18
+ - `files` whitelist now explicitly excludes `dist/__tests__/` and
19
+ `dist/**/*.map` from published tarballs; source maps remain emitted locally
20
+ for debugging but are no longer shipped.
21
+ - Core tarball now ships the `/docs` hub.
22
+ - Made `prism` a required peer dependency for all first-party workspace packages; it is no longer optional. The peer range remains `0.0.1` and will widen to `^1.0.0` at the 1.x stable release.
23
+ - Pinned the no-network `npm test` budget at < 30s on Node 20 (measured baseline ~22s); recorded in `roadmap.md` Phase 17.
24
+
25
+ ## [0.0.1] - 2026-06-22
26
+
27
+ ### Added
28
+
29
+ - Initial release of Prism: a framework for building agentic LLM applications
30
+ with configurable providers, sessions, tools, context providers, compaction,
31
+ extensions, and trust boundaries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prism contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # prism
2
+
3
+ `prism` is a TypeScript/Node.js agent harness. Host apps and extension packages
4
+ bring their tools, providers, credentials, storage, and UI; Prism supplies the
5
+ common contracts, registries, agent/session runtime, replaceable input/prompt
6
+ and compaction strategies, CLI/RPC adapters, and first-party provider/compaction
7
+ packages. Prism defines contracts, not apps.
8
+
9
+ ## Current scope
10
+
11
+ - **Agent/session runtime**: `createAgent`/`createAgentSession`, run prompts,
12
+ dispatch host tools, subscribe to normalized `AgentEvent` streams, abort runs,
13
+ compact, and navigate branches.
14
+ - **Providers and models**: provider/model registries, provider event helpers,
15
+ credential redaction helpers, mock provider, and an optional
16
+ OpenAI-compatible provider subpath.
17
+ - **First-party packages**: `@arnilo/prism-provider-openai`, `@arnilo/prism-provider-opencode-go`,
18
+ `@arnilo/prism-provider-openrouter`, `@arnilo/prism-provider-zai`, `@arnilo/prism-provider-kimi`,
19
+ `@arnilo/prism-compaction-llm`, and `@arnilo/prism-compaction-observational-memory`.
20
+ - **Tools, context, skills**: host-owned tool registry with allow/deny filtering
21
+ and dispatch, context providers, and a skill registry with progressive
22
+ disclosure.
23
+ - **Input/prompt/context**: default input and prompt builders, system-prompt
24
+ layering, and provider-input assembly — every stage replaceable.
25
+ - **Sessions and memory**: in-memory and JSONL session stores, branching/fork/
26
+ clone, default and LLM compaction strategies, retry policy, and
27
+ observational-memory recall/status/view.
28
+ - **Extensions and manifests**: extension kernel + event bus, contribution
29
+ registries, middleware hooks, and data-only package manifests.
30
+ - **Config, settings, security**: layered config merge, settings providers,
31
+ credential resolvers, trust/permission policies, and secret redaction.
32
+ - **CLI/RPC**: `prism --mode print|json|rpc` over the same `AgentSession` API.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm install @arnilo/prism
38
+ ```
39
+
40
+ First-party provider/compaction packages are separate installs and require
41
+ `@arnilo/prism` as a non-optional peer. Install a single package, or use an
42
+ umbrella to grab a whole family in one line:
43
+
44
+ ```bash
45
+ npm install @arnilo/prism @arnilo/prism-provider-openai # core + one provider
46
+ npm install @arnilo/prism @arnilo/prism-providers # core + all providers
47
+ npm install @arnilo/prism-all # everything
48
+ ```
49
+
50
+ See [docs/release-and-install.md](docs/release-and-install.md) for install
51
+ specifiers, tarball contents, and the offline test budget.
52
+
53
+ ## Quick start
54
+
55
+ ```ts
56
+ import { createAgent, createAgentSession, createMockProvider } from "@arnilo/prism";
57
+
58
+ // Host owns the provider. createMockProvider is for tests/demos only.
59
+ const agent = createAgent({
60
+ model: { provider: "mock", model: "demo" },
61
+ provider: createMockProvider([{ type: "text", text: "Hello" }, { type: "done" }]),
62
+ });
63
+
64
+ const session = createAgentSession({ agent });
65
+
66
+ (async () => {
67
+ for await (const event of session.subscribe()) {
68
+ // AgentEvent: agent_started, message_delta, turn_finished, ...
69
+ }
70
+ await session.run("Hi");
71
+ })();
72
+ ```
73
+
74
+ Register a first-party provider package through the extension kernel:
75
+
76
+ ```ts
77
+ import { createExtensionKernel, createEnvCredentialResolver } from "@arnilo/prism";
78
+ import { createOpenAIProviderPackage } from "@arnilo/prism-provider-openai";
79
+
80
+ const kernel = createExtensionKernel();
81
+ await kernel.load([
82
+ createOpenAIProviderPackage({
83
+ apiKey: createEnvCredentialResolver({ OPENAI_API_KEY: "fake" }, { openai: "OPENAI_API_KEY" }),
84
+ }),
85
+ ]);
86
+ ```
87
+
88
+ Hosts own credentials. Do not put secrets in prompts, messages, events, stores,
89
+ or logs. Prism never reads `process.env` on its own; credential resolvers are
90
+ caller-supplied.
91
+
92
+ ## CLI
93
+
94
+ ```bash
95
+ prism --provider mock --model demo -p "Hi" # print mode (default)
96
+ prism --provider mock --mode json -p "Hi" # one event envelope per line
97
+ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
98
+ | prism --provider mock --mode rpc # LF-delimited JSONL RPC
99
+ ```
100
+
101
+ ## Docs
102
+
103
+ - [docs/index.md](docs/index.md) — navigational map of every public surface.
104
+ - The `examples/` directory holds compile-checked typed examples and runnable
105
+ mock demos (provider registration, auth, tools, stores/branching, compaction,
106
+ observational-memory recall, CLI, RPC).
107
+
108
+ ## Packages
109
+
110
+ | package | purpose |
111
+ |---------|---------|
112
+ | `@arnilo/prism` | core contracts, runtime, registries, CLI/RPC |
113
+ | `@arnilo/prism-provider-openai` | OpenAI Responses + Codex OAuth provider |
114
+ | `@arnilo/prism-provider-opencode-go` | OpenCode Go provider |
115
+ | `@arnilo/prism-provider-openrouter` | OpenRouter provider with per-model cache control |
116
+ | `@arnilo/prism-provider-zai` | ZAI GLM provider |
117
+ | `@arnilo/prism-provider-kimi` | Kimi For Coding provider |
118
+ | `@arnilo/prism-compaction-llm` | provider-backed compaction strategy |
119
+ | `@arnilo/prism-compaction-observational-memory` | source-backed memory + recall tool |
120
+ | `@arnilo/prism-providers` | umbrella: all 5 provider adapters |
121
+ | `@arnilo/prism-compaction` | umbrella: both compaction strategies |
122
+ | `@arnilo/prism-all` | umbrella: core + providers + compaction |
123
+
124
+ ## Scripts
125
+
126
+ | command | action |
127
+ |---------|--------|
128
+ | `npm run build` | Compile TypeScript to `dist/` (core + workspaces) |
129
+ | `npm run typecheck` | Type-check without emitting |
130
+ | `npm test` | Build + run network-free tests |
131
+ | `prism --help` | CLI help |
132
+
133
+ ## Non-goals (v1)
134
+
135
+ - Built-in shell/filesystem/browser tools — ship as separate packages only.
136
+ - MCP bridge — external extension package after extension APIs settle.
137
+ - TUI or interactive terminal — CLI/RPC only in core.
138
+ - Workflow/graph orchestration — bounded agent loops first.
139
+ - Encrypted/keychain credential storage — host-owned until a real app needs it.
@@ -0,0 +1,5 @@
1
+ import type { Agent, AgentConfig, AgentSession, AgentSessionConfig } from "./contracts.js";
2
+ export declare function createAgent(config: AgentConfig): Agent;
3
+ export declare function createAgentSession(config: AgentSessionConfig & {
4
+ readonly agent: Agent;
5
+ }): AgentSession;
package/dist/agents.js ADDED
@@ -0,0 +1,439 @@
1
+ import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
2
+ import { assembleProviderInput } from "./input.js";
3
+ import { createProviderRequestPolicyChain, mergeProviderRequestOptions, normalizeProviderRequestPolicyResult } from "./provider-request-policy.js";
4
+ import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactSecrets, redactSessionEntry } from "./redaction.js";
5
+ import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
6
+ import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
7
+ import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext } from "./session-stores.js";
8
+ import { createToolRegistry, dispatchToolCall } from "./tools.js";
9
+ export function createAgent(config) {
10
+ return {
11
+ config,
12
+ createSession(sessionConfig = {}) {
13
+ return createAgentSession({ ...sessionConfig, agent: this });
14
+ },
15
+ };
16
+ }
17
+ export function createAgentSession(config) {
18
+ return new RuntimeAgentSession(config);
19
+ }
20
+ class RuntimeAgentSession {
21
+ id;
22
+ agent;
23
+ metadata;
24
+ store;
25
+ subscribers = new Set();
26
+ currentLeafId;
27
+ history = [];
28
+ activeRun;
29
+ activeRedactor;
30
+ constructor(config) {
31
+ this.id = config.id ?? randomId("session");
32
+ this.agent = config.agent;
33
+ this.metadata = config.metadata;
34
+ this.store = config.store ?? config.agent.config.store ?? createMemorySessionStore();
35
+ this.currentLeafId = config.leafId;
36
+ }
37
+ subscribe() {
38
+ const subscriber = new EventSubscriber(() => this.subscribers.delete(subscriber));
39
+ this.subscribers.add(subscriber);
40
+ return subscriber;
41
+ }
42
+ async run(input, options = {}) {
43
+ const runId = randomId("run");
44
+ if (this.activeRun) {
45
+ const error = new Error("Agent session already has an active run");
46
+ this.emit({ type: "error", sessionId: this.id, runId, error: errorToErrorInfo(error) });
47
+ throw error;
48
+ }
49
+ let usage;
50
+ const controller = new AbortController();
51
+ const cleanupSignal = bridgeAbort(options.signal, controller);
52
+ this.activeRun = controller;
53
+ this.activeRedactor = options.redactor ?? this.agent.config.redactor;
54
+ try {
55
+ this.requireProvider();
56
+ throwIfAborted(controller.signal);
57
+ this.emit({ type: "agent_started", sessionId: this.id, runId });
58
+ await this.rebuildHistory();
59
+ if (options.model && JSON.stringify(options.model) !== JSON.stringify(this.agent.config.model)) {
60
+ await this.appendEntry(createSessionEntry({ sessionId: this.id, parentId: this.currentLeafId, runId, kind: "model_change", previousModel: this.agent.config.model, model: options.model }));
61
+ }
62
+ const inputMessages = inputToMessages(input).map((message) => this.redact(message));
63
+ for (const message of inputMessages)
64
+ await this.appendMessage(message, runId);
65
+ await this.autoCompact(runId, options, controller.signal, inputMessages);
66
+ const metadata = { ...this.agent.config.metadata, ...this.metadata, ...options.metadata };
67
+ const { registry, tools } = activeTools(this.agent.config.tools);
68
+ const maxToolRounds = options.maxToolRounds ?? 1;
69
+ const toolResults = [];
70
+ let toolRounds = 0;
71
+ let nextInput = input;
72
+ for (let turn = 1;; turn += 1) {
73
+ throwIfAborted(controller.signal);
74
+ this.emit({ type: "turn_started", sessionId: this.id, runId, turn });
75
+ const request = await assembleProviderInput({
76
+ model: options.model ?? this.agent.config.model,
77
+ input: nextInput,
78
+ history: this.history,
79
+ summaries: (await this.snapshot()).summaries,
80
+ toolResults,
81
+ systemInstructions: composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), { base: this.agent.config.instructions }),
82
+ inputBuilder: this.agent.config.inputBuilder,
83
+ promptBuilder: this.agent.config.promptBuilder,
84
+ contextProviders: this.agent.config.context,
85
+ skills: this.agent.config.skills ? ("list" in this.agent.config.skills ? this.agent.config.skills.list() : this.agent.config.skills) : undefined,
86
+ tools,
87
+ resourceLoader: this.agent.config.resourceLoader,
88
+ providerOptions: mergeProviderRequestOptions(this.agent.config.providerOptions, options.providerOptions),
89
+ middleware: this.agent.config.middleware,
90
+ sessionId: this.id,
91
+ runId,
92
+ metadata,
93
+ signal: controller.signal,
94
+ });
95
+ throwIfAborted(controller.signal);
96
+ const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
97
+ const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
98
+ const { content, calls, messageId, started, usage: turnUsage } = await this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets);
99
+ usage = turnUsage ?? usage;
100
+ if (turn === 1)
101
+ this.history.push(...inputMessages);
102
+ if (started) {
103
+ const message = { id: messageId, role: "assistant", content };
104
+ this.history.push(message);
105
+ await this.appendMessage(message, runId);
106
+ this.emit({ type: "message_finished", sessionId: this.id, runId, message });
107
+ }
108
+ this.emit({ type: "turn_finished", sessionId: this.id, runId, turn });
109
+ if (calls.length === 0 || toolRounds >= maxToolRounds)
110
+ break;
111
+ toolRounds += 1;
112
+ for (const call of calls) {
113
+ const result = await dispatchToolCall({
114
+ call,
115
+ registry,
116
+ context: { sessionId: this.id, runId, toolCallId: call.id, signal: controller.signal, metadata },
117
+ middleware: this.agent.config.middleware,
118
+ emit: (event) => this.emit(event),
119
+ permission: this.agent.config.permission,
120
+ redactor: this.activeRedactor,
121
+ });
122
+ toolResults.push(result);
123
+ await this.appendMessage(toolResultMessage(result), runId);
124
+ throwIfAborted(controller.signal);
125
+ }
126
+ nextInput = [];
127
+ }
128
+ this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
129
+ }
130
+ catch (error) {
131
+ const info = errorToErrorInfo(error);
132
+ this.emit({ type: "error", sessionId: this.id, runId, error: info });
133
+ throw error;
134
+ }
135
+ finally {
136
+ if (this.activeRun === controller)
137
+ this.activeRun = undefined;
138
+ this.activeRedactor = undefined;
139
+ cleanupSignal();
140
+ this.closeSubscribers();
141
+ }
142
+ }
143
+ prompt(input, options) {
144
+ return this.run(input, options);
145
+ }
146
+ async compact(options = {}) {
147
+ if (this.activeRun)
148
+ throw new Error("Agent session already has an active run");
149
+ return this.compactBranch(options, undefined, options.signal, "manual");
150
+ }
151
+ abort(reason) {
152
+ this.activeRun?.abort(reason);
153
+ }
154
+ async entries() {
155
+ return getSessionBranchEntries(await this.store.list(this.id), { leafId: this.currentLeafId });
156
+ }
157
+ async checkout(leafId) {
158
+ this.currentLeafId = leafId;
159
+ await this.rebuildHistory();
160
+ }
161
+ fork(options = {}) {
162
+ return createAgentSession({ agent: this.agent, id: this.id, store: this.store, leafId: options.leafId ?? this.currentLeafId, metadata: this.metadata });
163
+ }
164
+ async clone(options = {}) {
165
+ const id = options.id ?? randomId("session");
166
+ const branch = getSessionBranchEntries(await this.store.list(this.id), { leafId: options.leafId ?? this.currentLeafId });
167
+ const remap = new Map();
168
+ for (const entry of branch) {
169
+ const nextId = randomId("entry");
170
+ remap.set(entry.id, nextId);
171
+ const { id: _oldId, parentId: _oldParentId, sessionId: _oldSessionId, ...rest } = entry;
172
+ await this.store.append({ ...rest, id: nextId, parentId: entry.parentId ? remap.get(entry.parentId) : undefined, sessionId: id });
173
+ }
174
+ return createAgentSession({ agent: this.agent, id, store: this.store, leafId: branch.length ? remap.get(branch[branch.length - 1].id) : undefined, metadata: this.metadata });
175
+ }
176
+ requireProvider() {
177
+ if (!this.agent.config.provider)
178
+ throw new Error(`Unknown provider: ${this.agent.config.model.provider}`);
179
+ }
180
+ emit(event) {
181
+ const redacted = redactAgentEvent(event, this.activeRedactor);
182
+ for (const subscriber of this.subscribers)
183
+ subscriber.push(redacted);
184
+ }
185
+ closeSubscribers() {
186
+ for (const subscriber of this.subscribers)
187
+ subscriber.close();
188
+ this.subscribers.clear();
189
+ }
190
+ async generateWithRetry(request, runId, options, signal, requestSecrets = []) {
191
+ const retry = mergeRetry(this.agent.config.retry, options.retry);
192
+ const secrets = [...requestSecrets, ...(retry?.secrets ?? [])];
193
+ const policy = retry?.policy ?? (retry ? createDefaultRetryPolicy(retry) : undefined);
194
+ for (let attempt = 1;; attempt += 1) {
195
+ try {
196
+ return await this.generateProviderTurn(request, runId, signal, secrets);
197
+ }
198
+ catch (error) {
199
+ const failure = error instanceof ProviderTurnFailure ? error : undefined;
200
+ const info = failure ? redactSecrets(failure.info, secrets) : errorToErrorInfo(error, secrets);
201
+ if (!policy || failure?.observable)
202
+ throw errorFromInfo(info);
203
+ const context = { sessionId: this.id, runId, attempt, error: info, metadata: retry?.metadata, signal };
204
+ let decision = await policy.decide(context);
205
+ const payload = await this.agent.config.middleware?.run("retry", { context, decision }) ?? { context, decision };
206
+ decision = payload.decision;
207
+ if (!decision.retry)
208
+ throw errorFromInfo(info);
209
+ const delayMs = decision.delayMs ?? 0;
210
+ this.emit({ type: "retry_scheduled", sessionId: this.id, runId, attempt, delayMs, error: info });
211
+ await waitForRetry(decision, signal);
212
+ }
213
+ }
214
+ }
215
+ async generateProviderTurn(request, runId, signal, secrets = []) {
216
+ const content = [];
217
+ const calls = [];
218
+ let messageId;
219
+ let started = false;
220
+ let usage;
221
+ try {
222
+ for await (const event of this.agent.config.provider.generate(request)) {
223
+ throwIfAborted(signal);
224
+ if (event.type === "error")
225
+ throw new ProviderTurnFailure(event.error, started);
226
+ if (event.type === "usage")
227
+ usage = event.usage;
228
+ if (event.type === "done") {
229
+ usage = event.usage ?? usage;
230
+ break;
231
+ }
232
+ if (event.type === "message_start") {
233
+ started = true;
234
+ messageId = event.messageId;
235
+ this.emit({ type: "message_started", sessionId: this.id, runId, message: { id: messageId, role: "assistant", content: [] } });
236
+ continue;
237
+ }
238
+ if (event.type === "content_delta" || event.type === "tool_call") {
239
+ if (!started) {
240
+ started = true;
241
+ this.emit({ type: "message_started", sessionId: this.id, runId, message: { role: "assistant", content: [] } });
242
+ }
243
+ const block = providerContent(event);
244
+ content.push(block);
245
+ if (block.type === "tool_call")
246
+ calls.push(block);
247
+ this.emit({ type: "message_delta", sessionId: this.id, runId, content: block });
248
+ }
249
+ }
250
+ return { content, calls, messageId, started, usage };
251
+ }
252
+ catch (error) {
253
+ if (error instanceof ProviderTurnFailure)
254
+ throw error;
255
+ throw new ProviderTurnFailure(errorToErrorInfo(error, secrets), started);
256
+ }
257
+ }
258
+ async applyProviderRequestPolicies(request, runId, options, metadata, signal) {
259
+ const policies = [...policyList(this.agent.config.providerRequestPolicies), ...policyList(options.providerRequestPolicies)];
260
+ if (policies.length === 0)
261
+ return { request, secrets: [] };
262
+ const result = await createProviderRequestPolicyChain(policies).apply({ request, sessionId: this.id, runId, metadata, signal });
263
+ return normalizeProviderRequestPolicyResult(result);
264
+ }
265
+ async appendMessage(message, runId) {
266
+ await this.appendEntry(createSessionEntry({ sessionId: this.id, parentId: this.currentLeafId, runId, kind: "message", message }));
267
+ }
268
+ async autoCompact(runId, options, signal, inputMessages) {
269
+ const compaction = mergeCompaction(this.agent.config.compaction, options.compaction);
270
+ if (!compaction || compaction.thresholdEntries === undefined)
271
+ return;
272
+ const snapshot = await this.snapshot();
273
+ if (snapshot.entries.length <= compaction.thresholdEntries || snapshot.entries.at(-1)?.kind === "compaction")
274
+ return;
275
+ await this.compactBranch(compaction, runId, signal, "auto");
276
+ const compacted = await this.snapshot();
277
+ this.history = withoutTrailingInput(compacted.messages, inputMessages);
278
+ }
279
+ async compactBranch(options, runId, signal, trigger) {
280
+ throwIfAbortedSignal(signal);
281
+ const entries = await this.entries();
282
+ const secrets = options.secrets ?? [];
283
+ const strategy = options.strategy ?? createDefaultCompactionStrategy({ keepRecentEntries: options.keepRecentEntries, maxSummaryChars: options.maxSummaryChars, secrets });
284
+ const context = { sessionId: this.id, entries, keepRecentEntries: options.keepRecentEntries, trigger, secrets, metadata: options.metadata, signal };
285
+ this.emit({ type: "compaction_started", sessionId: this.id, runId });
286
+ let result = await strategy.compact(context);
287
+ result = { ...result, summary: redactSecrets(result.summary, secrets) };
288
+ const payload = await this.agent.config.middleware?.run("compaction", { context, result }) ?? { context, result };
289
+ result = { ...payload.result, summary: redactSecrets(payload.result.summary, secrets) };
290
+ const source = result.entries?.find((entry) => entry.kind === "compaction");
291
+ const data = isCompactionEntryData(source?.data) ? source.data : undefined;
292
+ const entry = createSessionEntry({ sessionId: this.id, parentId: this.currentLeafId, runId, kind: "compaction", summary: result.summary, data });
293
+ await this.appendEntry(entry);
294
+ const finalResult = { ...result, entries: [entry] };
295
+ this.emit({ type: "compaction_finished", sessionId: this.id, runId, summary: finalResult.summary });
296
+ await this.rebuildHistory();
297
+ return finalResult;
298
+ }
299
+ async appendEntry(entry) {
300
+ const redacted = redactSessionEntry(entry, this.activeRedactor);
301
+ await this.store.append(redacted);
302
+ this.currentLeafId = redacted.id;
303
+ }
304
+ redact(value) {
305
+ return this.activeRedactor?.redact(value) ?? value;
306
+ }
307
+ redactProviderRequest(request) {
308
+ return redactProviderRequest(request, this.activeRedactor);
309
+ }
310
+ async rebuildHistory() {
311
+ this.history = (await this.snapshot()).messages.slice();
312
+ }
313
+ async snapshot() {
314
+ return rebuildSessionContext(await this.store.list(this.id), { leafId: this.currentLeafId });
315
+ }
316
+ }
317
+ class EventSubscriber {
318
+ onClose;
319
+ queue = [];
320
+ waiters = [];
321
+ closed = false;
322
+ constructor(onClose) {
323
+ this.onClose = onClose;
324
+ }
325
+ [Symbol.asyncIterator]() {
326
+ return this;
327
+ }
328
+ next() {
329
+ const event = this.queue.shift();
330
+ if (event)
331
+ return Promise.resolve({ value: event, done: false });
332
+ if (this.closed)
333
+ return Promise.resolve({ value: undefined, done: true });
334
+ return new Promise((resolve) => this.waiters.push(resolve));
335
+ }
336
+ return() {
337
+ this.close();
338
+ this.onClose();
339
+ return Promise.resolve({ value: undefined, done: true });
340
+ }
341
+ push(event) {
342
+ const waiter = this.waiters.shift();
343
+ if (waiter)
344
+ waiter({ value: event, done: false });
345
+ else if (!this.closed)
346
+ this.queue.push(event);
347
+ }
348
+ close() {
349
+ this.closed = true;
350
+ for (const waiter of this.waiters.splice(0))
351
+ waiter({ value: undefined, done: true });
352
+ }
353
+ }
354
+ function providerContent(event) {
355
+ return event.type === "content_delta" ? event.content : event.call;
356
+ }
357
+ function inputToMessages(input) {
358
+ if (typeof input === "string")
359
+ return [{ role: "user", content: [{ type: "text", text: input }] }];
360
+ if ("role" in input)
361
+ return [input];
362
+ return [...input];
363
+ }
364
+ function toolResultMessage(result) {
365
+ return {
366
+ role: "tool",
367
+ content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
368
+ metadata: result.metadata,
369
+ };
370
+ }
371
+ function activeTools(tools) {
372
+ if (!tools)
373
+ return { registry: createToolRegistry(), tools: [] };
374
+ if ("list" in tools)
375
+ return { registry: tools, tools: tools.list() };
376
+ const registry = createToolRegistry(tools);
377
+ return { registry, tools };
378
+ }
379
+ function policyList(policies) {
380
+ if (!policies)
381
+ return [];
382
+ return "apply" in policies ? [policies] : policies;
383
+ }
384
+ function errorFromInfo(error) {
385
+ return Object.assign(new Error(error.message), { name: error.name ?? "Error", cause: error.cause });
386
+ }
387
+ class ProviderTurnFailure extends Error {
388
+ info;
389
+ observable;
390
+ constructor(info, observable) {
391
+ super(info.message);
392
+ this.info = info;
393
+ this.observable = observable;
394
+ }
395
+ }
396
+ function mergeRetry(agent, run) {
397
+ if (run === false)
398
+ return undefined;
399
+ if (run)
400
+ return { ...(agent || {}), ...run };
401
+ return agent || undefined;
402
+ }
403
+ function mergeCompaction(agent, run) {
404
+ if (run === false)
405
+ return undefined;
406
+ if (run)
407
+ return { ...(agent || {}), ...run };
408
+ return agent || undefined;
409
+ }
410
+ function withoutTrailingInput(messages, input) {
411
+ const next = [...messages];
412
+ for (let i = input.length - 1; i >= 0; i -= 1) {
413
+ const last = next.at(-1);
414
+ if (last && JSON.stringify(last) === JSON.stringify(input[i]))
415
+ next.pop();
416
+ }
417
+ return next;
418
+ }
419
+ function bridgeAbort(signal, controller) {
420
+ if (!signal)
421
+ return () => undefined;
422
+ const abort = () => controller.abort(signal.reason);
423
+ if (signal.aborted)
424
+ abort();
425
+ else
426
+ signal.addEventListener("abort", abort, { once: true });
427
+ return () => signal.removeEventListener("abort", abort);
428
+ }
429
+ function throwIfAborted(signal) {
430
+ throwIfAbortedSignal(signal);
431
+ }
432
+ function throwIfAbortedSignal(signal) {
433
+ if (signal?.aborted)
434
+ throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
435
+ }
436
+ function randomId(prefix) {
437
+ return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
438
+ }
439
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1,33 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ import type { AgentSession } from "./contracts.js";
3
+ import { type RpcSessionFactory } from "./rpc.js";
4
+ export type CliMode = "print" | "json" | "rpc";
5
+ export interface CliOptions {
6
+ readonly mode: CliMode;
7
+ readonly prompt?: string;
8
+ readonly provider?: string;
9
+ readonly model?: string;
10
+ readonly session?: string;
11
+ readonly config: readonly string[];
12
+ readonly resources: readonly string[];
13
+ readonly extensions: readonly string[];
14
+ readonly tools: readonly string[];
15
+ readonly system?: string;
16
+ readonly context: readonly string[];
17
+ readonly compact?: number;
18
+ readonly maxToolRounds?: number;
19
+ readonly help: boolean;
20
+ }
21
+ export interface CliRuntime {
22
+ readonly stdin: Readable;
23
+ readonly stdout: Writable;
24
+ readonly stderr: Writable;
25
+ readonly createSession?: (options: CliOptions) => AgentSession;
26
+ readonly commands?: RpcSessionFactory["commands"];
27
+ }
28
+ export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --config <path> Explicit config path (recorded, not auto-loaded)\n --resource <uri> Explicit resource URI (recorded, not auto-loaded)\n --extension <name> Explicit extension name (recorded, not auto-loaded)\n --tool <name> Explicit tool name (recorded, not auto-enabled)\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n -h, --help Show this help\n";
29
+ export declare function parseCliArgs(argv: readonly string[]): CliOptions;
30
+ export declare function runCli(argv: readonly string[], runtime: CliRuntime): Promise<number>;
31
+ export declare function runPromptMode(session: AgentSession, options: CliOptions, stdout: Writable, mode: "print" | "json"): Promise<void>;
32
+ export declare class CliUsageError extends Error {
33
+ }