@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.
@@ -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") {
@@ -154,6 +263,9 @@ var BaseAgent = class {
154
263
  // src/utils/schema.ts
155
264
  function zodToJsonSchema(schema) {
156
265
  const schemaAny = schema;
266
+ if ("toJSONSchema" in schema && typeof schemaAny.toJSONSchema === "function") {
267
+ return schemaAny.toJSONSchema();
268
+ }
157
269
  if ("jsonSchema" in schema && typeof schemaAny.jsonSchema === "function") {
158
270
  return schemaAny.jsonSchema();
159
271
  }
@@ -342,7 +454,31 @@ function aggregateUsage(modelUsage) {
342
454
  }
343
455
  return { promptTokens, completionTokens };
344
456
  }
345
- function mapSDKMessage(msg, thinkingBlockIndices) {
457
+ var ClaudeToolCallTracker = class {
458
+ queues = /* @__PURE__ */ new Map();
459
+ trackStart(toolCallId, toolName) {
460
+ if (!this.queues.has(toolName)) {
461
+ this.queues.set(toolName, []);
462
+ }
463
+ this.queues.get(toolName).push(toolCallId);
464
+ }
465
+ /** Peek at the current tool call ID for a tool name (does not consume) */
466
+ peekToolCallId(toolName) {
467
+ const queue = this.queues.get(toolName);
468
+ if (!queue || queue.length === 0) return "";
469
+ return queue[0];
470
+ }
471
+ /** Consume and return the first tool call ID for a tool name */
472
+ consumeToolCallId(toolName) {
473
+ const queue = this.queues.get(toolName);
474
+ if (!queue || queue.length === 0) return "";
475
+ return queue.shift();
476
+ }
477
+ clear() {
478
+ this.queues.clear();
479
+ }
480
+ };
481
+ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
346
482
  switch (msg.type) {
347
483
  case "assistant": {
348
484
  const betaMessage = msg.message;
@@ -354,9 +490,15 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
354
490
  }
355
491
  for (const block of betaMessage.content) {
356
492
  if (block.type === "tool_use") {
493
+ const toolCallId = String(block.id ?? "");
494
+ const toolName = block.name ?? "unknown";
495
+ if (toolCallTracker) {
496
+ toolCallTracker.trackStart(toolCallId, toolName);
497
+ }
357
498
  events.push({
358
499
  type: "tool_call_start",
359
- toolName: block.name ?? "unknown",
500
+ toolCallId,
501
+ toolName,
360
502
  args: block.input ?? {}
361
503
  });
362
504
  }
@@ -373,9 +515,18 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
373
515
  case "tool_use_summary": {
374
516
  const summary = msg.summary;
375
517
  const toolName = msg.tool_name ?? "unknown";
518
+ const precedingIds = msg.preceding_tool_use_ids;
519
+ let toolCallId = "";
520
+ if (precedingIds && precedingIds.length > 0) {
521
+ toolCallId = precedingIds[0];
522
+ if (toolCallTracker) toolCallTracker.consumeToolCallId(toolName);
523
+ } else if (toolCallTracker) {
524
+ toolCallId = toolCallTracker.consumeToolCallId(toolName);
525
+ }
376
526
  if (summary) {
377
527
  return {
378
528
  type: "tool_call_end",
529
+ toolCallId,
379
530
  toolName,
380
531
  result: summary
381
532
  };
@@ -402,7 +553,9 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
402
553
  }
403
554
  case "tool_progress": {
404
555
  const toolName = msg.tool_name;
405
- return toolName ? { type: "tool_call_start", toolName, args: {} } : null;
556
+ if (!toolName) return null;
557
+ const toolCallId = toolCallTracker?.peekToolCallId(toolName) ?? "";
558
+ return { type: "tool_call_start", toolCallId, toolName, args: {} };
406
559
  }
407
560
  case "result": {
408
561
  if (msg.subtype === "success") {
@@ -427,6 +580,7 @@ function mapSDKMessage(msg, thinkingBlockIndices) {
427
580
  }
428
581
  }
429
582
  var ClaudeAgent = class extends BaseAgent {
583
+ backendName = "claude";
430
584
  options;
431
585
  tools;
432
586
  canUseTool;
@@ -607,10 +761,11 @@ var ClaudeAgent = class extends BaseAgent {
607
761
  opts = await this.buildMcpConfig(opts);
608
762
  const q = sdk.query({ prompt, options: opts });
609
763
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
764
+ const toolCallTracker = new ClaudeToolCallTracker();
610
765
  try {
611
766
  for await (const msg of q) {
612
767
  if (signal.aborted) throw new AbortError();
613
- const event = mapSDKMessage(msg, thinkingBlockIndices);
768
+ const event = mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker);
614
769
  if (event) {
615
770
  if (Array.isArray(event)) {
616
771
  for (const e of event) yield e;