@witqq/agent-sdk 0.7.0 → 0.9.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 (154) hide show
  1. package/dist/{types-CqvUAYxt.d.ts → agent-C6H2CgJA.d.cts} +139 -102
  2. package/dist/{types-CqvUAYxt.d.cts → agent-F7oB6eKp.d.ts} +139 -102
  3. package/dist/auth/index.cjs +72 -1
  4. package/dist/auth/index.cjs.map +1 -1
  5. package/dist/auth/index.d.cts +21 -154
  6. package/dist/auth/index.d.ts +21 -154
  7. package/dist/auth/index.js +72 -1
  8. package/dist/auth/index.js.map +1 -1
  9. package/dist/backends/claude.cjs +480 -261
  10. package/dist/backends/claude.cjs.map +1 -1
  11. package/dist/backends/claude.d.cts +3 -1
  12. package/dist/backends/claude.d.ts +3 -1
  13. package/dist/backends/claude.js +480 -261
  14. package/dist/backends/claude.js.map +1 -1
  15. package/dist/backends/copilot.cjs +337 -112
  16. package/dist/backends/copilot.cjs.map +1 -1
  17. package/dist/backends/copilot.d.cts +12 -4
  18. package/dist/backends/copilot.d.ts +12 -4
  19. package/dist/backends/copilot.js +337 -112
  20. package/dist/backends/copilot.js.map +1 -1
  21. package/dist/backends/mock-llm.cjs +719 -0
  22. package/dist/backends/mock-llm.cjs.map +1 -0
  23. package/dist/backends/mock-llm.d.cts +37 -0
  24. package/dist/backends/mock-llm.d.ts +37 -0
  25. package/dist/backends/mock-llm.js +717 -0
  26. package/dist/backends/mock-llm.js.map +1 -0
  27. package/dist/backends/vercel-ai.cjs +301 -61
  28. package/dist/backends/vercel-ai.cjs.map +1 -1
  29. package/dist/backends/vercel-ai.d.cts +3 -1
  30. package/dist/backends/vercel-ai.d.ts +3 -1
  31. package/dist/backends/vercel-ai.js +301 -61
  32. package/dist/backends/vercel-ai.js.map +1 -1
  33. package/dist/backends-Cno0gZjy.d.cts +114 -0
  34. package/dist/backends-Cno0gZjy.d.ts +114 -0
  35. package/dist/chat/accumulator.cjs +1 -1
  36. package/dist/chat/accumulator.cjs.map +1 -1
  37. package/dist/chat/accumulator.d.cts +5 -2
  38. package/dist/chat/accumulator.d.ts +5 -2
  39. package/dist/chat/accumulator.js +1 -1
  40. package/dist/chat/accumulator.js.map +1 -1
  41. package/dist/chat/backends.cjs +1084 -821
  42. package/dist/chat/backends.cjs.map +1 -1
  43. package/dist/chat/backends.d.cts +10 -6
  44. package/dist/chat/backends.d.ts +10 -6
  45. package/dist/chat/backends.js +1082 -800
  46. package/dist/chat/backends.js.map +1 -1
  47. package/dist/chat/context.cjs +50 -0
  48. package/dist/chat/context.cjs.map +1 -1
  49. package/dist/chat/context.d.cts +27 -3
  50. package/dist/chat/context.d.ts +27 -3
  51. package/dist/chat/context.js +50 -0
  52. package/dist/chat/context.js.map +1 -1
  53. package/dist/chat/core.cjs +60 -27
  54. package/dist/chat/core.cjs.map +1 -1
  55. package/dist/chat/core.d.cts +41 -382
  56. package/dist/chat/core.d.ts +41 -382
  57. package/dist/chat/core.js +58 -28
  58. package/dist/chat/core.js.map +1 -1
  59. package/dist/chat/errors.cjs +48 -26
  60. package/dist/chat/errors.cjs.map +1 -1
  61. package/dist/chat/errors.d.cts +6 -31
  62. package/dist/chat/errors.d.ts +6 -31
  63. package/dist/chat/errors.js +48 -25
  64. package/dist/chat/errors.js.map +1 -1
  65. package/dist/chat/events.cjs.map +1 -1
  66. package/dist/chat/events.d.cts +6 -2
  67. package/dist/chat/events.d.ts +6 -2
  68. package/dist/chat/events.js.map +1 -1
  69. package/dist/chat/index.cjs +1612 -1125
  70. package/dist/chat/index.cjs.map +1 -1
  71. package/dist/chat/index.d.cts +35 -10
  72. package/dist/chat/index.d.ts +35 -10
  73. package/dist/chat/index.js +1600 -1097
  74. package/dist/chat/index.js.map +1 -1
  75. package/dist/chat/react/theme.css +2517 -0
  76. package/dist/chat/react.cjs +2212 -1158
  77. package/dist/chat/react.cjs.map +1 -1
  78. package/dist/chat/react.d.cts +665 -122
  79. package/dist/chat/react.d.ts +665 -122
  80. package/dist/chat/react.js +2191 -1156
  81. package/dist/chat/react.js.map +1 -1
  82. package/dist/chat/runtime.cjs +405 -186
  83. package/dist/chat/runtime.cjs.map +1 -1
  84. package/dist/chat/runtime.d.cts +92 -28
  85. package/dist/chat/runtime.d.ts +92 -28
  86. package/dist/chat/runtime.js +405 -186
  87. package/dist/chat/runtime.js.map +1 -1
  88. package/dist/chat/server.cjs +2247 -212
  89. package/dist/chat/server.cjs.map +1 -1
  90. package/dist/chat/server.d.cts +451 -90
  91. package/dist/chat/server.d.ts +451 -90
  92. package/dist/chat/server.js +2234 -213
  93. package/dist/chat/server.js.map +1 -1
  94. package/dist/chat/sessions.cjs +64 -66
  95. package/dist/chat/sessions.cjs.map +1 -1
  96. package/dist/chat/sessions.d.cts +37 -118
  97. package/dist/chat/sessions.d.ts +37 -118
  98. package/dist/chat/sessions.js +65 -67
  99. package/dist/chat/sessions.js.map +1 -1
  100. package/dist/chat/sqlite.cjs +536 -0
  101. package/dist/chat/sqlite.cjs.map +1 -0
  102. package/dist/chat/sqlite.d.cts +164 -0
  103. package/dist/chat/sqlite.d.ts +164 -0
  104. package/dist/chat/sqlite.js +527 -0
  105. package/dist/chat/sqlite.js.map +1 -0
  106. package/dist/chat/state.cjs +14 -1
  107. package/dist/chat/state.cjs.map +1 -1
  108. package/dist/chat/state.d.cts +5 -2
  109. package/dist/chat/state.d.ts +5 -2
  110. package/dist/chat/state.js +14 -1
  111. package/dist/chat/state.js.map +1 -1
  112. package/dist/chat/storage.cjs +58 -33
  113. package/dist/chat/storage.cjs.map +1 -1
  114. package/dist/chat/storage.d.cts +18 -8
  115. package/dist/chat/storage.d.ts +18 -8
  116. package/dist/chat/storage.js +59 -34
  117. package/dist/chat/storage.js.map +1 -1
  118. package/dist/errors-C-so0M4t.d.cts +33 -0
  119. package/dist/errors-C-so0M4t.d.ts +33 -0
  120. package/dist/errors-CmVvczxZ.d.cts +28 -0
  121. package/dist/errors-CmVvczxZ.d.ts +28 -0
  122. package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-7EIit9Xk.d.ts} +72 -33
  123. package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-Ct9YcX8I.d.cts} +72 -33
  124. package/dist/index.cjs +354 -60
  125. package/dist/index.cjs.map +1 -1
  126. package/dist/index.d.cts +294 -123
  127. package/dist/index.d.ts +294 -123
  128. package/dist/index.js +347 -60
  129. package/dist/index.js.map +1 -1
  130. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  131. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  132. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  133. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  134. package/dist/testing.cjs +1107 -0
  135. package/dist/testing.cjs.map +1 -0
  136. package/dist/testing.d.cts +144 -0
  137. package/dist/testing.d.ts +144 -0
  138. package/dist/testing.js +1101 -0
  139. package/dist/testing.js.map +1 -0
  140. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  141. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  142. package/dist/{transport-DX1Nhm4N.d.cts → transport-DLWCN18G.d.cts} +5 -4
  143. package/dist/{transport-D1OaUgRk.d.ts → transport-DsuS-GeM.d.ts} +5 -4
  144. package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
  145. package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
  146. package/dist/types-DgtI1hzh.d.ts +364 -0
  147. package/dist/types-DkSXALKg.d.cts +364 -0
  148. package/package.json +41 -5
  149. package/LICENSE +0 -21
  150. package/README.md +0 -948
  151. package/dist/errors-BDLbNu9w.d.cts +0 -13
  152. package/dist/errors-BDLbNu9w.d.ts +0 -13
  153. package/dist/types-DLZzlJxt.d.ts +0 -39
  154. package/dist/types-tE0CXwBl.d.cts +0 -39
@@ -1,7 +1,3 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
-
5
1
  var __defProp = Object.defineProperty;
6
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -23,16 +19,69 @@ var __copyProps = (to, from, except, desc) => {
23
19
  };
24
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
21
 
26
- // src/errors.ts
27
- var AgentSDKError, ReentrancyError, DisposedError, BackendNotFoundError, BackendAlreadyRegisteredError, SubprocessError, DependencyError, AbortError, ToolExecutionError, StructuredOutputError;
22
+ // src/types/errors.ts
23
+ function isRecoverableErrorCode(code) {
24
+ return RECOVERABLE_CODES.has(code);
25
+ }
26
+ function classifyAgentError(error) {
27
+ const msg = (error instanceof Error ? error.message : error).toLowerCase();
28
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("timedout") || msg.includes("etimedout")) {
29
+ return "TIMEOUT" /* TIMEOUT */;
30
+ }
31
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429") || msg.includes("too many requests")) {
32
+ return "RATE_LIMIT" /* RATE_LIMIT */;
33
+ }
34
+ if (msg.includes("unauthorized") || msg.includes("401") || msg.includes("auth") && (msg.includes("expired") || msg.includes("invalid") || msg.includes("denied") || msg.includes("failed"))) {
35
+ return "AUTH_EXPIRED" /* AUTH_EXPIRED */;
36
+ }
37
+ if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("network") || msg.includes("fetch failed") || msg.includes("socket hang up")) {
38
+ return "NETWORK" /* NETWORK */;
39
+ }
40
+ if (msg.includes("subprocess") || msg.includes("process exited") || msg.includes("spawn") || msg.includes("enoent") || msg.includes("killed")) {
41
+ return "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */;
42
+ }
43
+ if (msg.includes("abort") || msg.includes("cancel")) {
44
+ return "ABORTED" /* ABORTED */;
45
+ }
46
+ 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")) {
47
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
48
+ }
49
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
50
+ }
51
+ var RECOVERABLE_CODES;
28
52
  var init_errors = __esm({
53
+ "src/types/errors.ts"() {
54
+ RECOVERABLE_CODES = /* @__PURE__ */ new Set([
55
+ "TIMEOUT" /* TIMEOUT */,
56
+ "RATE_LIMIT" /* RATE_LIMIT */,
57
+ "NETWORK" /* NETWORK */,
58
+ "TOOL_EXECUTION" /* TOOL_EXECUTION */,
59
+ "MODEL_OVERLOADED" /* MODEL_OVERLOADED */,
60
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
61
+ ]);
62
+ }
63
+ });
64
+
65
+ // src/errors.ts
66
+ var AgentSDKError, ReentrancyError, DisposedError, SubprocessError, DependencyError, AbortError, ToolExecutionError, ActivityTimeoutError;
67
+ var init_errors2 = __esm({
29
68
  "src/errors.ts"() {
69
+ init_errors();
30
70
  AgentSDKError = class extends Error {
31
71
  /** @internal Marker for cross-bundle identity checks */
32
72
  _agentSDKError = true;
73
+ /** Machine-readable error code. Prefer values from the ErrorCode enum. */
74
+ code;
75
+ /** Whether this error is safe to retry */
76
+ retryable;
77
+ /** HTTP status code hint for error classification */
78
+ httpStatus;
33
79
  constructor(message, options) {
34
80
  super(message, options);
35
81
  this.name = "AgentSDKError";
82
+ this.code = options?.code;
83
+ this.retryable = options?.retryable ?? false;
84
+ this.httpStatus = options?.httpStatus;
36
85
  }
37
86
  /** Check if an error is an AgentSDKError (works across bundled copies) */
38
87
  static is(error) {
@@ -41,83 +90,84 @@ var init_errors = __esm({
41
90
  };
42
91
  ReentrancyError = class extends AgentSDKError {
43
92
  constructor() {
44
- super("Agent is already running. Await the current run before starting another.");
93
+ super("Agent is already running. Await the current run before starting another.", {
94
+ code: "REENTRANCY" /* REENTRANCY */
95
+ });
45
96
  this.name = "ReentrancyError";
46
97
  }
47
98
  };
48
99
  DisposedError = class extends AgentSDKError {
49
100
  constructor(entity) {
50
- super(`${entity} has been disposed and cannot be used.`);
101
+ super(`${entity} has been disposed and cannot be used.`, {
102
+ code: "DISPOSED" /* DISPOSED */
103
+ });
51
104
  this.name = "DisposedError";
52
105
  }
53
106
  };
54
- BackendNotFoundError = class extends AgentSDKError {
55
- constructor(backend) {
56
- super(
57
- `Unknown backend: "${backend}". Built-in: copilot, claude, vercel-ai. Custom: use registerBackend() first.`
58
- );
59
- this.name = "BackendNotFoundError";
60
- }
61
- };
62
- BackendAlreadyRegisteredError = class extends AgentSDKError {
63
- constructor(backend) {
64
- super(`Backend "${backend}" is already registered. Use a different name or unregister first.`);
65
- this.name = "BackendAlreadyRegisteredError";
66
- }
67
- };
68
107
  SubprocessError = class extends AgentSDKError {
69
108
  constructor(message, options) {
70
- super(message, options);
109
+ super(message, { ...options, code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */ });
71
110
  this.name = "SubprocessError";
72
111
  }
73
112
  };
74
113
  DependencyError = class extends AgentSDKError {
75
114
  packageName;
76
115
  constructor(packageName) {
77
- super(`${packageName} is not installed. Install it: npm install ${packageName}`);
116
+ super(`${packageName} is not installed. Install it: npm install ${packageName}`, {
117
+ code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */
118
+ });
78
119
  this.name = "DependencyError";
79
120
  this.packageName = packageName;
80
121
  }
81
122
  };
82
123
  AbortError = class extends AgentSDKError {
83
124
  constructor() {
84
- super("Agent run was aborted.");
125
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
85
126
  this.name = "AbortError";
86
127
  }
87
128
  };
88
129
  ToolExecutionError = class extends AgentSDKError {
89
130
  toolName;
90
131
  constructor(toolName, message, options) {
91
- super(`Tool "${toolName}" failed: ${message}`, options);
132
+ super(`Tool "${toolName}" failed: ${message}`, { ...options, code: "TOOL_EXECUTION" /* TOOL_EXECUTION */ });
92
133
  this.name = "ToolExecutionError";
93
134
  this.toolName = toolName;
94
135
  }
95
136
  };
96
- StructuredOutputError = class extends AgentSDKError {
97
- constructor(message, options) {
98
- super(`Structured output error: ${message}`, options);
99
- this.name = "StructuredOutputError";
137
+ ActivityTimeoutError = class extends AgentSDKError {
138
+ constructor(timeoutMs) {
139
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
140
+ code: "TIMEOUT" /* TIMEOUT */,
141
+ retryable: true
142
+ });
143
+ this.name = "ActivityTimeoutError";
100
144
  }
101
145
  };
102
146
  }
103
147
  });
104
148
 
105
- // src/types.ts
106
- function isToolDefinition(tool) {
107
- return "execute" in tool && typeof tool.execute === "function";
108
- }
109
- function isTextContent(content) {
110
- return typeof content === "string";
111
- }
112
- function isMultiPartContent(content) {
113
- return Array.isArray(content);
114
- }
149
+ // src/types/guards.ts
115
150
  function getTextContent(content) {
116
151
  if (typeof content === "string") return content;
117
152
  return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
118
153
  }
154
+ var init_guards = __esm({
155
+ "src/types/guards.ts"() {
156
+ }
157
+ });
158
+
159
+ // src/types/index.ts
119
160
  var init_types = __esm({
161
+ "src/types/index.ts"() {
162
+ init_errors();
163
+ init_guards();
164
+ }
165
+ });
166
+
167
+ // src/types.ts
168
+ var init_types2 = __esm({
120
169
  "src/types.ts"() {
170
+ init_types();
121
171
  }
122
172
  });
123
173
 
@@ -125,12 +175,15 @@ var init_types = __esm({
125
175
  var BaseAgent;
126
176
  var init_base_agent = __esm({
127
177
  "src/base-agent.ts"() {
178
+ init_errors2();
179
+ init_errors2();
128
180
  init_errors();
129
181
  BaseAgent = class {
130
182
  state = "idle";
131
183
  abortController = null;
132
184
  config;
133
185
  _cleanupExternalSignal = null;
186
+ _streamMiddleware = [];
134
187
  /** CLI session ID for persistent mode. Override in backends that support it. */
135
188
  get sessionId() {
136
189
  return void 0;
@@ -146,8 +199,11 @@ var init_base_agent = __esm({
146
199
  this.state = "running";
147
200
  try {
148
201
  const messages = [{ role: "user", content: prompt }];
149
- const result = await this.executeRun(messages, options, ac.signal);
150
- this.enrichAndNotifyUsage(result);
202
+ const result = await this.withRetry(
203
+ () => this.executeRun(messages, options, ac.signal),
204
+ options
205
+ );
206
+ this.enrichAndNotifyUsage(result, options);
151
207
  return result;
152
208
  } finally {
153
209
  this.cleanupRun();
@@ -159,8 +215,11 @@ var init_base_agent = __esm({
159
215
  const ac = this.createAbortController(options?.signal);
160
216
  this.state = "running";
161
217
  try {
162
- const result = await this.executeRun(messages, options, ac.signal);
163
- this.enrichAndNotifyUsage(result);
218
+ const result = await this.withRetry(
219
+ () => this.executeRun(messages, options, ac.signal),
220
+ options
221
+ );
222
+ this.enrichAndNotifyUsage(result, options);
164
223
  return result;
165
224
  } finally {
166
225
  this.cleanupRun();
@@ -173,13 +232,11 @@ var init_base_agent = __esm({
173
232
  this.state = "running";
174
233
  try {
175
234
  const messages = [{ role: "user", content: prompt }];
176
- const result = await this.executeRunStructured(
177
- messages,
178
- schema,
179
- options,
180
- ac.signal
235
+ const result = await this.withRetry(
236
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
237
+ options
181
238
  );
182
- this.enrichAndNotifyUsage(result);
239
+ this.enrichAndNotifyUsage(result, options);
183
240
  return result;
184
241
  } finally {
185
242
  this.cleanupRun();
@@ -192,8 +249,10 @@ var init_base_agent = __esm({
192
249
  this.state = "streaming";
193
250
  try {
194
251
  const messages = [{ role: "user", content: prompt }];
195
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
196
- yield* this.heartbeatStream(enriched);
252
+ yield* this.streamWithRetry(
253
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
254
+ options
255
+ );
197
256
  } finally {
198
257
  this.cleanupRun();
199
258
  }
@@ -204,12 +263,37 @@ var init_base_agent = __esm({
204
263
  const ac = this.createAbortController(options?.signal);
205
264
  this.state = "streaming";
206
265
  try {
207
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
208
- yield* this.heartbeatStream(enriched);
266
+ yield* this.streamWithRetry(
267
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
268
+ options
269
+ );
209
270
  } finally {
210
271
  this.cleanupRun();
211
272
  }
212
273
  }
274
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
275
+ addStreamMiddleware(middleware) {
276
+ this.guardDisposed();
277
+ this._streamMiddleware.push(middleware);
278
+ }
279
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
280
+ async *applyStreamPipeline(source, options, ac) {
281
+ let stream = this.enrichStream(source, options);
282
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
283
+ stream = this.heartbeatStream(stream);
284
+ if (this._streamMiddleware.length > 0) {
285
+ const ctx = {
286
+ model: options.model,
287
+ backend: this.backendName,
288
+ abortController: ac,
289
+ config: Object.freeze({ ...this.config })
290
+ };
291
+ for (const mw of this._streamMiddleware) {
292
+ stream = mw(stream, ctx);
293
+ }
294
+ }
295
+ yield* stream;
296
+ }
213
297
  abort() {
214
298
  if (this.abortController) {
215
299
  this.abortController.abort();
@@ -232,26 +316,109 @@ var init_base_agent = __esm({
232
316
  this.abort();
233
317
  this.state = "disposed";
234
318
  }
319
+ // ─── Retry Logic ─────────────────────────────────────────────
320
+ /** Check if an error should be retried given the retry configuration. */
321
+ isRetryableError(error, retry) {
322
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
323
+ return false;
324
+ }
325
+ if (AgentSDKError.is(error)) {
326
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
327
+ return retry.retryableErrors.includes(error.code);
328
+ }
329
+ if (error.retryable) return true;
330
+ if (error.code) return isRecoverableErrorCode(error.code);
331
+ }
332
+ return false;
333
+ }
334
+ /** Execute a function with retry logic per RetryConfig. */
335
+ async withRetry(fn, options) {
336
+ const retry = options?.retry;
337
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
338
+ return fn();
339
+ }
340
+ const maxRetries = retry.maxRetries;
341
+ const initialDelay = retry.initialDelayMs ?? 1e3;
342
+ const multiplier = retry.backoffMultiplier ?? 2;
343
+ let lastError;
344
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
345
+ try {
346
+ return await fn();
347
+ } catch (err) {
348
+ lastError = err;
349
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
350
+ throw err;
351
+ }
352
+ const delay = initialDelay * Math.pow(multiplier, attempt);
353
+ await new Promise((resolve) => setTimeout(resolve, delay));
354
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
355
+ throw err;
356
+ }
357
+ }
358
+ }
359
+ throw lastError;
360
+ }
361
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
362
+ async *streamWithRetry(factory, options) {
363
+ const retry = options?.retry;
364
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
365
+ yield* factory();
366
+ return;
367
+ }
368
+ const maxRetries = retry.maxRetries;
369
+ const initialDelay = retry.initialDelayMs ?? 1e3;
370
+ const multiplier = retry.backoffMultiplier ?? 2;
371
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
372
+ try {
373
+ const stream = factory();
374
+ const iterator = stream[Symbol.asyncIterator]();
375
+ const first = await iterator.next();
376
+ if (first.done) return;
377
+ yield first.value;
378
+ while (true) {
379
+ const next = await iterator.next();
380
+ if (next.done) break;
381
+ yield next.value;
382
+ }
383
+ return;
384
+ } catch (err) {
385
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
386
+ throw err;
387
+ }
388
+ const delay = initialDelay * Math.pow(multiplier, attempt);
389
+ await new Promise((resolve) => setTimeout(resolve, delay));
390
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
391
+ throw err;
392
+ }
393
+ }
394
+ }
395
+ }
396
+ // ─── CallOptions Resolution ──────────────────────────────────
397
+ /** Resolve tools to use for this call (per-call override > config default) */
398
+ resolveTools(options) {
399
+ return options?.tools ?? this.config.tools ?? [];
400
+ }
235
401
  // ─── Usage Enrichment ───────────────────────────────────────────
236
402
  /** Enrich result usage with model/backend and fire onUsage callback */
237
- enrichAndNotifyUsage(result) {
403
+ enrichAndNotifyUsage(result, options) {
238
404
  if (result.usage) {
239
405
  result.usage = {
240
406
  ...result.usage,
241
- model: this.config.model,
407
+ model: options.model,
242
408
  backend: this.backendName
243
409
  };
244
410
  this.callOnUsage(result.usage);
245
411
  }
246
412
  }
247
413
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
248
- async *enrichStream(source) {
414
+ async *enrichStream(source, options) {
415
+ const model = options.model;
249
416
  for await (const event of source) {
250
417
  if (event.type === "usage_update") {
251
418
  const usage = {
252
419
  promptTokens: event.promptTokens,
253
420
  completionTokens: event.completionTokens,
254
- model: this.config.model,
421
+ model,
255
422
  backend: this.backendName
256
423
  };
257
424
  this.callOnUsage(usage);
@@ -287,9 +454,9 @@ var init_base_agent = __esm({
287
454
  let heartbeatResolve = null;
288
455
  const timer = setInterval(() => {
289
456
  if (heartbeatResolve) {
290
- const resolve2 = heartbeatResolve;
457
+ const resolve = heartbeatResolve;
291
458
  heartbeatResolve = null;
292
- resolve2();
459
+ resolve();
293
460
  }
294
461
  }, interval);
295
462
  try {
@@ -297,8 +464,8 @@ var init_base_agent = __esm({
297
464
  if (!pendingEvent) {
298
465
  pendingEvent = iterator.next();
299
466
  }
300
- const heartbeatPromise = new Promise((resolve2) => {
301
- heartbeatResolve = resolve2;
467
+ const heartbeatPromise = new Promise((resolve) => {
468
+ heartbeatResolve = resolve;
302
469
  });
303
470
  const eventDone = pendingEvent.then(
304
471
  (r) => ({ kind: "event", result: r })
@@ -321,6 +488,35 @@ var init_base_agent = __esm({
321
488
  heartbeatResolve = null;
322
489
  }
323
490
  }
491
+ // ─── Activity Timeout ────────────────────────────────────────
492
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
493
+ * When timeoutMs is not set, passes through directly. */
494
+ async *activityTimeoutStream(source, timeoutMs, ac) {
495
+ if (!timeoutMs || timeoutMs <= 0) {
496
+ yield* source;
497
+ return;
498
+ }
499
+ const iterator = source[Symbol.asyncIterator]();
500
+ let timerId;
501
+ try {
502
+ while (true) {
503
+ const timeoutPromise = new Promise((_, reject) => {
504
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
505
+ });
506
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
507
+ clearTimeout(timerId);
508
+ if (result.done) break;
509
+ yield result.value;
510
+ }
511
+ } catch (err) {
512
+ if (err instanceof ActivityTimeoutError) {
513
+ ac.abort(err);
514
+ }
515
+ throw err;
516
+ } finally {
517
+ clearTimeout(timerId);
518
+ }
519
+ }
324
520
  // ─── Guards ───────────────────────────────────────────────────
325
521
  guardReentrancy() {
326
522
  if (this.state === "running" || this.state === "streaming") {
@@ -425,6 +621,66 @@ var init_schema = __esm({
425
621
  }
426
622
  });
427
623
 
624
+ // src/backends/shared.ts
625
+ function extractLastUserPrompt(messages) {
626
+ for (let i = messages.length - 1; i >= 0; i--) {
627
+ const msg = messages[i];
628
+ if (msg.role === "user") {
629
+ return getTextContent(msg.content);
630
+ }
631
+ }
632
+ return "";
633
+ }
634
+ function serializeToolCall(tc) {
635
+ const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
636
+ return ` Tool call: ${tc.name}(${args})`;
637
+ }
638
+ function serializeToolResult(tr) {
639
+ const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
640
+ const prefix = tr.isError ? "[ERROR] " : "";
641
+ return ` ${tr.name} \u2192 ${prefix}${result}`;
642
+ }
643
+ function buildContextualPrompt(messages) {
644
+ if (messages.length <= 1) {
645
+ return extractLastUserPrompt(messages);
646
+ }
647
+ const history = messages.slice(0, -1).map((msg) => {
648
+ if (msg.role === "user") {
649
+ return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
650
+ }
651
+ if (msg.role === "tool" && msg.toolResults) {
652
+ const results = msg.toolResults.map(serializeToolResult).join("\n");
653
+ return `Tool results:
654
+ ${results}`;
655
+ }
656
+ if (msg.role === "assistant") {
657
+ const parts = [];
658
+ const thinking = msg.thinking;
659
+ if (thinking) {
660
+ parts.push(`[reasoning: ${thinking}]`);
661
+ }
662
+ const text2 = msg.content ? getTextContent(msg.content) : "";
663
+ if (text2) parts.push(text2);
664
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
665
+ parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
666
+ }
667
+ return `Assistant: ${parts.join("\n")}`;
668
+ }
669
+ const text = msg.content ? getTextContent(msg.content) : "";
670
+ return `${msg.role}: ${text}`;
671
+ }).join("\n");
672
+ const lastPrompt = extractLastUserPrompt(messages);
673
+ return `Conversation history:
674
+ ${history}
675
+
676
+ User: ${lastPrompt}`;
677
+ }
678
+ var init_shared = __esm({
679
+ "src/backends/shared.ts"() {
680
+ init_types2();
681
+ }
682
+ });
683
+
428
684
  // src/backends/copilot.ts
429
685
  var copilot_exports = {};
430
686
  __export(copilot_exports, {
@@ -433,10 +689,9 @@ __export(copilot_exports, {
433
689
  createCopilotService: () => createCopilotService
434
690
  });
435
691
  async function loadSDK() {
436
- if (sdkModule) return sdkModule;
692
+ if (_sdkMock) return _sdkMock;
437
693
  try {
438
- sdkModule = await import('@github/copilot-sdk');
439
- return sdkModule;
694
+ return await import('@github/copilot-sdk');
440
695
  } catch {
441
696
  throw new SubprocessError(
442
697
  "@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
@@ -444,10 +699,10 @@ async function loadSDK() {
444
699
  }
445
700
  }
446
701
  function _injectSDK(mock) {
447
- sdkModule = mock;
702
+ _sdkMock = mock;
448
703
  }
449
704
  function _resetSDK() {
450
- sdkModule = null;
705
+ _sdkMock = null;
451
706
  }
452
707
  function mapToolsToSDK(tools) {
453
708
  return tools.map((tool) => ({
@@ -467,17 +722,6 @@ function convertParameters(params) {
467
722
  }
468
723
  return params;
469
724
  }
470
- async function mapToolsToSDKAsync(tools) {
471
- return tools.map((tool) => ({
472
- name: tool.name,
473
- description: tool.description,
474
- parameters: convertParameters(tool.parameters),
475
- handler: async (args) => {
476
- const result = await tool.execute(args);
477
- return typeof result === "string" ? result : JSON.stringify(result);
478
- }
479
- }));
480
- }
481
725
  function buildPermissionHandler(config) {
482
726
  const onPermission = config.supervisor?.onPermission;
483
727
  if (!onPermission) {
@@ -492,6 +736,7 @@ function buildPermissionHandler(config) {
492
736
  const unifiedRequest = {
493
737
  toolName,
494
738
  toolArgs: { ...request },
739
+ toolCallId: request.toolCallId,
495
740
  rawSDKRequest: request
496
741
  };
497
742
  const ac = new AbortController();
@@ -596,15 +841,21 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
596
841
  };
597
842
  case "session.error":
598
843
  console.error("[copilot] mapSessionEvent error:", JSON.stringify(data));
599
- return {
600
- type: "error",
601
- error: String(data.message ?? "Unknown error"),
602
- recoverable: false
603
- };
844
+ {
845
+ const errorMsg = String(data.message ?? "Unknown error");
846
+ const code = classifyAgentError(errorMsg);
847
+ return {
848
+ type: "error",
849
+ error: errorMsg,
850
+ recoverable: isRecoverableErrorCode(code),
851
+ code
852
+ };
853
+ }
604
854
  case "assistant.message": {
605
855
  const doneEvent = {
606
856
  type: "done",
607
- finalOutput: data.content ? String(data.content) : null
857
+ finalOutput: null,
858
+ streamed: true
608
859
  };
609
860
  if (thinkingTracker.endThinking()) {
610
861
  return [{ type: "thinking_end" }, doneEvent];
@@ -615,66 +866,13 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
615
866
  return null;
616
867
  }
617
868
  }
618
- function extractLastUserPrompt(messages) {
619
- for (let i = messages.length - 1; i >= 0; i--) {
620
- const msg = messages[i];
621
- if (msg.role === "user") {
622
- return getTextContent(msg.content);
623
- }
624
- }
625
- return "";
626
- }
627
- function serializeToolCall(tc) {
628
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
629
- return ` Tool call: ${tc.name}(${args})`;
630
- }
631
- function serializeToolResult(tr) {
632
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
633
- const prefix = tr.isError ? "[ERROR] " : "";
634
- return ` ${tr.name} \u2192 ${prefix}${result}`;
635
- }
636
- function buildContextualPrompt(messages) {
637
- if (messages.length <= 1) {
638
- return extractLastUserPrompt(messages);
639
- }
640
- const history = messages.slice(0, -1).map((msg) => {
641
- if (msg.role === "user") {
642
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
643
- }
644
- if (msg.role === "tool" && msg.toolResults) {
645
- const results = msg.toolResults.map(serializeToolResult).join("\n");
646
- return `Tool results:
647
- ${results}`;
648
- }
649
- if (msg.role === "assistant") {
650
- const parts = [];
651
- const thinking = msg.thinking;
652
- if (thinking) {
653
- parts.push(`[reasoning: ${thinking}]`);
654
- }
655
- const text2 = msg.content ? getTextContent(msg.content) : "";
656
- if (text2) parts.push(text2);
657
- if (msg.toolCalls && msg.toolCalls.length > 0) {
658
- parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
659
- }
660
- return `Assistant: ${parts.join("\n")}`;
661
- }
662
- const text = msg.content ? getTextContent(msg.content) : "";
663
- return `${msg.role}: ${text}`;
664
- }).join("\n");
665
- const lastPrompt = extractLastUserPrompt(messages);
666
- return `Conversation history:
667
- ${history}
668
-
669
- User: ${lastPrompt}`;
670
- }
671
869
  function withTimeout(promise, ms, message) {
672
- return new Promise((resolve2, reject) => {
870
+ return new Promise((resolve, reject) => {
673
871
  const timer = setTimeout(() => reject(new SubprocessError(message)), ms);
674
872
  promise.then(
675
873
  (val) => {
676
874
  clearTimeout(timer);
677
- resolve2(val);
875
+ resolve(val);
678
876
  },
679
877
  (err) => {
680
878
  clearTimeout(timer);
@@ -686,14 +884,15 @@ function withTimeout(promise, ms, message) {
686
884
  function createCopilotService(options) {
687
885
  return new CopilotAgentService(options);
688
886
  }
689
- var sdkModule, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
887
+ var _sdkMock, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
690
888
  var init_copilot = __esm({
691
889
  "src/backends/copilot.ts"() {
692
- init_types();
890
+ init_types2();
693
891
  init_base_agent();
694
- init_errors();
892
+ init_errors2();
695
893
  init_schema();
696
- sdkModule = null;
894
+ init_shared();
895
+ _sdkMock = null;
697
896
  ToolCallTracker = class {
698
897
  map = /* @__PURE__ */ new Map();
699
898
  trackStart(toolCallId, toolName, args) {
@@ -741,6 +940,7 @@ var init_copilot = __esm({
741
940
  isPersistent;
742
941
  persistentSession = null;
743
942
  _sessionId;
943
+ _persistentModel;
744
944
  activeSession = null;
745
945
  _resumeSessionId;
746
946
  _toolsReady = null;
@@ -759,15 +959,15 @@ var init_copilot = __esm({
759
959
  },
760
960
  onPermissionRequest: buildPermissionHandler(config),
761
961
  onUserInputRequest: buildUserInputHandler(config),
762
- ...config.availableTools?.length ? { availableTools: config.availableTools } : {}
962
+ ...config.availableTools ? { availableTools: config.availableTools } : {}
763
963
  };
764
964
  this._toolsReady = this._initToolsAsync(config);
765
965
  this._resumeSessionId = resumeSessionId;
766
966
  }
767
- /** Pre-convert Zod schemas to JSON Schema asynchronously.
967
+ /** Pre-convert Zod schemas to JSON Schema.
768
968
  * Updates sdkTools and sessionConfig.tools before first session creation. */
769
969
  async _initToolsAsync(config) {
770
- this.sdkTools = await mapToolsToSDKAsync(config.tools ?? []);
970
+ this.sdkTools = mapToolsToSDK(config.tools ?? []);
771
971
  this.sessionConfig.tools = this.sdkTools;
772
972
  }
773
973
  get sessionId() {
@@ -791,47 +991,63 @@ var init_copilot = __esm({
791
991
  });
792
992
  this.persistentSession = null;
793
993
  this._sessionId = void 0;
994
+ this._persistentModel = void 0;
794
995
  }
795
996
  }
796
- async getOrCreateSession(streaming) {
997
+ async getOrCreateSession(streaming, options) {
797
998
  if (this.isPersistent && this.persistentSession) {
798
- return { session: this.persistentSession, isNew: false };
999
+ if (options.model !== this._persistentModel) {
1000
+ this.persistentSession.destroy().catch(() => {
1001
+ });
1002
+ this.persistentSession = null;
1003
+ this._sessionId = void 0;
1004
+ } else {
1005
+ return { session: this.persistentSession, isNew: false };
1006
+ }
799
1007
  }
800
1008
  if (this._toolsReady) {
801
1009
  await this._toolsReady;
802
1010
  this._toolsReady = null;
803
1011
  }
1012
+ const sessionConfig = { ...this.sessionConfig };
1013
+ sessionConfig.model = options.model;
1014
+ const resolvedTools = this.resolveTools(options);
1015
+ if (options?.tools) {
1016
+ sessionConfig.tools = mapToolsToSDK(resolvedTools);
1017
+ }
804
1018
  const client = await this.getClient();
805
1019
  if (this._resumeSessionId) {
806
1020
  const storedId = this._resumeSessionId;
807
1021
  this._resumeSessionId = void 0;
808
1022
  try {
809
1023
  const session2 = await client.resumeSession(storedId, {
810
- ...this.sessionConfig,
1024
+ ...sessionConfig,
811
1025
  streaming: this.isPersistent ? true : streaming
812
1026
  });
813
1027
  if (this.isPersistent) {
814
1028
  this.persistentSession = session2;
815
1029
  this._sessionId = session2.sessionId;
1030
+ this._persistentModel = options.model;
816
1031
  }
817
1032
  return { session: session2, isNew: false };
818
1033
  } catch {
819
1034
  }
820
1035
  }
821
1036
  const session = await client.createSession({
822
- ...this.sessionConfig,
1037
+ ...sessionConfig,
823
1038
  streaming: this.isPersistent ? true : streaming
824
1039
  });
825
1040
  if (this.isPersistent) {
826
1041
  this.persistentSession = session;
827
1042
  this._sessionId = session.sessionId;
1043
+ this._persistentModel = options.model;
828
1044
  }
829
1045
  return { session, isNew: true };
830
1046
  }
831
1047
  // ─── executeRun ─────────────────────────────────────────────────
832
- async executeRun(messages, _options, signal) {
1048
+ async executeRun(messages, options, signal) {
833
1049
  this.checkAbort(signal);
834
- const { session, isNew: isNewSession } = await this.getOrCreateSession(false);
1050
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(false, options);
835
1051
  this.activeSession = session;
836
1052
  const prompt = this.isPersistent && !isNewSession ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
837
1053
  const tracker = new ToolCallTracker();
@@ -928,9 +1144,9 @@ You MUST respond with ONLY valid JSON matching this schema:
928
1144
  };
929
1145
  }
930
1146
  // ─── executeStream ──────────────────────────────────────────────
931
- async *executeStream(messages, _options, signal) {
1147
+ async *executeStream(messages, options, signal) {
932
1148
  this.checkAbort(signal);
933
- const { session, isNew: isNewSession } = await this.getOrCreateSession(true);
1149
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(true, options);
934
1150
  this.activeSession = session;
935
1151
  if (isNewSession) {
936
1152
  yield this.emitSessionInfo(session.sessionId);
@@ -947,8 +1163,8 @@ You MUST respond with ONLY valid JSON matching this schema:
947
1163
  notify = null;
948
1164
  }
949
1165
  };
950
- const waitForItem = () => new Promise((resolve2) => {
951
- notify = resolve2;
1166
+ const waitForItem = () => new Promise((resolve) => {
1167
+ notify = resolve;
952
1168
  });
953
1169
  const unsubscribe = session.on((event) => {
954
1170
  const mapped = mapSessionEvent(event, tracker, thinkingTracker);
@@ -1042,7 +1258,11 @@ You MUST respond with ONLY valid JSON matching this schema:
1042
1258
  githubToken: this.options.githubToken,
1043
1259
  useLoggedInUser: this.options.useLoggedInUser ?? !this.options.githubToken,
1044
1260
  ...this.options.cliArgs ? { cliArgs: this.options.cliArgs } : {},
1045
- ...this.options.env ? { env: { ...process.env, ...this.options.env } } : {}
1261
+ env: {
1262
+ ...process.env,
1263
+ ...this.options.githubToken ? { GITHUB_TOKEN: this.options.githubToken } : {},
1264
+ ...this.options.env
1265
+ }
1046
1266
  });
1047
1267
  const startupTimeout = this.options.startupTimeoutMs ?? 3e4;
1048
1268
  await withTimeout(client.start(), startupTimeout, "CLI startup timed out");
@@ -1076,7 +1296,10 @@ You MUST respond with ONLY valid JSON matching this schema:
1076
1296
  return models.map((m) => ({
1077
1297
  id: m.id,
1078
1298
  name: m.name,
1079
- provider: "copilot"
1299
+ provider: "copilot",
1300
+ ...m.capabilities?.limits?.max_context_window_tokens != null && {
1301
+ contextWindow: m.capabilities.limits.max_context_window_tokens
1302
+ }
1080
1303
  }));
1081
1304
  }
1082
1305
  async validate() {
@@ -1129,10 +1352,9 @@ function stripMcpPrefix(name) {
1129
1352
  return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
1130
1353
  }
1131
1354
  async function loadSDK2() {
1132
- if (sdkModule2) return sdkModule2;
1355
+ if (_sdkMock2) return _sdkMock2;
1133
1356
  try {
1134
- sdkModule2 = await import('@anthropic-ai/claude-agent-sdk');
1135
- return sdkModule2;
1357
+ return await import('@anthropic-ai/claude-agent-sdk');
1136
1358
  } catch {
1137
1359
  throw new SubprocessError(
1138
1360
  "@anthropic-ai/claude-agent-sdk is not installed. Install it: npm install @anthropic-ai/claude-agent-sdk"
@@ -1140,13 +1362,32 @@ async function loadSDK2() {
1140
1362
  }
1141
1363
  }
1142
1364
  function _injectSDK2(mock) {
1143
- sdkModule2 = mock;
1365
+ _sdkMock2 = mock;
1144
1366
  }
1145
1367
  function _resetSDK2() {
1146
- sdkModule2 = null;
1368
+ _sdkMock2 = null;
1369
+ }
1370
+ function normalizeAskUserInput(args) {
1371
+ if (typeof args.question === "string") {
1372
+ return {
1373
+ question: args.question,
1374
+ choices: Array.isArray(args.choices) ? args.choices : void 0,
1375
+ allowFreeform: args.allowFreeform !== false
1376
+ };
1377
+ }
1378
+ const questions = args.questions;
1379
+ if (questions && questions.length > 0) {
1380
+ const first = questions[0];
1381
+ return {
1382
+ question: first.question,
1383
+ choices: first.options?.map((o) => o.label),
1384
+ allowFreeform: true
1385
+ };
1386
+ }
1387
+ return { question: JSON.stringify(args), allowFreeform: true };
1147
1388
  }
1148
- function buildMcpServer(sdk, tools, toolResultCapture) {
1149
- if (tools.length === 0) return void 0;
1389
+ function buildMcpServer(sdk, tools, toolResultCapture, onAskUser) {
1390
+ if (tools.length === 0 && !onAskUser) return void 0;
1150
1391
  const mcpTools = tools.map((tool) => {
1151
1392
  const zodSchema = tool.parameters;
1152
1393
  const inputSchema = zodSchema.shape ?? zodToJsonSchema(tool.parameters);
@@ -1170,6 +1411,39 @@ function buildMcpServer(sdk, tools, toolResultCapture) {
1170
1411
  }
1171
1412
  );
1172
1413
  });
1414
+ if (onAskUser) {
1415
+ const askUserTool = sdk.tool(
1416
+ "ask_user",
1417
+ "Ask the user a question and wait for their response",
1418
+ {
1419
+ question: { type: "string", description: "The question to ask the user" },
1420
+ choices: {
1421
+ type: "array",
1422
+ items: { type: "string" },
1423
+ description: "Optional list of choices for multiple choice"
1424
+ },
1425
+ questions: {
1426
+ type: "array",
1427
+ items: {
1428
+ type: "object",
1429
+ properties: {
1430
+ question: { type: "string" },
1431
+ options: { type: "array", items: { type: "object", properties: { label: { type: "string" } } } }
1432
+ }
1433
+ },
1434
+ description: "Alternative nested question format"
1435
+ }
1436
+ },
1437
+ async (args) => {
1438
+ const normalized = normalizeAskUserInput(args);
1439
+ const response = await onAskUser(normalized, AbortSignal.timeout(3e5));
1440
+ return {
1441
+ content: [{ type: "text", text: response.answer }]
1442
+ };
1443
+ }
1444
+ );
1445
+ mcpTools.push(askUserTool);
1446
+ }
1173
1447
  return sdk.createSdkMcpServer({
1174
1448
  name: MCP_SERVER_NAME,
1175
1449
  version: "1.0.0",
@@ -1219,6 +1493,7 @@ function buildCanUseTool(config) {
1219
1493
  const unifiedRequest = {
1220
1494
  toolName,
1221
1495
  toolArgs: input,
1496
+ toolCallId: options.toolUseID,
1222
1497
  suggestedScope: extractSuggestedScope(options.suggestions),
1223
1498
  rawSDKRequest: { toolName, input, ...options }
1224
1499
  };
@@ -1271,6 +1546,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1271
1546
  if (block.type === "tool_use") {
1272
1547
  const toolCallId = String(block.id ?? "");
1273
1548
  const toolName = stripMcpPrefix(block.name ?? "unknown");
1549
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1274
1550
  if (toolCallTracker) {
1275
1551
  toolCallTracker.trackStart(toolCallId, toolName);
1276
1552
  }
@@ -1294,6 +1570,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1294
1570
  case "tool_use_summary": {
1295
1571
  const summary = msg.summary;
1296
1572
  const toolName = stripMcpPrefix(msg.tool_name ?? "unknown");
1573
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) return null;
1297
1574
  const precedingIds = msg.preceding_tool_use_ids;
1298
1575
  let toolCallId = "";
1299
1576
  if (precedingIds && precedingIds.length > 0) {
@@ -1347,10 +1624,13 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1347
1624
  }
1348
1625
  if (msg.is_error) {
1349
1626
  const r = msg;
1627
+ const errorMsg = r.errors?.join("; ") ?? "Unknown error";
1628
+ const code = classifyAgentError(errorMsg);
1350
1629
  return {
1351
1630
  type: "error",
1352
- error: r.errors?.join("; ") ?? "Unknown error",
1353
- recoverable: false
1631
+ error: errorMsg,
1632
+ recoverable: isRecoverableErrorCode(code),
1633
+ code
1354
1634
  };
1355
1635
  }
1356
1636
  return null;
@@ -1359,72 +1639,21 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1359
1639
  return null;
1360
1640
  }
1361
1641
  }
1362
- function extractLastUserPrompt2(messages) {
1363
- for (let i = messages.length - 1; i >= 0; i--) {
1364
- const msg = messages[i];
1365
- if (msg.role === "user") {
1366
- return getTextContent(msg.content);
1367
- }
1368
- }
1369
- return "";
1370
- }
1371
- function serializeToolCall2(tc) {
1372
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
1373
- return ` Tool call: ${tc.name}(${args})`;
1374
- }
1375
- function serializeToolResult2(tr) {
1376
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
1377
- const prefix = tr.isError ? "[ERROR] " : "";
1378
- return ` ${tr.name} \u2192 ${prefix}${result}`;
1642
+ function createClaudeService(options) {
1643
+ return new ClaudeAgentService(options);
1379
1644
  }
1380
- function buildContextualPrompt2(messages) {
1381
- if (messages.length <= 1) {
1382
- return extractLastUserPrompt2(messages);
1383
- }
1384
- const history = messages.slice(0, -1).map((msg) => {
1385
- if (msg.role === "user") {
1386
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
1387
- }
1388
- if (msg.role === "tool" && msg.toolResults) {
1389
- const results = msg.toolResults.map(serializeToolResult2).join("\n");
1390
- return `Tool results:
1391
- ${results}`;
1392
- }
1393
- if (msg.role === "assistant") {
1394
- const parts = [];
1395
- const thinking = msg.thinking;
1396
- if (thinking) {
1397
- parts.push(`[reasoning: ${thinking}]`);
1398
- }
1399
- const text2 = msg.content ? getTextContent(msg.content) : "";
1400
- if (text2) parts.push(text2);
1401
- if (msg.toolCalls && msg.toolCalls.length > 0) {
1402
- parts.push(msg.toolCalls.map(serializeToolCall2).join("\n"));
1403
- }
1404
- return `Assistant: ${parts.join("\n")}`;
1405
- }
1406
- const text = msg.content ? getTextContent(msg.content) : "";
1407
- return `${msg.role}: ${text}`;
1408
- }).join("\n");
1409
- const lastPrompt = extractLastUserPrompt2(messages);
1410
- return `Conversation history:
1411
- ${history}
1412
-
1413
- User: ${lastPrompt}`;
1414
- }
1415
- function createClaudeService(options) {
1416
- return new ClaudeAgentService(options);
1417
- }
1418
- var MCP_SERVER_NAME, MCP_TOOL_PREFIX, sdkModule2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1645
+ var MCP_SERVER_NAME, MCP_TOOL_PREFIX, CLAUDE_INTERNAL_TOOL_NAMES, _sdkMock2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1419
1646
  var init_claude = __esm({
1420
1647
  "src/backends/claude.ts"() {
1421
- init_types();
1648
+ init_types2();
1422
1649
  init_base_agent();
1423
- init_errors();
1650
+ init_errors2();
1424
1651
  init_schema();
1652
+ init_shared();
1425
1653
  MCP_SERVER_NAME = "agent-sdk-tools";
1426
1654
  MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
1427
- sdkModule2 = null;
1655
+ CLAUDE_INTERNAL_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion"]);
1656
+ _sdkMock2 = null;
1428
1657
  ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
1429
1658
  ANTHROPIC_API_VERSION = "2023-06-01";
1430
1659
  ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
@@ -1469,11 +1698,6 @@ var init_claude = __esm({
1469
1698
  if (options.resumeSessionId) {
1470
1699
  this._sessionId = options.resumeSessionId;
1471
1700
  }
1472
- if (config.supervisor?.onAskUser) {
1473
- console.warn(
1474
- "[agent-sdk/claude] supervisor.onAskUser is not supported by the Claude CLI backend. User interaction requests from the model will not be forwarded."
1475
- );
1476
- }
1477
1701
  }
1478
1702
  get sessionId() {
1479
1703
  return this._sessionId;
@@ -1497,12 +1721,12 @@ var init_claude = __esm({
1497
1721
  const transcriptPath = home ? `${home}/.claude/projects/.session/sessions/${sessionId}/conversation.jsonl` : void 0;
1498
1722
  return { type: "session_info", sessionId, transcriptPath, backend: "claude" };
1499
1723
  }
1500
- buildQueryOptions(signal) {
1724
+ buildQueryOptions(signal, options) {
1501
1725
  const ac = new AbortController();
1502
1726
  signal.addEventListener("abort", () => ac.abort(), { once: true });
1503
1727
  const opts = {
1504
1728
  abortController: ac,
1505
- model: this.config.model,
1729
+ model: options.model,
1506
1730
  maxTurns: this.options.maxTurns,
1507
1731
  cwd: this.options.workingDirectory,
1508
1732
  pathToClaudeCodeExecutable: this.options.cliPath,
@@ -1532,25 +1756,85 @@ var init_claude = __esm({
1532
1756
  return opts;
1533
1757
  }
1534
1758
  async buildMcpConfig(opts, toolResultCapture) {
1535
- if (this.tools.length === 0) return opts;
1759
+ const onAskUser = this.config.supervisor?.onAskUser;
1760
+ if (this.tools.length === 0 && !onAskUser) return opts;
1536
1761
  const sdk = await loadSDK2();
1537
- const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture);
1762
+ const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture, onAskUser);
1538
1763
  if (mcpServer) {
1539
1764
  opts.mcpServers = {
1540
1765
  [MCP_SERVER_NAME]: mcpServer
1541
1766
  };
1542
1767
  const mcpToolNames = this.tools.map((t) => mcpToolName(t.name));
1768
+ if (onAskUser) {
1769
+ mcpToolNames.push(mcpToolName("ask_user"));
1770
+ }
1543
1771
  opts.allowedTools = [...opts.allowedTools ?? [], ...mcpToolNames];
1544
1772
  }
1773
+ if (onAskUser) {
1774
+ opts.disallowedTools = [...opts.disallowedTools ?? [], "AskUserQuestion"];
1775
+ }
1545
1776
  return opts;
1546
1777
  }
1778
+ // ─── Retry Helpers (shared across executeRun/RunStructured/Stream) ──
1779
+ /** Setup a retry query: clear session, rebuild with full history */
1780
+ async prepareRetryQuery(sdk, messages, signal, options, toolResultCapture, modifyOpts) {
1781
+ this.clearPersistentSession();
1782
+ const retryPrompt = buildContextualPrompt(messages);
1783
+ let retryOpts = this.buildQueryOptions(signal, options);
1784
+ toolResultCapture.clear();
1785
+ retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1786
+ modifyOpts?.(retryOpts);
1787
+ const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1788
+ this.activeQuery = retryQ;
1789
+ return retryQ;
1790
+ }
1791
+ /** Extract tool_use blocks from an assistant SDK message into toolCalls array */
1792
+ collectToolCallsFromMessage(msg, toolCalls, toolResultCapture) {
1793
+ if (msg.type !== "assistant") return;
1794
+ const betaMessage = msg.message;
1795
+ if (!betaMessage?.content) return;
1796
+ for (const block of betaMessage.content) {
1797
+ if (block.type === "tool_use") {
1798
+ const toolName = stripMcpPrefix(block.name ?? "unknown");
1799
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1800
+ toolCalls.push({
1801
+ toolName,
1802
+ args: block.input ?? {},
1803
+ result: toolResultCapture.get(toolName) ?? null,
1804
+ approved: true
1805
+ });
1806
+ }
1807
+ }
1808
+ }
1809
+ /** Back-fill tool results from capture map on summary/result messages */
1810
+ backfillToolResults(msg, toolCalls, toolResultCapture) {
1811
+ if (msg.type !== "tool_use_summary" && msg.type !== "result") return;
1812
+ for (const tc of toolCalls) {
1813
+ if (tc.result === null) {
1814
+ const captured = toolResultCapture.get(tc.toolName);
1815
+ if (captured !== void 0) tc.result = captured;
1816
+ }
1817
+ }
1818
+ }
1819
+ /** Wrap retry inner loop with shared error handling */
1820
+ async withRetryErrorHandling(signal, fn) {
1821
+ try {
1822
+ return await fn();
1823
+ } catch (retryError) {
1824
+ if (this.isPersistent) this.clearPersistentSession();
1825
+ if (signal.aborted) throw new AbortError();
1826
+ throw retryError;
1827
+ } finally {
1828
+ this.activeQuery = null;
1829
+ }
1830
+ }
1547
1831
  // ─── executeRun ─────────────────────────────────────────────────
1548
- async executeRun(messages, _options, signal) {
1832
+ async executeRun(messages, options, signal) {
1549
1833
  this.checkAbort(signal);
1550
1834
  const sdk = await loadSDK2();
1551
1835
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1552
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1553
- let opts = this.buildQueryOptions(signal);
1836
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1837
+ let opts = this.buildQueryOptions(signal, options);
1554
1838
  const toolResultCapture = /* @__PURE__ */ new Map();
1555
1839
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1556
1840
  const q = sdk.query({ prompt, options: opts });
@@ -1560,30 +1844,8 @@ var init_claude = __esm({
1560
1844
  let usage;
1561
1845
  try {
1562
1846
  for await (const msg of q) {
1563
- if (msg.type === "assistant") {
1564
- const betaMessage = msg.message;
1565
- if (betaMessage?.content) {
1566
- for (const block of betaMessage.content) {
1567
- if (block.type === "tool_use") {
1568
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1569
- toolCalls.push({
1570
- toolName,
1571
- args: block.input ?? {},
1572
- result: toolResultCapture.get(toolName) ?? null,
1573
- approved: true
1574
- });
1575
- }
1576
- }
1577
- }
1578
- }
1579
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1580
- for (const tc of toolCalls) {
1581
- if (tc.result === null) {
1582
- const captured = toolResultCapture.get(tc.toolName);
1583
- if (captured !== void 0) tc.result = captured;
1584
- }
1585
- }
1586
- }
1847
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1848
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1587
1849
  if (msg.type === "result") {
1588
1850
  if (msg.subtype === "success") {
1589
1851
  const r = msg;
@@ -1603,41 +1865,13 @@ var init_claude = __esm({
1603
1865
  } catch (e) {
1604
1866
  if (signal.aborted) throw new AbortError();
1605
1867
  if (isResuming && this.isPersistent) {
1606
- this.clearPersistentSession();
1607
- const retryPrompt = buildContextualPrompt2(messages);
1608
- let retryOpts = this.buildQueryOptions(signal);
1609
- toolResultCapture.clear();
1610
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1611
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1612
- this.activeQuery = retryQ;
1868
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1613
1869
  toolCalls.length = 0;
1614
1870
  output = null;
1615
- try {
1871
+ return this.withRetryErrorHandling(signal, async () => {
1616
1872
  for await (const msg of retryQ) {
1617
- if (msg.type === "assistant") {
1618
- const betaMessage = msg.message;
1619
- if (betaMessage?.content) {
1620
- for (const block of betaMessage.content) {
1621
- if (block.type === "tool_use") {
1622
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1623
- toolCalls.push({
1624
- toolName,
1625
- args: block.input ?? {},
1626
- result: toolResultCapture.get(toolName) ?? null,
1627
- approved: true
1628
- });
1629
- }
1630
- }
1631
- }
1632
- }
1633
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1634
- for (const tc of toolCalls) {
1635
- if (tc.result === null) {
1636
- const captured = toolResultCapture.get(tc.toolName);
1637
- if (captured !== void 0) tc.result = captured;
1638
- }
1639
- }
1640
- }
1873
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1874
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1641
1875
  if (msg.type === "result") {
1642
1876
  if (msg.subtype === "success") {
1643
1877
  const r = msg;
@@ -1654,23 +1888,17 @@ var init_claude = __esm({
1654
1888
  }
1655
1889
  }
1656
1890
  }
1657
- } catch (retryError) {
1658
- if (this.isPersistent) this.clearPersistentSession();
1659
- if (signal.aborted) throw new AbortError();
1660
- throw retryError;
1661
- } finally {
1662
- this.activeQuery = null;
1663
- }
1664
- return {
1665
- output,
1666
- structuredOutput: void 0,
1667
- toolCalls,
1668
- messages: [
1669
- ...messages,
1670
- ...output !== null ? [{ role: "assistant", content: output }] : []
1671
- ],
1672
- usage
1673
- };
1891
+ return {
1892
+ output,
1893
+ structuredOutput: void 0,
1894
+ toolCalls,
1895
+ messages: [
1896
+ ...messages,
1897
+ ...output !== null ? [{ role: "assistant", content: output }] : []
1898
+ ],
1899
+ usage
1900
+ };
1901
+ });
1674
1902
  }
1675
1903
  if (this.isPersistent) this.clearPersistentSession();
1676
1904
  throw e;
@@ -1689,12 +1917,12 @@ var init_claude = __esm({
1689
1917
  };
1690
1918
  }
1691
1919
  // ─── executeRunStructured ───────────────────────────────────────
1692
- async executeRunStructured(messages, schema, _options, signal) {
1920
+ async executeRunStructured(messages, schema, options, signal) {
1693
1921
  this.checkAbort(signal);
1694
1922
  const sdk = await loadSDK2();
1695
1923
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1696
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1697
- let opts = this.buildQueryOptions(signal);
1924
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1925
+ let opts = this.buildQueryOptions(signal, options);
1698
1926
  const toolResultCapture = /* @__PURE__ */ new Map();
1699
1927
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1700
1928
  const jsonSchema = zodToJsonSchema(schema.schema);
@@ -1710,30 +1938,8 @@ var init_claude = __esm({
1710
1938
  let usage;
1711
1939
  try {
1712
1940
  for await (const msg of q) {
1713
- if (msg.type === "assistant") {
1714
- const betaMessage = msg.message;
1715
- if (betaMessage?.content) {
1716
- for (const block of betaMessage.content) {
1717
- if (block.type === "tool_use") {
1718
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1719
- toolCalls.push({
1720
- toolName,
1721
- args: block.input ?? {},
1722
- result: toolResultCapture.get(toolName) ?? null,
1723
- approved: true
1724
- });
1725
- }
1726
- }
1727
- }
1728
- }
1729
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1730
- for (const tc of toolCalls) {
1731
- if (tc.result === null) {
1732
- const captured = toolResultCapture.get(tc.toolName);
1733
- if (captured !== void 0) tc.result = captured;
1734
- }
1735
- }
1736
- }
1941
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1942
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1737
1943
  if (msg.type === "result" && msg.subtype === "success") {
1738
1944
  const r = msg;
1739
1945
  output = r.result;
@@ -1768,46 +1974,23 @@ var init_claude = __esm({
1768
1974
  } catch (e) {
1769
1975
  if (signal.aborted) throw new AbortError();
1770
1976
  if (isResuming && this.isPersistent) {
1771
- this.clearPersistentSession();
1772
- const retryPrompt = buildContextualPrompt2(messages);
1773
- let retryOpts = this.buildQueryOptions(signal);
1774
- toolResultCapture.clear();
1775
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1776
- retryOpts.outputFormat = {
1777
- type: "json_schema",
1778
- schema: jsonSchema
1779
- };
1780
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1781
- this.activeQuery = retryQ;
1977
+ const retryQ = await this.prepareRetryQuery(
1978
+ sdk,
1979
+ messages,
1980
+ signal,
1981
+ options,
1982
+ toolResultCapture,
1983
+ (opts2) => {
1984
+ opts2.outputFormat = { type: "json_schema", schema: jsonSchema };
1985
+ }
1986
+ );
1782
1987
  toolCalls.length = 0;
1783
1988
  output = null;
1784
1989
  structuredOutput = void 0;
1785
- try {
1990
+ return this.withRetryErrorHandling(signal, async () => {
1786
1991
  for await (const msg of retryQ) {
1787
- if (msg.type === "assistant") {
1788
- const betaMessage = msg.message;
1789
- if (betaMessage?.content) {
1790
- for (const block of betaMessage.content) {
1791
- if (block.type === "tool_use") {
1792
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1793
- toolCalls.push({
1794
- toolName,
1795
- args: block.input ?? {},
1796
- result: toolResultCapture.get(toolName) ?? null,
1797
- approved: true
1798
- });
1799
- }
1800
- }
1801
- }
1802
- }
1803
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1804
- for (const tc of toolCalls) {
1805
- if (tc.result === null) {
1806
- const captured = toolResultCapture.get(tc.toolName);
1807
- if (captured !== void 0) tc.result = captured;
1808
- }
1809
- }
1810
- }
1992
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1993
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1811
1994
  if (msg.type === "result" && msg.subtype === "success") {
1812
1995
  const r = msg;
1813
1996
  output = r.result;
@@ -1839,23 +2022,17 @@ var init_claude = __esm({
1839
2022
  );
1840
2023
  }
1841
2024
  }
1842
- } catch (retryError) {
1843
- if (this.isPersistent) this.clearPersistentSession();
1844
- if (signal.aborted) throw new AbortError();
1845
- throw retryError;
1846
- } finally {
1847
- this.activeQuery = null;
1848
- }
1849
- return {
1850
- output,
1851
- structuredOutput,
1852
- toolCalls,
1853
- messages: [
1854
- ...messages,
1855
- ...output !== null ? [{ role: "assistant", content: output }] : []
1856
- ],
1857
- usage
1858
- };
2025
+ return {
2026
+ output,
2027
+ structuredOutput,
2028
+ toolCalls,
2029
+ messages: [
2030
+ ...messages,
2031
+ ...output !== null ? [{ role: "assistant", content: output }] : []
2032
+ ],
2033
+ usage
2034
+ };
2035
+ });
1859
2036
  }
1860
2037
  if (this.isPersistent) this.clearPersistentSession();
1861
2038
  throw e;
@@ -1874,12 +2051,12 @@ var init_claude = __esm({
1874
2051
  };
1875
2052
  }
1876
2053
  // ─── executeStream ──────────────────────────────────────────────
1877
- async *executeStream(messages, _options, signal) {
2054
+ async *executeStream(messages, options, signal) {
1878
2055
  this.checkAbort(signal);
1879
2056
  const sdk = await loadSDK2();
1880
2057
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1881
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1882
- let opts = this.buildQueryOptions(signal);
2058
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
2059
+ let opts = this.buildQueryOptions(signal, options);
1883
2060
  const toolResultCapture = /* @__PURE__ */ new Map();
1884
2061
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1885
2062
  const q = sdk.query({ prompt, options: opts });
@@ -1887,6 +2064,7 @@ var init_claude = __esm({
1887
2064
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
1888
2065
  const toolCallTracker = new ClaudeToolCallTracker();
1889
2066
  const pendingStreamToolCalls = /* @__PURE__ */ new Map();
2067
+ let hasStreamedText = false;
1890
2068
  try {
1891
2069
  for await (const msg of q) {
1892
2070
  if (signal.aborted) throw new AbortError();
@@ -1904,6 +2082,7 @@ var init_claude = __esm({
1904
2082
  } else if (e.type === "tool_call_end") {
1905
2083
  pendingStreamToolCalls.delete(e.toolCallId);
1906
2084
  }
2085
+ if (e.type === "text_delta") hasStreamedText = true;
1907
2086
  yield e;
1908
2087
  }
1909
2088
  }
@@ -1927,22 +2106,21 @@ var init_claude = __esm({
1927
2106
  }
1928
2107
  yield this.emitSessionInfo(r.session_id);
1929
2108
  }
1930
- yield { type: "done", finalOutput: r.result };
2109
+ yield {
2110
+ type: "done",
2111
+ finalOutput: hasStreamedText ? null : r.result,
2112
+ ...hasStreamedText ? { streamed: true } : {}
2113
+ };
1931
2114
  }
1932
2115
  }
1933
2116
  } catch (e) {
1934
2117
  if (signal.aborted) throw new AbortError();
1935
2118
  if (isResuming && this.isPersistent) {
1936
- this.clearPersistentSession();
1937
- const retryPrompt = buildContextualPrompt2(messages);
1938
- let retryOpts = this.buildQueryOptions(signal);
1939
- toolResultCapture.clear();
1940
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1941
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1942
- this.activeQuery = retryQ;
2119
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1943
2120
  const retryThinkingBlockIndices = /* @__PURE__ */ new Set();
1944
2121
  const retryToolCallTracker = new ClaudeToolCallTracker();
1945
2122
  const retryPendingToolCalls = /* @__PURE__ */ new Map();
2123
+ let retryHasStreamedText = false;
1946
2124
  try {
1947
2125
  for await (const msg of retryQ) {
1948
2126
  if (signal.aborted) throw new AbortError();
@@ -1960,6 +2138,7 @@ var init_claude = __esm({
1960
2138
  } else if (ev.type === "tool_call_end") {
1961
2139
  retryPendingToolCalls.delete(ev.toolCallId);
1962
2140
  }
2141
+ if (ev.type === "text_delta") retryHasStreamedText = true;
1963
2142
  yield ev;
1964
2143
  }
1965
2144
  }
@@ -1983,7 +2162,11 @@ var init_claude = __esm({
1983
2162
  }
1984
2163
  yield this.emitSessionInfo(r.session_id);
1985
2164
  }
1986
- yield { type: "done", finalOutput: r.result };
2165
+ yield {
2166
+ type: "done",
2167
+ finalOutput: retryHasStreamedText ? null : r.result,
2168
+ ...retryHasStreamedText ? { streamed: true } : {}
2169
+ };
1987
2170
  }
1988
2171
  }
1989
2172
  } catch (retryError) {
@@ -2045,7 +2228,8 @@ var init_claude = __esm({
2045
2228
  this.cachedModels = body.data.map((m) => ({
2046
2229
  id: m.id,
2047
2230
  name: m.display_name,
2048
- provider: "claude"
2231
+ provider: "claude",
2232
+ ...m.max_input_tokens != null && { contextWindow: m.max_input_tokens }
2049
2233
  }));
2050
2234
  return this.cachedModels;
2051
2235
  }
@@ -2103,32 +2287,30 @@ __export(vercel_ai_exports, {
2103
2287
  createVercelAIService: () => createVercelAIService
2104
2288
  });
2105
2289
  async function loadSDK3() {
2106
- if (sdkModule3) return sdkModule3;
2290
+ if (_sdkMock3) return _sdkMock3;
2107
2291
  try {
2108
- sdkModule3 = await import('ai');
2109
- return sdkModule3;
2292
+ return await import('ai');
2110
2293
  } catch {
2111
2294
  throw new DependencyError("ai");
2112
2295
  }
2113
2296
  }
2114
2297
  async function loadCompat() {
2115
- if (compatModule) return compatModule;
2298
+ if (_compatMock) return _compatMock;
2116
2299
  try {
2117
- compatModule = await import('@ai-sdk/openai-compatible');
2118
- return compatModule;
2300
+ return await import('@ai-sdk/openai-compatible');
2119
2301
  } catch {
2120
2302
  throw new DependencyError("@ai-sdk/openai-compatible");
2121
2303
  }
2122
2304
  }
2123
2305
  function _injectSDK3(mock) {
2124
- sdkModule3 = mock;
2306
+ _sdkMock3 = mock;
2125
2307
  }
2126
2308
  function _injectCompat(mock) {
2127
- compatModule = mock;
2309
+ _compatMock = mock;
2128
2310
  }
2129
2311
  function _resetSDK3() {
2130
- sdkModule3 = null;
2131
- compatModule = null;
2312
+ _sdkMock3 = null;
2313
+ _compatMock = null;
2132
2314
  }
2133
2315
  function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, signal) {
2134
2316
  const toolMap = {};
@@ -2171,13 +2353,14 @@ function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, s
2171
2353
  return toolMap;
2172
2354
  }
2173
2355
  function wrapToolExecute(ourTool, supervisor, sessionApprovals, permissionStore, signal) {
2174
- return async (args) => {
2356
+ return async (args, options) => {
2175
2357
  if (ourTool.needsApproval && supervisor?.onPermission) {
2176
2358
  const storeApproved = permissionStore && await permissionStore.isApproved(ourTool.name);
2177
2359
  if (!storeApproved && !sessionApprovals.has(ourTool.name)) {
2178
2360
  const request = {
2179
2361
  toolName: ourTool.name,
2180
- toolArgs: args ?? {}
2362
+ toolArgs: args ?? {},
2363
+ toolCallId: options?.toolCallId
2181
2364
  };
2182
2365
  const decision = await supervisor.onPermission(
2183
2366
  request,
@@ -2284,7 +2467,8 @@ function mapStreamPart(part) {
2284
2467
  return {
2285
2468
  type: "error",
2286
2469
  error: p.error instanceof Error ? p.error.message : String(p.error ?? "Tool execution failed"),
2287
- recoverable: true
2470
+ recoverable: true,
2471
+ code: "TOOL_EXECUTION" /* TOOL_EXECUTION */
2288
2472
  };
2289
2473
  }
2290
2474
  case "reasoning-start":
@@ -2305,10 +2489,13 @@ function mapStreamPart(part) {
2305
2489
  }
2306
2490
  case "error": {
2307
2491
  const p = part;
2492
+ const errorMsg = p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error");
2493
+ const code = classifyAgentError(errorMsg);
2308
2494
  return {
2309
2495
  type: "error",
2310
- error: p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error"),
2311
- recoverable: false
2496
+ error: errorMsg,
2497
+ recoverable: isRecoverableErrorCode(code),
2498
+ code
2312
2499
  };
2313
2500
  }
2314
2501
  default:
@@ -2318,15 +2505,15 @@ function mapStreamPart(part) {
2318
2505
  function createVercelAIService(options) {
2319
2506
  return new VercelAIAgentService(options);
2320
2507
  }
2321
- var sdkModule3, compatModule, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2508
+ var _sdkMock3, _compatMock, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2322
2509
  var init_vercel_ai = __esm({
2323
2510
  "src/backends/vercel-ai.ts"() {
2324
- init_types();
2511
+ init_types2();
2325
2512
  init_base_agent();
2326
- init_errors();
2513
+ init_errors2();
2327
2514
  init_schema();
2328
- sdkModule3 = null;
2329
- compatModule = null;
2515
+ _sdkMock3 = null;
2516
+ _compatMock = null;
2330
2517
  DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
2331
2518
  DEFAULT_PROVIDER = "openrouter";
2332
2519
  DEFAULT_MAX_TURNS = 10;
@@ -2339,28 +2526,33 @@ var init_vercel_ai = __esm({
2339
2526
  super(config);
2340
2527
  this.backendOptions = backendOptions;
2341
2528
  }
2342
- async getModel() {
2343
- if (this.model) return this.model;
2529
+ async getModel(options) {
2530
+ const requestedModel = options.model;
2531
+ const defaultModel = this.config.model;
2532
+ if (requestedModel === defaultModel && this.model) return this.model;
2344
2533
  const compat = await loadCompat();
2345
2534
  const provider = compat.createOpenAICompatible({
2346
2535
  name: this.backendOptions.provider ?? DEFAULT_PROVIDER,
2347
2536
  baseURL: this.backendOptions.baseUrl ?? DEFAULT_BASE_URL,
2348
2537
  apiKey: this.backendOptions.apiKey
2349
2538
  });
2350
- const modelId = this.config.model ?? "anthropic/claude-sonnet-4-5";
2351
- this.model = provider.chatModel(modelId);
2352
- return this.model;
2539
+ const model = provider.chatModel(requestedModel);
2540
+ if (requestedModel === defaultModel) {
2541
+ this.model = model;
2542
+ }
2543
+ return model;
2353
2544
  }
2354
- async getSDKTools(signal) {
2545
+ async getSDKTools(signal, options) {
2355
2546
  const sdk = await loadSDK3();
2356
- return mapToolsToSDK2(sdk, this.config.tools ?? [], this.config, this.sessionApprovals, this.config.permissionStore, signal);
2547
+ const tools = this.resolveTools(options);
2548
+ return mapToolsToSDK2(sdk, tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
2357
2549
  }
2358
2550
  // ─── executeRun ─────────────────────────────────────────────────
2359
- async executeRun(messages, _options, signal) {
2551
+ async executeRun(messages, options, signal) {
2360
2552
  this.checkAbort(signal);
2361
2553
  const sdk = await loadSDK3();
2362
- const model = await this.getModel();
2363
- const tools = await this.getSDKTools(signal);
2554
+ const model = await this.getModel(options);
2555
+ const tools = await this.getSDKTools(signal, options);
2364
2556
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2365
2557
  const sdkMessages = messagesToSDK(messages);
2366
2558
  const hasTools = Object.keys(tools).length > 0;
@@ -2416,10 +2608,10 @@ var init_vercel_ai = __esm({
2416
2608
  };
2417
2609
  }
2418
2610
  // ─── executeRunStructured ───────────────────────────────────────
2419
- async executeRunStructured(messages, schema, _options, signal) {
2611
+ async executeRunStructured(messages, schema, options, signal) {
2420
2612
  this.checkAbort(signal);
2421
2613
  const sdk = await loadSDK3();
2422
- const model = await this.getModel();
2614
+ const model = await this.getModel(options);
2423
2615
  const sdkMessages = messagesToSDK(messages);
2424
2616
  const jsonSchema = zodToJsonSchema(schema.schema);
2425
2617
  const result = await sdk.generateObject({
@@ -2461,11 +2653,11 @@ var init_vercel_ai = __esm({
2461
2653
  };
2462
2654
  }
2463
2655
  // ─── executeStream ──────────────────────────────────────────────
2464
- async *executeStream(messages, _options, signal) {
2656
+ async *executeStream(messages, options, signal) {
2465
2657
  this.checkAbort(signal);
2466
2658
  const sdk = await loadSDK3();
2467
- const model = await this.getModel();
2468
- const tools = await this.getSDKTools(signal);
2659
+ const model = await this.getModel(options);
2660
+ const tools = await this.getSDKTools(signal, options);
2469
2661
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2470
2662
  const sdkMessages = messagesToSDK(messages);
2471
2663
  const hasTools = Object.keys(tools).length > 0;
@@ -2490,6 +2682,7 @@ var init_vercel_ai = __esm({
2490
2682
  }
2491
2683
  });
2492
2684
  let finalText = "";
2685
+ let lastFinishReason;
2493
2686
  try {
2494
2687
  for await (const part of result.fullStream) {
2495
2688
  if (signal.aborted) throw new AbortError();
@@ -2500,10 +2693,15 @@ var init_vercel_ai = __esm({
2500
2693
  }
2501
2694
  if (part.type === "finish-step") {
2502
2695
  const p = part;
2696
+ lastFinishReason = p.finishReason;
2503
2697
  if (p.finishReason === "tool-calls") {
2504
2698
  finalText = "";
2505
2699
  }
2506
2700
  }
2701
+ if (part.type === "finish") {
2702
+ const p = part;
2703
+ lastFinishReason = p.finishReason;
2704
+ }
2507
2705
  }
2508
2706
  const totalUsage = await result.totalUsage;
2509
2707
  yield {
@@ -2511,9 +2709,12 @@ var init_vercel_ai = __esm({
2511
2709
  promptTokens: Number(totalUsage?.inputTokens ?? 0),
2512
2710
  completionTokens: Number(totalUsage?.outputTokens ?? 0)
2513
2711
  };
2712
+ const hasStreamed = finalText.length > 0;
2514
2713
  yield {
2515
2714
  type: "done",
2516
- finalOutput: finalText || null
2715
+ finalOutput: hasStreamed ? null : finalText || null,
2716
+ ...hasStreamed ? { streamed: true } : {},
2717
+ ...lastFinishReason ? { finishReason: lastFinishReason } : {}
2517
2718
  };
2518
2719
  } catch (e) {
2519
2720
  if (signal.aborted) throw new AbortError();
@@ -2542,16 +2743,33 @@ var init_vercel_ai = __esm({
2542
2743
  const baseUrl = (this.options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
2543
2744
  try {
2544
2745
  const res = await globalThis.fetch(`${baseUrl}/models`, {
2545
- headers: { Authorization: `Bearer ${this.options.apiKey}` }
2746
+ headers: {
2747
+ Authorization: `Bearer ${this.options.apiKey}`,
2748
+ // OpenRouter requires HTTP-Referer for API access
2749
+ "HTTP-Referer": "https://github.com/nicepkg/agent-sdk"
2750
+ }
2546
2751
  });
2547
2752
  if (!res.ok) {
2548
2753
  return [];
2549
2754
  }
2550
2755
  const body = await res.json();
2551
- if (!body.data || body.data.length === 0) {
2552
- return [];
2756
+ if (body.data && Array.isArray(body.data)) {
2757
+ return body.data.filter((m) => typeof m.id === "string").map((m) => ({
2758
+ id: m.id,
2759
+ ...typeof m.name === "string" && { name: m.name },
2760
+ ...typeof m.description === "string" && { description: m.description },
2761
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2762
+ }));
2553
2763
  }
2554
- return body.data.map((m) => ({ id: m.id }));
2764
+ if (Array.isArray(body)) {
2765
+ return body.filter((m) => typeof m.id === "string").map((m) => ({
2766
+ id: m.id,
2767
+ ...typeof m.name === "string" && { name: m.name },
2768
+ ...typeof m.description === "string" && { description: m.description },
2769
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2770
+ }));
2771
+ }
2772
+ return [];
2555
2773
  } catch {
2556
2774
  return [];
2557
2775
  }
@@ -2582,272 +2800,25 @@ var init_vercel_ai = __esm({
2582
2800
  }
2583
2801
  });
2584
2802
 
2585
- // src/registry.ts
2586
- function registerBackend(name, factory) {
2587
- if (registry.has(name)) {
2588
- throw new BackendAlreadyRegisteredError(name);
2589
- }
2590
- registry.set(name, { factory, builtin: false });
2591
- }
2592
- function unregisterBackend(name) {
2593
- return registry.delete(name);
2594
- }
2595
- function hasBackend(name) {
2596
- return registry.has(name) || isBuiltinName(name);
2803
+ // src/chat/backends/types.ts
2804
+ function isResumableBackend(adapter) {
2805
+ return "canResume" in adapter && typeof adapter.canResume === "function";
2597
2806
  }
2598
- function listBackends() {
2599
- const names = new Set(registry.keys());
2600
- for (const builtin of BUILTIN_BACKENDS) {
2601
- names.add(builtin);
2602
- }
2603
- return [...names];
2604
- }
2605
- function resetRegistry() {
2606
- registry.clear();
2607
- }
2608
- function isBuiltinName(name) {
2609
- return BUILTIN_BACKENDS.has(name);
2610
- }
2611
- async function loadBuiltinFactory(name) {
2612
- switch (name) {
2613
- case "copilot": {
2614
- const mod = await Promise.resolve().then(() => (init_copilot(), copilot_exports));
2615
- return (opts) => mod.createCopilotService(opts);
2616
- }
2617
- case "claude": {
2618
- const mod = await Promise.resolve().then(() => (init_claude(), claude_exports));
2619
- return (opts) => mod.createClaudeService(opts);
2620
- }
2621
- case "vercel-ai": {
2622
- const mod = await Promise.resolve().then(() => (init_vercel_ai(), vercel_ai_exports));
2623
- return (opts) => mod.createVercelAIService(opts);
2624
- }
2625
- }
2626
- }
2627
- async function createAgentService(name, options) {
2628
- const entry = registry.get(name);
2629
- if (entry) {
2630
- return entry.factory(options);
2631
- }
2632
- if (isBuiltinName(name)) {
2633
- const factory = await loadBuiltinFactory(name);
2634
- registry.set(name, { factory, builtin: true });
2635
- return factory(options);
2636
- }
2637
- throw new BackendNotFoundError(name);
2638
- }
2639
- var registry, BUILTIN_BACKENDS;
2640
- var init_registry = __esm({
2641
- "src/registry.ts"() {
2642
- init_errors();
2643
- registry = /* @__PURE__ */ new Map();
2644
- BUILTIN_BACKENDS = /* @__PURE__ */ new Set([
2645
- "copilot",
2646
- "claude",
2647
- "vercel-ai"
2648
- ]);
2649
- }
2650
- });
2651
-
2652
- // src/utils/messages.ts
2653
- function messagesToPrompt(messages) {
2654
- return messages.map((msg) => {
2655
- switch (msg.role) {
2656
- case "user":
2657
- return contentToText(msg.content);
2658
- case "assistant":
2659
- return contentToText(msg.content);
2660
- case "system":
2661
- return msg.content;
2662
- case "tool":
2663
- return msg.content ?? "";
2664
- }
2665
- }).filter(Boolean).join("\n\n");
2666
- }
2667
- function contentToText(content) {
2668
- return getTextContent(content);
2669
- }
2670
- function buildSystemPrompt(base, schemaInstruction) {
2671
- if (!schemaInstruction) return base;
2672
- return `${base}
2673
-
2674
- ${schemaInstruction}`;
2675
- }
2676
- var init_messages = __esm({
2677
- "src/utils/messages.ts"() {
2678
- init_types();
2679
- }
2680
- });
2681
- function createDefaultPermissionStore(projectDir) {
2682
- const sessionStore = new InMemoryPermissionStore();
2683
- const projectPath = projectDir ? path.join(projectDir, ".agent-sdk", "permissions.json") : path.join(process.cwd(), ".agent-sdk", "permissions.json");
2684
- const userPath = path.join(os.homedir(), ".agent-sdk", "permissions.json");
2685
- const projectStore = new FilePermissionStore(projectPath);
2686
- const userStore = new FilePermissionStore(userPath);
2687
- return new CompositePermissionStore(sessionStore, projectStore, userStore);
2688
- }
2689
- var InMemoryPermissionStore, FilePermissionStore, CompositePermissionStore;
2690
- var init_permission_store = __esm({
2691
- "src/permission-store.ts"() {
2692
- InMemoryPermissionStore = class {
2693
- approvals = /* @__PURE__ */ new Map();
2694
- async isApproved(toolName) {
2695
- return this.approvals.has(toolName);
2696
- }
2697
- async approve(toolName, scope) {
2698
- if (scope === "once") return;
2699
- this.approvals.set(toolName, scope);
2700
- }
2701
- async revoke(toolName) {
2702
- this.approvals.delete(toolName);
2703
- }
2704
- async clear() {
2705
- this.approvals.clear();
2706
- }
2707
- async dispose() {
2708
- this.approvals.clear();
2709
- }
2710
- };
2711
- FilePermissionStore = class {
2712
- filePath;
2713
- constructor(filePath) {
2714
- this.filePath = path.resolve(filePath);
2715
- }
2716
- async isApproved(toolName) {
2717
- const data = this.readFile();
2718
- return toolName in data.approvals;
2719
- }
2720
- async approve(toolName, scope) {
2721
- if (scope === "once") return;
2722
- const data = this.readFile();
2723
- data.approvals[toolName] = { scope, timestamp: Date.now() };
2724
- this.writeFileAtomic(data);
2725
- }
2726
- async revoke(toolName) {
2727
- const data = this.readFile();
2728
- delete data.approvals[toolName];
2729
- this.writeFileAtomic(data);
2730
- }
2731
- async clear() {
2732
- this.writeFileAtomic({ approvals: {} });
2733
- }
2734
- async dispose() {
2735
- }
2736
- readFile() {
2737
- try {
2738
- const raw = fs.readFileSync(this.filePath, "utf-8");
2739
- const parsed = JSON.parse(raw);
2740
- if (parsed && typeof parsed.approvals === "object") return parsed;
2741
- } catch {
2742
- }
2743
- return { approvals: {} };
2744
- }
2745
- writeFileAtomic(data) {
2746
- const dir = path.dirname(this.filePath);
2747
- fs.mkdirSync(dir, { recursive: true });
2748
- const tmpPath = this.filePath + `.tmp.${process.pid}.${Date.now()}`;
2749
- fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
2750
- fs.renameSync(tmpPath, this.filePath);
2751
- }
2752
- };
2753
- CompositePermissionStore = class {
2754
- sessionStore;
2755
- projectStore;
2756
- userStore;
2757
- constructor(sessionStore, projectStore, userStore) {
2758
- this.sessionStore = sessionStore;
2759
- this.projectStore = projectStore;
2760
- this.userStore = userStore ?? projectStore;
2761
- }
2762
- async isApproved(toolName) {
2763
- return await this.sessionStore.isApproved(toolName) || await this.projectStore.isApproved(toolName) || await this.userStore.isApproved(toolName);
2764
- }
2765
- async approve(toolName, scope) {
2766
- if (scope === "once") return;
2767
- if (scope === "session") {
2768
- await this.sessionStore.approve(toolName, scope);
2769
- } else if (scope === "project") {
2770
- await this.projectStore.approve(toolName, scope);
2771
- } else {
2772
- await this.userStore.approve(toolName, scope);
2773
- }
2774
- }
2775
- async revoke(toolName) {
2776
- await this.sessionStore.revoke(toolName);
2777
- await this.projectStore.revoke(toolName);
2778
- await this.userStore.revoke(toolName);
2779
- }
2780
- async clear() {
2781
- await this.sessionStore.clear();
2782
- await this.projectStore.clear();
2783
- await this.userStore.clear();
2784
- }
2785
- async dispose() {
2786
- await this.sessionStore.dispose();
2787
- await this.projectStore.dispose();
2788
- if (this.userStore !== this.projectStore) {
2789
- await this.userStore.dispose();
2790
- }
2791
- }
2792
- };
2793
- }
2794
- });
2795
-
2796
- // src/index.ts
2797
- var src_exports = {};
2798
- __export(src_exports, {
2799
- AbortError: () => AbortError,
2800
- AgentSDKError: () => AgentSDKError,
2801
- BackendAlreadyRegisteredError: () => BackendAlreadyRegisteredError,
2802
- BackendNotFoundError: () => BackendNotFoundError,
2803
- BaseAgent: () => BaseAgent,
2804
- CompositePermissionStore: () => CompositePermissionStore,
2805
- DependencyError: () => DependencyError,
2806
- DisposedError: () => DisposedError,
2807
- FilePermissionStore: () => FilePermissionStore,
2808
- InMemoryPermissionStore: () => InMemoryPermissionStore,
2809
- ReentrancyError: () => ReentrancyError,
2810
- StructuredOutputError: () => StructuredOutputError,
2811
- SubprocessError: () => SubprocessError,
2812
- ToolExecutionError: () => ToolExecutionError,
2813
- buildSystemPrompt: () => buildSystemPrompt,
2814
- contentToText: () => contentToText,
2815
- createAgentService: () => createAgentService,
2816
- createDefaultPermissionStore: () => createDefaultPermissionStore,
2817
- getTextContent: () => getTextContent,
2818
- hasBackend: () => hasBackend,
2819
- isMultiPartContent: () => isMultiPartContent,
2820
- isTextContent: () => isTextContent,
2821
- isToolDefinition: () => isToolDefinition,
2822
- listBackends: () => listBackends,
2823
- messagesToPrompt: () => messagesToPrompt,
2824
- registerBackend: () => registerBackend,
2825
- resetRegistry: () => resetRegistry,
2826
- unregisterBackend: () => unregisterBackend,
2827
- zodToJsonSchema: () => zodToJsonSchema
2828
- });
2829
- var init_src = __esm({
2830
- "src/index.ts"() {
2831
- init_types();
2832
- init_errors();
2833
- init_registry();
2834
- init_base_agent();
2835
- init_schema();
2836
- init_messages();
2837
- init_permission_store();
2838
- }
2839
- });
2840
2807
 
2841
- // src/chat/core.ts
2808
+ // src/chat/types.ts
2842
2809
  function createChatId() {
2843
2810
  return crypto.randomUUID();
2844
2811
  }
2812
+
2813
+ // src/chat/chat-utils.ts
2845
2814
  function getMessageText(message) {
2846
2815
  return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
2847
2816
  }
2848
2817
  function getMessageToolCalls(message) {
2849
2818
  return message.parts.filter((p) => p.type === "tool_call");
2850
2819
  }
2820
+
2821
+ // src/chat/bridge.ts
2851
2822
  function agentEventToChatEvent(event, messageId) {
2852
2823
  switch (event.type) {
2853
2824
  case "text_delta":
@@ -2900,6 +2871,7 @@ function agentEventToChatEvent(event, messageId) {
2900
2871
  type: "error",
2901
2872
  error: event.error,
2902
2873
  recoverable: event.recoverable,
2874
+ code: event.code,
2903
2875
  messageId
2904
2876
  };
2905
2877
  case "heartbeat":
@@ -2907,8 +2879,9 @@ function agentEventToChatEvent(event, messageId) {
2907
2879
  case "ask_user":
2908
2880
  case "ask_user_response":
2909
2881
  case "session_info":
2910
- case "done":
2911
2882
  return null;
2883
+ case "done":
2884
+ return { type: "done", finalOutput: event.finalOutput ?? void 0, finishReason: event.finishReason };
2912
2885
  default:
2913
2886
  return null;
2914
2887
  }
@@ -2921,26 +2894,42 @@ async function* adaptAgentEvents(events, messageId) {
2921
2894
  }
2922
2895
  }
2923
2896
  }
2924
- function toAgentMessage(message) {
2897
+
2898
+ // src/chat/conversion.ts
2899
+ function toAgentMessages(message) {
2925
2900
  const textContent = getMessageText(message);
2926
2901
  const toolCallParts = getMessageToolCalls(message);
2927
2902
  switch (message.role) {
2928
2903
  case "user":
2929
- return { role: "user", content: textContent };
2904
+ return [{ role: "user", content: textContent }];
2930
2905
  case "assistant": {
2931
2906
  const toolCalls = toolCallParts.length > 0 ? toolCallParts.map((p) => ({ id: p.toolCallId, name: p.name, args: p.args })) : void 0;
2932
- return {
2907
+ const assistantMsg = {
2933
2908
  role: "assistant",
2934
2909
  content: textContent,
2935
2910
  toolCalls
2936
2911
  };
2912
+ const toolResults = extractToolResults(message);
2913
+ if (toolResults.length > 0) {
2914
+ return [assistantMsg, { role: "tool", toolResults }];
2915
+ }
2916
+ return [assistantMsg];
2937
2917
  }
2938
2918
  case "system":
2939
- return { role: "system", content: textContent };
2919
+ return [{ role: "system", content: textContent }];
2940
2920
  }
2941
2921
  }
2922
+ function extractToolResults(message) {
2923
+ return getMessageToolCalls(message).filter((p) => p.result !== void 0).map((p) => ({
2924
+ toolCallId: p.toolCallId,
2925
+ name: p.name,
2926
+ result: p.result,
2927
+ isError: p.status === "error" ? true : void 0
2928
+ }));
2929
+ }
2942
2930
 
2943
2931
  // src/chat/errors.ts
2932
+ init_errors2();
2944
2933
  init_errors();
2945
2934
  var ChatError = class extends AgentSDKError {
2946
2935
  code;
@@ -2948,7 +2937,11 @@ var ChatError = class extends AgentSDKError {
2948
2937
  retryAfter;
2949
2938
  timestamp;
2950
2939
  constructor(message, options) {
2951
- super(message, { cause: options.cause });
2940
+ super(message, {
2941
+ cause: options.cause,
2942
+ code: options.code,
2943
+ retryable: options.retryable
2944
+ });
2952
2945
  this.name = "ChatError";
2953
2946
  this.code = options.code;
2954
2947
  this.retryable = options.retryable ?? false;
@@ -2960,25 +2953,51 @@ var ChatError = class extends AgentSDKError {
2960
2953
  // src/chat/backends/base.ts
2961
2954
  var BaseBackendAdapter = class {
2962
2955
  name;
2963
- _agentService;
2964
- _agent = null;
2956
+ _agentService = null;
2957
+ _agentServiceFactory = null;
2965
2958
  _disposed = false;
2966
2959
  _agentConfig;
2967
2960
  _ownsService;
2961
+ // Agent lifecycle: tracks current agent and the model it was created with.
2962
+ // For persistent sessions, reused across calls when model matches.
2963
+ // For non-persistent, recreated every call.
2964
+ _currentAgent = null;
2968
2965
  constructor(name, options) {
2969
2966
  this.name = name;
2970
2967
  this._agentConfig = options.agentConfig;
2971
2968
  if (options.agentService) {
2972
2969
  this._agentService = options.agentService;
2973
2970
  this._ownsService = false;
2971
+ } else if (options.agentServiceFactory) {
2972
+ this._agentServiceFactory = options.agentServiceFactory;
2973
+ this._ownsService = true;
2974
2974
  } else {
2975
2975
  this._agentService = this.createService();
2976
2976
  this._ownsService = true;
2977
2977
  }
2978
2978
  }
2979
2979
  get agentService() {
2980
+ if (!this._agentService) {
2981
+ if (this._agentServiceFactory) {
2982
+ this._agentService = this._agentServiceFactory();
2983
+ this._agentServiceFactory = null;
2984
+ } else {
2985
+ throw new ChatError("Agent service not available", {
2986
+ code: "BACKEND_NOT_INSTALLED" /* BACKEND_NOT_INSTALLED */
2987
+ });
2988
+ }
2989
+ }
2980
2990
  return this._agentService;
2981
2991
  }
2992
+ get currentModel() {
2993
+ return this._agentConfig.model;
2994
+ }
2995
+ /**
2996
+ * @deprecated No-op. Tools are passed per-call via SendMessageOptions.tools.
2997
+ * Kept for backward compatibility with code that calls setTools() directly.
2998
+ */
2999
+ setTools() {
3000
+ }
2982
3001
  async sendMessage(session, message, options) {
2983
3002
  this.assertNotDisposed();
2984
3003
  const events = this.streamMessage(session, message, options);
@@ -3006,7 +3025,7 @@ var BaseBackendAdapter = class {
3006
3025
  async *streamMessage(session, message, options) {
3007
3026
  this.assertNotDisposed();
3008
3027
  const agent = this.getOrCreateAgent(options);
3009
- const messages = session.messages.map(toAgentMessage);
3028
+ const messages = session.messages.flatMap(toAgentMessages);
3010
3029
  messages.push({ role: "user", content: message });
3011
3030
  yield* this.streamAgentEvents(agent, messages, options);
3012
3031
  }
@@ -3016,9 +3035,13 @@ var BaseBackendAdapter = class {
3016
3035
  */
3017
3036
  async *streamAgentEvents(agent, messages, options) {
3018
3037
  const messageId = createChatId();
3038
+ const model = options?.model ?? this._agentConfig.model ?? "";
3019
3039
  const agentEvents = agent.streamWithContext(messages, {
3040
+ model,
3020
3041
  signal: options?.signal,
3021
- context: options?.context
3042
+ context: options?.context,
3043
+ tools: options?.tools,
3044
+ ...options?.systemPrompt ? { systemMessage: options.systemPrompt } : {}
3022
3045
  });
3023
3046
  yield { type: "message:start", messageId, role: "assistant" };
3024
3047
  let text = "";
@@ -3044,31 +3067,46 @@ var BaseBackendAdapter = class {
3044
3067
  }
3045
3068
  async listModels() {
3046
3069
  this.assertNotDisposed();
3047
- return this._agentService.listModels();
3070
+ return this.agentService.listModels();
3048
3071
  }
3049
3072
  async validate() {
3050
3073
  this.assertNotDisposed();
3051
- return this._agentService.validate();
3074
+ return this.agentService.validate();
3052
3075
  }
3053
3076
  async dispose() {
3054
3077
  if (this._disposed) return;
3055
3078
  this._disposed = true;
3056
- this._agent?.dispose();
3057
- this._agent = null;
3058
- if (this._ownsService) {
3079
+ if (this._currentAgent) {
3080
+ this._currentAgent.instance.dispose();
3081
+ this._currentAgent = null;
3082
+ }
3083
+ if (this._ownsService && this._agentService && typeof this._agentService.dispose === "function") {
3059
3084
  await this._agentService.dispose();
3060
3085
  }
3061
3086
  }
3062
- /** Get or create an agent, applying model override from options */
3087
+ /** Get or create an agent. Model is passed per-call via RunOptions.
3088
+ * Tools are passed per-call via SendMessageOptions — not baked into config.
3089
+ * For persistent sessions, reuses agent when model matches. */
3063
3090
  getOrCreateAgent(options) {
3064
- const config = options?.model ? { ...this._agentConfig, model: options.model } : this._agentConfig;
3065
- if (this._agentConfig.sessionMode === "persistent" && this._agent) {
3066
- return this._agent;
3067
- }
3068
- const agent = this._agentService.createAgent(config);
3069
- if (this._agentConfig.sessionMode === "persistent") {
3070
- this._agent = agent;
3071
- }
3091
+ const model = options?.model ?? this._agentConfig.model;
3092
+ if (this._agentConfig.sessionMode === "persistent" && this._currentAgent) {
3093
+ if (this._currentAgent.model === model) {
3094
+ return this._currentAgent.instance;
3095
+ }
3096
+ this._currentAgent.instance.dispose();
3097
+ this._currentAgent = null;
3098
+ }
3099
+ if (this._currentAgent) {
3100
+ this._currentAgent.instance.dispose();
3101
+ this._currentAgent = null;
3102
+ }
3103
+ const config = {
3104
+ ...this._agentConfig,
3105
+ ...model !== void 0 && { model },
3106
+ ...options?.tools?.length ? { tools: options.tools } : {}
3107
+ };
3108
+ const agent = this.agentService.createAgent(config);
3109
+ this._currentAgent = { instance: agent, model };
3072
3110
  return agent;
3073
3111
  }
3074
3112
  assertNotDisposed() {
@@ -3080,21 +3118,15 @@ var BaseBackendAdapter = class {
3080
3118
  }
3081
3119
  };
3082
3120
 
3083
- // src/chat/backends/copilot.ts
3084
- var CopilotChatAdapter = class extends BaseBackendAdapter {
3121
+ // src/chat/backends/resumable.ts
3122
+ var ResumableChatAdapter = class extends BaseBackendAdapter {
3085
3123
  _backendSessionId = null;
3086
- _copilotOptions;
3087
- constructor(options) {
3124
+ constructor(name, options) {
3088
3125
  const agentConfig = {
3089
3126
  ...options.agentConfig,
3090
3127
  sessionMode: "persistent"
3091
3128
  };
3092
- super("copilot", { ...options, agentConfig });
3093
- this._copilotOptions = options.copilotOptions;
3094
- }
3095
- createService() {
3096
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
3097
- return createAgentService2("copilot", this._copilotOptions);
3129
+ super(name, { ...options, agentConfig });
3098
3130
  }
3099
3131
  get backendSessionId() {
3100
3132
  return this._backendSessionId;
@@ -3123,7 +3155,7 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
3123
3155
  { code: "SESSION_EXPIRED" /* SESSION_EXPIRED */ }
3124
3156
  );
3125
3157
  }
3126
- const messages = session.messages.map(toAgentMessage);
3158
+ const messages = session.messages.flatMap(toAgentMessages);
3127
3159
  yield* this.streamAgentEvents(agent, messages, options);
3128
3160
  }
3129
3161
  captureSessionId(agent) {
@@ -3133,81 +3165,325 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
3133
3165
  }
3134
3166
  };
3135
3167
 
3168
+ // src/chat/backends/copilot.ts
3169
+ var CopilotChatAdapter = class extends ResumableChatAdapter {
3170
+ _copilotOptions;
3171
+ constructor(options) {
3172
+ super("copilot", options);
3173
+ this._copilotOptions = options.copilotOptions;
3174
+ }
3175
+ createService() {
3176
+ const { createCopilotService: createCopilotService2 } = (init_copilot(), __toCommonJS(copilot_exports));
3177
+ return createCopilotService2(this._copilotOptions || {});
3178
+ }
3179
+ };
3180
+
3136
3181
  // src/chat/backends/claude.ts
3137
- var ClaudeChatAdapter = class extends BaseBackendAdapter {
3138
- _backendSessionId = null;
3182
+ var ClaudeChatAdapter = class extends ResumableChatAdapter {
3139
3183
  _claudeOptions;
3140
3184
  constructor(options) {
3141
- const agentConfig = {
3142
- ...options.agentConfig,
3143
- sessionMode: "persistent"
3144
- };
3145
- super("claude", { ...options, agentConfig });
3185
+ super("claude", options);
3146
3186
  this._claudeOptions = options.claudeOptions;
3147
3187
  }
3148
3188
  createService() {
3149
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
3150
- return createAgentService2("claude", this._claudeOptions);
3189
+ const { createClaudeService: createClaudeService2 } = (init_claude(), __toCommonJS(claude_exports));
3190
+ return createClaudeService2(this._claudeOptions || {});
3151
3191
  }
3152
- get backendSessionId() {
3153
- return this._backendSessionId;
3192
+ };
3193
+
3194
+ // src/chat/backends/vercel-ai.ts
3195
+ var VercelAIChatAdapter = class extends BaseBackendAdapter {
3196
+ _vercelOptions;
3197
+ constructor(options) {
3198
+ super("vercel-ai", options);
3199
+ this._vercelOptions = options.vercelOptions;
3154
3200
  }
3155
- canResume() {
3156
- return this._backendSessionId !== null;
3201
+ createService() {
3202
+ const { createVercelAIService: createVercelAIService2 } = (init_vercel_ai(), __toCommonJS(vercel_ai_exports));
3203
+ return createVercelAIService2(this._vercelOptions || {});
3157
3204
  }
3158
- async *resume(session, backendSessionId, options) {
3159
- this.assertNotDisposed();
3160
- if (!backendSessionId) {
3161
- throw new ChatError("Backend session ID is required for resume", {
3162
- code: "INVALID_INPUT" /* INVALID_INPUT */
3205
+ captureSessionId(_agent) {
3206
+ }
3207
+ };
3208
+
3209
+ // src/backends/mock-llm.ts
3210
+ init_base_agent();
3211
+ init_errors2();
3212
+ function extractPrompt(messages) {
3213
+ for (let i = messages.length - 1; i >= 0; i--) {
3214
+ const msg = messages[i];
3215
+ if (msg.role === "user") {
3216
+ return typeof msg.content === "string" ? msg.content : msg.content.filter((p) => p.type === "text").map((p) => p.text).join("");
3217
+ }
3218
+ }
3219
+ return "";
3220
+ }
3221
+ function resolveResponse(mode, messages, callIndex) {
3222
+ switch (mode.type) {
3223
+ case "echo":
3224
+ return extractPrompt(messages);
3225
+ case "static":
3226
+ return mode.response;
3227
+ case "scripted": {
3228
+ if (mode.loop) {
3229
+ return mode.responses[callIndex % mode.responses.length];
3230
+ }
3231
+ if (callIndex < mode.responses.length) {
3232
+ return mode.responses[callIndex];
3233
+ }
3234
+ return mode.responses[mode.responses.length - 1];
3235
+ }
3236
+ case "error":
3237
+ throw new AgentSDKError(mode.error, {
3238
+ code: mode.code ?? "backend_error",
3239
+ retryable: mode.recoverable ?? false
3163
3240
  });
3241
+ }
3242
+ }
3243
+ async function applyLatency(latency, signal) {
3244
+ if (!latency) return;
3245
+ const ms = latency.type === "fixed" ? latency.ms : latency.minMs + Math.random() * (latency.maxMs - latency.minMs);
3246
+ if (ms <= 0) return;
3247
+ await new Promise((resolve, reject) => {
3248
+ const timer = setTimeout(resolve, ms);
3249
+ const onAbort = () => {
3250
+ clearTimeout(timer);
3251
+ reject(new Error("aborted"));
3252
+ };
3253
+ if (signal.aborted) {
3254
+ clearTimeout(timer);
3255
+ reject(new Error("aborted"));
3256
+ return;
3164
3257
  }
3165
- const agent = this.getOrCreateAgent(options);
3166
- const currentSessionId = agent.sessionId;
3167
- if (!currentSessionId) {
3168
- throw new ChatError(
3169
- `No active session to resume (requested: ${backendSessionId})`,
3170
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
3171
- );
3258
+ signal.addEventListener("abort", onAbort, { once: true });
3259
+ });
3260
+ }
3261
+ function chunkText(text, streaming) {
3262
+ if (streaming?.chunkSize && streaming.chunkSize > 0) {
3263
+ const chunks = [];
3264
+ for (let i = 0; i < text.length; i += streaming.chunkSize) {
3265
+ chunks.push(text.slice(i, i + streaming.chunkSize));
3172
3266
  }
3173
- if (currentSessionId !== backendSessionId) {
3174
- throw new ChatError(
3175
- `Session expired: expected ${backendSessionId}, got ${currentSessionId}`,
3176
- { code: "SESSION_EXPIRED" /* SESSION_EXPIRED */ }
3177
- );
3267
+ return chunks;
3268
+ }
3269
+ return text.split(/(\s+)/).filter(Boolean);
3270
+ }
3271
+ async function chunkDelay(streaming, signal) {
3272
+ const ms = streaming?.chunkDelayMs;
3273
+ if (!ms || ms <= 0) return;
3274
+ await new Promise((resolve, reject) => {
3275
+ const timer = setTimeout(resolve, ms);
3276
+ const onAbort = () => {
3277
+ clearTimeout(timer);
3278
+ reject(new Error("aborted"));
3279
+ };
3280
+ if (signal.aborted) {
3281
+ clearTimeout(timer);
3282
+ reject(new Error("aborted"));
3283
+ return;
3178
3284
  }
3179
- const messages = session.messages.map(toAgentMessage);
3180
- yield* this.streamAgentEvents(agent, messages, options);
3285
+ signal.addEventListener("abort", onAbort, { once: true });
3286
+ });
3287
+ }
3288
+ var MockLLMAgent = class extends BaseAgent {
3289
+ backendName = "mock-llm";
3290
+ mode;
3291
+ latency;
3292
+ streaming;
3293
+ finishReason;
3294
+ permissions;
3295
+ toolCallConfigs;
3296
+ configuredStructuredOutput;
3297
+ callIndex = 0;
3298
+ constructor(config, options) {
3299
+ super(config);
3300
+ this.mode = options.mode ?? { type: "echo" };
3301
+ this.latency = options.latency;
3302
+ this.streaming = options.streaming;
3303
+ this.finishReason = options.finishReason ?? "stop";
3304
+ this.permissions = options.permissions;
3305
+ this.toolCallConfigs = options.toolCalls ?? [];
3306
+ this.configuredStructuredOutput = options.structuredOutput;
3307
+ }
3308
+ async executeRun(messages, _options, signal) {
3309
+ this.checkAbort(signal);
3310
+ await applyLatency(this.latency, signal);
3311
+ this.checkAbort(signal);
3312
+ const idx = this.callIndex++;
3313
+ const output = resolveResponse(this.mode, messages, idx);
3314
+ const toolCalls = this.toolCallConfigs.map((tc) => ({
3315
+ toolName: tc.toolName,
3316
+ args: tc.args ?? {},
3317
+ result: tc.result ?? null,
3318
+ approved: true
3319
+ }));
3320
+ return {
3321
+ output,
3322
+ structuredOutput: void 0,
3323
+ toolCalls,
3324
+ messages: [
3325
+ ...messages,
3326
+ { role: "assistant", content: output }
3327
+ ],
3328
+ usage: { promptTokens: 10, completionTokens: output.length }
3329
+ };
3181
3330
  }
3182
- captureSessionId(agent) {
3183
- if (agent.sessionId) {
3184
- this._backendSessionId = agent.sessionId;
3331
+ async executeRunStructured(messages, _schema, _options, signal) {
3332
+ this.checkAbort(signal);
3333
+ await applyLatency(this.latency, signal);
3334
+ this.checkAbort(signal);
3335
+ const idx = this.callIndex++;
3336
+ const output = resolveResponse(this.mode, messages, idx);
3337
+ let parsed;
3338
+ if (this.configuredStructuredOutput !== void 0) {
3339
+ parsed = this.configuredStructuredOutput;
3340
+ } else {
3341
+ try {
3342
+ parsed = JSON.parse(output);
3343
+ } catch {
3344
+ parsed = output;
3345
+ }
3346
+ }
3347
+ return {
3348
+ output,
3349
+ structuredOutput: parsed,
3350
+ toolCalls: [],
3351
+ messages: [
3352
+ ...messages,
3353
+ { role: "assistant", content: output }
3354
+ ],
3355
+ usage: { promptTokens: 10, completionTokens: output.length }
3356
+ };
3357
+ }
3358
+ async *executeStream(messages, _options, signal) {
3359
+ this.checkAbort(signal);
3360
+ await applyLatency(this.latency, signal);
3361
+ this.checkAbort(signal);
3362
+ if (this.permissions) {
3363
+ yield* this.simulatePermissions(signal);
3364
+ }
3365
+ if (this.toolCallConfigs.length > 0) {
3366
+ yield* this.simulateToolCalls(signal);
3367
+ }
3368
+ const idx = this.callIndex++;
3369
+ const output = resolveResponse(this.mode, messages, idx);
3370
+ const chunks = chunkText(output, this.streaming);
3371
+ for (let i = 0; i < chunks.length; i++) {
3372
+ this.checkAbort(signal);
3373
+ if (i > 0) {
3374
+ await chunkDelay(this.streaming, signal);
3375
+ }
3376
+ yield { type: "text_delta", text: chunks[i] };
3377
+ }
3378
+ yield {
3379
+ type: "usage_update",
3380
+ promptTokens: 10,
3381
+ completionTokens: output.length
3382
+ };
3383
+ yield {
3384
+ type: "done",
3385
+ finalOutput: output,
3386
+ finishReason: this.finishReason
3387
+ };
3388
+ }
3389
+ async *simulateToolCalls(signal) {
3390
+ for (let i = 0; i < this.toolCallConfigs.length; i++) {
3391
+ this.checkAbort(signal);
3392
+ const tc = this.toolCallConfigs[i];
3393
+ const toolCallId = tc.toolCallId ?? `mock-tc-${i}`;
3394
+ yield {
3395
+ type: "tool_call_start",
3396
+ toolCallId,
3397
+ toolName: tc.toolName,
3398
+ args: tc.args ?? {}
3399
+ };
3400
+ yield {
3401
+ type: "tool_call_end",
3402
+ toolCallId,
3403
+ toolName: tc.toolName,
3404
+ result: tc.result ?? null
3405
+ };
3406
+ }
3407
+ }
3408
+ async *simulatePermissions(signal) {
3409
+ const perms = this.permissions;
3410
+ for (const toolName of perms.toolNames) {
3411
+ this.checkAbort(signal);
3412
+ const request = {
3413
+ toolName,
3414
+ toolArgs: {}
3415
+ };
3416
+ yield { type: "permission_request", request };
3417
+ if (perms.denyTools?.includes(toolName)) {
3418
+ yield {
3419
+ type: "permission_response",
3420
+ toolName,
3421
+ decision: { allowed: false, reason: "Denied by mock configuration" }
3422
+ };
3423
+ } else if (perms.autoApprove) {
3424
+ yield {
3425
+ type: "permission_response",
3426
+ toolName,
3427
+ decision: { allowed: true, scope: "once" }
3428
+ };
3429
+ } else {
3430
+ const supervisor = this.getConfig().supervisor;
3431
+ if (supervisor?.onPermission) {
3432
+ const decision = await supervisor.onPermission(request, signal);
3433
+ yield { type: "permission_response", toolName, decision };
3434
+ } else {
3435
+ yield {
3436
+ type: "permission_response",
3437
+ toolName,
3438
+ decision: { allowed: true, scope: "once" }
3439
+ };
3440
+ }
3441
+ }
3185
3442
  }
3186
3443
  }
3187
3444
  };
3188
-
3189
- // src/chat/backends/vercel-ai.ts
3190
- var VercelAIChatAdapter = class extends BaseBackendAdapter {
3191
- _vercelOptions;
3192
- constructor(options) {
3193
- super("vercel-ai", options);
3194
- this._vercelOptions = options.vercelOptions;
3445
+ var MockLLMService = class {
3446
+ name = "mock-llm";
3447
+ options;
3448
+ models;
3449
+ constructor(options = {}) {
3450
+ this.options = options;
3451
+ this.models = (options.models ?? [
3452
+ { id: "mock-fast", name: "Mock Fast" },
3453
+ { id: "mock-quality", name: "Mock Quality" }
3454
+ ]).map((m) => ({
3455
+ id: m.id,
3456
+ name: m.name,
3457
+ description: m.description
3458
+ }));
3459
+ }
3460
+ createAgent(config) {
3461
+ return new MockLLMAgent(config, this.options);
3195
3462
  }
3196
- createService() {
3197
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
3198
- return createAgentService2("vercel-ai", this._vercelOptions);
3463
+ async listModels() {
3464
+ return this.models;
3199
3465
  }
3200
- get backendSessionId() {
3201
- return null;
3466
+ async validate() {
3467
+ return { valid: true, errors: [] };
3202
3468
  }
3203
- canResume() {
3204
- return false;
3469
+ async dispose() {
3205
3470
  }
3206
- async *resume(_session, _backendSessionId, _options) {
3207
- throw new ChatError(
3208
- "Vercel AI adapter does not support session resume (stateless)",
3209
- { code: "PROVIDER_ERROR" /* PROVIDER_ERROR */ }
3210
- );
3471
+ };
3472
+ function createMockLLMService(options = {}) {
3473
+ return new MockLLMService(options);
3474
+ }
3475
+
3476
+ // src/chat/backends/mock-llm.ts
3477
+ var MockLLMChatAdapter = class extends BaseBackendAdapter {
3478
+ constructor(options) {
3479
+ const mockOpts = options.mockOptions;
3480
+ super("mock-llm", {
3481
+ ...options,
3482
+ agentServiceFactory: () => createMockLLMService(mockOpts || {})
3483
+ });
3484
+ }
3485
+ createService() {
3486
+ return createMockLLMService({});
3211
3487
  }
3212
3488
  captureSessionId(_agent) {
3213
3489
  }
@@ -3288,16 +3564,22 @@ var SSEChatTransport = class {
3288
3564
  };
3289
3565
  async function streamToTransport(events, transport) {
3290
3566
  try {
3291
- let accumulatedText = "";
3567
+ const textChunks = [];
3568
+ let finishReason;
3292
3569
  for await (const event of events) {
3293
3570
  if (!transport.isOpen) break;
3571
+ if (event.type === "done") {
3572
+ finishReason = event.finishReason;
3573
+ continue;
3574
+ }
3294
3575
  transport.send(event);
3295
3576
  if (event.type === "message:delta") {
3296
- accumulatedText += event.text;
3577
+ textChunks.push(event.text);
3297
3578
  }
3298
3579
  }
3299
3580
  if (transport.isOpen) {
3300
- transport.send({ type: "done", finalOutput: accumulatedText || void 0 });
3581
+ const finalOutput = textChunks.length > 0 ? textChunks.join("") : void 0;
3582
+ transport.send({ type: "done", finalOutput, finishReason });
3301
3583
  }
3302
3584
  transport.close();
3303
3585
  } catch (err) {
@@ -3388,9 +3670,9 @@ var InProcessChatTransport = class {
3388
3670
  send(event) {
3389
3671
  if (!this._open) return;
3390
3672
  if (this._resolve) {
3391
- const resolve2 = this._resolve;
3673
+ const resolve = this._resolve;
3392
3674
  this._resolve = null;
3393
- resolve2({ value: event, done: false });
3675
+ resolve({ value: event, done: false });
3394
3676
  } else {
3395
3677
  this._buffer.push(event);
3396
3678
  }
@@ -3399,9 +3681,9 @@ var InProcessChatTransport = class {
3399
3681
  if (!this._open) return;
3400
3682
  this._open = false;
3401
3683
  if (this._resolve) {
3402
- const resolve2 = this._resolve;
3684
+ const resolve = this._resolve;
3403
3685
  this._resolve = null;
3404
- resolve2({ value: void 0, done: true });
3686
+ resolve({ value: void 0, done: true });
3405
3687
  }
3406
3688
  }
3407
3689
  error(err) {
@@ -3413,9 +3695,9 @@ var InProcessChatTransport = class {
3413
3695
  recoverable: false
3414
3696
  };
3415
3697
  if (this._resolve) {
3416
- const resolve2 = this._resolve;
3698
+ const resolve = this._resolve;
3417
3699
  this._resolve = null;
3418
- resolve2({ value: errorEvent, done: false });
3700
+ resolve({ value: errorEvent, done: false });
3419
3701
  } else {
3420
3702
  this._error = err;
3421
3703
  }
@@ -3440,8 +3722,8 @@ var InProcessChatTransport = class {
3440
3722
  if (!this._open) {
3441
3723
  return Promise.resolve({ value: void 0, done: true });
3442
3724
  }
3443
- return new Promise((resolve2) => {
3444
- this._resolve = resolve2;
3725
+ return new Promise((resolve) => {
3726
+ this._resolve = resolve;
3445
3727
  });
3446
3728
  }
3447
3729
  };
@@ -3496,6 +3778,6 @@ function withInterceptors(transport, interceptors) {
3496
3778
  return new InterceptedTransport(transport, interceptors);
3497
3779
  }
3498
3780
 
3499
- export { BaseBackendAdapter, ClaudeChatAdapter, CopilotChatAdapter, InProcessChatTransport, SSEChatTransport, VercelAIChatAdapter, WS_READY_STATE, WsChatTransport, streamToTransport, withInterceptors };
3781
+ export { BaseBackendAdapter, ClaudeChatAdapter, CopilotChatAdapter, InProcessChatTransport, MockLLMChatAdapter, ResumableChatAdapter, SSEChatTransport, VercelAIChatAdapter, WS_READY_STATE, WsChatTransport, isResumableBackend, streamToTransport, withInterceptors };
3500
3782
  //# sourceMappingURL=backends.js.map
3501
3783
  //# sourceMappingURL=backends.js.map