@cuylabs/agent-code 0.1.6 → 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,8 +531,12 @@ 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;
@@ -569,6 +600,10 @@ IMPORTANT:
569
600
  - Results are sorted by modification time (newest first)
570
601
  - Maximum ${MAX_RESULTS} results returned`,
571
602
  parameters: grepParameters,
603
+ replayPolicy: {
604
+ sideEffectLevel: "none",
605
+ reason: "Content search is read-only and safe to repeat."
606
+ },
572
607
  async execute(params, ctx) {
573
608
  if (!params.pattern) {
574
609
  throw new Error("pattern is required");
@@ -610,10 +645,10 @@ async function findFiles(pattern, cwd, host, abort) {
610
645
  const files = [];
611
646
  let truncated = false;
612
647
  try {
613
- const result = await host.exec(
614
- `rg --files --glob ${sq2(pattern)}`,
615
- { cwd, signal: abort }
616
- );
648
+ const result = await host.exec(`rg --files --glob ${sq2(pattern)}`, {
649
+ cwd,
650
+ signal: abort
651
+ });
617
652
  if (result.exitCode === 0 || result.exitCode === 1) {
618
653
  const found = result.stdout.trim().split(/\r?\n/).filter(Boolean);
619
654
  for (const file of found) {
@@ -658,8 +693,12 @@ async function findFiles(pattern, cwd, host, abort) {
658
693
  return { files, truncated };
659
694
  }
660
695
  var globParameters = z6.object({
661
- pattern: z6.string().describe('The glob pattern to match files (e.g. "**/*.ts", "src/**/*.js")'),
662
- 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
+ )
663
702
  });
664
703
  var globTool = Tool6.define("glob", {
665
704
  description: `Find files matching a glob pattern.
@@ -670,10 +709,19 @@ IMPORTANT:
670
709
  - Results are sorted by modification time (newest first)
671
710
  - Maximum ${MAX_RESULTS2} results returned`,
672
711
  parameters: globParameters,
712
+ replayPolicy: {
713
+ sideEffectLevel: "none",
714
+ reason: "Filesystem discovery does not mutate state."
715
+ },
673
716
  async execute(params, ctx) {
674
717
  const host = ctx.host;
675
718
  const searchPath = params.path ? path5.isAbsolute(params.path) ? params.path : path5.resolve(ctx.cwd, params.path) : ctx.cwd;
676
- 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
+ );
677
725
  if (files.length === 0) {
678
726
  return {
679
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-LMWOKL6B.js";
21
+ } from "./chunk-U6W6NJNW.js";
22
22
 
23
23
  // src/index.ts
24
24
  export * from "@cuylabs/agent-core";
@@ -11,13 +11,13 @@ declare const bashParameters: z.ZodObject<{
11
11
  workdir: z.ZodOptional<z.ZodString>;
12
12
  description: z.ZodString;
13
13
  }, "strip", z.ZodTypeAny, {
14
- command: string;
15
14
  description: string;
15
+ command: string;
16
16
  timeout?: number | undefined;
17
17
  workdir?: string | undefined;
18
18
  }, {
19
- command: string;
20
19
  description: string;
20
+ command: string;
21
21
  timeout?: number | undefined;
22
22
  workdir?: string | undefined;
23
23
  }>;
@@ -35,13 +35,13 @@ declare const bashTool: Tool.Info<z.ZodObject<{
35
35
  workdir: z.ZodOptional<z.ZodString>;
36
36
  description: z.ZodString;
37
37
  }, "strip", z.ZodTypeAny, {
38
- command: string;
39
38
  description: string;
39
+ command: string;
40
40
  timeout?: number | undefined;
41
41
  workdir?: string | undefined;
42
42
  }, {
43
- command: string;
44
43
  description: string;
44
+ command: string;
45
45
  timeout?: number | undefined;
46
46
  workdir?: string | undefined;
47
47
  }>, {
@@ -61,12 +61,12 @@ declare const readParameters: z.ZodObject<{
61
61
  limit: z.ZodOptional<z.ZodNumber>;
62
62
  }, "strip", z.ZodTypeAny, {
63
63
  filePath: string;
64
- offset?: number | undefined;
65
64
  limit?: number | undefined;
65
+ offset?: number | undefined;
66
66
  }, {
67
67
  filePath: string;
68
- offset?: number | undefined;
69
68
  limit?: number | undefined;
69
+ offset?: number | undefined;
70
70
  }>;
71
71
  type ReadParams = z.infer<typeof readParameters>;
72
72
  /**
@@ -78,12 +78,12 @@ declare const readTool: Tool.Info<z.ZodObject<{
78
78
  limit: z.ZodOptional<z.ZodNumber>;
79
79
  }, "strip", z.ZodTypeAny, {
80
80
  filePath: string;
81
- offset?: number | undefined;
82
81
  limit?: number | undefined;
82
+ offset?: number | undefined;
83
83
  }, {
84
84
  filePath: string;
85
- offset?: number | undefined;
86
85
  limit?: number | undefined;
86
+ offset?: number | undefined;
87
87
  }>, {
88
88
  totalLines: number;
89
89
  linesRead: number;
@@ -147,11 +147,11 @@ declare const writeParameters: z.ZodObject<{
147
147
  filePath: z.ZodString;
148
148
  content: z.ZodString;
149
149
  }, "strip", z.ZodTypeAny, {
150
- filePath: string;
151
150
  content: string;
152
- }, {
153
151
  filePath: string;
152
+ }, {
154
153
  content: string;
154
+ filePath: string;
155
155
  }>;
156
156
  type WriteParams = z.infer<typeof writeParameters>;
157
157
  /**
@@ -161,11 +161,11 @@ declare const writeTool: Tool.Info<z.ZodObject<{
161
161
  filePath: z.ZodString;
162
162
  content: z.ZodString;
163
163
  }, "strip", z.ZodTypeAny, {
164
- filePath: string;
165
164
  content: string;
166
- }, {
167
165
  filePath: string;
166
+ }, {
168
167
  content: string;
168
+ filePath: string;
169
169
  }>, {
170
170
  filepath: string;
171
171
  created: boolean;
@@ -18,7 +18,7 @@ import {
18
18
  toolset,
19
19
  writeParameters,
20
20
  writeTool
21
- } from "../chunk-LMWOKL6B.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.6",
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.4.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
  }