@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.
Files changed (154) hide show
  1. package/dist/{types-CqvUAYxt.d.ts → agent-C6H2CgJA.d.cts} +139 -102
  2. package/dist/{types-CqvUAYxt.d.cts → agent-F7oB6eKp.d.ts} +139 -102
  3. package/dist/auth/index.cjs +72 -1
  4. package/dist/auth/index.cjs.map +1 -1
  5. package/dist/auth/index.d.cts +21 -154
  6. package/dist/auth/index.d.ts +21 -154
  7. package/dist/auth/index.js +72 -1
  8. package/dist/auth/index.js.map +1 -1
  9. package/dist/backends/claude.cjs +480 -261
  10. package/dist/backends/claude.cjs.map +1 -1
  11. package/dist/backends/claude.d.cts +3 -1
  12. package/dist/backends/claude.d.ts +3 -1
  13. package/dist/backends/claude.js +480 -261
  14. package/dist/backends/claude.js.map +1 -1
  15. package/dist/backends/copilot.cjs +337 -112
  16. package/dist/backends/copilot.cjs.map +1 -1
  17. package/dist/backends/copilot.d.cts +12 -4
  18. package/dist/backends/copilot.d.ts +12 -4
  19. package/dist/backends/copilot.js +337 -112
  20. package/dist/backends/copilot.js.map +1 -1
  21. package/dist/backends/mock-llm.cjs +719 -0
  22. package/dist/backends/mock-llm.cjs.map +1 -0
  23. package/dist/backends/mock-llm.d.cts +37 -0
  24. package/dist/backends/mock-llm.d.ts +37 -0
  25. package/dist/backends/mock-llm.js +717 -0
  26. package/dist/backends/mock-llm.js.map +1 -0
  27. package/dist/backends/vercel-ai.cjs +301 -61
  28. package/dist/backends/vercel-ai.cjs.map +1 -1
  29. package/dist/backends/vercel-ai.d.cts +3 -1
  30. package/dist/backends/vercel-ai.d.ts +3 -1
  31. package/dist/backends/vercel-ai.js +301 -61
  32. package/dist/backends/vercel-ai.js.map +1 -1
  33. package/dist/backends-Cno0gZjy.d.cts +114 -0
  34. package/dist/backends-Cno0gZjy.d.ts +114 -0
  35. package/dist/chat/accumulator.cjs +1 -1
  36. package/dist/chat/accumulator.cjs.map +1 -1
  37. package/dist/chat/accumulator.d.cts +5 -2
  38. package/dist/chat/accumulator.d.ts +5 -2
  39. package/dist/chat/accumulator.js +1 -1
  40. package/dist/chat/accumulator.js.map +1 -1
  41. package/dist/chat/backends.cjs +1084 -821
  42. package/dist/chat/backends.cjs.map +1 -1
  43. package/dist/chat/backends.d.cts +10 -6
  44. package/dist/chat/backends.d.ts +10 -6
  45. package/dist/chat/backends.js +1082 -800
  46. package/dist/chat/backends.js.map +1 -1
  47. package/dist/chat/context.cjs +50 -0
  48. package/dist/chat/context.cjs.map +1 -1
  49. package/dist/chat/context.d.cts +27 -3
  50. package/dist/chat/context.d.ts +27 -3
  51. package/dist/chat/context.js +50 -0
  52. package/dist/chat/context.js.map +1 -1
  53. package/dist/chat/core.cjs +60 -27
  54. package/dist/chat/core.cjs.map +1 -1
  55. package/dist/chat/core.d.cts +41 -382
  56. package/dist/chat/core.d.ts +41 -382
  57. package/dist/chat/core.js +58 -28
  58. package/dist/chat/core.js.map +1 -1
  59. package/dist/chat/errors.cjs +48 -26
  60. package/dist/chat/errors.cjs.map +1 -1
  61. package/dist/chat/errors.d.cts +6 -31
  62. package/dist/chat/errors.d.ts +6 -31
  63. package/dist/chat/errors.js +48 -25
  64. package/dist/chat/errors.js.map +1 -1
  65. package/dist/chat/events.cjs.map +1 -1
  66. package/dist/chat/events.d.cts +6 -2
  67. package/dist/chat/events.d.ts +6 -2
  68. package/dist/chat/events.js.map +1 -1
  69. package/dist/chat/index.cjs +1612 -1125
  70. package/dist/chat/index.cjs.map +1 -1
  71. package/dist/chat/index.d.cts +35 -10
  72. package/dist/chat/index.d.ts +35 -10
  73. package/dist/chat/index.js +1600 -1097
  74. package/dist/chat/index.js.map +1 -1
  75. package/dist/chat/react/theme.css +2517 -0
  76. package/dist/chat/react.cjs +2212 -1158
  77. package/dist/chat/react.cjs.map +1 -1
  78. package/dist/chat/react.d.cts +665 -122
  79. package/dist/chat/react.d.ts +665 -122
  80. package/dist/chat/react.js +2191 -1156
  81. package/dist/chat/react.js.map +1 -1
  82. package/dist/chat/runtime.cjs +405 -186
  83. package/dist/chat/runtime.cjs.map +1 -1
  84. package/dist/chat/runtime.d.cts +92 -28
  85. package/dist/chat/runtime.d.ts +92 -28
  86. package/dist/chat/runtime.js +405 -186
  87. package/dist/chat/runtime.js.map +1 -1
  88. package/dist/chat/server.cjs +2247 -212
  89. package/dist/chat/server.cjs.map +1 -1
  90. package/dist/chat/server.d.cts +451 -90
  91. package/dist/chat/server.d.ts +451 -90
  92. package/dist/chat/server.js +2234 -213
  93. package/dist/chat/server.js.map +1 -1
  94. package/dist/chat/sessions.cjs +64 -66
  95. package/dist/chat/sessions.cjs.map +1 -1
  96. package/dist/chat/sessions.d.cts +37 -118
  97. package/dist/chat/sessions.d.ts +37 -118
  98. package/dist/chat/sessions.js +65 -67
  99. package/dist/chat/sessions.js.map +1 -1
  100. package/dist/chat/sqlite.cjs +536 -0
  101. package/dist/chat/sqlite.cjs.map +1 -0
  102. package/dist/chat/sqlite.d.cts +164 -0
  103. package/dist/chat/sqlite.d.ts +164 -0
  104. package/dist/chat/sqlite.js +527 -0
  105. package/dist/chat/sqlite.js.map +1 -0
  106. package/dist/chat/state.cjs +14 -1
  107. package/dist/chat/state.cjs.map +1 -1
  108. package/dist/chat/state.d.cts +5 -2
  109. package/dist/chat/state.d.ts +5 -2
  110. package/dist/chat/state.js +14 -1
  111. package/dist/chat/state.js.map +1 -1
  112. package/dist/chat/storage.cjs +58 -33
  113. package/dist/chat/storage.cjs.map +1 -1
  114. package/dist/chat/storage.d.cts +18 -8
  115. package/dist/chat/storage.d.ts +18 -8
  116. package/dist/chat/storage.js +59 -34
  117. package/dist/chat/storage.js.map +1 -1
  118. package/dist/errors-C-so0M4t.d.cts +33 -0
  119. package/dist/errors-C-so0M4t.d.ts +33 -0
  120. package/dist/errors-CmVvczxZ.d.cts +28 -0
  121. package/dist/errors-CmVvczxZ.d.ts +28 -0
  122. package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-7EIit9Xk.d.ts} +72 -33
  123. package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-Ct9YcX8I.d.cts} +72 -33
  124. package/dist/index.cjs +354 -60
  125. package/dist/index.cjs.map +1 -1
  126. package/dist/index.d.cts +294 -123
  127. package/dist/index.d.ts +294 -123
  128. package/dist/index.js +347 -60
  129. package/dist/index.js.map +1 -1
  130. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  131. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  132. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  133. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  134. package/dist/testing.cjs +1107 -0
  135. package/dist/testing.cjs.map +1 -0
  136. package/dist/testing.d.cts +144 -0
  137. package/dist/testing.d.ts +144 -0
  138. package/dist/testing.js +1101 -0
  139. package/dist/testing.js.map +1 -0
  140. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  141. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  142. package/dist/{transport-DX1Nhm4N.d.cts → transport-DLWCN18G.d.cts} +5 -4
  143. package/dist/{transport-D1OaUgRk.d.ts → transport-DsuS-GeM.d.ts} +5 -4
  144. package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
  145. package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
  146. package/dist/types-DgtI1hzh.d.ts +364 -0
  147. package/dist/types-DkSXALKg.d.cts +364 -0
  148. package/package.json +41 -5
  149. package/LICENSE +0 -21
  150. package/README.md +0 -948
  151. package/dist/errors-BDLbNu9w.d.cts +0 -13
  152. package/dist/errors-BDLbNu9w.d.ts +0 -13
  153. package/dist/types-DLZzlJxt.d.ts +0 -39
  154. 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