@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,51 +44,83 @@ 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 DependencyError = class extends AgentSDKError {
27
84
  packageName;
28
85
  constructor(packageName) {
29
- super(`${packageName} is not installed. Install it: npm install ${packageName}`);
86
+ super(`${packageName} is not installed. Install it: npm install ${packageName}`, {
87
+ code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */
88
+ });
30
89
  this.name = "DependencyError";
31
90
  this.packageName = packageName;
32
91
  }
33
92
  };
34
93
  var AbortError = class extends AgentSDKError {
35
94
  constructor() {
36
- super("Agent run was aborted.");
95
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
37
96
  this.name = "AbortError";
38
97
  }
39
98
  };
40
99
  var ToolExecutionError = class extends AgentSDKError {
41
100
  toolName;
42
101
  constructor(toolName, message, options) {
43
- super(`Tool "${toolName}" failed: ${message}`, options);
102
+ super(`Tool "${toolName}" failed: ${message}`, { ...options, code: "TOOL_EXECUTION" /* TOOL_EXECUTION */ });
44
103
  this.name = "ToolExecutionError";
45
104
  this.toolName = toolName;
46
105
  }
47
106
  };
107
+ var ActivityTimeoutError = class extends AgentSDKError {
108
+ constructor(timeoutMs) {
109
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
110
+ code: "TIMEOUT" /* TIMEOUT */,
111
+ retryable: true
112
+ });
113
+ this.name = "ActivityTimeoutError";
114
+ }
115
+ };
48
116
 
49
117
  // src/base-agent.ts
50
118
  var BaseAgent = class {
51
119
  state = "idle";
52
120
  abortController = null;
53
121
  config;
122
+ _cleanupExternalSignal = null;
123
+ _streamMiddleware = [];
54
124
  /** CLI session ID for persistent mode. Override in backends that support it. */
55
125
  get sessionId() {
56
126
  return void 0;
@@ -66,12 +136,14 @@ var BaseAgent = class {
66
136
  this.state = "running";
67
137
  try {
68
138
  const messages = [{ role: "user", content: prompt }];
69
- const result = await this.executeRun(messages, options, ac.signal);
70
- this.enrichAndNotifyUsage(result);
139
+ const result = await this.withRetry(
140
+ () => this.executeRun(messages, options, ac.signal),
141
+ options
142
+ );
143
+ this.enrichAndNotifyUsage(result, options);
71
144
  return result;
72
145
  } finally {
73
- this.state = "idle";
74
- this.abortController = null;
146
+ this.cleanupRun();
75
147
  }
76
148
  }
77
149
  async runWithContext(messages, options) {
@@ -80,12 +152,14 @@ var BaseAgent = class {
80
152
  const ac = this.createAbortController(options?.signal);
81
153
  this.state = "running";
82
154
  try {
83
- const result = await this.executeRun(messages, options, ac.signal);
84
- this.enrichAndNotifyUsage(result);
155
+ const result = await this.withRetry(
156
+ () => this.executeRun(messages, options, ac.signal),
157
+ options
158
+ );
159
+ this.enrichAndNotifyUsage(result, options);
85
160
  return result;
86
161
  } finally {
87
- this.state = "idle";
88
- this.abortController = null;
162
+ this.cleanupRun();
89
163
  }
90
164
  }
91
165
  async runStructured(prompt, schema, options) {
@@ -95,17 +169,14 @@ var BaseAgent = class {
95
169
  this.state = "running";
96
170
  try {
97
171
  const messages = [{ role: "user", content: prompt }];
98
- const result = await this.executeRunStructured(
99
- messages,
100
- schema,
101
- options,
102
- ac.signal
172
+ const result = await this.withRetry(
173
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
174
+ options
103
175
  );
104
- this.enrichAndNotifyUsage(result);
176
+ this.enrichAndNotifyUsage(result, options);
105
177
  return result;
106
178
  } finally {
107
- this.state = "idle";
108
- this.abortController = null;
179
+ this.cleanupRun();
109
180
  }
110
181
  }
111
182
  async *stream(prompt, options) {
@@ -115,11 +186,12 @@ var BaseAgent = class {
115
186
  this.state = "streaming";
116
187
  try {
117
188
  const messages = [{ role: "user", content: prompt }];
118
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
119
- yield* this.heartbeatStream(enriched);
189
+ yield* this.streamWithRetry(
190
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
191
+ options
192
+ );
120
193
  } finally {
121
- this.state = "idle";
122
- this.abortController = null;
194
+ this.cleanupRun();
123
195
  }
124
196
  }
125
197
  async *streamWithContext(messages, options) {
@@ -128,12 +200,36 @@ var BaseAgent = class {
128
200
  const ac = this.createAbortController(options?.signal);
129
201
  this.state = "streaming";
130
202
  try {
131
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
132
- yield* this.heartbeatStream(enriched);
203
+ yield* this.streamWithRetry(
204
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
205
+ options
206
+ );
133
207
  } finally {
134
- this.state = "idle";
135
- this.abortController = null;
208
+ this.cleanupRun();
209
+ }
210
+ }
211
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
212
+ addStreamMiddleware(middleware) {
213
+ this.guardDisposed();
214
+ this._streamMiddleware.push(middleware);
215
+ }
216
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
217
+ async *applyStreamPipeline(source, options, ac) {
218
+ let stream = this.enrichStream(source, options);
219
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
220
+ stream = this.heartbeatStream(stream);
221
+ if (this._streamMiddleware.length > 0) {
222
+ const ctx = {
223
+ model: options.model,
224
+ backend: this.backendName,
225
+ abortController: ac,
226
+ config: Object.freeze({ ...this.config })
227
+ };
228
+ for (const mw of this._streamMiddleware) {
229
+ stream = mw(stream, ctx);
230
+ }
136
231
  }
232
+ yield* stream;
137
233
  }
138
234
  abort() {
139
235
  if (this.abortController) {
@@ -152,29 +248,114 @@ var BaseAgent = class {
152
248
  }
153
249
  /** Mark agent as disposed. Override to add cleanup. */
154
250
  dispose() {
251
+ this._cleanupExternalSignal?.();
252
+ this._cleanupExternalSignal = null;
155
253
  this.abort();
156
254
  this.state = "disposed";
157
255
  }
256
+ // ─── Retry Logic ─────────────────────────────────────────────
257
+ /** Check if an error should be retried given the retry configuration. */
258
+ isRetryableError(error, retry) {
259
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
260
+ return false;
261
+ }
262
+ if (AgentSDKError.is(error)) {
263
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
264
+ return retry.retryableErrors.includes(error.code);
265
+ }
266
+ if (error.retryable) return true;
267
+ if (error.code) return isRecoverableErrorCode(error.code);
268
+ }
269
+ return false;
270
+ }
271
+ /** Execute a function with retry logic per RetryConfig. */
272
+ async withRetry(fn, options) {
273
+ const retry = options?.retry;
274
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
275
+ return fn();
276
+ }
277
+ const maxRetries = retry.maxRetries;
278
+ const initialDelay = retry.initialDelayMs ?? 1e3;
279
+ const multiplier = retry.backoffMultiplier ?? 2;
280
+ let lastError;
281
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
282
+ try {
283
+ return await fn();
284
+ } catch (err) {
285
+ lastError = err;
286
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
287
+ throw err;
288
+ }
289
+ const delay = initialDelay * Math.pow(multiplier, attempt);
290
+ await new Promise((resolve) => setTimeout(resolve, delay));
291
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
292
+ throw err;
293
+ }
294
+ }
295
+ }
296
+ throw lastError;
297
+ }
298
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
299
+ async *streamWithRetry(factory, options) {
300
+ const retry = options?.retry;
301
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
302
+ yield* factory();
303
+ return;
304
+ }
305
+ const maxRetries = retry.maxRetries;
306
+ const initialDelay = retry.initialDelayMs ?? 1e3;
307
+ const multiplier = retry.backoffMultiplier ?? 2;
308
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
309
+ try {
310
+ const stream = factory();
311
+ const iterator = stream[Symbol.asyncIterator]();
312
+ const first = await iterator.next();
313
+ if (first.done) return;
314
+ yield first.value;
315
+ while (true) {
316
+ const next = await iterator.next();
317
+ if (next.done) break;
318
+ yield next.value;
319
+ }
320
+ return;
321
+ } catch (err) {
322
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
323
+ throw err;
324
+ }
325
+ const delay = initialDelay * Math.pow(multiplier, attempt);
326
+ await new Promise((resolve) => setTimeout(resolve, delay));
327
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
328
+ throw err;
329
+ }
330
+ }
331
+ }
332
+ }
333
+ // ─── CallOptions Resolution ──────────────────────────────────
334
+ /** Resolve tools to use for this call (per-call override > config default) */
335
+ resolveTools(options) {
336
+ return options?.tools ?? this.config.tools ?? [];
337
+ }
158
338
  // ─── Usage Enrichment ───────────────────────────────────────────
159
339
  /** Enrich result usage with model/backend and fire onUsage callback */
160
- enrichAndNotifyUsage(result) {
340
+ enrichAndNotifyUsage(result, options) {
161
341
  if (result.usage) {
162
342
  result.usage = {
163
343
  ...result.usage,
164
- model: this.config.model,
344
+ model: options.model,
165
345
  backend: this.backendName
166
346
  };
167
347
  this.callOnUsage(result.usage);
168
348
  }
169
349
  }
170
350
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
171
- async *enrichStream(source) {
351
+ async *enrichStream(source, options) {
352
+ const model = options.model;
172
353
  for await (const event of source) {
173
354
  if (event.type === "usage_update") {
174
355
  const usage = {
175
356
  promptTokens: event.promptTokens,
176
357
  completionTokens: event.completionTokens,
177
- model: this.config.model,
358
+ model,
178
359
  backend: this.backendName
179
360
  };
180
361
  this.callOnUsage(usage);
@@ -244,6 +425,35 @@ var BaseAgent = class {
244
425
  heartbeatResolve = null;
245
426
  }
246
427
  }
428
+ // ─── Activity Timeout ────────────────────────────────────────
429
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
430
+ * When timeoutMs is not set, passes through directly. */
431
+ async *activityTimeoutStream(source, timeoutMs, ac) {
432
+ if (!timeoutMs || timeoutMs <= 0) {
433
+ yield* source;
434
+ return;
435
+ }
436
+ const iterator = source[Symbol.asyncIterator]();
437
+ let timerId;
438
+ try {
439
+ while (true) {
440
+ const timeoutPromise = new Promise((_, reject) => {
441
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
442
+ });
443
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
444
+ clearTimeout(timerId);
445
+ if (result.done) break;
446
+ yield result.value;
447
+ }
448
+ } catch (err) {
449
+ if (err instanceof ActivityTimeoutError) {
450
+ ac.abort(err);
451
+ }
452
+ throw err;
453
+ } finally {
454
+ clearTimeout(timerId);
455
+ }
456
+ }
247
457
  // ─── Guards ───────────────────────────────────────────────────
248
458
  guardReentrancy() {
249
459
  if (this.state === "running" || this.state === "streaming") {
@@ -262,16 +472,24 @@ var BaseAgent = class {
262
472
  }
263
473
  }
264
474
  // ─── Internal Helpers ─────────────────────────────────────────
475
+ /** Clean up after a run completes (success, error, or abort). */
476
+ cleanupRun() {
477
+ this._cleanupExternalSignal?.();
478
+ this._cleanupExternalSignal = null;
479
+ this.state = "idle";
480
+ this.abortController = null;
481
+ }
265
482
  createAbortController(externalSignal) {
266
483
  const ac = new AbortController();
267
484
  this.abortController = ac;
485
+ this._cleanupExternalSignal = null;
268
486
  if (externalSignal) {
269
487
  if (externalSignal.aborted) {
270
488
  ac.abort();
271
489
  } else {
272
- externalSignal.addEventListener("abort", () => ac.abort(), {
273
- once: true
274
- });
490
+ const listener = () => ac.abort();
491
+ externalSignal.addEventListener("abort", listener, { once: true });
492
+ this._cleanupExternalSignal = () => externalSignal.removeEventListener("abort", listener);
275
493
  }
276
494
  }
277
495
  return ac;
@@ -335,35 +553,33 @@ function extractSchemaFromDef(schema) {
335
553
  }
336
554
 
337
555
  // src/backends/vercel-ai.ts
338
- var sdkModule = null;
339
- var compatModule = null;
556
+ var _sdkMock = null;
557
+ var _compatMock = null;
340
558
  async function loadSDK() {
341
- if (sdkModule) return sdkModule;
559
+ if (_sdkMock) return _sdkMock;
342
560
  try {
343
- sdkModule = await import('ai');
344
- return sdkModule;
561
+ return await import('ai');
345
562
  } catch {
346
563
  throw new DependencyError("ai");
347
564
  }
348
565
  }
349
566
  async function loadCompat() {
350
- if (compatModule) return compatModule;
567
+ if (_compatMock) return _compatMock;
351
568
  try {
352
- compatModule = await import('@ai-sdk/openai-compatible');
353
- return compatModule;
569
+ return await import('@ai-sdk/openai-compatible');
354
570
  } catch {
355
571
  throw new DependencyError("@ai-sdk/openai-compatible");
356
572
  }
357
573
  }
358
574
  function _injectSDK(mock) {
359
- sdkModule = mock;
575
+ _sdkMock = mock;
360
576
  }
361
577
  function _injectCompat(mock) {
362
- compatModule = mock;
578
+ _compatMock = mock;
363
579
  }
364
580
  function _resetSDK() {
365
- sdkModule = null;
366
- compatModule = null;
581
+ _sdkMock = null;
582
+ _compatMock = null;
367
583
  }
368
584
  var DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
369
585
  var DEFAULT_PROVIDER = "openrouter";
@@ -409,13 +625,14 @@ function mapToolsToSDK(sdk, tools, config, sessionApprovals, permissionStore, si
409
625
  return toolMap;
410
626
  }
411
627
  function wrapToolExecute(ourTool, supervisor, sessionApprovals, permissionStore, signal) {
412
- return async (args) => {
628
+ return async (args, options) => {
413
629
  if (ourTool.needsApproval && supervisor?.onPermission) {
414
630
  const storeApproved = permissionStore && await permissionStore.isApproved(ourTool.name);
415
631
  if (!storeApproved && !sessionApprovals.has(ourTool.name)) {
416
632
  const request = {
417
633
  toolName: ourTool.name,
418
- toolArgs: args ?? {}
634
+ toolArgs: args ?? {},
635
+ toolCallId: options?.toolCallId
419
636
  };
420
637
  const decision = await supervisor.onPermission(
421
638
  request,
@@ -455,12 +672,39 @@ function messagesToSDK(messages) {
455
672
  switch (msg.role) {
456
673
  case "user":
457
674
  return { role: "user", content: getTextContent(msg.content) };
458
- case "assistant":
459
- return { role: "assistant", content: getTextContent(msg.content) };
675
+ case "assistant": {
676
+ let content = getTextContent(msg.content);
677
+ const thinking = msg.thinking;
678
+ if (thinking) {
679
+ content = `[reasoning: ${thinking}]
680
+ ${content}`;
681
+ }
682
+ const mapped = { role: "assistant", content };
683
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
684
+ mapped.toolCalls = msg.toolCalls.map((tc) => ({
685
+ id: tc.id,
686
+ name: tc.name,
687
+ args: tc.args
688
+ }));
689
+ }
690
+ return mapped;
691
+ }
460
692
  case "system":
461
693
  return { role: "system", content: msg.content };
462
- case "tool":
694
+ case "tool": {
695
+ if (msg.toolResults && msg.toolResults.length > 0) {
696
+ return {
697
+ role: "tool",
698
+ toolResults: msg.toolResults.map((tr) => ({
699
+ toolCallId: tr.toolCallId,
700
+ name: tr.name,
701
+ result: tr.result,
702
+ isError: tr.isError ?? false
703
+ }))
704
+ };
705
+ }
463
706
  return { role: "tool", content: msg.content ?? "" };
707
+ }
464
708
  default:
465
709
  return { role: "user", content: "" };
466
710
  }
@@ -495,7 +739,8 @@ function mapStreamPart(part) {
495
739
  return {
496
740
  type: "error",
497
741
  error: p.error instanceof Error ? p.error.message : String(p.error ?? "Tool execution failed"),
498
- recoverable: true
742
+ recoverable: true,
743
+ code: "TOOL_EXECUTION" /* TOOL_EXECUTION */
499
744
  };
500
745
  }
501
746
  case "reasoning-start":
@@ -516,10 +761,13 @@ function mapStreamPart(part) {
516
761
  }
517
762
  case "error": {
518
763
  const p = part;
764
+ const errorMsg = p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error");
765
+ const code = classifyAgentError(errorMsg);
519
766
  return {
520
767
  type: "error",
521
- error: p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error"),
522
- recoverable: false
768
+ error: errorMsg,
769
+ recoverable: isRecoverableErrorCode(code),
770
+ code
523
771
  };
524
772
  }
525
773
  default:
@@ -535,28 +783,33 @@ var VercelAIAgent = class extends BaseAgent {
535
783
  super(config);
536
784
  this.backendOptions = backendOptions;
537
785
  }
538
- async getModel() {
539
- if (this.model) return this.model;
786
+ async getModel(options) {
787
+ const requestedModel = options.model;
788
+ const defaultModel = this.config.model;
789
+ if (requestedModel === defaultModel && this.model) return this.model;
540
790
  const compat = await loadCompat();
541
791
  const provider = compat.createOpenAICompatible({
542
792
  name: this.backendOptions.provider ?? DEFAULT_PROVIDER,
543
793
  baseURL: this.backendOptions.baseUrl ?? DEFAULT_BASE_URL,
544
794
  apiKey: this.backendOptions.apiKey
545
795
  });
546
- const modelId = this.config.model ?? "anthropic/claude-sonnet-4-5";
547
- this.model = provider.chatModel(modelId);
548
- return this.model;
796
+ const model = provider.chatModel(requestedModel);
797
+ if (requestedModel === defaultModel) {
798
+ this.model = model;
799
+ }
800
+ return model;
549
801
  }
550
- async getSDKTools(signal) {
802
+ async getSDKTools(signal, options) {
551
803
  const sdk = await loadSDK();
552
- return mapToolsToSDK(sdk, this.config.tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
804
+ const tools = this.resolveTools(options);
805
+ return mapToolsToSDK(sdk, tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
553
806
  }
554
807
  // ─── executeRun ─────────────────────────────────────────────────
555
- async executeRun(messages, _options, signal) {
808
+ async executeRun(messages, options, signal) {
556
809
  this.checkAbort(signal);
557
810
  const sdk = await loadSDK();
558
- const model = await this.getModel();
559
- const tools = await this.getSDKTools(signal);
811
+ const model = await this.getModel(options);
812
+ const tools = await this.getSDKTools(signal, options);
560
813
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
561
814
  const sdkMessages = messagesToSDK(messages);
562
815
  const hasTools = Object.keys(tools).length > 0;
@@ -612,10 +865,10 @@ var VercelAIAgent = class extends BaseAgent {
612
865
  };
613
866
  }
614
867
  // ─── executeRunStructured ───────────────────────────────────────
615
- async executeRunStructured(messages, schema, _options, signal) {
868
+ async executeRunStructured(messages, schema, options, signal) {
616
869
  this.checkAbort(signal);
617
870
  const sdk = await loadSDK();
618
- const model = await this.getModel();
871
+ const model = await this.getModel(options);
619
872
  const sdkMessages = messagesToSDK(messages);
620
873
  const jsonSchema = zodToJsonSchema(schema.schema);
621
874
  const result = await sdk.generateObject({
@@ -657,11 +910,11 @@ var VercelAIAgent = class extends BaseAgent {
657
910
  };
658
911
  }
659
912
  // ─── executeStream ──────────────────────────────────────────────
660
- async *executeStream(messages, _options, signal) {
913
+ async *executeStream(messages, options, signal) {
661
914
  this.checkAbort(signal);
662
915
  const sdk = await loadSDK();
663
- const model = await this.getModel();
664
- const tools = await this.getSDKTools(signal);
916
+ const model = await this.getModel(options);
917
+ const tools = await this.getSDKTools(signal, options);
665
918
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
666
919
  const sdkMessages = messagesToSDK(messages);
667
920
  const hasTools = Object.keys(tools).length > 0;
@@ -707,9 +960,11 @@ var VercelAIAgent = class extends BaseAgent {
707
960
  promptTokens: Number(totalUsage?.inputTokens ?? 0),
708
961
  completionTokens: Number(totalUsage?.outputTokens ?? 0)
709
962
  };
963
+ const hasStreamed = finalText.length > 0;
710
964
  yield {
711
965
  type: "done",
712
- finalOutput: finalText || null
966
+ finalOutput: hasStreamed ? null : finalText || null,
967
+ ...hasStreamed ? { streamed: true } : {}
713
968
  };
714
969
  } catch (e) {
715
970
  if (signal.aborted) throw new AbortError();
@@ -738,16 +993,33 @@ var VercelAIAgentService = class {
738
993
  const baseUrl = (this.options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
739
994
  try {
740
995
  const res = await globalThis.fetch(`${baseUrl}/models`, {
741
- headers: { Authorization: `Bearer ${this.options.apiKey}` }
996
+ headers: {
997
+ Authorization: `Bearer ${this.options.apiKey}`,
998
+ // OpenRouter requires HTTP-Referer for API access
999
+ "HTTP-Referer": "https://github.com/nicepkg/agent-sdk"
1000
+ }
742
1001
  });
743
1002
  if (!res.ok) {
744
1003
  return [];
745
1004
  }
746
1005
  const body = await res.json();
747
- if (!body.data || body.data.length === 0) {
748
- return [];
1006
+ if (body.data && Array.isArray(body.data)) {
1007
+ return body.data.filter((m) => typeof m.id === "string").map((m) => ({
1008
+ id: m.id,
1009
+ ...typeof m.name === "string" && { name: m.name },
1010
+ ...typeof m.description === "string" && { description: m.description },
1011
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
1012
+ }));
749
1013
  }
750
- return body.data.map((m) => ({ id: m.id }));
1014
+ if (Array.isArray(body)) {
1015
+ return body.filter((m) => typeof m.id === "string").map((m) => ({
1016
+ id: m.id,
1017
+ ...typeof m.name === "string" && { name: m.name },
1018
+ ...typeof m.description === "string" && { description: m.description },
1019
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
1020
+ }));
1021
+ }
1022
+ return [];
751
1023
  } catch {
752
1024
  return [];
753
1025
  }