@witqq/agent-sdk 0.7.0 → 0.9.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/dist/{types-CqvUAYxt.d.ts → agent-C6H2CgJA.d.cts} +139 -102
- package/dist/{types-CqvUAYxt.d.cts → agent-F7oB6eKp.d.ts} +139 -102
- package/dist/auth/index.cjs +72 -1
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +21 -154
- package/dist/auth/index.d.ts +21 -154
- package/dist/auth/index.js +72 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/backends/claude.cjs +480 -261
- package/dist/backends/claude.cjs.map +1 -1
- package/dist/backends/claude.d.cts +3 -1
- package/dist/backends/claude.d.ts +3 -1
- package/dist/backends/claude.js +480 -261
- package/dist/backends/claude.js.map +1 -1
- package/dist/backends/copilot.cjs +337 -112
- package/dist/backends/copilot.cjs.map +1 -1
- package/dist/backends/copilot.d.cts +12 -4
- package/dist/backends/copilot.d.ts +12 -4
- package/dist/backends/copilot.js +337 -112
- package/dist/backends/copilot.js.map +1 -1
- package/dist/backends/mock-llm.cjs +719 -0
- package/dist/backends/mock-llm.cjs.map +1 -0
- package/dist/backends/mock-llm.d.cts +37 -0
- package/dist/backends/mock-llm.d.ts +37 -0
- package/dist/backends/mock-llm.js +717 -0
- package/dist/backends/mock-llm.js.map +1 -0
- package/dist/backends/vercel-ai.cjs +301 -61
- package/dist/backends/vercel-ai.cjs.map +1 -1
- package/dist/backends/vercel-ai.d.cts +3 -1
- package/dist/backends/vercel-ai.d.ts +3 -1
- package/dist/backends/vercel-ai.js +301 -61
- package/dist/backends/vercel-ai.js.map +1 -1
- package/dist/backends-Cno0gZjy.d.cts +114 -0
- package/dist/backends-Cno0gZjy.d.ts +114 -0
- package/dist/chat/accumulator.cjs +1 -1
- package/dist/chat/accumulator.cjs.map +1 -1
- package/dist/chat/accumulator.d.cts +5 -2
- package/dist/chat/accumulator.d.ts +5 -2
- package/dist/chat/accumulator.js +1 -1
- package/dist/chat/accumulator.js.map +1 -1
- package/dist/chat/backends.cjs +1084 -821
- package/dist/chat/backends.cjs.map +1 -1
- package/dist/chat/backends.d.cts +10 -6
- package/dist/chat/backends.d.ts +10 -6
- package/dist/chat/backends.js +1082 -800
- package/dist/chat/backends.js.map +1 -1
- package/dist/chat/context.cjs +50 -0
- package/dist/chat/context.cjs.map +1 -1
- package/dist/chat/context.d.cts +27 -3
- package/dist/chat/context.d.ts +27 -3
- package/dist/chat/context.js +50 -0
- package/dist/chat/context.js.map +1 -1
- package/dist/chat/core.cjs +60 -27
- package/dist/chat/core.cjs.map +1 -1
- package/dist/chat/core.d.cts +41 -382
- package/dist/chat/core.d.ts +41 -382
- package/dist/chat/core.js +58 -28
- package/dist/chat/core.js.map +1 -1
- package/dist/chat/errors.cjs +48 -26
- package/dist/chat/errors.cjs.map +1 -1
- package/dist/chat/errors.d.cts +6 -31
- package/dist/chat/errors.d.ts +6 -31
- package/dist/chat/errors.js +48 -25
- package/dist/chat/errors.js.map +1 -1
- package/dist/chat/events.cjs.map +1 -1
- package/dist/chat/events.d.cts +6 -2
- package/dist/chat/events.d.ts +6 -2
- package/dist/chat/events.js.map +1 -1
- package/dist/chat/index.cjs +1612 -1125
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +35 -10
- package/dist/chat/index.d.ts +35 -10
- package/dist/chat/index.js +1600 -1097
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react/theme.css +2517 -0
- package/dist/chat/react.cjs +2212 -1158
- package/dist/chat/react.cjs.map +1 -1
- package/dist/chat/react.d.cts +665 -122
- package/dist/chat/react.d.ts +665 -122
- package/dist/chat/react.js +2191 -1156
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/runtime.cjs +405 -186
- package/dist/chat/runtime.cjs.map +1 -1
- package/dist/chat/runtime.d.cts +92 -28
- package/dist/chat/runtime.d.ts +92 -28
- package/dist/chat/runtime.js +405 -186
- package/dist/chat/runtime.js.map +1 -1
- package/dist/chat/server.cjs +2247 -212
- package/dist/chat/server.cjs.map +1 -1
- package/dist/chat/server.d.cts +451 -90
- package/dist/chat/server.d.ts +451 -90
- package/dist/chat/server.js +2234 -213
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/sessions.cjs +64 -66
- package/dist/chat/sessions.cjs.map +1 -1
- package/dist/chat/sessions.d.cts +37 -118
- package/dist/chat/sessions.d.ts +37 -118
- package/dist/chat/sessions.js +65 -67
- package/dist/chat/sessions.js.map +1 -1
- package/dist/chat/sqlite.cjs +536 -0
- package/dist/chat/sqlite.cjs.map +1 -0
- package/dist/chat/sqlite.d.cts +164 -0
- package/dist/chat/sqlite.d.ts +164 -0
- package/dist/chat/sqlite.js +527 -0
- package/dist/chat/sqlite.js.map +1 -0
- package/dist/chat/state.cjs +14 -1
- package/dist/chat/state.cjs.map +1 -1
- package/dist/chat/state.d.cts +5 -2
- package/dist/chat/state.d.ts +5 -2
- package/dist/chat/state.js +14 -1
- package/dist/chat/state.js.map +1 -1
- package/dist/chat/storage.cjs +58 -33
- package/dist/chat/storage.cjs.map +1 -1
- package/dist/chat/storage.d.cts +18 -8
- package/dist/chat/storage.d.ts +18 -8
- package/dist/chat/storage.js +59 -34
- package/dist/chat/storage.js.map +1 -1
- package/dist/errors-C-so0M4t.d.cts +33 -0
- package/dist/errors-C-so0M4t.d.ts +33 -0
- package/dist/errors-CmVvczxZ.d.cts +28 -0
- package/dist/errors-CmVvczxZ.d.ts +28 -0
- package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-7EIit9Xk.d.ts} +72 -33
- package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-Ct9YcX8I.d.cts} +72 -33
- package/dist/index.cjs +354 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -123
- package/dist/index.d.ts +294 -123
- package/dist/index.js +347 -60
- package/dist/index.js.map +1 -1
- package/dist/provider-types-PTSlRPNB.d.cts +39 -0
- package/dist/provider-types-PTSlRPNB.d.ts +39 -0
- package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
- package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
- package/dist/testing.cjs +1107 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +144 -0
- package/dist/testing.d.ts +144 -0
- package/dist/testing.js +1101 -0
- package/dist/testing.js.map +1 -0
- package/dist/token-store-CSUBgYwn.d.ts +48 -0
- package/dist/token-store-CuC4hB9Z.d.cts +48 -0
- package/dist/{transport-DX1Nhm4N.d.cts → transport-DLWCN18G.d.cts} +5 -4
- package/dist/{transport-D1OaUgRk.d.ts → transport-DsuS-GeM.d.ts} +5 -4
- package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
- package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
- package/dist/types-DgtI1hzh.d.ts +364 -0
- package/dist/types-DkSXALKg.d.cts +364 -0
- package/package.json +41 -5
- package/LICENSE +0 -21
- package/README.md +0 -948
- package/dist/errors-BDLbNu9w.d.cts +0 -13
- package/dist/errors-BDLbNu9w.d.ts +0 -13
- package/dist/types-DLZzlJxt.d.ts +0 -39
- package/dist/types-tE0CXwBl.d.cts +0 -39
package/README.md
DELETED
|
@@ -1,948 +0,0 @@
|
|
|
1
|
-
# agent-sdk
|
|
2
|
-
|
|
3
|
-
Multi-backend AI agent abstraction layer for Node.js. Switch between Copilot CLI, Claude CLI, and Vercel AI SDK backends with a unified API.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @witqq/agent-sdk zod
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Backends
|
|
12
|
-
|
|
13
|
-
`zod` is the only required peer dependency. Backend SDKs are **optional** — install only what you use:
|
|
14
|
-
|
|
15
|
-
| Backend | Peer dependency | Required | Type |
|
|
16
|
-
|---|---|---|---|
|
|
17
|
-
| `copilot` | `@github/copilot-sdk` ^0.1.22 | optional | CLI subprocess |
|
|
18
|
-
| `claude` | `@anthropic-ai/claude-agent-sdk` >=0.2.0 | optional | CLI subprocess |
|
|
19
|
-
| `vercel-ai` | `ai` >=4.0.0 + `@ai-sdk/openai-compatible` >=2.0.0 | optional | API-based |
|
|
20
|
-
|
|
21
|
-
Install only the backend you need:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm install @github/copilot-sdk # copilot
|
|
25
|
-
npm install @anthropic-ai/claude-agent-sdk # claude
|
|
26
|
-
npm install ai @ai-sdk/openai-compatible # vercel-ai
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Quick Start
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { createAgentService } from "@witqq/agent-sdk";
|
|
33
|
-
import { z } from "zod";
|
|
34
|
-
|
|
35
|
-
const service = await createAgentService("copilot", { useLoggedInUser: true });
|
|
36
|
-
|
|
37
|
-
const agent = service.createAgent({
|
|
38
|
-
systemPrompt: "You are a helpful assistant.",
|
|
39
|
-
tools: [
|
|
40
|
-
{
|
|
41
|
-
name: "search",
|
|
42
|
-
description: "Search the web",
|
|
43
|
-
parameters: z.object({ query: z.string() }),
|
|
44
|
-
execute: async ({ query }) => ({ results: [`Result for: ${query}`] }),
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const result = await agent.run("Find news about AI");
|
|
50
|
-
console.log(result.output);
|
|
51
|
-
|
|
52
|
-
agent.dispose();
|
|
53
|
-
await service.dispose();
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Tool Definition
|
|
57
|
-
|
|
58
|
-
Tools are defined with a Zod schema for parameters and an `execute` function:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { z } from "zod";
|
|
62
|
-
import type { ToolDefinition } from "@witqq/agent-sdk";
|
|
63
|
-
|
|
64
|
-
// Basic tool
|
|
65
|
-
const searchTool: ToolDefinition = {
|
|
66
|
-
name: "search",
|
|
67
|
-
description: "Search the web",
|
|
68
|
-
parameters: z.object({ query: z.string() }),
|
|
69
|
-
execute: async ({ query }) => ({ results: [`Result for: ${query}`] }),
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Tool requiring user approval before execution
|
|
73
|
-
const writeFileTool: ToolDefinition = {
|
|
74
|
-
name: "write_file",
|
|
75
|
-
description: "Write content to a file",
|
|
76
|
-
parameters: z.object({ path: z.string(), content: z.string() }),
|
|
77
|
-
needsApproval: true,
|
|
78
|
-
execute: async ({ path, content }) => ({ written: true, path }),
|
|
79
|
-
};
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
When `needsApproval: true`, the `supervisor.onPermission` callback is invoked before execution. Without a supervisor, approval-required tools are denied by default.
|
|
83
|
-
|
|
84
|
-
Runtime-registered tools receive an optional `ToolContext` as their second parameter:
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import type { ToolContext } from "@witqq/agent-sdk";
|
|
88
|
-
|
|
89
|
-
const dbTool: ToolDefinition = {
|
|
90
|
-
name: "query_db",
|
|
91
|
-
description: "Query the database",
|
|
92
|
-
parameters: z.object({ sql: z.string() }),
|
|
93
|
-
execute: async (params, context?: ToolContext) => {
|
|
94
|
-
// context.sessionId — current chat session
|
|
95
|
-
// context.custom — session metadata
|
|
96
|
-
return db.query(params.sql);
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Permission Handling
|
|
102
|
-
|
|
103
|
-
The `supervisor` hooks intercept permission requests and user-facing questions:
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
const agent = service.createAgent({
|
|
107
|
-
systemPrompt: "File assistant",
|
|
108
|
-
tools: [writeFileTool],
|
|
109
|
-
supervisor: {
|
|
110
|
-
onPermission: async (req, signal) => {
|
|
111
|
-
// req.toolName, req.toolArgs, req.suggestedScope
|
|
112
|
-
console.log(`${req.toolName} wants to run with`, req.toolArgs);
|
|
113
|
-
return {
|
|
114
|
-
allowed: true,
|
|
115
|
-
scope: "session", // "once" | "session" | "project" | "always"
|
|
116
|
-
// modifiedInput: { ... }, // optionally modify args before execution
|
|
117
|
-
// reason: "...", // denial reason (if allowed: false)
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
onAskUser: async (req, signal) => {
|
|
121
|
-
// req.question, req.choices, req.allowFreeform
|
|
122
|
-
return { answer: "yes", wasFreeform: false };
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Permission Store
|
|
129
|
-
|
|
130
|
-
Persist permission decisions across runs so approved tools don't re-prompt:
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
import { createDefaultPermissionStore } from "@witqq/agent-sdk";
|
|
134
|
-
|
|
135
|
-
const store = createDefaultPermissionStore("./my-project");
|
|
136
|
-
const agent = service.createAgent({
|
|
137
|
-
systemPrompt: "File assistant",
|
|
138
|
-
permissionStore: store,
|
|
139
|
-
tools: [writeFileTool],
|
|
140
|
-
supervisor: {
|
|
141
|
-
onPermission: async (req) => ({ allowed: true, scope: "project" }),
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Scopes control persistence:
|
|
147
|
-
- `"once"` — not stored, one-time approval
|
|
148
|
-
- `"session"` — in-memory, cleared on dispose
|
|
149
|
-
- `"project"` — persisted to `<projectDir>/.agent-sdk/permissions.json`
|
|
150
|
-
- `"always"` — persisted to `~/.agent-sdk/permissions.json`
|
|
151
|
-
|
|
152
|
-
Custom stores implement `IPermissionStore`:
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
interface IPermissionStore {
|
|
156
|
-
isApproved(toolName: string): Promise<boolean>;
|
|
157
|
-
approve(toolName: string, scope: PermissionScope): Promise<void>;
|
|
158
|
-
revoke(toolName: string): Promise<void>;
|
|
159
|
-
clear(): Promise<void>;
|
|
160
|
-
dispose(): Promise<void>;
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## Structured Output
|
|
165
|
-
|
|
166
|
-
Extract typed data from LLM responses using `runStructured`:
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
import { z } from "zod";
|
|
170
|
-
|
|
171
|
-
const result = await agent.runStructured(
|
|
172
|
-
"What is the capital of France?",
|
|
173
|
-
{
|
|
174
|
-
schema: z.object({
|
|
175
|
-
city: z.string(),
|
|
176
|
-
country: z.string(),
|
|
177
|
-
population: z.number(),
|
|
178
|
-
}),
|
|
179
|
-
name: "city_info", // optional, helps the LLM
|
|
180
|
-
description: "City details", // optional
|
|
181
|
-
},
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
console.log(result.structuredOutput);
|
|
185
|
-
// { city: "Paris", country: "France", population: 2161000 }
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
The Vercel AI backend uses `generateObject()` for structured output. Copilot and Claude backends extract structured data from the LLM text response.
|
|
189
|
-
|
|
190
|
-
## Streaming Events
|
|
191
|
-
|
|
192
|
-
All backends emit the same event types:
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
for await (const event of agent.stream("Tell me a story")) {
|
|
196
|
-
switch (event.type) {
|
|
197
|
-
case "text_delta":
|
|
198
|
-
process.stdout.write(event.text);
|
|
199
|
-
break;
|
|
200
|
-
case "tool_call_start":
|
|
201
|
-
console.log(`Calling ${event.toolName}`, event.args);
|
|
202
|
-
break;
|
|
203
|
-
case "tool_call_end":
|
|
204
|
-
console.log(`${event.toolName} returned`, event.result);
|
|
205
|
-
break;
|
|
206
|
-
case "error":
|
|
207
|
-
console.error(event.error, "recoverable:", event.recoverable);
|
|
208
|
-
break;
|
|
209
|
-
case "done":
|
|
210
|
-
console.log("Final:", event.finalOutput);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Streaming with Conversation History
|
|
217
|
-
|
|
218
|
-
Use `streamWithContext` to stream with full conversation history:
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
const messages = [
|
|
222
|
-
{ role: "system" as const, content: "You are helpful." },
|
|
223
|
-
{ role: "user" as const, content: "Hello" },
|
|
224
|
-
{ role: "assistant" as const, content: "Hi! How can I help?" },
|
|
225
|
-
{ role: "user" as const, content: "What is 2+2?" },
|
|
226
|
-
];
|
|
227
|
-
|
|
228
|
-
for await (const event of agent.streamWithContext(messages)) {
|
|
229
|
-
if (event.type === "text_delta") process.stdout.write(event.text);
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
| Event | Fields | Description |
|
|
234
|
-
|-------|--------|-------------|
|
|
235
|
-
| `text_delta` | `text` | Incremental text output |
|
|
236
|
-
| `thinking_delta` | `text` | Incremental reasoning/thinking text |
|
|
237
|
-
| `thinking_start` | — | Model started reasoning |
|
|
238
|
-
| `thinking_end` | — | Model finished reasoning |
|
|
239
|
-
| `tool_call_start` | `toolCallId`, `toolName`, `args` | Tool invocation began |
|
|
240
|
-
| `tool_call_end` | `toolCallId`, `toolName`, `result` | Tool invocation completed |
|
|
241
|
-
| `permission_request` | `request` | Permission check initiated |
|
|
242
|
-
| `permission_response` | `toolName`, `decision` | Permission decision made |
|
|
243
|
-
| `ask_user` | `request` | User input requested |
|
|
244
|
-
| `ask_user_response` | `answer` | User response received |
|
|
245
|
-
| `session_info` | `sessionId`, `transcriptPath?`, `backend` | CLI session metadata (streaming only) |
|
|
246
|
-
| `usage_update` | `promptTokens`, `completionTokens`, `model?`, `backend?` | Token usage with metadata |
|
|
247
|
-
| `heartbeat` | — | Keepalive signal during long operations |
|
|
248
|
-
| `error` | `error`, `recoverable` | Error during execution |
|
|
249
|
-
| `done` | `finalOutput`, `structuredOutput?` | Execution completed |
|
|
250
|
-
|
|
251
|
-
## Usage Tracking
|
|
252
|
-
|
|
253
|
-
Track token usage with the `onUsage` callback. Called after each `run()`/`runWithContext()`/`runStructured()` completion and during `stream()`/`streamWithContext()` when usage data arrives:
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
const agent = service.createAgent({
|
|
257
|
-
systemPrompt: "You are a helpful assistant.",
|
|
258
|
-
onUsage: (usage) => {
|
|
259
|
-
console.log(`${usage.backend}/${usage.model}: ${usage.promptTokens}+${usage.completionTokens} tokens`);
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Usage data includes `promptTokens`, `completionTokens`, and optional `model` and `backend` fields. Callback errors are logged but not propagated (fire-and-forget).
|
|
265
|
-
|
|
266
|
-
## Heartbeat
|
|
267
|
-
|
|
268
|
-
Keep HTTP streams alive during long tool executions by emitting periodic heartbeat events:
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
const agent = service.createAgent({
|
|
272
|
-
systemPrompt: "You are a helpful assistant.",
|
|
273
|
-
heartbeatInterval: 15000, // emit heartbeat every 15s during gaps
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
for await (const event of agent.stream("Run a long analysis")) {
|
|
277
|
-
if (event.type === "heartbeat") continue; // ignore keepalive
|
|
278
|
-
// handle other events...
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
When `heartbeatInterval` is set, heartbeat events are emitted during streaming gaps (e.g., while a tool executes). No heartbeats are emitted when backend events flow continuously. The timer is cleaned up when the stream completes, errors, or is aborted.
|
|
283
|
-
|
|
284
|
-
## Persistent Sessions (CLI Backends)
|
|
285
|
-
|
|
286
|
-
CLI backends (Copilot, Claude) create a fresh subprocess session per `run()`/`stream()` call by default. Set `sessionMode: "persistent"` to reuse the same CLI session across calls — the CLI backend maintains conversation history natively:
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
const agent = service.createAgent({
|
|
290
|
-
systemPrompt: "You are a helpful assistant.",
|
|
291
|
-
sessionMode: "persistent", // reuse CLI session across calls
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
await agent.run("My name is Alice");
|
|
295
|
-
const result = await agent.run("What is my name?");
|
|
296
|
-
// result.output contains "Alice" — history maintained by CLI
|
|
297
|
-
|
|
298
|
-
console.log(agent.sessionId); // CLI session ID for external tracking
|
|
299
|
-
agent.dispose(); // destroys the persistent session
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
In persistent mode, if a session encounters an error, it is automatically cleared and recreated on the next call. The `sessionId` property exposes the CLI session ID for logging or external storage.
|
|
303
|
-
|
|
304
|
-
### Interrupting Running Operations
|
|
305
|
-
|
|
306
|
-
Call `interrupt()` to gracefully stop a running operation. For CLI backends, this calls the SDK's interrupt/abort method on the active session:
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
// In another context (e.g., timeout handler)
|
|
310
|
-
await agent.interrupt();
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
Default (`"per-call"`): each call creates and destroys a fresh session. Multi-message context is passed via prompt augmentation through `runWithContext()`/`streamWithContext()`.
|
|
314
|
-
|
|
315
|
-
API-based backends (Vercel AI) ignore `sessionMode` — they are stateless by design.
|
|
316
|
-
|
|
317
|
-
## Backend-Specific Options
|
|
318
|
-
|
|
319
|
-
### Copilot
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
import { createCopilotService } from "@witqq/agent-sdk/copilot";
|
|
323
|
-
|
|
324
|
-
const service = createCopilotService({
|
|
325
|
-
useLoggedInUser: true, // use GitHub CLI auth
|
|
326
|
-
cliPath: "/path/to/copilot", // optional custom CLI path
|
|
327
|
-
workingDirectory: process.cwd(),
|
|
328
|
-
githubToken: "ghp_...", // optional, alternative to useLoggedInUser
|
|
329
|
-
cliArgs: ["--allow-all"], // extra CLI flags for the subprocess
|
|
330
|
-
env: { PATH: "/custom/bin" }, // custom env vars for subprocess
|
|
331
|
-
});
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**System requirements:** `@github/copilot-sdk` includes a native binary that requires glibc. Alpine Linux (musl) is not supported — use `node:20-bookworm-slim` or similar glibc-based images.
|
|
335
|
-
|
|
336
|
-
**Headless defaults:** When `supervisor.onPermission` or `supervisor.onAskUser` are not provided, the Copilot backend auto-approves permission requests and auto-answers user questions to prevent the SDK from hanging in headless mode.
|
|
337
|
-
|
|
338
|
-
**System prompt mode:** By default, `systemPrompt` is appended to the Copilot CLI's built-in prompt (`mode: "append"`). Set `systemMessageMode: "replace"` in `AgentConfig` to fully replace it (note: this removes built-in tool instructions).
|
|
339
|
-
|
|
340
|
-
**Available tools filter:** Use `availableTools` in `AgentConfig` to restrict which Copilot built-in tools are available:
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
const agent = service.createAgent({
|
|
344
|
-
systemPrompt: "Research assistant",
|
|
345
|
-
tools: [],
|
|
346
|
-
availableTools: ["web_search", "web_fetch"], // only these built-in tools
|
|
347
|
-
});
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Claude
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
import { createClaudeService } from "@witqq/agent-sdk/claude";
|
|
354
|
-
|
|
355
|
-
const service = createClaudeService({
|
|
356
|
-
cliPath: "/path/to/claude", // optional custom CLI path
|
|
357
|
-
workingDirectory: process.cwd(),
|
|
358
|
-
maxTurns: 10,
|
|
359
|
-
env: { CLAUDE_CONFIG_DIR: "/custom/config" }, // custom env vars for subprocess
|
|
360
|
-
});
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
`supervisor.onAskUser` is not supported by the Claude backend; a warning is emitted if set.
|
|
364
|
-
|
|
365
|
-
When `supervisor.onPermission` is set, the Claude backend automatically sets `permissionMode: "default"` so the CLI invokes the callback instead of using built-in rules.
|
|
366
|
-
|
|
367
|
-
### Vercel AI (OpenRouter / OpenAI-compatible)
|
|
368
|
-
|
|
369
|
-
```typescript
|
|
370
|
-
import { createVercelAIService } from "@witqq/agent-sdk/vercel-ai";
|
|
371
|
-
|
|
372
|
-
const service = createVercelAIService({
|
|
373
|
-
apiKey: process.env.OPENROUTER_API_KEY!,
|
|
374
|
-
baseUrl: "https://openrouter.ai/api/v1", // default
|
|
375
|
-
provider: "openrouter", // default
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const agent = service.createAgent({
|
|
379
|
-
model: "anthropic/claude-sonnet-4-5",
|
|
380
|
-
systemPrompt: "You are a helpful assistant.",
|
|
381
|
-
tools: [searchTool],
|
|
382
|
-
});
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
Uses `generateText()` for runs, `generateObject()` for structured output, `streamText()` for streaming. Supports `supervisor.onAskUser` via an injected `ask_user` tool.
|
|
386
|
-
|
|
387
|
-
Pass model-specific options via `providerOptions`:
|
|
388
|
-
|
|
389
|
-
```typescript
|
|
390
|
-
const agent = service.createAgent({
|
|
391
|
-
model: "google/gemini-2.0-flash",
|
|
392
|
-
systemPrompt: "Think step by step.",
|
|
393
|
-
providerOptions: {
|
|
394
|
-
google: { thinkingConfig: { thinkingBudget: 1024 } },
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
## Switching Backends
|
|
400
|
-
|
|
401
|
-
All backends share the same `AgentConfig` and return the same `AgentResult`. To switch backends, change only the service creation:
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
import { createAgentService } from "@witqq/agent-sdk";
|
|
405
|
-
import { z } from "zod";
|
|
406
|
-
|
|
407
|
-
const tools = [
|
|
408
|
-
{
|
|
409
|
-
name: "greet",
|
|
410
|
-
description: "Greet a user",
|
|
411
|
-
parameters: z.object({ name: z.string() }),
|
|
412
|
-
execute: async ({ name }) => ({ message: `Hello, ${name}!` }),
|
|
413
|
-
},
|
|
414
|
-
];
|
|
415
|
-
|
|
416
|
-
const config = {
|
|
417
|
-
systemPrompt: "You are a helpful assistant.",
|
|
418
|
-
tools,
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
// Switch backend by changing the first argument:
|
|
422
|
-
const service = await createAgentService("copilot", { useLoggedInUser: true });
|
|
423
|
-
// const service = await createAgentService("claude", { workingDirectory: "." });
|
|
424
|
-
// const service = await createAgentService("vercel-ai", { apiKey: "..." });
|
|
425
|
-
|
|
426
|
-
const agent = service.createAgent(config);
|
|
427
|
-
const result = await agent.run("Greet Alice");
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
Or use direct backend imports to avoid lazy loading:
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
import { createCopilotService } from "@witqq/agent-sdk/copilot";
|
|
434
|
-
import { createClaudeService } from "@witqq/agent-sdk/claude";
|
|
435
|
-
import { createVercelAIService } from "@witqq/agent-sdk/vercel-ai";
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
## Model Names
|
|
439
|
-
|
|
440
|
-
`AgentConfig.model` accepts both full model IDs and short names:
|
|
441
|
-
|
|
442
|
-
| Backend | Full ID example | Short name |
|
|
443
|
-
|---|---|---|
|
|
444
|
-
| Copilot | `gpt-4o` | (same) |
|
|
445
|
-
| Claude | `claude-sonnet-4-5-20250514` | `sonnet` |
|
|
446
|
-
| Vercel AI | `anthropic/claude-sonnet-4-5` | (provider-specific) |
|
|
447
|
-
|
|
448
|
-
Use `service.listModels()` to get available model IDs for each backend. Copilot lists models from GitHub API. Claude queries the Anthropic `/v1/models` endpoint when `oauthToken` is provided (returns empty list without token). Vercel AI queries the provider's `/models` endpoint (returns empty list on failure).
|
|
449
|
-
|
|
450
|
-
## Build
|
|
451
|
-
|
|
452
|
-
```bash
|
|
453
|
-
npm run build # tsup → ESM + CJS
|
|
454
|
-
npm run test # vitest
|
|
455
|
-
npm run typecheck # tsc --noEmit
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
## Authentication
|
|
459
|
-
|
|
460
|
-
Programmatic OAuth flows for obtaining tokens without manual terminal interaction.
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
import { CopilotAuth, ClaudeAuth } from "@witqq/agent-sdk/auth";
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### Copilot (GitHub Device Flow)
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
const auth = new CopilotAuth();
|
|
470
|
-
const { verificationUrl, userCode, waitForToken } = await auth.startDeviceFlow();
|
|
471
|
-
|
|
472
|
-
// Show the user: open verificationUrl and enter userCode
|
|
473
|
-
console.log(`Open ${verificationUrl} and enter code: ${userCode}`);
|
|
474
|
-
|
|
475
|
-
const token = await waitForToken(); // polls until authorized
|
|
476
|
-
// token.accessToken = "gho_..." (long-lived, no expiration)
|
|
477
|
-
|
|
478
|
-
// Use with Copilot backend:
|
|
479
|
-
const service = createCopilotService({ githubToken: token.accessToken });
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### Claude (OAuth + PKCE)
|
|
483
|
-
|
|
484
|
-
```typescript
|
|
485
|
-
const auth = new ClaudeAuth();
|
|
486
|
-
const { authorizeUrl, completeAuth } = auth.startOAuthFlow();
|
|
487
|
-
|
|
488
|
-
// Open authorizeUrl in browser — user authorizes, gets redirected
|
|
489
|
-
// completeAuth accepts raw code, full redirect URL, or code#state format
|
|
490
|
-
console.log(`Open: ${authorizeUrl}`);
|
|
491
|
-
|
|
492
|
-
const token = await completeAuth(codeOrUrl);
|
|
493
|
-
// token.accessToken = "sk-ant-oat01-..." (expires in 8h, has refreshToken)
|
|
494
|
-
|
|
495
|
-
// Refresh before expiry:
|
|
496
|
-
const refreshed = await auth.refreshToken(token.refreshToken);
|
|
497
|
-
|
|
498
|
-
// Use with Claude backend:
|
|
499
|
-
const service = createClaudeService({ oauthToken: token.accessToken });
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
### Token Types
|
|
503
|
-
|
|
504
|
-
```typescript
|
|
505
|
-
interface AuthToken {
|
|
506
|
-
accessToken: string;
|
|
507
|
-
tokenType: string;
|
|
508
|
-
expiresIn?: number; // seconds until expiry (undefined = long-lived)
|
|
509
|
-
obtainedAt: number; // Date.now() when token was obtained
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
interface ClaudeAuthToken extends AuthToken {
|
|
513
|
-
refreshToken: string; // for refreshing expired tokens
|
|
514
|
-
scopes: string[];
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
interface CopilotAuthToken extends AuthToken {
|
|
518
|
-
login?: string; // GitHub username
|
|
519
|
-
}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
### Token Auto-Refresh
|
|
523
|
-
|
|
524
|
-
`TokenRefreshManager` schedules background token refresh before expiry:
|
|
525
|
-
|
|
526
|
-
```typescript
|
|
527
|
-
import { TokenRefreshManager } from "@witqq/agent-sdk/auth";
|
|
528
|
-
|
|
529
|
-
const manager = new TokenRefreshManager({
|
|
530
|
-
token: authToken,
|
|
531
|
-
refreshFn: async (token) => claudeAuth.refreshToken(token.refreshToken!),
|
|
532
|
-
refreshThreshold: 0.8, // refresh at 80% of token lifetime
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
manager.on("refreshed", (newToken) => { /* update stored token */ });
|
|
536
|
-
manager.on("expired", () => { /* re-authenticate */ });
|
|
537
|
-
manager.start();
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
## Chat SDK (experimental)
|
|
541
|
-
|
|
542
|
-
Higher-level primitives for building AI chat applications on top of agent-sdk.
|
|
543
|
-
|
|
544
|
-
### Barrel Import
|
|
545
|
-
|
|
546
|
-
For most consumer apps, import common types from a single path:
|
|
547
|
-
|
|
548
|
-
```typescript
|
|
549
|
-
import {
|
|
550
|
-
ChatMessage, ChatSession, ChatEvent, IChatRuntime,
|
|
551
|
-
createChatRuntime, ChatError, classifyError,
|
|
552
|
-
useChat, useRemoteChat, useRemoteAuth,
|
|
553
|
-
ChatProvider, Thread, Composer,
|
|
554
|
-
RemoteChatRuntime, SSEChatTransport,
|
|
555
|
-
} from "@witqq/agent-sdk/chat";
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
### Individual Module Imports
|
|
559
|
-
|
|
560
|
-
```typescript
|
|
561
|
-
import { ChatMessage, ChatSession, IChatProvider, isChatMessage } from "@witqq/agent-sdk/chat/core";
|
|
562
|
-
import {
|
|
563
|
-
classifyError, withRetry, isRetryable,
|
|
564
|
-
ChatSDKError, NetworkError, RateLimitError,
|
|
565
|
-
ExponentialBackoffStrategy
|
|
566
|
-
} from "@witqq/agent-sdk/chat/errors";
|
|
567
|
-
import {
|
|
568
|
-
ChatEventBus, filterEvents, collectText
|
|
569
|
-
} from "@witqq/agent-sdk/chat/events";
|
|
570
|
-
import {
|
|
571
|
-
InMemoryStorage, FileStorage,
|
|
572
|
-
type IStorageAdapter, StorageError
|
|
573
|
-
} from "@witqq/agent-sdk/chat/storage";
|
|
574
|
-
import {
|
|
575
|
-
InMemorySessionStore, FileSessionStore,
|
|
576
|
-
type IChatSessionStore
|
|
577
|
-
} from "@witqq/agent-sdk/chat/sessions";
|
|
578
|
-
import {
|
|
579
|
-
ContextWindowManager, estimateTokens
|
|
580
|
-
} from "@witqq/agent-sdk/chat/context";
|
|
581
|
-
import {
|
|
582
|
-
CopilotChatAdapter, VercelAIChatAdapter, BaseBackendAdapter,
|
|
583
|
-
SSEChatTransport, WsChatTransport, InProcessChatTransport,
|
|
584
|
-
streamToTransport, withInterceptors,
|
|
585
|
-
type IBackendAdapter, type BackendAdapterOptions, type IChatTransport
|
|
586
|
-
} from "@witqq/agent-sdk/chat/backends";
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
### Error Classification
|
|
590
|
-
|
|
591
|
-
```typescript
|
|
592
|
-
try {
|
|
593
|
-
await provider.send(message);
|
|
594
|
-
} catch (err) {
|
|
595
|
-
const classified = classifyError(err);
|
|
596
|
-
if (classified instanceof RateLimitError) {
|
|
597
|
-
console.log(`Rate limited, retry after ${classified.retryAfterSeconds}s`);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
### Retry with Backoff
|
|
603
|
-
|
|
604
|
-
```typescript
|
|
605
|
-
const result = await withRetry(
|
|
606
|
-
() => provider.send(message),
|
|
607
|
-
new ExponentialBackoffStrategy({ maxAttempts: 3 }),
|
|
608
|
-
{ signal: AbortSignal.timeout(30_000) },
|
|
609
|
-
);
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
### Event Bus with Middleware
|
|
613
|
-
|
|
614
|
-
```typescript
|
|
615
|
-
const bus = new ChatEventBus();
|
|
616
|
-
|
|
617
|
-
// Logging middleware
|
|
618
|
-
bus.use((ctx) => {
|
|
619
|
-
console.log(`[${ctx.event.type}]`);
|
|
620
|
-
ctx.next();
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// Filter out heartbeat events
|
|
624
|
-
bus.use((ctx) => {
|
|
625
|
-
if (ctx.event.type === "heartbeat") ctx.suppress();
|
|
626
|
-
else ctx.next();
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
bus.on("message_delta", (event) => console.log(event.text));
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### Storage Adapters
|
|
633
|
-
|
|
634
|
-
```typescript
|
|
635
|
-
// In-memory (dev/testing)
|
|
636
|
-
const mem = new InMemoryStorage<ChatSession>();
|
|
637
|
-
await mem.create("s1", session);
|
|
638
|
-
const s = await mem.get("s1"); // deep copy, mutation-safe
|
|
639
|
-
|
|
640
|
-
// File-based (persistence)
|
|
641
|
-
const fs = new FileStorage<ChatSession>({ directory: "./data/sessions" });
|
|
642
|
-
await fs.create("s1", session);
|
|
643
|
-
const items = await fs.query({
|
|
644
|
-
filter: (s) => s.metadata.tags.includes("important"),
|
|
645
|
-
sort: (a, b) => b.updatedAt - a.updatedAt,
|
|
646
|
-
limit: 10,
|
|
647
|
-
});
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
### Session Store
|
|
651
|
-
|
|
652
|
-
```typescript
|
|
653
|
-
const store = new InMemorySessionStore();
|
|
654
|
-
// or: new FileSessionStore({ directory: "./data/sessions" })
|
|
655
|
-
|
|
656
|
-
const session = await store.createSession({
|
|
657
|
-
config: { model: "gpt-4", backend: "vercel-ai" },
|
|
658
|
-
title: "Code Review",
|
|
659
|
-
tags: ["work"],
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
await store.addMessage(session.id, message);
|
|
663
|
-
const page = await store.getMessages(session.id, { limit: 20, offset: 0 });
|
|
664
|
-
// page.messages, page.total, page.hasMore
|
|
665
|
-
|
|
666
|
-
const results = await store.searchSessions({ query: "typescript" });
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
### Context Window Manager
|
|
670
|
-
|
|
671
|
-
```typescript
|
|
672
|
-
const manager = new ContextWindowManager({
|
|
673
|
-
maxTokens: 4096,
|
|
674
|
-
reservedTokens: 500,
|
|
675
|
-
strategy: "truncate-oldest", // or "sliding-window", "summarize-placeholder"
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
const result = manager.fitMessages(messages);
|
|
679
|
-
// result.messages — trimmed to fit budget
|
|
680
|
-
// result.wasTruncated — whether messages were removed
|
|
681
|
-
// result.totalTokens — estimated token usage
|
|
682
|
-
// result.removedCount — how many messages were dropped
|
|
683
|
-
|
|
684
|
-
// Async variant with optional summarizer (summarize-placeholder strategy)
|
|
685
|
-
const asyncManager = new ContextWindowManager({
|
|
686
|
-
maxTokens: 4096,
|
|
687
|
-
strategy: "summarize-placeholder",
|
|
688
|
-
summarizer: async (removed) => {
|
|
689
|
-
// Call LLM or custom logic to summarize removed messages
|
|
690
|
-
return `Summary of ${removed.length} messages: ...`;
|
|
691
|
-
},
|
|
692
|
-
});
|
|
693
|
-
const asyncResult = await asyncManager.fitMessagesAsync(messages);
|
|
694
|
-
|
|
695
|
-
// Per-message estimation
|
|
696
|
-
const tokens = estimateTokens(message); // ~chars/4
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
### Backend Adapters
|
|
700
|
-
|
|
701
|
-
Backend adapters bridge `IAgentService` to `IChatProvider`, adding session management and resume support:
|
|
702
|
-
|
|
703
|
-
```typescript
|
|
704
|
-
import { CopilotChatAdapter } from "@witqq/agent-sdk/chat/backends";
|
|
705
|
-
|
|
706
|
-
const adapter = new CopilotChatAdapter({
|
|
707
|
-
agentConfig: {
|
|
708
|
-
systemPrompt: "You are a helpful assistant.",
|
|
709
|
-
model: "gpt-4.1",
|
|
710
|
-
},
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
// Stream a message (creates persistent session automatically)
|
|
714
|
-
for await (const event of adapter.streamMessage(session, "Hello")) {
|
|
715
|
-
// ChatEvent: text_delta, message_start, message_complete, tool_call_start, etc.
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// Resume a previous session
|
|
719
|
-
if (adapter.canResume()) {
|
|
720
|
-
for await (const event of adapter.resume(session, adapter.backendSessionId!)) {
|
|
721
|
-
// Continues the existing conversation
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
adapter.dispose();
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
`IBackendAdapter` extends `IChatProvider` with `canResume()`, `resume()`, `backendSessionId`, and `agentService` accessor. Built-in adapters: `CopilotChatAdapter`, `ClaudeChatAdapter`, `VercelAIChatAdapter` (stateless, no resume). Create custom adapters by extending `BaseBackendAdapter`.
|
|
729
|
-
|
|
730
|
-
Service ownership: when `agentService` is passed via options, the adapter does **not** dispose it — the caller retains ownership. When omitted, the adapter creates and owns its service internally.
|
|
731
|
-
|
|
732
|
-
### Chat Transport
|
|
733
|
-
|
|
734
|
-
`IChatTransport` abstracts event delivery to clients. Three built-in implementations:
|
|
735
|
-
|
|
736
|
-
| Transport | Use case |
|
|
737
|
-
|---|---|
|
|
738
|
-
| `SSEChatTransport` | Server-Sent Events over HTTP |
|
|
739
|
-
| `WsChatTransport` | WebSocket via `WebSocketLike` abstraction |
|
|
740
|
-
| `InProcessChatTransport` | Zero-network async iterable for testing/embedded |
|
|
741
|
-
|
|
742
|
-
`streamToTransport()` pipes adapter events to any transport:
|
|
743
|
-
|
|
744
|
-
```typescript
|
|
745
|
-
import { SSEChatTransport, WsChatTransport, streamToTransport } from "@witqq/agent-sdk/chat/backends";
|
|
746
|
-
|
|
747
|
-
const transport = new SSEChatTransport(res);
|
|
748
|
-
await streamToTransport(adapter.streamMessage(session, message), transport);
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
**Interceptors** wrap any transport with composable hooks (logging, metrics, rate limiting):
|
|
752
|
-
|
|
753
|
-
```typescript
|
|
754
|
-
import { withInterceptors, type TransportInterceptor } from "@witqq/agent-sdk/chat/backends";
|
|
755
|
-
|
|
756
|
-
const logger: TransportInterceptor = {
|
|
757
|
-
beforeSend(event) { console.log("send:", event.type); return event; },
|
|
758
|
-
onError(err) { console.error(err); },
|
|
759
|
-
};
|
|
760
|
-
const wrapped = withInterceptors(transport, [logger]);
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
**Stream watchdog** — set `streamTimeoutMs` in runtime options to abort hanging streams:
|
|
764
|
-
|
|
765
|
-
```typescript
|
|
766
|
-
const runtime = createChatRuntime({
|
|
767
|
-
streamTimeoutMs: 30_000, // abort after 30s of inactivity
|
|
768
|
-
// ...
|
|
769
|
-
});
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
See [Custom Transports](docs/chat-sdk/custom-transports.md) for the implementation guide.
|
|
773
|
-
|
|
774
|
-
### Chat Runtime
|
|
775
|
-
|
|
776
|
-
`IChatRuntime<TMetadata>` is the unified facade that orchestrates backend adapters, sessions, context trimming, streaming, and middleware. `createChatRuntime()` builds one from a config:
|
|
777
|
-
|
|
778
|
-
```typescript
|
|
779
|
-
import { createChatRuntime } from "@witqq/agent-sdk/chat/runtime";
|
|
780
|
-
|
|
781
|
-
const runtime = createChatRuntime({
|
|
782
|
-
backends: {
|
|
783
|
-
copilot: () => new CopilotChatAdapter({ agentService }),
|
|
784
|
-
claude: () => new ClaudeChatAdapter({ agentService: claudeService }),
|
|
785
|
-
},
|
|
786
|
-
defaultBackend: "copilot",
|
|
787
|
-
sessionStore: new InMemorySessionStore(),
|
|
788
|
-
contextManager: new ContextWindowManager({ maxTokens: 8000 }),
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
// Create session, send message, stream events
|
|
792
|
-
const session = await runtime.createSession();
|
|
793
|
-
for await (const event of runtime.send(session.id, "Hello")) {
|
|
794
|
-
console.log(event.type, event);
|
|
795
|
-
}
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
Key capabilities: session delegation (create/get/list/delete/archive/switch), backend/model switching with `switchBackend(name)` / `switchModel(model)`, tool registration via `addTool(def)` / `removeTool(name)` (persists across switches), middleware pipeline (`use(middleware)`), state machine (`status` property), abort support (`abort()`), pre-stream retry with `RetryConfig`, generic `<TMetadata>` for typed session metadata, and `dispose()`.
|
|
799
|
-
|
|
800
|
-
Context monitoring:
|
|
801
|
-
|
|
802
|
-
```typescript
|
|
803
|
-
// Query context usage after send
|
|
804
|
-
const stats = runtime.getContextStats(session.id);
|
|
805
|
-
// stats: { totalTokens, removedCount, wasTruncated, availableBudget } | null
|
|
806
|
-
|
|
807
|
-
// Archive trimmed messages via callback
|
|
808
|
-
const runtime = createChatRuntime({
|
|
809
|
-
// ...backends, sessionStore, contextManager
|
|
810
|
-
onContextTrimmed: (sessionId, removedMessages) => {
|
|
811
|
-
db.archiveMessages(sessionId, removedMessages);
|
|
812
|
-
},
|
|
813
|
-
});
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
### Server Utilities
|
|
817
|
-
|
|
818
|
-
Framework-agnostic HTTP handlers for serving `IChatRuntime` over HTTP. Import from `@witqq/agent-sdk/chat/server`.
|
|
819
|
-
|
|
820
|
-
```typescript
|
|
821
|
-
import {
|
|
822
|
-
createChatHandler,
|
|
823
|
-
createAuthHandler,
|
|
824
|
-
FileTokenStore,
|
|
825
|
-
corsMiddleware,
|
|
826
|
-
createChatServer,
|
|
827
|
-
} from "@witqq/agent-sdk/chat/server";
|
|
828
|
-
import { createChatRuntime } from "@witqq/agent-sdk/chat/runtime";
|
|
829
|
-
|
|
830
|
-
const runtime = createChatRuntime({ /* ... */ });
|
|
831
|
-
|
|
832
|
-
// Option 1: Compose handlers manually
|
|
833
|
-
const chatHandler = createChatHandler(runtime, { prefix: "/api/chat" });
|
|
834
|
-
const authHandler = createAuthHandler({
|
|
835
|
-
tokenStore: new FileTokenStore({ directory: "./tokens" }),
|
|
836
|
-
onAuth: (backend, token) => { /* handle auth */ },
|
|
837
|
-
});
|
|
838
|
-
|
|
839
|
-
// Option 2: One-call server factory
|
|
840
|
-
const handler = createChatServer({
|
|
841
|
-
runtime,
|
|
842
|
-
cors: true,
|
|
843
|
-
staticDir: "./public",
|
|
844
|
-
});
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
`createChatHandler` maps all 10 `RemoteChatRuntime` endpoints (session CRUD, send via SSE, abort, models, backend/model switch). `createAuthHandler` handles Copilot Device Flow, Claude OAuth+PKCE, and API key auth with persistent token storage via `ITokenStore`. `corsMiddleware` supports multi-origin configuration.
|
|
848
|
-
|
|
849
|
-
## Interactive Demo
|
|
850
|
-
|
|
851
|
-
Single-screen chat UI with inline provider/model selection and auth.
|
|
852
|
-
|
|
853
|
-
```bash
|
|
854
|
-
npm run demo # Build & start in Docker (http://localhost:3456)
|
|
855
|
-
npm run demo -- stop # Stop
|
|
856
|
-
npm run demo -- logs # Follow logs
|
|
857
|
-
npm run demo -- restart # Rebuild & restart
|
|
858
|
-
npm run demo -- dev # Local dev without Docker
|
|
859
|
-
```
|
|
860
|
-
|
|
861
|
-
Features: inline provider switching, auth via modal dialog (Copilot Device Flow, Claude OAuth+PKCE, Vercel AI API key), model dropdown with search, SSE streaming chat with thinking blocks, tool calls, and error rendering.
|
|
862
|
-
|
|
863
|
-
## React Bindings
|
|
864
|
-
|
|
865
|
-
Headless React hooks and components for building chat UIs:
|
|
866
|
-
|
|
867
|
-
```typescript
|
|
868
|
-
import { useChat, Thread, Composer, ChatProvider } from "@witqq/agent-sdk/chat/react";
|
|
869
|
-
|
|
870
|
-
function App() {
|
|
871
|
-
return (
|
|
872
|
-
<ChatProvider runtime={runtime}>
|
|
873
|
-
<Thread />
|
|
874
|
-
<Composer />
|
|
875
|
-
</ChatProvider>
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
For client-server architectures, `useRemoteChat` manages the full auth → runtime → session lifecycle:
|
|
881
|
-
|
|
882
|
-
```typescript
|
|
883
|
-
import { useRemoteChat, ChatProvider, Thread, Composer } from "@witqq/agent-sdk/chat/react";
|
|
884
|
-
|
|
885
|
-
function App() {
|
|
886
|
-
const chat = useRemoteChat({
|
|
887
|
-
chatBaseUrl: "/api/chat",
|
|
888
|
-
authBaseUrl: "/api",
|
|
889
|
-
backend: "copilot",
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
if (chat.phase !== "ready" || !chat.runtime) return <div>Loading...</div>;
|
|
893
|
-
|
|
894
|
-
return (
|
|
895
|
-
<ChatProvider runtime={chat.runtime}>
|
|
896
|
-
<Thread />
|
|
897
|
-
<Composer />
|
|
898
|
-
</ChatProvider>
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
Or use `RemoteChatRuntime` directly for lower-level control:
|
|
904
|
-
|
|
905
|
-
```typescript
|
|
906
|
-
import { RemoteChatRuntime } from "@witqq/agent-sdk/chat/react";
|
|
907
|
-
|
|
908
|
-
const runtime = new RemoteChatRuntime({ baseUrl: "/api/chat" });
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
Reactive session list (replaces manual polling):
|
|
912
|
-
|
|
913
|
-
```typescript
|
|
914
|
-
import { useSessions } from "@witqq/agent-sdk/chat/react";
|
|
915
|
-
|
|
916
|
-
function SessionList() {
|
|
917
|
-
const { sessions, loading } = useSessions();
|
|
918
|
-
// Auto-updates on create, delete, archive, and message send
|
|
919
|
-
return sessions.map(s => <div key={s.id}>{s.title}</div>);
|
|
920
|
-
}
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
Server-delegated authentication (no `node:crypto` in browser):
|
|
924
|
-
|
|
925
|
-
```typescript
|
|
926
|
-
import { useRemoteAuth } from "@witqq/agent-sdk/chat/react";
|
|
927
|
-
|
|
928
|
-
const auth = useRemoteAuth({ backend: "copilot", baseUrl: "/api" });
|
|
929
|
-
// auth.startDeviceFlow(), auth.startOAuthFlow(), auth.submitApiKey()
|
|
930
|
-
```
|
|
931
|
-
|
|
932
|
-
See [Chat SDK docs](docs/chat-sdk/README.md) for the full React API reference.
|
|
933
|
-
|
|
934
|
-
## Documentation
|
|
935
|
-
|
|
936
|
-
| Document | Description |
|
|
937
|
-
|----------|-------------|
|
|
938
|
-
| [Chat SDK Modules](docs/chat-sdk/README.md) | Module-by-module API docs for chat primitives |
|
|
939
|
-
| [Chat SDK Architecture](docs/chat-sdk/ARCHITECTURE.md) | Architecture specification and design decisions |
|
|
940
|
-
| [Custom Transports](docs/chat-sdk/custom-transports.md) | Guide to building custom IChatTransport implementations |
|
|
941
|
-
| [Custom Renderers](docs/chat-sdk/custom-renderers.md) | Three approaches to customizing React UI components |
|
|
942
|
-
| [Roadmap](docs/architecture/ROADMAP.md) | Module implementation roadmap (M1-M12) |
|
|
943
|
-
| [Project Checklist](PROJECT_CHECKLIST.md) | Implementation checklist with completion status |
|
|
944
|
-
| [Changelog](CHANGELOG.md) | Release history and breaking changes |
|
|
945
|
-
|
|
946
|
-
## License
|
|
947
|
-
|
|
948
|
-
MIT
|