@cuylabs/agent-code 0.1.5 → 0.5.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,6 +4,18 @@ An embeddable AI coding agent library built on top of the [Vercel AI SDK](https:
4
4
 
5
5
  Designed to be embedded into larger applications, using modern Vercel AI SDK patterns for streaming, tool calling, and multi-provider support.
6
6
 
7
+ ## Package Boundary
8
+
9
+ Use `@cuylabs/agent-code` when you want ready-made coding tools and coding-agent defaults.
10
+
11
+ Use `@cuylabs/agent-core` directly when you need lower-level framework surfaces such as:
12
+ - runtime helpers
13
+ - middleware primitives
14
+ - prompt construction
15
+ - skills and sub-agent composition
16
+
17
+ `agent-code` re-exports `agent-core` for convenience, but its main responsibility is the coding-tool layer.
18
+
7
19
  ## Features
8
20
 
9
21
  - 🔌 **Embeddable** - Drop into any Node.js/TypeScript application
@@ -38,7 +50,7 @@ const agent = createAgent({
38
50
  // Non-streaming usage
39
51
  const { response, toolCalls } = await agent.send(
40
52
  "session-1",
41
- "List all TypeScript files in the src directory"
53
+ "List all TypeScript files in the src directory",
42
54
  );
43
55
  console.log(response);
44
56
 
@@ -63,14 +75,14 @@ for await (const event of agent.chat("session-1", "Fix the bug in utils.ts")) {
63
75
 
64
76
  ## Built-in Tools
65
77
 
66
- | Tool | Description |
67
- |------|-------------|
68
- | `bash` | Execute shell commands |
69
- | `read` | Read file contents with line numbers |
70
- | `edit` | Edit files by replacing text |
71
- | `write` | Create or overwrite files |
72
- | `grep` | Search file contents with regex |
73
- | `glob` | Find files by glob pattern |
78
+ | Tool | Description |
79
+ | ------- | ------------------------------------ |
80
+ | `bash` | Execute shell commands |
81
+ | `read` | Read file contents with line numbers |
82
+ | `edit` | Edit files by replacing text |
83
+ | `write` | Create or overwrite files |
84
+ | `grep` | Search file contents with regex |
85
+ | `glob` | Find files by glob pattern |
74
86
 
75
87
  ## Custom Tools
76
88
 
@@ -106,13 +118,13 @@ agent.addTool(myTool);
106
118
  ```typescript
107
119
  // OpenAI (recommended)
108
120
  import { openai } from "@ai-sdk/openai";
109
- const agent = createAgent({
121
+ const agent = createAgent({
110
122
  model: openai("gpt-4o"),
111
123
  cwd: process.cwd(),
112
124
  });
113
125
 
114
126
  // OpenAI with reasoning
115
- const reasoningAgent = createAgent({
127
+ const reasoningAgent = createAgent({
116
128
  model: openai("o3-mini"),
117
129
  cwd: process.cwd(),
118
130
  reasoningLevel: "high",
@@ -120,14 +132,14 @@ const reasoningAgent = createAgent({
120
132
 
121
133
  // Anthropic
122
134
  import { anthropic } from "@ai-sdk/anthropic";
123
- const agent = createAgent({
135
+ const agent = createAgent({
124
136
  model: anthropic("claude-sonnet-4-20250514"),
125
137
  cwd: process.cwd(),
126
138
  });
127
139
 
128
140
  // Google
129
141
  import { google } from "@ai-sdk/google";
130
- const agent = createAgent({
142
+ const agent = createAgent({
131
143
  model: google("gemini-2.0-flash"),
132
144
  cwd: process.cwd(),
133
145
  });
@@ -136,34 +148,40 @@ const agent = createAgent({
136
148
  ## Configuration
137
149
 
138
150
  ```typescript
139
- import { createAgent, defaultCodingTools, readTool, grepTool, globTool } from "@cuylabs/agent-code";
151
+ import {
152
+ createAgent,
153
+ defaultCodingTools,
154
+ readTool,
155
+ grepTool,
156
+ globTool,
157
+ } from "@cuylabs/agent-code";
140
158
  import { openai } from "@ai-sdk/openai";
141
159
 
142
160
  const agent = createAgent({
143
161
  // Required: Vercel AI SDK model instance
144
162
  model: openai("gpt-4o"),
145
-
163
+
146
164
  // Working directory for file operations (default: process.cwd())
147
165
  cwd: "/path/to/project",
148
-
166
+
149
167
  // System prompt (has sensible defaults for coding)
150
168
  systemPrompt: "You are a helpful coding assistant...",
151
-
169
+
152
170
  // Temperature (0-1, default varies by provider)
153
171
  temperature: 0.7,
154
-
172
+
155
173
  // Max output tokens (default: 32000)
156
174
  maxOutputTokens: 16000,
157
-
175
+
158
176
  // Max steps for tool calling loops (default: 50)
159
177
  maxSteps: 25,
160
-
178
+
161
179
  // Reasoning level for reasoning models (default: "off")
162
180
  reasoningLevel: "high", // "off", "low", "medium", "high"
163
-
181
+
164
182
  // Tools to enable (defaults to empty, use defaultCodingTools for all)
165
183
  tools: defaultCodingTools,
166
-
184
+
167
185
  // Or use a subset for read-only operations
168
186
  // tools: [readTool, grepTool, globTool],
169
187
  });
@@ -198,21 +216,31 @@ type AgentEvent =
198
216
  | { type: "text-start" }
199
217
  | { type: "text-delta"; text: string }
200
218
  | { type: "text-end" }
201
-
219
+
202
220
  // Reasoning (for o3-mini, etc.)
203
221
  | { type: "reasoning-start"; id: string }
204
222
  | { type: "reasoning-delta"; id: string; text: string }
205
223
  | { type: "reasoning-end"; id: string }
206
-
224
+
207
225
  // Tool execution
208
226
  | { type: "tool-start"; toolName: string; toolCallId: string; input: unknown }
209
- | { type: "tool-result"; toolName: string; toolCallId: string; result: unknown }
227
+ | {
228
+ type: "tool-result";
229
+ toolName: string;
230
+ toolCallId: string;
231
+ result: unknown;
232
+ }
210
233
  | { type: "tool-error"; toolName: string; toolCallId: string; error: string }
211
-
234
+
212
235
  // Progress
213
236
  | { type: "step-start"; step: number; maxSteps: number }
214
- | { type: "step-finish"; step: number; usage?: TokenUsage; finishReason?: string }
215
-
237
+ | {
238
+ type: "step-finish";
239
+ step: number;
240
+ usage?: TokenUsage;
241
+ finishReason?: string;
242
+ }
243
+
216
244
  // Completion
217
245
  | { type: "error"; error: Error }
218
246
  | { type: "complete"; usage?: TokenUsage };
@@ -222,12 +250,12 @@ type AgentEvent =
222
250
 
223
251
  See the [examples/](./examples/) folder for working examples:
224
252
 
225
- | # | File | What it shows |
226
- |---|------|---------------|
227
- | 01 | `01-basic.ts` | Basic streaming chat with coding tools |
228
- | 02 | `02-reasoning.ts` | Using reasoning models (o4-mini) |
229
- | 03 | `03-read-only.ts` | Read-only agent (no bash/edit/write) |
230
- | 04 | `04-custom-tools.ts` | Adding your own tools |
253
+ | # | File | What it shows |
254
+ | --- | -------------------- | -------------------------------------- |
255
+ | 01 | `01-basic.ts` | Basic streaming chat with coding tools |
256
+ | 02 | `02-reasoning.ts` | Using reasoning models (o4-mini) |
257
+ | 03 | `03-read-only.ts` | Read-only agent (no bash/edit/write) |
258
+ | 04 | `04-custom-tools.ts` | Adding your own tools |
231
259
 
232
260
  ```bash
233
261
  cd packages/agent-code/examples
@@ -237,11 +265,11 @@ cp .env.example .env
237
265
  npx tsx examples/01-basic.ts
238
266
  ```
239
267
 
240
- ## Architecture
268
+ ## Layering
241
269
 
242
270
  This library is built on:
243
271
 
244
- - **@cuylabs/agent-core** - Core agent infrastructure (sessions, streaming, tool execution)
272
+ - **@cuylabs/agent-core** - Core agent infrastructure (sessions, execution, tool orchestration)
245
273
  - **Vercel AI SDK** - LLM interaction and multi-provider support
246
274
  - **Zod** - Parameter validation for tools
247
275
 
@@ -5,8 +5,12 @@ var DEFAULT_TIMEOUT = 2 * 60 * 1e3;
5
5
  var bashParameters = z.object({
6
6
  command: z.string().describe("The shell command to execute"),
7
7
  timeout: z.number().optional().describe("Timeout in milliseconds (default: 120000)"),
8
- workdir: z.string().optional().describe("Working directory to run the command in. Defaults to current working directory."),
9
- description: z.string().describe("Clear, concise description of what this command does in 5-10 words")
8
+ workdir: z.string().optional().describe(
9
+ "Working directory to run the command in. Defaults to current working directory."
10
+ ),
11
+ description: z.string().describe(
12
+ "Clear, concise description of what this command does in 5-10 words"
13
+ )
10
14
  });
11
15
  async function executeBash(params, host, cwd, abort) {
12
16
  const workdir = params.workdir || cwd;
@@ -35,6 +39,11 @@ IMPORTANT:
35
39
  - Output is automatically truncated if too long
36
40
  - For pager commands, output is piped through 'cat' automatically`,
37
41
  parameters: bashParameters,
42
+ replayPolicy: {
43
+ mode: "manual",
44
+ sideEffectLevel: "external",
45
+ reason: "Arbitrary shell execution may affect processes, services, or systems outside the current turn."
46
+ },
38
47
  async execute(params, ctx) {
39
48
  const host = ctx.host;
40
49
  const result = await executeBash(params, host, ctx.cwd, ctx.abort);
@@ -141,7 +150,9 @@ async function isBinaryFile(filepath, host) {
141
150
  var readParameters = z2.object({
142
151
  filePath: z2.string().describe("The path to the file to read"),
143
152
  offset: z2.number().optional().describe("The line number to start reading from (0-based)"),
144
- limit: z2.number().optional().describe(`The number of lines to read (defaults to ${DEFAULT_READ_LIMIT})`)
153
+ limit: z2.number().optional().describe(
154
+ `The number of lines to read (defaults to ${DEFAULT_READ_LIMIT})`
155
+ )
145
156
  });
146
157
  var readTool = Tool2.define("read", {
147
158
  description: `Read the contents of a file.
@@ -153,6 +164,10 @@ IMPORTANT:
153
164
  - Binary files cannot be read
154
165
  - Images and PDFs are returned as attachments`,
155
166
  parameters: readParameters,
167
+ replayPolicy: {
168
+ sideEffectLevel: "none",
169
+ reason: "Read-only file inspection is safe to replay."
170
+ },
156
171
  async execute(params, ctx) {
157
172
  const host = ctx.host;
158
173
  let filepath = params.filePath;
@@ -338,7 +353,9 @@ function findTextFuzzy(content, searchText) {
338
353
  }
339
354
  var editParameters = z3.object({
340
355
  filePath: z3.string().describe("The absolute path to the file to modify"),
341
- oldString: z3.string().describe("The text to replace (must match exactly or with minor whitespace differences)"),
356
+ oldString: z3.string().describe(
357
+ "The text to replace (must match exactly or with minor whitespace differences)"
358
+ ),
342
359
  newString: z3.string().describe("The text to replace it with (must be different from oldString)"),
343
360
  replaceAll: z3.boolean().optional().describe("Replace all occurrences of oldString (default: false)")
344
361
  });
@@ -352,6 +369,11 @@ IMPORTANT:
352
369
  - Use replaceAll: true to replace all occurrences
353
370
  - For creating new files, use the write tool instead`,
354
371
  parameters: editParameters,
372
+ replayPolicy: {
373
+ mode: "manual",
374
+ sideEffectLevel: "local",
375
+ reason: "Text replacement mutates local files and needs explicit replay control."
376
+ },
355
377
  // Enable automatic baseline capture for turn tracking
356
378
  fileOps: {
357
379
  pathArgs: ["filePath"],
@@ -445,6 +467,11 @@ IMPORTANT:
445
467
  - Parent directories are created automatically
446
468
  - Existing content is completely replaced`,
447
469
  parameters: writeParameters,
470
+ replayPolicy: {
471
+ mode: "manual",
472
+ sideEffectLevel: "local",
473
+ reason: "Replaying a file write can overwrite state unexpectedly."
474
+ },
448
475
  // Enable automatic baseline capture for turn tracking
449
476
  fileOps: {
450
477
  pathArgs: ["filePath"],
@@ -504,11 +531,18 @@ function sq(s) {
504
531
  }
505
532
  var grepParameters = z5.object({
506
533
  pattern: z5.string().describe("The regex pattern to search for in file contents"),
507
- path: z5.string().optional().describe("The directory to search in. Defaults to current working directory."),
508
- include: z5.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")')
534
+ path: z5.string().optional().describe(
535
+ "The directory to search in. Defaults to current working directory."
536
+ ),
537
+ include: z5.string().optional().describe(
538
+ 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'
539
+ )
509
540
  });
510
541
  async function executeGrep(params, host, cwd, abort) {
511
542
  const searchPath = params.path ? path4.isAbsolute(params.path) ? params.path : path4.resolve(cwd, params.path) : cwd;
543
+ if (!await host.exists(searchPath)) {
544
+ return { matches: [], truncated: false };
545
+ }
512
546
  const parts = [
513
547
  "rg",
514
548
  "-nH",
@@ -566,6 +600,10 @@ IMPORTANT:
566
600
  - Results are sorted by modification time (newest first)
567
601
  - Maximum ${MAX_RESULTS} results returned`,
568
602
  parameters: grepParameters,
603
+ replayPolicy: {
604
+ sideEffectLevel: "none",
605
+ reason: "Content search is read-only and safe to repeat."
606
+ },
569
607
  async execute(params, ctx) {
570
608
  if (!params.pattern) {
571
609
  throw new Error("pattern is required");
@@ -607,10 +645,10 @@ async function findFiles(pattern, cwd, host, abort) {
607
645
  const files = [];
608
646
  let truncated = false;
609
647
  try {
610
- const result = await host.exec(
611
- `rg --files --glob ${sq2(pattern)}`,
612
- { cwd, signal: abort }
613
- );
648
+ const result = await host.exec(`rg --files --glob ${sq2(pattern)}`, {
649
+ cwd,
650
+ signal: abort
651
+ });
614
652
  if (result.exitCode === 0 || result.exitCode === 1) {
615
653
  const found = result.stdout.trim().split(/\r?\n/).filter(Boolean);
616
654
  for (const file of found) {
@@ -655,8 +693,12 @@ async function findFiles(pattern, cwd, host, abort) {
655
693
  return { files, truncated };
656
694
  }
657
695
  var globParameters = z6.object({
658
- pattern: z6.string().describe('The glob pattern to match files (e.g. "**/*.ts", "src/**/*.js")'),
659
- path: z6.string().optional().describe("The directory to search in. Defaults to current working directory.")
696
+ pattern: z6.string().describe(
697
+ 'The glob pattern to match files (e.g. "**/*.ts", "src/**/*.js")'
698
+ ),
699
+ path: z6.string().optional().describe(
700
+ "The directory to search in. Defaults to current working directory."
701
+ )
660
702
  });
661
703
  var globTool = Tool6.define("glob", {
662
704
  description: `Find files matching a glob pattern.
@@ -667,10 +709,19 @@ IMPORTANT:
667
709
  - Results are sorted by modification time (newest first)
668
710
  - Maximum ${MAX_RESULTS2} results returned`,
669
711
  parameters: globParameters,
712
+ replayPolicy: {
713
+ sideEffectLevel: "none",
714
+ reason: "Filesystem discovery does not mutate state."
715
+ },
670
716
  async execute(params, ctx) {
671
717
  const host = ctx.host;
672
718
  const searchPath = params.path ? path5.isAbsolute(params.path) ? params.path : path5.resolve(ctx.cwd, params.path) : ctx.cwd;
673
- const { files, truncated } = await findFiles(params.pattern, searchPath, host, ctx.abort);
719
+ const { files, truncated } = await findFiles(
720
+ params.pattern,
721
+ searchPath,
722
+ host,
723
+ ctx.abort
724
+ );
674
725
  if (files.length === 0) {
675
726
  return {
676
727
  title: params.pattern,
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  toolset,
19
19
  writeParameters,
20
20
  writeTool
21
- } from "./chunk-IOXLKDEB.js";
21
+ } from "./chunk-U6W6NJNW.js";
22
22
 
23
23
  // src/index.ts
24
24
  export * from "@cuylabs/agent-core";
@@ -3,7 +3,6 @@ import { z } from 'zod';
3
3
 
4
4
  /**
5
5
  * Bash tool - Execute shell commands
6
- * Based on OpenCode's tool/bash.ts patterns
7
6
  */
8
7
 
9
8
  declare const bashParameters: z.ZodObject<{
@@ -12,13 +11,13 @@ declare const bashParameters: z.ZodObject<{
12
11
  workdir: z.ZodOptional<z.ZodString>;
13
12
  description: z.ZodString;
14
13
  }, "strip", z.ZodTypeAny, {
15
- command: string;
16
14
  description: string;
15
+ command: string;
17
16
  timeout?: number | undefined;
18
17
  workdir?: string | undefined;
19
18
  }, {
20
- command: string;
21
19
  description: string;
20
+ command: string;
22
21
  timeout?: number | undefined;
23
22
  workdir?: string | undefined;
24
23
  }>;
@@ -36,13 +35,13 @@ declare const bashTool: Tool.Info<z.ZodObject<{
36
35
  workdir: z.ZodOptional<z.ZodString>;
37
36
  description: z.ZodString;
38
37
  }, "strip", z.ZodTypeAny, {
39
- command: string;
40
38
  description: string;
39
+ command: string;
41
40
  timeout?: number | undefined;
42
41
  workdir?: string | undefined;
43
42
  }, {
44
- command: string;
45
43
  description: string;
44
+ command: string;
46
45
  timeout?: number | undefined;
47
46
  workdir?: string | undefined;
48
47
  }>, {
@@ -54,7 +53,6 @@ declare const bashTool: Tool.Info<z.ZodObject<{
54
53
 
55
54
  /**
56
55
  * Read tool - Read file contents
57
- * Based on OpenCode's tool/read.ts patterns
58
56
  */
59
57
 
60
58
  declare const readParameters: z.ZodObject<{
@@ -63,12 +61,12 @@ declare const readParameters: z.ZodObject<{
63
61
  limit: z.ZodOptional<z.ZodNumber>;
64
62
  }, "strip", z.ZodTypeAny, {
65
63
  filePath: string;
66
- offset?: number | undefined;
67
64
  limit?: number | undefined;
65
+ offset?: number | undefined;
68
66
  }, {
69
67
  filePath: string;
70
- offset?: number | undefined;
71
68
  limit?: number | undefined;
69
+ offset?: number | undefined;
72
70
  }>;
73
71
  type ReadParams = z.infer<typeof readParameters>;
74
72
  /**
@@ -80,12 +78,12 @@ declare const readTool: Tool.Info<z.ZodObject<{
80
78
  limit: z.ZodOptional<z.ZodNumber>;
81
79
  }, "strip", z.ZodTypeAny, {
82
80
  filePath: string;
83
- offset?: number | undefined;
84
81
  limit?: number | undefined;
82
+ offset?: number | undefined;
85
83
  }, {
86
84
  filePath: string;
87
- offset?: number | undefined;
88
85
  limit?: number | undefined;
86
+ offset?: number | undefined;
89
87
  }>, {
90
88
  totalLines: number;
91
89
  linesRead: number;
@@ -96,7 +94,6 @@ declare const readTool: Tool.Info<z.ZodObject<{
96
94
 
97
95
  /**
98
96
  * Edit tool - Edit files by replacing text
99
- * Based on OpenCode's tool/edit.ts patterns
100
97
  */
101
98
 
102
99
  declare const editParameters: z.ZodObject<{
@@ -144,18 +141,17 @@ declare const editTool: Tool.Info<z.ZodObject<{
144
141
 
145
142
  /**
146
143
  * Write tool - Create or overwrite files
147
- * Based on OpenCode's tool/write.ts patterns
148
144
  */
149
145
 
150
146
  declare const writeParameters: z.ZodObject<{
151
147
  filePath: z.ZodString;
152
148
  content: z.ZodString;
153
149
  }, "strip", z.ZodTypeAny, {
154
- filePath: string;
155
150
  content: string;
156
- }, {
157
151
  filePath: string;
152
+ }, {
158
153
  content: string;
154
+ filePath: string;
159
155
  }>;
160
156
  type WriteParams = z.infer<typeof writeParameters>;
161
157
  /**
@@ -165,11 +161,11 @@ declare const writeTool: Tool.Info<z.ZodObject<{
165
161
  filePath: z.ZodString;
166
162
  content: z.ZodString;
167
163
  }, "strip", z.ZodTypeAny, {
168
- filePath: string;
169
164
  content: string;
170
- }, {
171
165
  filePath: string;
166
+ }, {
172
167
  content: string;
168
+ filePath: string;
173
169
  }>, {
174
170
  filepath: string;
175
171
  created: boolean;
@@ -179,7 +175,6 @@ declare const writeTool: Tool.Info<z.ZodObject<{
179
175
 
180
176
  /**
181
177
  * Grep tool - Search file contents
182
- * Based on OpenCode's tool/grep.ts patterns
183
178
  */
184
179
 
185
180
  declare const grepParameters: z.ZodObject<{
@@ -229,7 +224,6 @@ declare const grepTool: Tool.Info<z.ZodObject<{
229
224
 
230
225
  /**
231
226
  * Glob tool - Find files by pattern
232
- * Based on OpenCode's tool/glob.ts patterns
233
227
  */
234
228
 
235
229
  declare const globParameters: z.ZodObject<{
@@ -18,7 +18,7 @@ import {
18
18
  toolset,
19
19
  writeParameters,
20
20
  writeTool
21
- } from "../chunk-IOXLKDEB.js";
21
+ } from "../chunk-U6W6NJNW.js";
22
22
  export {
23
23
  ToolsetBuilder,
24
24
  bashParameters,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuylabs/agent-code",
3
- "version": "0.1.5",
3
+ "version": "0.5.0",
4
4
  "description": "Embeddable AI coding agent built on @cuylabs/agent-core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "ai": "^6.0.67",
26
26
  "zod": "^3.24.0",
27
- "@cuylabs/agent-core": "^0.3.0"
27
+ "@cuylabs/agent-core": "^0.5.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "@ai-sdk/anthropic": "^3.0.0",
@@ -76,6 +76,7 @@
76
76
  "build": "tsup src/index.ts src/tools/index.ts --format esm --dts --clean",
77
77
  "dev": "tsup src/index.ts src/tools/index.ts --format esm --dts --watch",
78
78
  "typecheck": "tsc --noEmit",
79
- "test": "vitest run"
79
+ "test": "vitest run",
80
+ "test:watch": "vitest"
80
81
  }
81
82
  }