@witqq/agent-sdk 0.6.1 → 0.8.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 (145) hide show
  1. package/README.md +539 -6
  2. package/dist/{types-BvwNzZCj.d.cts → agent-CW9XbmG_.d.ts} +148 -95
  3. package/dist/{types-BvwNzZCj.d.ts → agent-DxY68NZL.d.cts} +148 -95
  4. package/dist/auth/index.cjs +260 -2
  5. package/dist/auth/index.cjs.map +1 -1
  6. package/dist/auth/index.d.cts +21 -138
  7. package/dist/auth/index.d.ts +21 -138
  8. package/dist/auth/index.js +260 -3
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/backends/claude.cjs +653 -140
  11. package/dist/backends/claude.cjs.map +1 -1
  12. package/dist/backends/claude.d.cts +4 -1
  13. package/dist/backends/claude.d.ts +4 -1
  14. package/dist/backends/claude.js +653 -140
  15. package/dist/backends/claude.js.map +1 -1
  16. package/dist/backends/copilot.cjs +428 -88
  17. package/dist/backends/copilot.cjs.map +1 -1
  18. package/dist/backends/copilot.d.cts +13 -4
  19. package/dist/backends/copilot.d.ts +13 -4
  20. package/dist/backends/copilot.js +428 -88
  21. package/dist/backends/copilot.js.map +1 -1
  22. package/dist/backends/vercel-ai.cjs +349 -77
  23. package/dist/backends/vercel-ai.cjs.map +1 -1
  24. package/dist/backends/vercel-ai.d.cts +3 -1
  25. package/dist/backends/vercel-ai.d.ts +3 -1
  26. package/dist/backends/vercel-ai.js +349 -77
  27. package/dist/backends/vercel-ai.js.map +1 -1
  28. package/dist/backends-BSrsBYFn.d.cts +39 -0
  29. package/dist/backends-BSrsBYFn.d.ts +39 -0
  30. package/dist/chat/accumulator.cjs +147 -0
  31. package/dist/chat/accumulator.cjs.map +1 -0
  32. package/dist/chat/accumulator.d.cts +64 -0
  33. package/dist/chat/accumulator.d.ts +64 -0
  34. package/dist/chat/accumulator.js +145 -0
  35. package/dist/chat/accumulator.js.map +1 -0
  36. package/dist/chat/backends.cjs +3524 -0
  37. package/dist/chat/backends.cjs.map +1 -0
  38. package/dist/chat/backends.d.cts +66 -0
  39. package/dist/chat/backends.d.ts +66 -0
  40. package/dist/chat/backends.js +3512 -0
  41. package/dist/chat/backends.js.map +1 -0
  42. package/dist/chat/context.cjs +280 -0
  43. package/dist/chat/context.cjs.map +1 -0
  44. package/dist/chat/context.d.cts +191 -0
  45. package/dist/chat/context.d.ts +191 -0
  46. package/dist/chat/context.js +277 -0
  47. package/dist/chat/context.js.map +1 -0
  48. package/dist/chat/core.cjs +305 -0
  49. package/dist/chat/core.cjs.map +1 -0
  50. package/dist/chat/core.d.cts +84 -0
  51. package/dist/chat/core.d.ts +84 -0
  52. package/dist/chat/core.js +282 -0
  53. package/dist/chat/core.js.map +1 -0
  54. package/dist/chat/errors.cjs +273 -0
  55. package/dist/chat/errors.cjs.map +1 -0
  56. package/dist/chat/errors.d.cts +97 -0
  57. package/dist/chat/errors.d.ts +97 -0
  58. package/dist/chat/errors.js +266 -0
  59. package/dist/chat/errors.js.map +1 -0
  60. package/dist/chat/events.cjs +203 -0
  61. package/dist/chat/events.cjs.map +1 -0
  62. package/dist/chat/events.d.cts +245 -0
  63. package/dist/chat/events.d.ts +245 -0
  64. package/dist/chat/events.js +196 -0
  65. package/dist/chat/events.js.map +1 -0
  66. package/dist/chat/index.cjs +5550 -0
  67. package/dist/chat/index.cjs.map +1 -0
  68. package/dist/chat/index.d.cts +77 -0
  69. package/dist/chat/index.d.ts +77 -0
  70. package/dist/chat/index.js +5505 -0
  71. package/dist/chat/index.js.map +1 -0
  72. package/dist/chat/react/theme.css +2517 -0
  73. package/dist/chat/react.cjs +3589 -0
  74. package/dist/chat/react.cjs.map +1 -0
  75. package/dist/chat/react.d.cts +1088 -0
  76. package/dist/chat/react.d.ts +1088 -0
  77. package/dist/chat/react.js +3547 -0
  78. package/dist/chat/react.js.map +1 -0
  79. package/dist/chat/runtime.cjs +1245 -0
  80. package/dist/chat/runtime.cjs.map +1 -0
  81. package/dist/chat/runtime.d.cts +182 -0
  82. package/dist/chat/runtime.d.ts +182 -0
  83. package/dist/chat/runtime.js +1243 -0
  84. package/dist/chat/runtime.js.map +1 -0
  85. package/dist/chat/server.cjs +2668 -0
  86. package/dist/chat/server.cjs.map +1 -0
  87. package/dist/chat/server.d.cts +648 -0
  88. package/dist/chat/server.d.ts +648 -0
  89. package/dist/chat/server.js +2628 -0
  90. package/dist/chat/server.js.map +1 -0
  91. package/dist/chat/sessions.cjs +380 -0
  92. package/dist/chat/sessions.cjs.map +1 -0
  93. package/dist/chat/sessions.d.cts +158 -0
  94. package/dist/chat/sessions.d.ts +158 -0
  95. package/dist/chat/sessions.js +376 -0
  96. package/dist/chat/sessions.js.map +1 -0
  97. package/dist/chat/sqlite.cjs +441 -0
  98. package/dist/chat/sqlite.cjs.map +1 -0
  99. package/dist/chat/sqlite.d.cts +128 -0
  100. package/dist/chat/sqlite.d.ts +128 -0
  101. package/dist/chat/sqlite.js +435 -0
  102. package/dist/chat/sqlite.js.map +1 -0
  103. package/dist/chat/state.cjs +190 -0
  104. package/dist/chat/state.cjs.map +1 -0
  105. package/dist/chat/state.d.cts +95 -0
  106. package/dist/chat/state.d.ts +95 -0
  107. package/dist/chat/state.js +180 -0
  108. package/dist/chat/state.js.map +1 -0
  109. package/dist/chat/storage.cjs +249 -0
  110. package/dist/chat/storage.cjs.map +1 -0
  111. package/dist/chat/storage.d.cts +197 -0
  112. package/dist/chat/storage.d.ts +197 -0
  113. package/dist/chat/storage.js +245 -0
  114. package/dist/chat/storage.js.map +1 -0
  115. package/dist/errors-C-so0M4t.d.cts +33 -0
  116. package/dist/errors-C-so0M4t.d.ts +33 -0
  117. package/dist/errors-CmVvczxZ.d.cts +28 -0
  118. package/dist/errors-CmVvczxZ.d.ts +28 -0
  119. package/dist/in-process-transport-C1JnJGVR.d.ts +228 -0
  120. package/dist/in-process-transport-C7DSqPyX.d.cts +228 -0
  121. package/dist/index.cjs +365 -59
  122. package/dist/index.cjs.map +1 -1
  123. package/dist/index.d.cts +322 -125
  124. package/dist/index.d.ts +322 -125
  125. package/dist/index.js +359 -60
  126. package/dist/index.js.map +1 -1
  127. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  128. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  129. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  130. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  131. package/dist/testing.cjs +383 -0
  132. package/dist/testing.cjs.map +1 -0
  133. package/dist/testing.d.cts +132 -0
  134. package/dist/testing.d.ts +132 -0
  135. package/dist/testing.js +377 -0
  136. package/dist/testing.js.map +1 -0
  137. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  138. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  139. package/dist/transport-Cdh3M0tS.d.cts +68 -0
  140. package/dist/transport-Ciap4PWK.d.ts +68 -0
  141. package/dist/types-4vbcmPTp.d.cts +143 -0
  142. package/dist/types-BxggH0Yh.d.ts +143 -0
  143. package/dist/types-DRgd_9R7.d.cts +363 -0
  144. package/dist/types-ajANVzf7.d.ts +363 -0
  145. package/package.json +178 -6
@@ -1,6 +1,44 @@
1
1
  'use strict';
2
2
 
3
- // src/types.ts
3
+ // src/types/errors.ts
4
+ var RECOVERABLE_CODES = /* @__PURE__ */ new Set([
5
+ "TIMEOUT" /* TIMEOUT */,
6
+ "RATE_LIMIT" /* RATE_LIMIT */,
7
+ "NETWORK" /* NETWORK */,
8
+ "TOOL_EXECUTION" /* TOOL_EXECUTION */,
9
+ "MODEL_OVERLOADED" /* MODEL_OVERLOADED */,
10
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
11
+ ]);
12
+ function isRecoverableErrorCode(code) {
13
+ return RECOVERABLE_CODES.has(code);
14
+ }
15
+ function classifyAgentError(error) {
16
+ const msg = (error instanceof Error ? error.message : error).toLowerCase();
17
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("timedout") || msg.includes("etimedout")) {
18
+ return "TIMEOUT" /* TIMEOUT */;
19
+ }
20
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429") || msg.includes("too many requests")) {
21
+ return "RATE_LIMIT" /* RATE_LIMIT */;
22
+ }
23
+ if (msg.includes("unauthorized") || msg.includes("401") || msg.includes("auth") && (msg.includes("expired") || msg.includes("invalid") || msg.includes("denied") || msg.includes("failed"))) {
24
+ return "AUTH_EXPIRED" /* AUTH_EXPIRED */;
25
+ }
26
+ if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("network") || msg.includes("fetch failed") || msg.includes("socket hang up")) {
27
+ return "NETWORK" /* NETWORK */;
28
+ }
29
+ if (msg.includes("subprocess") || msg.includes("process exited") || msg.includes("spawn") || msg.includes("enoent") || msg.includes("killed")) {
30
+ return "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */;
31
+ }
32
+ if (msg.includes("abort") || msg.includes("cancel")) {
33
+ return "ABORTED" /* ABORTED */;
34
+ }
35
+ if (msg.includes("500") || msg.includes("502") || msg.includes("503") || msg.includes("internal server error") || msg.includes("service unavailable") || msg.includes("bad gateway") || msg.includes("overloaded")) {
36
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
37
+ }
38
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
39
+ }
40
+
41
+ // src/types/guards.ts
4
42
  function getTextContent(content) {
5
43
  if (typeof content === "string") return content;
6
44
  return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
@@ -8,41 +46,71 @@ function getTextContent(content) {
8
46
 
9
47
  // src/errors.ts
10
48
  var AgentSDKError = class extends Error {
49
+ /** @internal Marker for cross-bundle identity checks */
50
+ _agentSDKError = true;
51
+ /** Machine-readable error code. Prefer values from the ErrorCode enum. */
52
+ code;
53
+ /** Whether this error is safe to retry */
54
+ retryable;
55
+ /** HTTP status code hint for error classification */
56
+ httpStatus;
11
57
  constructor(message, options) {
12
58
  super(message, options);
13
59
  this.name = "AgentSDKError";
60
+ this.code = options?.code;
61
+ this.retryable = options?.retryable ?? false;
62
+ this.httpStatus = options?.httpStatus;
63
+ }
64
+ /** Check if an error is an AgentSDKError (works across bundled copies) */
65
+ static is(error) {
66
+ return error instanceof Error && "_agentSDKError" in error && error._agentSDKError === true;
14
67
  }
15
68
  };
16
69
  var ReentrancyError = class extends AgentSDKError {
17
70
  constructor() {
18
- super("Agent is already running. Await the current run before starting another.");
71
+ super("Agent is already running. Await the current run before starting another.", {
72
+ code: "REENTRANCY" /* REENTRANCY */
73
+ });
19
74
  this.name = "ReentrancyError";
20
75
  }
21
76
  };
22
77
  var DisposedError = class extends AgentSDKError {
23
78
  constructor(entity) {
24
- super(`${entity} has been disposed and cannot be used.`);
79
+ super(`${entity} has been disposed and cannot be used.`, {
80
+ code: "DISPOSED" /* DISPOSED */
81
+ });
25
82
  this.name = "DisposedError";
26
83
  }
27
84
  };
28
85
  var SubprocessError = class extends AgentSDKError {
29
86
  constructor(message, options) {
30
- super(message, options);
87
+ super(message, { ...options, code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */ });
31
88
  this.name = "SubprocessError";
32
89
  }
33
90
  };
34
91
  var AbortError = class extends AgentSDKError {
35
92
  constructor() {
36
- super("Agent run was aborted.");
93
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
37
94
  this.name = "AbortError";
38
95
  }
39
96
  };
97
+ var ActivityTimeoutError = class extends AgentSDKError {
98
+ constructor(timeoutMs) {
99
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
100
+ code: "TIMEOUT" /* TIMEOUT */,
101
+ retryable: true
102
+ });
103
+ this.name = "ActivityTimeoutError";
104
+ }
105
+ };
40
106
 
41
107
  // src/base-agent.ts
42
108
  var BaseAgent = class {
43
109
  state = "idle";
44
110
  abortController = null;
45
111
  config;
112
+ _cleanupExternalSignal = null;
113
+ _streamMiddleware = [];
46
114
  /** CLI session ID for persistent mode. Override in backends that support it. */
47
115
  get sessionId() {
48
116
  return void 0;
@@ -58,12 +126,14 @@ var BaseAgent = class {
58
126
  this.state = "running";
59
127
  try {
60
128
  const messages = [{ role: "user", content: prompt }];
61
- const result = await this.executeRun(messages, options, ac.signal);
62
- this.enrichAndNotifyUsage(result);
129
+ const result = await this.withRetry(
130
+ () => this.executeRun(messages, options, ac.signal),
131
+ options
132
+ );
133
+ this.enrichAndNotifyUsage(result, options);
63
134
  return result;
64
135
  } finally {
65
- this.state = "idle";
66
- this.abortController = null;
136
+ this.cleanupRun();
67
137
  }
68
138
  }
69
139
  async runWithContext(messages, options) {
@@ -72,12 +142,14 @@ var BaseAgent = class {
72
142
  const ac = this.createAbortController(options?.signal);
73
143
  this.state = "running";
74
144
  try {
75
- const result = await this.executeRun(messages, options, ac.signal);
76
- this.enrichAndNotifyUsage(result);
145
+ const result = await this.withRetry(
146
+ () => this.executeRun(messages, options, ac.signal),
147
+ options
148
+ );
149
+ this.enrichAndNotifyUsage(result, options);
77
150
  return result;
78
151
  } finally {
79
- this.state = "idle";
80
- this.abortController = null;
152
+ this.cleanupRun();
81
153
  }
82
154
  }
83
155
  async runStructured(prompt, schema, options) {
@@ -87,17 +159,14 @@ var BaseAgent = class {
87
159
  this.state = "running";
88
160
  try {
89
161
  const messages = [{ role: "user", content: prompt }];
90
- const result = await this.executeRunStructured(
91
- messages,
92
- schema,
93
- options,
94
- ac.signal
162
+ const result = await this.withRetry(
163
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
164
+ options
95
165
  );
96
- this.enrichAndNotifyUsage(result);
166
+ this.enrichAndNotifyUsage(result, options);
97
167
  return result;
98
168
  } finally {
99
- this.state = "idle";
100
- this.abortController = null;
169
+ this.cleanupRun();
101
170
  }
102
171
  }
103
172
  async *stream(prompt, options) {
@@ -107,11 +176,12 @@ var BaseAgent = class {
107
176
  this.state = "streaming";
108
177
  try {
109
178
  const messages = [{ role: "user", content: prompt }];
110
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
111
- yield* this.heartbeatStream(enriched);
179
+ yield* this.streamWithRetry(
180
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
181
+ options
182
+ );
112
183
  } finally {
113
- this.state = "idle";
114
- this.abortController = null;
184
+ this.cleanupRun();
115
185
  }
116
186
  }
117
187
  async *streamWithContext(messages, options) {
@@ -120,13 +190,37 @@ var BaseAgent = class {
120
190
  const ac = this.createAbortController(options?.signal);
121
191
  this.state = "streaming";
122
192
  try {
123
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
124
- yield* this.heartbeatStream(enriched);
193
+ yield* this.streamWithRetry(
194
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
195
+ options
196
+ );
125
197
  } finally {
126
- this.state = "idle";
127
- this.abortController = null;
198
+ this.cleanupRun();
128
199
  }
129
200
  }
201
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
202
+ addStreamMiddleware(middleware) {
203
+ this.guardDisposed();
204
+ this._streamMiddleware.push(middleware);
205
+ }
206
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
207
+ async *applyStreamPipeline(source, options, ac) {
208
+ let stream = this.enrichStream(source, options);
209
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
210
+ stream = this.heartbeatStream(stream);
211
+ if (this._streamMiddleware.length > 0) {
212
+ const ctx = {
213
+ model: options.model,
214
+ backend: this.backendName,
215
+ abortController: ac,
216
+ config: Object.freeze({ ...this.config })
217
+ };
218
+ for (const mw of this._streamMiddleware) {
219
+ stream = mw(stream, ctx);
220
+ }
221
+ }
222
+ yield* stream;
223
+ }
130
224
  abort() {
131
225
  if (this.abortController) {
132
226
  this.abortController.abort();
@@ -144,29 +238,114 @@ var BaseAgent = class {
144
238
  }
145
239
  /** Mark agent as disposed. Override to add cleanup. */
146
240
  dispose() {
241
+ this._cleanupExternalSignal?.();
242
+ this._cleanupExternalSignal = null;
147
243
  this.abort();
148
244
  this.state = "disposed";
149
245
  }
246
+ // ─── Retry Logic ─────────────────────────────────────────────
247
+ /** Check if an error should be retried given the retry configuration. */
248
+ isRetryableError(error, retry) {
249
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
250
+ return false;
251
+ }
252
+ if (AgentSDKError.is(error)) {
253
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
254
+ return retry.retryableErrors.includes(error.code);
255
+ }
256
+ if (error.retryable) return true;
257
+ if (error.code) return isRecoverableErrorCode(error.code);
258
+ }
259
+ return false;
260
+ }
261
+ /** Execute a function with retry logic per RetryConfig. */
262
+ async withRetry(fn, options) {
263
+ const retry = options?.retry;
264
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
265
+ return fn();
266
+ }
267
+ const maxRetries = retry.maxRetries;
268
+ const initialDelay = retry.initialDelayMs ?? 1e3;
269
+ const multiplier = retry.backoffMultiplier ?? 2;
270
+ let lastError;
271
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
272
+ try {
273
+ return await fn();
274
+ } catch (err) {
275
+ lastError = err;
276
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
277
+ throw err;
278
+ }
279
+ const delay = initialDelay * Math.pow(multiplier, attempt);
280
+ await new Promise((resolve) => setTimeout(resolve, delay));
281
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
282
+ throw err;
283
+ }
284
+ }
285
+ }
286
+ throw lastError;
287
+ }
288
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
289
+ async *streamWithRetry(factory, options) {
290
+ const retry = options?.retry;
291
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
292
+ yield* factory();
293
+ return;
294
+ }
295
+ const maxRetries = retry.maxRetries;
296
+ const initialDelay = retry.initialDelayMs ?? 1e3;
297
+ const multiplier = retry.backoffMultiplier ?? 2;
298
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
299
+ try {
300
+ const stream = factory();
301
+ const iterator = stream[Symbol.asyncIterator]();
302
+ const first = await iterator.next();
303
+ if (first.done) return;
304
+ yield first.value;
305
+ while (true) {
306
+ const next = await iterator.next();
307
+ if (next.done) break;
308
+ yield next.value;
309
+ }
310
+ return;
311
+ } catch (err) {
312
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
313
+ throw err;
314
+ }
315
+ const delay = initialDelay * Math.pow(multiplier, attempt);
316
+ await new Promise((resolve) => setTimeout(resolve, delay));
317
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
318
+ throw err;
319
+ }
320
+ }
321
+ }
322
+ }
323
+ // ─── CallOptions Resolution ──────────────────────────────────
324
+ /** Resolve tools to use for this call (per-call override > config default) */
325
+ resolveTools(options) {
326
+ return options?.tools ?? this.config.tools ?? [];
327
+ }
150
328
  // ─── Usage Enrichment ───────────────────────────────────────────
151
329
  /** Enrich result usage with model/backend and fire onUsage callback */
152
- enrichAndNotifyUsage(result) {
330
+ enrichAndNotifyUsage(result, options) {
153
331
  if (result.usage) {
154
332
  result.usage = {
155
333
  ...result.usage,
156
- model: this.config.model,
334
+ model: options.model,
157
335
  backend: this.backendName
158
336
  };
159
337
  this.callOnUsage(result.usage);
160
338
  }
161
339
  }
162
340
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
163
- async *enrichStream(source) {
341
+ async *enrichStream(source, options) {
342
+ const model = options.model;
164
343
  for await (const event of source) {
165
344
  if (event.type === "usage_update") {
166
345
  const usage = {
167
346
  promptTokens: event.promptTokens,
168
347
  completionTokens: event.completionTokens,
169
- model: this.config.model,
348
+ model,
170
349
  backend: this.backendName
171
350
  };
172
351
  this.callOnUsage(usage);
@@ -236,6 +415,35 @@ var BaseAgent = class {
236
415
  heartbeatResolve = null;
237
416
  }
238
417
  }
418
+ // ─── Activity Timeout ────────────────────────────────────────
419
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
420
+ * When timeoutMs is not set, passes through directly. */
421
+ async *activityTimeoutStream(source, timeoutMs, ac) {
422
+ if (!timeoutMs || timeoutMs <= 0) {
423
+ yield* source;
424
+ return;
425
+ }
426
+ const iterator = source[Symbol.asyncIterator]();
427
+ let timerId;
428
+ try {
429
+ while (true) {
430
+ const timeoutPromise = new Promise((_, reject) => {
431
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
432
+ });
433
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
434
+ clearTimeout(timerId);
435
+ if (result.done) break;
436
+ yield result.value;
437
+ }
438
+ } catch (err) {
439
+ if (err instanceof ActivityTimeoutError) {
440
+ ac.abort(err);
441
+ }
442
+ throw err;
443
+ } finally {
444
+ clearTimeout(timerId);
445
+ }
446
+ }
239
447
  // ─── Guards ───────────────────────────────────────────────────
240
448
  guardReentrancy() {
241
449
  if (this.state === "running" || this.state === "streaming") {
@@ -254,16 +462,24 @@ var BaseAgent = class {
254
462
  }
255
463
  }
256
464
  // ─── Internal Helpers ─────────────────────────────────────────
465
+ /** Clean up after a run completes (success, error, or abort). */
466
+ cleanupRun() {
467
+ this._cleanupExternalSignal?.();
468
+ this._cleanupExternalSignal = null;
469
+ this.state = "idle";
470
+ this.abortController = null;
471
+ }
257
472
  createAbortController(externalSignal) {
258
473
  const ac = new AbortController();
259
474
  this.abortController = ac;
475
+ this._cleanupExternalSignal = null;
260
476
  if (externalSignal) {
261
477
  if (externalSignal.aborted) {
262
478
  ac.abort();
263
479
  } else {
264
- externalSignal.addEventListener("abort", () => ac.abort(), {
265
- once: true
266
- });
480
+ const listener = () => ac.abort();
481
+ externalSignal.addEventListener("abort", listener, { once: true });
482
+ this._cleanupExternalSignal = () => externalSignal.removeEventListener("abort", listener);
267
483
  }
268
484
  }
269
485
  return ac;
@@ -326,13 +542,67 @@ function extractSchemaFromDef(schema) {
326
542
  }
327
543
  }
328
544
 
545
+ // src/backends/shared.ts
546
+ function extractLastUserPrompt(messages) {
547
+ for (let i = messages.length - 1; i >= 0; i--) {
548
+ const msg = messages[i];
549
+ if (msg.role === "user") {
550
+ return getTextContent(msg.content);
551
+ }
552
+ }
553
+ return "";
554
+ }
555
+ function serializeToolCall(tc) {
556
+ const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
557
+ return ` Tool call: ${tc.name}(${args})`;
558
+ }
559
+ function serializeToolResult(tr) {
560
+ const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
561
+ const prefix = tr.isError ? "[ERROR] " : "";
562
+ return ` ${tr.name} \u2192 ${prefix}${result}`;
563
+ }
564
+ function buildContextualPrompt(messages) {
565
+ if (messages.length <= 1) {
566
+ return extractLastUserPrompt(messages);
567
+ }
568
+ const history = messages.slice(0, -1).map((msg) => {
569
+ if (msg.role === "user") {
570
+ return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
571
+ }
572
+ if (msg.role === "tool" && msg.toolResults) {
573
+ const results = msg.toolResults.map(serializeToolResult).join("\n");
574
+ return `Tool results:
575
+ ${results}`;
576
+ }
577
+ if (msg.role === "assistant") {
578
+ const parts = [];
579
+ const thinking = msg.thinking;
580
+ if (thinking) {
581
+ parts.push(`[reasoning: ${thinking}]`);
582
+ }
583
+ const text2 = msg.content ? getTextContent(msg.content) : "";
584
+ if (text2) parts.push(text2);
585
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
586
+ parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
587
+ }
588
+ return `Assistant: ${parts.join("\n")}`;
589
+ }
590
+ const text = msg.content ? getTextContent(msg.content) : "";
591
+ return `${msg.role}: ${text}`;
592
+ }).join("\n");
593
+ const lastPrompt = extractLastUserPrompt(messages);
594
+ return `Conversation history:
595
+ ${history}
596
+
597
+ User: ${lastPrompt}`;
598
+ }
599
+
329
600
  // src/backends/copilot.ts
330
- var sdkModule = null;
601
+ var _sdkMock = null;
331
602
  async function loadSDK() {
332
- if (sdkModule) return sdkModule;
603
+ if (_sdkMock) return _sdkMock;
333
604
  try {
334
- sdkModule = await import('@github/copilot-sdk');
335
- return sdkModule;
605
+ return await import('@github/copilot-sdk');
336
606
  } catch {
337
607
  throw new SubprocessError(
338
608
  "@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
@@ -340,18 +610,34 @@ async function loadSDK() {
340
610
  }
341
611
  }
342
612
  function _injectSDK(mock) {
343
- sdkModule = mock;
613
+ _sdkMock = mock;
344
614
  }
345
615
  function _resetSDK() {
346
- sdkModule = null;
616
+ _sdkMock = null;
347
617
  }
348
618
  function mapToolsToSDK(tools) {
349
619
  return tools.map((tool) => ({
350
620
  name: tool.name,
351
621
  description: tool.description,
352
- // Pass Zod schema directly — Copilot SDK accepts ZodSchema natively
353
- // and handles conversion internally, avoiding potential schema format issues.
354
- parameters: tool.parameters,
622
+ parameters: convertParameters(tool.parameters),
623
+ handler: async (args) => {
624
+ const result = await tool.execute(args);
625
+ return typeof result === "string" ? result : JSON.stringify(result);
626
+ }
627
+ }));
628
+ }
629
+ function convertParameters(params) {
630
+ if (!params) return void 0;
631
+ if (params && typeof params === "object" && "_def" in params) {
632
+ return zodToJsonSchema(params);
633
+ }
634
+ return params;
635
+ }
636
+ async function mapToolsToSDKAsync(tools) {
637
+ return tools.map((tool) => ({
638
+ name: tool.name,
639
+ description: tool.description,
640
+ parameters: convertParameters(tool.parameters),
355
641
  handler: async (args) => {
356
642
  const result = await tool.execute(args);
357
643
  return typeof result === "string" ? result : JSON.stringify(result);
@@ -372,6 +658,7 @@ function buildPermissionHandler(config) {
372
658
  const unifiedRequest = {
373
659
  toolName,
374
660
  toolArgs: { ...request },
661
+ toolCallId: request.toolCallId,
375
662
  rawSDKRequest: request
376
663
  };
377
664
  const ac = new AbortController();
@@ -514,15 +801,21 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
514
801
  };
515
802
  case "session.error":
516
803
  console.error("[copilot] mapSessionEvent error:", JSON.stringify(data));
517
- return {
518
- type: "error",
519
- error: String(data.message ?? "Unknown error"),
520
- recoverable: false
521
- };
804
+ {
805
+ const errorMsg = String(data.message ?? "Unknown error");
806
+ const code = classifyAgentError(errorMsg);
807
+ return {
808
+ type: "error",
809
+ error: errorMsg,
810
+ recoverable: isRecoverableErrorCode(code),
811
+ code
812
+ };
813
+ }
522
814
  case "assistant.message": {
523
815
  const doneEvent = {
524
816
  type: "done",
525
- finalOutput: data.content ? String(data.content) : null
817
+ finalOutput: null,
818
+ streamed: true
526
819
  };
527
820
  if (thinkingTracker.endThinking()) {
528
821
  return [{ type: "thinking_end" }, doneEvent];
@@ -542,13 +835,16 @@ var CopilotAgent = class extends BaseAgent {
542
835
  isPersistent;
543
836
  persistentSession = null;
544
837
  _sessionId;
838
+ _persistentModel;
545
839
  activeSession = null;
546
- constructor(config, getClient, sendAndWaitTimeout) {
840
+ _resumeSessionId;
841
+ _toolsReady = null;
842
+ constructor(config, getClient, sendAndWaitTimeout, resumeSessionId) {
547
843
  super(config);
548
844
  this.getClient = getClient;
549
845
  this.sendAndWaitTimeout = sendAndWaitTimeout;
550
846
  this.isPersistent = config.sessionMode === "persistent";
551
- this.sdkTools = mapToolsToSDK(config.tools);
847
+ this.sdkTools = mapToolsToSDK(config.tools ?? []);
552
848
  this.sessionConfig = {
553
849
  model: config.model,
554
850
  tools: this.sdkTools,
@@ -558,8 +854,16 @@ var CopilotAgent = class extends BaseAgent {
558
854
  },
559
855
  onPermissionRequest: buildPermissionHandler(config),
560
856
  onUserInputRequest: buildUserInputHandler(config),
561
- ...config.availableTools ? { availableTools: config.availableTools } : {}
857
+ ...config.availableTools?.length ? { availableTools: config.availableTools } : {}
562
858
  };
859
+ this._toolsReady = this._initToolsAsync(config);
860
+ this._resumeSessionId = resumeSessionId;
861
+ }
862
+ /** Pre-convert Zod schemas to JSON Schema asynchronously.
863
+ * Updates sdkTools and sessionConfig.tools before first session creation. */
864
+ async _initToolsAsync(config) {
865
+ this.sdkTools = await mapToolsToSDKAsync(config.tools ?? []);
866
+ this.sessionConfig.tools = this.sdkTools;
563
867
  }
564
868
  get sessionId() {
565
869
  return this._sessionId;
@@ -582,27 +886,63 @@ var CopilotAgent = class extends BaseAgent {
582
886
  });
583
887
  this.persistentSession = null;
584
888
  this._sessionId = void 0;
889
+ this._persistentModel = void 0;
585
890
  }
586
891
  }
587
- async getOrCreateSession(streaming) {
892
+ async getOrCreateSession(streaming, options) {
588
893
  if (this.isPersistent && this.persistentSession) {
589
- return { session: this.persistentSession, isNew: false };
894
+ if (options.model !== this._persistentModel) {
895
+ this.persistentSession.destroy().catch(() => {
896
+ });
897
+ this.persistentSession = null;
898
+ this._sessionId = void 0;
899
+ } else {
900
+ return { session: this.persistentSession, isNew: false };
901
+ }
902
+ }
903
+ if (this._toolsReady) {
904
+ await this._toolsReady;
905
+ this._toolsReady = null;
906
+ }
907
+ const sessionConfig = { ...this.sessionConfig };
908
+ sessionConfig.model = options.model;
909
+ const resolvedTools = this.resolveTools(options);
910
+ if (options?.tools) {
911
+ sessionConfig.tools = mapToolsToSDK(resolvedTools);
590
912
  }
591
913
  const client = await this.getClient();
914
+ if (this._resumeSessionId) {
915
+ const storedId = this._resumeSessionId;
916
+ this._resumeSessionId = void 0;
917
+ try {
918
+ const session2 = await client.resumeSession(storedId, {
919
+ ...sessionConfig,
920
+ streaming: this.isPersistent ? true : streaming
921
+ });
922
+ if (this.isPersistent) {
923
+ this.persistentSession = session2;
924
+ this._sessionId = session2.sessionId;
925
+ this._persistentModel = options.model;
926
+ }
927
+ return { session: session2, isNew: false };
928
+ } catch {
929
+ }
930
+ }
592
931
  const session = await client.createSession({
593
- ...this.sessionConfig,
932
+ ...sessionConfig,
594
933
  streaming: this.isPersistent ? true : streaming
595
934
  });
596
935
  if (this.isPersistent) {
597
936
  this.persistentSession = session;
598
937
  this._sessionId = session.sessionId;
938
+ this._persistentModel = options.model;
599
939
  }
600
940
  return { session, isNew: true };
601
941
  }
602
942
  // ─── executeRun ─────────────────────────────────────────────────
603
- async executeRun(messages, _options, signal) {
943
+ async executeRun(messages, options, signal) {
604
944
  this.checkAbort(signal);
605
- const { session, isNew: isNewSession } = await this.getOrCreateSession(false);
945
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(false, options);
606
946
  this.activeSession = session;
607
947
  const prompt = this.isPersistent && !isNewSession ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
608
948
  const tracker = new ToolCallTracker();
@@ -699,9 +1039,9 @@ You MUST respond with ONLY valid JSON matching this schema:
699
1039
  };
700
1040
  }
701
1041
  // ─── executeStream ──────────────────────────────────────────────
702
- async *executeStream(messages, _options, signal) {
1042
+ async *executeStream(messages, options, signal) {
703
1043
  this.checkAbort(signal);
704
- const { session, isNew: isNewSession } = await this.getOrCreateSession(true);
1044
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(true, options);
705
1045
  this.activeSession = session;
706
1046
  if (isNewSession) {
707
1047
  yield this.emitSessionInfo(session.sessionId);
@@ -787,28 +1127,20 @@ You MUST respond with ONLY valid JSON matching this schema:
787
1127
  super.dispose();
788
1128
  }
789
1129
  };
790
- function extractLastUserPrompt(messages) {
791
- for (let i = messages.length - 1; i >= 0; i--) {
792
- const msg = messages[i];
793
- if (msg.role === "user") {
794
- return getTextContent(msg.content);
795
- }
796
- }
797
- return "";
798
- }
799
- function buildContextualPrompt(messages) {
800
- if (messages.length <= 1) {
801
- return extractLastUserPrompt(messages);
802
- }
803
- const history = messages.slice(0, -1).map((msg) => {
804
- const text = msg.content ? getTextContent(msg.content) : "";
805
- return msg.role === "user" ? `User: ${text}` : `Assistant: ${text}`;
806
- }).join("\n");
807
- const lastPrompt = extractLastUserPrompt(messages);
808
- return `Conversation history:
809
- ${history}
810
-
811
- User: ${lastPrompt}`;
1130
+ function withTimeout(promise, ms, message) {
1131
+ return new Promise((resolve, reject) => {
1132
+ const timer = setTimeout(() => reject(new SubprocessError(message)), ms);
1133
+ promise.then(
1134
+ (val) => {
1135
+ clearTimeout(timer);
1136
+ resolve(val);
1137
+ },
1138
+ (err) => {
1139
+ clearTimeout(timer);
1140
+ reject(err);
1141
+ }
1142
+ );
1143
+ });
812
1144
  }
813
1145
  var CopilotAgentService = class {
814
1146
  name = "copilot";
@@ -834,12 +1166,17 @@ var CopilotAgentService = class {
834
1166
  autoRestart: true,
835
1167
  logLevel: "error",
836
1168
  githubToken: this.options.githubToken,
837
- useLoggedInUser: this.options.useLoggedInUser ?? true,
1169
+ useLoggedInUser: this.options.useLoggedInUser ?? !this.options.githubToken,
838
1170
  ...this.options.cliArgs ? { cliArgs: this.options.cliArgs } : {},
839
1171
  ...this.options.env ? { env: { ...process.env, ...this.options.env } } : {}
840
1172
  });
841
- await client.start();
842
- const auth = await client.getAuthStatus();
1173
+ const startupTimeout = this.options.startupTimeoutMs ?? 3e4;
1174
+ await withTimeout(client.start(), startupTimeout, "CLI startup timed out");
1175
+ const auth = await withTimeout(
1176
+ client.getAuthStatus(),
1177
+ startupTimeout,
1178
+ "Auth status check timed out \u2014 token may be expired"
1179
+ );
843
1180
  if (!auth.isAuthenticated) {
844
1181
  await client.stop();
845
1182
  throw new SubprocessError(
@@ -857,7 +1194,7 @@ var CopilotAgentService = class {
857
1194
  }
858
1195
  createAgent(config) {
859
1196
  if (this.disposed) throw new DisposedError("CopilotAgentService");
860
- return new CopilotAgent(config, () => this.ensureClient(), this.options.timeout);
1197
+ return new CopilotAgent(config, () => this.ensureClient(), this.options.timeout, this.options.resumeSessionId);
861
1198
  }
862
1199
  async listModels() {
863
1200
  const client = await this.ensureClient();
@@ -865,7 +1202,10 @@ var CopilotAgentService = class {
865
1202
  return models.map((m) => ({
866
1203
  id: m.id,
867
1204
  name: m.name,
868
- provider: "copilot"
1205
+ provider: "copilot",
1206
+ ...m.capabilities?.limits?.max_context_window_tokens != null && {
1207
+ contextWindow: m.capabilities.limits.max_context_window_tokens
1208
+ }
869
1209
  }));
870
1210
  }
871
1211
  async validate() {