@arnilo/prism 0.0.4 → 0.0.6
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 +46 -1
- package/README.md +34 -10
- package/dist/agent-loops.d.ts +1 -0
- package/dist/agent-loops.js +26 -16
- package/dist/agents.js +147 -21
- 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/content.d.ts +19 -0
- package/dist/content.js +197 -69
- package/dist/contracts.d.ts +96 -9
- package/dist/contracts.js +8 -0
- package/dist/feedback.d.ts +48 -0
- package/dist/feedback.js +230 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +6 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +6 -3
- package/dist/providers/media.d.ts +3 -1
- package/dist/providers/media.js +11 -1
- package/dist/session-stores.js +2 -3
- package/dist/testing/feedback.d.ts +6 -0
- package/dist/testing/feedback.js +37 -0
- package/dist/testing/persistence-schema.d.ts +48 -10
- package/dist/testing/persistence-schema.js +166 -22
- package/dist/testing/run-ledger-conformance.js +7 -1
- package/dist/thinking.d.ts +42 -0
- package/dist/thinking.js +92 -0
- package/dist/tools.js +2 -3
- package/dist/use-case-model.d.ts +63 -0
- package/dist/use-case-model.js +52 -0
- package/docs/a2a.md +75 -0
- package/docs/agent-events.md +14 -21
- package/docs/agent-loops.md +12 -9
- package/docs/agent-session-runtime.md +14 -16
- package/docs/cli-rpc.md +35 -7
- package/docs/coding-agent-tools.md +35 -14
- package/docs/coding-security.md +7 -3
- package/docs/compaction-llm.md +17 -7
- package/docs/compaction-observational-memory.md +30 -4
- package/docs/context-and-skills.md +1 -0
- package/docs/credential-storage.md +58 -9
- package/docs/credentials-and-redaction.md +3 -3
- package/docs/database-persistence.md +17 -9
- package/docs/evaluations.md +122 -0
- package/docs/extensions.md +2 -2
- package/docs/host-security.md +26 -5
- package/docs/index.md +43 -28
- package/docs/mcp-tools.md +74 -13
- package/docs/migration.md +177 -3
- package/docs/multimodal-content.md +14 -6
- package/docs/node-filesystem-config.md +1 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/observability.md +14 -6
- package/docs/performance.md +209 -0
- package/docs/postgres-persistence.md +8 -6
- package/docs/provider-caching.md +16 -4
- package/docs/provider-conformance.md +40 -1
- package/docs/provider-packages.md +62 -3
- package/docs/providers/ai-sdk.md +149 -0
- package/docs/providers/kimi.md +124 -61
- package/docs/providers/neuralwatt.md +19 -13
- package/docs/providers/openai.md +56 -13
- package/docs/providers/opencode-go.md +118 -30
- package/docs/providers/openrouter.md +105 -35
- package/docs/providers/zai.md +94 -45
- package/docs/public-contracts.md +6 -5
- package/docs/rag.md +113 -0
- package/docs/release-and-install.md +100 -79
- package/docs/review-coverage-2026-07-15.md +193 -0
- package/docs/review-coverage-2026-07-17-provider-validation.md +192 -0
- package/docs/runs-and-usage.md +42 -5
- package/docs/server.md +139 -0
- package/docs/settings-auth-trust-security.md +5 -5
- package/docs/sqlite-persistence.md +6 -5
- package/docs/structured-output.md +1 -1
- package/docs/supervisors.md +71 -0
- package/docs/thinking-and-reasoning.md +98 -0
- package/docs/tool-execution-primitives.md +3 -3
- package/docs/tools.md +15 -0
- package/docs/use-case-model-selection.md +109 -0
- package/docs/workflow-orchestration-primitives.md +20 -3
- package/docs/workflows.md +114 -33
- package/docs/working-and-semantic-memory.md +170 -0
- package/package.json +13 -3
- 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
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,52 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.0.6] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Caller-gated model discovery: `listOpenAIModels`, `listKimiModels`, `listZaiModels`, `listOpenRouterModels`, and `listOpenCodeGoModels`. Provider setup remains network-free; hosts explicitly fetch and register current models.
|
|
13
|
+
- Shared `ThinkingLevel` helpers and use-case model bindings. Background compaction and observational-memory jobs can use an explicit provider/model or a supplied session-model fallback.
|
|
14
|
+
- Opt-in sequential artifact-loop tools: `loop: { strategy: "generate-validate-revise", toolCalls: "bounded" }`. Tool rounds use existing authorization/redaction/ledger paths, share `maxToolRounds` across candidates, and fail with `artifact_failed` metadata `{ reason: "tool_round_limit" }` after exhaustion.
|
|
15
|
+
- Checksummed SQLite/PostgreSQL migration histories and catalog-shape verification, bounded JSON Schema compilation LRU, and public `assertFiniteVector` validation.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Provider packages now document and implement current cache, reasoning, streaming, and discovery behavior. OpenAI Responses replay/function-call/SSE argument handling is corrected; Kimi adds optional Moonshot support; Z.AI and OpenCode Go catalogs/routes were refreshed; OpenRouter discovery/reasoning and NeuralWatt thinking controls are hardened. AI SDK remains host-model-owned.
|
|
20
|
+
- Workflow definitions now require a non-empty `revision`; cancellation requires exact ownership and the current workflow definition. All workflow limits have finite hard caps.
|
|
21
|
+
- Coding tools now enforce bounded streamed reads, write/edit inputs, shell wall time, total output, and spill-file lifecycle. Custom coding operation interfaces now receive bounded read/stat/write/edit options and abort signals.
|
|
22
|
+
- Encrypted credential helpers `encryptBytes`, `decryptBytes`, and envelope rotation are asynchronous. Existing credential files must meet restrictive Unix permission requirements. Linux Secret Service/GNOME Keyring byte-array reads are accepted by the keychain store.
|
|
23
|
+
- MCP Streamable HTTP requires HTTPS and explicit `allowedOrigins`; loopback HTTP requires explicit opt-in. Discovery, schemas, results, and response bodies are bounded.
|
|
24
|
+
- Compaction and observational-memory workers now have finite turn/call/transcript/error budgets. A2A streaming uses strict incremental UTF-8 and LF/CRLF SSE parsing.
|
|
25
|
+
- Generated Prism, workflow, and evaluation IDs use cryptographic UUIDs; non-finite embedding vectors now fail before scoring or persistence.
|
|
26
|
+
|
|
27
|
+
### Security
|
|
28
|
+
|
|
29
|
+
- Fixed cross-owner workflow cancellation and duplicate active-run overwrite risks.
|
|
30
|
+
- Added fail-closed limits and validation at file, process, credential, MCP, migration, schema, vector, provider-worker, and A2A trust boundaries.
|
|
31
|
+
|
|
32
|
+
### Upgrade notes
|
|
33
|
+
|
|
34
|
+
- Finish or deliberately migrate pre-0.0.6 workflow runs/checkpoints before upgrading: their definition hashes lack the required revision.
|
|
35
|
+
- Update workflow definitions with `revision`, cancellation callers with `workflow` plus exact ownership, MCP HTTP configs with `allowedOrigins`, and custom coding/credential integrations for the changed interfaces above.
|
|
36
|
+
|
|
37
|
+
## [0.0.5] - 2026-07-16
|
|
38
|
+
|
|
39
|
+
- `@arnilo/prism-providers` now installs all seven first-party adapters including AI SDK interoperability; `@arnilo/prism-all` now installs every first-party package while activating none automatically.
|
|
40
|
+
|
|
41
|
+
- Added optional `@arnilo/prism-supervisor` with bounded explicit child delegation, derived memory scope IDs, narrowing-only permissions, A2A 1.0 cards/ES256 signatures, authorized JSON-RPC/SSE serving, and an exact-origin remote client.
|
|
42
|
+
|
|
43
|
+
- Added bounded immutable run/trace feedback with exact ownership, evaluation linkage, memory/SQLite/PostgreSQL stores, schema migration 003, and safe OpenTelemetry projection.
|
|
44
|
+
|
|
45
|
+
- Phase 11 extends workflows with explicit durable schedules/background execution, nested composition, bounded validated state, immutable-lineage replay, and optional command/Web bindings over existing checkpoint/lease primitives.
|
|
46
|
+
|
|
47
|
+
- Optional `@arnilo/prism-server` package with authorized bounded Web-standard direct/SSE agent and durable workflow routes; `@arnilo/prism-mcp` now supports explicit authorized Prism tool/command server exposure and bounded Web-standard Streamable HTTP handling.
|
|
48
|
+
- Optional `@arnilo/prism-rag` package: bounded deterministic text/Markdown chunking, Phase 7 vector indexing/retrieval, stable citations, metadata filters, redaction, and explicit ContextProvider injection.
|
|
49
|
+
- Workflows now support durable human `suspend()`/approve/deny, expected-version exact-once resume, validated/redacted resume payloads, and opt-in tool approval with execution-policy recheck.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- Optional `@arnilo/prism-memory` package: schema/template-backed working memory, semantic recall, package-owned `Embedder`/`VectorStore` contracts, in-memory adapters, context provider, opt-in processor, shared conformance, and PostgreSQL/pgvector production path.
|
|
9
54
|
|
|
10
55
|
## [0.0.4] - 2026-07-14
|
|
11
56
|
|
package/README.md
CHANGED
|
@@ -26,13 +26,14 @@ packages. Prism defines contracts, not apps.
|
|
|
26
26
|
- **Input/prompt/context**: default input and prompt builders, system-prompt
|
|
27
27
|
layering, and provider-input assembly — every stage replaceable.
|
|
28
28
|
- **Sessions and memory**: in-memory and JSONL session stores, branching/fork/
|
|
29
|
-
clone, default and LLM compaction strategies, retry policy,
|
|
30
|
-
observational-memory recall/status/view
|
|
29
|
+
clone, default and LLM compaction strategies, retry policy,
|
|
30
|
+
observational-memory recall/status/view, optional working/semantic memory
|
|
31
|
+
(`@arnilo/prism-memory`), and bounded text/Markdown RAG (`@arnilo/prism-rag`).
|
|
31
32
|
- **Extensions and manifests**: extension kernel + event bus, contribution
|
|
32
33
|
registries, middleware hooks, and data-only package manifests.
|
|
33
34
|
- **Config, settings, security**: layered config merge, settings providers,
|
|
34
35
|
credential resolvers, trust/permission policies, and secret redaction.
|
|
35
|
-
- **CLI/RPC**: `prism --mode print|json|rpc`
|
|
36
|
+
- **CLI/RPC/server**: `prism --mode print|json|rpc`, `prism init`, optional framework-free authorized Web agent/workflow routes, and explicit MCP server exposure.
|
|
36
37
|
|
|
37
38
|
## Install
|
|
38
39
|
|
|
@@ -50,6 +51,8 @@ npm install @arnilo/prism-base # core + compaction
|
|
|
50
51
|
npm install @arnilo/prism-code @arnilo/prism-provider-openai # coding-agent profile
|
|
51
52
|
npm install @arnilo/prism-sdk @arnilo/prism-provider-openai # application profile
|
|
52
53
|
npm install @arnilo/prism-all # every first-party package
|
|
54
|
+
npm install @arnilo/prism-server @arnilo/prism-workflows # optional Web API boundary
|
|
55
|
+
npm install @arnilo/prism-supervisor # optional local delegation + A2A 1.0
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
See [docs/release-and-install.md](docs/release-and-install.md) for install
|
|
@@ -57,6 +60,17 @@ specifiers, tarball contents, and the offline test budget.
|
|
|
57
60
|
|
|
58
61
|
## Quick start
|
|
59
62
|
|
|
63
|
+
Scaffold a tiny project (offline mock test included):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx --package @arnilo/prism prism init my-agent
|
|
67
|
+
# or, with a real provider package selected:
|
|
68
|
+
npx --package @arnilo/prism prism init my-agent --provider openai
|
|
69
|
+
cd my-agent && npm install && npm test
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or embed Prism directly:
|
|
73
|
+
|
|
60
74
|
```ts
|
|
61
75
|
import { createAgent, createAgentSession, createMockProvider } from "@arnilo/prism";
|
|
62
76
|
|
|
@@ -68,9 +82,18 @@ const agent = createAgent({
|
|
|
68
82
|
|
|
69
83
|
const session = createAgentSession({ agent });
|
|
70
84
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
// Direct result: run/prompt return AgentRunResult (text, usage, status, ids).
|
|
86
|
+
const result = await session.run("Hi");
|
|
87
|
+
console.log(result.text, result.usage?.totalTokens);
|
|
88
|
+
|
|
89
|
+
// Integrated streaming: subscribe-before-run for one owned run.
|
|
90
|
+
for await (const event of session.stream("Hi again")) {
|
|
91
|
+
// AgentEvent: agent_started, message_delta, turn_finished, ...
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Long-lived subscribe() still works when you need a subscriber across runs.
|
|
95
|
+
// `subscribe()` only emits while a run is in progress, so the loop and `run()`
|
|
96
|
+
// must run together; awaiting the loop before calling `run()` would deadlock.
|
|
74
97
|
(async () => {
|
|
75
98
|
const consumer = (async () => {
|
|
76
99
|
for await (const event of session.subscribe()) {
|
|
@@ -132,12 +155,13 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
132
155
|
| `@arnilo/prism-coding-security` | coding approval, containment, and sandbox adapters |
|
|
133
156
|
| `@arnilo/prism-tool-validator-json-schema` | bounded JSON Schema tool validation |
|
|
134
157
|
| `@arnilo/prism-mcp` | MCP client/tool bridge |
|
|
135
|
-
| `@arnilo/prism-workflows` | bounded DAG workflows and
|
|
158
|
+
| `@arnilo/prism-workflows` | bounded DAG workflows, durable suspend/resume, schedules/background runs, composition/state/replay, and multi-process coordination |
|
|
159
|
+
| `@arnilo/prism-supervisor` | bounded local child delegation and A2A 1.0 interoperability |
|
|
136
160
|
| `@arnilo/prism-observability-opentelemetry` | optional OpenTelemetry adapter |
|
|
137
161
|
| `@arnilo/prism-credentials-node` | encrypted-file and keychain credentials |
|
|
138
|
-
| `@arnilo/prism-session-store-sqlite` | SQLite persistence/checkpoints/leases |
|
|
139
|
-
| `@arnilo/prism-session-store-postgres` | PostgreSQL persistence/checkpoints/leases |
|
|
140
|
-
| `@arnilo/prism-providers` | family: all
|
|
162
|
+
| `@arnilo/prism-session-store-sqlite` | SQLite persistence/checkpoints/leases/owned run feedback |
|
|
163
|
+
| `@arnilo/prism-session-store-postgres` | PostgreSQL persistence/checkpoints/leases/owned run feedback |
|
|
164
|
+
| `@arnilo/prism-providers` | family: all 7 provider adapters, including AI SDK interoperability |
|
|
141
165
|
| `@arnilo/prism-compaction` | family: both compaction strategies |
|
|
142
166
|
| `@arnilo/prism-base` | profile: core + compaction + JSON Schema validation |
|
|
143
167
|
| `@arnilo/prism-code` | profile: base + coding tools/security + MCP |
|
package/dist/agent-loops.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function generateValidateReviseLoop(opts: {
|
|
|
6
6
|
readonly parser?: ArtifactParser<unknown>;
|
|
7
7
|
readonly repairer?: ArtifactRepairer<unknown>;
|
|
8
8
|
readonly maxRevisions?: number;
|
|
9
|
+
readonly toolCalls?: "disabled" | "bounded";
|
|
9
10
|
}): AgentLoopStrategy;
|
|
10
11
|
export declare function resolveToolConcurrency(options: {
|
|
11
12
|
loop?: AgentLoopStrategy | AgentLoopOptions;
|
package/dist/agent-loops.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { inputMessages } from "./input.js";
|
|
2
|
+
import { createId } from "./ids.js";
|
|
2
3
|
function throwIfAborted(signal) {
|
|
3
4
|
if (signal.aborted)
|
|
4
5
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
@@ -55,10 +56,8 @@ function defaultRepairer() {
|
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
// ponytail: GenerateValidateReviseLoop reuses LoopContext primitives only —
|
|
58
|
-
// no provider/retry/store/event re-implementation.
|
|
59
|
-
//
|
|
60
|
-
// generate→validate→revise; tool coupling deferred). Phase 28 fires
|
|
61
|
-
// artifact_* events at the marked seams (noop here).
|
|
59
|
+
// no provider/retry/store/event re-implementation. Bounded artifact tools use
|
|
60
|
+
// same dispatcher at concurrency one; add parallelism only with ordering need.
|
|
62
61
|
export function generateValidateReviseLoop(opts) {
|
|
63
62
|
const max = opts.maxRevisions ?? 3;
|
|
64
63
|
const repairer = opts.repairer ?? defaultRepairer();
|
|
@@ -68,12 +67,14 @@ export function generateValidateReviseLoop(opts) {
|
|
|
68
67
|
let usage;
|
|
69
68
|
let nextInput = ctx.input;
|
|
70
69
|
let pendingHistory = [];
|
|
71
|
-
|
|
70
|
+
let toolRounds = 0;
|
|
71
|
+
let attempts = 0;
|
|
72
|
+
for (let turn = 1; attempts <= max; turn += 1) {
|
|
72
73
|
throwIfAborted(ctx.signal);
|
|
73
74
|
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
74
75
|
const request = await ctx.assemble(nextInput, undefined, turn);
|
|
75
76
|
throwIfAborted(ctx.signal);
|
|
76
|
-
const { content, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
77
|
+
const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
77
78
|
usage = turnUsage ?? usage;
|
|
78
79
|
if (pendingHistory.length > 0) {
|
|
79
80
|
ctx.history.push(...pendingHistory);
|
|
@@ -88,10 +89,17 @@ export function generateValidateReviseLoop(opts) {
|
|
|
88
89
|
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
89
90
|
}
|
|
90
91
|
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
if (opts.toolCalls === "bounded" && calls.length > 0) {
|
|
93
|
+
if (toolRounds >= ctx.maxToolRounds) {
|
|
94
|
+
const result = { ok: false, errors: [{ message: "maximum tool rounds exceeded" }], metadata: { reason: "tool_round_limit" } };
|
|
95
|
+
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
|
|
96
|
+
return usage;
|
|
97
|
+
}
|
|
98
|
+
toolRounds += 1;
|
|
99
|
+
await dispatchToolCallsInOrder(calls, { ...ctx, toolConcurrency: 1 });
|
|
100
|
+
nextInput = [];
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
95
103
|
const artifactCtx = {
|
|
96
104
|
sessionId: ctx.sessionId,
|
|
97
105
|
runId: ctx.runId,
|
|
@@ -99,13 +107,17 @@ export function generateValidateReviseLoop(opts) {
|
|
|
99
107
|
signal: ctx.signal,
|
|
100
108
|
metadata: ctx.metadata,
|
|
101
109
|
};
|
|
110
|
+
const text = content
|
|
111
|
+
.filter((b) => b.type === "text")
|
|
112
|
+
.map((b) => b.text)
|
|
113
|
+
.join("");
|
|
102
114
|
const parsed = opts.parser
|
|
103
115
|
? await opts.parser(text, artifactCtx)
|
|
104
116
|
: { ok: true, value: text };
|
|
105
117
|
// Parse failure ends the loop silently (terminal parse errors stay on `error`).
|
|
106
118
|
if (!parsed.ok || parsed.value === undefined)
|
|
107
119
|
return usage;
|
|
108
|
-
const attempt =
|
|
120
|
+
const attempt = ++attempts;
|
|
109
121
|
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
110
122
|
const result = await opts.validator(parsed.value, artifactCtx);
|
|
111
123
|
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
@@ -113,7 +125,7 @@ export function generateValidateReviseLoop(opts) {
|
|
|
113
125
|
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
114
126
|
return usage;
|
|
115
127
|
}
|
|
116
|
-
if (
|
|
128
|
+
if (attempt > max) {
|
|
117
129
|
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
118
130
|
return usage;
|
|
119
131
|
}
|
|
@@ -124,7 +136,6 @@ export function generateValidateReviseLoop(opts) {
|
|
|
124
136
|
await ctx.appendMessage(message);
|
|
125
137
|
pendingHistory = repairMessages;
|
|
126
138
|
nextInput = repairMessages;
|
|
127
|
-
continue;
|
|
128
139
|
}
|
|
129
140
|
return usage;
|
|
130
141
|
},
|
|
@@ -195,13 +206,12 @@ export function resolveLoop(options, config) {
|
|
|
195
206
|
parser: loop.parser,
|
|
196
207
|
repairer: loop.repairer,
|
|
197
208
|
maxRevisions: loop.maxRevisions,
|
|
209
|
+
toolCalls: loop.toolCalls,
|
|
198
210
|
});
|
|
199
211
|
}
|
|
200
212
|
throw new Error(`Unknown agent loop strategy: ${strategy}`);
|
|
201
213
|
}
|
|
202
214
|
return loop;
|
|
203
215
|
}
|
|
204
|
-
|
|
205
|
-
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
206
|
-
}
|
|
216
|
+
const randomId = createId;
|
|
207
217
|
//# sourceMappingURL=agent-loops.js.map
|
package/dist/agents.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { AgentRunError } from "./contracts.js";
|
|
1
2
|
import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
|
|
3
|
+
import { createId } from "./ids.js";
|
|
2
4
|
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
3
5
|
import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
|
|
4
6
|
import { assembleProviderInput } from "./input.js";
|
|
@@ -71,6 +73,8 @@ class RuntimeAgentSession {
|
|
|
71
73
|
const model = options.model ?? this.agent.config.model;
|
|
72
74
|
const startedAt = new Date().toISOString();
|
|
73
75
|
let runError;
|
|
76
|
+
const runUsage = createUsageAccumulator();
|
|
77
|
+
let usage;
|
|
74
78
|
try {
|
|
75
79
|
this.resolveRunProvider(options);
|
|
76
80
|
throwIfAborted(controller.signal);
|
|
@@ -114,6 +118,23 @@ class RuntimeAgentSession {
|
|
|
114
118
|
const loop = resolveLoop(options, this.agent.config);
|
|
115
119
|
const toolConcurrency = resolveToolConcurrency(options, this.agent.config);
|
|
116
120
|
this.activeLoopTurn = 1;
|
|
121
|
+
const recordProviderUsage = async (turnUsage, turn, attempt) => {
|
|
122
|
+
runUsage.add(turnUsage);
|
|
123
|
+
if (!this.activeLedger)
|
|
124
|
+
return;
|
|
125
|
+
const usageRecord = {
|
|
126
|
+
id: randomId("usage"),
|
|
127
|
+
sessionId: this.id,
|
|
128
|
+
runId,
|
|
129
|
+
scope: "provider_turn",
|
|
130
|
+
turn,
|
|
131
|
+
attempt,
|
|
132
|
+
usage: turnUsage,
|
|
133
|
+
recordedAt: new Date().toISOString(),
|
|
134
|
+
...this.activeOwnership,
|
|
135
|
+
};
|
|
136
|
+
await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
|
|
137
|
+
};
|
|
117
138
|
// ponytail: LoopContext binds existing private helpers; loop orchestrates only.
|
|
118
139
|
const ctx = {
|
|
119
140
|
sessionId: this.id,
|
|
@@ -152,7 +173,7 @@ class RuntimeAgentSession {
|
|
|
152
173
|
generate: async (request) => {
|
|
153
174
|
const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
|
|
154
175
|
const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
|
|
155
|
-
return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn);
|
|
176
|
+
return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets, this.activeLoopTurn, recordProviderUsage);
|
|
156
177
|
},
|
|
157
178
|
isToolCallExclusive: (call) => registry.get(call.name)?.exclusive === true,
|
|
158
179
|
dispatchToolCall: (call) => dispatchToolCall({
|
|
@@ -175,12 +196,14 @@ class RuntimeAgentSession {
|
|
|
175
196
|
this.emit(event);
|
|
176
197
|
},
|
|
177
198
|
};
|
|
178
|
-
const
|
|
199
|
+
const loopUsage = await loop.run(ctx);
|
|
200
|
+
usage = runUsage.value() ?? loopUsage;
|
|
179
201
|
if (usage && this.activeLedger) {
|
|
180
202
|
const usageRecord = {
|
|
181
203
|
id: randomId("usage"),
|
|
182
204
|
sessionId: this.id,
|
|
183
205
|
runId,
|
|
206
|
+
scope: "run_total",
|
|
184
207
|
usage,
|
|
185
208
|
recordedAt: new Date().toISOString(),
|
|
186
209
|
...this.activeOwnership,
|
|
@@ -189,11 +212,23 @@ class RuntimeAgentSession {
|
|
|
189
212
|
}
|
|
190
213
|
await this.drainLedger();
|
|
191
214
|
this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
|
|
215
|
+
return this.buildRunResult({
|
|
216
|
+
runId,
|
|
217
|
+
status: "succeeded",
|
|
218
|
+
usage,
|
|
219
|
+
});
|
|
192
220
|
}
|
|
193
221
|
catch (error) {
|
|
194
222
|
runError = errorToErrorInfo(error);
|
|
195
223
|
this.emit({ type: "error", sessionId: this.id, runId, error: runError });
|
|
196
|
-
|
|
224
|
+
const result = this.buildRunResult({
|
|
225
|
+
runId,
|
|
226
|
+
status: controller.signal.aborted ? "aborted" : "failed",
|
|
227
|
+
usage: runUsage.value() ?? usage,
|
|
228
|
+
error: runError,
|
|
229
|
+
abortReason: controller.signal.aborted ? String(controller.signal.reason) : undefined,
|
|
230
|
+
});
|
|
231
|
+
throw new AgentRunError(result, { cause: error });
|
|
197
232
|
}
|
|
198
233
|
finally {
|
|
199
234
|
if (this.activeRun === controller)
|
|
@@ -233,6 +268,48 @@ class RuntimeAgentSession {
|
|
|
233
268
|
prompt(input, options) {
|
|
234
269
|
return this.run(input, options);
|
|
235
270
|
}
|
|
271
|
+
async *stream(input, options = {}) {
|
|
272
|
+
const { maxQueuedEvents, overflow, ...runOptions } = options;
|
|
273
|
+
const subscription = this.subscribe({ maxQueuedEvents, overflow });
|
|
274
|
+
let runOwnedId;
|
|
275
|
+
let settled = false;
|
|
276
|
+
const runPromise = this.run(input, runOptions).finally(() => {
|
|
277
|
+
settled = true;
|
|
278
|
+
});
|
|
279
|
+
try {
|
|
280
|
+
for await (const event of subscription) {
|
|
281
|
+
if ("runId" in event && typeof event.runId === "string") {
|
|
282
|
+
if (runOwnedId === undefined && event.type === "agent_started")
|
|
283
|
+
runOwnedId = event.runId;
|
|
284
|
+
if (runOwnedId !== undefined && event.runId !== runOwnedId)
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
yield event;
|
|
288
|
+
}
|
|
289
|
+
await runPromise;
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
if (!settled) {
|
|
293
|
+
this.abort(new Error("stream consumer closed"));
|
|
294
|
+
await runPromise.catch(() => undefined);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
buildRunResult(input) {
|
|
299
|
+
const final = finalAssistantMessage(this.history);
|
|
300
|
+
return {
|
|
301
|
+
sessionId: this.id,
|
|
302
|
+
runId: input.runId,
|
|
303
|
+
status: input.status,
|
|
304
|
+
leafId: this.currentLeafId,
|
|
305
|
+
text: final.text,
|
|
306
|
+
content: final.content,
|
|
307
|
+
message: final.message,
|
|
308
|
+
usage: input.usage,
|
|
309
|
+
error: input.error,
|
|
310
|
+
abortReason: input.abortReason,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
236
313
|
async compact(options = {}) {
|
|
237
314
|
if (this.activeRun)
|
|
238
315
|
throw new Error("Agent session already has an active run");
|
|
@@ -340,13 +417,13 @@ class RuntimeAgentSession {
|
|
|
340
417
|
if (failure)
|
|
341
418
|
throw failure;
|
|
342
419
|
}
|
|
343
|
-
async generateWithRetry(request, runId, options, signal, requestSecrets = [], turn = 1) {
|
|
420
|
+
async generateWithRetry(request, runId, options, signal, requestSecrets = [], turn = 1, recordUsage) {
|
|
344
421
|
const retry = mergeRetry(this.agent.config.retry, options.retry);
|
|
345
422
|
const secrets = [...requestSecrets, ...(retry?.secrets ?? [])];
|
|
346
423
|
const policy = retry?.policy ?? (retry ? createDefaultRetryPolicy(retry) : undefined);
|
|
347
424
|
for (let attempt = 1;; attempt += 1) {
|
|
348
425
|
try {
|
|
349
|
-
return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt);
|
|
426
|
+
return await this.generateProviderTurn(request, runId, signal, secrets, turn, attempt, recordUsage);
|
|
350
427
|
}
|
|
351
428
|
catch (error) {
|
|
352
429
|
const failure = error instanceof ProviderTurnFailure ? error : undefined;
|
|
@@ -365,7 +442,7 @@ class RuntimeAgentSession {
|
|
|
365
442
|
}
|
|
366
443
|
}
|
|
367
444
|
}
|
|
368
|
-
async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1) {
|
|
445
|
+
async generateProviderTurn(request, runId, signal, secrets = [], turn = 1, attempt = 1, recordUsage) {
|
|
369
446
|
const startedAt = performance.now();
|
|
370
447
|
const providerId = this.activeProvider?.id ?? request.model.provider;
|
|
371
448
|
const buildMetadata = (extra = {}) => createProviderTurnMetadata(request, providerId, { attempt, ...extra });
|
|
@@ -382,25 +459,20 @@ class RuntimeAgentSession {
|
|
|
382
459
|
let messageId;
|
|
383
460
|
let started = false;
|
|
384
461
|
let usage;
|
|
462
|
+
let usageRecorded = false;
|
|
463
|
+
const recordTurnUsage = async () => {
|
|
464
|
+
if (!usage || usageRecorded)
|
|
465
|
+
return;
|
|
466
|
+
usageRecorded = true;
|
|
467
|
+
await recordUsage?.(usage, turn, attempt);
|
|
468
|
+
};
|
|
385
469
|
try {
|
|
386
470
|
for await (const event of this.activeProvider.generate(request)) {
|
|
387
471
|
throwIfAborted(signal);
|
|
388
472
|
if (event.type === "error")
|
|
389
473
|
throw new ProviderTurnFailure(event.error, started);
|
|
390
|
-
if (event.type === "usage")
|
|
474
|
+
if (event.type === "usage")
|
|
391
475
|
usage = event.usage;
|
|
392
|
-
if (this.activeLedger) {
|
|
393
|
-
const usageRecord = {
|
|
394
|
-
id: randomId("usage"),
|
|
395
|
-
sessionId: this.id,
|
|
396
|
-
runId,
|
|
397
|
-
usage: event.usage,
|
|
398
|
-
recordedAt: new Date().toISOString(),
|
|
399
|
-
...this.activeOwnership,
|
|
400
|
-
};
|
|
401
|
-
await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
476
|
if (event.type === "done") {
|
|
405
477
|
usage = event.usage ?? usage;
|
|
406
478
|
break;
|
|
@@ -433,6 +505,7 @@ class RuntimeAgentSession {
|
|
|
433
505
|
calls.push(call);
|
|
434
506
|
this.emit({ type: "message_delta", sessionId: this.id, runId, content: call });
|
|
435
507
|
}
|
|
508
|
+
await recordTurnUsage();
|
|
436
509
|
const latencyMs = Math.round(performance.now() - startedAt);
|
|
437
510
|
this.emit({
|
|
438
511
|
type: "provider_turn_finished",
|
|
@@ -447,12 +520,14 @@ class RuntimeAgentSession {
|
|
|
447
520
|
catch (error) {
|
|
448
521
|
const latencyMs = Math.round(performance.now() - startedAt);
|
|
449
522
|
const info = error instanceof ProviderTurnFailure ? redactSecrets(error.info, secrets) : errorToErrorInfo(error, secrets);
|
|
523
|
+
await recordTurnUsage();
|
|
450
524
|
this.emit({
|
|
451
525
|
type: "provider_turn_finished",
|
|
452
526
|
sessionId: this.id,
|
|
453
527
|
runId,
|
|
454
528
|
turn,
|
|
455
529
|
metadata: buildMetadata({ latencyMs, httpStatus: readProviderHttpStatus(info) }),
|
|
530
|
+
usage,
|
|
456
531
|
error: info,
|
|
457
532
|
});
|
|
458
533
|
if (error instanceof ProviderTurnFailure)
|
|
@@ -608,6 +683,19 @@ function inputToMessages(input) {
|
|
|
608
683
|
return [input];
|
|
609
684
|
return [...input];
|
|
610
685
|
}
|
|
686
|
+
function finalAssistantMessage(history) {
|
|
687
|
+
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
688
|
+
const message = history[index];
|
|
689
|
+
if (message.role !== "assistant")
|
|
690
|
+
continue;
|
|
691
|
+
const text = message.content
|
|
692
|
+
.filter((block) => block.type === "text")
|
|
693
|
+
.map((block) => block.text)
|
|
694
|
+
.join("");
|
|
695
|
+
return { message, content: message.content, text };
|
|
696
|
+
}
|
|
697
|
+
return { content: [], text: "" };
|
|
698
|
+
}
|
|
611
699
|
function activeTools(tools) {
|
|
612
700
|
if (!tools)
|
|
613
701
|
return { registry: createToolRegistry(), tools: [] };
|
|
@@ -673,7 +761,45 @@ function throwIfAbortedSignal(signal) {
|
|
|
673
761
|
if (signal?.aborted)
|
|
674
762
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
675
763
|
}
|
|
676
|
-
function
|
|
677
|
-
|
|
764
|
+
function createUsageAccumulator() {
|
|
765
|
+
const sums = new Map();
|
|
766
|
+
let costCurrency;
|
|
767
|
+
let costCompatible = true;
|
|
768
|
+
return {
|
|
769
|
+
add(usage) {
|
|
770
|
+
for (const key of ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"]) {
|
|
771
|
+
const value = usage[key];
|
|
772
|
+
if (value !== undefined)
|
|
773
|
+
sums.set(key, (sums.get(key) ?? 0) + value);
|
|
774
|
+
}
|
|
775
|
+
const total = usage.totalTokens
|
|
776
|
+
?? (usage.inputTokens !== undefined || usage.outputTokens !== undefined
|
|
777
|
+
? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
778
|
+
: undefined);
|
|
779
|
+
if (total !== undefined)
|
|
780
|
+
sums.set("totalTokens", (sums.get("totalTokens") ?? 0) + total);
|
|
781
|
+
if (usage.cost !== undefined && costCompatible) {
|
|
782
|
+
if (!sums.has("cost"))
|
|
783
|
+
costCurrency = usage.currency;
|
|
784
|
+
else if (usage.currency !== costCurrency)
|
|
785
|
+
costCompatible = false;
|
|
786
|
+
if (costCompatible)
|
|
787
|
+
sums.set("cost", (sums.get("cost") ?? 0) + usage.cost);
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
value() {
|
|
791
|
+
if (sums.size === 0)
|
|
792
|
+
return undefined;
|
|
793
|
+
const usage = {};
|
|
794
|
+
for (const [key, value] of sums) {
|
|
795
|
+
if (key !== "cost" || costCompatible)
|
|
796
|
+
usage[key] = value;
|
|
797
|
+
}
|
|
798
|
+
if (costCompatible && sums.has("cost") && costCurrency !== undefined)
|
|
799
|
+
usage.currency = costCurrency;
|
|
800
|
+
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
801
|
+
},
|
|
802
|
+
};
|
|
678
803
|
}
|
|
804
|
+
const randomId = createId;
|
|
679
805
|
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
export declare class InitUsageError extends Error {
|
|
3
|
+
}
|
|
4
|
+
export type InitProvider = string;
|
|
5
|
+
export interface InitOptions {
|
|
6
|
+
readonly directory: string;
|
|
7
|
+
readonly provider: InitProvider;
|
|
8
|
+
readonly withWorkflows: boolean;
|
|
9
|
+
readonly withEvals: boolean;
|
|
10
|
+
readonly force: boolean;
|
|
11
|
+
readonly help: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface InitRuntime {
|
|
14
|
+
readonly stdout: Writable;
|
|
15
|
+
readonly stderr: Writable;
|
|
16
|
+
/** Override template root (tests). Defaults to package `templates/init`. */
|
|
17
|
+
readonly templatesRoot?: string;
|
|
18
|
+
/** Override package version stamped into generated package.json. */
|
|
19
|
+
readonly packageVersion?: string;
|
|
20
|
+
/** Working directory used to resolve relative destinations. Defaults to process.cwd(). */
|
|
21
|
+
readonly cwd?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface InitResult {
|
|
24
|
+
readonly targetDir: string;
|
|
25
|
+
readonly writtenFiles: readonly string[];
|
|
26
|
+
readonly provider: InitProvider;
|
|
27
|
+
readonly withWorkflows: boolean;
|
|
28
|
+
readonly withEvals: boolean;
|
|
29
|
+
readonly totalBytes: number;
|
|
30
|
+
}
|
|
31
|
+
/** Provider ids supported by `prism init --provider`. Loaded from templates data. */
|
|
32
|
+
export declare function listInitProviders(templatesRoot?: string): readonly string[];
|
|
33
|
+
/** @deprecated Prefer listInitProviders(); retained for tests that import the name. */
|
|
34
|
+
export declare const INIT_PROVIDERS: readonly string[];
|
|
35
|
+
export declare function getInitUsage(templatesRoot?: string): string;
|
|
36
|
+
export declare const initUsage: string;
|
|
37
|
+
export declare function parseInitArgs(argv: readonly string[], templatesRoot?: string): InitOptions;
|
|
38
|
+
export declare function runInitCommand(argv: readonly string[], runtime: InitRuntime): Promise<number>;
|
|
39
|
+
export declare function createInitProject(options: InitOptions, runtime?: InitRuntime): Promise<InitResult>;
|
|
40
|
+
export declare function defaultTemplatesRoot(): string;
|
|
41
|
+
export declare function isInitProvider(value: string, templatesRoot?: string): value is InitProvider;
|