goatchain 0.0.2 → 0.0.3

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.
Files changed (5) hide show
  1. package/README.md +173 -236
  2. package/README.zh.md +430 -0
  3. package/dist/index.d.ts +2958 -1802
  4. package/dist/index.js +270 -136
  5. package/package.json +13 -8
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
  [![npm version](https://badge.fury.io/js/goatchain.svg)](https://badge.fury.io/js/goatchain)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
+ [中文文档 (Chinese Documentation)](README.zh.md)
9
+
8
10
  ## ✨ Features
9
11
 
10
12
  - **🔄 Agentic Loop** - Automatic tool calling loop with configurable max iterations
@@ -21,12 +23,42 @@
21
23
  pnpm add goatchain
22
24
  ```
23
25
 
24
- ## 📖 Documentation
26
+ ## 🚀 Quick Start
27
+
28
+ Simple Agent Loop example:
25
29
 
26
- 完整的文档请查看 [docs/](./docs/) 目录:
30
+ ```typescript
31
+ import process from 'node:process'
32
+ import { Agent, createModel, createOpenAIAdapter } from 'goatchain'
27
33
 
28
- - [快速开始](./docs/getting-started.md) - 创建你的第一个 Agent Loop
29
- - [文档索引](./docs/README.md) - 所有文档的完整列表
34
+ // Create model
35
+ const model = createModel({
36
+ adapter: createOpenAIAdapter({
37
+ defaultModelId: 'gpt-4o',
38
+ apiKey: process.env.OPENAI_API_KEY!,
39
+ }),
40
+ })
41
+
42
+ // Create Agent
43
+ const agent = new Agent({
44
+ name: 'Simple Assistant',
45
+ systemPrompt: 'You are a helpful assistant.',
46
+ model,
47
+ })
48
+
49
+ // Stream responses
50
+ const session = await agent.createSession()
51
+ session.send('Hello!')
52
+ for await (const event of session.receive()) {
53
+ if (event.type === 'text_delta') {
54
+ process.stdout.write(event.delta)
55
+ } else if (event.type === 'done') {
56
+ console.log('\nDone:', event.stopReason)
57
+ }
58
+ }
59
+ ```
60
+
61
+ 📖 **Full Documentation**: See [docs/getting-started.md](./docs/getting-started.md) for more examples and complete guide.
30
62
 
31
63
  ## 🧰 CLI
32
64
 
@@ -48,11 +80,14 @@ Commands:
48
80
 
49
81
  - `/help` help
50
82
  - `/model <id>` switch model id (OpenAI)
51
- - `/set <k> <v>` set request params (e.g. `temperature`, `maxTokens`, `topP`)
83
+ - `/plan [on|off|toggle]` toggle plan mode (plan confirm → execute)
84
+ - `/approvals` select approval mode (interactive)
85
+ - `/set <k> <v>` set request params (e.g. `temperature`, `maxTokens`)
52
86
  - `/unset <k>` clear a request param
53
87
  - `/params` show current request params
54
88
  - `/base-url <url>` set base URL
55
89
  - `/api-key <key>` set API key (not printed)
90
+ - `/web-search-key <key>` set Serper API key for WebSearch tool (not printed)
56
91
  - `/tools` list enabled tools (Read/Write/Edit/Glob/Grep/WebSearch*)
57
92
  - `/sessions` list and pick a saved session
58
93
  - `/use <sessionId>` restore a saved session (prints recent history)
@@ -65,9 +100,21 @@ Requires `OPENAI_API_KEY` in the environment.
65
100
  Web search (optional):
66
101
 
67
102
  - Set `SERPER_API_KEY` (or `GOATCHAIN_SERPER_API_KEY`) to enable the builtin `WebSearch` tool for up-to-date info like weather.
103
+ - In interactive mode, you can also run `/web-search-key <key>` to persist the key into the workspace config.
68
104
  - You can also set it in `./.goatchain/config.json` (workspace-scoped, gitignored):
69
105
  - `{"tools":{"webSearch":{"apiKey":"...","apiEndpoint":"...","numResults":10}}}`
70
106
 
107
+ Plan mode (interactive planning):
108
+
109
+ - Enable with `/plan on` to enter a plan-first workflow:
110
+ 1. Agent explores the codebase and researches your request
111
+ 2. Agent may ask clarifying questions using the `AskUserQuestion` tool to understand your preferences (e.g., library choices, architectural decisions)
112
+ 3. Agent creates a structured plan using `TodoPlan` (3-8 steps)
113
+ 4. You review and approve the plan
114
+ 5. Agent executes the approved plan
115
+ - During planning, file modifications are blocked (read-only phase)
116
+ - Set `GOATCHAIN_PLAN_MODE=1` or configure in `.goatchain/config.json` to enable by default
117
+
71
118
  Local persistence (workspace-scoped):
72
119
 
73
120
  - Config and sessions are saved under `./.goatchain/` (auto-created).
@@ -76,7 +123,42 @@ Local persistence (workspace-scoped):
76
123
  DeepSeek thinking mode compatibility:
77
124
 
78
125
  - Some OpenAI-compatible gateways (e.g. DeepSeek thinking mode) require `reasoning_content` to be present on assistant messages that contain `tool_calls` (and may reject empty strings). GoatChain will attach the accumulated thinking content when available.
79
- - If you use DeepSeek via a proxy where GoatChain can't detect it from `baseUrl`/`modelId`, set `openai.compat.requireReasoningContentForToolCalls=true` in `./.goatchain/config.json`.
126
+ - If you use DeepSeek via a proxy where GoatChain can't detect it from `baseUrl`/`modelId`, you can enable this explicitly:
127
+ - Interactive: Run `/settings` and toggle "Interleaved Thinking"
128
+ - Config file: Set `openai.compat.interleavedThinking=true` in `./.goatchain/config.json`
129
+
130
+ ## 🔌 ACP Server
131
+
132
+ GoatChain can be exposed as an ACP (Agent Client Protocol) server for integration with editors like Zed.
133
+
134
+ ```bash
135
+ pnpm acp-server
136
+ ```
137
+
138
+ **Configuration for Zed** (`settings.json`):
139
+
140
+ ```json
141
+ {
142
+ "agent_servers": {
143
+ "goatchain": {
144
+ "command": "pnpm",
145
+ "args": ["--dir", "/path/to/GoatChain", "acp-server"]
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ **Features:**
152
+ - File operations (read, write, edit)
153
+ - Search tools (glob, grep, ast-grep)
154
+ - Web search and task management
155
+ - Same tool set as CLI agent mode
156
+
157
+ **Available servers:**
158
+ - `pnpm acp-server` - Simple agent (recommended)
159
+ - `pnpm acp-server:plan` - With plan mode (experimental)
160
+
161
+ 📖 **Full Documentation**: See [docs/acp-server.md](./docs/acp-server.md) for configuration and troubleshooting.
80
162
 
81
163
  ## 🏗️ Architecture
82
164
 
@@ -84,27 +166,27 @@ DeepSeek thinking mode compatibility:
84
166
  classDiagram
85
167
  direction TB
86
168
 
87
- class Agent {
88
- +id: string
89
- +name: string
90
- +systemPrompt: string
91
- +model: BaseModel
92
- +tools: ToolRegistry
93
- +stateStore: StateStore
94
- +sessionManager: BaseSessionManager
95
- +stats: AgentStats
96
- +use(middleware): this
97
- +createSession(options): BaseSession
98
- +resumeSession(sessionId, options): BaseSession
99
- +setModel(modelOrRef): void
100
- }
101
-
102
- class BaseModel {
103
- <<abstract>>
104
- +modelId: string
105
- +invoke(messages, options): Promise~ChatResponse~
106
- +stream(messages, options): AsyncIterable~StreamEvent~
107
- }
169
+ class Agent {
170
+ +id: string
171
+ +name: string
172
+ +systemPrompt: string
173
+ +model: ModelClient
174
+ +tools: ToolRegistry
175
+ +stateStore: StateStore
176
+ +sessionManager: BaseSessionManager
177
+ +stats: AgentStats
178
+ +use(middleware): this
179
+ +createSession(options): BaseSession
180
+ +resumeSession(sessionId, options): BaseSession
181
+ +setModel(modelOrRef): void
182
+ }
183
+
184
+ class ModelClient {
185
+ <<interface>>
186
+ +modelId: string
187
+ +stream(request): AsyncIterable~ModelStreamEvent~
188
+ +run?(request): Promise~ModelRunResult~
189
+ }
108
190
 
109
191
  class StateStore {
110
192
  <<interface>>
@@ -113,7 +195,7 @@ classDiagram
113
195
  +saveCheckpoint(checkpoint): Promise~void~
114
196
  +loadCheckpoint(sessionId): Promise~AgentLoopCheckpoint~
115
197
  +deleteCheckpoint(sessionId): Promise~void~
116
- +listCheckpoints(agentId): Promise~AgentLoopCheckpoint[]~
198
+ +listCheckpoints(): Promise~AgentLoopCheckpoint[]~
117
199
  }
118
200
 
119
201
  class BaseTool {
@@ -135,7 +217,6 @@ classDiagram
135
217
  class BaseSession {
136
218
  <<abstract>>
137
219
  +id: string
138
- +agentId: string
139
220
  +status: SessionStatus
140
221
  +messages: Message[]
141
222
  +usage: Usage
@@ -148,9 +229,9 @@ classDiagram
148
229
 
149
230
  class BaseSessionManager {
150
231
  <<abstract>>
151
- +create(agentId, metadata): Promise~BaseSession~
232
+ +create(sessionId?): Promise~BaseSession~
152
233
  +get(sessionId): Promise~BaseSession~
153
- +list(agentId): Promise~BaseSession[]~
234
+ +list(): Promise~BaseSession[]~
154
235
  +destroy(sessionId): Promise~void~
155
236
  }
156
237
 
@@ -161,7 +242,6 @@ classDiagram
161
242
 
162
243
  class AgentLoopState {
163
244
  +sessionId: string
164
- +agentId: string
165
245
  +messages: Message[]
166
246
  +iteration: number
167
247
  +pendingToolCalls: ToolCallWithResult[]
@@ -170,7 +250,7 @@ classDiagram
170
250
  +usage: Usage
171
251
  }
172
252
 
173
- Agent --> BaseModel : uses
253
+ Agent --> ModelClient : uses
174
254
  Agent --> ToolRegistry : uses
175
255
  Agent --> StateStore : uses
176
256
  Agent --> BaseSessionManager : uses
@@ -180,80 +260,98 @@ classDiagram
180
260
  BaseSessionManager --> BaseSession : manages
181
261
  ```
182
262
 
183
- ## 🚀 Quick Start
263
+ ## 🧅 Middleware Pattern
264
+
265
+ GoatChain uses a Koa-style onion model for middleware. Each middleware wraps around the core execution:
266
+
267
+ ```
268
+ outer:before → inner:before → exec (model.stream) → inner:after → outer:after
269
+ ```
184
270
 
185
- 最简单的 Agent Loop 示例:
271
+ ### Named Middleware
272
+
273
+ Middlewares can be named for easier management and removal:
186
274
 
187
275
  ```typescript
188
- import process from 'node:process'
189
- import { Agent, createModel, createOpenAIAdapter } from 'goatchain'
276
+ // Add named middleware (recommended)
277
+ agent.use(async (state, next) => {
278
+ const start = Date.now()
279
+ console.log(`[${state.iteration}] Before model call`)
280
+ const nextState = await next(state)
281
+ console.log(`[${state.iteration}] After model call (${Date.now() - start}ms)`)
282
+ return nextState
283
+ }, 'logging')
190
284
 
191
- // 创建模型
192
- const model = createModel({
193
- adapters: [
194
- createOpenAIAdapter({
195
- defaultModelId: 'gpt-4o',
196
- apiKey: process.env.OPENAI_API_KEY!,
197
- }),
198
- ],
199
- })
285
+ // Remove by name
286
+ agent.removeMiddleware('logging')
200
287
 
201
- // 创建 Agent
202
- const agent = new Agent({
203
- name: 'Simple Assistant',
204
- systemPrompt: 'You are a helpful assistant.',
205
- model,
206
- })
288
+ // View all middleware names
289
+ console.log(agent.middlewareNames) // ['logging', 'compression', ...]
207
290
 
208
- // 流式处理响应
209
- const session = await agent.createSession()
210
- session.send('Hello!')
211
- for await (const event of session.receive()) {
212
- if (event.type === 'text_delta') {
213
- process.stdout.write(event.delta)
214
- } else if (event.type === 'done') {
215
- console.log('\n完成:', event.stopReason)
216
- }
217
- }
291
+ // Use unsubscribe function
292
+ const unsubscribe = agent.use(middleware, 'temp')
293
+ unsubscribe() // Remove middleware
218
294
  ```
219
295
 
220
- 📖 **详细文档**: 查看 [docs/getting-started.md](./docs/getting-started.md) 了解更多示例和完整指南。
221
-
222
- ## 🧅 Middleware Pattern
296
+ ### Built-in Middleware Default Names
223
297
 
224
- GoatChain uses a Koa-style onion model for middleware. Each middleware wraps around the core execution:
298
+ GoatChain's built-in middleware factories automatically provide default names:
225
299
 
226
- ```
227
- outer:before inner:before exec (model.stream) inner:after → outer:after
300
+ ```typescript
301
+ // Plan mode middleware - automatically named 'plan-mode'
302
+ agent.use(createPlanModeMiddleware())
303
+
304
+ // Context compression middleware - automatically named 'context-compression'
305
+ agent.use(createContextCompressionMiddleware({
306
+ maxTokens: 128000,
307
+ }))
308
+
309
+ // View all middleware
310
+ console.log(agent.middlewareNames)
311
+ // Output: ['plan-mode', 'context-compression']
312
+
313
+ // Remove by default name
314
+ agent.removeMiddleware('plan-mode')
315
+
316
+ // Or customize the name
317
+ agent.use(createPlanModeMiddleware({ name: 'my-plan' }))
318
+ agent.use(createContextCompressionMiddleware({
319
+ maxTokens: 128000,
320
+ }), 'my-compression') // Override default name
228
321
  ```
229
322
 
323
+ ### Middleware Examples
324
+
230
325
  ```typescript
231
326
  // Logging middleware
232
327
  agent.use(async (state, next) => {
233
328
  const start = Date.now()
234
329
  console.log(`[${state.iteration}] Before model call`)
235
330
 
236
- await next() // Execute model stream
331
+ const nextState = await next(state) // Execute model stream
237
332
 
238
333
  console.log(`[${state.iteration}] After model call (${Date.now() - start}ms)`)
239
- })
334
+ return nextState
335
+ }, 'logging')
240
336
 
241
337
  // Error handling middleware
242
338
  agent.use(async (state, next) => {
243
339
  try {
244
- await next()
245
- } catch (error) {
340
+ return await next(state)
341
+ }
342
+ catch (error) {
246
343
  state.shouldContinue = false
247
344
  state.stopReason = 'error'
248
345
  state.error = error
346
+ return state
249
347
  }
250
- })
348
+ }, 'error-handler')
251
349
 
252
350
  // Rate limiting middleware
253
351
  agent.use(async (state, next) => {
254
352
  await rateLimiter.acquire()
255
- await next()
256
- })
353
+ return next(state)
354
+ }, 'rate-limiter')
257
355
  ```
258
356
 
259
357
  ## 📡 Event Types
@@ -340,92 +438,6 @@ Available state stores:
340
438
  - `FileStateStore` - File-based persistence
341
439
  - `InMemoryStateStore` - In-memory (for testing)
342
440
 
343
- ## 🔧 Session Management
344
-
345
- Sessions represent individual conversations with per-session configuration overrides:
346
-
347
- ```typescript
348
- // Create session
349
- const session = await sessionManager.create(agent.id, {
350
- customField: 'value',
351
- })
352
-
353
- // Session-level overrides
354
- session.setModelOverride({ modelId: 'gpt-4o-mini' })
355
- session.setSystemPromptOverride('You are a concise assistant.')
356
- session.disableTools(['dangerous_tool'])
357
-
358
- // Track session activity
359
- session.addMessage({ role: 'user', content: 'Hello!' })
360
- session.addUsage({ promptTokens: 10, completionTokens: 5, totalTokens: 15 })
361
- session.recordResponse(1500) // ms
362
-
363
- // Get session snapshot for persistence
364
- const snapshot = session.toSnapshot()
365
- ```
366
-
367
- ## 📁 Project Structure
368
-
369
- ```
370
- src/
371
- ├── index.ts # Public exports
372
- ├── types/
373
- │ ├── index.ts # Re-export all types
374
- │ ├── message.ts # Message types (User, Assistant, Tool, System)
375
- │ ├── event.ts # Stream event types
376
- │ ├── common.ts # Shared types (ToolCall, Usage, JSONSchema)
377
- │ └── snapshot.ts # Snapshot types (Agent, Session)
378
- ├── model/
379
- │ ├── index.ts
380
- │ ├── base.ts # BaseModel abstract class
381
- │ └── types.ts # Model-specific types
382
- ├── state/
383
- │ ├── index.ts
384
- │ ├── stateStore.ts # StateStore interface
385
- │ ├── FileStateStore.ts # File-based state storage
386
- │ └── InMemoryStateStore.ts # In-memory state storage
387
- ├── tool/
388
- │ ├── index.ts
389
- │ ├── base.ts # BaseTool abstract class
390
- │ └── registry.ts # ToolRegistry class
391
- ├── session/
392
- │ ├── index.ts
393
- │ ├── base.ts # BaseSession abstract class
394
- │ └── manager.ts # BaseSessionManager abstract class
395
- └── agent/
396
- ├── index.ts
397
- ├── agent.ts # Agent class
398
- ├── types.ts # Agent types (AgentLoopState, AgentInput, etc.)
399
- ├── middleware.ts # Middleware compose function
400
- └── errors.ts # Agent-specific errors
401
- ```
402
-
403
- ## 🛡️ Error Handling
404
-
405
- ```typescript
406
- import { AgentAbortError, AgentMaxIterationsError } from 'goatchain'
407
-
408
- // Cancellation support
409
- const controller = new AbortController()
410
-
411
- try {
412
- const session = await agent.createSession({ maxIterations: 5 })
413
- session.send('Hello', { signal: controller.signal })
414
- for await (const event of session.receive()) {
415
- // Handle events...
416
- }
417
- } catch (error) {
418
- if (error instanceof AgentAbortError) {
419
- console.log('Agent was cancelled')
420
- } else if (error instanceof AgentMaxIterationsError) {
421
- console.log('Max iterations reached')
422
- }
423
- }
424
-
425
- // Cancel from another context
426
- controller.abort()
427
- ```
428
-
429
441
  ## 📖 API Reference
430
442
 
431
443
  ### Agent
@@ -446,81 +458,6 @@ controller.abort()
446
458
  | `receive(options?)` | Stream events, resuming from checkpoint if present |
447
459
  | `messages` | Conversation history |
448
460
 
449
- ### CreateSessionOptions
450
-
451
- | Property | Type | Description |
452
- | ---------------- | ------------- | ---------------------------------------- |
453
- | `model?` | `ModelRef` | Optional model override for this session |
454
- | `maxIterations?` | `number` | Max loop iterations (default: 10) |
455
- | `hooks?` | `ToolHooks` | Tool execution hooks |
456
- | `requestParams?` | `object` | Model request parameters |
457
-
458
- ### SendOptions
459
-
460
- | Property | Type | Description |
461
- | ---------------- | ------------- | ---------------------------------------- |
462
- | `signal?` | `AbortSignal` | Cancellation support |
463
- | `toolContext?` | `object` | Tool execution context input |
464
-
465
- ## 🔄 Model Switching
466
-
467
- ### Per-Request Model Override
468
-
469
- Temporarily use a different model for a single request:
470
-
471
- ```typescript
472
- const session = await agent.createSession({
473
- model: { provider: 'openai', modelId: 'gpt-4' }, // Use GPT-4 for this session
474
- })
475
- session.send('Explain quantum physics')
476
- for await (const event of session.receive()) {
477
- // ...
478
- }
479
- ```
480
-
481
- ### Persistent Model Switch
482
-
483
- Change the default model for all subsequent requests:
484
-
485
- ```typescript
486
- // Switch model at runtime
487
- model.setModelId('gpt-4')
488
-
489
- // All subsequent requests will use the new model
490
- const session = await agent.createSession()
491
- session.send('Hello')
492
- for await (const event of session.receive()) {
493
- // Uses gpt-4
494
- }
495
- ```
496
-
497
- ### Pin Default Model (Overrides Routing)
498
-
499
- If your `ModelClient` supports routing (e.g. created via `createModel()`), you can pin a specific default model at the agent level:
500
-
501
- ```typescript
502
- agent.setModel({ provider: 'openai', modelId: 'gpt-4' })
503
- ```
504
-
505
- ### Multi-Provider Fallback
506
-
507
- Configure multiple models with automatic fallback:
508
-
509
- ```typescript
510
- const model = createModel({
511
- adapters: [
512
- createOpenAIAdapter({ defaultModelId: 'gpt-4' }),
513
- createAnthropicAdapter({ defaultModelId: 'claude-3' }),
514
- ],
515
- routing: {
516
- fallbackOrder: [
517
- { provider: 'openai', modelId: 'gpt-4' }, // Try first
518
- { provider: 'anthropic', modelId: 'claude-3' }, // Fallback if first fails
519
- ],
520
- },
521
- })
522
- ```
523
-
524
461
  ## 📄 License
525
462
 
526
463
  MIT © [Simon He](https://github.com/Simon-He95)