@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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/agents.d.ts +5 -0
- package/dist/agents.js +439 -0
- package/dist/cli-runner.d.ts +33 -0
- package/dist/cli-runner.js +167 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +10 -0
- package/dist/compaction.d.ts +9 -0
- package/dist/compaction.js +67 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +69 -0
- package/dist/contracts.d.ts +670 -0
- package/dist/contracts.js +2 -0
- package/dist/contributions.d.ts +35 -0
- package/dist/contributions.js +47 -0
- package/dist/credentials.d.ts +22 -0
- package/dist/credentials.js +63 -0
- package/dist/extensions.d.ts +25 -0
- package/dist/extensions.js +131 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +28 -0
- package/dist/input.d.ts +57 -0
- package/dist/input.js +225 -0
- package/dist/manifests.d.ts +28 -0
- package/dist/manifests.js +108 -0
- package/dist/middleware.d.ts +15 -0
- package/dist/middleware.js +49 -0
- package/dist/mock-provider.d.ts +6 -0
- package/dist/mock-provider.js +14 -0
- package/dist/models.d.ts +8 -0
- package/dist/models.js +25 -0
- package/dist/node/config.d.ts +9 -0
- package/dist/node/config.js +51 -0
- package/dist/node/session-store-jsonl.d.ts +17 -0
- package/dist/node/session-store-jsonl.js +134 -0
- package/dist/node/settings.d.ts +7 -0
- package/dist/node/settings.js +26 -0
- package/dist/node/trust.d.ts +13 -0
- package/dist/node/trust.js +56 -0
- package/dist/provider-events.d.ts +15 -0
- package/dist/provider-events.js +30 -0
- package/dist/provider-packages.d.ts +4 -0
- package/dist/provider-packages.js +12 -0
- package/dist/provider-request-policy.d.ts +9 -0
- package/dist/provider-request-policy.js +49 -0
- package/dist/providers/openai-compatible.d.ts +9 -0
- package/dist/providers/openai-compatible.js +197 -0
- package/dist/providers.d.ts +8 -0
- package/dist/providers.js +25 -0
- package/dist/redaction.d.ts +11 -0
- package/dist/redaction.js +68 -0
- package/dist/resources.d.ts +5 -0
- package/dist/resources.js +30 -0
- package/dist/retry.d.ts +11 -0
- package/dist/retry.js +48 -0
- package/dist/rpc.d.ts +18 -0
- package/dist/rpc.js +187 -0
- package/dist/security.d.ts +51 -0
- package/dist/security.js +60 -0
- package/dist/session-stores.d.ts +25 -0
- package/dist/session-stores.js +116 -0
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +26 -0
- package/dist/skills.d.ts +8 -0
- package/dist/skills.js +34 -0
- package/dist/system-prompts.d.ts +6 -0
- package/dist/system-prompts.js +47 -0
- package/dist/testing/provider-conformance.d.ts +36 -0
- package/dist/testing/provider-conformance.js +164 -0
- package/dist/tools.d.ts +25 -0
- package/dist/tools.js +109 -0
- package/docs/agent-session-runtime.md +167 -0
- package/docs/api-page-template.md +32 -0
- package/docs/cli-rpc.md +140 -0
- package/docs/compaction-and-retry.md +177 -0
- package/docs/compaction-llm.md +108 -0
- package/docs/compaction-observational-memory.md +123 -0
- package/docs/configuration-and-manifests.md +142 -0
- package/docs/context-and-skills.md +113 -0
- package/docs/contribution-registries.md +118 -0
- package/docs/credentials-and-redaction.md +122 -0
- package/docs/extensions.md +139 -0
- package/docs/index.md +56 -0
- package/docs/input-and-prompt-assembly.md +168 -0
- package/docs/middleware-hooks.md +123 -0
- package/docs/node-filesystem-config.md +85 -0
- package/docs/node-jsonl-session-store.md +81 -0
- package/docs/provider-conformance.md +109 -0
- package/docs/provider-layer.md +172 -0
- package/docs/provider-packages.md +171 -0
- package/docs/providers/kimi.md +110 -0
- package/docs/providers/openai-compatible.md +125 -0
- package/docs/providers/openai.md +131 -0
- package/docs/providers/opencode-go.md +103 -0
- package/docs/providers/openrouter.md +105 -0
- package/docs/providers/zai.md +108 -0
- package/docs/public-contracts.md +375 -0
- package/docs/release-and-install.md +141 -0
- package/docs/resource-loading.md +97 -0
- package/docs/session-stores-and-branching.md +119 -0
- package/docs/settings-auth-trust-security.md +73 -0
- package/docs/system-prompts.md +116 -0
- package/docs/tools.md +151 -0
- 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.
|
package/dist/agents.d.ts
ADDED
|
@@ -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
|
+
}
|