@witqq/agent-sdk 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -3
- package/dist/backends/claude.cjs +163 -8
- package/dist/backends/claude.cjs.map +1 -1
- package/dist/backends/claude.d.cts +1 -1
- package/dist/backends/claude.d.ts +1 -1
- package/dist/backends/claude.js +163 -8
- package/dist/backends/claude.js.map +1 -1
- package/dist/backends/copilot.cjs +119 -5
- package/dist/backends/copilot.cjs.map +1 -1
- package/dist/backends/copilot.d.cts +1 -1
- package/dist/backends/copilot.d.ts +1 -1
- package/dist/backends/copilot.js +119 -5
- package/dist/backends/copilot.js.map +1 -1
- package/dist/backends/vercel-ai.cjs +120 -5
- package/dist/backends/vercel-ai.cjs.map +1 -1
- package/dist/backends/vercel-ai.d.cts +1 -1
- package/dist/backends/vercel-ai.d.ts +1 -1
- package/dist/backends/vercel-ai.js +120 -5
- package/dist/backends/vercel-ai.js.map +1 -1
- package/dist/index.cjs +116 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -6
- package/dist/index.d.ts +47 -6
- package/dist/index.js +116 -4
- package/dist/index.js.map +1 -1
- package/dist/{types-DxstXhLL.d.cts → types-CBzhRrN9.d.cts} +33 -4
- package/dist/{types-DxstXhLL.d.ts → types-CBzhRrN9.d.ts} +33 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -196,21 +196,73 @@ for await (const event of agent.stream("Tell me a story")) {
|
|
|
196
196
|
}
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
### Streaming with Conversation History
|
|
200
|
+
|
|
201
|
+
Use `streamWithContext` to stream with full conversation history:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const messages = [
|
|
205
|
+
{ role: "system" as const, content: "You are helpful." },
|
|
206
|
+
{ role: "user" as const, content: "Hello" },
|
|
207
|
+
{ role: "assistant" as const, content: "Hi! How can I help?" },
|
|
208
|
+
{ role: "user" as const, content: "What is 2+2?" },
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
for await (const event of agent.streamWithContext(messages)) {
|
|
212
|
+
if (event.type === "text_delta") process.stdout.write(event.text);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
199
216
|
| Event | Fields | Description |
|
|
200
217
|
|-------|--------|-------------|
|
|
201
218
|
| `text_delta` | `text` | Incremental text output |
|
|
219
|
+
| `thinking_delta` | `text` | Incremental reasoning/thinking text |
|
|
202
220
|
| `thinking_start` | — | Model started reasoning |
|
|
203
221
|
| `thinking_end` | — | Model finished reasoning |
|
|
204
|
-
| `tool_call_start` | `toolName`, `args` | Tool invocation began |
|
|
205
|
-
| `tool_call_end` | `toolName`, `result` | Tool invocation completed |
|
|
222
|
+
| `tool_call_start` | `toolCallId`, `toolName`, `args` | Tool invocation began |
|
|
223
|
+
| `tool_call_end` | `toolCallId`, `toolName`, `result` | Tool invocation completed |
|
|
206
224
|
| `permission_request` | `request` | Permission check initiated |
|
|
207
225
|
| `permission_response` | `toolName`, `decision` | Permission decision made |
|
|
208
226
|
| `ask_user` | `request` | User input requested |
|
|
209
227
|
| `ask_user_response` | `answer` | User response received |
|
|
210
|
-
| `usage_update` | `promptTokens`, `completionTokens` | Token usage |
|
|
228
|
+
| `usage_update` | `promptTokens`, `completionTokens`, `model?`, `backend?` | Token usage with metadata |
|
|
229
|
+
| `heartbeat` | — | Keepalive signal during long operations |
|
|
211
230
|
| `error` | `error`, `recoverable` | Error during execution |
|
|
212
231
|
| `done` | `finalOutput`, `structuredOutput?` | Execution completed |
|
|
213
232
|
|
|
233
|
+
## Usage Tracking
|
|
234
|
+
|
|
235
|
+
Track token usage with the `onUsage` callback. Called after each `run()`/`runWithContext()`/`runStructured()` completion and during `stream()`/`streamWithContext()` when usage data arrives:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const agent = service.createAgent({
|
|
239
|
+
systemPrompt: "You are a helpful assistant.",
|
|
240
|
+
onUsage: (usage) => {
|
|
241
|
+
console.log(`${usage.backend}/${usage.model}: ${usage.promptTokens}+${usage.completionTokens} tokens`);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Usage data includes `promptTokens`, `completionTokens`, and optional `model` and `backend` fields. Callback errors are logged but not propagated (fire-and-forget).
|
|
247
|
+
|
|
248
|
+
## Heartbeat
|
|
249
|
+
|
|
250
|
+
Keep HTTP streams alive during long tool executions by emitting periodic heartbeat events:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const agent = service.createAgent({
|
|
254
|
+
systemPrompt: "You are a helpful assistant.",
|
|
255
|
+
heartbeatInterval: 15000, // emit heartbeat every 15s during gaps
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
for await (const event of agent.stream("Run a long analysis")) {
|
|
259
|
+
if (event.type === "heartbeat") continue; // ignore keepalive
|
|
260
|
+
// handle other events...
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
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.
|
|
265
|
+
|
|
214
266
|
## Backend-Specific Options
|
|
215
267
|
|
|
216
268
|
### Copilot
|
package/dist/backends/claude.cjs
CHANGED
|
@@ -54,7 +54,9 @@ var BaseAgent = class {
|
|
|
54
54
|
this.state = "running";
|
|
55
55
|
try {
|
|
56
56
|
const messages = [{ role: "user", content: prompt }];
|
|
57
|
-
|
|
57
|
+
const result = await this.executeRun(messages, options, ac.signal);
|
|
58
|
+
this.enrichAndNotifyUsage(result);
|
|
59
|
+
return result;
|
|
58
60
|
} finally {
|
|
59
61
|
this.state = "idle";
|
|
60
62
|
this.abortController = null;
|
|
@@ -66,7 +68,9 @@ var BaseAgent = class {
|
|
|
66
68
|
const ac = this.createAbortController(options?.signal);
|
|
67
69
|
this.state = "running";
|
|
68
70
|
try {
|
|
69
|
-
|
|
71
|
+
const result = await this.executeRun(messages, options, ac.signal);
|
|
72
|
+
this.enrichAndNotifyUsage(result);
|
|
73
|
+
return result;
|
|
70
74
|
} finally {
|
|
71
75
|
this.state = "idle";
|
|
72
76
|
this.abortController = null;
|
|
@@ -79,12 +83,14 @@ var BaseAgent = class {
|
|
|
79
83
|
this.state = "running";
|
|
80
84
|
try {
|
|
81
85
|
const messages = [{ role: "user", content: prompt }];
|
|
82
|
-
|
|
86
|
+
const result = await this.executeRunStructured(
|
|
83
87
|
messages,
|
|
84
88
|
schema,
|
|
85
89
|
options,
|
|
86
90
|
ac.signal
|
|
87
91
|
);
|
|
92
|
+
this.enrichAndNotifyUsage(result);
|
|
93
|
+
return result;
|
|
88
94
|
} finally {
|
|
89
95
|
this.state = "idle";
|
|
90
96
|
this.abortController = null;
|
|
@@ -97,7 +103,21 @@ var BaseAgent = class {
|
|
|
97
103
|
this.state = "streaming";
|
|
98
104
|
try {
|
|
99
105
|
const messages = [{ role: "user", content: prompt }];
|
|
100
|
-
|
|
106
|
+
const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
|
|
107
|
+
yield* this.heartbeatStream(enriched);
|
|
108
|
+
} finally {
|
|
109
|
+
this.state = "idle";
|
|
110
|
+
this.abortController = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async *streamWithContext(messages, options) {
|
|
114
|
+
this.guardReentrancy();
|
|
115
|
+
this.guardDisposed();
|
|
116
|
+
const ac = this.createAbortController(options?.signal);
|
|
117
|
+
this.state = "streaming";
|
|
118
|
+
try {
|
|
119
|
+
const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
|
|
120
|
+
yield* this.heartbeatStream(enriched);
|
|
101
121
|
} finally {
|
|
102
122
|
this.state = "idle";
|
|
103
123
|
this.abortController = null;
|
|
@@ -119,6 +139,95 @@ var BaseAgent = class {
|
|
|
119
139
|
this.abort();
|
|
120
140
|
this.state = "disposed";
|
|
121
141
|
}
|
|
142
|
+
// ─── Usage Enrichment ───────────────────────────────────────────
|
|
143
|
+
/** Enrich result usage with model/backend and fire onUsage callback */
|
|
144
|
+
enrichAndNotifyUsage(result) {
|
|
145
|
+
if (result.usage) {
|
|
146
|
+
result.usage = {
|
|
147
|
+
...result.usage,
|
|
148
|
+
model: this.config.model,
|
|
149
|
+
backend: this.backendName
|
|
150
|
+
};
|
|
151
|
+
this.callOnUsage(result.usage);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Wrap a stream to enrich usage_update events and fire onUsage callback */
|
|
155
|
+
async *enrichStream(source) {
|
|
156
|
+
for await (const event of source) {
|
|
157
|
+
if (event.type === "usage_update") {
|
|
158
|
+
const usage = {
|
|
159
|
+
promptTokens: event.promptTokens,
|
|
160
|
+
completionTokens: event.completionTokens,
|
|
161
|
+
model: this.config.model,
|
|
162
|
+
backend: this.backendName
|
|
163
|
+
};
|
|
164
|
+
this.callOnUsage(usage);
|
|
165
|
+
yield { type: "usage_update", ...usage };
|
|
166
|
+
} else {
|
|
167
|
+
yield event;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Fire onUsage callback (fire-and-forget: errors logged, not propagated) */
|
|
172
|
+
callOnUsage(usage) {
|
|
173
|
+
if (!this.config.onUsage) return;
|
|
174
|
+
try {
|
|
175
|
+
this.config.onUsage(usage);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.warn(
|
|
178
|
+
"[agent-sdk] onUsage callback error:",
|
|
179
|
+
e instanceof Error ? e.message : String(e)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// ─── Heartbeat ───────────────────────────────────────────────
|
|
184
|
+
/** Wrap a stream to emit heartbeat events at configured intervals.
|
|
185
|
+
* When heartbeatInterval is not set, passes through directly. */
|
|
186
|
+
async *heartbeatStream(source) {
|
|
187
|
+
const interval = this.config.heartbeatInterval;
|
|
188
|
+
if (!interval || interval <= 0) {
|
|
189
|
+
yield* source;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
193
|
+
let pendingEvent = null;
|
|
194
|
+
let heartbeatResolve = null;
|
|
195
|
+
const timer = setInterval(() => {
|
|
196
|
+
if (heartbeatResolve) {
|
|
197
|
+
const resolve = heartbeatResolve;
|
|
198
|
+
heartbeatResolve = null;
|
|
199
|
+
resolve();
|
|
200
|
+
}
|
|
201
|
+
}, interval);
|
|
202
|
+
try {
|
|
203
|
+
while (true) {
|
|
204
|
+
if (!pendingEvent) {
|
|
205
|
+
pendingEvent = iterator.next();
|
|
206
|
+
}
|
|
207
|
+
const heartbeatPromise = new Promise((resolve) => {
|
|
208
|
+
heartbeatResolve = resolve;
|
|
209
|
+
});
|
|
210
|
+
const eventDone = pendingEvent.then(
|
|
211
|
+
(r) => ({ kind: "event", result: r })
|
|
212
|
+
);
|
|
213
|
+
const heartbeatDone = heartbeatPromise.then(
|
|
214
|
+
() => ({ kind: "heartbeat" })
|
|
215
|
+
);
|
|
216
|
+
const winner = await Promise.race([eventDone, heartbeatDone]);
|
|
217
|
+
if (winner.kind === "heartbeat") {
|
|
218
|
+
yield { type: "heartbeat" };
|
|
219
|
+
} else {
|
|
220
|
+
pendingEvent = null;
|
|
221
|
+
heartbeatResolve = null;
|
|
222
|
+
if (winner.result.done) break;
|
|
223
|
+
yield winner.result.value;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
clearInterval(timer);
|
|
228
|
+
heartbeatResolve = null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
122
231
|
// ─── Guards ───────────────────────────────────────────────────
|
|
123
232
|
guardReentrancy() {
|
|
124
233
|
if (this.state === "running" || this.state === "streaming") {
|
|
@@ -156,6 +265,9 @@ var BaseAgent = class {
|
|
|
156
265
|
// src/utils/schema.ts
|
|
157
266
|
function zodToJsonSchema(schema) {
|
|
158
267
|
const schemaAny = schema;
|
|
268
|
+
if ("toJSONSchema" in schema && typeof schemaAny.toJSONSchema === "function") {
|
|
269
|
+
return schemaAny.toJSONSchema();
|
|
270
|
+
}
|
|
159
271
|
if ("jsonSchema" in schema && typeof schemaAny.jsonSchema === "function") {
|
|
160
272
|
return schemaAny.jsonSchema();
|
|
161
273
|
}
|
|
@@ -344,7 +456,31 @@ function aggregateUsage(modelUsage) {
|
|
|
344
456
|
}
|
|
345
457
|
return { promptTokens, completionTokens };
|
|
346
458
|
}
|
|
347
|
-
|
|
459
|
+
var ClaudeToolCallTracker = class {
|
|
460
|
+
queues = /* @__PURE__ */ new Map();
|
|
461
|
+
trackStart(toolCallId, toolName) {
|
|
462
|
+
if (!this.queues.has(toolName)) {
|
|
463
|
+
this.queues.set(toolName, []);
|
|
464
|
+
}
|
|
465
|
+
this.queues.get(toolName).push(toolCallId);
|
|
466
|
+
}
|
|
467
|
+
/** Peek at the current tool call ID for a tool name (does not consume) */
|
|
468
|
+
peekToolCallId(toolName) {
|
|
469
|
+
const queue = this.queues.get(toolName);
|
|
470
|
+
if (!queue || queue.length === 0) return "";
|
|
471
|
+
return queue[0];
|
|
472
|
+
}
|
|
473
|
+
/** Consume and return the first tool call ID for a tool name */
|
|
474
|
+
consumeToolCallId(toolName) {
|
|
475
|
+
const queue = this.queues.get(toolName);
|
|
476
|
+
if (!queue || queue.length === 0) return "";
|
|
477
|
+
return queue.shift();
|
|
478
|
+
}
|
|
479
|
+
clear() {
|
|
480
|
+
this.queues.clear();
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
|
|
348
484
|
switch (msg.type) {
|
|
349
485
|
case "assistant": {
|
|
350
486
|
const betaMessage = msg.message;
|
|
@@ -356,9 +492,15 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
|
|
|
356
492
|
}
|
|
357
493
|
for (const block of betaMessage.content) {
|
|
358
494
|
if (block.type === "tool_use") {
|
|
495
|
+
const toolCallId = String(block.id ?? "");
|
|
496
|
+
const toolName = block.name ?? "unknown";
|
|
497
|
+
if (toolCallTracker) {
|
|
498
|
+
toolCallTracker.trackStart(toolCallId, toolName);
|
|
499
|
+
}
|
|
359
500
|
events.push({
|
|
360
501
|
type: "tool_call_start",
|
|
361
|
-
|
|
502
|
+
toolCallId,
|
|
503
|
+
toolName,
|
|
362
504
|
args: block.input ?? {}
|
|
363
505
|
});
|
|
364
506
|
}
|
|
@@ -375,9 +517,18 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
|
|
|
375
517
|
case "tool_use_summary": {
|
|
376
518
|
const summary = msg.summary;
|
|
377
519
|
const toolName = msg.tool_name ?? "unknown";
|
|
520
|
+
const precedingIds = msg.preceding_tool_use_ids;
|
|
521
|
+
let toolCallId = "";
|
|
522
|
+
if (precedingIds && precedingIds.length > 0) {
|
|
523
|
+
toolCallId = precedingIds[0];
|
|
524
|
+
if (toolCallTracker) toolCallTracker.consumeToolCallId(toolName);
|
|
525
|
+
} else if (toolCallTracker) {
|
|
526
|
+
toolCallId = toolCallTracker.consumeToolCallId(toolName);
|
|
527
|
+
}
|
|
378
528
|
if (summary) {
|
|
379
529
|
return {
|
|
380
530
|
type: "tool_call_end",
|
|
531
|
+
toolCallId,
|
|
381
532
|
toolName,
|
|
382
533
|
result: summary
|
|
383
534
|
};
|
|
@@ -404,7 +555,9 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
|
|
|
404
555
|
}
|
|
405
556
|
case "tool_progress": {
|
|
406
557
|
const toolName = msg.tool_name;
|
|
407
|
-
|
|
558
|
+
if (!toolName) return null;
|
|
559
|
+
const toolCallId = toolCallTracker?.peekToolCallId(toolName) ?? "";
|
|
560
|
+
return { type: "tool_call_start", toolCallId, toolName, args: {} };
|
|
408
561
|
}
|
|
409
562
|
case "result": {
|
|
410
563
|
if (msg.subtype === "success") {
|
|
@@ -429,6 +582,7 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
|
|
|
429
582
|
}
|
|
430
583
|
}
|
|
431
584
|
var ClaudeAgent = class extends BaseAgent {
|
|
585
|
+
backendName = "claude";
|
|
432
586
|
options;
|
|
433
587
|
tools;
|
|
434
588
|
canUseTool;
|
|
@@ -609,10 +763,11 @@ var ClaudeAgent = class extends BaseAgent {
|
|
|
609
763
|
opts = await this.buildMcpConfig(opts);
|
|
610
764
|
const q = sdk.query({ prompt, options: opts });
|
|
611
765
|
const thinkingBlockIndices = /* @__PURE__ */ new Set();
|
|
766
|
+
const toolCallTracker = new ClaudeToolCallTracker();
|
|
612
767
|
try {
|
|
613
768
|
for await (const msg of q) {
|
|
614
769
|
if (signal.aborted) throw new AbortError();
|
|
615
|
-
const event = mapSDKMessage(msg, thinkingBlockIndices);
|
|
770
|
+
const event = mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker);
|
|
616
771
|
if (event) {
|
|
617
772
|
if (Array.isArray(event)) {
|
|
618
773
|
for (const e of event) yield e;
|