@witqq/agent-sdk 0.1.0 → 0.2.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.
@@ -52,7 +52,9 @@ var BaseAgent = class {
52
52
  this.state = "running";
53
53
  try {
54
54
  const messages = [{ role: "user", content: prompt }];
55
- return await this.executeRun(messages, options, ac.signal);
55
+ const result = await this.executeRun(messages, options, ac.signal);
56
+ this.enrichAndNotifyUsage(result);
57
+ return result;
56
58
  } finally {
57
59
  this.state = "idle";
58
60
  this.abortController = null;
@@ -64,7 +66,9 @@ var BaseAgent = class {
64
66
  const ac = this.createAbortController(options?.signal);
65
67
  this.state = "running";
66
68
  try {
67
- return await this.executeRun(messages, options, ac.signal);
69
+ const result = await this.executeRun(messages, options, ac.signal);
70
+ this.enrichAndNotifyUsage(result);
71
+ return result;
68
72
  } finally {
69
73
  this.state = "idle";
70
74
  this.abortController = null;
@@ -77,12 +81,14 @@ var BaseAgent = class {
77
81
  this.state = "running";
78
82
  try {
79
83
  const messages = [{ role: "user", content: prompt }];
80
- return await this.executeRunStructured(
84
+ const result = await this.executeRunStructured(
81
85
  messages,
82
86
  schema,
83
87
  options,
84
88
  ac.signal
85
89
  );
90
+ this.enrichAndNotifyUsage(result);
91
+ return result;
86
92
  } finally {
87
93
  this.state = "idle";
88
94
  this.abortController = null;
@@ -95,7 +101,21 @@ var BaseAgent = class {
95
101
  this.state = "streaming";
96
102
  try {
97
103
  const messages = [{ role: "user", content: prompt }];
98
- yield* this.executeStream(messages, options, ac.signal);
104
+ const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
105
+ yield* this.heartbeatStream(enriched);
106
+ } finally {
107
+ this.state = "idle";
108
+ this.abortController = null;
109
+ }
110
+ }
111
+ async *streamWithContext(messages, options) {
112
+ this.guardReentrancy();
113
+ this.guardDisposed();
114
+ const ac = this.createAbortController(options?.signal);
115
+ this.state = "streaming";
116
+ try {
117
+ const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
118
+ yield* this.heartbeatStream(enriched);
99
119
  } finally {
100
120
  this.state = "idle";
101
121
  this.abortController = null;
@@ -117,6 +137,95 @@ var BaseAgent = class {
117
137
  this.abort();
118
138
  this.state = "disposed";
119
139
  }
140
+ // ─── Usage Enrichment ───────────────────────────────────────────
141
+ /** Enrich result usage with model/backend and fire onUsage callback */
142
+ enrichAndNotifyUsage(result) {
143
+ if (result.usage) {
144
+ result.usage = {
145
+ ...result.usage,
146
+ model: this.config.model,
147
+ backend: this.backendName
148
+ };
149
+ this.callOnUsage(result.usage);
150
+ }
151
+ }
152
+ /** Wrap a stream to enrich usage_update events and fire onUsage callback */
153
+ async *enrichStream(source) {
154
+ for await (const event of source) {
155
+ if (event.type === "usage_update") {
156
+ const usage = {
157
+ promptTokens: event.promptTokens,
158
+ completionTokens: event.completionTokens,
159
+ model: this.config.model,
160
+ backend: this.backendName
161
+ };
162
+ this.callOnUsage(usage);
163
+ yield { type: "usage_update", ...usage };
164
+ } else {
165
+ yield event;
166
+ }
167
+ }
168
+ }
169
+ /** Fire onUsage callback (fire-and-forget: errors logged, not propagated) */
170
+ callOnUsage(usage) {
171
+ if (!this.config.onUsage) return;
172
+ try {
173
+ this.config.onUsage(usage);
174
+ } catch (e) {
175
+ console.warn(
176
+ "[agent-sdk] onUsage callback error:",
177
+ e instanceof Error ? e.message : String(e)
178
+ );
179
+ }
180
+ }
181
+ // ─── Heartbeat ───────────────────────────────────────────────
182
+ /** Wrap a stream to emit heartbeat events at configured intervals.
183
+ * When heartbeatInterval is not set, passes through directly. */
184
+ async *heartbeatStream(source) {
185
+ const interval = this.config.heartbeatInterval;
186
+ if (!interval || interval <= 0) {
187
+ yield* source;
188
+ return;
189
+ }
190
+ const iterator = source[Symbol.asyncIterator]();
191
+ let pendingEvent = null;
192
+ let heartbeatResolve = null;
193
+ const timer = setInterval(() => {
194
+ if (heartbeatResolve) {
195
+ const resolve = heartbeatResolve;
196
+ heartbeatResolve = null;
197
+ resolve();
198
+ }
199
+ }, interval);
200
+ try {
201
+ while (true) {
202
+ if (!pendingEvent) {
203
+ pendingEvent = iterator.next();
204
+ }
205
+ const heartbeatPromise = new Promise((resolve) => {
206
+ heartbeatResolve = resolve;
207
+ });
208
+ const eventDone = pendingEvent.then(
209
+ (r) => ({ kind: "event", result: r })
210
+ );
211
+ const heartbeatDone = heartbeatPromise.then(
212
+ () => ({ kind: "heartbeat" })
213
+ );
214
+ const winner = await Promise.race([eventDone, heartbeatDone]);
215
+ if (winner.kind === "heartbeat") {
216
+ yield { type: "heartbeat" };
217
+ } else {
218
+ pendingEvent = null;
219
+ heartbeatResolve = null;
220
+ if (winner.result.done) break;
221
+ yield winner.result.value;
222
+ }
223
+ }
224
+ } finally {
225
+ clearInterval(timer);
226
+ heartbeatResolve = null;
227
+ }
228
+ }
120
229
  // ─── Guards ───────────────────────────────────────────────────
121
230
  guardReentrancy() {
122
231
  if (this.state === "running" || this.state === "streaming") {
@@ -342,7 +451,31 @@ function aggregateUsage(modelUsage) {
342
451
  }
343
452
  return { promptTokens, completionTokens };
344
453
  }
345
- function mapSDKMessage(msg, thinkingBlockIndices) {
454
+ var ClaudeToolCallTracker = class {
455
+ queues = /* @__PURE__ */ new Map();
456
+ trackStart(toolCallId, toolName) {
457
+ if (!this.queues.has(toolName)) {
458
+ this.queues.set(toolName, []);
459
+ }
460
+ this.queues.get(toolName).push(toolCallId);
461
+ }
462
+ /** Peek at the current tool call ID for a tool name (does not consume) */
463
+ peekToolCallId(toolName) {
464
+ const queue = this.queues.get(toolName);
465
+ if (!queue || queue.length === 0) return "";
466
+ return queue[0];
467
+ }
468
+ /** Consume and return the first tool call ID for a tool name */
469
+ consumeToolCallId(toolName) {
470
+ const queue = this.queues.get(toolName);
471
+ if (!queue || queue.length === 0) return "";
472
+ return queue.shift();
473
+ }
474
+ clear() {
475
+ this.queues.clear();
476
+ }
477
+ };
478
+ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
346
479
  switch (msg.type) {
347
480
  case "assistant": {
348
481
  const betaMessage = msg.message;
@@ -354,9 +487,15 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
354
487
  }
355
488
  for (const block of betaMessage.content) {
356
489
  if (block.type === "tool_use") {
490
+ const toolCallId = String(block.id ?? "");
491
+ const toolName = block.name ?? "unknown";
492
+ if (toolCallTracker) {
493
+ toolCallTracker.trackStart(toolCallId, toolName);
494
+ }
357
495
  events.push({
358
496
  type: "tool_call_start",
359
- toolName: block.name ?? "unknown",
497
+ toolCallId,
498
+ toolName,
360
499
  args: block.input ?? {}
361
500
  });
362
501
  }
@@ -373,9 +512,18 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
373
512
  case "tool_use_summary": {
374
513
  const summary = msg.summary;
375
514
  const toolName = msg.tool_name ?? "unknown";
515
+ const precedingIds = msg.preceding_tool_use_ids;
516
+ let toolCallId = "";
517
+ if (precedingIds && precedingIds.length > 0) {
518
+ toolCallId = precedingIds[0];
519
+ if (toolCallTracker) toolCallTracker.consumeToolCallId(toolName);
520
+ } else if (toolCallTracker) {
521
+ toolCallId = toolCallTracker.consumeToolCallId(toolName);
522
+ }
376
523
  if (summary) {
377
524
  return {
378
525
  type: "tool_call_end",
526
+ toolCallId,
379
527
  toolName,
380
528
  result: summary
381
529
  };
@@ -402,7 +550,9 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
402
550
  }
403
551
  case "tool_progress": {
404
552
  const toolName = msg.tool_name;
405
- return toolName ? { type: "tool_call_start", toolName, args: {} } : null;
553
+ if (!toolName) return null;
554
+ const toolCallId = toolCallTracker?.peekToolCallId(toolName) ?? "";
555
+ return { type: "tool_call_start", toolCallId, toolName, args: {} };
406
556
  }
407
557
  case "result": {
408
558
  if (msg.subtype === "success") {
@@ -427,6 +577,7 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
427
577
  }
428
578
  }
429
579
  var ClaudeAgent = class extends BaseAgent {
580
+ backendName = "claude";
430
581
  options;
431
582
  tools;
432
583
  canUseTool;
@@ -607,10 +758,11 @@ var ClaudeAgent = class extends BaseAgent {
607
758
  opts = await this.buildMcpConfig(opts);
608
759
  const q = sdk.query({ prompt, options: opts });
609
760
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
761
+ const toolCallTracker = new ClaudeToolCallTracker();
610
762
  try {
611
763
  for await (const msg of q) {
612
764
  if (signal.aborted) throw new AbortError();
613
- const event = mapSDKMessage(msg, thinkingBlockIndices);
765
+ const event = mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker);
614
766
  if (event) {
615
767
  if (Array.isArray(event)) {
616
768
  for (const e of event) yield e;