@cuylabs/agent-core 0.2.0 → 0.4.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/README.md CHANGED
@@ -4,16 +4,20 @@ Embeddable AI agent infrastructure using Vercel AI SDK. Core building blocks for
4
4
 
5
5
  ## Features
6
6
 
7
- - **Agent Framework** - Create AI agents with tool support and streaming responses
8
- - **Session Management** - Persistent conversation state with branching support
9
- - **LLM Streaming** - Real-time streaming with proper backpressure handling
10
- - **Error Resilience** - Automatic retry with exponential backoff
11
- - **Context Management** - Token counting and automatic context pruning
12
- - **Tool Framework** - Type-safe tool definitions with Zod schemas
13
- - **Approval System** - Configurable tool approval workflows
14
- - **Checkpoint System** - Undo/restore capabilities for file operations
15
- - **Model Capabilities** - Runtime model capability detection
16
- - **MCP Support** - Extend agents with Model Context Protocol servers
7
+ - **Agent Framework** Create AI agents with tool support and streaming responses
8
+ - **Session Management** Persistent conversation state with branching support
9
+ - **LLM Streaming** Real-time streaming with proper backpressure handling
10
+ - **Error Resilience** Automatic retry with exponential backoff
11
+ - **Context Management** Token counting and automatic context pruning
12
+ - **Tool Framework** Type-safe tool definitions with Zod schemas
13
+ - **Middleware** Composable lifecycle hooks for tool interception, prompt injection, logging, and guardrails
14
+ - **Skills** Modular knowledge packs with progressive disclosure (L1 summary → L2 content → L3 resources)
15
+ - **Approval System** Configurable tool approval via middleware (rules + interactive prompts)
16
+ - **Checkpoint System** Undo/restore capabilities for file operations
17
+ - **Model Capabilities** — Runtime model capability detection
18
+ - **MCP Support** — Extend agents with Model Context Protocol servers
19
+ - **Sub-Agents** — Fork agents with inherited config, run tasks in parallel, or let the LLM delegate via `invoke_agent`
20
+ - **Presets** — Reusable agent configurations with tool filtering
17
21
 
18
22
  ## Installation
19
23
 
@@ -44,22 +48,23 @@ import { openai } from "@ai-sdk/openai";
44
48
  import { z } from "zod";
45
49
 
46
50
  // Define tools
47
- const tools: Tool[] = [
48
- {
49
- name: "greet",
50
- description: "Greet a user by name",
51
- parameters: z.object({
52
- name: z.string().describe("Name to greet"),
53
- }),
54
- execute: async ({ name }) => `Hello, ${name}!`,
55
- },
56
- ];
51
+ const greet = Tool.define("greet", {
52
+ description: "Greet a user by name",
53
+ parameters: z.object({
54
+ name: z.string().describe("Name to greet"),
55
+ }),
56
+ execute: async ({ name }) => ({
57
+ title: "Greeting",
58
+ output: `Hello, ${name}!`,
59
+ metadata: {},
60
+ }),
61
+ });
57
62
 
58
63
  // Create agent
59
64
  const agent = createAgent({
60
65
  model: openai("gpt-4o"),
61
- cwd: process.cwd(),
62
- tools,
66
+ tools: [greet],
67
+ systemPrompt: "You are a helpful assistant.",
63
68
  });
64
69
 
65
70
  // Stream responses
@@ -68,7 +73,7 @@ for await (const event of agent.chat("session-1", "Hello!")) {
68
73
  case "text-delta":
69
74
  process.stdout.write(event.text);
70
75
  break;
71
- case "tool-call":
76
+ case "tool-start":
72
77
  console.log(`Calling tool: ${event.toolName}`);
73
78
  break;
74
79
  case "tool-result":
@@ -150,26 +155,131 @@ await sessions.createSession({ id: "my-session" });
150
155
  ### Tool Framework
151
156
 
152
157
  ```typescript
153
- import { defineTool, Tool, ToolRegistry } from "@cuylabs/agent-core";
158
+ import { Tool } from "@cuylabs/agent-core";
154
159
  import { z } from "zod";
155
160
 
156
- const myTool = defineTool({
157
- name: "calculate",
158
- description: "Perform a calculation",
161
+ const calculator = Tool.define("calculator", {
162
+ description: "Evaluate a math expression",
159
163
  parameters: z.object({
160
164
  expression: z.string(),
161
165
  }),
162
166
  execute: async ({ expression }, ctx) => {
163
- // ctx provides sessionId, cwd, abort signal, etc.
164
- return eval(expression); // Don't actually do this!
167
+ // ctx provides sessionID, cwd, abort signal, host, etc.
168
+ return {
169
+ title: "Calculator",
170
+ output: String(eval(expression)), // Don't actually do this!
171
+ metadata: {},
172
+ };
165
173
  },
166
174
  });
175
+ ```
176
+
177
+ ### Middleware
167
178
 
168
- // Register tools
169
- const registry = new ToolRegistry();
170
- registry.register(myTool);
179
+ Composable lifecycle hooks for tool interception, prompt injection, logging, and guardrails:
180
+
181
+ ```typescript
182
+ import { createAgent, type AgentMiddleware, approvalMiddleware } from "@cuylabs/agent-core";
183
+
184
+ // Simple logging middleware
185
+ const logger: AgentMiddleware = {
186
+ name: "logger",
187
+ async beforeToolCall(tool, args) {
188
+ console.log(`→ ${tool}`, args);
189
+ return { action: "allow" };
190
+ },
191
+ async afterToolCall(tool, _args, result) {
192
+ console.log(`← ${tool}`, result.title);
193
+ return result;
194
+ },
195
+ async onChatStart(sessionId, message) {
196
+ console.log(`Chat started: ${sessionId}`);
197
+ },
198
+ async onChatEnd(sessionId, { usage, error }) {
199
+ console.log(`Chat ended: ${usage?.totalTokens} tokens`);
200
+ },
201
+ };
202
+
203
+ // Guardrail middleware — block dangerous tools
204
+ const guardrail: AgentMiddleware = {
205
+ name: "guardrail",
206
+ async beforeToolCall(tool) {
207
+ if (tool === "bash") {
208
+ return { action: "deny", reason: "Shell commands are disabled." };
209
+ }
210
+ return { action: "allow" };
211
+ },
212
+ };
213
+
214
+ const agent = createAgent({
215
+ model: openai("gpt-4o"),
216
+ tools: myTools,
217
+ middleware: [
218
+ logger,
219
+ guardrail,
220
+ approvalMiddleware({
221
+ rules: [{ pattern: "*", tool: "read_file", action: "allow" }],
222
+ onRequest: async (req) => {
223
+ // Prompt user for approval
224
+ return await askUser(req);
225
+ },
226
+ }),
227
+ ],
228
+ });
171
229
  ```
172
230
 
231
+ Middleware hooks: `beforeToolCall`, `afterToolCall`, `promptSections`, `onEvent`, `onChatStart`, `onChatEnd`. Sub-agents inherit middleware via `fork()`.
232
+
233
+ ### Skills
234
+
235
+ Modular knowledge packs with three-level progressive disclosure:
236
+
237
+ ```typescript
238
+ import { createAgent, createSkillRegistry, createSkillTools } from "@cuylabs/agent-core";
239
+
240
+ // Discover skills from SKILL.md files
241
+ const registry = await createSkillRegistry(process.cwd(), {
242
+ externalDirs: [".agents", ".claude"],
243
+ });
244
+
245
+ // Create skill tools (skill + skill_resource)
246
+ const skillTools = createSkillTools(registry);
247
+
248
+ // Inject L1 summary into system prompt, give agent skill tools
249
+ const agent = createAgent({
250
+ model: openai("gpt-4o"),
251
+ tools: [...myTools, ...skillTools],
252
+ systemPrompt: `You are a coding assistant.\n\n${registry.formatSummary()}`,
253
+ });
254
+ ```
255
+
256
+ Skills use the `SKILL.md` format with YAML frontmatter. See [examples/skills/](examples/skills/) for samples.
257
+
258
+ ### Built-in Sub-Agent Tools
259
+
260
+ Let the LLM delegate work to specialized sub-agents via tool calls:
261
+
262
+ ```typescript
263
+ import { createAgent, createSubAgentTools, Presets } from "@cuylabs/agent-core";
264
+ import type { AgentProfile } from "@cuylabs/agent-core";
265
+
266
+ const profiles: AgentProfile[] = [
267
+ { name: "explorer", description: "Fast code search", preset: Presets.explore },
268
+ { name: "reviewer", description: "Thorough code review", preset: Presets.review },
269
+ ];
270
+
271
+ const parent = createAgent({ model, tools: codeTools });
272
+ const subTools = createSubAgentTools(parent, { profiles, async: true });
273
+
274
+ const agent = createAgent({
275
+ model,
276
+ tools: [...codeTools, ...subTools],
277
+ });
278
+ // The LLM can now call invoke_agent, wait_agent, and close_agent
279
+ ```
280
+
281
+ Tools: `invoke_agent` (spawn), `wait_agent` (collect results), `close_agent` (cancel). Supports sync and async modes, depth limiting, and concurrency control. See [docs/subagents.md](docs/subagents.md#built-in-sub-agent-tools-llm-callable).
282
+
173
283
  ### Error Handling & Retry
174
284
 
175
285
  ```typescript
@@ -262,14 +372,30 @@ The `chat()` method yields these event types:
262
372
  | Event Type | Description |
263
373
  |------------|-------------|
264
374
  | `text-delta` | Streaming text chunk |
265
- | `tool-call` | Tool invocation started |
375
+ | `text-start` / `text-end` | Text generation boundaries |
376
+ | `tool-start` | Tool invocation started |
266
377
  | `tool-result` | Tool execution completed |
267
- | `reasoning` | Model reasoning (if supported) |
268
- | `message` | Complete assistant message |
378
+ | `tool-error` | Tool execution failed |
379
+ | `step-start` / `step-finish` | LLM step boundaries with usage stats |
380
+ | `reasoning-delta` | Model reasoning chunk (if supported) |
381
+ | `status` | Agent state changes (thinking, calling-tool, etc.) |
382
+ | `doom-loop` | Repeated tool call pattern detected |
383
+ | `context-overflow` | Context window exceeded |
384
+ | `message` | Complete user/assistant message |
269
385
  | `complete` | Stream finished with usage stats |
270
- | `error` | Error occurred |
386
+ | `error` | Unrecoverable error |
387
+
388
+ See [examples/03-streaming.ts](examples/03-streaming.ts) for a complete event handling example.
271
389
 
272
390
  ## Concurrency Note
273
391
 
274
392
  When using `runConcurrent()` to run multiple tasks in parallel, be aware that all tasks share the same `SessionManager` instance. For independent conversations, create separate `Agent` instances or use distinct session IDs.
275
393
 
394
+ ## Examples
395
+
396
+ See [examples/](examples/) for 17 runnable examples covering every feature — from basic chat to middleware, skills, and Docker execution. The [examples README](examples/README.md) has setup instructions and run commands.
397
+
398
+ ## Documentation
399
+
400
+ See [docs/](docs/) for in-depth guides on each subsystem.
401
+
@@ -203,17 +203,43 @@ interface ToolMetadata {
203
203
  /**
204
204
  * Tool definition namespace for @cuylabs/agent-core
205
205
  *
206
- * Following OpenCode's Tool.define pattern for consistent tool creation.
206
+ * Provides consistent tool creation via Tool.define.
207
207
  */
208
208
 
209
209
  /**
210
- * Tool namespace - OpenCode-style tool definition
210
+ * A schema type compatible with both Zod 3 and Zod 4.
211
+ *
212
+ * Uses structural typing so that schemas from either Zod version satisfy
213
+ * the constraint. This avoids the problem where Zod 3's `ZodType` class
214
+ * and Zod 4's `ZodType` class have incompatible internal shapes, causing
215
+ * type errors when a library compiled against one version is consumed with
216
+ * the other.
217
+ *
218
+ * Both Zod 3 and Zod 4 (classic API) schemas have `parse()` and `_output`.
219
+ */
220
+ interface CompatibleSchema<T = any> {
221
+ /** Parse and validate input data. Present in both Zod 3 and Zod 4. */
222
+ parse(data: unknown): T;
223
+ /**
224
+ * Type-level output marker used by both Zod versions:
225
+ * - Zod 3: `readonly _output!: T` (definite assignment assertion)
226
+ * - Zod 4 classic: `get _output(): T` (getter)
227
+ */
228
+ readonly _output: T;
229
+ }
230
+ /**
231
+ * Infer the output type from a compatible schema.
232
+ * Equivalent to `z.infer<T>` but works with both Zod 3 and Zod 4 schemas.
233
+ */
234
+ type InferSchemaOutput<T extends CompatibleSchema> = T["_output"];
235
+ /**
236
+ * Tool namespace - tool definition utilities
211
237
  */
212
238
  declare namespace Tool {
213
239
  /**
214
240
  * Tool info interface - the shape of a defined tool
215
241
  */
216
- interface Info<TParams extends z.ZodType = z.ZodType, TMeta extends ToolMetadata = ToolMetadata> {
242
+ interface Info<TParams extends CompatibleSchema = any, TMeta extends ToolMetadata = ToolMetadata> {
217
243
  /** Unique tool identifier */
218
244
  id: string;
219
245
  /** Initialize the tool (can be async for dynamic descriptions) */
@@ -235,13 +261,13 @@ declare namespace Tool {
235
261
  /**
236
262
  * Result of tool initialization
237
263
  */
238
- interface InitResult<TParams extends z.ZodType = z.ZodType, TMeta extends ToolMetadata = ToolMetadata> {
264
+ interface InitResult<TParams extends CompatibleSchema = any, TMeta extends ToolMetadata = ToolMetadata> {
239
265
  /** Tool description for the LLM */
240
266
  description: string;
241
267
  /** Zod schema for parameters */
242
268
  parameters: TParams;
243
269
  /** Execute the tool */
244
- execute: (params: z.infer<TParams>, ctx: ToolContext) => Promise<ExecuteResult<TMeta>>;
270
+ execute: (params: InferSchemaOutput<TParams>, ctx: ToolContext) => Promise<ExecuteResult<TMeta>>;
245
271
  /** Optional custom validation error formatter */
246
272
  formatValidationError?: (error: z.ZodError) => string;
247
273
  /**
@@ -272,7 +298,7 @@ declare namespace Tool {
272
298
  metadata: TMeta;
273
299
  }
274
300
  /**
275
- * Define a tool with OpenCode-style patterns
301
+ * Define a tool with standard patterns
276
302
  *
277
303
  * @example
278
304
  * ```typescript
@@ -289,14 +315,14 @@ declare namespace Tool {
289
315
  * });
290
316
  * ```
291
317
  */
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>;
318
+ function define<TParams extends CompatibleSchema, TMeta extends ToolMetadata = ToolMetadata>(id: string, init: Info<TParams, TMeta>["init"] | Awaited<ReturnType<Info<TParams, TMeta>["init"]>>): Info<TParams, TMeta>;
293
319
  /**
294
320
  * Simple define for static tools (no async init)
295
321
  */
296
- function defineSimple<TParams extends z.ZodType, TMeta extends ToolMetadata = ToolMetadata>(id: string, config: {
322
+ function defineSimple<TParams extends CompatibleSchema, TMeta extends ToolMetadata = ToolMetadata>(id: string, config: {
297
323
  description: string;
298
324
  parameters: TParams;
299
- execute: (params: z.infer<TParams>, ctx: ToolContext) => Promise<ExecuteResult<TMeta>>;
325
+ execute: (params: InferSchemaOutput<TParams>, ctx: ToolContext) => Promise<ExecuteResult<TMeta>>;
300
326
  }): Info<TParams, TMeta>;
301
327
  }
302
328
  /**
@@ -304,11 +330,11 @@ declare namespace Tool {
304
330
  *
305
331
  * @deprecated Use Tool.define instead
306
332
  */
307
- declare function defineTool<TParams extends z.ZodType>(definition: {
333
+ declare function defineTool<TParams extends CompatibleSchema>(definition: {
308
334
  id: string;
309
335
  description: string;
310
336
  parameters: TParams;
311
- execute: (params: z.infer<TParams>, ctx: ToolContext) => Promise<ToolResult>;
337
+ execute: (params: InferSchemaOutput<TParams>, ctx: ToolContext) => Promise<ToolResult>;
312
338
  }): Tool.Info<TParams>;
313
339
 
314
340
  /**
@@ -412,8 +438,6 @@ declare const defaultRegistry: ToolRegistry;
412
438
 
413
439
  /**
414
440
  * Output truncation utilities for @cuylabs/agent-core
415
- *
416
- * Based on OpenCode's tool/truncation.ts
417
441
  */
418
442
  /** Maximum lines before truncation */
419
443
  declare const MAX_LINES = 2000;
@@ -443,4 +467,4 @@ declare function truncateOutput(output: string, options?: {
443
467
  */
444
468
  declare function formatSize(bytes: number): string;
445
469
 
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 };
470
+ export { type CompatibleSchema as C, type DirEntry as D, type ExecOptions as E, type FileOperationMeta as F, type InferSchemaOutput as I, MAX_BYTES as M, type ToolHost as T, type ToolContext as a, Tool as b, type TurnTrackerContext as c, type ExecResult as d, type FileStat as e, MAX_LINES as f, TRUNCATE_DIR as g, TRUNCATE_GLOB 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 };