goatchain 0.0.11 β 0.0.13
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 +1143 -254
- package/dist/acp-adapter/ProtocolConverter.d.ts +1 -1
- package/dist/acp-server.d.ts +4 -2
- package/dist/agent/types.d.ts +12 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +328 -213
- package/dist/middleware/contextCompressionMiddleware.d.ts +21 -1
- package/dist/middleware/gitUtils.d.ts +5 -0
- package/dist/middleware/reviewMiddleware.d.ts +85 -0
- package/dist/model/types.d.ts +9 -0
- package/dist/session/session.d.ts +2 -1
- package/dist/state/FileStateStore.d.ts +3 -0
- package/dist/state/types.d.ts +19 -0
- package/dist/tool/builtin/astGrepReplace.d.ts +1 -1
- package/dist/tool/builtin/interactive-bash/index.d.ts +1 -1
- package/dist/types/snapshot.d.ts +7 -0
- package/package.json +11 -8
- package/README.zh.md +0 -39
package/README.md
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# GoatChain π
|
|
1
|
+
# GoatChain SDK Developer Guide π
|
|
2
2
|
|
|
3
|
-
> A
|
|
3
|
+
> A TypeScript SDK for building AI agents with streaming support, tool calling, and middleware pattern.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/js/goatchain)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
[δΈζζζ‘£ (Chinese Documentation)](README.zh.md)
|
|
9
|
-
|
|
10
|
-
## β¨ Features
|
|
11
|
-
|
|
12
|
-
- **π Agentic Loop** - Automatic tool calling loop with configurable max iterations
|
|
13
|
-
- **π‘ Streaming First** - Real-time streaming responses with detailed events
|
|
14
|
-
- **π§
Middleware Pattern** - Koa-style onion model for extensible hooks
|
|
15
|
-
- **π§ Tool System** - Easy-to-use tool registration and execution
|
|
16
|
-
- **πΎ State Management** - Two-level state store (Agent + Session level)
|
|
17
|
-
- **πΈ Snapshot/Restore** - Full persistence support for agents and sessions
|
|
18
|
-
- **π― TypeScript Native** - Full type safety with comprehensive type exports
|
|
19
|
-
|
|
20
8
|
## π¦ Installation
|
|
21
9
|
|
|
22
10
|
```bash
|
|
23
11
|
pnpm add goatchain
|
|
12
|
+
# or
|
|
13
|
+
npm install goatchain
|
|
14
|
+
# or
|
|
15
|
+
bun add goatchain
|
|
24
16
|
```
|
|
25
17
|
|
|
18
|
+
## π― Core Concepts
|
|
19
|
+
|
|
20
|
+
GoatChain SDK is built around three core components:
|
|
21
|
+
|
|
22
|
+
- **Agent** - The main orchestrator that manages the agent loop, middleware, and tools
|
|
23
|
+
- **Session** - A conversation context that handles message history and streaming
|
|
24
|
+
- **ModelClient** - Abstraction layer for LLM providers (OpenAI, Anthropic, etc.)
|
|
25
|
+
|
|
26
26
|
## π Quick Start
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
### Basic Usage
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
31
|
import process from 'node:process'
|
|
32
32
|
import { Agent, createModel, createOpenAIAdapter } from 'goatchain'
|
|
33
33
|
|
|
34
|
-
// Create model
|
|
34
|
+
// 1. Create model client
|
|
35
35
|
const model = createModel({
|
|
36
36
|
adapter: createOpenAIAdapter({
|
|
37
37
|
defaultModelId: 'gpt-4o',
|
|
@@ -39,16 +39,18 @@ const model = createModel({
|
|
|
39
39
|
}),
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
// Create
|
|
42
|
+
// 2. Create agent
|
|
43
43
|
const agent = new Agent({
|
|
44
44
|
name: 'Simple Assistant',
|
|
45
45
|
systemPrompt: 'You are a helpful assistant.',
|
|
46
46
|
model,
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
-
//
|
|
49
|
+
// 3. Create session and interact
|
|
50
50
|
const session = await agent.createSession()
|
|
51
51
|
session.send('Hello!')
|
|
52
|
+
|
|
53
|
+
// 4. Stream responses
|
|
52
54
|
for await (const event of session.receive()) {
|
|
53
55
|
if (event.type === 'text_delta') {
|
|
54
56
|
process.stdout.write(event.delta)
|
|
@@ -58,182 +60,381 @@ for await (const event of session.receive()) {
|
|
|
58
60
|
}
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
## π‘ Session Management
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
Session is the core component for managing conversations. It handles message history, state persistence, and event streaming.
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
### Creating Sessions
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
```typescript
|
|
70
|
+
// Create a new session
|
|
71
|
+
const session = await agent.createSession()
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
// Create session with custom ID
|
|
74
|
+
const session = await agent.createSession({ id: 'my-session-id' })
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
// Create session with configuration overrides
|
|
77
|
+
const session = await agent.createSession({
|
|
78
|
+
configOverride: {
|
|
79
|
+
maxIterations: 10,
|
|
80
|
+
maxConcurrentToolCalls: 3,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
73
83
|
```
|
|
74
84
|
|
|
75
|
-
|
|
85
|
+
### Session Configuration Options
|
|
76
86
|
|
|
77
|
-
```
|
|
78
|
-
|
|
87
|
+
```typescript
|
|
88
|
+
interface SessionConfigOverride {
|
|
89
|
+
maxIterations?: number // Max agent loop iterations (default: 5)
|
|
90
|
+
maxConcurrentToolCalls?: number // Max parallel tool calls (default: 5)
|
|
91
|
+
temperature?: number // Model temperature
|
|
92
|
+
maxTokens?: number // Max output tokens
|
|
93
|
+
topP?: number // Nucleus sampling parameter
|
|
94
|
+
}
|
|
79
95
|
```
|
|
80
96
|
|
|
81
|
-
###
|
|
97
|
+
### Sending Messages
|
|
82
98
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
99
|
+
```typescript
|
|
100
|
+
// Simple text message
|
|
101
|
+
session.send('What is the weather today?')
|
|
102
|
+
|
|
103
|
+
// Multiple messages in sequence
|
|
104
|
+
session.send('First question')
|
|
105
|
+
// Wait for response...
|
|
106
|
+
session.send('Follow-up question')
|
|
107
|
+
|
|
108
|
+
// Message with options
|
|
109
|
+
session.send('Analyze this data', {
|
|
110
|
+
temperature: 0.7,
|
|
111
|
+
maxTokens: 2000,
|
|
112
|
+
})
|
|
87
113
|
```
|
|
88
114
|
|
|
89
|
-
|
|
115
|
+
### Receiving Events
|
|
90
116
|
|
|
91
|
-
|
|
92
|
-
- Slash commands in the input: `/settings`, `/approvals`, `/sessions`, `/new` (Chat also supports `/redo`)
|
|
117
|
+
The `receive()` method returns an async generator that streams events:
|
|
93
118
|
|
|
94
|
-
|
|
119
|
+
```typescript
|
|
120
|
+
for await (const event of session.receive()) {
|
|
121
|
+
switch (event.type) {
|
|
122
|
+
case 'text_delta':
|
|
123
|
+
// Partial text from LLM
|
|
124
|
+
process.stdout.write(event.delta)
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
case 'tool_call_start':
|
|
128
|
+
console.log(`\nCalling tool: ${event.name}`)
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
case 'tool_result':
|
|
132
|
+
console.log(`Tool result: ${event.result}`)
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
case 'iteration_end':
|
|
136
|
+
console.log(`\nIteration ${event.iteration} complete`)
|
|
137
|
+
console.log(`Tokens used: ${event.usage?.totalTokens}`)
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
case 'done':
|
|
141
|
+
console.log(`\nConversation done: ${event.stopReason}`)
|
|
142
|
+
console.log(`Total tokens: ${event.usage?.totalTokens}`)
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
case 'error':
|
|
146
|
+
console.error(`Error: ${event.error}`)
|
|
147
|
+
break
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
95
151
|
|
|
96
|
-
|
|
152
|
+
### Session Event Types
|
|
153
|
+
|
|
154
|
+
| Event Type | Description | Key Fields |
|
|
155
|
+
|------------|-------------|------------|
|
|
156
|
+
| `iteration_start` | Agent loop iteration begins | `iteration` |
|
|
157
|
+
| `text_delta` | Partial text response | `delta` |
|
|
158
|
+
| `thinking_start` | Reasoning phase begins | - |
|
|
159
|
+
| `thinking_delta` | Reasoning content | `delta` |
|
|
160
|
+
| `thinking_end` | Reasoning phase ends | - |
|
|
161
|
+
| `tool_call_start` | Tool invocation begins | `name`, `id` |
|
|
162
|
+
| `tool_call_delta` | Tool arguments stream | `delta` |
|
|
163
|
+
| `tool_call_end` | Tool call complete | `name`, `args` |
|
|
164
|
+
| `tool_result` | Tool execution result | `result`, `error` |
|
|
165
|
+
| `iteration_end` | Iteration complete | `usage`, `iteration` |
|
|
166
|
+
| `done` | Stream finished | `stopReason`, `usage` |
|
|
167
|
+
| `error` | Error occurred | `error` |
|
|
168
|
+
|
|
169
|
+
### Session State Management
|
|
97
170
|
|
|
98
|
-
```
|
|
99
|
-
|
|
171
|
+
```typescript
|
|
172
|
+
// Access message history
|
|
173
|
+
console.log(session.messages) // Message[]
|
|
174
|
+
|
|
175
|
+
// Check session status
|
|
176
|
+
console.log(session.status) // 'idle' | 'running' | 'completed' | 'error'
|
|
177
|
+
|
|
178
|
+
// Get token usage
|
|
179
|
+
console.log(session.usage)
|
|
180
|
+
// {
|
|
181
|
+
// promptTokens: 150,
|
|
182
|
+
// completionTokens: 80,
|
|
183
|
+
// totalTokens: 230
|
|
184
|
+
// }
|
|
185
|
+
|
|
186
|
+
// Session metadata
|
|
187
|
+
console.log(session.id) // Session ID
|
|
188
|
+
console.log(session.createdAt) // Creation timestamp
|
|
189
|
+
console.log(session.updatedAt) // Last update timestamp
|
|
100
190
|
```
|
|
101
191
|
|
|
102
|
-
|
|
192
|
+
### Session Persistence
|
|
103
193
|
|
|
104
|
-
|
|
194
|
+
Sessions can be saved and restored:
|
|
105
195
|
|
|
106
|
-
|
|
196
|
+
```typescript
|
|
197
|
+
// Save session to snapshot
|
|
198
|
+
const snapshot = session.toSnapshot()
|
|
199
|
+
// Store snapshot to database, file, etc.
|
|
107
200
|
|
|
108
|
-
|
|
109
|
-
|
|
201
|
+
// Restore session from snapshot
|
|
202
|
+
session.restoreFromSnapshot(snapshot)
|
|
203
|
+
|
|
204
|
+
// Or create a new session from snapshot
|
|
205
|
+
const restored = await agent.createSession()
|
|
206
|
+
restored.restoreFromSnapshot(snapshot)
|
|
110
207
|
```
|
|
111
208
|
|
|
112
|
-
|
|
209
|
+
### Multi-turn Conversations
|
|
113
210
|
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
211
|
+
```typescript
|
|
212
|
+
const session = await agent.createSession()
|
|
213
|
+
|
|
214
|
+
// Turn 1
|
|
215
|
+
session.send('What is 2 + 2?')
|
|
216
|
+
for await (const event of session.receive()) {
|
|
217
|
+
if (event.type === 'text_delta') {
|
|
218
|
+
process.stdout.write(event.delta)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Turn 2 (maintains context)
|
|
223
|
+
session.send('What about multiplying that by 3?')
|
|
224
|
+
for await (const event of session.receive()) {
|
|
225
|
+
if (event.type === 'text_delta') {
|
|
226
|
+
process.stdout.write(event.delta)
|
|
121
227
|
}
|
|
122
228
|
}
|
|
229
|
+
|
|
230
|
+
// Session now contains full conversation history
|
|
231
|
+
console.log(session.messages.length) // 4 (2 user, 2 assistant)
|
|
123
232
|
```
|
|
124
233
|
|
|
125
|
-
|
|
126
|
-
- File operations (read, write, edit)
|
|
127
|
-
- Search tools (glob, grep, ast-grep)
|
|
128
|
-
- Web search and task management
|
|
129
|
-
- Same tool set as CLI agent mode
|
|
234
|
+
### Resuming Sessions
|
|
130
235
|
|
|
131
|
-
|
|
132
|
-
- `pnpm acp-server` - Simple agent (recommended)
|
|
133
|
-
- `pnpm acp-server:plan` - With plan mode (experimental)
|
|
236
|
+
Resume interrupted sessions using checkpoints:
|
|
134
237
|
|
|
135
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
import { FileStateStore } from 'goatchain'
|
|
136
240
|
|
|
137
|
-
|
|
241
|
+
// Create agent with state store
|
|
242
|
+
const stateStore = new FileStateStore({
|
|
243
|
+
dir: './checkpoints',
|
|
244
|
+
deleteOnComplete: true,
|
|
245
|
+
})
|
|
138
246
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
247
|
+
const agent = new Agent({
|
|
248
|
+
name: 'MyAgent',
|
|
249
|
+
systemPrompt: 'You are helpful.',
|
|
250
|
+
model,
|
|
251
|
+
stateStore,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Start session (automatically checkpointed)
|
|
255
|
+
const session = await agent.createSession({ id: 'session-123' })
|
|
256
|
+
session.send('Start a long task')
|
|
257
|
+
for await (const event of session.receive()) {
|
|
258
|
+
// Process events...
|
|
259
|
+
}
|
|
142
260
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
+stateStore: StateStore
|
|
150
|
-
+sessionManager: BaseSessionManager
|
|
151
|
-
+stats: AgentStats
|
|
152
|
-
+use(middleware): this
|
|
153
|
-
+createSession(options): BaseSession
|
|
154
|
-
+resumeSession(sessionId, options): BaseSession
|
|
155
|
-
+setModel(modelOrRef): void
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
class ModelClient {
|
|
159
|
-
<<interface>>
|
|
160
|
-
+modelId: string
|
|
161
|
-
+stream(request): AsyncIterable~ModelStreamEvent~
|
|
162
|
-
+run?(request): Promise~ModelRunResult~
|
|
163
|
-
}
|
|
261
|
+
// Later, resume from checkpoint
|
|
262
|
+
const resumed = await agent.resumeSession('session-123')
|
|
263
|
+
for await (const event of resumed.receive()) {
|
|
264
|
+
// Continue from where it left off
|
|
265
|
+
}
|
|
266
|
+
```
|
|
164
267
|
|
|
165
|
-
|
|
166
|
-
<<interface>>
|
|
167
|
-
+deleteOnComplete: boolean
|
|
168
|
-
+saveCheckpoint(checkpoint): Promise~void~
|
|
169
|
-
+loadCheckpoint(sessionId): Promise~AgentLoopCheckpoint~
|
|
170
|
-
+deleteCheckpoint(sessionId): Promise~void~
|
|
171
|
-
+listCheckpoints(): Promise~AgentLoopCheckpoint[]~
|
|
172
|
-
}
|
|
268
|
+
### Session Lifecycle Hooks
|
|
173
269
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
270
|
+
```typescript
|
|
271
|
+
// Add message manually
|
|
272
|
+
session.addMessage({
|
|
273
|
+
role: 'user',
|
|
274
|
+
content: 'Custom message',
|
|
275
|
+
})
|
|
181
276
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
+unregister(name): boolean
|
|
185
|
-
+get(name): BaseTool
|
|
186
|
-
+list(): BaseTool[]
|
|
187
|
-
+toOpenAIFormat(): OpenAITool[]
|
|
188
|
-
}
|
|
277
|
+
// Save session state manually
|
|
278
|
+
await session.save()
|
|
189
279
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
+status: SessionStatus
|
|
194
|
-
+messages: Message[]
|
|
195
|
-
+usage: Usage
|
|
196
|
-
+configOverride: SessionConfigOverride
|
|
197
|
-
+addMessage(message): void
|
|
198
|
-
+save(): Promise~void~
|
|
199
|
-
+toSnapshot(): SessionSnapshot
|
|
200
|
-
+restoreFromSnapshot(snapshot): void
|
|
201
|
-
}
|
|
280
|
+
// Clear session history
|
|
281
|
+
session.messages = []
|
|
282
|
+
```
|
|
202
283
|
|
|
203
|
-
|
|
204
|
-
<<abstract>>
|
|
205
|
-
+create(sessionId?): Promise~BaseSession~
|
|
206
|
-
+get(sessionId): Promise~BaseSession~
|
|
207
|
-
+list(): Promise~BaseSession[]~
|
|
208
|
-
+destroy(sessionId): Promise~void~
|
|
209
|
-
}
|
|
284
|
+
## π€ Agent Configuration
|
|
210
285
|
|
|
211
|
-
|
|
212
|
-
<<function>>
|
|
213
|
-
(ctx: AgentLoopState, next: NextFunction) => Promise~void~
|
|
214
|
-
}
|
|
286
|
+
### Creating an Agent
|
|
215
287
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
+messages: Message[]
|
|
219
|
-
+iteration: number
|
|
220
|
-
+pendingToolCalls: ToolCallWithResult[]
|
|
221
|
-
+currentResponse: string
|
|
222
|
-
+shouldContinue: boolean
|
|
223
|
-
+usage: Usage
|
|
224
|
-
}
|
|
288
|
+
```typescript
|
|
289
|
+
import { Agent, createModel, createOpenAIAdapter } from 'goatchain'
|
|
225
290
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
const agent = new Agent({
|
|
292
|
+
name: 'MyAgent',
|
|
293
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
294
|
+
model,
|
|
295
|
+
tools: [], // Optional: custom tools
|
|
296
|
+
stateStore, // Optional: for persistence
|
|
297
|
+
maxIterations: 5, // Optional: max loop iterations
|
|
298
|
+
maxConcurrentToolCalls: 5, // Optional: parallel tool calls
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Agent Options
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface AgentOptions {
|
|
306
|
+
name: string // Agent name
|
|
307
|
+
systemPrompt: string // System instructions
|
|
308
|
+
model: ModelClient | ModelRef // LLM client or reference
|
|
309
|
+
tools?: BaseTool[] // Custom tools
|
|
310
|
+
stateStore?: StateStore // Persistence layer
|
|
311
|
+
maxIterations?: number // Max agent loop iterations (default: 5)
|
|
312
|
+
maxConcurrentToolCalls?: number // Max parallel tool calls (default: 5)
|
|
313
|
+
modelSwapWhitelist?: string[] // Allowed model IDs for runtime switching
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Runtime Model Switching
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// Pin to specific model
|
|
321
|
+
agent.setModel('gpt-4o-mini')
|
|
322
|
+
|
|
323
|
+
// Allow model to switch back
|
|
324
|
+
agent.setModel({ type: 'ref', ref: 'the-model' })
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Session Manager
|
|
328
|
+
|
|
329
|
+
Manage multiple sessions:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
const sessionManager = agent.sessionManager
|
|
333
|
+
|
|
334
|
+
// List all sessions
|
|
335
|
+
const sessions = await sessionManager.list()
|
|
336
|
+
|
|
337
|
+
// Get specific session
|
|
338
|
+
const session = await sessionManager.get('session-id')
|
|
339
|
+
|
|
340
|
+
// Destroy session
|
|
341
|
+
await sessionManager.destroy('session-id')
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## π§ Tool System
|
|
345
|
+
|
|
346
|
+
### Using Built-in Tools
|
|
347
|
+
|
|
348
|
+
GoatChain includes 23 built-in tools:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import {
|
|
352
|
+
ReadTool,
|
|
353
|
+
WriteTool,
|
|
354
|
+
EditTool,
|
|
355
|
+
BashTool,
|
|
356
|
+
GrepTool,
|
|
357
|
+
GlobTool,
|
|
358
|
+
WebSearchTool,
|
|
359
|
+
WebFetchTool,
|
|
360
|
+
} from 'goatchain'
|
|
361
|
+
|
|
362
|
+
const agent = new Agent({
|
|
363
|
+
name: 'MyAgent',
|
|
364
|
+
systemPrompt: 'You are helpful.',
|
|
365
|
+
model,
|
|
366
|
+
tools: [
|
|
367
|
+
new ReadTool(),
|
|
368
|
+
new WriteTool(),
|
|
369
|
+
new EditTool(),
|
|
370
|
+
new BashTool(),
|
|
371
|
+
new GrepTool(),
|
|
372
|
+
new GlobTool(),
|
|
373
|
+
new WebSearchTool({ apiKey: process.env.SERPER_API_KEY }),
|
|
374
|
+
new WebFetchTool(),
|
|
375
|
+
],
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Creating Custom Tools
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { BaseTool } from 'goatchain'
|
|
383
|
+
|
|
384
|
+
class MyCustomTool extends BaseTool {
|
|
385
|
+
name = 'my_tool'
|
|
386
|
+
description = 'Does something useful'
|
|
387
|
+
|
|
388
|
+
parameters = {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
input: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description: 'Input parameter',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
required: ['input'],
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async execute(args: { input: string }) {
|
|
400
|
+
// Your tool logic here
|
|
401
|
+
return `Processed: ${args.input}`
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Use it
|
|
406
|
+
const agent = new Agent({
|
|
407
|
+
name: 'MyAgent',
|
|
408
|
+
systemPrompt: 'You are helpful.',
|
|
409
|
+
model,
|
|
410
|
+
tools: [new MyCustomTool()],
|
|
411
|
+
})
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Tool Registry
|
|
415
|
+
|
|
416
|
+
Dynamically manage tools:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const registry = agent.tools
|
|
420
|
+
|
|
421
|
+
// Register new tool
|
|
422
|
+
registry.register(new MyCustomTool())
|
|
423
|
+
|
|
424
|
+
// Unregister tool
|
|
425
|
+
registry.unregister('my_tool')
|
|
426
|
+
|
|
427
|
+
// Get tool
|
|
428
|
+
const tool = registry.get('my_tool')
|
|
429
|
+
|
|
430
|
+
// List all tools
|
|
431
|
+
const allTools = registry.list()
|
|
432
|
+
|
|
433
|
+
// Convert to OpenAI format
|
|
434
|
+
const openaiTools = registry.toOpenAIFormat()
|
|
234
435
|
```
|
|
235
436
|
|
|
236
|
-
## π§
Middleware
|
|
437
|
+
## π§
Middleware System
|
|
237
438
|
|
|
238
439
|
GoatChain uses a Koa-style onion model for middleware. Each middleware wraps around the core execution:
|
|
239
440
|
|
|
@@ -241,16 +442,16 @@ GoatChain uses a Koa-style onion model for middleware. Each middleware wraps aro
|
|
|
241
442
|
outer:before β inner:before β exec (model.stream) β inner:after β outer:after
|
|
242
443
|
```
|
|
243
444
|
|
|
244
|
-
###
|
|
245
|
-
|
|
246
|
-
Middlewares can be named for easier management and removal:
|
|
445
|
+
### Adding Middleware
|
|
247
446
|
|
|
248
447
|
```typescript
|
|
249
448
|
// Add named middleware (recommended)
|
|
250
449
|
agent.use(async (state, next) => {
|
|
251
450
|
const start = Date.now()
|
|
252
451
|
console.log(`[${state.iteration}] Before model call`)
|
|
253
|
-
|
|
452
|
+
|
|
453
|
+
const nextState = await next(state) // Execute next middleware/model
|
|
454
|
+
|
|
254
455
|
console.log(`[${state.iteration}] After model call (${Date.now() - start}ms)`)
|
|
255
456
|
return nextState
|
|
256
457
|
}, 'logging')
|
|
@@ -266,169 +467,857 @@ const unsubscribe = agent.use(middleware, 'temp')
|
|
|
266
467
|
unsubscribe() // Remove middleware
|
|
267
468
|
```
|
|
268
469
|
|
|
269
|
-
### Built-in Middleware
|
|
470
|
+
### Built-in Middleware
|
|
270
471
|
|
|
271
|
-
|
|
472
|
+
#### Plan Mode Middleware
|
|
473
|
+
|
|
474
|
+
Adds planning phase before execution:
|
|
272
475
|
|
|
273
476
|
```typescript
|
|
274
|
-
|
|
477
|
+
import { createPlanModeMiddleware } from 'goatchain'
|
|
478
|
+
|
|
479
|
+
// Automatically named 'plan-mode'
|
|
275
480
|
agent.use(createPlanModeMiddleware())
|
|
276
481
|
|
|
277
|
-
//
|
|
278
|
-
agent.use(
|
|
279
|
-
|
|
482
|
+
// With custom configuration
|
|
483
|
+
agent.use(createPlanModeMiddleware({
|
|
484
|
+
name: 'my-plan', // Custom name
|
|
485
|
+
planPrompt: 'Create a detailed plan...', // Custom prompt
|
|
280
486
|
}))
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### Context Compression Middleware
|
|
281
490
|
|
|
282
|
-
|
|
283
|
-
console.log(agent.middlewareNames)
|
|
284
|
-
// Output: ['plan-mode', 'context-compression']
|
|
491
|
+
Automatically compresses context when token limit is reached using a two-stage strategy:
|
|
285
492
|
|
|
286
|
-
|
|
287
|
-
|
|
493
|
+
```typescript
|
|
494
|
+
import { createContextCompressionMiddleware } from 'goatchain'
|
|
288
495
|
|
|
289
|
-
//
|
|
290
|
-
agent.use(createPlanModeMiddleware({ name: 'my-plan' }))
|
|
496
|
+
// Automatically named 'context-compression'
|
|
291
497
|
agent.use(createContextCompressionMiddleware({
|
|
292
498
|
maxTokens: 128000,
|
|
293
|
-
|
|
499
|
+
protectedTurns: 2, // Keep last 2 conversation turns
|
|
500
|
+
model: model,
|
|
501
|
+
stateStore: agent.stateStore,
|
|
502
|
+
toolCompressionTarget: 0.45, // Compress to 45% of maxTokens
|
|
503
|
+
minKeepToolResults: 5, // Keep last 5 tool results
|
|
504
|
+
// Optional: Enable detailed logging
|
|
505
|
+
enableLogging: true,
|
|
506
|
+
logFilePath: 'compression-logs.jsonl',
|
|
507
|
+
}))
|
|
294
508
|
```
|
|
295
509
|
|
|
296
|
-
|
|
510
|
+
See [Context Compression Logging Guide](./docs/context-compression-logging.md) for details on monitoring compression behavior.
|
|
511
|
+
|
|
512
|
+
### Custom Middleware Examples
|
|
513
|
+
|
|
514
|
+
#### Logging Middleware
|
|
297
515
|
|
|
298
516
|
```typescript
|
|
299
|
-
// Logging middleware
|
|
300
517
|
agent.use(async (state, next) => {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
518
|
+
console.log(`Iteration ${state.iteration}:`, {
|
|
519
|
+
messages: state.messages.length,
|
|
520
|
+
pendingTools: state.pendingToolCalls.length,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
const result = await next(state)
|
|
524
|
+
|
|
525
|
+
console.log(`Completed iteration ${state.iteration}:`, {
|
|
526
|
+
shouldContinue: result.shouldContinue,
|
|
527
|
+
usage: result.usage,
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
return result
|
|
531
|
+
}, 'logger')
|
|
532
|
+
```
|
|
305
533
|
|
|
306
|
-
|
|
307
|
-
return nextState
|
|
308
|
-
}, 'logging')
|
|
534
|
+
#### Error Handling Middleware
|
|
309
535
|
|
|
310
|
-
|
|
536
|
+
```typescript
|
|
311
537
|
agent.use(async (state, next) => {
|
|
312
538
|
try {
|
|
313
539
|
return await next(state)
|
|
314
|
-
}
|
|
315
|
-
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.error('Agent error:', error)
|
|
316
542
|
state.shouldContinue = false
|
|
317
543
|
state.stopReason = 'error'
|
|
318
544
|
state.error = error
|
|
319
545
|
return state
|
|
320
546
|
}
|
|
321
547
|
}, 'error-handler')
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### Rate Limiting Middleware
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
import { RateLimiter } from 'some-rate-limiter'
|
|
554
|
+
|
|
555
|
+
const limiter = new RateLimiter({ requestsPerMinute: 60 })
|
|
322
556
|
|
|
323
|
-
// Rate limiting middleware
|
|
324
557
|
agent.use(async (state, next) => {
|
|
325
|
-
await
|
|
558
|
+
await limiter.acquire()
|
|
326
559
|
return next(state)
|
|
327
560
|
}, 'rate-limiter')
|
|
328
561
|
```
|
|
329
562
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
//
|
|
563
|
+
#### Custom Retry Middleware
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
agent.use(async (state, next) => {
|
|
567
|
+
let retries = 3
|
|
568
|
+
|
|
569
|
+
while (retries > 0) {
|
|
570
|
+
try {
|
|
571
|
+
return await next(state)
|
|
572
|
+
} catch (error) {
|
|
573
|
+
retries--
|
|
574
|
+
if (retries === 0) throw error
|
|
575
|
+
|
|
576
|
+
console.log(`Retrying... (${retries} attempts left)`)
|
|
577
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return state
|
|
582
|
+
}, 'retry')
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Middleware State
|
|
586
|
+
|
|
587
|
+
The `AgentLoopState` object passed to middleware:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
interface AgentLoopState {
|
|
591
|
+
sessionId: string // Current session ID
|
|
592
|
+
messages: Message[] // Conversation history
|
|
593
|
+
iteration: number // Current iteration number
|
|
594
|
+
pendingToolCalls: ToolCallWithResult[] // Pending tool executions
|
|
595
|
+
currentResponse: string // Current LLM response
|
|
596
|
+
shouldContinue: boolean // Whether to continue loop
|
|
597
|
+
stopReason?: string // Reason for stopping
|
|
598
|
+
usage?: Usage // Token usage
|
|
599
|
+
error?: Error // Error if any
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## π Model Client
|
|
604
|
+
|
|
605
|
+
### Creating a Model Client
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
import { createModel, createOpenAIAdapter } from 'goatchain'
|
|
609
|
+
|
|
610
|
+
const model = createModel({
|
|
611
|
+
adapter: createOpenAIAdapter({
|
|
612
|
+
defaultModelId: 'gpt-4o',
|
|
613
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
614
|
+
baseURL: 'https://api.openai.com/v1', // Optional
|
|
615
|
+
organization: 'org-xxx', // Optional
|
|
616
|
+
}),
|
|
617
|
+
})
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### OpenAI Adapter Options
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
interface OpenAIAdapterOptions {
|
|
624
|
+
defaultModelId?: string // Default model ID
|
|
625
|
+
apiKey?: string // OpenAI API key
|
|
626
|
+
baseURL?: string // Custom API endpoint
|
|
627
|
+
organization?: string // OpenAI organization ID
|
|
628
|
+
defaultHeaders?: Record<string, string> // Custom headers
|
|
629
|
+
timeout?: number // Request timeout (ms)
|
|
630
|
+
maxRetries?: number // Max retry attempts (default: 2)
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
### Using Different Models
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
// OpenAI
|
|
638
|
+
const openai = createModel({
|
|
639
|
+
adapter: createOpenAIAdapter({
|
|
640
|
+
defaultModelId: 'gpt-4o',
|
|
641
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
642
|
+
}),
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
// OpenAI-compatible endpoints
|
|
646
|
+
const deepseek = createModel({
|
|
647
|
+
adapter: createOpenAIAdapter({
|
|
648
|
+
defaultModelId: 'deepseek-chat',
|
|
649
|
+
apiKey: process.env.DEEPSEEK_API_KEY!,
|
|
650
|
+
baseURL: 'https://api.deepseek.com/v1',
|
|
651
|
+
}),
|
|
652
|
+
})
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Model Interface
|
|
656
|
+
|
|
657
|
+
Implement custom model adapters:
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
interface ModelClient {
|
|
661
|
+
modelId: string
|
|
662
|
+
|
|
663
|
+
// Streaming API (required)
|
|
664
|
+
stream(request: ModelRequest): AsyncIterable<ModelStreamEvent>
|
|
665
|
+
|
|
666
|
+
// Non-streaming API (optional)
|
|
667
|
+
run?(request: ModelRequest): Promise<ModelRunResult>
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
interface ModelRequest {
|
|
671
|
+
messages: Message[]
|
|
672
|
+
tools?: OpenAITool[]
|
|
673
|
+
temperature?: number
|
|
674
|
+
maxTokens?: number
|
|
675
|
+
topP?: number
|
|
676
|
+
stopSequences?: string[]
|
|
365
677
|
}
|
|
366
678
|
```
|
|
367
679
|
|
|
368
|
-
|
|
680
|
+
## πΎ State Management
|
|
369
681
|
|
|
370
|
-
|
|
682
|
+
### File State Store
|
|
371
683
|
|
|
372
|
-
|
|
684
|
+
Persist agent state to filesystem:
|
|
373
685
|
|
|
374
686
|
```typescript
|
|
375
|
-
import {
|
|
687
|
+
import { FileStateStore } from 'goatchain'
|
|
376
688
|
|
|
377
|
-
// Create state store with configuration
|
|
378
689
|
const stateStore = new FileStateStore({
|
|
379
|
-
dir: './checkpoints',
|
|
380
|
-
deleteOnComplete: true,
|
|
690
|
+
dir: './checkpoints', // Storage directory
|
|
691
|
+
deleteOnComplete: true, // Auto-delete successful completions
|
|
381
692
|
})
|
|
382
693
|
|
|
383
|
-
// Agent automatically saves checkpoints when stateStore is provided
|
|
384
694
|
const agent = new Agent({
|
|
385
695
|
name: 'MyAgent',
|
|
386
696
|
systemPrompt: 'You are helpful.',
|
|
387
697
|
model,
|
|
388
698
|
stateStore,
|
|
389
699
|
})
|
|
700
|
+
```
|
|
390
701
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
702
|
+
### In-Memory State Store
|
|
703
|
+
|
|
704
|
+
For testing or temporary state:
|
|
705
|
+
|
|
706
|
+
```typescript
|
|
707
|
+
import { InMemoryStateStore } from 'goatchain'
|
|
708
|
+
|
|
709
|
+
const stateStore = new InMemoryStateStore({
|
|
710
|
+
deleteOnComplete: false,
|
|
711
|
+
})
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### State Store Interface
|
|
715
|
+
|
|
716
|
+
Implement custom state stores:
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
interface StateStore {
|
|
720
|
+
deleteOnComplete: boolean
|
|
721
|
+
|
|
722
|
+
saveCheckpoint(checkpoint: AgentLoopCheckpoint): Promise<void>
|
|
723
|
+
loadCheckpoint(sessionId: string): Promise<AgentLoopCheckpoint | null>
|
|
724
|
+
deleteCheckpoint(sessionId: string): Promise<void>
|
|
725
|
+
listCheckpoints(): Promise<AgentLoopCheckpoint[]>
|
|
396
726
|
}
|
|
397
727
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
728
|
+
interface AgentLoopCheckpoint {
|
|
729
|
+
sessionId: string
|
|
730
|
+
messages: Message[]
|
|
731
|
+
iteration: number
|
|
732
|
+
usage: Usage
|
|
733
|
+
createdAt: number
|
|
734
|
+
updatedAt: number
|
|
405
735
|
}
|
|
406
736
|
```
|
|
407
737
|
|
|
408
|
-
|
|
738
|
+
### Manual Checkpoint Management
|
|
409
739
|
|
|
410
|
-
|
|
411
|
-
|
|
740
|
+
```typescript
|
|
741
|
+
// Save checkpoint manually
|
|
742
|
+
await stateStore.saveCheckpoint({
|
|
743
|
+
sessionId: session.id,
|
|
744
|
+
messages: session.messages,
|
|
745
|
+
iteration: 3,
|
|
746
|
+
usage: session.usage,
|
|
747
|
+
createdAt: Date.now(),
|
|
748
|
+
updatedAt: Date.now(),
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
// Load checkpoint
|
|
752
|
+
const checkpoint = await stateStore.loadCheckpoint('session-id')
|
|
753
|
+
|
|
754
|
+
// List all checkpoints
|
|
755
|
+
const checkpoints = await stateStore.listCheckpoints()
|
|
756
|
+
|
|
757
|
+
// Delete checkpoint
|
|
758
|
+
await stateStore.deleteCheckpoint('session-id')
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
## π Complete Examples
|
|
762
|
+
|
|
763
|
+
### Example 1: Simple Q&A Bot
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
import process from 'node:process'
|
|
767
|
+
import { Agent, createModel, createOpenAIAdapter } from 'goatchain'
|
|
768
|
+
|
|
769
|
+
const model = createModel({
|
|
770
|
+
adapter: createOpenAIAdapter({
|
|
771
|
+
defaultModelId: 'gpt-4o',
|
|
772
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
773
|
+
}),
|
|
774
|
+
})
|
|
775
|
+
|
|
776
|
+
const agent = new Agent({
|
|
777
|
+
name: 'Q&A Bot',
|
|
778
|
+
systemPrompt: 'You are a helpful assistant that answers questions concisely.',
|
|
779
|
+
model,
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
const session = await agent.createSession()
|
|
783
|
+
session.send('What is the capital of France?')
|
|
784
|
+
|
|
785
|
+
for await (const event of session.receive()) {
|
|
786
|
+
if (event.type === 'text_delta') {
|
|
787
|
+
process.stdout.write(event.delta)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### Example 2: Agent with Tools
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
import {
|
|
796
|
+
Agent,
|
|
797
|
+
createModel,
|
|
798
|
+
createOpenAIAdapter,
|
|
799
|
+
ReadTool,
|
|
800
|
+
WriteTool,
|
|
801
|
+
BashTool,
|
|
802
|
+
} from 'goatchain'
|
|
803
|
+
|
|
804
|
+
const model = createModel({
|
|
805
|
+
adapter: createOpenAIAdapter({
|
|
806
|
+
defaultModelId: 'gpt-4o',
|
|
807
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
808
|
+
}),
|
|
809
|
+
})
|
|
810
|
+
|
|
811
|
+
const agent = new Agent({
|
|
812
|
+
name: 'File Assistant',
|
|
813
|
+
systemPrompt: 'You help users manage their files.',
|
|
814
|
+
model,
|
|
815
|
+
tools: [
|
|
816
|
+
new ReadTool(),
|
|
817
|
+
new WriteTool(),
|
|
818
|
+
new BashTool(),
|
|
819
|
+
],
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
const session = await agent.createSession()
|
|
823
|
+
session.send('Read the package.json file and tell me the version')
|
|
824
|
+
|
|
825
|
+
for await (const event of session.receive()) {
|
|
826
|
+
if (event.type === 'text_delta') {
|
|
827
|
+
process.stdout.write(event.delta)
|
|
828
|
+
} else if (event.type === 'tool_call_start') {
|
|
829
|
+
console.log(`\nCalling: ${event.name}`)
|
|
830
|
+
} else if (event.type === 'tool_result') {
|
|
831
|
+
console.log(`Result: ${JSON.stringify(event.result).slice(0, 100)}...`)
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### Example 3: Persistent Sessions
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
import {
|
|
840
|
+
Agent,
|
|
841
|
+
createModel,
|
|
842
|
+
createOpenAIAdapter,
|
|
843
|
+
FileStateStore,
|
|
844
|
+
} from 'goatchain'
|
|
845
|
+
|
|
846
|
+
const model = createModel({
|
|
847
|
+
adapter: createOpenAIAdapter({
|
|
848
|
+
defaultModelId: 'gpt-4o',
|
|
849
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
850
|
+
}),
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
const stateStore = new FileStateStore({
|
|
854
|
+
dir: './agent-state',
|
|
855
|
+
deleteOnComplete: false, // Keep history
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
const agent = new Agent({
|
|
859
|
+
name: 'Persistent Agent',
|
|
860
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
861
|
+
model,
|
|
862
|
+
stateStore,
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
// Create or resume session
|
|
866
|
+
let session
|
|
867
|
+
const sessionId = 'my-conversation'
|
|
868
|
+
|
|
869
|
+
try {
|
|
870
|
+
session = await agent.resumeSession(sessionId)
|
|
871
|
+
console.log('Resumed existing session')
|
|
872
|
+
} catch {
|
|
873
|
+
session = await agent.createSession({ id: sessionId })
|
|
874
|
+
console.log('Created new session')
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
session.send('Remember this: My favorite color is blue')
|
|
878
|
+
|
|
879
|
+
for await (const event of session.receive()) {
|
|
880
|
+
if (event.type === 'text_delta') {
|
|
881
|
+
process.stdout.write(event.delta)
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
### Example 4: Session with Middleware
|
|
887
|
+
|
|
888
|
+
```typescript
|
|
889
|
+
import {
|
|
890
|
+
Agent,
|
|
891
|
+
createModel,
|
|
892
|
+
createOpenAIAdapter,
|
|
893
|
+
createPlanModeMiddleware,
|
|
894
|
+
} from 'goatchain'
|
|
895
|
+
|
|
896
|
+
const model = createModel({
|
|
897
|
+
adapter: createOpenAIAdapter({
|
|
898
|
+
defaultModelId: 'gpt-4o',
|
|
899
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
900
|
+
}),
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
const agent = new Agent({
|
|
904
|
+
name: 'Planning Agent',
|
|
905
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
906
|
+
model,
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
// Add logging middleware
|
|
910
|
+
agent.use(async (state, next) => {
|
|
911
|
+
console.log(`\n=== Iteration ${state.iteration} ===`)
|
|
912
|
+
const result = await next(state)
|
|
913
|
+
console.log(`Tokens used: ${result.usage?.totalTokens || 0}`)
|
|
914
|
+
return result
|
|
915
|
+
}, 'logger')
|
|
916
|
+
|
|
917
|
+
// Add plan mode
|
|
918
|
+
agent.use(createPlanModeMiddleware())
|
|
919
|
+
|
|
920
|
+
const session = await agent.createSession()
|
|
921
|
+
session.send('Create a todo list app with React and TypeScript')
|
|
922
|
+
|
|
923
|
+
for await (const event of session.receive()) {
|
|
924
|
+
if (event.type === 'text_delta') {
|
|
925
|
+
process.stdout.write(event.delta)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
### Example 5: Multi-turn Conversation
|
|
931
|
+
|
|
932
|
+
```typescript
|
|
933
|
+
import { Agent, createModel, createOpenAIAdapter } from 'goatchain'
|
|
934
|
+
|
|
935
|
+
const model = createModel({
|
|
936
|
+
adapter: createOpenAIAdapter({
|
|
937
|
+
defaultModelId: 'gpt-4o',
|
|
938
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
939
|
+
}),
|
|
940
|
+
})
|
|
941
|
+
|
|
942
|
+
const agent = new Agent({
|
|
943
|
+
name: 'Conversational Agent',
|
|
944
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
945
|
+
model,
|
|
946
|
+
})
|
|
947
|
+
|
|
948
|
+
const session = await agent.createSession()
|
|
949
|
+
|
|
950
|
+
async function chat(message: string) {
|
|
951
|
+
console.log(`\nUser: ${message}`)
|
|
952
|
+
console.log('Assistant: ')
|
|
953
|
+
|
|
954
|
+
session.send(message)
|
|
955
|
+
|
|
956
|
+
for await (const event of session.receive()) {
|
|
957
|
+
if (event.type === 'text_delta') {
|
|
958
|
+
process.stdout.write(event.delta)
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
console.log('\n')
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Multi-turn conversation
|
|
966
|
+
await chat('My name is Alice')
|
|
967
|
+
await chat('What is 2 + 2?')
|
|
968
|
+
await chat('What is my name?') // Should remember "Alice"
|
|
969
|
+
await chat('Multiply the previous result by 3') // Should remember "4"
|
|
970
|
+
|
|
971
|
+
console.log(`Total messages: ${session.messages.length}`)
|
|
972
|
+
console.log(`Total tokens: ${session.usage.totalTokens}`)
|
|
973
|
+
```
|
|
412
974
|
|
|
413
975
|
## π API Reference
|
|
414
976
|
|
|
415
|
-
### Agent
|
|
977
|
+
### Agent Class
|
|
978
|
+
|
|
979
|
+
#### Constructor
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
new Agent(options: AgentOptions)
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
**Options:**
|
|
986
|
+
- `name: string` - Agent name (required)
|
|
987
|
+
- `systemPrompt: string` - System instructions (required)
|
|
988
|
+
- `model: ModelClient | ModelRef` - LLM client (required)
|
|
989
|
+
- `tools?: BaseTool[]` - Custom tools
|
|
990
|
+
- `stateStore?: StateStore` - Persistence layer
|
|
991
|
+
- `maxIterations?: number` - Max loop iterations (default: 5)
|
|
992
|
+
- `maxConcurrentToolCalls?: number` - Max parallel tools (default: 5)
|
|
993
|
+
- `modelSwapWhitelist?: string[]` - Allowed model IDs
|
|
994
|
+
|
|
995
|
+
#### Methods
|
|
996
|
+
|
|
997
|
+
**`createSession(options?): Promise<BaseSession>`**
|
|
998
|
+
|
|
999
|
+
Create a new session.
|
|
1000
|
+
|
|
1001
|
+
```typescript
|
|
1002
|
+
const session = await agent.createSession({
|
|
1003
|
+
id: 'custom-id', // Optional custom ID
|
|
1004
|
+
configOverride: { // Optional config overrides
|
|
1005
|
+
maxIterations: 10,
|
|
1006
|
+
temperature: 0.7,
|
|
1007
|
+
},
|
|
1008
|
+
})
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
**`resumeSession(sessionId, options?): Promise<BaseSession>`**
|
|
1012
|
+
|
|
1013
|
+
Resume an existing session from checkpoint.
|
|
1014
|
+
|
|
1015
|
+
```typescript
|
|
1016
|
+
const session = await agent.resumeSession('session-123')
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
**`use(middleware, name?): () => void`**
|
|
1020
|
+
|
|
1021
|
+
Add middleware. Returns unsubscribe function.
|
|
1022
|
+
|
|
1023
|
+
```typescript
|
|
1024
|
+
const unsubscribe = agent.use(myMiddleware, 'my-middleware')
|
|
1025
|
+
unsubscribe() // Remove middleware
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
**`removeMiddleware(name): boolean`**
|
|
1029
|
+
|
|
1030
|
+
Remove middleware by name.
|
|
1031
|
+
|
|
1032
|
+
```typescript
|
|
1033
|
+
agent.removeMiddleware('my-middleware')
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
**`setModel(modelOrRef): void`**
|
|
1037
|
+
|
|
1038
|
+
Switch or pin model at runtime.
|
|
1039
|
+
|
|
1040
|
+
```typescript
|
|
1041
|
+
// Pin to specific model
|
|
1042
|
+
agent.setModel('gpt-4o-mini')
|
|
1043
|
+
|
|
1044
|
+
// Use dynamic reference
|
|
1045
|
+
agent.setModel({ type: 'ref', ref: 'the-model' })
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
#### Properties
|
|
1049
|
+
|
|
1050
|
+
- `id: string` - Agent ID
|
|
1051
|
+
- `name: string` - Agent name
|
|
1052
|
+
- `systemPrompt: string` - System prompt
|
|
1053
|
+
- `model: ModelClient` - Current model client
|
|
1054
|
+
- `tools: ToolRegistry` - Tool registry
|
|
1055
|
+
- `stateStore?: StateStore` - State store
|
|
1056
|
+
- `sessionManager: BaseSessionManager` - Session manager
|
|
1057
|
+
- `middlewareNames: string[]` - List of middleware names
|
|
1058
|
+
- `stats: AgentStats` - Usage statistics
|
|
1059
|
+
|
|
1060
|
+
### Session Class
|
|
1061
|
+
|
|
1062
|
+
#### Methods
|
|
1063
|
+
|
|
1064
|
+
**`send(input, options?): void`**
|
|
1065
|
+
|
|
1066
|
+
Send a message to the session.
|
|
1067
|
+
|
|
1068
|
+
```typescript
|
|
1069
|
+
session.send('Hello!', {
|
|
1070
|
+
temperature: 0.8,
|
|
1071
|
+
maxTokens: 1000,
|
|
1072
|
+
})
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
**`receive(options?): AsyncGenerator<AgentEvent>`**
|
|
1076
|
+
|
|
1077
|
+
Stream agent events.
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
for await (const event of session.receive()) {
|
|
1081
|
+
console.log(event)
|
|
1082
|
+
}
|
|
1083
|
+
```
|
|
1084
|
+
|
|
1085
|
+
**`addMessage(message): void`**
|
|
1086
|
+
|
|
1087
|
+
Manually add a message.
|
|
1088
|
+
|
|
1089
|
+
```typescript
|
|
1090
|
+
session.addMessage({
|
|
1091
|
+
role: 'user',
|
|
1092
|
+
content: 'Hello',
|
|
1093
|
+
})
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
**`save(): Promise<void>`**
|
|
1097
|
+
|
|
1098
|
+
Manually save session state.
|
|
1099
|
+
|
|
1100
|
+
```typescript
|
|
1101
|
+
await session.save()
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
**`toSnapshot(): SessionSnapshot`**
|
|
1105
|
+
|
|
1106
|
+
Export session to snapshot.
|
|
1107
|
+
|
|
1108
|
+
```typescript
|
|
1109
|
+
const snapshot = session.toSnapshot()
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
**`restoreFromSnapshot(snapshot): void`**
|
|
1113
|
+
|
|
1114
|
+
Restore session from snapshot.
|
|
1115
|
+
|
|
1116
|
+
```typescript
|
|
1117
|
+
session.restoreFromSnapshot(snapshot)
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
#### Properties
|
|
1121
|
+
|
|
1122
|
+
- `id: string` - Session ID
|
|
1123
|
+
- `status: SessionStatus` - Session status ('idle' | 'running' | 'completed' | 'error')
|
|
1124
|
+
- `messages: Message[]` - Message history
|
|
1125
|
+
- `usage: Usage` - Token usage statistics
|
|
1126
|
+
- `configOverride?: SessionConfigOverride` - Config overrides
|
|
1127
|
+
- `createdAt: number` - Creation timestamp
|
|
1128
|
+
- `updatedAt: number` - Last update timestamp
|
|
1129
|
+
|
|
1130
|
+
### Message Type
|
|
1131
|
+
|
|
1132
|
+
```typescript
|
|
1133
|
+
interface Message {
|
|
1134
|
+
role: 'system' | 'user' | 'assistant' | 'tool'
|
|
1135
|
+
content: string | ToolCall[] | ToolResult[]
|
|
1136
|
+
name?: string // For tool messages
|
|
1137
|
+
toolCallId?: string // For tool messages
|
|
1138
|
+
}
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### Usage Type
|
|
1142
|
+
|
|
1143
|
+
```typescript
|
|
1144
|
+
interface Usage {
|
|
1145
|
+
promptTokens: number
|
|
1146
|
+
completionTokens: number
|
|
1147
|
+
totalTokens: number
|
|
1148
|
+
}
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
### AgentEvent Types
|
|
1152
|
+
|
|
1153
|
+
```typescript
|
|
1154
|
+
type AgentEvent =
|
|
1155
|
+
| TextDeltaEvent
|
|
1156
|
+
| ToolCallStartEvent
|
|
1157
|
+
| ToolCallDeltaEvent
|
|
1158
|
+
| ToolCallEndEvent
|
|
1159
|
+
| ToolResultEvent
|
|
1160
|
+
| ThinkingStartEvent
|
|
1161
|
+
| ThinkingDeltaEvent
|
|
1162
|
+
| ThinkingEndEvent
|
|
1163
|
+
| IterationStartEvent
|
|
1164
|
+
| IterationEndEvent
|
|
1165
|
+
| DoneEvent
|
|
1166
|
+
| ErrorEvent
|
|
1167
|
+
|
|
1168
|
+
interface TextDeltaEvent {
|
|
1169
|
+
type: 'text_delta'
|
|
1170
|
+
delta: string
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
interface ToolCallStartEvent {
|
|
1174
|
+
type: 'tool_call_start'
|
|
1175
|
+
id: string
|
|
1176
|
+
name: string
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
interface ToolResultEvent {
|
|
1180
|
+
type: 'tool_result'
|
|
1181
|
+
id: string
|
|
1182
|
+
name: string
|
|
1183
|
+
result: unknown
|
|
1184
|
+
error?: string
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
interface DoneEvent {
|
|
1188
|
+
type: 'done'
|
|
1189
|
+
stopReason: 'stop' | 'max_iterations' | 'error' | 'cancel'
|
|
1190
|
+
usage?: Usage
|
|
1191
|
+
}
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
## ποΈ Architecture
|
|
1195
|
+
|
|
1196
|
+
```mermaid
|
|
1197
|
+
classDiagram
|
|
1198
|
+
direction TB
|
|
1199
|
+
|
|
1200
|
+
class Agent {
|
|
1201
|
+
+id: string
|
|
1202
|
+
+name: string
|
|
1203
|
+
+systemPrompt: string
|
|
1204
|
+
+model: ModelClient
|
|
1205
|
+
+tools: ToolRegistry
|
|
1206
|
+
+stateStore: StateStore
|
|
1207
|
+
+sessionManager: BaseSessionManager
|
|
1208
|
+
+use(middleware): this
|
|
1209
|
+
+createSession(): Promise~BaseSession~
|
|
1210
|
+
+resumeSession(id): Promise~BaseSession~
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
class ModelClient {
|
|
1214
|
+
<<interface>>
|
|
1215
|
+
+modelId: string
|
|
1216
|
+
+stream(request): AsyncIterable~ModelStreamEvent~
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
class StateStore {
|
|
1220
|
+
<<interface>>
|
|
1221
|
+
+saveCheckpoint(): Promise~void~
|
|
1222
|
+
+loadCheckpoint(): Promise~Checkpoint~
|
|
1223
|
+
+deleteCheckpoint(): Promise~void~
|
|
1224
|
+
+listCheckpoints(): Promise~Checkpoint[]~
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
class BaseTool {
|
|
1228
|
+
<<abstract>>
|
|
1229
|
+
+name: string
|
|
1230
|
+
+description: string
|
|
1231
|
+
+parameters: JSONSchema
|
|
1232
|
+
+execute(args): Promise~unknown~
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
class ToolRegistry {
|
|
1236
|
+
+register(tool): void
|
|
1237
|
+
+unregister(name): boolean
|
|
1238
|
+
+get(name): BaseTool
|
|
1239
|
+
+list(): BaseTool[]
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
class BaseSession {
|
|
1243
|
+
<<abstract>>
|
|
1244
|
+
+id: string
|
|
1245
|
+
+status: SessionStatus
|
|
1246
|
+
+messages: Message[]
|
|
1247
|
+
+usage: Usage
|
|
1248
|
+
+send(input): void
|
|
1249
|
+
+receive(): AsyncGenerator~AgentEvent~
|
|
1250
|
+
+save(): Promise~void~
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
class Middleware {
|
|
1254
|
+
<<function>>
|
|
1255
|
+
(state, next) => Promise~AgentLoopState~
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
Agent --> ModelClient : uses
|
|
1259
|
+
Agent --> ToolRegistry : uses
|
|
1260
|
+
Agent --> StateStore : uses
|
|
1261
|
+
Agent --> BaseSession : creates
|
|
1262
|
+
Agent ..> Middleware : applies
|
|
1263
|
+
ToolRegistry --> BaseTool : contains
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
## π§° Additional Tools
|
|
1267
|
+
|
|
1268
|
+
### CLI
|
|
1269
|
+
|
|
1270
|
+
DimCode includes a terminal UI (TUI) for interactive agent sessions:
|
|
1271
|
+
|
|
1272
|
+
```bash
|
|
1273
|
+
# Install globally
|
|
1274
|
+
npm install -g dimcode@latest
|
|
1275
|
+
|
|
1276
|
+
# Run
|
|
1277
|
+
dim
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
**Features:**
|
|
1281
|
+
- Interactive chat interface
|
|
1282
|
+
- Session management
|
|
1283
|
+
- Tool approval system
|
|
1284
|
+
- Settings configuration
|
|
1285
|
+
|
|
1286
|
+
See [docs/cli.md](./docs/cli.md) for details.
|
|
1287
|
+
|
|
1288
|
+
### ACP Server
|
|
1289
|
+
|
|
1290
|
+
Expose DimCode as an Agent Client Protocol server for editor integrations:
|
|
1291
|
+
|
|
1292
|
+
```bash
|
|
1293
|
+
bun run acp-server
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
**Configuration for Zed** (`settings.json`):
|
|
1297
|
+
|
|
1298
|
+
```json
|
|
1299
|
+
{
|
|
1300
|
+
"agent_servers": {
|
|
1301
|
+
"dimcode": {
|
|
1302
|
+
"command": "pnpm",
|
|
1303
|
+
"args": ["--dir", "/path/to/DimCode", "acp-server"]
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
See [docs/acp-server.md](./docs/acp-server.md) for details.
|
|
1310
|
+
|
|
1311
|
+
## π Documentation
|
|
416
1312
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
| `createSession(options)` | Create a session |
|
|
422
|
-
| `resumeSession(sessionId, options)` | Resume a session |
|
|
423
|
-
| `setModel(modelOrRef)` | Switch/pin model at runtime |
|
|
1313
|
+
- [Getting Started Guide](./docs/getting-started.md) - Comprehensive tutorial
|
|
1314
|
+
- [CLI Documentation](./docs/cli.md) - Terminal UI features and usage
|
|
1315
|
+
- [ACP Server Guide](./docs/acp-server.md) - Editor integration setup
|
|
1316
|
+
- [Examples](./examples/) - Sample implementations
|
|
424
1317
|
|
|
425
|
-
|
|
1318
|
+
## π€ Contributing
|
|
426
1319
|
|
|
427
|
-
|
|
428
|
-
| ----------------------- | -------------------------------------------- |
|
|
429
|
-
| `send(input, options?)` | Queue input for the session |
|
|
430
|
-
| `receive(options?)` | Stream events, resuming from checkpoint if present |
|
|
431
|
-
| `messages` | Conversation history |
|
|
1320
|
+
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
|
|
432
1321
|
|
|
433
1322
|
## π License
|
|
434
1323
|
|