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