@arnilo/prism 0.0.1 → 0.0.3
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 +19 -2
- package/README.md +17 -7
- package/dist/agent-definitions.d.ts +12 -0
- package/dist/agent-definitions.js +131 -0
- package/dist/agent-loops.d.ts +14 -0
- package/dist/agent-loops.js +161 -0
- package/dist/agents.js +263 -76
- package/dist/cache-helpers.d.ts +28 -0
- package/dist/cache-helpers.js +73 -0
- package/dist/cli-runner.d.ts +38 -2
- package/dist/cli-runner.js +167 -5
- package/dist/compaction.js +2 -0
- package/dist/config.js +47 -12
- package/dist/contracts.d.ts +581 -6
- package/dist/contracts.js +41 -1
- package/dist/contribution-parsing.d.ts +19 -0
- package/dist/contribution-parsing.js +124 -0
- package/dist/contributions.d.ts +13 -3
- package/dist/contributions.js +96 -20
- package/dist/extensions.js +3 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +10 -4
- package/dist/input.d.ts +7 -1
- package/dist/input.js +52 -11
- package/dist/instruction-injection.d.ts +28 -0
- package/dist/instruction-injection.js +55 -0
- package/dist/manifests.d.ts +1 -1
- package/dist/manifests.js +3 -3
- package/dist/models.d.ts +4 -1
- package/dist/models.js +5 -2
- package/dist/node/agent-definitions.d.ts +98 -0
- package/dist/node/agent-definitions.js +389 -0
- package/dist/node/contribution-discovery.d.ts +17 -0
- package/dist/node/contribution-discovery.js +163 -0
- package/dist/node/instruction-injectors.d.ts +32 -0
- package/dist/node/instruction-injectors.js +72 -0
- package/dist/node/session-store-jsonl.d.ts +1 -1
- package/dist/node/session-store-jsonl.js +42 -4
- package/dist/node/system-project-prompts.d.ts +30 -0
- package/dist/node/system-project-prompts.js +53 -0
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +34 -0
- package/dist/provider-request-policy.js +15 -1
- package/dist/providers/openai-compatible.js +1 -1
- package/dist/providers.d.ts +6 -2
- package/dist/providers.js +15 -1
- package/dist/redaction.d.ts +2 -1
- package/dist/redaction.js +3 -0
- package/dist/registry-options.d.ts +5 -0
- package/dist/registry-options.js +5 -0
- package/dist/rpc.d.ts +6 -2
- package/dist/rpc.js +71 -13
- package/dist/session-stores.d.ts +3 -1
- package/dist/session-stores.js +67 -6
- package/dist/skills.d.ts +4 -1
- package/dist/skills.js +3 -1
- package/dist/system-prompts.js +6 -2
- package/dist/testing/compaction-conformance.d.ts +17 -0
- package/dist/testing/compaction-conformance.js +61 -0
- package/dist/testing/extension-conformance.d.ts +26 -0
- package/dist/testing/extension-conformance.js +55 -0
- package/dist/testing/provider-conformance.d.ts +7 -0
- package/dist/testing/provider-conformance.js +18 -31
- package/dist/testing/session-store-conformance.d.ts +20 -0
- package/dist/testing/session-store-conformance.js +92 -0
- package/dist/testing/tool-conformance.d.ts +39 -0
- package/dist/testing/tool-conformance.js +79 -0
- package/dist/tools.d.ts +7 -2
- package/dist/tools.js +50 -13
- package/docs/agent-definitions.md +251 -0
- package/docs/agent-events.md +199 -0
- package/docs/agent-loops.md +217 -0
- package/docs/agent-session-runtime.md +20 -8
- package/docs/cli-rpc.md +39 -4
- package/docs/coding-agent-tools.md +208 -0
- package/docs/compaction-and-retry.md +2 -2
- package/docs/compaction-conformance.md +76 -0
- package/docs/compaction-llm.md +6 -3
- package/docs/compaction-observational-memory.md +4 -4
- package/docs/configuration-and-manifests.md +6 -1
- package/docs/context-and-skills.md +79 -6
- package/docs/contribution-discovery.md +149 -0
- package/docs/contribution-registries.md +9 -6
- package/docs/credentials-and-redaction.md +2 -0
- package/docs/customization.md +191 -0
- package/docs/database-persistence.md +407 -0
- package/docs/extension-authoring.md +193 -0
- package/docs/extension-conformance.md +80 -0
- package/docs/extensions.md +6 -0
- package/docs/host-security.md +141 -0
- package/docs/index.md +41 -19
- package/docs/input-and-prompt-assembly.md +19 -3
- package/docs/instruction-injection.md +183 -0
- package/docs/migration.md +201 -0
- package/docs/model-registry.md +122 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/performance.md +127 -0
- package/docs/provider-caching.md +206 -0
- package/docs/provider-conformance.md +32 -5
- package/docs/provider-layer.md +51 -11
- package/docs/provider-packages.md +65 -5
- package/docs/provider-request-policies.md +113 -0
- package/docs/providers/kimi.md +22 -0
- package/docs/providers/neuralwatt.md +388 -0
- package/docs/providers/openai-compatible.md +1 -0
- package/docs/providers/openai.md +21 -0
- package/docs/providers/opencode-go.md +31 -3
- package/docs/providers/openrouter.md +29 -0
- package/docs/providers/zai.md +17 -0
- package/docs/public-contracts.md +87 -12
- package/docs/release-and-install.md +79 -27
- package/docs/runs-and-usage.md +236 -0
- package/docs/session-store-conformance.md +78 -0
- package/docs/session-stores-and-branching.md +10 -6
- package/docs/session-stores.md +126 -0
- package/docs/settings-auth-trust-security.md +18 -4
- package/docs/structured-output.md +247 -0
- package/docs/system-prompts.md +104 -2
- package/docs/tool-conformance.md +87 -0
- package/docs/tools.md +65 -8
- package/package.json +36 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.3] - 2026-07-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- New first-party workspace package `@arnilo/prism-coding-agent` providing optional host coding tools (`shell`, `read`, `write`, `edit`) as Prism `ToolDefinition` objects. The package is opt-in and is **not** included in `@arnilo/prism-all` because the tools perform host shell/filesystem operations.
|
|
15
|
+
- `createCodingTools`, `createReadOnlyTools`, and `createAllTools` aggregator factories for importing/registering coding tools.
|
|
16
|
+
- Documentation: `docs/coding-agent-tools.md`, updated `docs/index.md` and `docs/tools.md`, and expanded `packages/coding-agent/README.md`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Bumped all package versions from `0.0.2` to `0.0.3` (core, first-party workspace packages, and umbrella packages).
|
|
21
|
+
- Updated `@arnilo/prism` peer dependency range in every first-party workspace package to `0.0.3`.
|
|
22
|
+
- Updated umbrella package dependency pins to `0.0.3`.
|
|
23
|
+
- `docs/release-and-install.md` now documents nine first-party workspace packages, thirteen total manifests, and the explicit install command for `@arnilo/prism-coding-agent`.
|
|
24
|
+
|
|
25
|
+
## [0.0.2] - 2026-07-05
|
|
26
|
+
|
|
10
27
|
### Added
|
|
11
28
|
|
|
12
29
|
- Added `LICENSE` (MIT) and `CHANGELOG.md` to the published `prism` package.
|
|
@@ -19,8 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
36
|
`dist/**/*.map` from published tarballs; source maps remain emitted locally
|
|
20
37
|
for debugging but are no longer shipped.
|
|
21
38
|
- 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.
|
|
23
|
-
- Pinned the no-network `npm test` budget at <
|
|
39
|
+
- Made `prism` a required peer dependency for all first-party workspace packages; it is no longer optional. The peer range remains `0.0.2` and will widen to `^1.0.0` at the 1.x stable release.
|
|
40
|
+
- Pinned the no-network `npm test` budget at < 60s on Node 20 (measured baseline ~45s) after the default suite grew to include every first-party package, offline install smoke, packaging guards, docs examples, and workspace tests.
|
|
24
41
|
|
|
25
42
|
## [0.0.1] - 2026-06-22
|
|
26
43
|
|
package/README.md
CHANGED
|
@@ -13,10 +13,14 @@ packages. Prism defines contracts, not apps.
|
|
|
13
13
|
compact, and navigate branches.
|
|
14
14
|
- **Providers and models**: provider/model registries, provider event helpers,
|
|
15
15
|
credential redaction helpers, mock provider, and an optional
|
|
16
|
-
OpenAI-compatible provider subpath.
|
|
16
|
+
OpenAI-compatible provider subpath. Cache support is provider-specific:
|
|
17
|
+
OpenAI/OpenRouter use best-effort explicit cache hints, NeuralWatt uses
|
|
18
|
+
best-effort implicit prefix caching, and other providers have route/model-specific
|
|
19
|
+
or no cache-control support; see [docs/provider-caching.md](docs/provider-caching.md).
|
|
17
20
|
- **First-party packages**: `@arnilo/prism-provider-openai`, `@arnilo/prism-provider-opencode-go`,
|
|
18
21
|
`@arnilo/prism-provider-openrouter`, `@arnilo/prism-provider-zai`, `@arnilo/prism-provider-kimi`,
|
|
19
|
-
`@arnilo/prism-
|
|
22
|
+
`@arnilo/prism-provider-neuralwatt`, `@arnilo/prism-compaction-llm`, and
|
|
23
|
+
`@arnilo/prism-compaction-observational-memory`.
|
|
20
24
|
- **Tools, context, skills**: host-owned tool registry with allow/deny filtering
|
|
21
25
|
and dispatch, context providers, and a skill registry with progressive
|
|
22
26
|
disclosure.
|
|
@@ -63,11 +67,16 @@ const agent = createAgent({
|
|
|
63
67
|
|
|
64
68
|
const session = createAgentSession({ agent });
|
|
65
69
|
|
|
70
|
+
// Consume the event stream concurrently with the run. `subscribe()` only
|
|
71
|
+
// emits while a run is in progress, so the loop and `run()` must run together;
|
|
72
|
+
// awaiting the loop before calling `run()` would deadlock.
|
|
66
73
|
(async () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
const consumer = (async () => {
|
|
75
|
+
for await (const event of session.subscribe()) {
|
|
76
|
+
// AgentEvent: agent_started, message_delta, turn_finished, ...
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
await Promise.all([consumer, session.run("Hi")]);
|
|
71
80
|
})();
|
|
72
81
|
```
|
|
73
82
|
|
|
@@ -115,9 +124,10 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
115
124
|
| `@arnilo/prism-provider-openrouter` | OpenRouter provider with per-model cache control |
|
|
116
125
|
| `@arnilo/prism-provider-zai` | ZAI GLM provider |
|
|
117
126
|
| `@arnilo/prism-provider-kimi` | Kimi For Coding provider |
|
|
127
|
+
| `@arnilo/prism-provider-neuralwatt` | NeuralWatt provider with implicit vLLM prefix caching |
|
|
118
128
|
| `@arnilo/prism-compaction-llm` | provider-backed compaction strategy |
|
|
119
129
|
| `@arnilo/prism-compaction-observational-memory` | source-backed memory + recall tool |
|
|
120
|
-
| `@arnilo/prism-providers` | umbrella: all
|
|
130
|
+
| `@arnilo/prism-providers` | umbrella: all 6 provider adapters |
|
|
121
131
|
| `@arnilo/prism-compaction` | umbrella: both compaction strategies |
|
|
122
132
|
| `@arnilo/prism-all` | umbrella: core + providers + compaction |
|
|
123
133
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Agent, AgentDefinition, AgentDefinitionResolutionContext } from "./contracts.js";
|
|
2
|
+
/** Resolve an {@link AgentDefinition} into a runnable {@link Agent}.
|
|
3
|
+
*
|
|
4
|
+
* If the definition provides a `create()` escape hatch, that factory is called
|
|
5
|
+
* with a config built from the declarative fields and `context.overrides` are
|
|
6
|
+
* merged into the returned agent. Otherwise the declarative fields are resolved
|
|
7
|
+
* against the supplied registries and turned into an agent via
|
|
8
|
+
* {@link createAgent}.
|
|
9
|
+
*
|
|
10
|
+
* The host controls scope by which registries it passes. Missing dependencies
|
|
11
|
+
* fail closed at resolution time, before any provider turn. */
|
|
12
|
+
export declare function resolveAgentDefinition(def: AgentDefinition, context: AgentDefinitionResolutionContext): Promise<Agent> | Agent;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createAgent } from "./agents.js";
|
|
2
|
+
import { resolveActiveSkills, createSkillRegistry } from "./skills.js";
|
|
3
|
+
import { createToolRegistry } from "./tools.js";
|
|
4
|
+
/** Resolve an {@link AgentDefinition} into a runnable {@link Agent}.
|
|
5
|
+
*
|
|
6
|
+
* If the definition provides a `create()` escape hatch, that factory is called
|
|
7
|
+
* with a config built from the declarative fields and `context.overrides` are
|
|
8
|
+
* merged into the returned agent. Otherwise the declarative fields are resolved
|
|
9
|
+
* against the supplied registries and turned into an agent via
|
|
10
|
+
* {@link createAgent}.
|
|
11
|
+
*
|
|
12
|
+
* The host controls scope by which registries it passes. Missing dependencies
|
|
13
|
+
* fail closed at resolution time, before any provider turn. */
|
|
14
|
+
export function resolveAgentDefinition(def, context) {
|
|
15
|
+
const baseConfig = buildBaseConfig(def, context);
|
|
16
|
+
if (def.create) {
|
|
17
|
+
const agentOrPromise = def.create(baseConfig);
|
|
18
|
+
return applyAgentOverrides(agentOrPromise, context.overrides);
|
|
19
|
+
}
|
|
20
|
+
return createAgent(applyConfigOverrides(baseConfig, context.overrides));
|
|
21
|
+
}
|
|
22
|
+
function buildBaseConfig(def, context) {
|
|
23
|
+
const model = resolveModel(def.name, def.model, context);
|
|
24
|
+
const tools = resolveTools(def.tools, context);
|
|
25
|
+
const skills = resolveSkills(def.skills, tools, context);
|
|
26
|
+
return {
|
|
27
|
+
model,
|
|
28
|
+
...resolveProviderOptions(model, context),
|
|
29
|
+
...(tools !== undefined && { tools }),
|
|
30
|
+
...(skills !== undefined && { skills }),
|
|
31
|
+
...(def.context !== undefined && { context: resolveContextProviders(def.name, def.context, context) }),
|
|
32
|
+
...(def.systemPrompt !== undefined && { systemPrompt: def.systemPrompt }),
|
|
33
|
+
...(def.instructions !== undefined && { instructions: def.instructions }),
|
|
34
|
+
...(def.loop !== undefined && { loop: def.loop }),
|
|
35
|
+
...(def.metadata !== undefined && { metadata: def.metadata }),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function resolveModel(agentName, model, context) {
|
|
39
|
+
if (!model)
|
|
40
|
+
throw new Error(`Agent "${agentName}" has no model`);
|
|
41
|
+
if (typeof model !== "string")
|
|
42
|
+
return model;
|
|
43
|
+
const slash = model.indexOf("/");
|
|
44
|
+
if (slash === -1) {
|
|
45
|
+
throw new Error(`Unknown model: ${model}`);
|
|
46
|
+
}
|
|
47
|
+
const provider = model.slice(0, slash);
|
|
48
|
+
const modelId = model.slice(slash + 1);
|
|
49
|
+
if (context.registries?.models) {
|
|
50
|
+
return context.registries.models.resolve(provider, modelId);
|
|
51
|
+
}
|
|
52
|
+
return { provider, model: modelId };
|
|
53
|
+
}
|
|
54
|
+
function resolveProviderOptions(model, context) {
|
|
55
|
+
if (context.providerSource) {
|
|
56
|
+
return { providerSource: context.providerSource };
|
|
57
|
+
}
|
|
58
|
+
if (context.registries?.providers) {
|
|
59
|
+
return { provider: context.registries.providers.resolve(model) };
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function resolveTools(names, context) {
|
|
64
|
+
const source = context.tools ?? context.registries?.tools;
|
|
65
|
+
if (!names) {
|
|
66
|
+
if (!context.activateAllCapabilities)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (!source)
|
|
69
|
+
return undefined;
|
|
70
|
+
return asToolRegistry(source);
|
|
71
|
+
}
|
|
72
|
+
if (!source)
|
|
73
|
+
throw new Error("No tool registry in scope");
|
|
74
|
+
const available = listFromToolSource(source);
|
|
75
|
+
const resolved = names.map((name) => {
|
|
76
|
+
const tool = available.find((item) => item.name === name);
|
|
77
|
+
if (!tool)
|
|
78
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
79
|
+
return tool;
|
|
80
|
+
});
|
|
81
|
+
return createToolRegistry(resolved);
|
|
82
|
+
}
|
|
83
|
+
function asToolRegistry(source) {
|
|
84
|
+
if (hasList(source)) {
|
|
85
|
+
return createToolRegistry(source.list());
|
|
86
|
+
}
|
|
87
|
+
return createToolRegistry(source);
|
|
88
|
+
}
|
|
89
|
+
function listFromToolSource(source) {
|
|
90
|
+
if (hasList(source)) {
|
|
91
|
+
return source.list();
|
|
92
|
+
}
|
|
93
|
+
return source;
|
|
94
|
+
}
|
|
95
|
+
function hasList(value) {
|
|
96
|
+
return typeof value === "object" && value !== null && "list" in value && typeof value.list === "function";
|
|
97
|
+
}
|
|
98
|
+
function resolveSkills(names, tools, context) {
|
|
99
|
+
const registry = context.skillsRegistry ??
|
|
100
|
+
(context.registries?.skills ? createSkillRegistry(context.registries.skills.list()) : undefined);
|
|
101
|
+
if (!names && !context.activateAllCapabilities)
|
|
102
|
+
return undefined;
|
|
103
|
+
if (!registry) {
|
|
104
|
+
if (!names)
|
|
105
|
+
return undefined;
|
|
106
|
+
throw new Error("No skill registry in scope");
|
|
107
|
+
}
|
|
108
|
+
const toolList = tools?.list() ?? [];
|
|
109
|
+
const activeNames = names ?? registry.list().map((skill) => skill.name);
|
|
110
|
+
return resolveActiveSkills({ registry, names: activeNames, tools: toolList });
|
|
111
|
+
}
|
|
112
|
+
function resolveContextProviders(agentName, names, context) {
|
|
113
|
+
const registry = context.registries?.contextProviders;
|
|
114
|
+
if (!registry)
|
|
115
|
+
throw new Error(`Agent "${agentName}" declares context providers but no registry is in scope`);
|
|
116
|
+
return names.map((name) => registry.resolve(name));
|
|
117
|
+
}
|
|
118
|
+
function applyConfigOverrides(config, overrides) {
|
|
119
|
+
if (!overrides)
|
|
120
|
+
return config;
|
|
121
|
+
return { ...config, ...overrides };
|
|
122
|
+
}
|
|
123
|
+
function applyAgentOverrides(agent, overrides) {
|
|
124
|
+
if (!overrides)
|
|
125
|
+
return agent;
|
|
126
|
+
if (agent instanceof Promise) {
|
|
127
|
+
return agent.then((resolved) => createAgent({ ...resolved.config, ...overrides }));
|
|
128
|
+
}
|
|
129
|
+
return createAgent({ ...agent.config, ...overrides });
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=agent-definitions.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentLoopOptions, AgentLoopStrategy, ArtifactParser, ArtifactRepairer, ArtifactValidator } from "./contracts.js";
|
|
2
|
+
export declare const singleShotLoop: AgentLoopStrategy;
|
|
3
|
+
export declare function isAgentLoopOptions(value: AgentLoopStrategy | AgentLoopOptions | undefined): value is AgentLoopOptions;
|
|
4
|
+
export declare function generateValidateReviseLoop(opts: {
|
|
5
|
+
readonly validator: ArtifactValidator<unknown>;
|
|
6
|
+
readonly parser?: ArtifactParser<unknown>;
|
|
7
|
+
readonly repairer?: ArtifactRepairer<unknown>;
|
|
8
|
+
readonly maxRevisions?: number;
|
|
9
|
+
}): AgentLoopStrategy;
|
|
10
|
+
export declare function resolveLoop(options: {
|
|
11
|
+
loop?: AgentLoopStrategy | AgentLoopOptions;
|
|
12
|
+
}, config: {
|
|
13
|
+
loop?: AgentLoopStrategy | AgentLoopOptions;
|
|
14
|
+
}): AgentLoopStrategy;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { inputMessages } from "./input.js";
|
|
2
|
+
function throwIfAborted(signal) {
|
|
3
|
+
if (signal.aborted)
|
|
4
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
5
|
+
}
|
|
6
|
+
function toolResultMessage(result) {
|
|
7
|
+
return {
|
|
8
|
+
role: "tool",
|
|
9
|
+
content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
|
|
10
|
+
metadata: result.metadata,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// ponytail: SingleShotLoop extracted bit-for-bit from the former inline turn
|
|
14
|
+
// loop in RuntimeAgentSession.run. The runtime owns provider calls, retry,
|
|
15
|
+
// abort, store, events via LoopContext; this only orchestrates turns. Phase 28
|
|
16
|
+
// fires artifact_* events here as a noop seam — single-shot emits zero.
|
|
17
|
+
export const singleShotLoop = {
|
|
18
|
+
name: "single-shot",
|
|
19
|
+
async run(ctx) {
|
|
20
|
+
let usage;
|
|
21
|
+
const toolResults = [];
|
|
22
|
+
let toolRounds = 0;
|
|
23
|
+
let nextInput = ctx.input;
|
|
24
|
+
for (let turn = 1;; turn += 1) {
|
|
25
|
+
throwIfAborted(ctx.signal);
|
|
26
|
+
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
27
|
+
const request = await ctx.assemble(nextInput, toolResults, turn);
|
|
28
|
+
throwIfAborted(ctx.signal);
|
|
29
|
+
const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
30
|
+
usage = turnUsage ?? usage;
|
|
31
|
+
if (turn === 1)
|
|
32
|
+
ctx.history.push(...ctx.inputMessages);
|
|
33
|
+
if (started) {
|
|
34
|
+
const message = { id: messageId, role: "assistant", content };
|
|
35
|
+
ctx.history.push(message);
|
|
36
|
+
await ctx.appendMessage(message);
|
|
37
|
+
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
38
|
+
}
|
|
39
|
+
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
40
|
+
if (calls.length === 0 || toolRounds >= ctx.maxToolRounds)
|
|
41
|
+
break;
|
|
42
|
+
toolRounds += 1;
|
|
43
|
+
for (const call of calls) {
|
|
44
|
+
const result = await ctx.dispatchToolCall(call);
|
|
45
|
+
toolResults.push(result);
|
|
46
|
+
await ctx.appendMessage(toolResultMessage(result));
|
|
47
|
+
throwIfAborted(ctx.signal);
|
|
48
|
+
}
|
|
49
|
+
nextInput = [];
|
|
50
|
+
}
|
|
51
|
+
return usage;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
export function isAgentLoopOptions(value) {
|
|
55
|
+
return typeof value === "object" && value !== null && "strategy" in value;
|
|
56
|
+
}
|
|
57
|
+
function defaultRepairer() {
|
|
58
|
+
return (_value, failure) => ({
|
|
59
|
+
role: "user",
|
|
60
|
+
content: [{ type: "text", text: failure.errors?.map((e) => e.message).join("\n") ?? "invalid artifact" }],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// ponytail: GenerateValidateReviseLoop reuses LoopContext primitives only —
|
|
64
|
+
// no provider/retry/store/event re-implementation. T is host-defined, Prism
|
|
65
|
+
// never instantiates it. No tools in artifact revisions (roadmap scope is
|
|
66
|
+
// generate→validate→revise; tool coupling deferred). Phase 28 fires
|
|
67
|
+
// artifact_* events at the marked seams (noop here).
|
|
68
|
+
export function generateValidateReviseLoop(opts) {
|
|
69
|
+
const max = opts.maxRevisions ?? 3;
|
|
70
|
+
const repairer = opts.repairer ?? defaultRepairer();
|
|
71
|
+
return {
|
|
72
|
+
name: "generate-validate-revise",
|
|
73
|
+
async run(ctx) {
|
|
74
|
+
let usage;
|
|
75
|
+
let nextInput = ctx.input;
|
|
76
|
+
for (let turn = 1; turn <= max + 1; turn += 1) {
|
|
77
|
+
throwIfAborted(ctx.signal);
|
|
78
|
+
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
79
|
+
const request = await ctx.assemble(nextInput, undefined, turn);
|
|
80
|
+
throwIfAborted(ctx.signal);
|
|
81
|
+
const { content, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
82
|
+
usage = turnUsage ?? usage;
|
|
83
|
+
if (turn === 1)
|
|
84
|
+
ctx.history.push(...ctx.inputMessages);
|
|
85
|
+
if (started) {
|
|
86
|
+
const message = { id: messageId, role: "assistant", content };
|
|
87
|
+
ctx.history.push(message);
|
|
88
|
+
await ctx.appendMessage(message);
|
|
89
|
+
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
90
|
+
}
|
|
91
|
+
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
92
|
+
const text = content
|
|
93
|
+
.filter((b) => b.type === "text")
|
|
94
|
+
.map((b) => b.text)
|
|
95
|
+
.join("");
|
|
96
|
+
const artifactCtx = {
|
|
97
|
+
sessionId: ctx.sessionId,
|
|
98
|
+
runId: ctx.runId,
|
|
99
|
+
turn,
|
|
100
|
+
signal: ctx.signal,
|
|
101
|
+
metadata: ctx.metadata,
|
|
102
|
+
};
|
|
103
|
+
const parsed = opts.parser
|
|
104
|
+
? await opts.parser(text, artifactCtx)
|
|
105
|
+
: { ok: true, value: text };
|
|
106
|
+
// Parse failure ends the loop silently (terminal parse errors stay on `error`).
|
|
107
|
+
if (!parsed.ok || parsed.value === undefined)
|
|
108
|
+
return usage;
|
|
109
|
+
const attempt = turn;
|
|
110
|
+
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
111
|
+
const result = await opts.validator(parsed.value, artifactCtx);
|
|
112
|
+
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
113
|
+
if (result.ok) {
|
|
114
|
+
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
115
|
+
return usage;
|
|
116
|
+
}
|
|
117
|
+
if (turn > max) {
|
|
118
|
+
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
119
|
+
return usage;
|
|
120
|
+
}
|
|
121
|
+
ctx.emit({ type: "artifact_revision_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, failure: result });
|
|
122
|
+
const repair = await repairer(parsed.value, result, artifactCtx);
|
|
123
|
+
const repairMessages = inputMessages(repair).map((m) => ({ ...m, id: randomId("msg") }));
|
|
124
|
+
for (const message of repairMessages) {
|
|
125
|
+
ctx.history.push(message);
|
|
126
|
+
await ctx.appendMessage(message);
|
|
127
|
+
}
|
|
128
|
+
nextInput = repairMessages;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
return usage;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// ponytail: resolveLoop returns the loop to run. RunOptions.loop wins over
|
|
136
|
+
// AgentConfig.loop; default is singleShotLoop. generate-validate-revise maps
|
|
137
|
+
// its options to the factory; unknown strategy throws.
|
|
138
|
+
export function resolveLoop(options, config) {
|
|
139
|
+
const loop = options.loop ?? config.loop;
|
|
140
|
+
if (!loop)
|
|
141
|
+
return singleShotLoop;
|
|
142
|
+
if (isAgentLoopOptions(loop)) {
|
|
143
|
+
const { strategy } = loop;
|
|
144
|
+
if (strategy === "single-shot")
|
|
145
|
+
return singleShotLoop;
|
|
146
|
+
if (strategy === "generate-validate-revise") {
|
|
147
|
+
return generateValidateReviseLoop({
|
|
148
|
+
validator: loop.validator,
|
|
149
|
+
parser: loop.parser,
|
|
150
|
+
repairer: loop.repairer,
|
|
151
|
+
maxRevisions: loop.maxRevisions,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`Unknown agent loop strategy: ${strategy}`);
|
|
155
|
+
}
|
|
156
|
+
return loop;
|
|
157
|
+
}
|
|
158
|
+
function randomId(prefix) {
|
|
159
|
+
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=agent-loops.js.map
|