@witqq/agent-sdk 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +140 -34
  2. package/dist/{types-CqvUAYxt.d.cts → agent-CW9XbmG_.d.ts} +137 -102
  3. package/dist/{types-CqvUAYxt.d.ts → agent-DxY68NZL.d.cts} +137 -102
  4. package/dist/auth/index.cjs +72 -1
  5. package/dist/auth/index.cjs.map +1 -1
  6. package/dist/auth/index.d.cts +21 -154
  7. package/dist/auth/index.d.ts +21 -154
  8. package/dist/auth/index.js +72 -1
  9. package/dist/auth/index.js.map +1 -1
  10. package/dist/backends/claude.cjs +480 -261
  11. package/dist/backends/claude.cjs.map +1 -1
  12. package/dist/backends/claude.d.cts +3 -1
  13. package/dist/backends/claude.d.ts +3 -1
  14. package/dist/backends/claude.js +480 -261
  15. package/dist/backends/claude.js.map +1 -1
  16. package/dist/backends/copilot.cjs +329 -97
  17. package/dist/backends/copilot.cjs.map +1 -1
  18. package/dist/backends/copilot.d.cts +12 -4
  19. package/dist/backends/copilot.d.ts +12 -4
  20. package/dist/backends/copilot.js +329 -97
  21. package/dist/backends/copilot.js.map +1 -1
  22. package/dist/backends/vercel-ai.cjs +294 -61
  23. package/dist/backends/vercel-ai.cjs.map +1 -1
  24. package/dist/backends/vercel-ai.d.cts +3 -1
  25. package/dist/backends/vercel-ai.d.ts +3 -1
  26. package/dist/backends/vercel-ai.js +294 -61
  27. package/dist/backends/vercel-ai.js.map +1 -1
  28. package/dist/backends-BSrsBYFn.d.cts +39 -0
  29. package/dist/backends-BSrsBYFn.d.ts +39 -0
  30. package/dist/chat/accumulator.cjs +1 -1
  31. package/dist/chat/accumulator.cjs.map +1 -1
  32. package/dist/chat/accumulator.d.cts +5 -2
  33. package/dist/chat/accumulator.d.ts +5 -2
  34. package/dist/chat/accumulator.js +1 -1
  35. package/dist/chat/accumulator.js.map +1 -1
  36. package/dist/chat/backends.cjs +736 -746
  37. package/dist/chat/backends.cjs.map +1 -1
  38. package/dist/chat/backends.d.cts +10 -6
  39. package/dist/chat/backends.d.ts +10 -6
  40. package/dist/chat/backends.js +736 -725
  41. package/dist/chat/backends.js.map +1 -1
  42. package/dist/chat/context.cjs +50 -0
  43. package/dist/chat/context.cjs.map +1 -1
  44. package/dist/chat/context.d.cts +27 -3
  45. package/dist/chat/context.d.ts +27 -3
  46. package/dist/chat/context.js +50 -0
  47. package/dist/chat/context.js.map +1 -1
  48. package/dist/chat/core.cjs +25 -2
  49. package/dist/chat/core.cjs.map +1 -1
  50. package/dist/chat/core.d.cts +30 -381
  51. package/dist/chat/core.d.ts +30 -381
  52. package/dist/chat/core.js +24 -3
  53. package/dist/chat/core.js.map +1 -1
  54. package/dist/chat/errors.cjs +48 -26
  55. package/dist/chat/errors.cjs.map +1 -1
  56. package/dist/chat/errors.d.cts +6 -31
  57. package/dist/chat/errors.d.ts +6 -31
  58. package/dist/chat/errors.js +48 -25
  59. package/dist/chat/errors.js.map +1 -1
  60. package/dist/chat/events.cjs.map +1 -1
  61. package/dist/chat/events.d.cts +6 -2
  62. package/dist/chat/events.d.ts +6 -2
  63. package/dist/chat/events.js.map +1 -1
  64. package/dist/chat/index.cjs +1199 -1008
  65. package/dist/chat/index.cjs.map +1 -1
  66. package/dist/chat/index.d.cts +35 -10
  67. package/dist/chat/index.d.ts +35 -10
  68. package/dist/chat/index.js +1196 -987
  69. package/dist/chat/index.js.map +1 -1
  70. package/dist/chat/react/theme.css +2517 -0
  71. package/dist/chat/react.cjs +2003 -1153
  72. package/dist/chat/react.cjs.map +1 -1
  73. package/dist/chat/react.d.cts +590 -121
  74. package/dist/chat/react.d.ts +590 -121
  75. package/dist/chat/react.js +1984 -1151
  76. package/dist/chat/react.js.map +1 -1
  77. package/dist/chat/runtime.cjs +401 -186
  78. package/dist/chat/runtime.cjs.map +1 -1
  79. package/dist/chat/runtime.d.cts +92 -28
  80. package/dist/chat/runtime.d.ts +92 -28
  81. package/dist/chat/runtime.js +401 -186
  82. package/dist/chat/runtime.js.map +1 -1
  83. package/dist/chat/server.cjs +2234 -209
  84. package/dist/chat/server.cjs.map +1 -1
  85. package/dist/chat/server.d.cts +451 -90
  86. package/dist/chat/server.d.ts +451 -90
  87. package/dist/chat/server.js +2221 -210
  88. package/dist/chat/server.js.map +1 -1
  89. package/dist/chat/sessions.cjs +25 -43
  90. package/dist/chat/sessions.cjs.map +1 -1
  91. package/dist/chat/sessions.d.cts +37 -118
  92. package/dist/chat/sessions.d.ts +37 -118
  93. package/dist/chat/sessions.js +25 -43
  94. package/dist/chat/sessions.js.map +1 -1
  95. package/dist/chat/sqlite.cjs +441 -0
  96. package/dist/chat/sqlite.cjs.map +1 -0
  97. package/dist/chat/sqlite.d.cts +128 -0
  98. package/dist/chat/sqlite.d.ts +128 -0
  99. package/dist/chat/sqlite.js +435 -0
  100. package/dist/chat/sqlite.js.map +1 -0
  101. package/dist/chat/state.cjs +14 -1
  102. package/dist/chat/state.cjs.map +1 -1
  103. package/dist/chat/state.d.cts +5 -2
  104. package/dist/chat/state.d.ts +5 -2
  105. package/dist/chat/state.js +14 -1
  106. package/dist/chat/state.js.map +1 -1
  107. package/dist/chat/storage.cjs +19 -10
  108. package/dist/chat/storage.cjs.map +1 -1
  109. package/dist/chat/storage.d.cts +11 -5
  110. package/dist/chat/storage.d.ts +11 -5
  111. package/dist/chat/storage.js +19 -10
  112. package/dist/chat/storage.js.map +1 -1
  113. package/dist/errors-C-so0M4t.d.cts +33 -0
  114. package/dist/errors-C-so0M4t.d.ts +33 -0
  115. package/dist/errors-CmVvczxZ.d.cts +28 -0
  116. package/dist/errors-CmVvczxZ.d.ts +28 -0
  117. package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-C1JnJGVR.d.ts} +28 -23
  118. package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-C7DSqPyX.d.cts} +28 -23
  119. package/dist/index.cjs +340 -46
  120. package/dist/index.cjs.map +1 -1
  121. package/dist/index.d.cts +292 -123
  122. package/dist/index.d.ts +292 -123
  123. package/dist/index.js +334 -47
  124. package/dist/index.js.map +1 -1
  125. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  126. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  127. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  128. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  129. package/dist/testing.cjs +383 -0
  130. package/dist/testing.cjs.map +1 -0
  131. package/dist/testing.d.cts +132 -0
  132. package/dist/testing.d.ts +132 -0
  133. package/dist/testing.js +377 -0
  134. package/dist/testing.js.map +1 -0
  135. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  136. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  137. package/dist/{transport-DX1Nhm4N.d.cts → transport-Cdh3M0tS.d.cts} +5 -4
  138. package/dist/{transport-D1OaUgRk.d.ts → transport-Ciap4PWK.d.ts} +5 -4
  139. package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
  140. package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
  141. package/dist/types-DRgd_9R7.d.cts +363 -0
  142. package/dist/types-ajANVzf7.d.ts +363 -0
  143. package/package.json +31 -6
  144. package/dist/errors-BDLbNu9w.d.cts +0 -13
  145. package/dist/errors-BDLbNu9w.d.ts +0 -13
  146. package/dist/types-DLZzlJxt.d.ts +0 -39
  147. package/dist/types-tE0CXwBl.d.cts +0 -39
@@ -1,8 +1,5 @@
1
- import * as fs from 'fs';
2
1
  import { existsSync, readdirSync, unlinkSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
- import * as path from 'path';
4
2
  import { join } from 'path';
5
- import * as os from 'os';
6
3
 
7
4
  var __defProp = Object.defineProperty;
8
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -25,16 +22,100 @@ var __copyProps = (to, from, except, desc) => {
25
22
  };
26
23
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
24
 
28
- // src/errors.ts
29
- var AgentSDKError, ReentrancyError, DisposedError, BackendNotFoundError, BackendAlreadyRegisteredError, SubprocessError, DependencyError, AbortError, ToolExecutionError, StructuredOutputError;
25
+ // src/types/errors.ts
26
+ function isRecoverableErrorCode(code) {
27
+ return RECOVERABLE_CODES.has(code);
28
+ }
29
+ function classifyAgentError(error) {
30
+ const msg = (error instanceof Error ? error.message : error).toLowerCase();
31
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("timedout") || msg.includes("etimedout")) {
32
+ return "TIMEOUT" /* TIMEOUT */;
33
+ }
34
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429") || msg.includes("too many requests")) {
35
+ return "RATE_LIMIT" /* RATE_LIMIT */;
36
+ }
37
+ if (msg.includes("unauthorized") || msg.includes("401") || msg.includes("auth") && (msg.includes("expired") || msg.includes("invalid") || msg.includes("denied") || msg.includes("failed"))) {
38
+ return "AUTH_EXPIRED" /* AUTH_EXPIRED */;
39
+ }
40
+ if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("network") || msg.includes("fetch failed") || msg.includes("socket hang up")) {
41
+ return "NETWORK" /* NETWORK */;
42
+ }
43
+ if (msg.includes("subprocess") || msg.includes("process exited") || msg.includes("spawn") || msg.includes("enoent") || msg.includes("killed")) {
44
+ return "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */;
45
+ }
46
+ if (msg.includes("abort") || msg.includes("cancel")) {
47
+ return "ABORTED" /* ABORTED */;
48
+ }
49
+ 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")) {
50
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
51
+ }
52
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
53
+ }
54
+ var ErrorCode, RECOVERABLE_CODES;
30
55
  var init_errors = __esm({
56
+ "src/types/errors.ts"() {
57
+ ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
58
+ ErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
59
+ ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
60
+ ErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
61
+ ErrorCode2["NETWORK"] = "NETWORK";
62
+ ErrorCode2["TIMEOUT"] = "TIMEOUT";
63
+ ErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
64
+ ErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
65
+ ErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
66
+ ErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
67
+ ErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
68
+ ErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
69
+ ErrorCode2["REENTRANCY"] = "REENTRANCY";
70
+ ErrorCode2["DISPOSED"] = "DISPOSED";
71
+ ErrorCode2["ABORTED"] = "ABORTED";
72
+ ErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
73
+ ErrorCode2["DEPENDENCY_MISSING"] = "DEPENDENCY_MISSING";
74
+ ErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
75
+ ErrorCode2["TOOL_EXECUTION"] = "TOOL_EXECUTION";
76
+ ErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
77
+ ErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
78
+ ErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
79
+ ErrorCode2["PROVIDER_NOT_FOUND"] = "PROVIDER_NOT_FOUND";
80
+ ErrorCode2["AUTH_REQUIRED"] = "AUTH_REQUIRED";
81
+ ErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
82
+ ErrorCode2["STORAGE_NOT_FOUND"] = "STORAGE_NOT_FOUND";
83
+ ErrorCode2["STORAGE_DUPLICATE_KEY"] = "STORAGE_DUPLICATE_KEY";
84
+ ErrorCode2["STORAGE_IO_ERROR"] = "STORAGE_IO_ERROR";
85
+ ErrorCode2["STORAGE_SERIALIZATION_ERROR"] = "STORAGE_SERIALIZATION_ERROR";
86
+ return ErrorCode2;
87
+ })(ErrorCode || {});
88
+ RECOVERABLE_CODES = /* @__PURE__ */ new Set([
89
+ "TIMEOUT" /* TIMEOUT */,
90
+ "RATE_LIMIT" /* RATE_LIMIT */,
91
+ "NETWORK" /* NETWORK */,
92
+ "TOOL_EXECUTION" /* TOOL_EXECUTION */,
93
+ "MODEL_OVERLOADED" /* MODEL_OVERLOADED */,
94
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
95
+ ]);
96
+ }
97
+ });
98
+
99
+ // src/errors.ts
100
+ var AgentSDKError, ReentrancyError, DisposedError, SubprocessError, DependencyError, AbortError, ToolExecutionError, ActivityTimeoutError;
101
+ var init_errors2 = __esm({
31
102
  "src/errors.ts"() {
103
+ init_errors();
32
104
  AgentSDKError = class extends Error {
33
105
  /** @internal Marker for cross-bundle identity checks */
34
106
  _agentSDKError = true;
107
+ /** Machine-readable error code. Prefer values from the ErrorCode enum. */
108
+ code;
109
+ /** Whether this error is safe to retry */
110
+ retryable;
111
+ /** HTTP status code hint for error classification */
112
+ httpStatus;
35
113
  constructor(message, options) {
36
114
  super(message, options);
37
115
  this.name = "AgentSDKError";
116
+ this.code = options?.code;
117
+ this.retryable = options?.retryable ?? false;
118
+ this.httpStatus = options?.httpStatus;
38
119
  }
39
120
  /** Check if an error is an AgentSDKError (works across bundled copies) */
40
121
  static is(error) {
@@ -43,83 +124,84 @@ var init_errors = __esm({
43
124
  };
44
125
  ReentrancyError = class extends AgentSDKError {
45
126
  constructor() {
46
- super("Agent is already running. Await the current run before starting another.");
127
+ super("Agent is already running. Await the current run before starting another.", {
128
+ code: "REENTRANCY" /* REENTRANCY */
129
+ });
47
130
  this.name = "ReentrancyError";
48
131
  }
49
132
  };
50
133
  DisposedError = class extends AgentSDKError {
51
134
  constructor(entity) {
52
- super(`${entity} has been disposed and cannot be used.`);
135
+ super(`${entity} has been disposed and cannot be used.`, {
136
+ code: "DISPOSED" /* DISPOSED */
137
+ });
53
138
  this.name = "DisposedError";
54
139
  }
55
140
  };
56
- BackendNotFoundError = class extends AgentSDKError {
57
- constructor(backend) {
58
- super(
59
- `Unknown backend: "${backend}". Built-in: copilot, claude, vercel-ai. Custom: use registerBackend() first.`
60
- );
61
- this.name = "BackendNotFoundError";
62
- }
63
- };
64
- BackendAlreadyRegisteredError = class extends AgentSDKError {
65
- constructor(backend) {
66
- super(`Backend "${backend}" is already registered. Use a different name or unregister first.`);
67
- this.name = "BackendAlreadyRegisteredError";
68
- }
69
- };
70
141
  SubprocessError = class extends AgentSDKError {
71
142
  constructor(message, options) {
72
- super(message, options);
143
+ super(message, { ...options, code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */ });
73
144
  this.name = "SubprocessError";
74
145
  }
75
146
  };
76
147
  DependencyError = class extends AgentSDKError {
77
148
  packageName;
78
149
  constructor(packageName) {
79
- super(`${packageName} is not installed. Install it: npm install ${packageName}`);
150
+ super(`${packageName} is not installed. Install it: npm install ${packageName}`, {
151
+ code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */
152
+ });
80
153
  this.name = "DependencyError";
81
154
  this.packageName = packageName;
82
155
  }
83
156
  };
84
157
  AbortError = class extends AgentSDKError {
85
158
  constructor() {
86
- super("Agent run was aborted.");
159
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
87
160
  this.name = "AbortError";
88
161
  }
89
162
  };
90
163
  ToolExecutionError = class extends AgentSDKError {
91
164
  toolName;
92
165
  constructor(toolName, message, options) {
93
- super(`Tool "${toolName}" failed: ${message}`, options);
166
+ super(`Tool "${toolName}" failed: ${message}`, { ...options, code: "TOOL_EXECUTION" /* TOOL_EXECUTION */ });
94
167
  this.name = "ToolExecutionError";
95
168
  this.toolName = toolName;
96
169
  }
97
170
  };
98
- StructuredOutputError = class extends AgentSDKError {
99
- constructor(message, options) {
100
- super(`Structured output error: ${message}`, options);
101
- this.name = "StructuredOutputError";
171
+ ActivityTimeoutError = class extends AgentSDKError {
172
+ constructor(timeoutMs) {
173
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
174
+ code: "TIMEOUT" /* TIMEOUT */,
175
+ retryable: true
176
+ });
177
+ this.name = "ActivityTimeoutError";
102
178
  }
103
179
  };
104
180
  }
105
181
  });
106
182
 
107
- // src/types.ts
108
- function isToolDefinition(tool) {
109
- return "execute" in tool && typeof tool.execute === "function";
110
- }
111
- function isTextContent(content) {
112
- return typeof content === "string";
113
- }
114
- function isMultiPartContent(content) {
115
- return Array.isArray(content);
116
- }
183
+ // src/types/guards.ts
117
184
  function getTextContent(content) {
118
185
  if (typeof content === "string") return content;
119
186
  return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
120
187
  }
188
+ var init_guards = __esm({
189
+ "src/types/guards.ts"() {
190
+ }
191
+ });
192
+
193
+ // src/types/index.ts
121
194
  var init_types = __esm({
195
+ "src/types/index.ts"() {
196
+ init_errors();
197
+ init_guards();
198
+ }
199
+ });
200
+
201
+ // src/types.ts
202
+ var init_types2 = __esm({
122
203
  "src/types.ts"() {
204
+ init_types();
123
205
  }
124
206
  });
125
207
 
@@ -127,12 +209,15 @@ var init_types = __esm({
127
209
  var BaseAgent;
128
210
  var init_base_agent = __esm({
129
211
  "src/base-agent.ts"() {
212
+ init_errors2();
213
+ init_errors2();
130
214
  init_errors();
131
215
  BaseAgent = class {
132
216
  state = "idle";
133
217
  abortController = null;
134
218
  config;
135
219
  _cleanupExternalSignal = null;
220
+ _streamMiddleware = [];
136
221
  /** CLI session ID for persistent mode. Override in backends that support it. */
137
222
  get sessionId() {
138
223
  return void 0;
@@ -148,8 +233,11 @@ var init_base_agent = __esm({
148
233
  this.state = "running";
149
234
  try {
150
235
  const messages = [{ role: "user", content: prompt }];
151
- const result = await this.executeRun(messages, options, ac.signal);
152
- this.enrichAndNotifyUsage(result);
236
+ const result = await this.withRetry(
237
+ () => this.executeRun(messages, options, ac.signal),
238
+ options
239
+ );
240
+ this.enrichAndNotifyUsage(result, options);
153
241
  return result;
154
242
  } finally {
155
243
  this.cleanupRun();
@@ -161,8 +249,11 @@ var init_base_agent = __esm({
161
249
  const ac = this.createAbortController(options?.signal);
162
250
  this.state = "running";
163
251
  try {
164
- const result = await this.executeRun(messages, options, ac.signal);
165
- this.enrichAndNotifyUsage(result);
252
+ const result = await this.withRetry(
253
+ () => this.executeRun(messages, options, ac.signal),
254
+ options
255
+ );
256
+ this.enrichAndNotifyUsage(result, options);
166
257
  return result;
167
258
  } finally {
168
259
  this.cleanupRun();
@@ -175,13 +266,11 @@ var init_base_agent = __esm({
175
266
  this.state = "running";
176
267
  try {
177
268
  const messages = [{ role: "user", content: prompt }];
178
- const result = await this.executeRunStructured(
179
- messages,
180
- schema,
181
- options,
182
- ac.signal
269
+ const result = await this.withRetry(
270
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
271
+ options
183
272
  );
184
- this.enrichAndNotifyUsage(result);
273
+ this.enrichAndNotifyUsage(result, options);
185
274
  return result;
186
275
  } finally {
187
276
  this.cleanupRun();
@@ -194,8 +283,10 @@ var init_base_agent = __esm({
194
283
  this.state = "streaming";
195
284
  try {
196
285
  const messages = [{ role: "user", content: prompt }];
197
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
198
- yield* this.heartbeatStream(enriched);
286
+ yield* this.streamWithRetry(
287
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
288
+ options
289
+ );
199
290
  } finally {
200
291
  this.cleanupRun();
201
292
  }
@@ -206,12 +297,37 @@ var init_base_agent = __esm({
206
297
  const ac = this.createAbortController(options?.signal);
207
298
  this.state = "streaming";
208
299
  try {
209
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
210
- yield* this.heartbeatStream(enriched);
300
+ yield* this.streamWithRetry(
301
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
302
+ options
303
+ );
211
304
  } finally {
212
305
  this.cleanupRun();
213
306
  }
214
307
  }
308
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
309
+ addStreamMiddleware(middleware) {
310
+ this.guardDisposed();
311
+ this._streamMiddleware.push(middleware);
312
+ }
313
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
314
+ async *applyStreamPipeline(source, options, ac) {
315
+ let stream = this.enrichStream(source, options);
316
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
317
+ stream = this.heartbeatStream(stream);
318
+ if (this._streamMiddleware.length > 0) {
319
+ const ctx = {
320
+ model: options.model,
321
+ backend: this.backendName,
322
+ abortController: ac,
323
+ config: Object.freeze({ ...this.config })
324
+ };
325
+ for (const mw of this._streamMiddleware) {
326
+ stream = mw(stream, ctx);
327
+ }
328
+ }
329
+ yield* stream;
330
+ }
215
331
  abort() {
216
332
  if (this.abortController) {
217
333
  this.abortController.abort();
@@ -234,26 +350,109 @@ var init_base_agent = __esm({
234
350
  this.abort();
235
351
  this.state = "disposed";
236
352
  }
353
+ // ─── Retry Logic ─────────────────────────────────────────────
354
+ /** Check if an error should be retried given the retry configuration. */
355
+ isRetryableError(error, retry) {
356
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
357
+ return false;
358
+ }
359
+ if (AgentSDKError.is(error)) {
360
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
361
+ return retry.retryableErrors.includes(error.code);
362
+ }
363
+ if (error.retryable) return true;
364
+ if (error.code) return isRecoverableErrorCode(error.code);
365
+ }
366
+ return false;
367
+ }
368
+ /** Execute a function with retry logic per RetryConfig. */
369
+ async withRetry(fn, options) {
370
+ const retry = options?.retry;
371
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
372
+ return fn();
373
+ }
374
+ const maxRetries = retry.maxRetries;
375
+ const initialDelay = retry.initialDelayMs ?? 1e3;
376
+ const multiplier = retry.backoffMultiplier ?? 2;
377
+ let lastError;
378
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
379
+ try {
380
+ return await fn();
381
+ } catch (err) {
382
+ lastError = err;
383
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
384
+ throw err;
385
+ }
386
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
387
+ await new Promise((resolve) => setTimeout(resolve, delay2));
388
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
389
+ throw err;
390
+ }
391
+ }
392
+ }
393
+ throw lastError;
394
+ }
395
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
396
+ async *streamWithRetry(factory, options) {
397
+ const retry = options?.retry;
398
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
399
+ yield* factory();
400
+ return;
401
+ }
402
+ const maxRetries = retry.maxRetries;
403
+ const initialDelay = retry.initialDelayMs ?? 1e3;
404
+ const multiplier = retry.backoffMultiplier ?? 2;
405
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
406
+ try {
407
+ const stream = factory();
408
+ const iterator = stream[Symbol.asyncIterator]();
409
+ const first = await iterator.next();
410
+ if (first.done) return;
411
+ yield first.value;
412
+ while (true) {
413
+ const next = await iterator.next();
414
+ if (next.done) break;
415
+ yield next.value;
416
+ }
417
+ return;
418
+ } catch (err) {
419
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
420
+ throw err;
421
+ }
422
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
423
+ await new Promise((resolve) => setTimeout(resolve, delay2));
424
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
425
+ throw err;
426
+ }
427
+ }
428
+ }
429
+ }
430
+ // ─── CallOptions Resolution ──────────────────────────────────
431
+ /** Resolve tools to use for this call (per-call override > config default) */
432
+ resolveTools(options) {
433
+ return options?.tools ?? this.config.tools ?? [];
434
+ }
237
435
  // ─── Usage Enrichment ───────────────────────────────────────────
238
436
  /** Enrich result usage with model/backend and fire onUsage callback */
239
- enrichAndNotifyUsage(result) {
437
+ enrichAndNotifyUsage(result, options) {
240
438
  if (result.usage) {
241
439
  result.usage = {
242
440
  ...result.usage,
243
- model: this.config.model,
441
+ model: options.model,
244
442
  backend: this.backendName
245
443
  };
246
444
  this.callOnUsage(result.usage);
247
445
  }
248
446
  }
249
447
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
250
- async *enrichStream(source) {
448
+ async *enrichStream(source, options) {
449
+ const model = options.model;
251
450
  for await (const event of source) {
252
451
  if (event.type === "usage_update") {
253
452
  const usage = {
254
453
  promptTokens: event.promptTokens,
255
454
  completionTokens: event.completionTokens,
256
- model: this.config.model,
455
+ model,
257
456
  backend: this.backendName
258
457
  };
259
458
  this.callOnUsage(usage);
@@ -289,9 +488,9 @@ var init_base_agent = __esm({
289
488
  let heartbeatResolve = null;
290
489
  const timer = setInterval(() => {
291
490
  if (heartbeatResolve) {
292
- const resolve2 = heartbeatResolve;
491
+ const resolve = heartbeatResolve;
293
492
  heartbeatResolve = null;
294
- resolve2();
493
+ resolve();
295
494
  }
296
495
  }, interval);
297
496
  try {
@@ -299,8 +498,8 @@ var init_base_agent = __esm({
299
498
  if (!pendingEvent) {
300
499
  pendingEvent = iterator.next();
301
500
  }
302
- const heartbeatPromise = new Promise((resolve2) => {
303
- heartbeatResolve = resolve2;
501
+ const heartbeatPromise = new Promise((resolve) => {
502
+ heartbeatResolve = resolve;
304
503
  });
305
504
  const eventDone = pendingEvent.then(
306
505
  (r) => ({ kind: "event", result: r })
@@ -323,6 +522,35 @@ var init_base_agent = __esm({
323
522
  heartbeatResolve = null;
324
523
  }
325
524
  }
525
+ // ─── Activity Timeout ────────────────────────────────────────
526
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
527
+ * When timeoutMs is not set, passes through directly. */
528
+ async *activityTimeoutStream(source, timeoutMs, ac) {
529
+ if (!timeoutMs || timeoutMs <= 0) {
530
+ yield* source;
531
+ return;
532
+ }
533
+ const iterator = source[Symbol.asyncIterator]();
534
+ let timerId;
535
+ try {
536
+ while (true) {
537
+ const timeoutPromise = new Promise((_, reject) => {
538
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
539
+ });
540
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
541
+ clearTimeout(timerId);
542
+ if (result.done) break;
543
+ yield result.value;
544
+ }
545
+ } catch (err) {
546
+ if (err instanceof ActivityTimeoutError) {
547
+ ac.abort(err);
548
+ }
549
+ throw err;
550
+ } finally {
551
+ clearTimeout(timerId);
552
+ }
553
+ }
326
554
  // ─── Guards ───────────────────────────────────────────────────
327
555
  guardReentrancy() {
328
556
  if (this.state === "running" || this.state === "streaming") {
@@ -427,6 +655,66 @@ var init_schema = __esm({
427
655
  }
428
656
  });
429
657
 
658
+ // src/backends/shared.ts
659
+ function extractLastUserPrompt(messages) {
660
+ for (let i = messages.length - 1; i >= 0; i--) {
661
+ const msg = messages[i];
662
+ if (msg.role === "user") {
663
+ return getTextContent(msg.content);
664
+ }
665
+ }
666
+ return "";
667
+ }
668
+ function serializeToolCall(tc) {
669
+ const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
670
+ return ` Tool call: ${tc.name}(${args})`;
671
+ }
672
+ function serializeToolResult(tr) {
673
+ const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
674
+ const prefix = tr.isError ? "[ERROR] " : "";
675
+ return ` ${tr.name} \u2192 ${prefix}${result}`;
676
+ }
677
+ function buildContextualPrompt(messages) {
678
+ if (messages.length <= 1) {
679
+ return extractLastUserPrompt(messages);
680
+ }
681
+ const history = messages.slice(0, -1).map((msg) => {
682
+ if (msg.role === "user") {
683
+ return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
684
+ }
685
+ if (msg.role === "tool" && msg.toolResults) {
686
+ const results = msg.toolResults.map(serializeToolResult).join("\n");
687
+ return `Tool results:
688
+ ${results}`;
689
+ }
690
+ if (msg.role === "assistant") {
691
+ const parts = [];
692
+ const thinking = msg.thinking;
693
+ if (thinking) {
694
+ parts.push(`[reasoning: ${thinking}]`);
695
+ }
696
+ const text2 = msg.content ? getTextContent(msg.content) : "";
697
+ if (text2) parts.push(text2);
698
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
699
+ parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
700
+ }
701
+ return `Assistant: ${parts.join("\n")}`;
702
+ }
703
+ const text = msg.content ? getTextContent(msg.content) : "";
704
+ return `${msg.role}: ${text}`;
705
+ }).join("\n");
706
+ const lastPrompt = extractLastUserPrompt(messages);
707
+ return `Conversation history:
708
+ ${history}
709
+
710
+ User: ${lastPrompt}`;
711
+ }
712
+ var init_shared = __esm({
713
+ "src/backends/shared.ts"() {
714
+ init_types2();
715
+ }
716
+ });
717
+
430
718
  // src/backends/copilot.ts
431
719
  var copilot_exports = {};
432
720
  __export(copilot_exports, {
@@ -435,10 +723,9 @@ __export(copilot_exports, {
435
723
  createCopilotService: () => createCopilotService
436
724
  });
437
725
  async function loadSDK() {
438
- if (sdkModule) return sdkModule;
726
+ if (_sdkMock) return _sdkMock;
439
727
  try {
440
- sdkModule = await import('@github/copilot-sdk');
441
- return sdkModule;
728
+ return await import('@github/copilot-sdk');
442
729
  } catch {
443
730
  throw new SubprocessError(
444
731
  "@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
@@ -446,10 +733,10 @@ async function loadSDK() {
446
733
  }
447
734
  }
448
735
  function _injectSDK(mock) {
449
- sdkModule = mock;
736
+ _sdkMock = mock;
450
737
  }
451
738
  function _resetSDK() {
452
- sdkModule = null;
739
+ _sdkMock = null;
453
740
  }
454
741
  function mapToolsToSDK(tools) {
455
742
  return tools.map((tool) => ({
@@ -494,6 +781,7 @@ function buildPermissionHandler(config) {
494
781
  const unifiedRequest = {
495
782
  toolName,
496
783
  toolArgs: { ...request },
784
+ toolCallId: request.toolCallId,
497
785
  rawSDKRequest: request
498
786
  };
499
787
  const ac = new AbortController();
@@ -598,15 +886,21 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
598
886
  };
599
887
  case "session.error":
600
888
  console.error("[copilot] mapSessionEvent error:", JSON.stringify(data));
601
- return {
602
- type: "error",
603
- error: String(data.message ?? "Unknown error"),
604
- recoverable: false
605
- };
889
+ {
890
+ const errorMsg = String(data.message ?? "Unknown error");
891
+ const code = classifyAgentError(errorMsg);
892
+ return {
893
+ type: "error",
894
+ error: errorMsg,
895
+ recoverable: isRecoverableErrorCode(code),
896
+ code
897
+ };
898
+ }
606
899
  case "assistant.message": {
607
900
  const doneEvent = {
608
901
  type: "done",
609
- finalOutput: data.content ? String(data.content) : null
902
+ finalOutput: null,
903
+ streamed: true
610
904
  };
611
905
  if (thinkingTracker.endThinking()) {
612
906
  return [{ type: "thinking_end" }, doneEvent];
@@ -617,66 +911,13 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
617
911
  return null;
618
912
  }
619
913
  }
620
- function extractLastUserPrompt(messages) {
621
- for (let i = messages.length - 1; i >= 0; i--) {
622
- const msg = messages[i];
623
- if (msg.role === "user") {
624
- return getTextContent(msg.content);
625
- }
626
- }
627
- return "";
628
- }
629
- function serializeToolCall(tc) {
630
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
631
- return ` Tool call: ${tc.name}(${args})`;
632
- }
633
- function serializeToolResult(tr) {
634
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
635
- const prefix = tr.isError ? "[ERROR] " : "";
636
- return ` ${tr.name} \u2192 ${prefix}${result}`;
637
- }
638
- function buildContextualPrompt(messages) {
639
- if (messages.length <= 1) {
640
- return extractLastUserPrompt(messages);
641
- }
642
- const history = messages.slice(0, -1).map((msg) => {
643
- if (msg.role === "user") {
644
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
645
- }
646
- if (msg.role === "tool" && msg.toolResults) {
647
- const results = msg.toolResults.map(serializeToolResult).join("\n");
648
- return `Tool results:
649
- ${results}`;
650
- }
651
- if (msg.role === "assistant") {
652
- const parts = [];
653
- const thinking = msg.thinking;
654
- if (thinking) {
655
- parts.push(`[reasoning: ${thinking}]`);
656
- }
657
- const text2 = msg.content ? getTextContent(msg.content) : "";
658
- if (text2) parts.push(text2);
659
- if (msg.toolCalls && msg.toolCalls.length > 0) {
660
- parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
661
- }
662
- return `Assistant: ${parts.join("\n")}`;
663
- }
664
- const text = msg.content ? getTextContent(msg.content) : "";
665
- return `${msg.role}: ${text}`;
666
- }).join("\n");
667
- const lastPrompt = extractLastUserPrompt(messages);
668
- return `Conversation history:
669
- ${history}
670
-
671
- User: ${lastPrompt}`;
672
- }
673
914
  function withTimeout(promise, ms, message) {
674
- return new Promise((resolve2, reject) => {
915
+ return new Promise((resolve, reject) => {
675
916
  const timer = setTimeout(() => reject(new SubprocessError(message)), ms);
676
917
  promise.then(
677
918
  (val) => {
678
919
  clearTimeout(timer);
679
- resolve2(val);
920
+ resolve(val);
680
921
  },
681
922
  (err) => {
682
923
  clearTimeout(timer);
@@ -688,14 +929,15 @@ function withTimeout(promise, ms, message) {
688
929
  function createCopilotService(options) {
689
930
  return new CopilotAgentService(options);
690
931
  }
691
- var sdkModule, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
932
+ var _sdkMock, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
692
933
  var init_copilot = __esm({
693
934
  "src/backends/copilot.ts"() {
694
- init_types();
935
+ init_types2();
695
936
  init_base_agent();
696
- init_errors();
937
+ init_errors2();
697
938
  init_schema();
698
- sdkModule = null;
939
+ init_shared();
940
+ _sdkMock = null;
699
941
  ToolCallTracker = class {
700
942
  map = /* @__PURE__ */ new Map();
701
943
  trackStart(toolCallId, toolName, args) {
@@ -743,6 +985,7 @@ var init_copilot = __esm({
743
985
  isPersistent;
744
986
  persistentSession = null;
745
987
  _sessionId;
988
+ _persistentModel;
746
989
  activeSession = null;
747
990
  _resumeSessionId;
748
991
  _toolsReady = null;
@@ -793,47 +1036,63 @@ var init_copilot = __esm({
793
1036
  });
794
1037
  this.persistentSession = null;
795
1038
  this._sessionId = void 0;
1039
+ this._persistentModel = void 0;
796
1040
  }
797
1041
  }
798
- async getOrCreateSession(streaming) {
1042
+ async getOrCreateSession(streaming, options) {
799
1043
  if (this.isPersistent && this.persistentSession) {
800
- return { session: this.persistentSession, isNew: false };
1044
+ if (options.model !== this._persistentModel) {
1045
+ this.persistentSession.destroy().catch(() => {
1046
+ });
1047
+ this.persistentSession = null;
1048
+ this._sessionId = void 0;
1049
+ } else {
1050
+ return { session: this.persistentSession, isNew: false };
1051
+ }
801
1052
  }
802
1053
  if (this._toolsReady) {
803
1054
  await this._toolsReady;
804
1055
  this._toolsReady = null;
805
1056
  }
1057
+ const sessionConfig = { ...this.sessionConfig };
1058
+ sessionConfig.model = options.model;
1059
+ const resolvedTools = this.resolveTools(options);
1060
+ if (options?.tools) {
1061
+ sessionConfig.tools = mapToolsToSDK(resolvedTools);
1062
+ }
806
1063
  const client = await this.getClient();
807
1064
  if (this._resumeSessionId) {
808
1065
  const storedId = this._resumeSessionId;
809
1066
  this._resumeSessionId = void 0;
810
1067
  try {
811
1068
  const session2 = await client.resumeSession(storedId, {
812
- ...this.sessionConfig,
1069
+ ...sessionConfig,
813
1070
  streaming: this.isPersistent ? true : streaming
814
1071
  });
815
1072
  if (this.isPersistent) {
816
1073
  this.persistentSession = session2;
817
1074
  this._sessionId = session2.sessionId;
1075
+ this._persistentModel = options.model;
818
1076
  }
819
1077
  return { session: session2, isNew: false };
820
1078
  } catch {
821
1079
  }
822
1080
  }
823
1081
  const session = await client.createSession({
824
- ...this.sessionConfig,
1082
+ ...sessionConfig,
825
1083
  streaming: this.isPersistent ? true : streaming
826
1084
  });
827
1085
  if (this.isPersistent) {
828
1086
  this.persistentSession = session;
829
1087
  this._sessionId = session.sessionId;
1088
+ this._persistentModel = options.model;
830
1089
  }
831
1090
  return { session, isNew: true };
832
1091
  }
833
1092
  // ─── executeRun ─────────────────────────────────────────────────
834
- async executeRun(messages, _options, signal) {
1093
+ async executeRun(messages, options, signal) {
835
1094
  this.checkAbort(signal);
836
- const { session, isNew: isNewSession } = await this.getOrCreateSession(false);
1095
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(false, options);
837
1096
  this.activeSession = session;
838
1097
  const prompt = this.isPersistent && !isNewSession ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
839
1098
  const tracker = new ToolCallTracker();
@@ -930,9 +1189,9 @@ You MUST respond with ONLY valid JSON matching this schema:
930
1189
  };
931
1190
  }
932
1191
  // ─── executeStream ──────────────────────────────────────────────
933
- async *executeStream(messages, _options, signal) {
1192
+ async *executeStream(messages, options, signal) {
934
1193
  this.checkAbort(signal);
935
- const { session, isNew: isNewSession } = await this.getOrCreateSession(true);
1194
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(true, options);
936
1195
  this.activeSession = session;
937
1196
  if (isNewSession) {
938
1197
  yield this.emitSessionInfo(session.sessionId);
@@ -949,8 +1208,8 @@ You MUST respond with ONLY valid JSON matching this schema:
949
1208
  notify = null;
950
1209
  }
951
1210
  };
952
- const waitForItem = () => new Promise((resolve2) => {
953
- notify = resolve2;
1211
+ const waitForItem = () => new Promise((resolve) => {
1212
+ notify = resolve;
954
1213
  });
955
1214
  const unsubscribe = session.on((event) => {
956
1215
  const mapped = mapSessionEvent(event, tracker, thinkingTracker);
@@ -1078,7 +1337,10 @@ You MUST respond with ONLY valid JSON matching this schema:
1078
1337
  return models.map((m) => ({
1079
1338
  id: m.id,
1080
1339
  name: m.name,
1081
- provider: "copilot"
1340
+ provider: "copilot",
1341
+ ...m.capabilities?.limits?.max_context_window_tokens != null && {
1342
+ contextWindow: m.capabilities.limits.max_context_window_tokens
1343
+ }
1082
1344
  }));
1083
1345
  }
1084
1346
  async validate() {
@@ -1131,10 +1393,9 @@ function stripMcpPrefix(name) {
1131
1393
  return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
1132
1394
  }
1133
1395
  async function loadSDK2() {
1134
- if (sdkModule2) return sdkModule2;
1396
+ if (_sdkMock2) return _sdkMock2;
1135
1397
  try {
1136
- sdkModule2 = await import('@anthropic-ai/claude-agent-sdk');
1137
- return sdkModule2;
1398
+ return await import('@anthropic-ai/claude-agent-sdk');
1138
1399
  } catch {
1139
1400
  throw new SubprocessError(
1140
1401
  "@anthropic-ai/claude-agent-sdk is not installed. Install it: npm install @anthropic-ai/claude-agent-sdk"
@@ -1142,13 +1403,32 @@ async function loadSDK2() {
1142
1403
  }
1143
1404
  }
1144
1405
  function _injectSDK2(mock) {
1145
- sdkModule2 = mock;
1406
+ _sdkMock2 = mock;
1146
1407
  }
1147
1408
  function _resetSDK2() {
1148
- sdkModule2 = null;
1409
+ _sdkMock2 = null;
1149
1410
  }
1150
- function buildMcpServer(sdk, tools, toolResultCapture) {
1151
- if (tools.length === 0) return void 0;
1411
+ function normalizeAskUserInput(args) {
1412
+ if (typeof args.question === "string") {
1413
+ return {
1414
+ question: args.question,
1415
+ choices: Array.isArray(args.choices) ? args.choices : void 0,
1416
+ allowFreeform: args.allowFreeform !== false
1417
+ };
1418
+ }
1419
+ const questions = args.questions;
1420
+ if (questions && questions.length > 0) {
1421
+ const first = questions[0];
1422
+ return {
1423
+ question: first.question,
1424
+ choices: first.options?.map((o) => o.label),
1425
+ allowFreeform: true
1426
+ };
1427
+ }
1428
+ return { question: JSON.stringify(args), allowFreeform: true };
1429
+ }
1430
+ function buildMcpServer(sdk, tools, toolResultCapture, onAskUser) {
1431
+ if (tools.length === 0 && !onAskUser) return void 0;
1152
1432
  const mcpTools = tools.map((tool) => {
1153
1433
  const zodSchema = tool.parameters;
1154
1434
  const inputSchema = zodSchema.shape ?? zodToJsonSchema(tool.parameters);
@@ -1172,6 +1452,39 @@ function buildMcpServer(sdk, tools, toolResultCapture) {
1172
1452
  }
1173
1453
  );
1174
1454
  });
1455
+ if (onAskUser) {
1456
+ const askUserTool = sdk.tool(
1457
+ "ask_user",
1458
+ "Ask the user a question and wait for their response",
1459
+ {
1460
+ question: { type: "string", description: "The question to ask the user" },
1461
+ choices: {
1462
+ type: "array",
1463
+ items: { type: "string" },
1464
+ description: "Optional list of choices for multiple choice"
1465
+ },
1466
+ questions: {
1467
+ type: "array",
1468
+ items: {
1469
+ type: "object",
1470
+ properties: {
1471
+ question: { type: "string" },
1472
+ options: { type: "array", items: { type: "object", properties: { label: { type: "string" } } } }
1473
+ }
1474
+ },
1475
+ description: "Alternative nested question format"
1476
+ }
1477
+ },
1478
+ async (args) => {
1479
+ const normalized = normalizeAskUserInput(args);
1480
+ const response = await onAskUser(normalized, AbortSignal.timeout(3e5));
1481
+ return {
1482
+ content: [{ type: "text", text: response.answer }]
1483
+ };
1484
+ }
1485
+ );
1486
+ mcpTools.push(askUserTool);
1487
+ }
1175
1488
  return sdk.createSdkMcpServer({
1176
1489
  name: MCP_SERVER_NAME,
1177
1490
  version: "1.0.0",
@@ -1221,6 +1534,7 @@ function buildCanUseTool(config) {
1221
1534
  const unifiedRequest = {
1222
1535
  toolName,
1223
1536
  toolArgs: input,
1537
+ toolCallId: options.toolUseID,
1224
1538
  suggestedScope: extractSuggestedScope(options.suggestions),
1225
1539
  rawSDKRequest: { toolName, input, ...options }
1226
1540
  };
@@ -1273,6 +1587,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1273
1587
  if (block.type === "tool_use") {
1274
1588
  const toolCallId = String(block.id ?? "");
1275
1589
  const toolName = stripMcpPrefix(block.name ?? "unknown");
1590
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1276
1591
  if (toolCallTracker) {
1277
1592
  toolCallTracker.trackStart(toolCallId, toolName);
1278
1593
  }
@@ -1296,6 +1611,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1296
1611
  case "tool_use_summary": {
1297
1612
  const summary = msg.summary;
1298
1613
  const toolName = stripMcpPrefix(msg.tool_name ?? "unknown");
1614
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) return null;
1299
1615
  const precedingIds = msg.preceding_tool_use_ids;
1300
1616
  let toolCallId = "";
1301
1617
  if (precedingIds && precedingIds.length > 0) {
@@ -1349,10 +1665,13 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1349
1665
  }
1350
1666
  if (msg.is_error) {
1351
1667
  const r = msg;
1668
+ const errorMsg = r.errors?.join("; ") ?? "Unknown error";
1669
+ const code = classifyAgentError(errorMsg);
1352
1670
  return {
1353
1671
  type: "error",
1354
- error: r.errors?.join("; ") ?? "Unknown error",
1355
- recoverable: false
1672
+ error: errorMsg,
1673
+ recoverable: isRecoverableErrorCode(code),
1674
+ code
1356
1675
  };
1357
1676
  }
1358
1677
  return null;
@@ -1361,72 +1680,21 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1361
1680
  return null;
1362
1681
  }
1363
1682
  }
1364
- function extractLastUserPrompt2(messages) {
1365
- for (let i = messages.length - 1; i >= 0; i--) {
1366
- const msg = messages[i];
1367
- if (msg.role === "user") {
1368
- return getTextContent(msg.content);
1369
- }
1370
- }
1371
- return "";
1372
- }
1373
- function serializeToolCall2(tc) {
1374
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
1375
- return ` Tool call: ${tc.name}(${args})`;
1376
- }
1377
- function serializeToolResult2(tr) {
1378
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
1379
- const prefix = tr.isError ? "[ERROR] " : "";
1380
- return ` ${tr.name} \u2192 ${prefix}${result}`;
1381
- }
1382
- function buildContextualPrompt2(messages) {
1383
- if (messages.length <= 1) {
1384
- return extractLastUserPrompt2(messages);
1385
- }
1386
- const history = messages.slice(0, -1).map((msg) => {
1387
- if (msg.role === "user") {
1388
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
1389
- }
1390
- if (msg.role === "tool" && msg.toolResults) {
1391
- const results = msg.toolResults.map(serializeToolResult2).join("\n");
1392
- return `Tool results:
1393
- ${results}`;
1394
- }
1395
- if (msg.role === "assistant") {
1396
- const parts = [];
1397
- const thinking = msg.thinking;
1398
- if (thinking) {
1399
- parts.push(`[reasoning: ${thinking}]`);
1400
- }
1401
- const text2 = msg.content ? getTextContent(msg.content) : "";
1402
- if (text2) parts.push(text2);
1403
- if (msg.toolCalls && msg.toolCalls.length > 0) {
1404
- parts.push(msg.toolCalls.map(serializeToolCall2).join("\n"));
1405
- }
1406
- return `Assistant: ${parts.join("\n")}`;
1407
- }
1408
- const text = msg.content ? getTextContent(msg.content) : "";
1409
- return `${msg.role}: ${text}`;
1410
- }).join("\n");
1411
- const lastPrompt = extractLastUserPrompt2(messages);
1412
- return `Conversation history:
1413
- ${history}
1414
-
1415
- User: ${lastPrompt}`;
1416
- }
1417
1683
  function createClaudeService(options) {
1418
1684
  return new ClaudeAgentService(options);
1419
1685
  }
1420
- var MCP_SERVER_NAME, MCP_TOOL_PREFIX, sdkModule2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1686
+ var MCP_SERVER_NAME, MCP_TOOL_PREFIX, CLAUDE_INTERNAL_TOOL_NAMES, _sdkMock2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1421
1687
  var init_claude = __esm({
1422
1688
  "src/backends/claude.ts"() {
1423
- init_types();
1689
+ init_types2();
1424
1690
  init_base_agent();
1425
- init_errors();
1691
+ init_errors2();
1426
1692
  init_schema();
1693
+ init_shared();
1427
1694
  MCP_SERVER_NAME = "agent-sdk-tools";
1428
1695
  MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
1429
- sdkModule2 = null;
1696
+ CLAUDE_INTERNAL_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion"]);
1697
+ _sdkMock2 = null;
1430
1698
  ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
1431
1699
  ANTHROPIC_API_VERSION = "2023-06-01";
1432
1700
  ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
@@ -1471,11 +1739,6 @@ var init_claude = __esm({
1471
1739
  if (options.resumeSessionId) {
1472
1740
  this._sessionId = options.resumeSessionId;
1473
1741
  }
1474
- if (config.supervisor?.onAskUser) {
1475
- console.warn(
1476
- "[agent-sdk/claude] supervisor.onAskUser is not supported by the Claude CLI backend. User interaction requests from the model will not be forwarded."
1477
- );
1478
- }
1479
1742
  }
1480
1743
  get sessionId() {
1481
1744
  return this._sessionId;
@@ -1499,12 +1762,12 @@ var init_claude = __esm({
1499
1762
  const transcriptPath = home ? `${home}/.claude/projects/.session/sessions/${sessionId}/conversation.jsonl` : void 0;
1500
1763
  return { type: "session_info", sessionId, transcriptPath, backend: "claude" };
1501
1764
  }
1502
- buildQueryOptions(signal) {
1765
+ buildQueryOptions(signal, options) {
1503
1766
  const ac = new AbortController();
1504
1767
  signal.addEventListener("abort", () => ac.abort(), { once: true });
1505
1768
  const opts = {
1506
1769
  abortController: ac,
1507
- model: this.config.model,
1770
+ model: options.model,
1508
1771
  maxTurns: this.options.maxTurns,
1509
1772
  cwd: this.options.workingDirectory,
1510
1773
  pathToClaudeCodeExecutable: this.options.cliPath,
@@ -1534,25 +1797,85 @@ var init_claude = __esm({
1534
1797
  return opts;
1535
1798
  }
1536
1799
  async buildMcpConfig(opts, toolResultCapture) {
1537
- if (this.tools.length === 0) return opts;
1800
+ const onAskUser = this.config.supervisor?.onAskUser;
1801
+ if (this.tools.length === 0 && !onAskUser) return opts;
1538
1802
  const sdk = await loadSDK2();
1539
- const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture);
1803
+ const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture, onAskUser);
1540
1804
  if (mcpServer) {
1541
1805
  opts.mcpServers = {
1542
1806
  [MCP_SERVER_NAME]: mcpServer
1543
1807
  };
1544
1808
  const mcpToolNames = this.tools.map((t) => mcpToolName(t.name));
1809
+ if (onAskUser) {
1810
+ mcpToolNames.push(mcpToolName("ask_user"));
1811
+ }
1545
1812
  opts.allowedTools = [...opts.allowedTools ?? [], ...mcpToolNames];
1546
1813
  }
1814
+ if (onAskUser) {
1815
+ opts.disallowedTools = [...opts.disallowedTools ?? [], "AskUserQuestion"];
1816
+ }
1547
1817
  return opts;
1548
1818
  }
1819
+ // ─── Retry Helpers (shared across executeRun/RunStructured/Stream) ──
1820
+ /** Setup a retry query: clear session, rebuild with full history */
1821
+ async prepareRetryQuery(sdk, messages, signal, options, toolResultCapture, modifyOpts) {
1822
+ this.clearPersistentSession();
1823
+ const retryPrompt = buildContextualPrompt(messages);
1824
+ let retryOpts = this.buildQueryOptions(signal, options);
1825
+ toolResultCapture.clear();
1826
+ retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1827
+ modifyOpts?.(retryOpts);
1828
+ const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1829
+ this.activeQuery = retryQ;
1830
+ return retryQ;
1831
+ }
1832
+ /** Extract tool_use blocks from an assistant SDK message into toolCalls array */
1833
+ collectToolCallsFromMessage(msg, toolCalls, toolResultCapture) {
1834
+ if (msg.type !== "assistant") return;
1835
+ const betaMessage = msg.message;
1836
+ if (!betaMessage?.content) return;
1837
+ for (const block of betaMessage.content) {
1838
+ if (block.type === "tool_use") {
1839
+ const toolName = stripMcpPrefix(block.name ?? "unknown");
1840
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1841
+ toolCalls.push({
1842
+ toolName,
1843
+ args: block.input ?? {},
1844
+ result: toolResultCapture.get(toolName) ?? null,
1845
+ approved: true
1846
+ });
1847
+ }
1848
+ }
1849
+ }
1850
+ /** Back-fill tool results from capture map on summary/result messages */
1851
+ backfillToolResults(msg, toolCalls, toolResultCapture) {
1852
+ if (msg.type !== "tool_use_summary" && msg.type !== "result") return;
1853
+ for (const tc of toolCalls) {
1854
+ if (tc.result === null) {
1855
+ const captured = toolResultCapture.get(tc.toolName);
1856
+ if (captured !== void 0) tc.result = captured;
1857
+ }
1858
+ }
1859
+ }
1860
+ /** Wrap retry inner loop with shared error handling */
1861
+ async withRetryErrorHandling(signal, fn) {
1862
+ try {
1863
+ return await fn();
1864
+ } catch (retryError) {
1865
+ if (this.isPersistent) this.clearPersistentSession();
1866
+ if (signal.aborted) throw new AbortError();
1867
+ throw retryError;
1868
+ } finally {
1869
+ this.activeQuery = null;
1870
+ }
1871
+ }
1549
1872
  // ─── executeRun ─────────────────────────────────────────────────
1550
- async executeRun(messages, _options, signal) {
1873
+ async executeRun(messages, options, signal) {
1551
1874
  this.checkAbort(signal);
1552
1875
  const sdk = await loadSDK2();
1553
1876
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1554
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1555
- let opts = this.buildQueryOptions(signal);
1877
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1878
+ let opts = this.buildQueryOptions(signal, options);
1556
1879
  const toolResultCapture = /* @__PURE__ */ new Map();
1557
1880
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1558
1881
  const q = sdk.query({ prompt, options: opts });
@@ -1562,30 +1885,8 @@ var init_claude = __esm({
1562
1885
  let usage;
1563
1886
  try {
1564
1887
  for await (const msg of q) {
1565
- if (msg.type === "assistant") {
1566
- const betaMessage = msg.message;
1567
- if (betaMessage?.content) {
1568
- for (const block of betaMessage.content) {
1569
- if (block.type === "tool_use") {
1570
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1571
- toolCalls.push({
1572
- toolName,
1573
- args: block.input ?? {},
1574
- result: toolResultCapture.get(toolName) ?? null,
1575
- approved: true
1576
- });
1577
- }
1578
- }
1579
- }
1580
- }
1581
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1582
- for (const tc of toolCalls) {
1583
- if (tc.result === null) {
1584
- const captured = toolResultCapture.get(tc.toolName);
1585
- if (captured !== void 0) tc.result = captured;
1586
- }
1587
- }
1588
- }
1888
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1889
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1589
1890
  if (msg.type === "result") {
1590
1891
  if (msg.subtype === "success") {
1591
1892
  const r = msg;
@@ -1605,41 +1906,13 @@ var init_claude = __esm({
1605
1906
  } catch (e) {
1606
1907
  if (signal.aborted) throw new AbortError();
1607
1908
  if (isResuming && this.isPersistent) {
1608
- this.clearPersistentSession();
1609
- const retryPrompt = buildContextualPrompt2(messages);
1610
- let retryOpts = this.buildQueryOptions(signal);
1611
- toolResultCapture.clear();
1612
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1613
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1614
- this.activeQuery = retryQ;
1909
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1615
1910
  toolCalls.length = 0;
1616
1911
  output = null;
1617
- try {
1912
+ return this.withRetryErrorHandling(signal, async () => {
1618
1913
  for await (const msg of retryQ) {
1619
- if (msg.type === "assistant") {
1620
- const betaMessage = msg.message;
1621
- if (betaMessage?.content) {
1622
- for (const block of betaMessage.content) {
1623
- if (block.type === "tool_use") {
1624
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1625
- toolCalls.push({
1626
- toolName,
1627
- args: block.input ?? {},
1628
- result: toolResultCapture.get(toolName) ?? null,
1629
- approved: true
1630
- });
1631
- }
1632
- }
1633
- }
1634
- }
1635
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1636
- for (const tc of toolCalls) {
1637
- if (tc.result === null) {
1638
- const captured = toolResultCapture.get(tc.toolName);
1639
- if (captured !== void 0) tc.result = captured;
1640
- }
1641
- }
1642
- }
1914
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1915
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1643
1916
  if (msg.type === "result") {
1644
1917
  if (msg.subtype === "success") {
1645
1918
  const r = msg;
@@ -1656,23 +1929,17 @@ var init_claude = __esm({
1656
1929
  }
1657
1930
  }
1658
1931
  }
1659
- } catch (retryError) {
1660
- if (this.isPersistent) this.clearPersistentSession();
1661
- if (signal.aborted) throw new AbortError();
1662
- throw retryError;
1663
- } finally {
1664
- this.activeQuery = null;
1665
- }
1666
- return {
1667
- output,
1668
- structuredOutput: void 0,
1669
- toolCalls,
1670
- messages: [
1671
- ...messages,
1672
- ...output !== null ? [{ role: "assistant", content: output }] : []
1673
- ],
1674
- usage
1675
- };
1932
+ return {
1933
+ output,
1934
+ structuredOutput: void 0,
1935
+ toolCalls,
1936
+ messages: [
1937
+ ...messages,
1938
+ ...output !== null ? [{ role: "assistant", content: output }] : []
1939
+ ],
1940
+ usage
1941
+ };
1942
+ });
1676
1943
  }
1677
1944
  if (this.isPersistent) this.clearPersistentSession();
1678
1945
  throw e;
@@ -1691,12 +1958,12 @@ var init_claude = __esm({
1691
1958
  };
1692
1959
  }
1693
1960
  // ─── executeRunStructured ───────────────────────────────────────
1694
- async executeRunStructured(messages, schema, _options, signal) {
1961
+ async executeRunStructured(messages, schema, options, signal) {
1695
1962
  this.checkAbort(signal);
1696
1963
  const sdk = await loadSDK2();
1697
1964
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1698
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1699
- let opts = this.buildQueryOptions(signal);
1965
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1966
+ let opts = this.buildQueryOptions(signal, options);
1700
1967
  const toolResultCapture = /* @__PURE__ */ new Map();
1701
1968
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1702
1969
  const jsonSchema = zodToJsonSchema(schema.schema);
@@ -1712,30 +1979,8 @@ var init_claude = __esm({
1712
1979
  let usage;
1713
1980
  try {
1714
1981
  for await (const msg of q) {
1715
- if (msg.type === "assistant") {
1716
- const betaMessage = msg.message;
1717
- if (betaMessage?.content) {
1718
- for (const block of betaMessage.content) {
1719
- if (block.type === "tool_use") {
1720
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1721
- toolCalls.push({
1722
- toolName,
1723
- args: block.input ?? {},
1724
- result: toolResultCapture.get(toolName) ?? null,
1725
- approved: true
1726
- });
1727
- }
1728
- }
1729
- }
1730
- }
1731
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1732
- for (const tc of toolCalls) {
1733
- if (tc.result === null) {
1734
- const captured = toolResultCapture.get(tc.toolName);
1735
- if (captured !== void 0) tc.result = captured;
1736
- }
1737
- }
1738
- }
1982
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1983
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1739
1984
  if (msg.type === "result" && msg.subtype === "success") {
1740
1985
  const r = msg;
1741
1986
  output = r.result;
@@ -1770,46 +2015,23 @@ var init_claude = __esm({
1770
2015
  } catch (e) {
1771
2016
  if (signal.aborted) throw new AbortError();
1772
2017
  if (isResuming && this.isPersistent) {
1773
- this.clearPersistentSession();
1774
- const retryPrompt = buildContextualPrompt2(messages);
1775
- let retryOpts = this.buildQueryOptions(signal);
1776
- toolResultCapture.clear();
1777
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1778
- retryOpts.outputFormat = {
1779
- type: "json_schema",
1780
- schema: jsonSchema
1781
- };
1782
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1783
- this.activeQuery = retryQ;
2018
+ const retryQ = await this.prepareRetryQuery(
2019
+ sdk,
2020
+ messages,
2021
+ signal,
2022
+ options,
2023
+ toolResultCapture,
2024
+ (opts2) => {
2025
+ opts2.outputFormat = { type: "json_schema", schema: jsonSchema };
2026
+ }
2027
+ );
1784
2028
  toolCalls.length = 0;
1785
2029
  output = null;
1786
2030
  structuredOutput = void 0;
1787
- try {
2031
+ return this.withRetryErrorHandling(signal, async () => {
1788
2032
  for await (const msg of retryQ) {
1789
- if (msg.type === "assistant") {
1790
- const betaMessage = msg.message;
1791
- if (betaMessage?.content) {
1792
- for (const block of betaMessage.content) {
1793
- if (block.type === "tool_use") {
1794
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1795
- toolCalls.push({
1796
- toolName,
1797
- args: block.input ?? {},
1798
- result: toolResultCapture.get(toolName) ?? null,
1799
- approved: true
1800
- });
1801
- }
1802
- }
1803
- }
1804
- }
1805
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1806
- for (const tc of toolCalls) {
1807
- if (tc.result === null) {
1808
- const captured = toolResultCapture.get(tc.toolName);
1809
- if (captured !== void 0) tc.result = captured;
1810
- }
1811
- }
1812
- }
2033
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
2034
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1813
2035
  if (msg.type === "result" && msg.subtype === "success") {
1814
2036
  const r = msg;
1815
2037
  output = r.result;
@@ -1841,23 +2063,17 @@ var init_claude = __esm({
1841
2063
  );
1842
2064
  }
1843
2065
  }
1844
- } catch (retryError) {
1845
- if (this.isPersistent) this.clearPersistentSession();
1846
- if (signal.aborted) throw new AbortError();
1847
- throw retryError;
1848
- } finally {
1849
- this.activeQuery = null;
1850
- }
1851
- return {
1852
- output,
1853
- structuredOutput,
1854
- toolCalls,
1855
- messages: [
1856
- ...messages,
1857
- ...output !== null ? [{ role: "assistant", content: output }] : []
1858
- ],
1859
- usage
1860
- };
2066
+ return {
2067
+ output,
2068
+ structuredOutput,
2069
+ toolCalls,
2070
+ messages: [
2071
+ ...messages,
2072
+ ...output !== null ? [{ role: "assistant", content: output }] : []
2073
+ ],
2074
+ usage
2075
+ };
2076
+ });
1861
2077
  }
1862
2078
  if (this.isPersistent) this.clearPersistentSession();
1863
2079
  throw e;
@@ -1876,12 +2092,12 @@ var init_claude = __esm({
1876
2092
  };
1877
2093
  }
1878
2094
  // ─── executeStream ──────────────────────────────────────────────
1879
- async *executeStream(messages, _options, signal) {
2095
+ async *executeStream(messages, options, signal) {
1880
2096
  this.checkAbort(signal);
1881
2097
  const sdk = await loadSDK2();
1882
2098
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1883
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1884
- let opts = this.buildQueryOptions(signal);
2099
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
2100
+ let opts = this.buildQueryOptions(signal, options);
1885
2101
  const toolResultCapture = /* @__PURE__ */ new Map();
1886
2102
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1887
2103
  const q = sdk.query({ prompt, options: opts });
@@ -1889,6 +2105,7 @@ var init_claude = __esm({
1889
2105
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
1890
2106
  const toolCallTracker = new ClaudeToolCallTracker();
1891
2107
  const pendingStreamToolCalls = /* @__PURE__ */ new Map();
2108
+ let hasStreamedText = false;
1892
2109
  try {
1893
2110
  for await (const msg of q) {
1894
2111
  if (signal.aborted) throw new AbortError();
@@ -1906,6 +2123,7 @@ var init_claude = __esm({
1906
2123
  } else if (e.type === "tool_call_end") {
1907
2124
  pendingStreamToolCalls.delete(e.toolCallId);
1908
2125
  }
2126
+ if (e.type === "text_delta") hasStreamedText = true;
1909
2127
  yield e;
1910
2128
  }
1911
2129
  }
@@ -1929,22 +2147,21 @@ var init_claude = __esm({
1929
2147
  }
1930
2148
  yield this.emitSessionInfo(r.session_id);
1931
2149
  }
1932
- yield { type: "done", finalOutput: r.result };
2150
+ yield {
2151
+ type: "done",
2152
+ finalOutput: hasStreamedText ? null : r.result,
2153
+ ...hasStreamedText ? { streamed: true } : {}
2154
+ };
1933
2155
  }
1934
2156
  }
1935
2157
  } catch (e) {
1936
2158
  if (signal.aborted) throw new AbortError();
1937
2159
  if (isResuming && this.isPersistent) {
1938
- this.clearPersistentSession();
1939
- const retryPrompt = buildContextualPrompt2(messages);
1940
- let retryOpts = this.buildQueryOptions(signal);
1941
- toolResultCapture.clear();
1942
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1943
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1944
- this.activeQuery = retryQ;
2160
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1945
2161
  const retryThinkingBlockIndices = /* @__PURE__ */ new Set();
1946
2162
  const retryToolCallTracker = new ClaudeToolCallTracker();
1947
2163
  const retryPendingToolCalls = /* @__PURE__ */ new Map();
2164
+ let retryHasStreamedText = false;
1948
2165
  try {
1949
2166
  for await (const msg of retryQ) {
1950
2167
  if (signal.aborted) throw new AbortError();
@@ -1962,6 +2179,7 @@ var init_claude = __esm({
1962
2179
  } else if (ev.type === "tool_call_end") {
1963
2180
  retryPendingToolCalls.delete(ev.toolCallId);
1964
2181
  }
2182
+ if (ev.type === "text_delta") retryHasStreamedText = true;
1965
2183
  yield ev;
1966
2184
  }
1967
2185
  }
@@ -1985,7 +2203,11 @@ var init_claude = __esm({
1985
2203
  }
1986
2204
  yield this.emitSessionInfo(r.session_id);
1987
2205
  }
1988
- yield { type: "done", finalOutput: r.result };
2206
+ yield {
2207
+ type: "done",
2208
+ finalOutput: retryHasStreamedText ? null : r.result,
2209
+ ...retryHasStreamedText ? { streamed: true } : {}
2210
+ };
1989
2211
  }
1990
2212
  }
1991
2213
  } catch (retryError) {
@@ -2047,7 +2269,8 @@ var init_claude = __esm({
2047
2269
  this.cachedModels = body.data.map((m) => ({
2048
2270
  id: m.id,
2049
2271
  name: m.display_name,
2050
- provider: "claude"
2272
+ provider: "claude",
2273
+ ...m.max_input_tokens != null && { contextWindow: m.max_input_tokens }
2051
2274
  }));
2052
2275
  return this.cachedModels;
2053
2276
  }
@@ -2105,32 +2328,30 @@ __export(vercel_ai_exports, {
2105
2328
  createVercelAIService: () => createVercelAIService
2106
2329
  });
2107
2330
  async function loadSDK3() {
2108
- if (sdkModule3) return sdkModule3;
2331
+ if (_sdkMock3) return _sdkMock3;
2109
2332
  try {
2110
- sdkModule3 = await import('ai');
2111
- return sdkModule3;
2333
+ return await import('ai');
2112
2334
  } catch {
2113
2335
  throw new DependencyError("ai");
2114
2336
  }
2115
2337
  }
2116
2338
  async function loadCompat() {
2117
- if (compatModule) return compatModule;
2339
+ if (_compatMock) return _compatMock;
2118
2340
  try {
2119
- compatModule = await import('@ai-sdk/openai-compatible');
2120
- return compatModule;
2341
+ return await import('@ai-sdk/openai-compatible');
2121
2342
  } catch {
2122
2343
  throw new DependencyError("@ai-sdk/openai-compatible");
2123
2344
  }
2124
2345
  }
2125
2346
  function _injectSDK3(mock) {
2126
- sdkModule3 = mock;
2347
+ _sdkMock3 = mock;
2127
2348
  }
2128
2349
  function _injectCompat(mock) {
2129
- compatModule = mock;
2350
+ _compatMock = mock;
2130
2351
  }
2131
2352
  function _resetSDK3() {
2132
- sdkModule3 = null;
2133
- compatModule = null;
2353
+ _sdkMock3 = null;
2354
+ _compatMock = null;
2134
2355
  }
2135
2356
  function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, signal) {
2136
2357
  const toolMap = {};
@@ -2173,13 +2394,14 @@ function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, s
2173
2394
  return toolMap;
2174
2395
  }
2175
2396
  function wrapToolExecute(ourTool, supervisor, sessionApprovals, permissionStore, signal) {
2176
- return async (args) => {
2397
+ return async (args, options) => {
2177
2398
  if (ourTool.needsApproval && supervisor?.onPermission) {
2178
2399
  const storeApproved = permissionStore && await permissionStore.isApproved(ourTool.name);
2179
2400
  if (!storeApproved && !sessionApprovals.has(ourTool.name)) {
2180
2401
  const request = {
2181
2402
  toolName: ourTool.name,
2182
- toolArgs: args ?? {}
2403
+ toolArgs: args ?? {},
2404
+ toolCallId: options?.toolCallId
2183
2405
  };
2184
2406
  const decision = await supervisor.onPermission(
2185
2407
  request,
@@ -2286,7 +2508,8 @@ function mapStreamPart(part) {
2286
2508
  return {
2287
2509
  type: "error",
2288
2510
  error: p.error instanceof Error ? p.error.message : String(p.error ?? "Tool execution failed"),
2289
- recoverable: true
2511
+ recoverable: true,
2512
+ code: "TOOL_EXECUTION" /* TOOL_EXECUTION */
2290
2513
  };
2291
2514
  }
2292
2515
  case "reasoning-start":
@@ -2307,10 +2530,13 @@ function mapStreamPart(part) {
2307
2530
  }
2308
2531
  case "error": {
2309
2532
  const p = part;
2533
+ const errorMsg = p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error");
2534
+ const code = classifyAgentError(errorMsg);
2310
2535
  return {
2311
2536
  type: "error",
2312
- error: p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error"),
2313
- recoverable: false
2537
+ error: errorMsg,
2538
+ recoverable: isRecoverableErrorCode(code),
2539
+ code
2314
2540
  };
2315
2541
  }
2316
2542
  default:
@@ -2320,15 +2546,15 @@ function mapStreamPart(part) {
2320
2546
  function createVercelAIService(options) {
2321
2547
  return new VercelAIAgentService(options);
2322
2548
  }
2323
- var sdkModule3, compatModule, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2549
+ var _sdkMock3, _compatMock, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2324
2550
  var init_vercel_ai = __esm({
2325
2551
  "src/backends/vercel-ai.ts"() {
2326
- init_types();
2552
+ init_types2();
2327
2553
  init_base_agent();
2328
- init_errors();
2554
+ init_errors2();
2329
2555
  init_schema();
2330
- sdkModule3 = null;
2331
- compatModule = null;
2556
+ _sdkMock3 = null;
2557
+ _compatMock = null;
2332
2558
  DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
2333
2559
  DEFAULT_PROVIDER = "openrouter";
2334
2560
  DEFAULT_MAX_TURNS = 10;
@@ -2341,28 +2567,33 @@ var init_vercel_ai = __esm({
2341
2567
  super(config);
2342
2568
  this.backendOptions = backendOptions;
2343
2569
  }
2344
- async getModel() {
2345
- if (this.model) return this.model;
2570
+ async getModel(options) {
2571
+ const requestedModel = options.model;
2572
+ const defaultModel = this.config.model;
2573
+ if (requestedModel === defaultModel && this.model) return this.model;
2346
2574
  const compat = await loadCompat();
2347
2575
  const provider = compat.createOpenAICompatible({
2348
2576
  name: this.backendOptions.provider ?? DEFAULT_PROVIDER,
2349
2577
  baseURL: this.backendOptions.baseUrl ?? DEFAULT_BASE_URL,
2350
2578
  apiKey: this.backendOptions.apiKey
2351
2579
  });
2352
- const modelId = this.config.model ?? "anthropic/claude-sonnet-4-5";
2353
- this.model = provider.chatModel(modelId);
2354
- return this.model;
2580
+ const model = provider.chatModel(requestedModel);
2581
+ if (requestedModel === defaultModel) {
2582
+ this.model = model;
2583
+ }
2584
+ return model;
2355
2585
  }
2356
- async getSDKTools(signal) {
2586
+ async getSDKTools(signal, options) {
2357
2587
  const sdk = await loadSDK3();
2358
- return mapToolsToSDK2(sdk, this.config.tools ?? [], this.config, this.sessionApprovals, this.config.permissionStore, signal);
2588
+ const tools = this.resolveTools(options);
2589
+ return mapToolsToSDK2(sdk, tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
2359
2590
  }
2360
2591
  // ─── executeRun ─────────────────────────────────────────────────
2361
- async executeRun(messages, _options, signal) {
2592
+ async executeRun(messages, options, signal) {
2362
2593
  this.checkAbort(signal);
2363
2594
  const sdk = await loadSDK3();
2364
- const model = await this.getModel();
2365
- const tools = await this.getSDKTools(signal);
2595
+ const model = await this.getModel(options);
2596
+ const tools = await this.getSDKTools(signal, options);
2366
2597
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2367
2598
  const sdkMessages = messagesToSDK(messages);
2368
2599
  const hasTools = Object.keys(tools).length > 0;
@@ -2418,10 +2649,10 @@ var init_vercel_ai = __esm({
2418
2649
  };
2419
2650
  }
2420
2651
  // ─── executeRunStructured ───────────────────────────────────────
2421
- async executeRunStructured(messages, schema, _options, signal) {
2652
+ async executeRunStructured(messages, schema, options, signal) {
2422
2653
  this.checkAbort(signal);
2423
2654
  const sdk = await loadSDK3();
2424
- const model = await this.getModel();
2655
+ const model = await this.getModel(options);
2425
2656
  const sdkMessages = messagesToSDK(messages);
2426
2657
  const jsonSchema = zodToJsonSchema(schema.schema);
2427
2658
  const result = await sdk.generateObject({
@@ -2463,11 +2694,11 @@ var init_vercel_ai = __esm({
2463
2694
  };
2464
2695
  }
2465
2696
  // ─── executeStream ──────────────────────────────────────────────
2466
- async *executeStream(messages, _options, signal) {
2697
+ async *executeStream(messages, options, signal) {
2467
2698
  this.checkAbort(signal);
2468
2699
  const sdk = await loadSDK3();
2469
- const model = await this.getModel();
2470
- const tools = await this.getSDKTools(signal);
2700
+ const model = await this.getModel(options);
2701
+ const tools = await this.getSDKTools(signal, options);
2471
2702
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2472
2703
  const sdkMessages = messagesToSDK(messages);
2473
2704
  const hasTools = Object.keys(tools).length > 0;
@@ -2513,9 +2744,11 @@ var init_vercel_ai = __esm({
2513
2744
  promptTokens: Number(totalUsage?.inputTokens ?? 0),
2514
2745
  completionTokens: Number(totalUsage?.outputTokens ?? 0)
2515
2746
  };
2747
+ const hasStreamed = finalText.length > 0;
2516
2748
  yield {
2517
2749
  type: "done",
2518
- finalOutput: finalText || null
2750
+ finalOutput: hasStreamed ? null : finalText || null,
2751
+ ...hasStreamed ? { streamed: true } : {}
2519
2752
  };
2520
2753
  } catch (e) {
2521
2754
  if (signal.aborted) throw new AbortError();
@@ -2544,16 +2777,33 @@ var init_vercel_ai = __esm({
2544
2777
  const baseUrl = (this.options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
2545
2778
  try {
2546
2779
  const res = await globalThis.fetch(`${baseUrl}/models`, {
2547
- headers: { Authorization: `Bearer ${this.options.apiKey}` }
2780
+ headers: {
2781
+ Authorization: `Bearer ${this.options.apiKey}`,
2782
+ // OpenRouter requires HTTP-Referer for API access
2783
+ "HTTP-Referer": "https://github.com/nicepkg/agent-sdk"
2784
+ }
2548
2785
  });
2549
2786
  if (!res.ok) {
2550
2787
  return [];
2551
2788
  }
2552
2789
  const body = await res.json();
2553
- if (!body.data || body.data.length === 0) {
2554
- return [];
2790
+ if (body.data && Array.isArray(body.data)) {
2791
+ return body.data.filter((m) => typeof m.id === "string").map((m) => ({
2792
+ id: m.id,
2793
+ ...typeof m.name === "string" && { name: m.name },
2794
+ ...typeof m.description === "string" && { description: m.description },
2795
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2796
+ }));
2797
+ }
2798
+ if (Array.isArray(body)) {
2799
+ return body.filter((m) => typeof m.id === "string").map((m) => ({
2800
+ id: m.id,
2801
+ ...typeof m.name === "string" && { name: m.name },
2802
+ ...typeof m.description === "string" && { description: m.description },
2803
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2804
+ }));
2555
2805
  }
2556
- return body.data.map((m) => ({ id: m.id }));
2806
+ return [];
2557
2807
  } catch {
2558
2808
  return [];
2559
2809
  }
@@ -2567,280 +2817,24 @@ var init_vercel_ai = __esm({
2567
2817
  try {
2568
2818
  await loadSDK3();
2569
2819
  } catch (e) {
2570
- errors.push(e instanceof Error ? e.message : String(e));
2571
- }
2572
- try {
2573
- await loadCompat();
2574
- } catch (e) {
2575
- errors.push(e instanceof Error ? e.message : String(e));
2576
- }
2577
- return { valid: errors.length === 0, errors };
2578
- }
2579
- async dispose() {
2580
- if (this.disposed) return;
2581
- this.disposed = true;
2582
- }
2583
- };
2584
- }
2585
- });
2586
-
2587
- // src/registry.ts
2588
- function registerBackend(name, factory) {
2589
- if (registry.has(name)) {
2590
- throw new BackendAlreadyRegisteredError(name);
2591
- }
2592
- registry.set(name, { factory, builtin: false });
2593
- }
2594
- function unregisterBackend(name) {
2595
- return registry.delete(name);
2596
- }
2597
- function hasBackend(name) {
2598
- return registry.has(name) || isBuiltinName(name);
2599
- }
2600
- function listBackends() {
2601
- const names = new Set(registry.keys());
2602
- for (const builtin of BUILTIN_BACKENDS) {
2603
- names.add(builtin);
2604
- }
2605
- return [...names];
2606
- }
2607
- function resetRegistry() {
2608
- registry.clear();
2609
- }
2610
- function isBuiltinName(name) {
2611
- return BUILTIN_BACKENDS.has(name);
2612
- }
2613
- async function loadBuiltinFactory(name) {
2614
- switch (name) {
2615
- case "copilot": {
2616
- const mod = await Promise.resolve().then(() => (init_copilot(), copilot_exports));
2617
- return (opts) => mod.createCopilotService(opts);
2618
- }
2619
- case "claude": {
2620
- const mod = await Promise.resolve().then(() => (init_claude(), claude_exports));
2621
- return (opts) => mod.createClaudeService(opts);
2622
- }
2623
- case "vercel-ai": {
2624
- const mod = await Promise.resolve().then(() => (init_vercel_ai(), vercel_ai_exports));
2625
- return (opts) => mod.createVercelAIService(opts);
2626
- }
2627
- }
2628
- }
2629
- async function createAgentService(name, options) {
2630
- const entry = registry.get(name);
2631
- if (entry) {
2632
- return entry.factory(options);
2633
- }
2634
- if (isBuiltinName(name)) {
2635
- const factory = await loadBuiltinFactory(name);
2636
- registry.set(name, { factory, builtin: true });
2637
- return factory(options);
2638
- }
2639
- throw new BackendNotFoundError(name);
2640
- }
2641
- var registry, BUILTIN_BACKENDS;
2642
- var init_registry = __esm({
2643
- "src/registry.ts"() {
2644
- init_errors();
2645
- registry = /* @__PURE__ */ new Map();
2646
- BUILTIN_BACKENDS = /* @__PURE__ */ new Set([
2647
- "copilot",
2648
- "claude",
2649
- "vercel-ai"
2650
- ]);
2651
- }
2652
- });
2653
-
2654
- // src/utils/messages.ts
2655
- function messagesToPrompt(messages) {
2656
- return messages.map((msg) => {
2657
- switch (msg.role) {
2658
- case "user":
2659
- return contentToText(msg.content);
2660
- case "assistant":
2661
- return contentToText(msg.content);
2662
- case "system":
2663
- return msg.content;
2664
- case "tool":
2665
- return msg.content ?? "";
2666
- }
2667
- }).filter(Boolean).join("\n\n");
2668
- }
2669
- function contentToText(content) {
2670
- return getTextContent(content);
2671
- }
2672
- function buildSystemPrompt(base, schemaInstruction) {
2673
- if (!schemaInstruction) return base;
2674
- return `${base}
2675
-
2676
- ${schemaInstruction}`;
2677
- }
2678
- var init_messages = __esm({
2679
- "src/utils/messages.ts"() {
2680
- init_types();
2681
- }
2682
- });
2683
- function createDefaultPermissionStore(projectDir) {
2684
- const sessionStore = new InMemoryPermissionStore();
2685
- const projectPath = projectDir ? path.join(projectDir, ".agent-sdk", "permissions.json") : path.join(process.cwd(), ".agent-sdk", "permissions.json");
2686
- const userPath = path.join(os.homedir(), ".agent-sdk", "permissions.json");
2687
- const projectStore = new FilePermissionStore(projectPath);
2688
- const userStore = new FilePermissionStore(userPath);
2689
- return new CompositePermissionStore(sessionStore, projectStore, userStore);
2690
- }
2691
- var InMemoryPermissionStore, FilePermissionStore, CompositePermissionStore;
2692
- var init_permission_store = __esm({
2693
- "src/permission-store.ts"() {
2694
- InMemoryPermissionStore = class {
2695
- approvals = /* @__PURE__ */ new Map();
2696
- async isApproved(toolName) {
2697
- return this.approvals.has(toolName);
2698
- }
2699
- async approve(toolName, scope) {
2700
- if (scope === "once") return;
2701
- this.approvals.set(toolName, scope);
2702
- }
2703
- async revoke(toolName) {
2704
- this.approvals.delete(toolName);
2705
- }
2706
- async clear() {
2707
- this.approvals.clear();
2708
- }
2709
- async dispose() {
2710
- this.approvals.clear();
2711
- }
2712
- };
2713
- FilePermissionStore = class {
2714
- filePath;
2715
- constructor(filePath) {
2716
- this.filePath = path.resolve(filePath);
2717
- }
2718
- async isApproved(toolName) {
2719
- const data = this.readFile();
2720
- return toolName in data.approvals;
2721
- }
2722
- async approve(toolName, scope) {
2723
- if (scope === "once") return;
2724
- const data = this.readFile();
2725
- data.approvals[toolName] = { scope, timestamp: Date.now() };
2726
- this.writeFileAtomic(data);
2727
- }
2728
- async revoke(toolName) {
2729
- const data = this.readFile();
2730
- delete data.approvals[toolName];
2731
- this.writeFileAtomic(data);
2732
- }
2733
- async clear() {
2734
- this.writeFileAtomic({ approvals: {} });
2735
- }
2736
- async dispose() {
2737
- }
2738
- readFile() {
2739
- try {
2740
- const raw = fs.readFileSync(this.filePath, "utf-8");
2741
- const parsed = JSON.parse(raw);
2742
- if (parsed && typeof parsed.approvals === "object") return parsed;
2743
- } catch {
2744
- }
2745
- return { approvals: {} };
2746
- }
2747
- writeFileAtomic(data) {
2748
- const dir = path.dirname(this.filePath);
2749
- fs.mkdirSync(dir, { recursive: true });
2750
- const tmpPath = this.filePath + `.tmp.${process.pid}.${Date.now()}`;
2751
- fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
2752
- fs.renameSync(tmpPath, this.filePath);
2753
- }
2754
- };
2755
- CompositePermissionStore = class {
2756
- sessionStore;
2757
- projectStore;
2758
- userStore;
2759
- constructor(sessionStore, projectStore, userStore) {
2760
- this.sessionStore = sessionStore;
2761
- this.projectStore = projectStore;
2762
- this.userStore = userStore ?? projectStore;
2763
- }
2764
- async isApproved(toolName) {
2765
- return await this.sessionStore.isApproved(toolName) || await this.projectStore.isApproved(toolName) || await this.userStore.isApproved(toolName);
2766
- }
2767
- async approve(toolName, scope) {
2768
- if (scope === "once") return;
2769
- if (scope === "session") {
2770
- await this.sessionStore.approve(toolName, scope);
2771
- } else if (scope === "project") {
2772
- await this.projectStore.approve(toolName, scope);
2773
- } else {
2774
- await this.userStore.approve(toolName, scope);
2820
+ errors.push(e instanceof Error ? e.message : String(e));
2775
2821
  }
2776
- }
2777
- async revoke(toolName) {
2778
- await this.sessionStore.revoke(toolName);
2779
- await this.projectStore.revoke(toolName);
2780
- await this.userStore.revoke(toolName);
2781
- }
2782
- async clear() {
2783
- await this.sessionStore.clear();
2784
- await this.projectStore.clear();
2785
- await this.userStore.clear();
2822
+ try {
2823
+ await loadCompat();
2824
+ } catch (e) {
2825
+ errors.push(e instanceof Error ? e.message : String(e));
2826
+ }
2827
+ return { valid: errors.length === 0, errors };
2786
2828
  }
2787
2829
  async dispose() {
2788
- await this.sessionStore.dispose();
2789
- await this.projectStore.dispose();
2790
- if (this.userStore !== this.projectStore) {
2791
- await this.userStore.dispose();
2792
- }
2830
+ if (this.disposed) return;
2831
+ this.disposed = true;
2793
2832
  }
2794
2833
  };
2795
2834
  }
2796
2835
  });
2797
2836
 
2798
- // src/index.ts
2799
- var src_exports = {};
2800
- __export(src_exports, {
2801
- AbortError: () => AbortError,
2802
- AgentSDKError: () => AgentSDKError,
2803
- BackendAlreadyRegisteredError: () => BackendAlreadyRegisteredError,
2804
- BackendNotFoundError: () => BackendNotFoundError,
2805
- BaseAgent: () => BaseAgent,
2806
- CompositePermissionStore: () => CompositePermissionStore,
2807
- DependencyError: () => DependencyError,
2808
- DisposedError: () => DisposedError,
2809
- FilePermissionStore: () => FilePermissionStore,
2810
- InMemoryPermissionStore: () => InMemoryPermissionStore,
2811
- ReentrancyError: () => ReentrancyError,
2812
- StructuredOutputError: () => StructuredOutputError,
2813
- SubprocessError: () => SubprocessError,
2814
- ToolExecutionError: () => ToolExecutionError,
2815
- buildSystemPrompt: () => buildSystemPrompt,
2816
- contentToText: () => contentToText,
2817
- createAgentService: () => createAgentService,
2818
- createDefaultPermissionStore: () => createDefaultPermissionStore,
2819
- getTextContent: () => getTextContent,
2820
- hasBackend: () => hasBackend,
2821
- isMultiPartContent: () => isMultiPartContent,
2822
- isTextContent: () => isTextContent,
2823
- isToolDefinition: () => isToolDefinition,
2824
- listBackends: () => listBackends,
2825
- messagesToPrompt: () => messagesToPrompt,
2826
- registerBackend: () => registerBackend,
2827
- resetRegistry: () => resetRegistry,
2828
- unregisterBackend: () => unregisterBackend,
2829
- zodToJsonSchema: () => zodToJsonSchema
2830
- });
2831
- var init_src = __esm({
2832
- "src/index.ts"() {
2833
- init_types();
2834
- init_errors();
2835
- init_registry();
2836
- init_base_agent();
2837
- init_schema();
2838
- init_messages();
2839
- init_permission_store();
2840
- }
2841
- });
2842
-
2843
- // src/chat/core.ts
2837
+ // src/chat/types.ts
2844
2838
  function createChatId() {
2845
2839
  return crypto.randomUUID();
2846
2840
  }
@@ -2851,6 +2845,20 @@ function toChatId(value) {
2851
2845
  }
2852
2846
  return value;
2853
2847
  }
2848
+ function createTextMessage(text, role = "user") {
2849
+ return {
2850
+ id: createChatId(),
2851
+ role,
2852
+ parts: [{ type: "text", text, status: "complete" }],
2853
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2854
+ status: "complete"
2855
+ };
2856
+ }
2857
+ function isObservableSession(session) {
2858
+ return "subscribe" in session && typeof session.subscribe === "function" && "getSnapshot" in session && typeof session.getSnapshot === "function";
2859
+ }
2860
+
2861
+ // src/chat/chat-utils.ts
2854
2862
  function getMessageText(message) {
2855
2863
  return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
2856
2864
  }
@@ -2860,6 +2868,8 @@ function getMessageToolCalls(message) {
2860
2868
  function getMessageReasoning(message) {
2861
2869
  return message.parts.filter((p) => p.type === "reasoning").map((p) => p.text).join("");
2862
2870
  }
2871
+
2872
+ // src/chat/guards.ts
2863
2873
  function isChatMessage(value) {
2864
2874
  if (typeof value !== "object" || value === null) return false;
2865
2875
  const obj = value;
@@ -2925,6 +2935,8 @@ function isChatEvent(value) {
2925
2935
  ];
2926
2936
  return validTypes.includes(obj.type);
2927
2937
  }
2938
+
2939
+ // src/chat/bridge.ts
2928
2940
  function agentEventToChatEvent(event, messageId) {
2929
2941
  switch (event.type) {
2930
2942
  case "text_delta":
@@ -2977,6 +2989,7 @@ function agentEventToChatEvent(event, messageId) {
2977
2989
  type: "error",
2978
2990
  error: event.error,
2979
2991
  recoverable: event.recoverable,
2992
+ code: event.code,
2980
2993
  messageId
2981
2994
  };
2982
2995
  case "heartbeat":
@@ -3023,11 +3036,13 @@ function chatEventToAgentEvent(event) {
3023
3036
  result: event.result
3024
3037
  };
3025
3038
  case "error":
3026
- return { type: "error", error: event.error, recoverable: event.recoverable };
3039
+ return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
3027
3040
  default:
3028
3041
  return null;
3029
3042
  }
3030
3043
  }
3044
+
3045
+ // src/chat/conversion.ts
3031
3046
  function toAgentMessage(message) {
3032
3047
  const textContent = getMessageText(message);
3033
3048
  const toolCallParts = getMessageToolCalls(message);
@@ -3198,6 +3213,56 @@ var ContextWindowManager = class {
3198
3213
  });
3199
3214
  return { ...result, messages: updatedMessages };
3200
3215
  }
3216
+ /**
3217
+ * Trim messages using real token usage data from the previous API call.
3218
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
3219
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
3220
+ *
3221
+ * @param messages - All messages in the session
3222
+ * @param lastPromptTokens - Real prompt tokens from the last API response
3223
+ * @param modelContextWindow - Model's total context window size in tokens
3224
+ * @returns Result with fitted messages and metadata
3225
+ */
3226
+ fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
3227
+ if (messages.length === 0) {
3228
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
3229
+ }
3230
+ const budget = modelContextWindow - this.config.reservedTokens;
3231
+ if (budget <= 0 || lastPromptTokens <= budget) {
3232
+ return {
3233
+ messages: [...messages],
3234
+ totalTokens: lastPromptTokens,
3235
+ removedCount: 0,
3236
+ wasTruncated: false
3237
+ };
3238
+ }
3239
+ const avgTokensPerMessage = lastPromptTokens / messages.length;
3240
+ const tokensToFree = lastPromptTokens - budget;
3241
+ const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
3242
+ const nonSystemIndices = [];
3243
+ for (let i = 0; i < messages.length; i++) {
3244
+ if (messages[i].role === "system") ; else {
3245
+ nonSystemIndices.push(i);
3246
+ }
3247
+ }
3248
+ const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
3249
+ const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
3250
+ const result = [];
3251
+ for (let i = 0; i < messages.length; i++) {
3252
+ if (!removedIndices.has(i)) {
3253
+ result.push(messages[i]);
3254
+ }
3255
+ }
3256
+ const estimatedTokens = Math.round(
3257
+ lastPromptTokens * (result.length / messages.length)
3258
+ );
3259
+ return {
3260
+ messages: result,
3261
+ totalTokens: estimatedTokens,
3262
+ removedCount: removableCount,
3263
+ wasTruncated: removableCount > 0
3264
+ };
3265
+ }
3201
3266
  /**
3202
3267
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
3203
3268
  * Always keeps the most recent user message.
@@ -3315,37 +3380,19 @@ var ContextWindowManager = class {
3315
3380
  };
3316
3381
 
3317
3382
  // src/chat/errors.ts
3383
+ init_errors2();
3318
3384
  init_errors();
3319
- var ChatErrorCode = /* @__PURE__ */ ((ChatErrorCode2) => {
3320
- ChatErrorCode2["NETWORK"] = "NETWORK";
3321
- ChatErrorCode2["TIMEOUT"] = "TIMEOUT";
3322
- ChatErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
3323
- ChatErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
3324
- ChatErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
3325
- ChatErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
3326
- ChatErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
3327
- ChatErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
3328
- ChatErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
3329
- ChatErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
3330
- ChatErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
3331
- ChatErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
3332
- ChatErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
3333
- ChatErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
3334
- ChatErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
3335
- ChatErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
3336
- ChatErrorCode2["DISPOSED"] = "DISPOSED";
3337
- ChatErrorCode2["ABORTED"] = "ABORTED";
3338
- ChatErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
3339
- ChatErrorCode2["REENTRANCY"] = "REENTRANCY";
3340
- return ChatErrorCode2;
3341
- })(ChatErrorCode || {});
3342
3385
  var ChatError = class extends AgentSDKError {
3343
3386
  code;
3344
3387
  retryable;
3345
3388
  retryAfter;
3346
3389
  timestamp;
3347
3390
  constructor(message, options) {
3348
- super(message, { cause: options.cause });
3391
+ super(message, {
3392
+ cause: options.cause,
3393
+ code: options.code,
3394
+ retryable: options.retryable
3395
+ });
3349
3396
  this.name = "ChatError";
3350
3397
  this.code = options.code;
3351
3398
  this.retryable = options.retryable ?? false;
@@ -3524,12 +3571,12 @@ function isRetryable(error) {
3524
3571
  return classified.retryable;
3525
3572
  }
3526
3573
  function sleep(ms, signal) {
3527
- return new Promise((resolve2, reject) => {
3574
+ return new Promise((resolve, reject) => {
3528
3575
  if (signal?.aborted) {
3529
3576
  reject(new ChatError("Retry aborted", { code: "ABORTED" /* ABORTED */ }));
3530
3577
  return;
3531
3578
  }
3532
- const timer = setTimeout(resolve2, ms);
3579
+ const timer = setTimeout(resolve, ms);
3533
3580
  signal?.addEventListener(
3534
3581
  "abort",
3535
3582
  () => {
@@ -3853,6 +3900,35 @@ var CancellableTimeout = class {
3853
3900
  }
3854
3901
  };
3855
3902
 
3903
+ // src/chat/listener-set.ts
3904
+ var ListenerSet = class {
3905
+ _listeners = /* @__PURE__ */ new Set();
3906
+ /** Add a listener. Returns an unsubscribe function. */
3907
+ add(callback) {
3908
+ this._listeners.add(callback);
3909
+ return () => {
3910
+ this._listeners.delete(callback);
3911
+ };
3912
+ }
3913
+ /** Notify all listeners with the given arguments. Errors are isolated per listener. */
3914
+ notify(...args) {
3915
+ for (const cb of this._listeners) {
3916
+ try {
3917
+ cb(...args);
3918
+ } catch {
3919
+ }
3920
+ }
3921
+ }
3922
+ /** Remove all listeners. */
3923
+ clear() {
3924
+ this._listeners.clear();
3925
+ }
3926
+ /** Current number of listeners. */
3927
+ get size() {
3928
+ return this._listeners.size;
3929
+ }
3930
+ };
3931
+
3856
3932
  // src/chat/runtime.ts
3857
3933
  var ChatRuntime = class {
3858
3934
  _state;
@@ -3864,20 +3940,19 @@ var ChatRuntime = class {
3864
3940
  _tools = /* @__PURE__ */ new Map();
3865
3941
  _retryConfig;
3866
3942
  _contextStats = /* @__PURE__ */ new Map();
3943
+ _sessionUsage = /* @__PURE__ */ new Map();
3944
+ _modelContextWindows = /* @__PURE__ */ new Map();
3867
3945
  _onContextTrimmed;
3868
3946
  _streamTimeoutMs;
3869
- _sessionListeners = /* @__PURE__ */ new Set();
3870
- _activeAdapter = null;
3871
- _currentBackend;
3872
- _currentModel;
3873
- _activeSessionId = null;
3947
+ _sessionListeners = new ListenerSet();
3948
+ _adapterPool = /* @__PURE__ */ new Map();
3949
+ _defaultBackend;
3874
3950
  _abortController = null;
3875
3951
  constructor(options) {
3876
3952
  this._state = new StateMachine("idle", RUNTIME_TRANSITIONS);
3877
3953
  this._guard = new ChatReentrancyGuard();
3878
3954
  this._backends = options.backends;
3879
- this._currentBackend = options.defaultBackend;
3880
- this._currentModel = options.defaultModel;
3955
+ this._defaultBackend = options.defaultBackend;
3881
3956
  this._sessionStore = options.sessionStore;
3882
3957
  this._contextConfig = options.context;
3883
3958
  this._middleware = [...options.middleware ?? []];
@@ -3890,6 +3965,11 @@ var ChatRuntime = class {
3890
3965
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
3891
3966
  );
3892
3967
  }
3968
+ if (options.tools) {
3969
+ for (const tool of options.tools) {
3970
+ this._tools.set(tool.name, tool);
3971
+ }
3972
+ }
3893
3973
  }
3894
3974
  // ── Lifecycle ──────────────────────────────────────────────
3895
3975
  get status() {
@@ -3901,24 +3981,23 @@ var ChatRuntime = class {
3901
3981
  this._abortController?.dispose();
3902
3982
  this._abortController = null;
3903
3983
  this._state.transition("disposed");
3904
- if (this._activeAdapter) {
3905
- await this._activeAdapter.dispose();
3906
- this._activeAdapter = null;
3984
+ for (const adapter of this._adapterPool.values()) {
3985
+ try {
3986
+ await adapter.dispose();
3987
+ } catch {
3988
+ }
3907
3989
  }
3990
+ this._adapterPool.clear();
3908
3991
  }
3909
3992
  // ── Sessions ───────────────────────────────────────────────
3910
- get activeSessionId() {
3911
- return this._activeSessionId;
3912
- }
3913
3993
  async createSession(options) {
3914
3994
  this.assertNotDisposed();
3915
3995
  const config = {
3916
- model: options.config?.model ?? this._currentModel ?? "",
3917
- backend: options.config?.backend ?? this._currentBackend,
3996
+ model: options.config?.model ?? "",
3997
+ backend: options.config?.backend ?? this._defaultBackend,
3918
3998
  ...options.config
3919
3999
  };
3920
4000
  const session = await this._sessionStore.createSession({ ...options, config });
3921
- this._activeSessionId = session.id;
3922
4001
  this._notifySessionChange();
3923
4002
  return session;
3924
4003
  }
@@ -3938,36 +4017,12 @@ var ChatRuntime = class {
3938
4017
  if (!session) return;
3939
4018
  await this._sessionStore.deleteSession(cid);
3940
4019
  this._contextStats.delete(cid);
3941
- if (this._activeSessionId === cid) {
3942
- this._activeSessionId = null;
3943
- }
3944
- this._notifySessionChange();
3945
- }
3946
- async archiveSession(id) {
3947
- this.assertNotDisposed();
3948
- const cid = toChatId(id);
3949
- await this._sessionStore.archiveSession(cid);
4020
+ this._sessionUsage.delete(cid);
3950
4021
  this._notifySessionChange();
3951
4022
  }
3952
- async switchSession(id) {
3953
- this.assertNotDisposed();
3954
- const cid = toChatId(id);
3955
- const session = await this._sessionStore.getSession(cid);
3956
- if (!session) {
3957
- throw new ChatError(
3958
- `Session "${id}" not found`,
3959
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
3960
- );
3961
- }
3962
- this._activeSessionId = session.id;
3963
- return session;
3964
- }
3965
4023
  // ── Messaging ──────────────────────────────────────────────
3966
4024
  async *send(sessionId, message, options) {
3967
- this.assertNotDisposed();
3968
- if (!message || message.trim().length === 0) {
3969
- throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
3970
- }
4025
+ this.validateSendInput(message, options);
3971
4026
  this._guard.acquire();
3972
4027
  const cid = toChatId(sessionId);
3973
4028
  this._abortController = new ChatAbortController(options?.signal);
@@ -3976,150 +4031,274 @@ var ChatRuntime = class {
3976
4031
  this._state.transition("idle");
3977
4032
  }
3978
4033
  this._state.transition("streaming");
3979
- const session = await this._sessionStore.getSession(cid);
3980
- if (!session) {
3981
- throw new ChatError(
3982
- `Session "${cid}" not found`,
3983
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
3984
- );
3985
- }
3986
- const middlewareContext = {
4034
+ await this.loadSession(cid);
4035
+ const mwCtx = {
3987
4036
  sessionId: cid,
3988
4037
  signal: this._abortController.signal
3989
4038
  };
3990
- let userMessage = this.createUserMessage(message);
3991
- for (const mw of this._middleware) {
3992
- if (mw.onBeforeSend) {
3993
- userMessage = await mw.onBeforeSend(userMessage, middlewareContext);
3994
- }
3995
- }
3996
- await this._sessionStore.appendMessage(cid, userMessage);
3997
- const updatedSession = await this._sessionStore.getSession(cid);
3998
- let messagesToSend = updatedSession.messages;
3999
- if (this._contextConfig) {
4000
- const ctxManager = new ContextWindowManager(this._contextConfig);
4001
- const result = await ctxManager.fitMessagesAsync(messagesToSend);
4002
- this._contextStats.set(cid, {
4003
- totalTokens: result.totalTokens,
4004
- removedCount: result.removedCount,
4005
- wasTruncated: result.wasTruncated,
4006
- availableBudget: ctxManager.availableBudget
4007
- });
4008
- if (result.wasTruncated && this._onContextTrimmed) {
4009
- const keptIds = new Set(result.messages.map((m) => m.id));
4010
- const removed = messagesToSend.filter((m) => !keptIds.has(m.id));
4011
- if (removed.length > 0) {
4012
- try {
4013
- this._onContextTrimmed(cid, removed);
4014
- } catch {
4015
- }
4016
- }
4017
- }
4018
- messagesToSend = result.messages;
4039
+ const userMessage = await this.applyBeforeSendMiddleware(
4040
+ this.createUserMessage(message),
4041
+ mwCtx
4042
+ );
4043
+ if (userMessage === null) {
4044
+ this._state.transition("idle");
4045
+ return;
4019
4046
  }
4020
- const sessionForAdapter = {
4021
- ...updatedSession,
4022
- messages: messagesToSend
4023
- };
4024
- const adapter = await this.getOrCreateAdapterWithRetry();
4047
+ const updatedSession = await this.persistAndReload(cid, userMessage);
4048
+ const sessionForAdapter = await this.trimSessionContext(cid, updatedSession, options.model);
4049
+ const stream = await this.prepareEventStream(
4050
+ cid,
4051
+ sessionForAdapter,
4052
+ updatedSession,
4053
+ message,
4054
+ options
4055
+ );
4025
4056
  const accumulator = new MessageAccumulator();
4026
- const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4027
- sessionId: cid,
4028
- custom: updatedSession.metadata?.custom
4029
- }) : void 0;
4030
- const streamOptions = {
4031
- ...options,
4032
- signal: this._abortController.signal,
4033
- model: options?.model ?? this._currentModel,
4034
- tools: runtimeTools
4035
- };
4036
- const stream = await this.createStreamWithRetry(adapter, sessionForAdapter, message, streamOptions);
4037
4057
  const eventSource = this._streamTimeoutMs ? withStreamWatchdog(stream, { timeoutMs: this._streamTimeoutMs, signal: this._abortController.signal }) : stream;
4038
4058
  for await (const event of eventSource) {
4039
4059
  if (this._abortController.isAborted) break;
4040
4060
  this.feedAccumulator(accumulator, event);
4041
- let processedEvent = event;
4042
- for (const mw of this._middleware) {
4043
- if (mw.onEvent && processedEvent) {
4044
- processedEvent = await mw.onEvent(processedEvent, middlewareContext);
4045
- }
4046
- }
4047
- if (processedEvent) {
4048
- yield processedEvent;
4049
- }
4050
- }
4051
- if (this._state.current === "disposed") {
4052
- return;
4053
- }
4054
- let assistantMessage = accumulator.finalize();
4055
- for (const mw of this._middleware) {
4056
- if (mw.onAfterReceive) {
4057
- assistantMessage = await mw.onAfterReceive(assistantMessage, middlewareContext);
4061
+ if (event.type === "usage") {
4062
+ this._sessionUsage.set(cid, {
4063
+ promptTokens: event.promptTokens,
4064
+ completionTokens: event.completionTokens
4065
+ });
4066
+ this.updateContextStatsWithUsage(cid, event.promptTokens, event.completionTokens, options);
4058
4067
  }
4068
+ const processed = await this.applyOnEventMiddleware(event, mwCtx);
4069
+ if (processed) yield processed;
4059
4070
  }
4060
- await this._sessionStore.appendMessage(cid, assistantMessage);
4061
- this._notifySessionChange();
4071
+ if (this._state.current === "disposed") return;
4072
+ await this.finalizeAssistantMessage(cid, accumulator, mwCtx);
4062
4073
  this._state.transition("idle");
4063
4074
  } catch (error) {
4064
- let processedError = error instanceof Error ? error : new Error(String(error));
4065
- const middlewareContext = {
4066
- sessionId: cid,
4067
- signal: this._abortController?.signal ?? new AbortController().signal
4068
- };
4069
- for (const mw of this._middleware) {
4070
- if (mw.onError) {
4071
- const result = await mw.onError(processedError, middlewareContext);
4072
- if (result === null) {
4073
- if (this._state.canTransition("idle")) {
4074
- this._state.transition("idle");
4075
- }
4076
- return;
4077
- }
4078
- processedError = result;
4079
- }
4080
- }
4081
- if (this._state.canTransition("error")) {
4082
- this._state.transition("error");
4083
- }
4084
- throw processedError;
4075
+ const result = await this.handleSendError(error, cid);
4076
+ if (result !== null) throw result;
4085
4077
  } finally {
4086
4078
  this._guard.release();
4087
4079
  this._abortController?.dispose();
4088
4080
  this._abortController = null;
4089
4081
  }
4090
4082
  }
4091
- abort() {
4092
- this._abortController?.abort("User abort");
4093
- }
4094
- // ── Backend / Model ────────────────────────────────────────
4095
- get currentBackend() {
4096
- return this._currentBackend;
4097
- }
4098
- get currentModel() {
4099
- return this._currentModel;
4100
- }
4101
- async switchBackend(name) {
4083
+ // ── Send Pipeline Stages ──────────────────────────────────────
4084
+ /** Stage 1: Validate send inputs (message content + required fields). */
4085
+ validateSendInput(message, options) {
4102
4086
  this.assertNotDisposed();
4103
- if (!this._backends[name]) {
4087
+ if (!message || message.trim().length === 0) {
4088
+ throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
4089
+ }
4090
+ if (!options.model) {
4091
+ throw new ChatError(
4092
+ "options.model is required \u2014 caller must specify which model to use",
4093
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4094
+ );
4095
+ }
4096
+ if (!options.backend) {
4097
+ throw new ChatError(
4098
+ "options.backend is required \u2014 caller must specify which backend to use",
4099
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4100
+ );
4101
+ }
4102
+ if (!options.credentials) {
4104
4103
  throw new ChatError(
4105
- `Backend "${name}" not found in backends map`,
4104
+ "options.credentials is required \u2014 caller must provide authentication credentials",
4106
4105
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4107
4106
  );
4108
4107
  }
4109
- if (this._activeAdapter) {
4110
- await this._activeAdapter.dispose();
4111
- this._activeAdapter = null;
4108
+ }
4109
+ /** Stage 2: Load session from store. */
4110
+ async loadSession(cid) {
4111
+ const session = await this._sessionStore.getSession(cid);
4112
+ if (!session) {
4113
+ throw new ChatError(
4114
+ `Session "${cid}" not found`,
4115
+ { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4116
+ );
4117
+ }
4118
+ return session;
4119
+ }
4120
+ /** Stage 3: Apply onBeforeSend middleware pipeline. Returns null if middleware rejected the send. */
4121
+ async applyBeforeSendMiddleware(userMessage, ctx) {
4122
+ let msg = userMessage;
4123
+ for (const mw of this._middleware) {
4124
+ if (mw.onBeforeSend && msg) {
4125
+ msg = await mw.onBeforeSend(msg, ctx);
4126
+ if (msg === null) return null;
4127
+ }
4128
+ }
4129
+ return msg;
4130
+ }
4131
+ /** Stage 4: Persist user message and reload session with full history. */
4132
+ async persistAndReload(cid, userMessage) {
4133
+ await this._sessionStore.appendMessage(cid, userMessage);
4134
+ return await this._sessionStore.getSession(cid);
4135
+ }
4136
+ /** Stage 5: Auto-trim context window if configured. Returns session snapshot for adapter. */
4137
+ async trimSessionContext(cid, session, model) {
4138
+ if (!this._contextConfig) return session;
4139
+ const ctxManager = new ContextWindowManager(this._contextConfig);
4140
+ const lastUsage = this._sessionUsage.get(cid);
4141
+ const modelContextWindow = model ? this._modelContextWindows.get(model) : void 0;
4142
+ if (lastUsage && modelContextWindow) {
4143
+ const result2 = ctxManager.fitMessagesWithUsage(
4144
+ session.messages,
4145
+ lastUsage.promptTokens,
4146
+ modelContextWindow
4147
+ );
4148
+ this._contextStats.set(cid, {
4149
+ totalTokens: result2.totalTokens,
4150
+ removedCount: result2.removedCount,
4151
+ wasTruncated: result2.wasTruncated,
4152
+ availableBudget: Math.max(0, modelContextWindow - result2.totalTokens),
4153
+ realPromptTokens: lastUsage.promptTokens,
4154
+ realCompletionTokens: lastUsage.completionTokens,
4155
+ modelContextWindow
4156
+ });
4157
+ if (result2.wasTruncated && this._onContextTrimmed) {
4158
+ const keptIds = new Set(result2.messages.map((m) => m.id));
4159
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4160
+ if (removed.length > 0) {
4161
+ try {
4162
+ this._onContextTrimmed(cid, removed);
4163
+ } catch {
4164
+ }
4165
+ }
4166
+ }
4167
+ return { ...session, messages: result2.messages };
4168
+ }
4169
+ const result = await ctxManager.fitMessagesAsync(session.messages);
4170
+ this._contextStats.set(cid, {
4171
+ totalTokens: result.totalTokens,
4172
+ removedCount: result.removedCount,
4173
+ wasTruncated: result.wasTruncated,
4174
+ availableBudget: ctxManager.availableBudget,
4175
+ modelContextWindow
4176
+ });
4177
+ if (result.wasTruncated && this._onContextTrimmed) {
4178
+ const keptIds = new Set(result.messages.map((m) => m.id));
4179
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4180
+ if (removed.length > 0) {
4181
+ try {
4182
+ this._onContextTrimmed(cid, removed);
4183
+ } catch {
4184
+ }
4185
+ }
4186
+ }
4187
+ return { ...session, messages: result.messages };
4188
+ }
4189
+ /** Update context stats with real usage data from a usage event. */
4190
+ updateContextStatsWithUsage(cid, promptTokens, completionTokens, options) {
4191
+ const modelContextWindow = options.model ? this._modelContextWindows.get(options.model) : void 0;
4192
+ const existing = this._contextStats.get(cid);
4193
+ this._contextStats.set(cid, {
4194
+ totalTokens: promptTokens,
4195
+ removedCount: existing?.removedCount ?? 0,
4196
+ wasTruncated: existing?.wasTruncated ?? false,
4197
+ availableBudget: modelContextWindow ? Math.max(0, modelContextWindow - promptTokens) : existing?.availableBudget ?? 0,
4198
+ realPromptTokens: promptTokens,
4199
+ realCompletionTokens: completionTokens,
4200
+ modelContextWindow
4201
+ });
4202
+ }
4203
+ /** Stage 6: Prepare event stream — adapter with retry, tool injection. */
4204
+ async prepareEventStream(cid, sessionForAdapter, fullSession, message, options) {
4205
+ const adapter = await this.getOrCreateAdapterWithRetry(options.backend, options.credentials);
4206
+ const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4207
+ sessionId: cid,
4208
+ custom: fullSession.metadata?.custom
4209
+ }) : void 0;
4210
+ const streamOptions = {
4211
+ signal: this._abortController.signal,
4212
+ model: options.model,
4213
+ systemPrompt: options.systemPrompt,
4214
+ tools: runtimeTools
4215
+ };
4216
+ return this.createStreamWithRetry(
4217
+ adapter,
4218
+ sessionForAdapter,
4219
+ message,
4220
+ streamOptions,
4221
+ options.backend,
4222
+ options.credentials
4223
+ );
4224
+ }
4225
+ /** Stage 7: Apply onEvent middleware pipeline (sequential transform/suppress). */
4226
+ async applyOnEventMiddleware(event, ctx) {
4227
+ let processed = event;
4228
+ for (const mw of this._middleware) {
4229
+ if (mw.onEvent && processed) {
4230
+ processed = await mw.onEvent(processed, ctx);
4231
+ }
4232
+ }
4233
+ return processed;
4234
+ }
4235
+ /** Stage 8: Finalize accumulator, apply afterReceive middleware, persist assistant message. */
4236
+ async finalizeAssistantMessage(cid, accumulator, ctx) {
4237
+ let assistantMessage = accumulator.finalize();
4238
+ for (const mw of this._middleware) {
4239
+ if (mw.onAfterReceive) {
4240
+ assistantMessage = await mw.onAfterReceive(assistantMessage, ctx);
4241
+ }
4242
+ }
4243
+ await this._sessionStore.appendMessage(cid, assistantMessage);
4244
+ this._notifySessionChange();
4245
+ }
4246
+ /** Stage 9: Error handling — apply onError middleware, transition state. Returns null if suppressed. */
4247
+ async handleSendError(error, cid) {
4248
+ let processedError = error instanceof Error ? error : new Error(String(error));
4249
+ const ctx = {
4250
+ sessionId: cid,
4251
+ signal: this._abortController?.signal ?? new AbortController().signal
4252
+ };
4253
+ for (const mw of this._middleware) {
4254
+ if (mw.onError) {
4255
+ const result = await mw.onError(processedError, ctx);
4256
+ if (result === null) {
4257
+ if (this._state.canTransition("idle")) {
4258
+ this._state.transition("idle");
4259
+ }
4260
+ return null;
4261
+ }
4262
+ processedError = result;
4263
+ }
4264
+ }
4265
+ if (this._state.canTransition("error")) {
4266
+ this._state.transition("error");
4112
4267
  }
4113
- this._currentBackend = name;
4268
+ return processedError;
4269
+ }
4270
+ abort() {
4271
+ this._abortController?.abort("User abort");
4114
4272
  }
4115
- switchModel(model) {
4273
+ // ── Backend / Model ────────────────────────────────────────
4274
+ async listModels(options) {
4116
4275
  this.assertNotDisposed();
4117
- this._currentModel = model;
4276
+ let models = [];
4277
+ const firstAdapter = [...this._adapterPool.values()][0];
4278
+ if (firstAdapter) {
4279
+ try {
4280
+ models = await firstAdapter.listModels();
4281
+ } catch {
4282
+ return [];
4283
+ }
4284
+ } else if (options?.backend && options?.credentials) {
4285
+ try {
4286
+ const adapter = await this.getOrCreateAdapter(options.backend, options.credentials);
4287
+ models = await adapter.listModels();
4288
+ } catch {
4289
+ return [];
4290
+ }
4291
+ }
4292
+ for (const model of models) {
4293
+ if (model.contextWindow != null) {
4294
+ this._modelContextWindows.set(model.id, model.contextWindow);
4295
+ }
4296
+ }
4297
+ return models;
4118
4298
  }
4119
- async listModels() {
4299
+ async listBackends() {
4120
4300
  this.assertNotDisposed();
4121
- const adapter = await this.getOrCreateAdapter();
4122
- return adapter.listModels();
4301
+ return Object.keys(this._backends).map((name) => ({ name }));
4123
4302
  }
4124
4303
  // ── Tools ──────────────────────────────────────────────────
4125
4304
  get registeredTools() {
@@ -4144,37 +4323,46 @@ var ChatRuntime = class {
4144
4323
  if (idx >= 0) this._middleware.splice(idx, 1);
4145
4324
  }
4146
4325
  // ── Context Stats ─────────────────────────────────────────
4147
- getContextStats(sessionId) {
4326
+ async getContextStats(sessionId) {
4148
4327
  const cid = toChatId(sessionId);
4149
4328
  return this._contextStats.get(cid) ?? null;
4150
4329
  }
4151
4330
  // ── Session Subscription ──────────────────────────────────
4152
4331
  onSessionChange(callback) {
4153
- this._sessionListeners.add(callback);
4154
- return () => {
4155
- this._sessionListeners.delete(callback);
4156
- };
4332
+ return this._sessionListeners.add(callback);
4157
4333
  }
4158
4334
  _notifySessionChange() {
4159
- for (const cb of this._sessionListeners) {
4160
- try {
4161
- cb();
4162
- } catch {
4163
- }
4164
- }
4335
+ this._sessionListeners.notify();
4165
4336
  }
4166
4337
  // ── Private Helpers ────────────────────────────────────────
4167
- async getOrCreateAdapter() {
4168
- if (this._activeAdapter) return this._activeAdapter;
4169
- const factory = this._backends[this._currentBackend];
4338
+ async getOrCreateAdapter(backend, credentials) {
4339
+ const key = this.getPoolKey(backend, credentials);
4340
+ const existing = this._adapterPool.get(key);
4341
+ if (existing) return existing;
4342
+ for (const [oldKey, oldAdapter] of this._adapterPool) {
4343
+ if (oldKey.startsWith(backend + ":")) {
4344
+ try {
4345
+ await oldAdapter.dispose();
4346
+ } catch {
4347
+ }
4348
+ this._adapterPool.delete(oldKey);
4349
+ }
4350
+ }
4351
+ const factory = this._backends[backend];
4170
4352
  if (!factory) {
4171
4353
  throw new ChatError(
4172
- `Backend "${this._currentBackend}" not found`,
4354
+ `Backend "${backend}" not found`,
4173
4355
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4174
4356
  );
4175
4357
  }
4176
- this._activeAdapter = await factory();
4177
- return this._activeAdapter;
4358
+ const adapter = await factory(credentials);
4359
+ this._adapterPool.set(key, adapter);
4360
+ return adapter;
4361
+ }
4362
+ getPoolKey(backend, credentials) {
4363
+ const token = credentials.accessToken;
4364
+ const hash = token.length > 16 ? token.slice(0, 8) + token.slice(-8) : token;
4365
+ return `${backend}:${hash}`;
4178
4366
  }
4179
4367
  /** Wrap each tool's execute to inject ToolContext as 2nd argument */
4180
4368
  injectToolContext(tools, context) {
@@ -4206,17 +4394,25 @@ var ChatRuntime = class {
4206
4394
  }
4207
4395
  }
4208
4396
  /** Get or create adapter with retry on connection errors */
4209
- async getOrCreateAdapterWithRetry() {
4397
+ async getOrCreateAdapterWithRetry(backend, credentials) {
4210
4398
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4211
4399
  const delayMs = this._retryConfig?.delayMs ?? 0;
4212
4400
  let lastError;
4213
4401
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4214
4402
  try {
4215
- return await this.getOrCreateAdapter();
4403
+ return await this.getOrCreateAdapter(backend, credentials);
4216
4404
  } catch (err) {
4217
4405
  lastError = err instanceof Error ? err : new Error(String(err));
4218
4406
  if (attempt < maxAttempts) {
4219
- this._activeAdapter = null;
4407
+ const key = this.getPoolKey(backend, credentials);
4408
+ const old = this._adapterPool.get(key);
4409
+ if (old) {
4410
+ try {
4411
+ await old.dispose();
4412
+ } catch {
4413
+ }
4414
+ }
4415
+ this._adapterPool.delete(key);
4220
4416
  await delay(delayMs);
4221
4417
  }
4222
4418
  }
@@ -4229,7 +4425,7 @@ var ChatRuntime = class {
4229
4425
  * retries with a fresh adapter. Once first event is received,
4230
4426
  * the stream is committed (no more retries).
4231
4427
  */
4232
- async createStreamWithRetry(adapter, session, message, options) {
4428
+ async createStreamWithRetry(adapter, session, message, options, backend, credentials) {
4233
4429
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4234
4430
  const delayMs = this._retryConfig?.delayMs ?? 0;
4235
4431
  let lastError;
@@ -4250,13 +4446,14 @@ var ChatRuntime = class {
4250
4446
  } catch (err) {
4251
4447
  lastError = err instanceof Error ? err : new Error(String(err));
4252
4448
  if (attempt < maxAttempts) {
4253
- if (this._activeAdapter) {
4254
- await this._activeAdapter.dispose().catch(() => {
4255
- });
4449
+ try {
4450
+ await currentAdapter.dispose();
4451
+ } catch {
4256
4452
  }
4257
- this._activeAdapter = null;
4453
+ const key = this.getPoolKey(backend, credentials);
4454
+ this._adapterPool.delete(key);
4258
4455
  await delay(delayMs);
4259
- currentAdapter = await this.getOrCreateAdapter();
4456
+ currentAdapter = await this.getOrCreateAdapter(backend, credentials);
4260
4457
  }
4261
4458
  }
4262
4459
  }
@@ -4264,16 +4461,17 @@ var ChatRuntime = class {
4264
4461
  }
4265
4462
  };
4266
4463
  function delay(ms) {
4267
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4464
+ return new Promise((resolve) => setTimeout(resolve, ms));
4268
4465
  }
4269
4466
  function createChatRuntime(options) {
4270
4467
  return new ChatRuntime(options);
4271
4468
  }
4272
4469
 
4273
4470
  // src/chat/storage.ts
4471
+ init_errors2();
4274
4472
  init_errors();
4275
4473
  var StorageError = class extends AgentSDKError {
4276
- /** Machine-readable error code */
4474
+ /** Machine-readable error code from the unified ErrorCode enum */
4277
4475
  code;
4278
4476
  constructor(message, code) {
4279
4477
  super(message);
@@ -4310,7 +4508,7 @@ var InMemoryStorage = class {
4310
4508
  if (this.data.has(key)) {
4311
4509
  throw new StorageError(
4312
4510
  `Item with key "${key}" already exists`,
4313
- "DUPLICATE_KEY"
4511
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4314
4512
  );
4315
4513
  }
4316
4514
  this.data.set(key, structuredClone(item));
@@ -4320,7 +4518,7 @@ var InMemoryStorage = class {
4320
4518
  if (!this.data.has(key)) {
4321
4519
  throw new StorageError(
4322
4520
  `Item with key "${key}" not found`,
4323
- "NOT_FOUND"
4521
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4324
4522
  );
4325
4523
  }
4326
4524
  this.data.set(key, structuredClone(item));
@@ -4330,7 +4528,7 @@ var InMemoryStorage = class {
4330
4528
  if (!this.data.has(key)) {
4331
4529
  throw new StorageError(
4332
4530
  `Item with key "${key}" not found`,
4333
- "NOT_FOUND"
4531
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4334
4532
  );
4335
4533
  }
4336
4534
  this.data.delete(key);
@@ -4395,7 +4593,7 @@ var FileStorage = class {
4395
4593
  if (existsSync(filePath)) {
4396
4594
  throw new StorageError(
4397
4595
  `Item with key "${key}" already exists`,
4398
- "DUPLICATE_KEY"
4596
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4399
4597
  );
4400
4598
  }
4401
4599
  this.writeFile(filePath, item);
@@ -4406,7 +4604,7 @@ var FileStorage = class {
4406
4604
  if (!existsSync(filePath)) {
4407
4605
  throw new StorageError(
4408
4606
  `Item with key "${key}" not found`,
4409
- "NOT_FOUND"
4607
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4410
4608
  );
4411
4609
  }
4412
4610
  this.writeFile(filePath, item);
@@ -4417,7 +4615,7 @@ var FileStorage = class {
4417
4615
  if (!existsSync(filePath)) {
4418
4616
  throw new StorageError(
4419
4617
  `Item with key "${key}" not found`,
4420
- "NOT_FOUND"
4618
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4421
4619
  );
4422
4620
  }
4423
4621
  unlinkSync(filePath);
@@ -4463,12 +4661,12 @@ var FileStorage = class {
4463
4661
  if (error instanceof SyntaxError) {
4464
4662
  throw new StorageError(
4465
4663
  `Failed to parse file: ${filePath}`,
4466
- "SERIALIZATION_ERROR"
4664
+ "STORAGE_SERIALIZATION_ERROR" /* STORAGE_SERIALIZATION_ERROR */
4467
4665
  );
4468
4666
  }
4469
4667
  throw new StorageError(
4470
4668
  `Failed to read file: ${filePath}`,
4471
- "IO_ERROR"
4669
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4472
4670
  );
4473
4671
  }
4474
4672
  }
@@ -4479,13 +4677,14 @@ var FileStorage = class {
4479
4677
  } catch {
4480
4678
  throw new StorageError(
4481
4679
  `Failed to write file: ${filePath}`,
4482
- "IO_ERROR"
4680
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4483
4681
  );
4484
4682
  }
4485
4683
  }
4486
4684
  };
4487
4685
 
4488
4686
  // src/chat/sessions.ts
4687
+ init_errors();
4489
4688
  var BaseSessionStore = class {
4490
4689
  constructor(adapter) {
4491
4690
  this.adapter = adapter;
@@ -4524,7 +4723,7 @@ var BaseSessionStore = class {
4524
4723
  async updateTitle(id, title) {
4525
4724
  const session = await this.adapter.get(id);
4526
4725
  if (!session) {
4527
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4726
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4528
4727
  }
4529
4728
  session.title = title;
4530
4729
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4533,7 +4732,7 @@ var BaseSessionStore = class {
4533
4732
  async updateConfig(id, config) {
4534
4733
  const session = await this.adapter.get(id);
4535
4734
  if (!session) {
4536
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4735
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4537
4736
  }
4538
4737
  session.config = { ...session.config, ...config };
4539
4738
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4545,7 +4744,7 @@ var BaseSessionStore = class {
4545
4744
  async appendMessage(sessionId, message) {
4546
4745
  const session = await this.adapter.get(sessionId);
4547
4746
  if (!session) {
4548
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4747
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4549
4748
  }
4550
4749
  session.messages.push(structuredClone(message));
4551
4750
  session.metadata.messageCount = session.messages.length;
@@ -4556,7 +4755,7 @@ var BaseSessionStore = class {
4556
4755
  if (messages.length === 0) return;
4557
4756
  const session = await this.adapter.get(sessionId);
4558
4757
  if (!session) {
4559
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4758
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4560
4759
  }
4561
4760
  for (const msg of messages) {
4562
4761
  session.messages.push(structuredClone(msg));
@@ -4568,7 +4767,7 @@ var BaseSessionStore = class {
4568
4767
  async loadMessages(sessionId, options) {
4569
4768
  const session = await this.adapter.get(sessionId);
4570
4769
  if (!session) {
4571
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4770
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4572
4771
  }
4573
4772
  const total = session.messages.length;
4574
4773
  const offset = options?.offset ?? 0;
@@ -4580,24 +4779,6 @@ var BaseSessionStore = class {
4580
4779
  hasMore: offset + limit < total
4581
4780
  };
4582
4781
  }
4583
- async archiveSession(id) {
4584
- const session = await this.adapter.get(id);
4585
- if (!session) {
4586
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4587
- }
4588
- session.status = "archived";
4589
- session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4590
- await this.adapter.update(id, session);
4591
- }
4592
- async unarchiveSession(id) {
4593
- const session = await this.adapter.get(id);
4594
- if (!session) {
4595
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4596
- }
4597
- session.status = "active";
4598
- session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4599
- await this.adapter.update(id, session);
4600
- }
4601
4782
  async searchSessions(options) {
4602
4783
  const query = options.query.toLowerCase();
4603
4784
  const limit = options.limit ?? 20;
@@ -4619,15 +4800,6 @@ var BaseSessionStore = class {
4619
4800
  async clear() {
4620
4801
  return this.adapter.clear();
4621
4802
  }
4622
- // ── Deprecated Aliases ──────────────────────────────────────
4623
- /** @deprecated Use `appendMessage()` instead */
4624
- async addMessage(sessionId, message) {
4625
- return this.appendMessage(sessionId, message);
4626
- }
4627
- /** @deprecated Use `loadMessages()` instead */
4628
- async getMessages(sessionId, options) {
4629
- return this.loadMessages(sessionId, options);
4630
- }
4631
4803
  };
4632
4804
  var InMemorySessionStore = class extends BaseSessionStore {
4633
4805
  constructor() {
@@ -4640,28 +4812,59 @@ var FileSessionStore = class extends BaseSessionStore {
4640
4812
  }
4641
4813
  };
4642
4814
 
4815
+ // src/chat/backends/types.ts
4816
+ function isResumableBackend(adapter) {
4817
+ return "canResume" in adapter && typeof adapter.canResume === "function";
4818
+ }
4819
+
4643
4820
  // src/chat/backends/base.ts
4644
4821
  var BaseBackendAdapter = class {
4645
4822
  name;
4646
- _agentService;
4647
- _agent = null;
4823
+ _agentService = null;
4824
+ _agentServiceFactory = null;
4648
4825
  _disposed = false;
4649
4826
  _agentConfig;
4650
4827
  _ownsService;
4828
+ // Agent lifecycle: tracks current agent and the model it was created with.
4829
+ // For persistent sessions, reused across calls when model matches.
4830
+ // For non-persistent, recreated every call.
4831
+ _currentAgent = null;
4651
4832
  constructor(name, options) {
4652
4833
  this.name = name;
4653
4834
  this._agentConfig = options.agentConfig;
4654
4835
  if (options.agentService) {
4655
4836
  this._agentService = options.agentService;
4656
4837
  this._ownsService = false;
4838
+ } else if (options.agentServiceFactory) {
4839
+ this._agentServiceFactory = options.agentServiceFactory;
4840
+ this._ownsService = true;
4657
4841
  } else {
4658
4842
  this._agentService = this.createService();
4659
4843
  this._ownsService = true;
4660
4844
  }
4661
4845
  }
4662
4846
  get agentService() {
4847
+ if (!this._agentService) {
4848
+ if (this._agentServiceFactory) {
4849
+ this._agentService = this._agentServiceFactory();
4850
+ this._agentServiceFactory = null;
4851
+ } else {
4852
+ throw new ChatError("Agent service not available", {
4853
+ code: "BACKEND_NOT_INSTALLED" /* BACKEND_NOT_INSTALLED */
4854
+ });
4855
+ }
4856
+ }
4663
4857
  return this._agentService;
4664
4858
  }
4859
+ get currentModel() {
4860
+ return this._agentConfig.model;
4861
+ }
4862
+ /**
4863
+ * @deprecated No-op. Tools are passed per-call via SendMessageOptions.tools.
4864
+ * Kept for backward compatibility with code that calls setTools() directly.
4865
+ */
4866
+ setTools() {
4867
+ }
4665
4868
  async sendMessage(session, message, options) {
4666
4869
  this.assertNotDisposed();
4667
4870
  const events = this.streamMessage(session, message, options);
@@ -4699,9 +4902,13 @@ var BaseBackendAdapter = class {
4699
4902
  */
4700
4903
  async *streamAgentEvents(agent, messages, options) {
4701
4904
  const messageId = createChatId();
4905
+ const model = options?.model ?? this._agentConfig.model ?? "";
4702
4906
  const agentEvents = agent.streamWithContext(messages, {
4907
+ model,
4703
4908
  signal: options?.signal,
4704
- context: options?.context
4909
+ context: options?.context,
4910
+ tools: options?.tools,
4911
+ ...options?.systemPrompt ? { systemMessage: options.systemPrompt } : {}
4705
4912
  });
4706
4913
  yield { type: "message:start", messageId, role: "assistant" };
4707
4914
  let text = "";
@@ -4727,31 +4934,45 @@ var BaseBackendAdapter = class {
4727
4934
  }
4728
4935
  async listModels() {
4729
4936
  this.assertNotDisposed();
4730
- return this._agentService.listModels();
4937
+ return this.agentService.listModels();
4731
4938
  }
4732
4939
  async validate() {
4733
4940
  this.assertNotDisposed();
4734
- return this._agentService.validate();
4941
+ return this.agentService.validate();
4735
4942
  }
4736
4943
  async dispose() {
4737
4944
  if (this._disposed) return;
4738
4945
  this._disposed = true;
4739
- this._agent?.dispose();
4740
- this._agent = null;
4741
- if (this._ownsService) {
4946
+ if (this._currentAgent) {
4947
+ this._currentAgent.instance.dispose();
4948
+ this._currentAgent = null;
4949
+ }
4950
+ if (this._ownsService && this._agentService && typeof this._agentService.dispose === "function") {
4742
4951
  await this._agentService.dispose();
4743
4952
  }
4744
4953
  }
4745
- /** Get or create an agent, applying model override from options */
4954
+ /** Get or create an agent. Model is passed per-call via RunOptions.
4955
+ * Tools are passed per-call via SendMessageOptions — not baked into config.
4956
+ * For persistent sessions, reuses agent when model matches. */
4746
4957
  getOrCreateAgent(options) {
4747
- const config = options?.model ? { ...this._agentConfig, model: options.model } : this._agentConfig;
4748
- if (this._agentConfig.sessionMode === "persistent" && this._agent) {
4749
- return this._agent;
4958
+ const model = options?.model ?? this._agentConfig.model;
4959
+ if (this._agentConfig.sessionMode === "persistent" && this._currentAgent) {
4960
+ if (this._currentAgent.model === model) {
4961
+ return this._currentAgent.instance;
4962
+ }
4963
+ this._currentAgent.instance.dispose();
4964
+ this._currentAgent = null;
4750
4965
  }
4751
- const agent = this._agentService.createAgent(config);
4752
- if (this._agentConfig.sessionMode === "persistent") {
4753
- this._agent = agent;
4966
+ if (this._currentAgent) {
4967
+ this._currentAgent.instance.dispose();
4968
+ this._currentAgent = null;
4754
4969
  }
4970
+ const config = {
4971
+ ...this._agentConfig,
4972
+ ...model !== void 0 && { model }
4973
+ };
4974
+ const agent = this.agentService.createAgent(config);
4975
+ this._currentAgent = { instance: agent, model };
4755
4976
  return agent;
4756
4977
  }
4757
4978
  assertNotDisposed() {
@@ -4776,8 +4997,8 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
4776
4997
  this._copilotOptions = options.copilotOptions;
4777
4998
  }
4778
4999
  createService() {
4779
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4780
- return createAgentService2("copilot", this._copilotOptions);
5000
+ const { createCopilotService: createCopilotService2 } = (init_copilot(), __toCommonJS(copilot_exports));
5001
+ return createCopilotService2(this._copilotOptions || {});
4781
5002
  }
4782
5003
  get backendSessionId() {
4783
5004
  return this._backendSessionId;
@@ -4829,8 +5050,8 @@ var ClaudeChatAdapter = class extends BaseBackendAdapter {
4829
5050
  this._claudeOptions = options.claudeOptions;
4830
5051
  }
4831
5052
  createService() {
4832
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4833
- return createAgentService2("claude", this._claudeOptions);
5053
+ const { createClaudeService: createClaudeService2 } = (init_claude(), __toCommonJS(claude_exports));
5054
+ return createClaudeService2(this._claudeOptions || {});
4834
5055
  }
4835
5056
  get backendSessionId() {
4836
5057
  return this._backendSessionId;
@@ -4877,20 +5098,8 @@ var VercelAIChatAdapter = class extends BaseBackendAdapter {
4877
5098
  this._vercelOptions = options.vercelOptions;
4878
5099
  }
4879
5100
  createService() {
4880
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4881
- return createAgentService2("vercel-ai", this._vercelOptions);
4882
- }
4883
- get backendSessionId() {
4884
- return null;
4885
- }
4886
- canResume() {
4887
- return false;
4888
- }
4889
- async *resume(_session, _backendSessionId, _options) {
4890
- throw new ChatError(
4891
- "Vercel AI adapter does not support session resume (stateless)",
4892
- { code: "PROVIDER_ERROR" /* PROVIDER_ERROR */ }
4893
- );
5101
+ const { createVercelAIService: createVercelAIService2 } = (init_vercel_ai(), __toCommonJS(vercel_ai_exports));
5102
+ return createVercelAIService2(this._vercelOptions || {});
4894
5103
  }
4895
5104
  captureSessionId(_agent) {
4896
5105
  }
@@ -5067,9 +5276,9 @@ var InProcessChatTransport = class {
5067
5276
  send(event) {
5068
5277
  if (!this._open) return;
5069
5278
  if (this._resolve) {
5070
- const resolve2 = this._resolve;
5279
+ const resolve = this._resolve;
5071
5280
  this._resolve = null;
5072
- resolve2({ value: event, done: false });
5281
+ resolve({ value: event, done: false });
5073
5282
  } else {
5074
5283
  this._buffer.push(event);
5075
5284
  }
@@ -5078,9 +5287,9 @@ var InProcessChatTransport = class {
5078
5287
  if (!this._open) return;
5079
5288
  this._open = false;
5080
5289
  if (this._resolve) {
5081
- const resolve2 = this._resolve;
5290
+ const resolve = this._resolve;
5082
5291
  this._resolve = null;
5083
- resolve2({ value: void 0, done: true });
5292
+ resolve({ value: void 0, done: true });
5084
5293
  }
5085
5294
  }
5086
5295
  error(err) {
@@ -5092,9 +5301,9 @@ var InProcessChatTransport = class {
5092
5301
  recoverable: false
5093
5302
  };
5094
5303
  if (this._resolve) {
5095
- const resolve2 = this._resolve;
5304
+ const resolve = this._resolve;
5096
5305
  this._resolve = null;
5097
- resolve2({ value: errorEvent, done: false });
5306
+ resolve({ value: errorEvent, done: false });
5098
5307
  } else {
5099
5308
  this._error = err;
5100
5309
  }
@@ -5119,8 +5328,8 @@ var InProcessChatTransport = class {
5119
5328
  if (!this._open) {
5120
5329
  return Promise.resolve({ value: void 0, done: true });
5121
5330
  }
5122
- return new Promise((resolve2) => {
5123
- this._resolve = resolve2;
5331
+ return new Promise((resolve) => {
5332
+ this._resolve = resolve;
5124
5333
  });
5125
5334
  }
5126
5335
  };
@@ -5291,6 +5500,6 @@ var ChatEventBus = class extends TypedEventEmitter {
5291
5500
  }
5292
5501
  };
5293
5502
 
5294
- export { BaseBackendAdapter, ChatError, ChatErrorCode, ChatEventBus, ChatError as ChatSDKError, ClaudeChatAdapter, ContextWindowManager, CopilotChatAdapter, ExponentialBackoffStrategy, FileSessionStore, InMemorySessionStore, InProcessChatTransport, MessageAccumulator, SSEChatTransport, TypedEventEmitter, VercelAIChatAdapter, WsChatTransport, adaptAgentEvents, agentEventToChatEvent, classifyError, createChatId, createChatRuntime, estimateTokens, fromAgentMessage, getMessageReasoning, getMessageText, getMessageToolCalls, isChatEvent, isChatMessage, isChatSession, isFilePart, isMessagePart, isReasoningPart, isRetryable, isSourcePart, isTextPart, isToolCallPart, streamToTransport, toAgentMessage, toChatId, withRetry, withStreamWatchdog };
5503
+ export { BaseBackendAdapter, ChatError, ChatEventBus, ClaudeChatAdapter, ContextWindowManager, CopilotChatAdapter, ErrorCode, ExponentialBackoffStrategy, FileSessionStore, InMemorySessionStore, InProcessChatTransport, ListenerSet, MessageAccumulator, SSEChatTransport, TypedEventEmitter, VercelAIChatAdapter, WsChatTransport, adaptAgentEvents, agentEventToChatEvent, classifyError, createChatId, createChatRuntime, createTextMessage, estimateTokens, fromAgentMessage, getMessageReasoning, getMessageText, getMessageToolCalls, isChatEvent, isChatMessage, isChatSession, isFilePart, isMessagePart, isObservableSession, isReasoningPart, isResumableBackend, isRetryable, isSourcePart, isTextPart, isToolCallPart, streamToTransport, toAgentMessage, toChatId, withRetry, withStreamWatchdog };
5295
5504
  //# sourceMappingURL=index.js.map
5296
5505
  //# sourceMappingURL=index.js.map