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