@vietor/agent-core 0.7.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 +849 -0
- package/dist/create-session.d.ts +4 -0
- package/dist/create-session.js +51 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +5 -0
- package/dist/llm/anthropic.d.ts +14 -0
- package/dist/llm/anthropic.js +200 -0
- package/dist/llm/base.d.ts +10 -0
- package/dist/llm/base.js +12 -0
- package/dist/llm/client.d.ts +4 -0
- package/dist/llm/client.js +61 -0
- package/dist/llm/completions.d.ts +8 -0
- package/dist/llm/completions.js +88 -0
- package/dist/llm/messages.d.ts +50 -0
- package/dist/llm/messages.js +28 -0
- package/dist/llm/responses.d.ts +14 -0
- package/dist/llm/responses.js +130 -0
- package/dist/llm/types.d.ts +40 -0
- package/dist/llm/types.js +1 -0
- package/dist/mcp/client.d.ts +15 -0
- package/dist/mcp/client.js +53 -0
- package/dist/mcp/manager.d.ts +18 -0
- package/dist/mcp/manager.js +155 -0
- package/dist/mcp/types.d.ts +26 -0
- package/dist/mcp/types.js +1 -0
- package/dist/runtime/agent.d.ts +56 -0
- package/dist/runtime/agent.js +253 -0
- package/dist/runtime/events.d.ts +69 -0
- package/dist/runtime/events.js +1 -0
- package/dist/runtime/prompts.d.ts +5 -0
- package/dist/runtime/prompts.js +45 -0
- package/dist/runtime/session-messages.d.ts +43 -0
- package/dist/runtime/session-messages.js +174 -0
- package/dist/runtime/session.d.ts +102 -0
- package/dist/runtime/session.js +375 -0
- package/dist/runtime/sub-agent-runner.d.ts +18 -0
- package/dist/runtime/sub-agent-runner.js +26 -0
- package/dist/runtime/timeline.d.ts +21 -0
- package/dist/runtime/timeline.js +146 -0
- package/dist/runtime/todo-store.d.ts +8 -0
- package/dist/runtime/todo-store.js +15 -0
- package/dist/skills/loader.d.ts +6 -0
- package/dist/skills/loader.js +43 -0
- package/dist/tools/ask-user.d.ts +3 -0
- package/dist/tools/ask-user.js +26 -0
- package/dist/tools/file-edit.d.ts +2 -0
- package/dist/tools/file-edit.js +43 -0
- package/dist/tools/file-read.d.ts +2 -0
- package/dist/tools/file-read.js +93 -0
- package/dist/tools/file-write.d.ts +2 -0
- package/dist/tools/file-write.js +25 -0
- package/dist/tools/glob.d.ts +2 -0
- package/dist/tools/glob.js +31 -0
- package/dist/tools/grep.d.ts +2 -0
- package/dist/tools/grep.js +67 -0
- package/dist/tools/registry.d.ts +30 -0
- package/dist/tools/registry.js +107 -0
- package/dist/tools/shell.d.ts +2 -0
- package/dist/tools/shell.js +57 -0
- package/dist/tools/skill.d.ts +3 -0
- package/dist/tools/skill.js +30 -0
- package/dist/tools/sub-agent.d.ts +7 -0
- package/dist/tools/sub-agent.js +81 -0
- package/dist/tools/todo-write.d.ts +3 -0
- package/dist/tools/todo-write.js +72 -0
- package/dist/tools/types.d.ts +32 -0
- package/dist/tools/types.js +4 -0
- package/dist/tools/web-fetch.d.ts +2 -0
- package/dist/tools/web-fetch.js +104 -0
- package/dist/util/async.d.ts +19 -0
- package/dist/util/async.js +93 -0
- package/dist/util/constants.d.ts +25 -0
- package/dist/util/constants.js +27 -0
- package/dist/util/emitter.d.ts +5 -0
- package/dist/util/emitter.js +15 -0
- package/dist/util/file.d.ts +3 -0
- package/dist/util/file.js +19 -0
- package/dist/util/html.d.ts +1 -0
- package/dist/util/html.js +14 -0
- package/dist/util/index.d.ts +7 -0
- package/dist/util/index.js +7 -0
- package/dist/util/net.d.ts +1 -0
- package/dist/util/net.js +31 -0
- package/dist/util/ripgrep.d.ts +10 -0
- package/dist/util/ripgrep.js +34 -0
- package/dist/util/subprocess.d.ts +15 -0
- package/dist/util/subprocess.js +113 -0
- package/dist/util/text.d.ts +15 -0
- package/dist/util/text.js +72 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
# @vietor/agent-core
|
|
2
|
+
|
|
3
|
+
> Lightweight AI agent framework — session orchestration, tool system, MCP client, skill loader.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @vietor/agent-core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Requires Node.js ≥ 22 (ESM only).
|
|
10
|
+
|
|
11
|
+
## Import paths
|
|
12
|
+
|
|
13
|
+
The package exposes two entry points:
|
|
14
|
+
|
|
15
|
+
| Import | Contents |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `@vietor/agent-core` | `createSession`, the `Session` API, tools, skills, MCP, LLM config, shared types |
|
|
18
|
+
| `@vietor/agent-core/util` | Framework utilities: async helpers, text formatting, subprocess, file/HTML/net helpers, constants |
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createSession, type Tool } from "@vietor/agent-core";
|
|
22
|
+
import { runProcess, formatDuration } from "@vietor/agent-core/util";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createSession, tryLoadSkills } from "@vietor/agent-core";
|
|
31
|
+
|
|
32
|
+
const session = await createSession({
|
|
33
|
+
systemPrompt: "You are a helpful assistant.",
|
|
34
|
+
llm: {
|
|
35
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
36
|
+
apiKey: "your-api-key",
|
|
37
|
+
model: "deepseek-v4-flash",
|
|
38
|
+
thinkingEffort: "high",
|
|
39
|
+
backend: "completions",
|
|
40
|
+
maxInputTokens: 1_000_000,
|
|
41
|
+
},
|
|
42
|
+
mcpServers: {
|
|
43
|
+
filesystem: { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "."] },
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
session.onEvent((e) => {
|
|
48
|
+
if (e.type === "assistant_delta") process.stdout.write(e.text);
|
|
49
|
+
else if (e.type === "run_metrics")
|
|
50
|
+
console.log(`tokens: ${e.inputTokens} prompt / ${e.outputTokens} completion`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const result = await session.prompt("What files are in the current directory?");
|
|
54
|
+
console.log(result.reply); // final assistant reply
|
|
55
|
+
|
|
56
|
+
console.log(session.getSnapshot().timeline); // full session timeline
|
|
57
|
+
console.log(session.export()); // LLM message history
|
|
58
|
+
session.dispose();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
# API Reference — `@vietor/agent-core`
|
|
64
|
+
|
|
65
|
+
## `createSession`
|
|
66
|
+
|
|
67
|
+
**`createSession(options: SessionOptions): Promise<Session>`**
|
|
68
|
+
|
|
69
|
+
Factory that wires together the LLM client, tool registry, MCP servers, and skills into a ready-to-use `Session` instance. Connects the MCP servers listed in `mcpServers` before resolving.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { createSession } from "@vietor/agent-core";
|
|
73
|
+
|
|
74
|
+
const session = await createSession({
|
|
75
|
+
systemPrompt: "You are a helpful assistant.",
|
|
76
|
+
llm: {
|
|
77
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
78
|
+
apiKey: "your-api-key",
|
|
79
|
+
model: "deepseek-v4-flash",
|
|
80
|
+
thinkingEffort: "high",
|
|
81
|
+
backend: "completions",
|
|
82
|
+
maxInputTokens: 1_000_000,
|
|
83
|
+
},
|
|
84
|
+
tools: [myCustomTool],
|
|
85
|
+
skills: tryLoadSkills("./skills") ?? [],
|
|
86
|
+
mcpServers: {
|
|
87
|
+
filesystem: { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "."] },
|
|
88
|
+
},
|
|
89
|
+
builtInTools: { askUser: true },
|
|
90
|
+
clientInfo: { name: "my-app", version: "1.0.0" },
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## `SessionOptions`
|
|
95
|
+
|
|
96
|
+
| Property | Type | Default | Description |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `systemPrompt` | `string` | *(required)* | System prompt for the LLM. |
|
|
99
|
+
| `llm` | `LLMConfig` | *(required)* | LLM endpoint config (OpenAI-compatible or Anthropic; see `backend`). Only `baseUrl`, `apiKey`, and `model` are required; `thinkingEffort`, `backend`, `maxInputTokens`, and `maxOutputTokens` default to `"high"`, `"completions"`, `1_000_000`, and `128_000`. |
|
|
100
|
+
| `cwd` | `string` | `process.cwd()` | Working directory used by tools (e.g. path-based tools). |
|
|
101
|
+
| `tools` | `Tool[]` | `undefined` | Additional tools registered alongside built-ins. |
|
|
102
|
+
| `skills` | `Skill[]` | `undefined` | Skills loaded from SKILL.md files; invoked via the built-in Skill tool or via `session.runSkill()` (hosts may map them to slash commands). |
|
|
103
|
+
| `mcpServers` | `Record<string, MCPServerConfig>` | `undefined` | MCP servers to connect on startup. |
|
|
104
|
+
| `builtInTools` | `BuiltinToolsOptions \| false` | *(7 core tools enabled; interactive tools off)* | `readOnly: true` registers only the read-only core tools (FileRead/Glob/Grep/WebFetch); `askUser`/`todoWrite`/`subAgent` enable interactive tools (all off by default); `false` to disable all built-in tools. |
|
|
105
|
+
| `clientInfo` | `{ name: string; version: string }` | `{ name: "agent-core", version: "0.0.0" }` | Client identity sent to MCP servers. |
|
|
106
|
+
| `sessionId` | `string` | `randomUUID()` | Unique session identifier. |
|
|
107
|
+
| `maxTurns` | `number` | `50` | Maximum agent turns (LLM calls with tool calls) per prompt before the run errors out. |
|
|
108
|
+
| `stallThreshold` | `number` | `3` | Stall tolerance: consecutive identical tool-call sets, or consecutive text-only responses while todos are incomplete, before the run is treated as stalled. |
|
|
109
|
+
|
|
110
|
+
The auto-compaction threshold is not configurable — it's derived internally as 75% of `llm.maxInputTokens` and exposed via `session.contextLimit`.
|
|
111
|
+
|
|
112
|
+
## `SYSTEM_PROMPT_BOUNDARY`
|
|
113
|
+
|
|
114
|
+
**`SYSTEM_PROMPT_BOUNDARY: string`**
|
|
115
|
+
|
|
116
|
+
A constant separator that `createSession` appends between the user-provided `systemPrompt` and the auto-generated tool-use/behavior guidelines. Also exported so callers can use it when composing their own system prompt from multiple segments:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { SYSTEM_PROMPT_BOUNDARY } from "@vietor/agent-core";
|
|
120
|
+
|
|
121
|
+
const systemPrompt = [
|
|
122
|
+
coreInstructions,
|
|
123
|
+
contextRules,
|
|
124
|
+
].join(SYSTEM_PROMPT_BOUNDARY);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## `Session`
|
|
128
|
+
|
|
129
|
+
The main session object. Create one via `createSession()` — it wires the LLM client, tool registry, and MCP servers, and connects the MCP servers listed in `mcpServers`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const session = await createSession({ systemPrompt, llm });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Running prompts
|
|
136
|
+
|
|
137
|
+
| Method | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `prompt(text: string): Promise<PromptResult>` | Submit a user message and run the agent loop (LLM → tool calls → LLM) until a final answer or error. Returns a `PromptResult` with the run `status` and the final assistant `reply`. |
|
|
140
|
+
|
|
141
|
+
### Managing conversation
|
|
142
|
+
|
|
143
|
+
| Method | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `clear(): void` | Reset the conversation and log. |
|
|
146
|
+
| `importState(state: SessionState): void` | Replace conversation messages and todos from a previously exported `SessionState`, rebuilding the timeline. Throws `SessionBusyError` if a run is in progress. |
|
|
147
|
+
| `export(): SessionMessage[]` | Return all session messages (excluding the system prompt). |
|
|
148
|
+
| `exportState(): SessionState` | Return the full session state (`{ messages, todos }`) for the host to persist; `export()` returns only messages. |
|
|
149
|
+
| `compact(): Promise<RunStatus>` | Ask the LLM to summarize the conversation so far, replacing history with a single summary message. Runs through the run loop — streams the summary and can be aborted via `abort()`. |
|
|
150
|
+
| `abort(): void` | Abort the current prompt or compact, cancel pending tool calls, and dismiss unanswered user questions. |
|
|
151
|
+
| `submitAnswer(id: string, answer: string): void` | Supply an answer to a pending user question (from the built-in AskUser tool). |
|
|
152
|
+
| `pendingQuestion: Extract<TimelineEvent, { type: "question" }> \| undefined` | *(getter)* The most recent unanswered question, or `undefined` if none are pending. |
|
|
153
|
+
|
|
154
|
+
### Events
|
|
155
|
+
|
|
156
|
+
| Method | Description |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `onEvent(listener: (e: SessionEvent) => void): () => void` | Subscribe to structured incremental events (streaming deltas, tool calls, errors, questions, run state). Supports multiple listeners; returns an unsubscribe function. |
|
|
159
|
+
|
|
160
|
+
#### `SessionEvent`
|
|
161
|
+
|
|
162
|
+
A discriminated union emitted as the session runs — the union of the standalone `TimelineEvent` and `StreamEvent` types:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
type SessionEvent = TimelineEvent | StreamEvent;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Timeline events are also stored in `session.getSnapshot().timeline`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
type TimelineEvent =
|
|
172
|
+
| { type: "user"; text: string }
|
|
173
|
+
| { type: "skill"; name: string }
|
|
174
|
+
| { type: "assistant"; text: string }
|
|
175
|
+
| { type: "tool"; id: string; name: string; argsSummary: string; result: string | null; isError?: boolean; resultSummary?: string }
|
|
176
|
+
| { type: "retry"; attempt: number; max: number; reason: string }
|
|
177
|
+
| { type: "error"; text: string }
|
|
178
|
+
| { type: "interrupted" }
|
|
179
|
+
| { type: "question"; id: string; text: string; options: string[]; answer: string | null }
|
|
180
|
+
| { type: "notice"; text: string };
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Streaming events are transient — never stored in the timeline, delivered only to `onEvent` listeners:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
type StreamEvent =
|
|
187
|
+
| { type: "assistant_delta"; text: string }
|
|
188
|
+
| { type: "thinking_delta"; text: string }
|
|
189
|
+
| { type: "thinking_cleared" }
|
|
190
|
+
| { type: "tool_start"; id: string; name: string; argsSummary: string }
|
|
191
|
+
| { type: "tool_end"; id: string; result: string; isError?: boolean; resultSummary?: string }
|
|
192
|
+
| ({ type: "run_metrics" } & RunMetrics);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
| Type | Emitted when | In timeline |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `user` | User submits a prompt (`prompt`). | ✓ |
|
|
198
|
+
| `skill` | A skill is invoked. | ✓ |
|
|
199
|
+
| `assistant_delta` | A streaming token delta from the LLM. | — |
|
|
200
|
+
| `thinking_delta` | A streaming thinking token delta (extended thinking). | — |
|
|
201
|
+
| `thinking_cleared` | The accumulated thinking text is cleared (e.g. on new tool round). | — |
|
|
202
|
+
| `assistant` | A text response segment is flushed (on tool call or completion). | ✓ |
|
|
203
|
+
| `tool_start` | A tool call starts. | ✓ (stored as `tool`) |
|
|
204
|
+
| `tool_end` | A tool call finishes. | — (merged into its `tool` entry) |
|
|
205
|
+
| `retry` | The LLM client retries after a transient API error. | ✓ |
|
|
206
|
+
| `error` | An error occurred. | ✓ |
|
|
207
|
+
| `interrupted` | The current run was aborted. | ✓ |
|
|
208
|
+
| `question` | The AskUser tool poses a question. | ✓ |
|
|
209
|
+
| `notice` | `session.addNotice()` is called, or the run auto-compacts context. | ✓ |
|
|
210
|
+
| `run_metrics` | Run metrics change: at run start, every second, and at run end (`running: false`). | — |
|
|
211
|
+
|
|
212
|
+
Note: `onEvent` is the primary stream for network/remote consumers (multi-subscriber, incremental). For local React `useSyncExternalStore` view invalidation use `subscribe` + `getSnapshot`.
|
|
213
|
+
|
|
214
|
+
#### `RunMetrics`
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
interface RunMetrics {
|
|
218
|
+
running: boolean; // whether a prompt is in progress
|
|
219
|
+
elapsed: number; // seconds since the current prompt started
|
|
220
|
+
thinkingElapsed: number; // seconds before the first assistant text token (incl. thinking/tools)
|
|
221
|
+
replyElapsed: number; // seconds after the first assistant text token (incl. later tool rounds)
|
|
222
|
+
inputTokens: number; // cumulative input (prompt) tokens for the current run
|
|
223
|
+
outputTokens: number; // cumulative output (completion) tokens for the current run
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`INITIAL_RUN_METRICS: RunMetrics` is the all-zero, not-running initial value.
|
|
228
|
+
|
|
229
|
+
### Skills & notices
|
|
230
|
+
|
|
231
|
+
The command system lives in host code. Core exposes the primitives hosts build on:
|
|
232
|
+
|
|
233
|
+
| Method | Description |
|
|
234
|
+
|---|---|
|
|
235
|
+
| `runSkill(name: string): Promise<boolean>` | Run a skill by name through the agent loop. Returns `false` (no error emitted) if the name is unknown; throws `SessionBusyError` if a run is in progress. |
|
|
236
|
+
| `addNotice(text: string): void` | Append a notice entry to the timeline and emit a `notice` event. |
|
|
237
|
+
| `addError(text: string): void` | Append an error entry to the timeline and emit an `error` event. |
|
|
238
|
+
| `skills: readonly Skill[]` | *(getter)* All loaded skills. |
|
|
239
|
+
|
|
240
|
+
### State accessors
|
|
241
|
+
|
|
242
|
+
| Property | Type | Description |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `model` | `string` | The LLM model name (e.g. `"deepseek-v4-flash"`). |
|
|
245
|
+
| `thinkingEffort` | `"high" \| "max"` | The configured thinking effort. |
|
|
246
|
+
| `contextLimit` | `number` | Estimated-token threshold that triggers auto-compaction. |
|
|
247
|
+
| `contextTokens` | `number` | Estimated token count of the current conversation. |
|
|
248
|
+
| `mcpServers` | `readonly MCPServerInfo[]` | Status and tool list of connected MCP servers. |
|
|
249
|
+
| `cwd` | `string` | The resolved working directory used by tools. |
|
|
250
|
+
| `sessionId` | `string` | The unique session identifier. |
|
|
251
|
+
| `running` | `boolean` | Whether a prompt/compact is in progress. Check before issuing a driver call (see Reentrancy). |
|
|
252
|
+
|
|
253
|
+
### Reentrancy
|
|
254
|
+
|
|
255
|
+
A `Session` runs one prompt/compact at a time. While a run is in progress, calling a **driver** method throws `SessionBusyError` so a host can map it to an HTTP 409:
|
|
256
|
+
|
|
257
|
+
| Driver method | Behavior when busy |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `prompt`, `compact`, `runSkill`, `clear`, `importState` | Throws `SessionBusyError`. |
|
|
260
|
+
|
|
261
|
+
These remain callable during a run (they are inputs to the running loop, or read-only/teardown):
|
|
262
|
+
|
|
263
|
+
| Method | Behavior when busy |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `abort`, `submitAnswer` | Allowed - control the running loop. |
|
|
266
|
+
| `onEvent`, `subscribe`, `getSnapshot`, `pendingQuestion`, `export`, `exportState`, `dispose`, accessors | Allowed. |
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { SessionBusyError } from "@vietor/agent-core";
|
|
270
|
+
|
|
271
|
+
if (!session.running) {
|
|
272
|
+
try {
|
|
273
|
+
await session.prompt(text);
|
|
274
|
+
} catch (e) {
|
|
275
|
+
if (e instanceof SessionBusyError) /* -> HTTP 409 */;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The `run_metrics` event (`running: boolean`) also signals run start/end for stream consumers.
|
|
281
|
+
|
|
282
|
+
### Snapshot subscription
|
|
283
|
+
|
|
284
|
+
| Method | Description |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `subscribe(listener: () => void): () => void` | Subscribe to timeline or todo changes; returns an unsubscribe function. |
|
|
287
|
+
| `getSnapshot(): SessionView` | Current session snapshot (`{ timeline, todos }`); the reference stays stable until the next change. Designed for `useSyncExternalStore`. |
|
|
288
|
+
|
|
289
|
+
### Cleanup
|
|
290
|
+
|
|
291
|
+
| Method | Description |
|
|
292
|
+
|---|---|
|
|
293
|
+
| `dispose(): void` | Kill all MCP server processes and clean up. |
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Types
|
|
298
|
+
|
|
299
|
+
### `SessionView`
|
|
300
|
+
|
|
301
|
+
The snapshot returned by `session.getSnapshot()`.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
interface SessionView {
|
|
305
|
+
timeline: readonly TimelineEvent[];
|
|
306
|
+
todos: readonly Todo[];
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### `PromptResult`
|
|
311
|
+
|
|
312
|
+
Returned by `session.prompt()`.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
interface PromptResult {
|
|
316
|
+
status: RunStatus;
|
|
317
|
+
reply: string;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`status` indicates how the run ended; `reply` is the final assistant text (may be partial or empty when `status !== "ok"`). Error details are delivered via the `error` event; subscribe to `onEvent` for the full picture.
|
|
322
|
+
|
|
323
|
+
### `RunStatus`
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
type RunStatus = "ok" | "aborted" | "error" | "stalled" | "maxTurns";
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| Status | Meaning |
|
|
330
|
+
|---|---|
|
|
331
|
+
| `ok` | The run completed with a final assistant reply. |
|
|
332
|
+
| `aborted` | The run was aborted via `abort()`. |
|
|
333
|
+
| `error` | The run ended due to an LLM/API error. |
|
|
334
|
+
| `stalled` | The agent stalled past `stallThreshold`: repeated identical tool calls, or repeated text-only responses while todos are incomplete. |
|
|
335
|
+
| `maxTurns` | The agent exceeded `maxTurns`. |
|
|
336
|
+
|
|
337
|
+
Also returned by `session.compact()` (`"ok"` on success, `"aborted"` if aborted, `"error"` on failure).
|
|
338
|
+
|
|
339
|
+
### `Timeline`
|
|
340
|
+
|
|
341
|
+
`SessionView.timeline` is `readonly TimelineEvent[]` — a standalone union of nine entry types (`user`, `skill`, `assistant`, `tool`, `retry`, `error`, `interrupted`, `question`, `notice`), defined independently of the event stream. Transient stream events (`assistant_delta`, `thinking_delta`, `thinking_cleared`, `tool_start`/`tool_end`, `run_metrics`) are never stored; `tool_start` opens a `tool` entry with `result: null` and `tool_end` is merged into it.
|
|
342
|
+
|
|
343
|
+
`tool` and `question` entries carry lifecycle state as pending fields, set `null` while outstanding and replaced on completion:
|
|
344
|
+
|
|
345
|
+
| Field | Meaning |
|
|
346
|
+
|---|---|
|
|
347
|
+
| `result: string \| null` (`tool`) | `null` while the tool is running; the result text once `tool_end` arrives, or `"aborted"` if the run was interrupted. |
|
|
348
|
+
| `isError?: boolean` / `resultSummary?: string` (`tool`) | Set when the tool ended with an error / a condensed summary of the result. |
|
|
349
|
+
| `answer: string \| null` (`question`) | `null` until the user answers (via `submitAnswer` or `abort`). |
|
|
350
|
+
|
|
351
|
+
### `SessionMessage`
|
|
352
|
+
|
|
353
|
+
The internal message format exchanged with the agent, also returned by `session.export()`.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
type SessionMessage =
|
|
357
|
+
| { role: "system"; content: string }
|
|
358
|
+
| { role: "user"; content: string }
|
|
359
|
+
| { role: "skill"; name: string; content: string }
|
|
360
|
+
| LLMAssistantMessage
|
|
361
|
+
| { role: "tool"; tool_call_id: string; content: string; resultSummary?: string; isError?: boolean };
|
|
362
|
+
|
|
363
|
+
// LLMAssistantMessage includes optional tool_calls[] for function-calling
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### `LLMConfig`
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
interface LLMConfig {
|
|
370
|
+
baseUrl: string; // API endpoint (e.g. "https://api.deepseek.com/v1" or "https://api.anthropic.com") — required
|
|
371
|
+
apiKey: string; // API key — required
|
|
372
|
+
model: string; // Model name (e.g. "deepseek-v4-flash" or "claude-sonnet-5") — required
|
|
373
|
+
thinkingEffort?: LLMThinkingEffort; // Thinking depth; "high" for standard tasks, "max" for deeper thinking on complex tasks (default: "high")
|
|
374
|
+
backend?: LLMBackend; // Wire protocol; "completions" (OpenAI Chat Completions), "anthropic" (Anthropic Messages API via the official SDK), or "responses" (OpenAI Responses API via the official SDK) (default: "completions")
|
|
375
|
+
maxInputTokens?: number; // Context window in tokens; 75% of it is used as the auto-compaction threshold (default: 1,000,000)
|
|
376
|
+
maxOutputTokens?: number; // Max output tokens per request, capped by the model's output limit (default: 128,000)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
type LLMThinkingEffort = "high" | "max";
|
|
380
|
+
|
|
381
|
+
type LLMBackend = "completions" | "anthropic" | "responses";
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Both aliases are exported so hosts can reference them in their own config types.
|
|
385
|
+
|
|
386
|
+
`backend` selects the request/response protocol the client speaks:
|
|
387
|
+
|
|
388
|
+
- `"completions"` - OpenAI Chat Completions compatible endpoint. `thinkingEffort` is sent as `reasoning_effort`; `maxOutputTokens` is sent as `max_tokens`.
|
|
389
|
+
- `"anthropic"` - Anthropic Messages API (via `@anthropic-ai/sdk`). Point `baseUrl` at an Anthropic-compatible endpoint and `model` at a Claude model. `maxOutputTokens` is sent as `max_tokens`; `thinkingEffort` enables extended thinking (`"high"` = 16k token budget, `"max"` = 32k, both capped by `maxOutputTokens`); thinking blocks are preserved across tool-use turns as required by the API.
|
|
390
|
+
- `"responses"` - OpenAI Responses API (via `openai` SDK). Tool results round-trip as `function_call`/`function_call_output` items; `maxOutputTokens` is sent as `max_output_tokens`; `thinkingEffort` is sent as `reasoning.effort`, and reasoning summaries are streamed via `reasoning.summary_text`.
|
|
391
|
+
|
|
392
|
+
### `SessionState`
|
|
393
|
+
|
|
394
|
+
The payload exchanged with a host's persistence layer — the complete state to save and the input to restore:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
interface SessionState {
|
|
398
|
+
messages: SessionMessage[];
|
|
399
|
+
todos: Todo[];
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Core has no storage backend and never saves on its own. The host calls `session.exportState()` to snapshot the current state (e.g. after each prompt) and `session.importState(state)` to restore one — import is synchronous and rebuilds the timeline from the messages. Storage concerns (serialization, file layout, write serialization) are entirely the host's; `sessionId` is the suggested storage key.
|
|
404
|
+
|
|
405
|
+
### `Todo`
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
interface Todo {
|
|
409
|
+
content: string;
|
|
410
|
+
status: TodoStatus;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
type TodoStatus = "pending" | "inProgress" | "completed";
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Tool System
|
|
419
|
+
|
|
420
|
+
### `Tool`
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
interface Tool {
|
|
424
|
+
name: string;
|
|
425
|
+
readOnly?: boolean;
|
|
426
|
+
description: string;
|
|
427
|
+
parameters: Record<string, unknown>; // JSON Schema object
|
|
428
|
+
summaryKeys?: string[]; // parameter keys used for display summary
|
|
429
|
+
summarizeArgs?: (args: Record<string, unknown>) => string; // custom summary function
|
|
430
|
+
summarizeResult?(result: TextResult): string; // result summary for timeline display
|
|
431
|
+
execute(args: Record<string, unknown>, ctx: ToolContext): Promise<TextResult>;
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- `readOnly` (optional) marks the tool as read-only. Read-only tools are what `builtInTools: { readOnly: true }` registers, and what the SubAgent tool equips sub-agents with.
|
|
436
|
+
- `parameters` is passed to the LLM as a JSON Schema to describe the tool's arguments.
|
|
437
|
+
- When the LLM calls a tool, `execute` receives the parsed arguments and a context object.
|
|
438
|
+
- `execute` returns a `TextResult` (`{ content, isError? }`). Expected failures return `toolError(...)` (exported from the package); unexpected errors may throw and are wrapped by the registry.
|
|
439
|
+
- `summaryKeys` / `summarizeArgs` control what appears in the tool log entry's `argsSummary` field.
|
|
440
|
+
- `summarizeResult` (optional) returns a short result summary for timeline display. Called after execution with the result; the registry prefixes the wall-clock duration. Falls back to a default summary (byte/line count) when not defined.
|
|
441
|
+
|
|
442
|
+
### `ToolContext`
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
interface ToolContext {
|
|
446
|
+
signal?: AbortSignal; // abort signal for the current run
|
|
447
|
+
cwd: string; // resolved working directory for path-based tools
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### `TextResult`
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
interface TextResult {
|
|
455
|
+
content: string;
|
|
456
|
+
isError?: boolean;
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### `ToolSchema`
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
interface ToolSchema {
|
|
464
|
+
type: "function";
|
|
465
|
+
function: {
|
|
466
|
+
name: string;
|
|
467
|
+
description: string;
|
|
468
|
+
parameters: Record<string, unknown>;
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The format sent to the LLM's `tools` parameter. Generated automatically from registered `Tool` objects.
|
|
474
|
+
|
|
475
|
+
### Built-in tools
|
|
476
|
+
|
|
477
|
+
Core tools (registered by default; `builtInTools: { readOnly: true }` registers only the read-only subset):
|
|
478
|
+
|
|
479
|
+
| Tool | Description |
|
|
480
|
+
|---|---|
|
|
481
|
+
| **FileRead** *(read-only)* | Read files with line numbers. |
|
|
482
|
+
| **Glob** *(read-only)* | File listing by glob pattern. |
|
|
483
|
+
| **Grep** *(read-only)* | Content search with regex. |
|
|
484
|
+
| **WebFetch** *(read-only)* | General-purpose HTTP GET — converts HTML to markdown, returns JSON/XML/text raw. Retries transient failures (network, timeouts, 408/429/5xx) up to 3 attempts. |
|
|
485
|
+
| **Shell** | Run shell commands. |
|
|
486
|
+
| **FileWrite** | Create or overwrite files. |
|
|
487
|
+
| **FileEdit** | Surgical text replacement. |
|
|
488
|
+
|
|
489
|
+
Interactive tools are **off by default** and registered only when explicitly enabled via `builtInTools` (`askUser: true`, `todoWrite: true`, `subAgent: true`):
|
|
490
|
+
|
|
491
|
+
| Tool | Description |
|
|
492
|
+
|---|---|
|
|
493
|
+
| **AskUser** | Ask the user a question and wait for the answer. |
|
|
494
|
+
| **TodoWrite** | Track multi-step task progress; the agent must complete every task before its final reply. |
|
|
495
|
+
| **Skill** | Invoke a skill by name; loads its instructions into context. Registered automatically whenever `skills` are provided. |
|
|
496
|
+
| **SubAgent** | Run a nested sub-agent: read-only "explore" investigation or "plan" implementation planning. Sub-agents are equipped with the session's read-only tools (FileRead/Glob/Grep/WebFetch, plus any custom tools marked `readOnly`). |
|
|
497
|
+
|
|
498
|
+
`builtInTools: false` disables all built-in tools.
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
const session = await createSession({
|
|
502
|
+
systemPrompt: "...",
|
|
503
|
+
llm: { ... },
|
|
504
|
+
builtInTools: { askUser: true, todoWrite: true },
|
|
505
|
+
});
|
|
506
|
+
// Disable all built-in tools:
|
|
507
|
+
// builtInTools: false
|
|
508
|
+
// Read-only session (no Shell / FileWrite / FileEdit):
|
|
509
|
+
// builtInTools: { readOnly: true }
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### Custom tools example
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
import type { Tool } from "@vietor/agent-core";
|
|
516
|
+
|
|
517
|
+
const greetTool: Tool = {
|
|
518
|
+
name: "greet",
|
|
519
|
+
description: "Greet someone by name",
|
|
520
|
+
parameters: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: { name: { type: "string" } },
|
|
523
|
+
required: ["name"],
|
|
524
|
+
},
|
|
525
|
+
async execute(args) {
|
|
526
|
+
return `Hello, ${args.name as string}!`;
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const session = await createSession({
|
|
531
|
+
systemPrompt: "...",
|
|
532
|
+
llm: { ... },
|
|
533
|
+
tools: [greetTool],
|
|
534
|
+
});
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## Skill System
|
|
540
|
+
|
|
541
|
+
### `Skill`
|
|
542
|
+
|
|
543
|
+
```ts
|
|
544
|
+
interface Skill {
|
|
545
|
+
name: string;
|
|
546
|
+
description?: string;
|
|
547
|
+
prompt: string;
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Skills are loaded from directories containing a `SKILL.md` file. They are listed in the system prompt so the agent can invoke them via the built-in **Skill** tool, and can also be run directly by hosts via `session.runSkill(name)` (e.g. mapped to slash commands).
|
|
552
|
+
|
|
553
|
+
### `tryLoadSkills`
|
|
554
|
+
|
|
555
|
+
**`tryLoadSkills(path: string): Skill[] | undefined`**
|
|
556
|
+
|
|
557
|
+
Load skills from a directory. Each subdirectory containing a `SKILL.md` file becomes one skill. Returns `undefined` if the directory doesn't exist or contains no valid skills.
|
|
558
|
+
|
|
559
|
+
SKILL.md supports front matter:
|
|
560
|
+
```markdown
|
|
561
|
+
---
|
|
562
|
+
name: my-skill
|
|
563
|
+
description: Does something useful
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
Your skill prompt here.
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
If no `name` is set in front matter, the directory name is used.
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
const skills = tryLoadSkills("./my-skills") ?? [];
|
|
573
|
+
|
|
574
|
+
const session = await createSession({
|
|
575
|
+
systemPrompt: "...",
|
|
576
|
+
llm: { ... },
|
|
577
|
+
skills,
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## MCP (Model Context Protocol)
|
|
584
|
+
|
|
585
|
+
### `MCPServerConfig`
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
type MCPServerConfig = StdioServerConfig | HttpServerConfig;
|
|
589
|
+
|
|
590
|
+
interface StdioServerConfig {
|
|
591
|
+
type: "stdio";
|
|
592
|
+
command: string;
|
|
593
|
+
args?: string[];
|
|
594
|
+
env?: Record<string, string>;
|
|
595
|
+
enabled?: boolean; // set false to skip this server
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
interface HttpServerConfig {
|
|
599
|
+
type: "http";
|
|
600
|
+
url: string;
|
|
601
|
+
headers?: Record<string, string>;
|
|
602
|
+
enabled?: boolean;
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### `MCPServerInfo`
|
|
607
|
+
|
|
608
|
+
Returned by `session.mcpServers`.
|
|
609
|
+
|
|
610
|
+
```ts
|
|
611
|
+
interface MCPServerInfo {
|
|
612
|
+
name: string;
|
|
613
|
+
type: "stdio" | "http";
|
|
614
|
+
status: "pending" | "connected" | "failed" | "disabled";
|
|
615
|
+
tools: string[];
|
|
616
|
+
error?: string; // connection error message when status is "failed"
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
MCP tools are exposed to the LLM with the prefixed name `MCP__<server>__<tool>`. Connection timeout is 30 seconds per server.
|
|
621
|
+
|
|
622
|
+
### `session.connectMCP`
|
|
623
|
+
|
|
624
|
+
**`connectMCP(servers: Record<string, MCPServerConfig>): Promise<void>`**
|
|
625
|
+
|
|
626
|
+
Connect additional MCP servers after session creation. Called internally by `createSession` when `mcpServers` is set; usable by hosts to add servers at runtime.
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
# API Reference — `@vietor/agent-core/util`
|
|
631
|
+
|
|
632
|
+
All utilities below are exported from the `@vietor/agent-core/util` subpath.
|
|
633
|
+
|
|
634
|
+
## Async helpers
|
|
635
|
+
|
|
636
|
+
### `withRetry`
|
|
637
|
+
|
|
638
|
+
**`withRetry<T>(fn: () => Promise<T>, opts: RetryOptions): Promise<T>`**
|
|
639
|
+
|
|
640
|
+
Run `fn` with retry. On a retryable error, waits `backoff(attempt)` (abortable via `opts.signal`) and retries, up to `opts.retries` retries; the last error is rethrown.
|
|
641
|
+
|
|
642
|
+
```ts
|
|
643
|
+
interface RetryOptions {
|
|
644
|
+
retries: number; // additional attempts after the first failure
|
|
645
|
+
retryable: (e: unknown) => boolean; // decide whether an error warrants a retry
|
|
646
|
+
backoff: (attempt: number) => number; // delay in ms before retry attempt
|
|
647
|
+
onRetry?: (attempt: number, max: number, error: unknown) => void;
|
|
648
|
+
signal?: AbortSignal;
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### `withTimeout`
|
|
653
|
+
|
|
654
|
+
**`withTimeout<T>(p: Promise<T>, ms: number): Promise<T>`**
|
|
655
|
+
|
|
656
|
+
Reject the promise with `Error("timeout after Nms")` if it doesn't settle within `ms`.
|
|
657
|
+
|
|
658
|
+
### `withTimeoutFn`
|
|
659
|
+
|
|
660
|
+
**`withTimeoutFn<T>(fn: (signal: AbortSignal) => Promise<T>, ms: number, signal: AbortSignal | undefined, timeoutMessage: string, otherError?: (e: unknown) => unknown): Promise<T>`**
|
|
661
|
+
|
|
662
|
+
The function variant of `withTimeout`: calls `fn` with a signal that aborts on timeout or external abort. On timeout throws `Error(timeoutMessage)`; `otherError` optionally remaps other rejections.
|
|
663
|
+
|
|
664
|
+
### `withAbort`
|
|
665
|
+
|
|
666
|
+
**`withAbort<T>(promise: Promise<T>, signal?: AbortSignal, onAbort?: () => T): Promise<T>`**
|
|
667
|
+
|
|
668
|
+
Race `promise` against `signal`. On abort, resolves `onAbort()` if provided, otherwise rejects with `AbortedError`. Pass no `signal` to return the promise untouched.
|
|
669
|
+
|
|
670
|
+
### `AbortedError`
|
|
671
|
+
|
|
672
|
+
**`class AbortedError extends Error`**
|
|
673
|
+
|
|
674
|
+
Thrown by `withAbort` (and abortable retry sleeps) when an operation is aborted.
|
|
675
|
+
|
|
676
|
+
### `isAbortError`
|
|
677
|
+
|
|
678
|
+
**`isAbortError(e: unknown): boolean`**
|
|
679
|
+
|
|
680
|
+
`true` for `AbortedError`, the DOM `AbortError`, or the LLM SDK's `APIUserAbortError` — use it to distinguish user aborts from real errors.
|
|
681
|
+
|
|
682
|
+
### `backoffDelay`
|
|
683
|
+
|
|
684
|
+
**`backoffDelay(attempt: number): number`**
|
|
685
|
+
|
|
686
|
+
Exponential backoff in ms: `1000 * 2 ** attempt`. Suitable as the `backoff` field of `RetryOptions`.
|
|
687
|
+
|
|
688
|
+
### `mapWithConcurrency`
|
|
689
|
+
|
|
690
|
+
**`mapWithConcurrency<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>, signal?: AbortSignal): Promise<R[]>`**
|
|
691
|
+
|
|
692
|
+
Map over `items` in chunks of `limit` concurrent executions (unlike `Promise.all`, never runs more than `limit` at once). Stops scheduling new chunks once `signal` aborts; results keep input order.
|
|
693
|
+
|
|
694
|
+
## Text helpers
|
|
695
|
+
|
|
696
|
+
### `formatDuration`
|
|
697
|
+
|
|
698
|
+
**`formatDuration(value: number): string`**
|
|
699
|
+
|
|
700
|
+
Format a duration in seconds for display, upgrading to larger units when needed (e.g. `3.2s`, `1m 30s`, `1h 1m 5s`). Used for tool-result summaries and the CLI spinner.
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
import { formatDuration } from "@vietor/agent-core/util";
|
|
704
|
+
|
|
705
|
+
formatDuration(3.24); // "3.24s"
|
|
706
|
+
formatDuration(90.4); // "1m 30s"
|
|
707
|
+
formatDuration(3665); // "1h 1m 5s"
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### `formatCompactNumber`
|
|
711
|
+
|
|
712
|
+
**`formatCompactNumber(value: number): string`**
|
|
713
|
+
|
|
714
|
+
Format a number compactly (e.g. `1.2K`). Used for byte/line counts in summaries.
|
|
715
|
+
|
|
716
|
+
```ts
|
|
717
|
+
import { formatCompactNumber } from "@vietor/agent-core/util";
|
|
718
|
+
|
|
719
|
+
formatCompactNumber(1234); // "1.23K"
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### `getTextBytes`
|
|
723
|
+
|
|
724
|
+
**`getTextBytes(content: string): number`**
|
|
725
|
+
|
|
726
|
+
Return the UTF-8 byte length of a string (via `Buffer.byteLength`).
|
|
727
|
+
|
|
728
|
+
```ts
|
|
729
|
+
import { getTextBytes } from "@vietor/agent-core/util";
|
|
730
|
+
|
|
731
|
+
const bytes = getTextBytes("Hello"); // 5
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### `summarizeText`
|
|
735
|
+
|
|
736
|
+
**`summarizeText(content: string, length: number, showChars?: boolean): string`**
|
|
737
|
+
|
|
738
|
+
Collapse whitespace and truncate text to `length` characters with a trailing `…`. With `showChars`, append the total character count when truncated — useful for text that changes size over time.
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
import { summarizeText } from "@vietor/agent-core/util";
|
|
742
|
+
|
|
743
|
+
summarizeText("a\nvery long line", 8); // "a very l…"
|
|
744
|
+
summarizeText("a very long line here", 8, true); // "a very l… (21)"
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### `toErrorMessage`
|
|
748
|
+
|
|
749
|
+
**`toErrorMessage(e: unknown): string`**
|
|
750
|
+
|
|
751
|
+
Stringify an unknown error for display (`e instanceof Error ? e.message : String(e)`). Used across core for error events.
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
import { toErrorMessage } from "@vietor/agent-core/util";
|
|
755
|
+
|
|
756
|
+
toErrorMessage(new Error("boom")); // "boom"
|
|
757
|
+
toErrorMessage("oops"); // "oops"
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## Subprocess
|
|
761
|
+
|
|
762
|
+
### `runProcess`
|
|
763
|
+
|
|
764
|
+
**`runProcess(cmd: string, args: string[], opts?: { cwd?: string; timeout?: number }, signal?: AbortSignal): Promise<ProcessResult>`**
|
|
765
|
+
|
|
766
|
+
Run a subprocess, capturing stdout and stderr (used by the built-in Shell tool). The promise never rejects — spawn failures, timeouts, and output over the 10MB cap are reported via `ProcessResult.error`. Pass a `timeout` (ms) or an `AbortSignal` to kill the process tree. Live processes are killed on process exit.
|
|
767
|
+
|
|
768
|
+
```ts
|
|
769
|
+
import { runProcess } from "@vietor/agent-core/util";
|
|
770
|
+
|
|
771
|
+
const result = await runProcess("ls", ["-la"], { cwd: "./src" });
|
|
772
|
+
if (result.error) console.error(result.error.message);
|
|
773
|
+
else console.log(result.stdout, result.status);
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### `ProcessResult`
|
|
777
|
+
|
|
778
|
+
Returned by `runProcess`.
|
|
779
|
+
|
|
780
|
+
```ts
|
|
781
|
+
interface ProcessResult {
|
|
782
|
+
stdout: string;
|
|
783
|
+
stderr: string;
|
|
784
|
+
status: number | null; // exit code; null when killed (signal, abort, timeout, buffer overflow)
|
|
785
|
+
error?: Error; // spawn failure, timeout, or output exceeded the 10MB cap
|
|
786
|
+
truncated?: boolean; // true when output was cut off at the 10MB cap
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
## File
|
|
791
|
+
|
|
792
|
+
### `tryReadFileText`
|
|
793
|
+
|
|
794
|
+
**`tryReadFileText(path: string): string | undefined`**
|
|
795
|
+
|
|
796
|
+
Read a text file, returning `undefined` on any failure (missing file, empty content, read error).
|
|
797
|
+
|
|
798
|
+
```ts
|
|
799
|
+
const content = tryReadFileText("./config.json");
|
|
800
|
+
if (content) {
|
|
801
|
+
const config = JSON.parse(content);
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
## HTML
|
|
806
|
+
|
|
807
|
+
### `htmlToMarkdown`
|
|
808
|
+
|
|
809
|
+
**`htmlToMarkdown(html: string): string`**
|
|
810
|
+
|
|
811
|
+
Convert HTML to Markdown using [Turndown](https://github.com/mixmark-io/turndown). Strips script, style, title, meta, head, noscript, template, link, and base elements.
|
|
812
|
+
|
|
813
|
+
```ts
|
|
814
|
+
import { htmlToMarkdown } from "@vietor/agent-core/util";
|
|
815
|
+
|
|
816
|
+
const md = htmlToMarkdown("<h1>Hello</h1><p>World</p>");
|
|
817
|
+
// "# Hello\n\nWorld"
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
## Network
|
|
821
|
+
|
|
822
|
+
### `netFetch`
|
|
823
|
+
|
|
824
|
+
**`netFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>`**
|
|
825
|
+
|
|
826
|
+
A drop-in replacement for `fetch` that automatically routes through an HTTP(S) proxy when configured. Respects the standard environment variables:
|
|
827
|
+
|
|
828
|
+
- `HTTPS_PROXY` / `https_proxy` — proxy URL for HTTPS requests (preferred)
|
|
829
|
+
- `HTTP_PROXY` / `http_proxy` — proxy URL for HTTP requests (fallback)
|
|
830
|
+
- `NO_PROXY` / `no_proxy` — comma-separated hostnames/domains to bypass the proxy
|
|
831
|
+
|
|
832
|
+
```ts
|
|
833
|
+
import { netFetch } from "@vietor/agent-core/util";
|
|
834
|
+
|
|
835
|
+
// Same signature as fetch — automatically uses proxy if env vars are set
|
|
836
|
+
const res = await netFetch("https://api.example.com/data");
|
|
837
|
+
const data = await res.json();
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## Constants
|
|
841
|
+
|
|
842
|
+
Default values mirrored by `LLMConfig` — exported so host config types can reference the same defaults.
|
|
843
|
+
|
|
844
|
+
| Constant | Value | Meaning |
|
|
845
|
+
|---|---|---|
|
|
846
|
+
| `DEFAULT_THINKING_EFFORT` | `"high"` | Default `LLMConfig.thinkingEffort` |
|
|
847
|
+
| `DEFAULT_BACKEND` | `"completions"` | Default `LLMConfig.backend` |
|
|
848
|
+
| `DEFAULT_MAX_INPUT_TOKENS` | `1_000_000` | Default `LLMConfig.maxInputTokens` |
|
|
849
|
+
| `DEFAULT_MAX_OUTPUT_TOKENS` | `128_000` | Default `LLMConfig.maxOutputTokens` |
|