@cuylabs/agent-core 0.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.
@@ -0,0 +1,446 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * ToolHost — execution environment abstraction for agent tools.
5
+ *
6
+ * A ToolHost defines *where* tools execute: local machine, Docker container,
7
+ * SSH remote, etc. Every host provides two surfaces:
8
+ *
9
+ * 1. **File system** — read, write, stat, list, check existence
10
+ * 2. **Process execution** — spawn commands, kill processes
11
+ *
12
+ * Tools call `ctx.host.readFile()` / `ctx.host.exec()` instead of importing
13
+ * `fs` and `child_process` directly. The host implementation handles the rest.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Default: runs everything locally
18
+ * const agent = createAgent({ host: localHost() });
19
+ *
20
+ * // Future: run tools inside a Docker container
21
+ * const agent = createAgent({ host: dockerHost("my-container") });
22
+ * ```
23
+ */
24
+ /** Options for spawning a process. */
25
+ interface ExecOptions {
26
+ /** Working directory for the command. */
27
+ cwd?: string;
28
+ /** Environment variables (merged with host defaults). */
29
+ env?: Record<string, string | undefined>;
30
+ /** Abort signal for cancellation. */
31
+ signal?: AbortSignal;
32
+ /** Timeout in milliseconds. 0 = no timeout. */
33
+ timeout?: number;
34
+ /** Callback for stdout data as it arrives. */
35
+ onStdout?: (data: Buffer) => void;
36
+ /** Callback for stderr data as it arrives. */
37
+ onStderr?: (data: Buffer) => void;
38
+ }
39
+ /** Result of a process execution. */
40
+ interface ExecResult {
41
+ /** Combined stdout output. */
42
+ stdout: string;
43
+ /** Combined stderr output. */
44
+ stderr: string;
45
+ /** Exit code (null if killed by signal). */
46
+ exitCode: number | null;
47
+ /** Whether the process was killed due to timeout. */
48
+ timedOut: boolean;
49
+ }
50
+ /** Minimal stat result — only the fields tools actually need. */
51
+ interface FileStat {
52
+ /** File size in bytes. */
53
+ size: number;
54
+ /** Last modification time. */
55
+ mtime: Date;
56
+ /** Whether this entry is a directory. */
57
+ isDirectory: boolean;
58
+ /** Whether this entry is a regular file. */
59
+ isFile: boolean;
60
+ }
61
+ /** A directory entry returned by `readdir`. */
62
+ interface DirEntry {
63
+ /** Entry name (not full path). */
64
+ name: string;
65
+ /** Whether this entry is a directory. */
66
+ isDirectory: boolean;
67
+ /** Whether this entry is a regular file. */
68
+ isFile: boolean;
69
+ }
70
+ /**
71
+ * The execution environment for agent tools.
72
+ *
73
+ * Abstracts filesystem and process operations so tools work identically
74
+ * whether running locally, in Docker, over SSH, or in any other environment.
75
+ */
76
+ interface ToolHost {
77
+ /** Human-readable host identifier (e.g. "local", "docker:my-container"). */
78
+ readonly name: string;
79
+ /** Read a file as a UTF-8 string. Throws if the file doesn't exist. */
80
+ readFile(path: string): Promise<string>;
81
+ /** Read raw bytes from a file. Throws if the file doesn't exist. */
82
+ readBytes(path: string, offset: number, length: number): Promise<Buffer>;
83
+ /** Write a UTF-8 string to a file. Creates parent directories as needed. */
84
+ writeFile(path: string, content: string): Promise<void>;
85
+ /** Check if a path exists. Never throws. */
86
+ exists(path: string): Promise<boolean>;
87
+ /** Get file metadata. Throws if the path doesn't exist. */
88
+ stat(path: string): Promise<FileStat>;
89
+ /** List directory entries. Throws if the path is not a directory. */
90
+ readdir(path: string): Promise<DirEntry[]>;
91
+ /** Create directories recursively. No-op if they already exist. */
92
+ mkdir(path: string): Promise<void>;
93
+ /**
94
+ * Execute a shell command.
95
+ *
96
+ * The host decides which shell to use (e.g. local host uses the user's
97
+ * `$SHELL`, Docker host uses `docker exec`, SSH host uses remote shell).
98
+ */
99
+ exec(command: string, options?: ExecOptions): Promise<ExecResult>;
100
+ }
101
+
102
+ /**
103
+ * Tool-related types for @cuylabs/agent-core
104
+ *
105
+ * Defines the interfaces for tool execution context, results,
106
+ * and metadata used throughout the agent system.
107
+ */
108
+
109
+ /**
110
+ * Turn tracker interface for tool context
111
+ * Minimal interface to avoid circular dependencies
112
+ */
113
+ interface TurnTrackerContext {
114
+ /** Capture baseline before writing to a file */
115
+ beforeWrite(path: string): Promise<boolean>;
116
+ /** Check if a file is being tracked */
117
+ isTracking(path: string): boolean;
118
+ }
119
+ /**
120
+ * Tool execution context
121
+ */
122
+ interface ToolContext {
123
+ /** Working directory for file operations */
124
+ cwd: string;
125
+ /** Abort signal for cancellation */
126
+ abort: AbortSignal;
127
+ /** Session ID */
128
+ sessionID: string;
129
+ /** Message ID */
130
+ messageID: string;
131
+ /** Agent name */
132
+ agent: string;
133
+ /**
134
+ * Execution environment for file and process operations.
135
+ *
136
+ * Tools should call `ctx.host.readFile()`, `ctx.host.exec()`, etc.
137
+ * instead of importing `fs` and `child_process` directly. This
138
+ * allows the same tools to work on local, Docker, SSH, or any
139
+ * other host without code changes.
140
+ *
141
+ * When not provided, tools may fall back to direct Node.js APIs,
142
+ * but new tools should always prefer `ctx.host`.
143
+ */
144
+ host?: ToolHost;
145
+ /**
146
+ * Turn change tracker - automatically captures file baselines.
147
+ * Call `turnTracker.beforeWrite(path)` before modifying files
148
+ * for undo/diff capabilities.
149
+ */
150
+ turnTracker?: TurnTrackerContext;
151
+ /** Extra context data */
152
+ extra?: Record<string, unknown>;
153
+ }
154
+ /**
155
+ * Result returned by a tool
156
+ */
157
+ interface ToolResult {
158
+ /** Short title for display */
159
+ title: string;
160
+ /** Main output text */
161
+ output: string;
162
+ /** Optional metadata */
163
+ metadata?: ToolMetadata;
164
+ }
165
+ /**
166
+ * File operation metadata - declare how a tool modifies files
167
+ * Used by TurnChangeTracker for automatic baseline capture
168
+ */
169
+ interface FileOperationMeta {
170
+ /**
171
+ * Names of parameters that contain file paths to track.
172
+ * The tracker will capture baselines for these files before the tool runs.
173
+ * @example ['path'] for write tool, ['filePath'] for edit tool
174
+ */
175
+ pathArgs?: string[];
176
+ /**
177
+ * Type of file operation - determines if baseline capture is needed.
178
+ * - 'read': No baseline capture (file not modified)
179
+ * - 'write': Capture baseline before overwriting
180
+ * - 'create': Capture baseline (to know file didn't exist)
181
+ * - 'delete': Capture baseline before deletion
182
+ */
183
+ operationType?: "read" | "write" | "delete" | "create";
184
+ }
185
+ /**
186
+ * Tool metadata
187
+ */
188
+ interface ToolMetadata {
189
+ /** Whether output was truncated */
190
+ truncated?: boolean;
191
+ /** Path to full output if truncated */
192
+ outputPath?: string;
193
+ /**
194
+ * File operation metadata for automatic turn tracking.
195
+ * When set, the agent will automatically capture file baselines
196
+ * before tool execution for undo/diff capabilities.
197
+ */
198
+ fileOps?: FileOperationMeta;
199
+ /** Additional metadata */
200
+ [key: string]: unknown;
201
+ }
202
+
203
+ /**
204
+ * Tool definition namespace for @cuylabs/agent-core
205
+ *
206
+ * Following OpenCode's Tool.define pattern for consistent tool creation.
207
+ */
208
+
209
+ /**
210
+ * Tool namespace - OpenCode-style tool definition
211
+ */
212
+ declare namespace Tool {
213
+ /**
214
+ * Tool info interface - the shape of a defined tool
215
+ */
216
+ interface Info<TParams extends z.ZodType = z.ZodType, TMeta extends ToolMetadata = ToolMetadata> {
217
+ /** Unique tool identifier */
218
+ id: string;
219
+ /** Initialize the tool (can be async for dynamic descriptions) */
220
+ init: (ctx?: InitContext) => Promise<InitResult<TParams, TMeta>> | InitResult<TParams, TMeta>;
221
+ }
222
+ /**
223
+ * Any tool info - for use in arrays and collections where generic types vary
224
+ */
225
+ type AnyInfo = Info<any, any>;
226
+ /**
227
+ * Context passed during tool initialization
228
+ */
229
+ interface InitContext {
230
+ /** Working directory */
231
+ cwd?: string;
232
+ /** Agent name (if known) */
233
+ agent?: string;
234
+ }
235
+ /**
236
+ * Result of tool initialization
237
+ */
238
+ interface InitResult<TParams extends z.ZodType = z.ZodType, TMeta extends ToolMetadata = ToolMetadata> {
239
+ /** Tool description for the LLM */
240
+ description: string;
241
+ /** Zod schema for parameters */
242
+ parameters: TParams;
243
+ /** Execute the tool */
244
+ execute: (params: z.infer<TParams>, ctx: ToolContext) => Promise<ExecuteResult<TMeta>>;
245
+ /** Optional custom validation error formatter */
246
+ formatValidationError?: (error: z.ZodError) => string;
247
+ /**
248
+ * File operation metadata for automatic turn tracking.
249
+ * When set, the agent will automatically capture file baselines
250
+ * before tool execution for undo/diff capabilities.
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * // For a write tool
255
+ * fileOps: {
256
+ * pathArgs: ['path'],
257
+ * operationType: 'write'
258
+ * }
259
+ * ```
260
+ */
261
+ fileOps?: FileOperationMeta;
262
+ }
263
+ /**
264
+ * Result of tool execution
265
+ */
266
+ interface ExecuteResult<TMeta extends ToolMetadata = ToolMetadata> {
267
+ /** Short title for display */
268
+ title: string;
269
+ /** Main output text */
270
+ output: string;
271
+ /** Metadata */
272
+ metadata: TMeta;
273
+ }
274
+ /**
275
+ * Define a tool with OpenCode-style patterns
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const myTool = Tool.define("my_tool", {
280
+ * description: "Does something useful",
281
+ * parameters: z.object({
282
+ * input: z.string().describe("The input to process"),
283
+ * }),
284
+ * execute: async (params, ctx) => ({
285
+ * title: "Processed",
286
+ * output: `Result: ${params.input}`,
287
+ * metadata: {},
288
+ * }),
289
+ * });
290
+ * ```
291
+ */
292
+ function define<TParams extends z.ZodType, TMeta extends ToolMetadata = ToolMetadata>(id: string, init: Info<TParams, TMeta>["init"] | Awaited<ReturnType<Info<TParams, TMeta>["init"]>>): Info<TParams, TMeta>;
293
+ /**
294
+ * Simple define for static tools (no async init)
295
+ */
296
+ function defineSimple<TParams extends z.ZodType, TMeta extends ToolMetadata = ToolMetadata>(id: string, config: {
297
+ description: string;
298
+ parameters: TParams;
299
+ execute: (params: z.infer<TParams>, ctx: ToolContext) => Promise<ExecuteResult<TMeta>>;
300
+ }): Info<TParams, TMeta>;
301
+ }
302
+ /**
303
+ * Legacy helper - wraps to Tool.Info format
304
+ *
305
+ * @deprecated Use Tool.define instead
306
+ */
307
+ declare function defineTool<TParams extends z.ZodType>(definition: {
308
+ id: string;
309
+ description: string;
310
+ parameters: TParams;
311
+ execute: (params: z.infer<TParams>, ctx: ToolContext) => Promise<ToolResult>;
312
+ }): Tool.Info<TParams>;
313
+
314
+ /**
315
+ * Tool registry for @cuylabs/agent-core
316
+ *
317
+ * Manages available tools, named groups, and resolution from specs.
318
+ */
319
+
320
+ /**
321
+ * A tool spec that can be resolved to an array of tools.
322
+ *
323
+ * - `string` — group name (`"read-only"`), single tool ID (`"bash"`),
324
+ * or comma-separated list (`"read,grep,glob"`).
325
+ * Prefix with `-` to exclude: `"all,-bash"`.
326
+ * - `string[]` — array of tool IDs and/or group names.
327
+ * - `Tool.AnyInfo[]` — pass-through (already resolved).
328
+ * - `true` — all registered tools.
329
+ * - `false` — no tools.
330
+ */
331
+ type ToolSpec = string | string[] | Tool.AnyInfo[] | boolean;
332
+ /**
333
+ * Tool registry — manages available tools and named groups.
334
+ *
335
+ * Tools are registered individually. Groups are named collections of tool IDs
336
+ * that resolve against the registry at lookup time.
337
+ *
338
+ * @example
339
+ * ```typescript
340
+ * const reg = new ToolRegistry();
341
+ * reg.registerAll([readTool, grepTool, bashTool]);
342
+ * reg.registerGroup("read-only", ["read", "grep", "glob"]);
343
+ *
344
+ * // Resolve a spec to a tool array
345
+ * reg.resolve("read-only"); // → [readTool, grepTool]
346
+ * reg.resolve("all,-bash"); // → [readTool, grepTool]
347
+ * reg.resolve(["read", "grep"]); // → [readTool, grepTool]
348
+ * reg.resolve(true); // → all tools
349
+ * ```
350
+ */
351
+ declare class ToolRegistry {
352
+ private tools;
353
+ private groups;
354
+ /**
355
+ * Register a tool. Throws if a tool with the same ID is already registered.
356
+ * Use `set()` for upsert semantics.
357
+ */
358
+ register(tool: Tool.AnyInfo): void;
359
+ /** Register multiple tools (throws on duplicates). */
360
+ registerAll(tools: Tool.AnyInfo[]): void;
361
+ /** Register or replace a tool (upsert). */
362
+ set(tool: Tool.AnyInfo): void;
363
+ /** Unregister a tool by ID. Returns `true` if it existed. */
364
+ unregister(id: string): boolean;
365
+ /** Get a tool by ID. */
366
+ get(id: string): Tool.AnyInfo | undefined;
367
+ /** Check if a tool is registered. */
368
+ has(id: string): boolean;
369
+ /** Get all tool IDs. */
370
+ ids(): string[];
371
+ /** Get all tools. */
372
+ all(): Tool.AnyInfo[];
373
+ /** Clear all tools and groups. */
374
+ clear(): void;
375
+ /** Number of registered tools. */
376
+ get size(): number;
377
+ /**
378
+ * Register a named group of tool IDs.
379
+ * The group name can be used in `resolve()` specs.
380
+ * Tool IDs don't need to be registered yet — resolution is lazy.
381
+ */
382
+ registerGroup(name: string, toolIds: string[]): void;
383
+ /** Get tools in a group (only returns registered tools). */
384
+ getGroup(name: string): Tool.AnyInfo[] | undefined;
385
+ /** Check if a group is registered. */
386
+ hasGroup(name: string): boolean;
387
+ /** List all group names. */
388
+ listGroups(): string[];
389
+ /**
390
+ * Resolve a `ToolSpec` to an array of tools.
391
+ *
392
+ * Supports group names, individual tool IDs, comma-separated strings,
393
+ * exclusions with `-` prefix, booleans, and pass-through arrays.
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * registry.resolve("read-only"); // group name
398
+ * registry.resolve("read,grep,glob"); // comma-separated IDs
399
+ * registry.resolve("all,-bash"); // all except bash
400
+ * registry.resolve(["read", "grep"]); // array of IDs
401
+ * registry.resolve(true); // all registered tools
402
+ * registry.resolve(false); // empty array
403
+ * ```
404
+ */
405
+ resolve(spec: ToolSpec): Tool.AnyInfo[];
406
+ }
407
+ /**
408
+ * Default tool registry instance.
409
+ * Shared across the application — register tools here for global access.
410
+ */
411
+ declare const defaultRegistry: ToolRegistry;
412
+
413
+ /**
414
+ * Output truncation utilities for @cuylabs/agent-core
415
+ *
416
+ * Based on OpenCode's tool/truncation.ts
417
+ */
418
+ /** Maximum lines before truncation */
419
+ declare const MAX_LINES = 2000;
420
+ /** Maximum bytes before truncation */
421
+ declare const MAX_BYTES = 100000;
422
+ /** Directory for storing truncated outputs */
423
+ declare const TRUNCATE_DIR: string;
424
+ /** Glob pattern for truncation directory */
425
+ declare const TRUNCATE_GLOB: string;
426
+ interface TruncateResult {
427
+ /** The (possibly truncated) content */
428
+ content: string;
429
+ /** Whether the content was truncated */
430
+ truncated: boolean;
431
+ /** Path to full output if truncated */
432
+ outputPath?: string;
433
+ }
434
+ /**
435
+ * Truncate output if it exceeds limits
436
+ */
437
+ declare function truncateOutput(output: string, options?: {
438
+ maxLines?: number;
439
+ maxBytes?: number;
440
+ }): TruncateResult;
441
+ /**
442
+ * Format file size for display
443
+ */
444
+ declare function formatSize(bytes: number): string;
445
+
446
+ export { type DirEntry as D, type ExecOptions as E, type FileOperationMeta as F, MAX_BYTES as M, type ToolHost as T, Tool as a, type TurnTrackerContext as b, type ExecResult as c, type FileStat as d, MAX_LINES as e, TRUNCATE_DIR as f, TRUNCATE_GLOB as g, type ToolContext as h, type ToolMetadata as i, ToolRegistry as j, type ToolResult as k, type ToolSpec as l, type TruncateResult as m, defaultRegistry as n, defineTool as o, formatSize as p, truncateOutput as t };