@zenera/neo 1.1.0
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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/agent.d.ts +66 -0
- package/dist/agent.js +97 -0
- package/dist/architecture.d.ts +74 -0
- package/dist/architecture.js +105 -0
- package/dist/embedding.d.ts +60 -0
- package/dist/embedding.js +21 -0
- package/dist/embeddings/gemini.d.ts +24 -0
- package/dist/embeddings/gemini.js +71 -0
- package/dist/embeddings/index.d.ts +4 -0
- package/dist/embeddings/index.js +4 -0
- package/dist/embeddings/openai.d.ts +18 -0
- package/dist/embeddings/openai.js +27 -0
- package/dist/embeddings/openrouter.d.ts +21 -0
- package/dist/embeddings/openrouter.js +54 -0
- package/dist/events.d.ts +144 -0
- package/dist/events.js +110 -0
- package/dist/ids.d.ts +14 -0
- package/dist/ids.js +33 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/inspect/client.d.ts +2 -0
- package/dist/inspect/client.js +1326 -0
- package/dist/inspect/css.d.ts +2 -0
- package/dist/inspect/css.js +164 -0
- package/dist/inspect/index.d.ts +9 -0
- package/dist/inspect/index.js +12 -0
- package/dist/inspect/page.d.ts +3 -0
- package/dist/inspect/page.js +93 -0
- package/dist/inspect/report.d.ts +54 -0
- package/dist/inspect/report.js +62 -0
- package/dist/kernel.d.ts +178 -0
- package/dist/kernel.js +929 -0
- package/dist/memory-stores/factory.d.ts +15 -0
- package/dist/memory-stores/factory.js +29 -0
- package/dist/memory-stores/file.d.ts +37 -0
- package/dist/memory-stores/file.js +174 -0
- package/dist/memory-stores/in-memory.d.ts +17 -0
- package/dist/memory-stores/in-memory.js +80 -0
- package/dist/memory-stores/index.d.ts +4 -0
- package/dist/memory-stores/index.js +5 -0
- package/dist/memory.d.ts +115 -0
- package/dist/memory.js +213 -0
- package/dist/model.d.ts +25 -0
- package/dist/model.js +2 -0
- package/dist/models/anthropic.d.ts +36 -0
- package/dist/models/anthropic.js +293 -0
- package/dist/models/factory.d.ts +202 -0
- package/dist/models/factory.js +533 -0
- package/dist/models/gemini.d.ts +45 -0
- package/dist/models/gemini.js +368 -0
- package/dist/models/index.d.ts +7 -0
- package/dist/models/index.js +7 -0
- package/dist/models/openai-chat.d.ts +19 -0
- package/dist/models/openai-chat.js +203 -0
- package/dist/models/openai-responses.d.ts +31 -0
- package/dist/models/openai-responses.js +258 -0
- package/dist/models/openrouter.d.ts +44 -0
- package/dist/models/openrouter.js +230 -0
- package/dist/payload-stores/factory.d.ts +15 -0
- package/dist/payload-stores/factory.js +30 -0
- package/dist/payload-stores/file.d.ts +29 -0
- package/dist/payload-stores/file.js +112 -0
- package/dist/payload-stores/in-memory.d.ts +17 -0
- package/dist/payload-stores/in-memory.js +40 -0
- package/dist/payload-stores/index.d.ts +4 -0
- package/dist/payload-stores/index.js +5 -0
- package/dist/payload.d.ts +89 -0
- package/dist/payload.js +181 -0
- package/dist/project/config.d.ts +302 -0
- package/dist/project/config.js +290 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +4 -0
- package/dist/project/load.d.ts +142 -0
- package/dist/project/load.js +430 -0
- package/dist/project/refs.d.ts +23 -0
- package/dist/project/refs.js +70 -0
- package/dist/prompt.d.ts +66 -0
- package/dist/prompt.js +62 -0
- package/dist/runner.d.ts +103 -0
- package/dist/runner.js +537 -0
- package/dist/services.d.ts +23 -0
- package/dist/services.js +44 -0
- package/dist/skill-providers/factory.d.ts +16 -0
- package/dist/skill-providers/factory.js +26 -0
- package/dist/skill-providers/file.d.ts +44 -0
- package/dist/skill-providers/file.js +209 -0
- package/dist/skill-providers/index.d.ts +4 -0
- package/dist/skill-providers/index.js +5 -0
- package/dist/skill-providers/static.d.ts +13 -0
- package/dist/skill-providers/static.js +47 -0
- package/dist/skills.d.ts +102 -0
- package/dist/skills.js +232 -0
- package/dist/state.d.ts +86 -0
- package/dist/state.js +49 -0
- package/dist/tools/exa.d.ts +23 -0
- package/dist/tools/exa.js +389 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/sandbox.d.ts +172 -0
- package/dist/tools/sandbox.js +602 -0
- package/dist/tools/workspace.d.ts +53 -0
- package/dist/tools/workspace.js +1034 -0
- package/dist/trajectory.d.ts +213 -0
- package/dist/trajectory.js +232 -0
- package/dist/types.d.ts +195 -0
- package/dist/types.js +134 -0
- package/package.json +98 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { MemoryQuery } from './memory.ts';
|
|
2
|
+
import type { Payload, PayloadResolver } from './payload.ts';
|
|
3
|
+
import type { PromptSource } from './prompt.ts';
|
|
4
|
+
import { type MediaKind, type Message, type TokenUsage } from './types.ts';
|
|
5
|
+
export interface NodeBase {
|
|
6
|
+
/** ulid — unique within a trajectory */
|
|
7
|
+
id: string;
|
|
8
|
+
/** ISO timestamp (informational, not used for logic) */
|
|
9
|
+
ts: string;
|
|
10
|
+
/** active agent when the node was created */
|
|
11
|
+
agent: string;
|
|
12
|
+
}
|
|
13
|
+
/** A content part whose text lives behind a payload reference. */
|
|
14
|
+
export type PayloadPart = {
|
|
15
|
+
type: 'text';
|
|
16
|
+
text: Payload;
|
|
17
|
+
} | {
|
|
18
|
+
type: MediaKind;
|
|
19
|
+
url: string;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
};
|
|
22
|
+
export interface UserInputNode extends NodeBase {
|
|
23
|
+
type: 'user_input';
|
|
24
|
+
content: PayloadPart[];
|
|
25
|
+
/** produced by the runtime (e.g. a "call final_output" nudge), not the user */
|
|
26
|
+
synthetic?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SystemPromptNode extends NodeBase {
|
|
29
|
+
type: 'system_prompt';
|
|
30
|
+
/** exactly what the provider received */
|
|
31
|
+
prompt: Payload;
|
|
32
|
+
/**
|
|
33
|
+
* The file-backed contributors, in the order they were rendered — the
|
|
34
|
+
* answer to "which file do I edit to change this?". Inline text, the skill
|
|
35
|
+
* index and runtime notes are inside `prompt` but not listed here: none of
|
|
36
|
+
* them is a document anyone opens.
|
|
37
|
+
*/
|
|
38
|
+
sources?: PromptSource[];
|
|
39
|
+
}
|
|
40
|
+
export interface LoadSkillsNode extends NodeBase {
|
|
41
|
+
type: 'load_skills';
|
|
42
|
+
provider: string;
|
|
43
|
+
skills: {
|
|
44
|
+
name: string;
|
|
45
|
+
version?: string;
|
|
46
|
+
contentHash: string;
|
|
47
|
+
file?: string;
|
|
48
|
+
}[];
|
|
49
|
+
/** concatenated instructions the model saw */
|
|
50
|
+
content: Payload;
|
|
51
|
+
/** tools this activation unlocked */
|
|
52
|
+
toolNames: string[];
|
|
53
|
+
}
|
|
54
|
+
export interface MemoryRecallNode extends NodeBase {
|
|
55
|
+
type: 'memory_recall';
|
|
56
|
+
store: string;
|
|
57
|
+
scope: string;
|
|
58
|
+
query: MemoryQuery;
|
|
59
|
+
/** ids, not bodies — the rendered block is the payload */
|
|
60
|
+
hits: {
|
|
61
|
+
id: string;
|
|
62
|
+
score: number;
|
|
63
|
+
revision: number;
|
|
64
|
+
}[];
|
|
65
|
+
content: Payload;
|
|
66
|
+
}
|
|
67
|
+
export interface MemoryOpNode extends NodeBase {
|
|
68
|
+
type: 'memory_op';
|
|
69
|
+
op: 'write' | 'update' | 'delete';
|
|
70
|
+
store: string;
|
|
71
|
+
scope: string;
|
|
72
|
+
/** sha256(runId, callId) — deterministic, so a replay is deduplicated */
|
|
73
|
+
opId: string;
|
|
74
|
+
recordId: string;
|
|
75
|
+
revision: number;
|
|
76
|
+
before?: Payload;
|
|
77
|
+
after?: Payload;
|
|
78
|
+
}
|
|
79
|
+
export interface LlmCallNode extends NodeBase {
|
|
80
|
+
type: 'llm_call';
|
|
81
|
+
/** exact model id used */
|
|
82
|
+
model: string;
|
|
83
|
+
/** sha256 of the projected request, for replay checks */
|
|
84
|
+
requestDigest: string;
|
|
85
|
+
request?: Payload;
|
|
86
|
+
text: Payload;
|
|
87
|
+
thinking?: Payload;
|
|
88
|
+
/** `signature` is opaque provider state — see `ToolCall.signature` */
|
|
89
|
+
toolCalls: {
|
|
90
|
+
callId: string;
|
|
91
|
+
name: string;
|
|
92
|
+
args: Payload;
|
|
93
|
+
signature?: string;
|
|
94
|
+
}[];
|
|
95
|
+
usage: TokenUsage;
|
|
96
|
+
stopReason: 'stop' | 'tool_calls' | 'length' | 'content_filter';
|
|
97
|
+
}
|
|
98
|
+
export interface ToolCallNode extends NodeBase {
|
|
99
|
+
type: 'tool_call';
|
|
100
|
+
callId: string;
|
|
101
|
+
name: string;
|
|
102
|
+
args: Payload;
|
|
103
|
+
}
|
|
104
|
+
export interface ToolResultNode extends NodeBase {
|
|
105
|
+
type: 'tool_result';
|
|
106
|
+
callId: string;
|
|
107
|
+
name: string;
|
|
108
|
+
result: Payload;
|
|
109
|
+
isError: boolean;
|
|
110
|
+
durationMs?: number;
|
|
111
|
+
}
|
|
112
|
+
export interface HandoffNode extends NodeBase {
|
|
113
|
+
type: 'handoff';
|
|
114
|
+
from: string;
|
|
115
|
+
to: string;
|
|
116
|
+
reason?: string;
|
|
117
|
+
}
|
|
118
|
+
export interface ForkNode extends NodeBase {
|
|
119
|
+
type: 'fork';
|
|
120
|
+
/** the fork tool call that caused it — also the fork's identity */
|
|
121
|
+
callId: string;
|
|
122
|
+
contextMode: 'inherit' | 'compact' | 'none';
|
|
123
|
+
branches: {
|
|
124
|
+
name: string;
|
|
125
|
+
agent: string;
|
|
126
|
+
instructions: Payload;
|
|
127
|
+
childRunId: string;
|
|
128
|
+
}[];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The outcome of a fork. Each branch's own history hangs off its row: fork is a
|
|
132
|
+
* scope, and nesting makes that structural rather than conventional — a walk of
|
|
133
|
+
* the parent array simply never encounters a branch node.
|
|
134
|
+
*/
|
|
135
|
+
export interface JoinNode extends NodeBase {
|
|
136
|
+
type: 'join';
|
|
137
|
+
/** the fork tool call this answers */
|
|
138
|
+
callId: string;
|
|
139
|
+
/** always in declared branch order, never completion order */
|
|
140
|
+
branches: {
|
|
141
|
+
name: string;
|
|
142
|
+
agent: string;
|
|
143
|
+
status: 'ok' | 'error' | 'aborted';
|
|
144
|
+
output: Payload;
|
|
145
|
+
error?: string;
|
|
146
|
+
/** display only — the branch's own `llm_call` nodes carry the tokens */
|
|
147
|
+
usage: TokenUsage;
|
|
148
|
+
/**
|
|
149
|
+
* What the branch did. Never projected into the parent's prompt; kept
|
|
150
|
+
* for audit, accounting and export. A nested fork's history hangs off
|
|
151
|
+
* a `join` in here, so depth costs nothing but recursion.
|
|
152
|
+
*/
|
|
153
|
+
nodes: TrajectoryNode[];
|
|
154
|
+
}[];
|
|
155
|
+
/** display only; `totalUsage` recurses into the branches instead */
|
|
156
|
+
usage: TokenUsage;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Collapses a set of nodes into one summary. The covered nodes stay in the
|
|
160
|
+
* trajectory — the log is append-only — and only the projection skips them.
|
|
161
|
+
*/
|
|
162
|
+
export interface CompactionNode extends NodeBase {
|
|
163
|
+
type: 'compaction';
|
|
164
|
+
/** real when the model called `compact`, synthesized when policy-driven */
|
|
165
|
+
callId: string;
|
|
166
|
+
/** ids of the nodes this summary replaces */
|
|
167
|
+
covers: string[];
|
|
168
|
+
summary: Payload;
|
|
169
|
+
reason: 'handoff_noise' | 'token_budget' | 'branch_context' | 'manual' | string;
|
|
170
|
+
/** what the summarizer itself burned */
|
|
171
|
+
usage: TokenUsage;
|
|
172
|
+
}
|
|
173
|
+
export interface FinalOutputNode extends NodeBase {
|
|
174
|
+
type: 'final_output';
|
|
175
|
+
output: Payload;
|
|
176
|
+
/** present when an output schema was set */
|
|
177
|
+
parsed?: unknown;
|
|
178
|
+
}
|
|
179
|
+
export type TrajectoryNode = UserInputNode | SystemPromptNode | LoadSkillsNode | MemoryRecallNode | MemoryOpNode | LlmCallNode | ToolCallNode | ToolResultNode | HandoffNode | ForkNode | JoinNode | CompactionNode | FinalOutputNode;
|
|
180
|
+
export type NodeBody<T extends TrajectoryNode> = Omit<T, keyof NodeBase>;
|
|
181
|
+
/**
|
|
182
|
+
* Ids replaced by a compaction. A branch's compactions live inside its own
|
|
183
|
+
* `nodes` array, so they are structurally out of reach here — a branch can
|
|
184
|
+
* never collapse the parent's history, even though it may compact the prefix
|
|
185
|
+
* it inherited and name ids the parent also has.
|
|
186
|
+
*
|
|
187
|
+
* The union is unconditional, so compaction is monotone: once a node has been
|
|
188
|
+
* summarized it stays hidden, even if a later compaction covers the summary.
|
|
189
|
+
*/
|
|
190
|
+
export declare function coveredIds(nodes: TrajectoryNode[]): Set<string>;
|
|
191
|
+
/**
|
|
192
|
+
* What the model sees. Compaction hides nodes, it never deletes them: the
|
|
193
|
+
* trajectory is append-only, and audit and replay always see the full history.
|
|
194
|
+
*/
|
|
195
|
+
export declare function projected(nodes: TrajectoryNode[]): TrajectoryNode[];
|
|
196
|
+
/**
|
|
197
|
+
* Total consumption, including every token spent inside a fork at any depth.
|
|
198
|
+
* This is the one place that deliberately crosses the branch boundary, so the
|
|
199
|
+
* recursion is the explicit opt-in; `JoinNode.usage` is display only and is not
|
|
200
|
+
* added, since counting it too would double every branch.
|
|
201
|
+
*/
|
|
202
|
+
export declare function totalUsage(trajectory: TrajectoryNode[]): TokenUsage;
|
|
203
|
+
export declare function lastOfType<T extends TrajectoryNode['type']>(trajectory: TrajectoryNode[], type: T): Extract<TrajectoryNode, {
|
|
204
|
+
type: T;
|
|
205
|
+
}> | undefined;
|
|
206
|
+
/** What a node contributes to the *projection* — not everything it references. */
|
|
207
|
+
export declare function nodePayloads(n: TrajectoryNode): Payload[];
|
|
208
|
+
export interface Projection {
|
|
209
|
+
system?: string;
|
|
210
|
+
messages: Message[];
|
|
211
|
+
}
|
|
212
|
+
export declare function projectMessages(trajectory: TrajectoryNode[], payloads: PayloadResolver): Promise<Projection>;
|
|
213
|
+
//# sourceMappingURL=trajectory.d.ts.map
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { addUsage, zeroUsage, } from "./types.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Derived views
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
/**
|
|
6
|
+
* Ids replaced by a compaction. A branch's compactions live inside its own
|
|
7
|
+
* `nodes` array, so they are structurally out of reach here — a branch can
|
|
8
|
+
* never collapse the parent's history, even though it may compact the prefix
|
|
9
|
+
* it inherited and name ids the parent also has.
|
|
10
|
+
*
|
|
11
|
+
* The union is unconditional, so compaction is monotone: once a node has been
|
|
12
|
+
* summarized it stays hidden, even if a later compaction covers the summary.
|
|
13
|
+
*/
|
|
14
|
+
export function coveredIds(nodes) {
|
|
15
|
+
const out = new Set();
|
|
16
|
+
for (const n of nodes) {
|
|
17
|
+
if (n.type === 'compaction') {
|
|
18
|
+
for (const id of n.covers) {
|
|
19
|
+
out.add(id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What the model sees. Compaction hides nodes, it never deletes them: the
|
|
27
|
+
* trajectory is append-only, and audit and replay always see the full history.
|
|
28
|
+
*/
|
|
29
|
+
export function projected(nodes) {
|
|
30
|
+
const covered = coveredIds(nodes);
|
|
31
|
+
return covered.size ? nodes.filter((n) => !covered.has(n.id)) : nodes;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Total consumption, including every token spent inside a fork at any depth.
|
|
35
|
+
* This is the one place that deliberately crosses the branch boundary, so the
|
|
36
|
+
* recursion is the explicit opt-in; `JoinNode.usage` is display only and is not
|
|
37
|
+
* added, since counting it too would double every branch.
|
|
38
|
+
*/
|
|
39
|
+
export function totalUsage(trajectory) {
|
|
40
|
+
return trajectory.reduce((acc, n) => {
|
|
41
|
+
if (n.type === 'llm_call' || n.type === 'compaction') {
|
|
42
|
+
return addUsage(acc, n.usage);
|
|
43
|
+
}
|
|
44
|
+
if (n.type === 'join') {
|
|
45
|
+
return n.branches.reduce((sum, b) => addUsage(sum, totalUsage(b.nodes)), acc);
|
|
46
|
+
}
|
|
47
|
+
return acc;
|
|
48
|
+
}, zeroUsage());
|
|
49
|
+
}
|
|
50
|
+
export function lastOfType(trajectory, type) {
|
|
51
|
+
for (let i = trajectory.length - 1; i >= 0; i--) {
|
|
52
|
+
if (trajectory[i].type === type) {
|
|
53
|
+
return trajectory[i];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/** What a node contributes to the *projection* — not everything it references. */
|
|
59
|
+
export function nodePayloads(n) {
|
|
60
|
+
switch (n.type) {
|
|
61
|
+
case 'user_input':
|
|
62
|
+
return n.content.flatMap((p) => (p.type === 'text' ? [p.text] : []));
|
|
63
|
+
case 'system_prompt':
|
|
64
|
+
return [n.prompt];
|
|
65
|
+
case 'load_skills':
|
|
66
|
+
return [n.content];
|
|
67
|
+
case 'memory_recall':
|
|
68
|
+
return [n.content];
|
|
69
|
+
case 'llm_call':
|
|
70
|
+
return [n.text, ...n.toolCalls.map((c) => c.args)];
|
|
71
|
+
case 'tool_call':
|
|
72
|
+
return [n.args];
|
|
73
|
+
case 'tool_result':
|
|
74
|
+
return [n.result];
|
|
75
|
+
case 'join':
|
|
76
|
+
// Branch histories are not projected; only their outputs are.
|
|
77
|
+
return n.branches.map((b) => b.output);
|
|
78
|
+
case 'compaction':
|
|
79
|
+
return [n.summary];
|
|
80
|
+
case 'final_output':
|
|
81
|
+
return [n.output];
|
|
82
|
+
default:
|
|
83
|
+
// memory_op / handoff / fork carry only provenance in the projection
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function projectMessages(trajectory, payloads) {
|
|
88
|
+
const visible = projected(trajectory);
|
|
89
|
+
const blobs = await payloads.getMany(visible.flatMap(nodePayloads));
|
|
90
|
+
const get = (p) => blobs.get(p.sha256) ?? '';
|
|
91
|
+
let system;
|
|
92
|
+
const messages = [];
|
|
93
|
+
for (const n of visible) {
|
|
94
|
+
switch (n.type) {
|
|
95
|
+
case 'system_prompt':
|
|
96
|
+
// Last one wins; earlier prompts are superseded, not emitted.
|
|
97
|
+
system = get(n.prompt);
|
|
98
|
+
break;
|
|
99
|
+
case 'user_input':
|
|
100
|
+
messages.push({
|
|
101
|
+
role: 'user',
|
|
102
|
+
content: n.content.map((p) => p.type === 'text'
|
|
103
|
+
? { type: 'text', text: get(p.text) }
|
|
104
|
+
: { type: p.type, url: p.url, mimeType: p.mimeType }),
|
|
105
|
+
});
|
|
106
|
+
break;
|
|
107
|
+
case 'load_skills':
|
|
108
|
+
case 'memory_recall': {
|
|
109
|
+
const content = get(n.content);
|
|
110
|
+
if (content) {
|
|
111
|
+
messages.push({ role: 'user', content: [{ type: 'text', text: content }] });
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'llm_call': {
|
|
116
|
+
const toolCalls = n.toolCalls.map((c) => ({
|
|
117
|
+
id: c.callId,
|
|
118
|
+
name: c.name,
|
|
119
|
+
args: get(c.args),
|
|
120
|
+
signature: c.signature,
|
|
121
|
+
}));
|
|
122
|
+
messages.push({
|
|
123
|
+
role: 'assistant',
|
|
124
|
+
content: get(n.text),
|
|
125
|
+
toolCalls: toolCalls.length ? toolCalls : undefined,
|
|
126
|
+
agent: n.agent,
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case 'tool_call':
|
|
131
|
+
// Already folded into the owning llm_call; the standalone node
|
|
132
|
+
// exists only so a checkpoint can be cut before execution.
|
|
133
|
+
break;
|
|
134
|
+
case 'tool_result':
|
|
135
|
+
messages.push({
|
|
136
|
+
role: 'tool',
|
|
137
|
+
callId: n.callId,
|
|
138
|
+
name: n.name,
|
|
139
|
+
content: get(n.result),
|
|
140
|
+
isError: n.isError || undefined,
|
|
141
|
+
});
|
|
142
|
+
break;
|
|
143
|
+
case 'join': {
|
|
144
|
+
const rendered = n.branches
|
|
145
|
+
.map((b) => `### ${b.name} (${b.status})\n` +
|
|
146
|
+
(b.status === 'ok' ? get(b.output) : (b.error ?? get(b.output))))
|
|
147
|
+
.join('\n\n');
|
|
148
|
+
messages.push({
|
|
149
|
+
role: 'tool',
|
|
150
|
+
callId: n.callId,
|
|
151
|
+
name: 'fork',
|
|
152
|
+
content: rendered,
|
|
153
|
+
isError: n.branches.some((b) => b.status !== 'ok') || undefined,
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'compaction': {
|
|
158
|
+
// A tool-call pair, not a user message: the summary is the
|
|
159
|
+
// runtime's, and putting it in the user's mouth misattributes
|
|
160
|
+
// it. Both halves come from this one node, so a later
|
|
161
|
+
// compaction removes them together and cannot orphan either.
|
|
162
|
+
messages.push({
|
|
163
|
+
role: 'assistant',
|
|
164
|
+
content: '',
|
|
165
|
+
agent: n.agent,
|
|
166
|
+
toolCalls: [
|
|
167
|
+
{
|
|
168
|
+
id: n.callId,
|
|
169
|
+
name: 'compact',
|
|
170
|
+
args: JSON.stringify({ reason: n.reason, nodes: n.covers.length }),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
messages.push({
|
|
175
|
+
role: 'tool',
|
|
176
|
+
callId: n.callId,
|
|
177
|
+
name: 'compact',
|
|
178
|
+
content: get(n.summary),
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case 'final_output':
|
|
183
|
+
{
|
|
184
|
+
const text = get(n.output);
|
|
185
|
+
const prev = messages.at(-1);
|
|
186
|
+
// An untyped run ends by simply answering, and the kernel
|
|
187
|
+
// records that answer as a node of its own — so the text is
|
|
188
|
+
// already in the projection as the assistant's last turn.
|
|
189
|
+
// Emitting it again would duplicate a whole turn in every
|
|
190
|
+
// later request of a multi-turn conversation, and in every
|
|
191
|
+
// branch that inherits one. A typed run has no such twin:
|
|
192
|
+
// its answer travelled as tool-call arguments, and the
|
|
193
|
+
// assistant message that carried them is gone.
|
|
194
|
+
if (prev?.role !== 'assistant' || prev.content !== text) {
|
|
195
|
+
messages.push({ role: 'assistant', content: text, agent: n.agent });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
// handoff / memory_op / fork project through other nodes
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { system, messages: repairToolCalls(messages) };
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Providers reject a thread whose assistant tool call has no matching tool
|
|
208
|
+
* message — and vice versa. Compaction can cover either side of a pair, so the
|
|
209
|
+
* projection drops the orphaned half instead of producing an invalid request.
|
|
210
|
+
* This is also what makes non-contiguous compaction safe, and what keeps an
|
|
211
|
+
* unanswered `fork` call out of an inheriting branch's prompt.
|
|
212
|
+
*/
|
|
213
|
+
function repairToolCalls(messages) {
|
|
214
|
+
const called = new Set(messages.flatMap((m) => m.role === 'assistant' ? (m.toolCalls ?? []).map((c) => c.id) : []));
|
|
215
|
+
const kept = messages.filter((m) => m.role !== 'tool' || called.has(m.callId));
|
|
216
|
+
const answered = new Set(kept.flatMap((m) => (m.role === 'tool' ? [m.callId] : [])));
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const m of kept) {
|
|
219
|
+
if (m.role !== 'assistant' || !m.toolCalls?.length) {
|
|
220
|
+
out.push(m);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const calls = m.toolCalls.filter((c) => answered.has(c.id));
|
|
224
|
+
if (!calls.length && !m.content) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const fixed = { ...m, toolCalls: calls.length ? calls : undefined };
|
|
228
|
+
out.push(fixed);
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=trajectory.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { Agent } from './agent.ts';
|
|
2
|
+
import type { MemoryOpSpec } from './memory.ts';
|
|
3
|
+
import type { Services } from './services.ts';
|
|
4
|
+
import type { SkillLoadSpec } from './skills.ts';
|
|
5
|
+
import type { AgentState } from './state.ts';
|
|
6
|
+
export type MediaKind = 'image' | 'audio' | 'video' | 'file';
|
|
7
|
+
export interface TextPart {
|
|
8
|
+
type: 'text';
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MediaPart {
|
|
12
|
+
type: MediaKind;
|
|
13
|
+
/** http(s) url or data: uri */
|
|
14
|
+
url: string;
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
}
|
|
17
|
+
export type ContentPart = TextPart | MediaPart;
|
|
18
|
+
/**
|
|
19
|
+
* Sugar accepted on input boundaries. A bare string inside an array is text,
|
|
20
|
+
* `{ image: url }` & co. are media shorthands.
|
|
21
|
+
*/
|
|
22
|
+
export type InputPart = string | ContentPart | {
|
|
23
|
+
image: string;
|
|
24
|
+
mimeType?: string;
|
|
25
|
+
} | {
|
|
26
|
+
audio: string;
|
|
27
|
+
mimeType?: string;
|
|
28
|
+
} | {
|
|
29
|
+
video: string;
|
|
30
|
+
mimeType?: string;
|
|
31
|
+
} | {
|
|
32
|
+
file: string;
|
|
33
|
+
mimeType?: string;
|
|
34
|
+
};
|
|
35
|
+
/** Either plain text, or an array of content parts. */
|
|
36
|
+
export type Input = string | InputPart[];
|
|
37
|
+
export declare function text(value: string): TextPart;
|
|
38
|
+
export declare function media(kind: MediaKind, url: string, mimeType?: string): MediaPart;
|
|
39
|
+
/** Normalizes any accepted input shape into a flat `ContentPart[]`. */
|
|
40
|
+
export declare function toContent(input: Input): ContentPart[];
|
|
41
|
+
export declare function contentToText(content: ContentPart[]): string;
|
|
42
|
+
export interface ToolCall {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
/** raw JSON string as produced by the model */
|
|
46
|
+
args: string;
|
|
47
|
+
/**
|
|
48
|
+
* Opaque provider state that has to be handed back verbatim when the call
|
|
49
|
+
* is replayed. Gemini 3 puts an encrypted thought signature here and
|
|
50
|
+
* rejects a function call that comes back without one; every other vendor
|
|
51
|
+
* ignores the field.
|
|
52
|
+
*
|
|
53
|
+
* It is vendor-specific and so arguably does not belong in a neutral type
|
|
54
|
+
* — but the alternative is an adapter-side cache, and the identity that
|
|
55
|
+
* would key it does not survive a handoff to an agent on a different model
|
|
56
|
+
* or a run resumed in another process. The call is the only thing that
|
|
57
|
+
* lives exactly as long as the signature is needed.
|
|
58
|
+
*/
|
|
59
|
+
signature?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface SystemMessage {
|
|
62
|
+
role: 'system';
|
|
63
|
+
content: string;
|
|
64
|
+
}
|
|
65
|
+
export interface UserMessage {
|
|
66
|
+
role: 'user';
|
|
67
|
+
content: ContentPart[];
|
|
68
|
+
}
|
|
69
|
+
export interface AssistantMessage {
|
|
70
|
+
role: 'assistant';
|
|
71
|
+
content: string;
|
|
72
|
+
toolCalls?: ToolCall[];
|
|
73
|
+
/** which agent produced it — useful after hand-offs */
|
|
74
|
+
agent?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ToolMessage {
|
|
77
|
+
role: 'tool';
|
|
78
|
+
callId: string;
|
|
79
|
+
name: string;
|
|
80
|
+
content: string;
|
|
81
|
+
isError?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export type Message = SystemMessage | UserMessage | AssistantMessage | ToolMessage;
|
|
84
|
+
export interface TokenUsage {
|
|
85
|
+
inputTokens: number;
|
|
86
|
+
/** subset of inputTokens served from cache */
|
|
87
|
+
cachedInputTokens: number;
|
|
88
|
+
outputTokens: number;
|
|
89
|
+
/** subset of outputTokens spent on thinking */
|
|
90
|
+
reasoningTokens: number;
|
|
91
|
+
}
|
|
92
|
+
export declare function zeroUsage(): TokenUsage;
|
|
93
|
+
export declare function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage;
|
|
94
|
+
export interface JsonSchema {
|
|
95
|
+
type: 'object';
|
|
96
|
+
properties?: Record<string, unknown>;
|
|
97
|
+
required?: string[];
|
|
98
|
+
additionalProperties?: boolean;
|
|
99
|
+
[key: string]: unknown;
|
|
100
|
+
}
|
|
101
|
+
export interface ToolContext<TCtx = unknown> {
|
|
102
|
+
ctx: TCtx;
|
|
103
|
+
state: AgentState;
|
|
104
|
+
agent: Agent<TCtx>;
|
|
105
|
+
/** id of the call being executed — memory ops derive their opId from it */
|
|
106
|
+
callId: string;
|
|
107
|
+
services: Services;
|
|
108
|
+
signal?: AbortSignal;
|
|
109
|
+
}
|
|
110
|
+
/** What the model needs to know about a tool — no execution details. */
|
|
111
|
+
export interface ToolSchema {
|
|
112
|
+
name: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
parameters: JsonSchema;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* What a tool produced. `effects` are structured side-effect records the kernel
|
|
118
|
+
* must fold into the trajectory (memory writes, skill activations); the tool
|
|
119
|
+
* itself performed the I/O, because the kernel stays free of it.
|
|
120
|
+
*/
|
|
121
|
+
export interface ToolOutcome {
|
|
122
|
+
output: string;
|
|
123
|
+
isError: boolean;
|
|
124
|
+
durationMs?: number;
|
|
125
|
+
effects?: ToolEffect[];
|
|
126
|
+
}
|
|
127
|
+
export type ToolEffect = {
|
|
128
|
+
kind: 'memory_op';
|
|
129
|
+
spec: MemoryOpSpec;
|
|
130
|
+
} | {
|
|
131
|
+
kind: 'skill_load';
|
|
132
|
+
spec: SkillLoadSpec;
|
|
133
|
+
};
|
|
134
|
+
declare const TOOL_RETURN: unique symbol;
|
|
135
|
+
/**
|
|
136
|
+
* What a tool returns when it needs the kernel to record something beyond the
|
|
137
|
+
* tool message itself. Tagged with a symbol so an ordinary object result can
|
|
138
|
+
* never be mistaken for one.
|
|
139
|
+
*/
|
|
140
|
+
export interface ToolReturn {
|
|
141
|
+
[TOOL_RETURN]: true;
|
|
142
|
+
output: unknown;
|
|
143
|
+
effects: ToolEffect[];
|
|
144
|
+
}
|
|
145
|
+
export declare function withEffects(output: unknown, ...effects: ToolEffect[]): ToolReturn;
|
|
146
|
+
export declare function isToolReturn(v: unknown): v is ToolReturn;
|
|
147
|
+
export interface Tool<TArgs = unknown, TCtx = unknown> extends ToolSchema {
|
|
148
|
+
/**
|
|
149
|
+
* The family this tool belongs to, so config can name the whole set at
|
|
150
|
+
* once as `<group>:*`. Purely an authoring convenience — it is never sent
|
|
151
|
+
* to the model, which sees one flat list of names either way.
|
|
152
|
+
*/
|
|
153
|
+
group?: string;
|
|
154
|
+
execute(args: TArgs, tc: ToolContext<TCtx>): unknown | Promise<unknown>;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Heterogeneous tool lists: every tool has a different `TArgs`, so there is no
|
|
158
|
+
* single type parameter that fits an array of them. `any` here is deliberate --
|
|
159
|
+
* `unknown` would make `execute` contravariant and reject concrete tools, and
|
|
160
|
+
* `never` propagates into `ToolContext<TCtx>` and breaks assignability. Arg
|
|
161
|
+
* safety is preserved at the definition site by `tool()` below.
|
|
162
|
+
*/
|
|
163
|
+
export type AnyTool<TCtx = unknown> = Tool<any, TCtx>;
|
|
164
|
+
/**
|
|
165
|
+
* Identity helper. Its only job is inference: written inline, `execute`'s
|
|
166
|
+
* `args` would be `unknown`; going through `tool<{city: string}>()` types it.
|
|
167
|
+
*/
|
|
168
|
+
export declare function tool<TArgs, TCtx = unknown>(def: Tool<TArgs, TCtx>): Tool<TArgs, TCtx>;
|
|
169
|
+
export interface ToolSelection {
|
|
170
|
+
/** the config key to name in an error, e.g. `agents.triage.tools` */
|
|
171
|
+
where: string;
|
|
172
|
+
/** how the caller should register a tool that is missing */
|
|
173
|
+
hint?: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolves selectors against the tools a host provided, in the order written
|
|
177
|
+
* and without duplicates. A selector is a tool name, `<group>:*` for every tool
|
|
178
|
+
* in a group, or `*` for everything; any of them prefixed with `-` removes
|
|
179
|
+
* what it matches from the selection so far.
|
|
180
|
+
*/
|
|
181
|
+
export declare function selectTools<TCtx>(available: AnyTool<TCtx>[], selectors: string[], opts: ToolSelection): AnyTool<TCtx>[];
|
|
182
|
+
export declare const HANDOFF_PREFIX = "transfer_to_";
|
|
183
|
+
export declare const FORK_TOOL = "fork";
|
|
184
|
+
export declare const FINAL_OUTPUT_TOOL = "final_output";
|
|
185
|
+
export declare const MEMORY_SEARCH_TOOL = "memory_search";
|
|
186
|
+
export declare const MEMORY_WRITE_TOOL = "memory_write";
|
|
187
|
+
export declare const MEMORY_UPDATE_TOOL = "memory_update";
|
|
188
|
+
export declare const MEMORY_DELETE_TOOL = "memory_delete";
|
|
189
|
+
export declare const SKILL_SEARCH_TOOL = "skill_search";
|
|
190
|
+
export declare const SKILL_LOAD_TOOL = "skill_load";
|
|
191
|
+
export declare function parseArgs(raw: string): Record<string, unknown>;
|
|
192
|
+
export declare function asString(v: unknown): string | undefined;
|
|
193
|
+
export declare function stringify(v: unknown): string;
|
|
194
|
+
export {};
|
|
195
|
+
//# sourceMappingURL=types.d.ts.map
|