@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
@@ -2,29 +2,6 @@
2
2
 
3
3
  var fs = require('fs');
4
4
  var path = require('path');
5
- var os = require('os');
6
-
7
- function _interopNamespace(e) {
8
- if (e && e.__esModule) return e;
9
- var n = Object.create(null);
10
- if (e) {
11
- Object.keys(e).forEach(function (k) {
12
- if (k !== 'default') {
13
- var d = Object.getOwnPropertyDescriptor(e, k);
14
- Object.defineProperty(n, k, d.get ? d : {
15
- enumerable: true,
16
- get: function () { return e[k]; }
17
- });
18
- }
19
- });
20
- }
21
- n.default = e;
22
- return Object.freeze(n);
23
- }
24
-
25
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
26
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
- var os__namespace = /*#__PURE__*/_interopNamespace(os);
28
5
 
29
6
  var __defProp = Object.defineProperty;
30
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -47,16 +24,100 @@ var __copyProps = (to, from, except, desc) => {
47
24
  };
48
25
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
49
26
 
50
- // src/errors.ts
51
- var AgentSDKError, ReentrancyError, DisposedError, BackendNotFoundError, BackendAlreadyRegisteredError, SubprocessError, DependencyError, AbortError, ToolExecutionError, StructuredOutputError;
27
+ // src/types/errors.ts
28
+ function isRecoverableErrorCode(code) {
29
+ return RECOVERABLE_CODES.has(code);
30
+ }
31
+ function classifyAgentError(error) {
32
+ const msg = (error instanceof Error ? error.message : error).toLowerCase();
33
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("timedout") || msg.includes("etimedout")) {
34
+ return "TIMEOUT" /* TIMEOUT */;
35
+ }
36
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429") || msg.includes("too many requests")) {
37
+ return "RATE_LIMIT" /* RATE_LIMIT */;
38
+ }
39
+ if (msg.includes("unauthorized") || msg.includes("401") || msg.includes("auth") && (msg.includes("expired") || msg.includes("invalid") || msg.includes("denied") || msg.includes("failed"))) {
40
+ return "AUTH_EXPIRED" /* AUTH_EXPIRED */;
41
+ }
42
+ if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("network") || msg.includes("fetch failed") || msg.includes("socket hang up")) {
43
+ return "NETWORK" /* NETWORK */;
44
+ }
45
+ if (msg.includes("subprocess") || msg.includes("process exited") || msg.includes("spawn") || msg.includes("enoent") || msg.includes("killed")) {
46
+ return "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */;
47
+ }
48
+ if (msg.includes("abort") || msg.includes("cancel")) {
49
+ return "ABORTED" /* ABORTED */;
50
+ }
51
+ 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")) {
52
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
53
+ }
54
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
55
+ }
56
+ exports.ErrorCode = void 0; var RECOVERABLE_CODES;
52
57
  var init_errors = __esm({
58
+ "src/types/errors.ts"() {
59
+ exports.ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
60
+ ErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
61
+ ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
62
+ ErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
63
+ ErrorCode2["NETWORK"] = "NETWORK";
64
+ ErrorCode2["TIMEOUT"] = "TIMEOUT";
65
+ ErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
66
+ ErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
67
+ ErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
68
+ ErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
69
+ ErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
70
+ ErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
71
+ ErrorCode2["REENTRANCY"] = "REENTRANCY";
72
+ ErrorCode2["DISPOSED"] = "DISPOSED";
73
+ ErrorCode2["ABORTED"] = "ABORTED";
74
+ ErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
75
+ ErrorCode2["DEPENDENCY_MISSING"] = "DEPENDENCY_MISSING";
76
+ ErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
77
+ ErrorCode2["TOOL_EXECUTION"] = "TOOL_EXECUTION";
78
+ ErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
79
+ ErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
80
+ ErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
81
+ ErrorCode2["PROVIDER_NOT_FOUND"] = "PROVIDER_NOT_FOUND";
82
+ ErrorCode2["AUTH_REQUIRED"] = "AUTH_REQUIRED";
83
+ ErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
84
+ ErrorCode2["STORAGE_NOT_FOUND"] = "STORAGE_NOT_FOUND";
85
+ ErrorCode2["STORAGE_DUPLICATE_KEY"] = "STORAGE_DUPLICATE_KEY";
86
+ ErrorCode2["STORAGE_IO_ERROR"] = "STORAGE_IO_ERROR";
87
+ ErrorCode2["STORAGE_SERIALIZATION_ERROR"] = "STORAGE_SERIALIZATION_ERROR";
88
+ return ErrorCode2;
89
+ })(exports.ErrorCode || {});
90
+ RECOVERABLE_CODES = /* @__PURE__ */ new Set([
91
+ "TIMEOUT" /* TIMEOUT */,
92
+ "RATE_LIMIT" /* RATE_LIMIT */,
93
+ "NETWORK" /* NETWORK */,
94
+ "TOOL_EXECUTION" /* TOOL_EXECUTION */,
95
+ "MODEL_OVERLOADED" /* MODEL_OVERLOADED */,
96
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
97
+ ]);
98
+ }
99
+ });
100
+
101
+ // src/errors.ts
102
+ var AgentSDKError, ReentrancyError, DisposedError, SubprocessError, DependencyError, AbortError, ToolExecutionError, ActivityTimeoutError;
103
+ var init_errors2 = __esm({
53
104
  "src/errors.ts"() {
105
+ init_errors();
54
106
  AgentSDKError = class extends Error {
55
107
  /** @internal Marker for cross-bundle identity checks */
56
108
  _agentSDKError = true;
109
+ /** Machine-readable error code. Prefer values from the ErrorCode enum. */
110
+ code;
111
+ /** Whether this error is safe to retry */
112
+ retryable;
113
+ /** HTTP status code hint for error classification */
114
+ httpStatus;
57
115
  constructor(message, options) {
58
116
  super(message, options);
59
117
  this.name = "AgentSDKError";
118
+ this.code = options?.code;
119
+ this.retryable = options?.retryable ?? false;
120
+ this.httpStatus = options?.httpStatus;
60
121
  }
61
122
  /** Check if an error is an AgentSDKError (works across bundled copies) */
62
123
  static is(error) {
@@ -65,83 +126,84 @@ var init_errors = __esm({
65
126
  };
66
127
  ReentrancyError = class extends AgentSDKError {
67
128
  constructor() {
68
- super("Agent is already running. Await the current run before starting another.");
129
+ super("Agent is already running. Await the current run before starting another.", {
130
+ code: "REENTRANCY" /* REENTRANCY */
131
+ });
69
132
  this.name = "ReentrancyError";
70
133
  }
71
134
  };
72
135
  DisposedError = class extends AgentSDKError {
73
136
  constructor(entity) {
74
- super(`${entity} has been disposed and cannot be used.`);
137
+ super(`${entity} has been disposed and cannot be used.`, {
138
+ code: "DISPOSED" /* DISPOSED */
139
+ });
75
140
  this.name = "DisposedError";
76
141
  }
77
142
  };
78
- BackendNotFoundError = class extends AgentSDKError {
79
- constructor(backend) {
80
- super(
81
- `Unknown backend: "${backend}". Built-in: copilot, claude, vercel-ai. Custom: use registerBackend() first.`
82
- );
83
- this.name = "BackendNotFoundError";
84
- }
85
- };
86
- BackendAlreadyRegisteredError = class extends AgentSDKError {
87
- constructor(backend) {
88
- super(`Backend "${backend}" is already registered. Use a different name or unregister first.`);
89
- this.name = "BackendAlreadyRegisteredError";
90
- }
91
- };
92
143
  SubprocessError = class extends AgentSDKError {
93
144
  constructor(message, options) {
94
- super(message, options);
145
+ super(message, { ...options, code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */ });
95
146
  this.name = "SubprocessError";
96
147
  }
97
148
  };
98
149
  DependencyError = class extends AgentSDKError {
99
150
  packageName;
100
151
  constructor(packageName) {
101
- super(`${packageName} is not installed. Install it: npm install ${packageName}`);
152
+ super(`${packageName} is not installed. Install it: npm install ${packageName}`, {
153
+ code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */
154
+ });
102
155
  this.name = "DependencyError";
103
156
  this.packageName = packageName;
104
157
  }
105
158
  };
106
159
  AbortError = class extends AgentSDKError {
107
160
  constructor() {
108
- super("Agent run was aborted.");
161
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
109
162
  this.name = "AbortError";
110
163
  }
111
164
  };
112
165
  ToolExecutionError = class extends AgentSDKError {
113
166
  toolName;
114
167
  constructor(toolName, message, options) {
115
- super(`Tool "${toolName}" failed: ${message}`, options);
168
+ super(`Tool "${toolName}" failed: ${message}`, { ...options, code: "TOOL_EXECUTION" /* TOOL_EXECUTION */ });
116
169
  this.name = "ToolExecutionError";
117
170
  this.toolName = toolName;
118
171
  }
119
172
  };
120
- StructuredOutputError = class extends AgentSDKError {
121
- constructor(message, options) {
122
- super(`Structured output error: ${message}`, options);
123
- this.name = "StructuredOutputError";
173
+ ActivityTimeoutError = class extends AgentSDKError {
174
+ constructor(timeoutMs) {
175
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
176
+ code: "TIMEOUT" /* TIMEOUT */,
177
+ retryable: true
178
+ });
179
+ this.name = "ActivityTimeoutError";
124
180
  }
125
181
  };
126
182
  }
127
183
  });
128
184
 
129
- // src/types.ts
130
- function isToolDefinition(tool) {
131
- return "execute" in tool && typeof tool.execute === "function";
132
- }
133
- function isTextContent(content) {
134
- return typeof content === "string";
135
- }
136
- function isMultiPartContent(content) {
137
- return Array.isArray(content);
138
- }
185
+ // src/types/guards.ts
139
186
  function getTextContent(content) {
140
187
  if (typeof content === "string") return content;
141
188
  return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
142
189
  }
190
+ var init_guards = __esm({
191
+ "src/types/guards.ts"() {
192
+ }
193
+ });
194
+
195
+ // src/types/index.ts
143
196
  var init_types = __esm({
197
+ "src/types/index.ts"() {
198
+ init_errors();
199
+ init_guards();
200
+ }
201
+ });
202
+
203
+ // src/types.ts
204
+ var init_types2 = __esm({
144
205
  "src/types.ts"() {
206
+ init_types();
145
207
  }
146
208
  });
147
209
 
@@ -149,12 +211,15 @@ var init_types = __esm({
149
211
  var BaseAgent;
150
212
  var init_base_agent = __esm({
151
213
  "src/base-agent.ts"() {
214
+ init_errors2();
215
+ init_errors2();
152
216
  init_errors();
153
217
  BaseAgent = class {
154
218
  state = "idle";
155
219
  abortController = null;
156
220
  config;
157
221
  _cleanupExternalSignal = null;
222
+ _streamMiddleware = [];
158
223
  /** CLI session ID for persistent mode. Override in backends that support it. */
159
224
  get sessionId() {
160
225
  return void 0;
@@ -170,8 +235,11 @@ var init_base_agent = __esm({
170
235
  this.state = "running";
171
236
  try {
172
237
  const messages = [{ role: "user", content: prompt }];
173
- const result = await this.executeRun(messages, options, ac.signal);
174
- this.enrichAndNotifyUsage(result);
238
+ const result = await this.withRetry(
239
+ () => this.executeRun(messages, options, ac.signal),
240
+ options
241
+ );
242
+ this.enrichAndNotifyUsage(result, options);
175
243
  return result;
176
244
  } finally {
177
245
  this.cleanupRun();
@@ -183,8 +251,11 @@ var init_base_agent = __esm({
183
251
  const ac = this.createAbortController(options?.signal);
184
252
  this.state = "running";
185
253
  try {
186
- const result = await this.executeRun(messages, options, ac.signal);
187
- this.enrichAndNotifyUsage(result);
254
+ const result = await this.withRetry(
255
+ () => this.executeRun(messages, options, ac.signal),
256
+ options
257
+ );
258
+ this.enrichAndNotifyUsage(result, options);
188
259
  return result;
189
260
  } finally {
190
261
  this.cleanupRun();
@@ -197,13 +268,11 @@ var init_base_agent = __esm({
197
268
  this.state = "running";
198
269
  try {
199
270
  const messages = [{ role: "user", content: prompt }];
200
- const result = await this.executeRunStructured(
201
- messages,
202
- schema,
203
- options,
204
- ac.signal
271
+ const result = await this.withRetry(
272
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
273
+ options
205
274
  );
206
- this.enrichAndNotifyUsage(result);
275
+ this.enrichAndNotifyUsage(result, options);
207
276
  return result;
208
277
  } finally {
209
278
  this.cleanupRun();
@@ -216,8 +285,10 @@ var init_base_agent = __esm({
216
285
  this.state = "streaming";
217
286
  try {
218
287
  const messages = [{ role: "user", content: prompt }];
219
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
220
- yield* this.heartbeatStream(enriched);
288
+ yield* this.streamWithRetry(
289
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
290
+ options
291
+ );
221
292
  } finally {
222
293
  this.cleanupRun();
223
294
  }
@@ -228,12 +299,37 @@ var init_base_agent = __esm({
228
299
  const ac = this.createAbortController(options?.signal);
229
300
  this.state = "streaming";
230
301
  try {
231
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
232
- yield* this.heartbeatStream(enriched);
302
+ yield* this.streamWithRetry(
303
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
304
+ options
305
+ );
233
306
  } finally {
234
307
  this.cleanupRun();
235
308
  }
236
309
  }
310
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
311
+ addStreamMiddleware(middleware) {
312
+ this.guardDisposed();
313
+ this._streamMiddleware.push(middleware);
314
+ }
315
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
316
+ async *applyStreamPipeline(source, options, ac) {
317
+ let stream = this.enrichStream(source, options);
318
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
319
+ stream = this.heartbeatStream(stream);
320
+ if (this._streamMiddleware.length > 0) {
321
+ const ctx = {
322
+ model: options.model,
323
+ backend: this.backendName,
324
+ abortController: ac,
325
+ config: Object.freeze({ ...this.config })
326
+ };
327
+ for (const mw of this._streamMiddleware) {
328
+ stream = mw(stream, ctx);
329
+ }
330
+ }
331
+ yield* stream;
332
+ }
237
333
  abort() {
238
334
  if (this.abortController) {
239
335
  this.abortController.abort();
@@ -256,26 +352,109 @@ var init_base_agent = __esm({
256
352
  this.abort();
257
353
  this.state = "disposed";
258
354
  }
355
+ // ─── Retry Logic ─────────────────────────────────────────────
356
+ /** Check if an error should be retried given the retry configuration. */
357
+ isRetryableError(error, retry) {
358
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
359
+ return false;
360
+ }
361
+ if (AgentSDKError.is(error)) {
362
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
363
+ return retry.retryableErrors.includes(error.code);
364
+ }
365
+ if (error.retryable) return true;
366
+ if (error.code) return isRecoverableErrorCode(error.code);
367
+ }
368
+ return false;
369
+ }
370
+ /** Execute a function with retry logic per RetryConfig. */
371
+ async withRetry(fn, options) {
372
+ const retry = options?.retry;
373
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
374
+ return fn();
375
+ }
376
+ const maxRetries = retry.maxRetries;
377
+ const initialDelay = retry.initialDelayMs ?? 1e3;
378
+ const multiplier = retry.backoffMultiplier ?? 2;
379
+ let lastError;
380
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
381
+ try {
382
+ return await fn();
383
+ } catch (err) {
384
+ lastError = err;
385
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
386
+ throw err;
387
+ }
388
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
389
+ await new Promise((resolve) => setTimeout(resolve, delay2));
390
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
391
+ throw err;
392
+ }
393
+ }
394
+ }
395
+ throw lastError;
396
+ }
397
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
398
+ async *streamWithRetry(factory, options) {
399
+ const retry = options?.retry;
400
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
401
+ yield* factory();
402
+ return;
403
+ }
404
+ const maxRetries = retry.maxRetries;
405
+ const initialDelay = retry.initialDelayMs ?? 1e3;
406
+ const multiplier = retry.backoffMultiplier ?? 2;
407
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
408
+ try {
409
+ const stream = factory();
410
+ const iterator = stream[Symbol.asyncIterator]();
411
+ const first = await iterator.next();
412
+ if (first.done) return;
413
+ yield first.value;
414
+ while (true) {
415
+ const next = await iterator.next();
416
+ if (next.done) break;
417
+ yield next.value;
418
+ }
419
+ return;
420
+ } catch (err) {
421
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
422
+ throw err;
423
+ }
424
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
425
+ await new Promise((resolve) => setTimeout(resolve, delay2));
426
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
427
+ throw err;
428
+ }
429
+ }
430
+ }
431
+ }
432
+ // ─── CallOptions Resolution ──────────────────────────────────
433
+ /** Resolve tools to use for this call (per-call override > config default) */
434
+ resolveTools(options) {
435
+ return options?.tools ?? this.config.tools ?? [];
436
+ }
259
437
  // ─── Usage Enrichment ───────────────────────────────────────────
260
438
  /** Enrich result usage with model/backend and fire onUsage callback */
261
- enrichAndNotifyUsage(result) {
439
+ enrichAndNotifyUsage(result, options) {
262
440
  if (result.usage) {
263
441
  result.usage = {
264
442
  ...result.usage,
265
- model: this.config.model,
443
+ model: options.model,
266
444
  backend: this.backendName
267
445
  };
268
446
  this.callOnUsage(result.usage);
269
447
  }
270
448
  }
271
449
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
272
- async *enrichStream(source) {
450
+ async *enrichStream(source, options) {
451
+ const model = options.model;
273
452
  for await (const event of source) {
274
453
  if (event.type === "usage_update") {
275
454
  const usage = {
276
455
  promptTokens: event.promptTokens,
277
456
  completionTokens: event.completionTokens,
278
- model: this.config.model,
457
+ model,
279
458
  backend: this.backendName
280
459
  };
281
460
  this.callOnUsage(usage);
@@ -311,9 +490,9 @@ var init_base_agent = __esm({
311
490
  let heartbeatResolve = null;
312
491
  const timer = setInterval(() => {
313
492
  if (heartbeatResolve) {
314
- const resolve2 = heartbeatResolve;
493
+ const resolve = heartbeatResolve;
315
494
  heartbeatResolve = null;
316
- resolve2();
495
+ resolve();
317
496
  }
318
497
  }, interval);
319
498
  try {
@@ -321,8 +500,8 @@ var init_base_agent = __esm({
321
500
  if (!pendingEvent) {
322
501
  pendingEvent = iterator.next();
323
502
  }
324
- const heartbeatPromise = new Promise((resolve2) => {
325
- heartbeatResolve = resolve2;
503
+ const heartbeatPromise = new Promise((resolve) => {
504
+ heartbeatResolve = resolve;
326
505
  });
327
506
  const eventDone = pendingEvent.then(
328
507
  (r) => ({ kind: "event", result: r })
@@ -345,6 +524,35 @@ var init_base_agent = __esm({
345
524
  heartbeatResolve = null;
346
525
  }
347
526
  }
527
+ // ─── Activity Timeout ────────────────────────────────────────
528
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
529
+ * When timeoutMs is not set, passes through directly. */
530
+ async *activityTimeoutStream(source, timeoutMs, ac) {
531
+ if (!timeoutMs || timeoutMs <= 0) {
532
+ yield* source;
533
+ return;
534
+ }
535
+ const iterator = source[Symbol.asyncIterator]();
536
+ let timerId;
537
+ try {
538
+ while (true) {
539
+ const timeoutPromise = new Promise((_, reject) => {
540
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
541
+ });
542
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
543
+ clearTimeout(timerId);
544
+ if (result.done) break;
545
+ yield result.value;
546
+ }
547
+ } catch (err) {
548
+ if (err instanceof ActivityTimeoutError) {
549
+ ac.abort(err);
550
+ }
551
+ throw err;
552
+ } finally {
553
+ clearTimeout(timerId);
554
+ }
555
+ }
348
556
  // ─── Guards ───────────────────────────────────────────────────
349
557
  guardReentrancy() {
350
558
  if (this.state === "running" || this.state === "streaming") {
@@ -449,6 +657,66 @@ var init_schema = __esm({
449
657
  }
450
658
  });
451
659
 
660
+ // src/backends/shared.ts
661
+ function extractLastUserPrompt(messages) {
662
+ for (let i = messages.length - 1; i >= 0; i--) {
663
+ const msg = messages[i];
664
+ if (msg.role === "user") {
665
+ return getTextContent(msg.content);
666
+ }
667
+ }
668
+ return "";
669
+ }
670
+ function serializeToolCall(tc) {
671
+ const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
672
+ return ` Tool call: ${tc.name}(${args})`;
673
+ }
674
+ function serializeToolResult(tr) {
675
+ const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
676
+ const prefix = tr.isError ? "[ERROR] " : "";
677
+ return ` ${tr.name} \u2192 ${prefix}${result}`;
678
+ }
679
+ function buildContextualPrompt(messages) {
680
+ if (messages.length <= 1) {
681
+ return extractLastUserPrompt(messages);
682
+ }
683
+ const history = messages.slice(0, -1).map((msg) => {
684
+ if (msg.role === "user") {
685
+ return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
686
+ }
687
+ if (msg.role === "tool" && msg.toolResults) {
688
+ const results = msg.toolResults.map(serializeToolResult).join("\n");
689
+ return `Tool results:
690
+ ${results}`;
691
+ }
692
+ if (msg.role === "assistant") {
693
+ const parts = [];
694
+ const thinking = msg.thinking;
695
+ if (thinking) {
696
+ parts.push(`[reasoning: ${thinking}]`);
697
+ }
698
+ const text2 = msg.content ? getTextContent(msg.content) : "";
699
+ if (text2) parts.push(text2);
700
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
701
+ parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
702
+ }
703
+ return `Assistant: ${parts.join("\n")}`;
704
+ }
705
+ const text = msg.content ? getTextContent(msg.content) : "";
706
+ return `${msg.role}: ${text}`;
707
+ }).join("\n");
708
+ const lastPrompt = extractLastUserPrompt(messages);
709
+ return `Conversation history:
710
+ ${history}
711
+
712
+ User: ${lastPrompt}`;
713
+ }
714
+ var init_shared = __esm({
715
+ "src/backends/shared.ts"() {
716
+ init_types2();
717
+ }
718
+ });
719
+
452
720
  // src/backends/copilot.ts
453
721
  var copilot_exports = {};
454
722
  __export(copilot_exports, {
@@ -457,10 +725,9 @@ __export(copilot_exports, {
457
725
  createCopilotService: () => createCopilotService
458
726
  });
459
727
  async function loadSDK() {
460
- if (sdkModule) return sdkModule;
728
+ if (_sdkMock) return _sdkMock;
461
729
  try {
462
- sdkModule = await import('@github/copilot-sdk');
463
- return sdkModule;
730
+ return await import('@github/copilot-sdk');
464
731
  } catch {
465
732
  throw new SubprocessError(
466
733
  "@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
@@ -468,10 +735,10 @@ async function loadSDK() {
468
735
  }
469
736
  }
470
737
  function _injectSDK(mock) {
471
- sdkModule = mock;
738
+ _sdkMock = mock;
472
739
  }
473
740
  function _resetSDK() {
474
- sdkModule = null;
741
+ _sdkMock = null;
475
742
  }
476
743
  function mapToolsToSDK(tools) {
477
744
  return tools.map((tool) => ({
@@ -516,6 +783,7 @@ function buildPermissionHandler(config) {
516
783
  const unifiedRequest = {
517
784
  toolName,
518
785
  toolArgs: { ...request },
786
+ toolCallId: request.toolCallId,
519
787
  rawSDKRequest: request
520
788
  };
521
789
  const ac = new AbortController();
@@ -620,15 +888,21 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
620
888
  };
621
889
  case "session.error":
622
890
  console.error("[copilot] mapSessionEvent error:", JSON.stringify(data));
623
- return {
624
- type: "error",
625
- error: String(data.message ?? "Unknown error"),
626
- recoverable: false
627
- };
891
+ {
892
+ const errorMsg = String(data.message ?? "Unknown error");
893
+ const code = classifyAgentError(errorMsg);
894
+ return {
895
+ type: "error",
896
+ error: errorMsg,
897
+ recoverable: isRecoverableErrorCode(code),
898
+ code
899
+ };
900
+ }
628
901
  case "assistant.message": {
629
902
  const doneEvent = {
630
903
  type: "done",
631
- finalOutput: data.content ? String(data.content) : null
904
+ finalOutput: null,
905
+ streamed: true
632
906
  };
633
907
  if (thinkingTracker.endThinking()) {
634
908
  return [{ type: "thinking_end" }, doneEvent];
@@ -639,66 +913,13 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
639
913
  return null;
640
914
  }
641
915
  }
642
- function extractLastUserPrompt(messages) {
643
- for (let i = messages.length - 1; i >= 0; i--) {
644
- const msg = messages[i];
645
- if (msg.role === "user") {
646
- return getTextContent(msg.content);
647
- }
648
- }
649
- return "";
650
- }
651
- function serializeToolCall(tc) {
652
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
653
- return ` Tool call: ${tc.name}(${args})`;
654
- }
655
- function serializeToolResult(tr) {
656
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
657
- const prefix = tr.isError ? "[ERROR] " : "";
658
- return ` ${tr.name} \u2192 ${prefix}${result}`;
659
- }
660
- function buildContextualPrompt(messages) {
661
- if (messages.length <= 1) {
662
- return extractLastUserPrompt(messages);
663
- }
664
- const history = messages.slice(0, -1).map((msg) => {
665
- if (msg.role === "user") {
666
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
667
- }
668
- if (msg.role === "tool" && msg.toolResults) {
669
- const results = msg.toolResults.map(serializeToolResult).join("\n");
670
- return `Tool results:
671
- ${results}`;
672
- }
673
- if (msg.role === "assistant") {
674
- const parts = [];
675
- const thinking = msg.thinking;
676
- if (thinking) {
677
- parts.push(`[reasoning: ${thinking}]`);
678
- }
679
- const text2 = msg.content ? getTextContent(msg.content) : "";
680
- if (text2) parts.push(text2);
681
- if (msg.toolCalls && msg.toolCalls.length > 0) {
682
- parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
683
- }
684
- return `Assistant: ${parts.join("\n")}`;
685
- }
686
- const text = msg.content ? getTextContent(msg.content) : "";
687
- return `${msg.role}: ${text}`;
688
- }).join("\n");
689
- const lastPrompt = extractLastUserPrompt(messages);
690
- return `Conversation history:
691
- ${history}
692
-
693
- User: ${lastPrompt}`;
694
- }
695
916
  function withTimeout(promise, ms, message) {
696
- return new Promise((resolve2, reject) => {
917
+ return new Promise((resolve, reject) => {
697
918
  const timer = setTimeout(() => reject(new SubprocessError(message)), ms);
698
919
  promise.then(
699
920
  (val) => {
700
921
  clearTimeout(timer);
701
- resolve2(val);
922
+ resolve(val);
702
923
  },
703
924
  (err) => {
704
925
  clearTimeout(timer);
@@ -710,14 +931,15 @@ function withTimeout(promise, ms, message) {
710
931
  function createCopilotService(options) {
711
932
  return new CopilotAgentService(options);
712
933
  }
713
- var sdkModule, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
934
+ var _sdkMock, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
714
935
  var init_copilot = __esm({
715
936
  "src/backends/copilot.ts"() {
716
- init_types();
937
+ init_types2();
717
938
  init_base_agent();
718
- init_errors();
939
+ init_errors2();
719
940
  init_schema();
720
- sdkModule = null;
941
+ init_shared();
942
+ _sdkMock = null;
721
943
  ToolCallTracker = class {
722
944
  map = /* @__PURE__ */ new Map();
723
945
  trackStart(toolCallId, toolName, args) {
@@ -765,6 +987,7 @@ var init_copilot = __esm({
765
987
  isPersistent;
766
988
  persistentSession = null;
767
989
  _sessionId;
990
+ _persistentModel;
768
991
  activeSession = null;
769
992
  _resumeSessionId;
770
993
  _toolsReady = null;
@@ -815,47 +1038,63 @@ var init_copilot = __esm({
815
1038
  });
816
1039
  this.persistentSession = null;
817
1040
  this._sessionId = void 0;
1041
+ this._persistentModel = void 0;
818
1042
  }
819
1043
  }
820
- async getOrCreateSession(streaming) {
1044
+ async getOrCreateSession(streaming, options) {
821
1045
  if (this.isPersistent && this.persistentSession) {
822
- return { session: this.persistentSession, isNew: false };
1046
+ if (options.model !== this._persistentModel) {
1047
+ this.persistentSession.destroy().catch(() => {
1048
+ });
1049
+ this.persistentSession = null;
1050
+ this._sessionId = void 0;
1051
+ } else {
1052
+ return { session: this.persistentSession, isNew: false };
1053
+ }
823
1054
  }
824
1055
  if (this._toolsReady) {
825
1056
  await this._toolsReady;
826
1057
  this._toolsReady = null;
827
1058
  }
1059
+ const sessionConfig = { ...this.sessionConfig };
1060
+ sessionConfig.model = options.model;
1061
+ const resolvedTools = this.resolveTools(options);
1062
+ if (options?.tools) {
1063
+ sessionConfig.tools = mapToolsToSDK(resolvedTools);
1064
+ }
828
1065
  const client = await this.getClient();
829
1066
  if (this._resumeSessionId) {
830
1067
  const storedId = this._resumeSessionId;
831
1068
  this._resumeSessionId = void 0;
832
1069
  try {
833
1070
  const session2 = await client.resumeSession(storedId, {
834
- ...this.sessionConfig,
1071
+ ...sessionConfig,
835
1072
  streaming: this.isPersistent ? true : streaming
836
1073
  });
837
1074
  if (this.isPersistent) {
838
1075
  this.persistentSession = session2;
839
1076
  this._sessionId = session2.sessionId;
1077
+ this._persistentModel = options.model;
840
1078
  }
841
1079
  return { session: session2, isNew: false };
842
1080
  } catch {
843
1081
  }
844
1082
  }
845
1083
  const session = await client.createSession({
846
- ...this.sessionConfig,
1084
+ ...sessionConfig,
847
1085
  streaming: this.isPersistent ? true : streaming
848
1086
  });
849
1087
  if (this.isPersistent) {
850
1088
  this.persistentSession = session;
851
1089
  this._sessionId = session.sessionId;
1090
+ this._persistentModel = options.model;
852
1091
  }
853
1092
  return { session, isNew: true };
854
1093
  }
855
1094
  // ─── executeRun ─────────────────────────────────────────────────
856
- async executeRun(messages, _options, signal) {
1095
+ async executeRun(messages, options, signal) {
857
1096
  this.checkAbort(signal);
858
- const { session, isNew: isNewSession } = await this.getOrCreateSession(false);
1097
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(false, options);
859
1098
  this.activeSession = session;
860
1099
  const prompt = this.isPersistent && !isNewSession ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
861
1100
  const tracker = new ToolCallTracker();
@@ -952,9 +1191,9 @@ You MUST respond with ONLY valid JSON matching this schema:
952
1191
  };
953
1192
  }
954
1193
  // ─── executeStream ──────────────────────────────────────────────
955
- async *executeStream(messages, _options, signal) {
1194
+ async *executeStream(messages, options, signal) {
956
1195
  this.checkAbort(signal);
957
- const { session, isNew: isNewSession } = await this.getOrCreateSession(true);
1196
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(true, options);
958
1197
  this.activeSession = session;
959
1198
  if (isNewSession) {
960
1199
  yield this.emitSessionInfo(session.sessionId);
@@ -971,8 +1210,8 @@ You MUST respond with ONLY valid JSON matching this schema:
971
1210
  notify = null;
972
1211
  }
973
1212
  };
974
- const waitForItem = () => new Promise((resolve2) => {
975
- notify = resolve2;
1213
+ const waitForItem = () => new Promise((resolve) => {
1214
+ notify = resolve;
976
1215
  });
977
1216
  const unsubscribe = session.on((event) => {
978
1217
  const mapped = mapSessionEvent(event, tracker, thinkingTracker);
@@ -1100,7 +1339,10 @@ You MUST respond with ONLY valid JSON matching this schema:
1100
1339
  return models.map((m) => ({
1101
1340
  id: m.id,
1102
1341
  name: m.name,
1103
- provider: "copilot"
1342
+ provider: "copilot",
1343
+ ...m.capabilities?.limits?.max_context_window_tokens != null && {
1344
+ contextWindow: m.capabilities.limits.max_context_window_tokens
1345
+ }
1104
1346
  }));
1105
1347
  }
1106
1348
  async validate() {
@@ -1153,10 +1395,9 @@ function stripMcpPrefix(name) {
1153
1395
  return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
1154
1396
  }
1155
1397
  async function loadSDK2() {
1156
- if (sdkModule2) return sdkModule2;
1398
+ if (_sdkMock2) return _sdkMock2;
1157
1399
  try {
1158
- sdkModule2 = await import('@anthropic-ai/claude-agent-sdk');
1159
- return sdkModule2;
1400
+ return await import('@anthropic-ai/claude-agent-sdk');
1160
1401
  } catch {
1161
1402
  throw new SubprocessError(
1162
1403
  "@anthropic-ai/claude-agent-sdk is not installed. Install it: npm install @anthropic-ai/claude-agent-sdk"
@@ -1164,13 +1405,32 @@ async function loadSDK2() {
1164
1405
  }
1165
1406
  }
1166
1407
  function _injectSDK2(mock) {
1167
- sdkModule2 = mock;
1408
+ _sdkMock2 = mock;
1168
1409
  }
1169
1410
  function _resetSDK2() {
1170
- sdkModule2 = null;
1411
+ _sdkMock2 = null;
1412
+ }
1413
+ function normalizeAskUserInput(args) {
1414
+ if (typeof args.question === "string") {
1415
+ return {
1416
+ question: args.question,
1417
+ choices: Array.isArray(args.choices) ? args.choices : void 0,
1418
+ allowFreeform: args.allowFreeform !== false
1419
+ };
1420
+ }
1421
+ const questions = args.questions;
1422
+ if (questions && questions.length > 0) {
1423
+ const first = questions[0];
1424
+ return {
1425
+ question: first.question,
1426
+ choices: first.options?.map((o) => o.label),
1427
+ allowFreeform: true
1428
+ };
1429
+ }
1430
+ return { question: JSON.stringify(args), allowFreeform: true };
1171
1431
  }
1172
- function buildMcpServer(sdk, tools, toolResultCapture) {
1173
- if (tools.length === 0) return void 0;
1432
+ function buildMcpServer(sdk, tools, toolResultCapture, onAskUser) {
1433
+ if (tools.length === 0 && !onAskUser) return void 0;
1174
1434
  const mcpTools = tools.map((tool) => {
1175
1435
  const zodSchema = tool.parameters;
1176
1436
  const inputSchema = zodSchema.shape ?? zodToJsonSchema(tool.parameters);
@@ -1194,6 +1454,39 @@ function buildMcpServer(sdk, tools, toolResultCapture) {
1194
1454
  }
1195
1455
  );
1196
1456
  });
1457
+ if (onAskUser) {
1458
+ const askUserTool = sdk.tool(
1459
+ "ask_user",
1460
+ "Ask the user a question and wait for their response",
1461
+ {
1462
+ question: { type: "string", description: "The question to ask the user" },
1463
+ choices: {
1464
+ type: "array",
1465
+ items: { type: "string" },
1466
+ description: "Optional list of choices for multiple choice"
1467
+ },
1468
+ questions: {
1469
+ type: "array",
1470
+ items: {
1471
+ type: "object",
1472
+ properties: {
1473
+ question: { type: "string" },
1474
+ options: { type: "array", items: { type: "object", properties: { label: { type: "string" } } } }
1475
+ }
1476
+ },
1477
+ description: "Alternative nested question format"
1478
+ }
1479
+ },
1480
+ async (args) => {
1481
+ const normalized = normalizeAskUserInput(args);
1482
+ const response = await onAskUser(normalized, AbortSignal.timeout(3e5));
1483
+ return {
1484
+ content: [{ type: "text", text: response.answer }]
1485
+ };
1486
+ }
1487
+ );
1488
+ mcpTools.push(askUserTool);
1489
+ }
1197
1490
  return sdk.createSdkMcpServer({
1198
1491
  name: MCP_SERVER_NAME,
1199
1492
  version: "1.0.0",
@@ -1243,6 +1536,7 @@ function buildCanUseTool(config) {
1243
1536
  const unifiedRequest = {
1244
1537
  toolName,
1245
1538
  toolArgs: input,
1539
+ toolCallId: options.toolUseID,
1246
1540
  suggestedScope: extractSuggestedScope(options.suggestions),
1247
1541
  rawSDKRequest: { toolName, input, ...options }
1248
1542
  };
@@ -1295,6 +1589,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1295
1589
  if (block.type === "tool_use") {
1296
1590
  const toolCallId = String(block.id ?? "");
1297
1591
  const toolName = stripMcpPrefix(block.name ?? "unknown");
1592
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1298
1593
  if (toolCallTracker) {
1299
1594
  toolCallTracker.trackStart(toolCallId, toolName);
1300
1595
  }
@@ -1318,6 +1613,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1318
1613
  case "tool_use_summary": {
1319
1614
  const summary = msg.summary;
1320
1615
  const toolName = stripMcpPrefix(msg.tool_name ?? "unknown");
1616
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) return null;
1321
1617
  const precedingIds = msg.preceding_tool_use_ids;
1322
1618
  let toolCallId = "";
1323
1619
  if (precedingIds && precedingIds.length > 0) {
@@ -1371,10 +1667,13 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1371
1667
  }
1372
1668
  if (msg.is_error) {
1373
1669
  const r = msg;
1670
+ const errorMsg = r.errors?.join("; ") ?? "Unknown error";
1671
+ const code = classifyAgentError(errorMsg);
1374
1672
  return {
1375
1673
  type: "error",
1376
- error: r.errors?.join("; ") ?? "Unknown error",
1377
- recoverable: false
1674
+ error: errorMsg,
1675
+ recoverable: isRecoverableErrorCode(code),
1676
+ code
1378
1677
  };
1379
1678
  }
1380
1679
  return null;
@@ -1383,72 +1682,21 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1383
1682
  return null;
1384
1683
  }
1385
1684
  }
1386
- function extractLastUserPrompt2(messages) {
1387
- for (let i = messages.length - 1; i >= 0; i--) {
1388
- const msg = messages[i];
1389
- if (msg.role === "user") {
1390
- return getTextContent(msg.content);
1391
- }
1392
- }
1393
- return "";
1394
- }
1395
- function serializeToolCall2(tc) {
1396
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
1397
- return ` Tool call: ${tc.name}(${args})`;
1398
- }
1399
- function serializeToolResult2(tr) {
1400
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
1401
- const prefix = tr.isError ? "[ERROR] " : "";
1402
- return ` ${tr.name} \u2192 ${prefix}${result}`;
1403
- }
1404
- function buildContextualPrompt2(messages) {
1405
- if (messages.length <= 1) {
1406
- return extractLastUserPrompt2(messages);
1407
- }
1408
- const history = messages.slice(0, -1).map((msg) => {
1409
- if (msg.role === "user") {
1410
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
1411
- }
1412
- if (msg.role === "tool" && msg.toolResults) {
1413
- const results = msg.toolResults.map(serializeToolResult2).join("\n");
1414
- return `Tool results:
1415
- ${results}`;
1416
- }
1417
- if (msg.role === "assistant") {
1418
- const parts = [];
1419
- const thinking = msg.thinking;
1420
- if (thinking) {
1421
- parts.push(`[reasoning: ${thinking}]`);
1422
- }
1423
- const text2 = msg.content ? getTextContent(msg.content) : "";
1424
- if (text2) parts.push(text2);
1425
- if (msg.toolCalls && msg.toolCalls.length > 0) {
1426
- parts.push(msg.toolCalls.map(serializeToolCall2).join("\n"));
1427
- }
1428
- return `Assistant: ${parts.join("\n")}`;
1429
- }
1430
- const text = msg.content ? getTextContent(msg.content) : "";
1431
- return `${msg.role}: ${text}`;
1432
- }).join("\n");
1433
- const lastPrompt = extractLastUserPrompt2(messages);
1434
- return `Conversation history:
1435
- ${history}
1436
-
1437
- User: ${lastPrompt}`;
1438
- }
1439
1685
  function createClaudeService(options) {
1440
1686
  return new ClaudeAgentService(options);
1441
1687
  }
1442
- var MCP_SERVER_NAME, MCP_TOOL_PREFIX, sdkModule2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1688
+ var MCP_SERVER_NAME, MCP_TOOL_PREFIX, CLAUDE_INTERNAL_TOOL_NAMES, _sdkMock2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1443
1689
  var init_claude = __esm({
1444
1690
  "src/backends/claude.ts"() {
1445
- init_types();
1691
+ init_types2();
1446
1692
  init_base_agent();
1447
- init_errors();
1693
+ init_errors2();
1448
1694
  init_schema();
1695
+ init_shared();
1449
1696
  MCP_SERVER_NAME = "agent-sdk-tools";
1450
1697
  MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
1451
- sdkModule2 = null;
1698
+ CLAUDE_INTERNAL_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion"]);
1699
+ _sdkMock2 = null;
1452
1700
  ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
1453
1701
  ANTHROPIC_API_VERSION = "2023-06-01";
1454
1702
  ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
@@ -1493,11 +1741,6 @@ var init_claude = __esm({
1493
1741
  if (options.resumeSessionId) {
1494
1742
  this._sessionId = options.resumeSessionId;
1495
1743
  }
1496
- if (config.supervisor?.onAskUser) {
1497
- console.warn(
1498
- "[agent-sdk/claude] supervisor.onAskUser is not supported by the Claude CLI backend. User interaction requests from the model will not be forwarded."
1499
- );
1500
- }
1501
1744
  }
1502
1745
  get sessionId() {
1503
1746
  return this._sessionId;
@@ -1521,12 +1764,12 @@ var init_claude = __esm({
1521
1764
  const transcriptPath = home ? `${home}/.claude/projects/.session/sessions/${sessionId}/conversation.jsonl` : void 0;
1522
1765
  return { type: "session_info", sessionId, transcriptPath, backend: "claude" };
1523
1766
  }
1524
- buildQueryOptions(signal) {
1767
+ buildQueryOptions(signal, options) {
1525
1768
  const ac = new AbortController();
1526
1769
  signal.addEventListener("abort", () => ac.abort(), { once: true });
1527
1770
  const opts = {
1528
1771
  abortController: ac,
1529
- model: this.config.model,
1772
+ model: options.model,
1530
1773
  maxTurns: this.options.maxTurns,
1531
1774
  cwd: this.options.workingDirectory,
1532
1775
  pathToClaudeCodeExecutable: this.options.cliPath,
@@ -1556,25 +1799,85 @@ var init_claude = __esm({
1556
1799
  return opts;
1557
1800
  }
1558
1801
  async buildMcpConfig(opts, toolResultCapture) {
1559
- if (this.tools.length === 0) return opts;
1802
+ const onAskUser = this.config.supervisor?.onAskUser;
1803
+ if (this.tools.length === 0 && !onAskUser) return opts;
1560
1804
  const sdk = await loadSDK2();
1561
- const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture);
1805
+ const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture, onAskUser);
1562
1806
  if (mcpServer) {
1563
1807
  opts.mcpServers = {
1564
1808
  [MCP_SERVER_NAME]: mcpServer
1565
1809
  };
1566
1810
  const mcpToolNames = this.tools.map((t) => mcpToolName(t.name));
1811
+ if (onAskUser) {
1812
+ mcpToolNames.push(mcpToolName("ask_user"));
1813
+ }
1567
1814
  opts.allowedTools = [...opts.allowedTools ?? [], ...mcpToolNames];
1568
1815
  }
1816
+ if (onAskUser) {
1817
+ opts.disallowedTools = [...opts.disallowedTools ?? [], "AskUserQuestion"];
1818
+ }
1569
1819
  return opts;
1570
1820
  }
1821
+ // ─── Retry Helpers (shared across executeRun/RunStructured/Stream) ──
1822
+ /** Setup a retry query: clear session, rebuild with full history */
1823
+ async prepareRetryQuery(sdk, messages, signal, options, toolResultCapture, modifyOpts) {
1824
+ this.clearPersistentSession();
1825
+ const retryPrompt = buildContextualPrompt(messages);
1826
+ let retryOpts = this.buildQueryOptions(signal, options);
1827
+ toolResultCapture.clear();
1828
+ retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1829
+ modifyOpts?.(retryOpts);
1830
+ const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1831
+ this.activeQuery = retryQ;
1832
+ return retryQ;
1833
+ }
1834
+ /** Extract tool_use blocks from an assistant SDK message into toolCalls array */
1835
+ collectToolCallsFromMessage(msg, toolCalls, toolResultCapture) {
1836
+ if (msg.type !== "assistant") return;
1837
+ const betaMessage = msg.message;
1838
+ if (!betaMessage?.content) return;
1839
+ for (const block of betaMessage.content) {
1840
+ if (block.type === "tool_use") {
1841
+ const toolName = stripMcpPrefix(block.name ?? "unknown");
1842
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1843
+ toolCalls.push({
1844
+ toolName,
1845
+ args: block.input ?? {},
1846
+ result: toolResultCapture.get(toolName) ?? null,
1847
+ approved: true
1848
+ });
1849
+ }
1850
+ }
1851
+ }
1852
+ /** Back-fill tool results from capture map on summary/result messages */
1853
+ backfillToolResults(msg, toolCalls, toolResultCapture) {
1854
+ if (msg.type !== "tool_use_summary" && msg.type !== "result") return;
1855
+ for (const tc of toolCalls) {
1856
+ if (tc.result === null) {
1857
+ const captured = toolResultCapture.get(tc.toolName);
1858
+ if (captured !== void 0) tc.result = captured;
1859
+ }
1860
+ }
1861
+ }
1862
+ /** Wrap retry inner loop with shared error handling */
1863
+ async withRetryErrorHandling(signal, fn) {
1864
+ try {
1865
+ return await fn();
1866
+ } catch (retryError) {
1867
+ if (this.isPersistent) this.clearPersistentSession();
1868
+ if (signal.aborted) throw new AbortError();
1869
+ throw retryError;
1870
+ } finally {
1871
+ this.activeQuery = null;
1872
+ }
1873
+ }
1571
1874
  // ─── executeRun ─────────────────────────────────────────────────
1572
- async executeRun(messages, _options, signal) {
1875
+ async executeRun(messages, options, signal) {
1573
1876
  this.checkAbort(signal);
1574
1877
  const sdk = await loadSDK2();
1575
1878
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1576
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1577
- let opts = this.buildQueryOptions(signal);
1879
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1880
+ let opts = this.buildQueryOptions(signal, options);
1578
1881
  const toolResultCapture = /* @__PURE__ */ new Map();
1579
1882
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1580
1883
  const q = sdk.query({ prompt, options: opts });
@@ -1584,30 +1887,8 @@ var init_claude = __esm({
1584
1887
  let usage;
1585
1888
  try {
1586
1889
  for await (const msg of q) {
1587
- if (msg.type === "assistant") {
1588
- const betaMessage = msg.message;
1589
- if (betaMessage?.content) {
1590
- for (const block of betaMessage.content) {
1591
- if (block.type === "tool_use") {
1592
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1593
- toolCalls.push({
1594
- toolName,
1595
- args: block.input ?? {},
1596
- result: toolResultCapture.get(toolName) ?? null,
1597
- approved: true
1598
- });
1599
- }
1600
- }
1601
- }
1602
- }
1603
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1604
- for (const tc of toolCalls) {
1605
- if (tc.result === null) {
1606
- const captured = toolResultCapture.get(tc.toolName);
1607
- if (captured !== void 0) tc.result = captured;
1608
- }
1609
- }
1610
- }
1890
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1891
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1611
1892
  if (msg.type === "result") {
1612
1893
  if (msg.subtype === "success") {
1613
1894
  const r = msg;
@@ -1627,41 +1908,13 @@ var init_claude = __esm({
1627
1908
  } catch (e) {
1628
1909
  if (signal.aborted) throw new AbortError();
1629
1910
  if (isResuming && this.isPersistent) {
1630
- this.clearPersistentSession();
1631
- const retryPrompt = buildContextualPrompt2(messages);
1632
- let retryOpts = this.buildQueryOptions(signal);
1633
- toolResultCapture.clear();
1634
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1635
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1636
- this.activeQuery = retryQ;
1911
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1637
1912
  toolCalls.length = 0;
1638
1913
  output = null;
1639
- try {
1914
+ return this.withRetryErrorHandling(signal, async () => {
1640
1915
  for await (const msg of retryQ) {
1641
- if (msg.type === "assistant") {
1642
- const betaMessage = msg.message;
1643
- if (betaMessage?.content) {
1644
- for (const block of betaMessage.content) {
1645
- if (block.type === "tool_use") {
1646
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1647
- toolCalls.push({
1648
- toolName,
1649
- args: block.input ?? {},
1650
- result: toolResultCapture.get(toolName) ?? null,
1651
- approved: true
1652
- });
1653
- }
1654
- }
1655
- }
1656
- }
1657
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1658
- for (const tc of toolCalls) {
1659
- if (tc.result === null) {
1660
- const captured = toolResultCapture.get(tc.toolName);
1661
- if (captured !== void 0) tc.result = captured;
1662
- }
1663
- }
1664
- }
1916
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1917
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1665
1918
  if (msg.type === "result") {
1666
1919
  if (msg.subtype === "success") {
1667
1920
  const r = msg;
@@ -1678,23 +1931,17 @@ var init_claude = __esm({
1678
1931
  }
1679
1932
  }
1680
1933
  }
1681
- } catch (retryError) {
1682
- if (this.isPersistent) this.clearPersistentSession();
1683
- if (signal.aborted) throw new AbortError();
1684
- throw retryError;
1685
- } finally {
1686
- this.activeQuery = null;
1687
- }
1688
- return {
1689
- output,
1690
- structuredOutput: void 0,
1691
- toolCalls,
1692
- messages: [
1693
- ...messages,
1694
- ...output !== null ? [{ role: "assistant", content: output }] : []
1695
- ],
1696
- usage
1697
- };
1934
+ return {
1935
+ output,
1936
+ structuredOutput: void 0,
1937
+ toolCalls,
1938
+ messages: [
1939
+ ...messages,
1940
+ ...output !== null ? [{ role: "assistant", content: output }] : []
1941
+ ],
1942
+ usage
1943
+ };
1944
+ });
1698
1945
  }
1699
1946
  if (this.isPersistent) this.clearPersistentSession();
1700
1947
  throw e;
@@ -1713,12 +1960,12 @@ var init_claude = __esm({
1713
1960
  };
1714
1961
  }
1715
1962
  // ─── executeRunStructured ───────────────────────────────────────
1716
- async executeRunStructured(messages, schema, _options, signal) {
1963
+ async executeRunStructured(messages, schema, options, signal) {
1717
1964
  this.checkAbort(signal);
1718
1965
  const sdk = await loadSDK2();
1719
1966
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1720
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1721
- let opts = this.buildQueryOptions(signal);
1967
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1968
+ let opts = this.buildQueryOptions(signal, options);
1722
1969
  const toolResultCapture = /* @__PURE__ */ new Map();
1723
1970
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1724
1971
  const jsonSchema = zodToJsonSchema(schema.schema);
@@ -1734,30 +1981,8 @@ var init_claude = __esm({
1734
1981
  let usage;
1735
1982
  try {
1736
1983
  for await (const msg of q) {
1737
- if (msg.type === "assistant") {
1738
- const betaMessage = msg.message;
1739
- if (betaMessage?.content) {
1740
- for (const block of betaMessage.content) {
1741
- if (block.type === "tool_use") {
1742
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1743
- toolCalls.push({
1744
- toolName,
1745
- args: block.input ?? {},
1746
- result: toolResultCapture.get(toolName) ?? null,
1747
- approved: true
1748
- });
1749
- }
1750
- }
1751
- }
1752
- }
1753
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1754
- for (const tc of toolCalls) {
1755
- if (tc.result === null) {
1756
- const captured = toolResultCapture.get(tc.toolName);
1757
- if (captured !== void 0) tc.result = captured;
1758
- }
1759
- }
1760
- }
1984
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1985
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1761
1986
  if (msg.type === "result" && msg.subtype === "success") {
1762
1987
  const r = msg;
1763
1988
  output = r.result;
@@ -1792,46 +2017,23 @@ var init_claude = __esm({
1792
2017
  } catch (e) {
1793
2018
  if (signal.aborted) throw new AbortError();
1794
2019
  if (isResuming && this.isPersistent) {
1795
- this.clearPersistentSession();
1796
- const retryPrompt = buildContextualPrompt2(messages);
1797
- let retryOpts = this.buildQueryOptions(signal);
1798
- toolResultCapture.clear();
1799
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1800
- retryOpts.outputFormat = {
1801
- type: "json_schema",
1802
- schema: jsonSchema
1803
- };
1804
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1805
- this.activeQuery = retryQ;
2020
+ const retryQ = await this.prepareRetryQuery(
2021
+ sdk,
2022
+ messages,
2023
+ signal,
2024
+ options,
2025
+ toolResultCapture,
2026
+ (opts2) => {
2027
+ opts2.outputFormat = { type: "json_schema", schema: jsonSchema };
2028
+ }
2029
+ );
1806
2030
  toolCalls.length = 0;
1807
2031
  output = null;
1808
2032
  structuredOutput = void 0;
1809
- try {
2033
+ return this.withRetryErrorHandling(signal, async () => {
1810
2034
  for await (const msg of retryQ) {
1811
- if (msg.type === "assistant") {
1812
- const betaMessage = msg.message;
1813
- if (betaMessage?.content) {
1814
- for (const block of betaMessage.content) {
1815
- if (block.type === "tool_use") {
1816
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1817
- toolCalls.push({
1818
- toolName,
1819
- args: block.input ?? {},
1820
- result: toolResultCapture.get(toolName) ?? null,
1821
- approved: true
1822
- });
1823
- }
1824
- }
1825
- }
1826
- }
1827
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1828
- for (const tc of toolCalls) {
1829
- if (tc.result === null) {
1830
- const captured = toolResultCapture.get(tc.toolName);
1831
- if (captured !== void 0) tc.result = captured;
1832
- }
1833
- }
1834
- }
2035
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
2036
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1835
2037
  if (msg.type === "result" && msg.subtype === "success") {
1836
2038
  const r = msg;
1837
2039
  output = r.result;
@@ -1863,23 +2065,17 @@ var init_claude = __esm({
1863
2065
  );
1864
2066
  }
1865
2067
  }
1866
- } catch (retryError) {
1867
- if (this.isPersistent) this.clearPersistentSession();
1868
- if (signal.aborted) throw new AbortError();
1869
- throw retryError;
1870
- } finally {
1871
- this.activeQuery = null;
1872
- }
1873
- return {
1874
- output,
1875
- structuredOutput,
1876
- toolCalls,
1877
- messages: [
1878
- ...messages,
1879
- ...output !== null ? [{ role: "assistant", content: output }] : []
1880
- ],
1881
- usage
1882
- };
2068
+ return {
2069
+ output,
2070
+ structuredOutput,
2071
+ toolCalls,
2072
+ messages: [
2073
+ ...messages,
2074
+ ...output !== null ? [{ role: "assistant", content: output }] : []
2075
+ ],
2076
+ usage
2077
+ };
2078
+ });
1883
2079
  }
1884
2080
  if (this.isPersistent) this.clearPersistentSession();
1885
2081
  throw e;
@@ -1898,12 +2094,12 @@ var init_claude = __esm({
1898
2094
  };
1899
2095
  }
1900
2096
  // ─── executeStream ──────────────────────────────────────────────
1901
- async *executeStream(messages, _options, signal) {
2097
+ async *executeStream(messages, options, signal) {
1902
2098
  this.checkAbort(signal);
1903
2099
  const sdk = await loadSDK2();
1904
2100
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1905
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1906
- let opts = this.buildQueryOptions(signal);
2101
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
2102
+ let opts = this.buildQueryOptions(signal, options);
1907
2103
  const toolResultCapture = /* @__PURE__ */ new Map();
1908
2104
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1909
2105
  const q = sdk.query({ prompt, options: opts });
@@ -1911,6 +2107,7 @@ var init_claude = __esm({
1911
2107
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
1912
2108
  const toolCallTracker = new ClaudeToolCallTracker();
1913
2109
  const pendingStreamToolCalls = /* @__PURE__ */ new Map();
2110
+ let hasStreamedText = false;
1914
2111
  try {
1915
2112
  for await (const msg of q) {
1916
2113
  if (signal.aborted) throw new AbortError();
@@ -1928,6 +2125,7 @@ var init_claude = __esm({
1928
2125
  } else if (e.type === "tool_call_end") {
1929
2126
  pendingStreamToolCalls.delete(e.toolCallId);
1930
2127
  }
2128
+ if (e.type === "text_delta") hasStreamedText = true;
1931
2129
  yield e;
1932
2130
  }
1933
2131
  }
@@ -1951,22 +2149,21 @@ var init_claude = __esm({
1951
2149
  }
1952
2150
  yield this.emitSessionInfo(r.session_id);
1953
2151
  }
1954
- yield { type: "done", finalOutput: r.result };
2152
+ yield {
2153
+ type: "done",
2154
+ finalOutput: hasStreamedText ? null : r.result,
2155
+ ...hasStreamedText ? { streamed: true } : {}
2156
+ };
1955
2157
  }
1956
2158
  }
1957
2159
  } catch (e) {
1958
2160
  if (signal.aborted) throw new AbortError();
1959
2161
  if (isResuming && this.isPersistent) {
1960
- this.clearPersistentSession();
1961
- const retryPrompt = buildContextualPrompt2(messages);
1962
- let retryOpts = this.buildQueryOptions(signal);
1963
- toolResultCapture.clear();
1964
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1965
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1966
- this.activeQuery = retryQ;
2162
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1967
2163
  const retryThinkingBlockIndices = /* @__PURE__ */ new Set();
1968
2164
  const retryToolCallTracker = new ClaudeToolCallTracker();
1969
2165
  const retryPendingToolCalls = /* @__PURE__ */ new Map();
2166
+ let retryHasStreamedText = false;
1970
2167
  try {
1971
2168
  for await (const msg of retryQ) {
1972
2169
  if (signal.aborted) throw new AbortError();
@@ -1984,6 +2181,7 @@ var init_claude = __esm({
1984
2181
  } else if (ev.type === "tool_call_end") {
1985
2182
  retryPendingToolCalls.delete(ev.toolCallId);
1986
2183
  }
2184
+ if (ev.type === "text_delta") retryHasStreamedText = true;
1987
2185
  yield ev;
1988
2186
  }
1989
2187
  }
@@ -2007,7 +2205,11 @@ var init_claude = __esm({
2007
2205
  }
2008
2206
  yield this.emitSessionInfo(r.session_id);
2009
2207
  }
2010
- yield { type: "done", finalOutput: r.result };
2208
+ yield {
2209
+ type: "done",
2210
+ finalOutput: retryHasStreamedText ? null : r.result,
2211
+ ...retryHasStreamedText ? { streamed: true } : {}
2212
+ };
2011
2213
  }
2012
2214
  }
2013
2215
  } catch (retryError) {
@@ -2069,7 +2271,8 @@ var init_claude = __esm({
2069
2271
  this.cachedModels = body.data.map((m) => ({
2070
2272
  id: m.id,
2071
2273
  name: m.display_name,
2072
- provider: "claude"
2274
+ provider: "claude",
2275
+ ...m.max_input_tokens != null && { contextWindow: m.max_input_tokens }
2073
2276
  }));
2074
2277
  return this.cachedModels;
2075
2278
  }
@@ -2127,32 +2330,30 @@ __export(vercel_ai_exports, {
2127
2330
  createVercelAIService: () => createVercelAIService
2128
2331
  });
2129
2332
  async function loadSDK3() {
2130
- if (sdkModule3) return sdkModule3;
2333
+ if (_sdkMock3) return _sdkMock3;
2131
2334
  try {
2132
- sdkModule3 = await import('ai');
2133
- return sdkModule3;
2335
+ return await import('ai');
2134
2336
  } catch {
2135
2337
  throw new DependencyError("ai");
2136
2338
  }
2137
2339
  }
2138
2340
  async function loadCompat() {
2139
- if (compatModule) return compatModule;
2341
+ if (_compatMock) return _compatMock;
2140
2342
  try {
2141
- compatModule = await import('@ai-sdk/openai-compatible');
2142
- return compatModule;
2343
+ return await import('@ai-sdk/openai-compatible');
2143
2344
  } catch {
2144
2345
  throw new DependencyError("@ai-sdk/openai-compatible");
2145
2346
  }
2146
2347
  }
2147
2348
  function _injectSDK3(mock) {
2148
- sdkModule3 = mock;
2349
+ _sdkMock3 = mock;
2149
2350
  }
2150
2351
  function _injectCompat(mock) {
2151
- compatModule = mock;
2352
+ _compatMock = mock;
2152
2353
  }
2153
2354
  function _resetSDK3() {
2154
- sdkModule3 = null;
2155
- compatModule = null;
2355
+ _sdkMock3 = null;
2356
+ _compatMock = null;
2156
2357
  }
2157
2358
  function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, signal) {
2158
2359
  const toolMap = {};
@@ -2195,13 +2396,14 @@ function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, s
2195
2396
  return toolMap;
2196
2397
  }
2197
2398
  function wrapToolExecute(ourTool, supervisor, sessionApprovals, permissionStore, signal) {
2198
- return async (args) => {
2399
+ return async (args, options) => {
2199
2400
  if (ourTool.needsApproval && supervisor?.onPermission) {
2200
2401
  const storeApproved = permissionStore && await permissionStore.isApproved(ourTool.name);
2201
2402
  if (!storeApproved && !sessionApprovals.has(ourTool.name)) {
2202
2403
  const request = {
2203
2404
  toolName: ourTool.name,
2204
- toolArgs: args ?? {}
2405
+ toolArgs: args ?? {},
2406
+ toolCallId: options?.toolCallId
2205
2407
  };
2206
2408
  const decision = await supervisor.onPermission(
2207
2409
  request,
@@ -2308,7 +2510,8 @@ function mapStreamPart(part) {
2308
2510
  return {
2309
2511
  type: "error",
2310
2512
  error: p.error instanceof Error ? p.error.message : String(p.error ?? "Tool execution failed"),
2311
- recoverable: true
2513
+ recoverable: true,
2514
+ code: "TOOL_EXECUTION" /* TOOL_EXECUTION */
2312
2515
  };
2313
2516
  }
2314
2517
  case "reasoning-start":
@@ -2329,10 +2532,13 @@ function mapStreamPart(part) {
2329
2532
  }
2330
2533
  case "error": {
2331
2534
  const p = part;
2535
+ const errorMsg = p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error");
2536
+ const code = classifyAgentError(errorMsg);
2332
2537
  return {
2333
2538
  type: "error",
2334
- error: p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error"),
2335
- recoverable: false
2539
+ error: errorMsg,
2540
+ recoverable: isRecoverableErrorCode(code),
2541
+ code
2336
2542
  };
2337
2543
  }
2338
2544
  default:
@@ -2342,15 +2548,15 @@ function mapStreamPart(part) {
2342
2548
  function createVercelAIService(options) {
2343
2549
  return new VercelAIAgentService(options);
2344
2550
  }
2345
- var sdkModule3, compatModule, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2551
+ var _sdkMock3, _compatMock, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2346
2552
  var init_vercel_ai = __esm({
2347
2553
  "src/backends/vercel-ai.ts"() {
2348
- init_types();
2554
+ init_types2();
2349
2555
  init_base_agent();
2350
- init_errors();
2556
+ init_errors2();
2351
2557
  init_schema();
2352
- sdkModule3 = null;
2353
- compatModule = null;
2558
+ _sdkMock3 = null;
2559
+ _compatMock = null;
2354
2560
  DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
2355
2561
  DEFAULT_PROVIDER = "openrouter";
2356
2562
  DEFAULT_MAX_TURNS = 10;
@@ -2363,28 +2569,33 @@ var init_vercel_ai = __esm({
2363
2569
  super(config);
2364
2570
  this.backendOptions = backendOptions;
2365
2571
  }
2366
- async getModel() {
2367
- if (this.model) return this.model;
2572
+ async getModel(options) {
2573
+ const requestedModel = options.model;
2574
+ const defaultModel = this.config.model;
2575
+ if (requestedModel === defaultModel && this.model) return this.model;
2368
2576
  const compat = await loadCompat();
2369
2577
  const provider = compat.createOpenAICompatible({
2370
2578
  name: this.backendOptions.provider ?? DEFAULT_PROVIDER,
2371
2579
  baseURL: this.backendOptions.baseUrl ?? DEFAULT_BASE_URL,
2372
2580
  apiKey: this.backendOptions.apiKey
2373
2581
  });
2374
- const modelId = this.config.model ?? "anthropic/claude-sonnet-4-5";
2375
- this.model = provider.chatModel(modelId);
2376
- return this.model;
2582
+ const model = provider.chatModel(requestedModel);
2583
+ if (requestedModel === defaultModel) {
2584
+ this.model = model;
2585
+ }
2586
+ return model;
2377
2587
  }
2378
- async getSDKTools(signal) {
2588
+ async getSDKTools(signal, options) {
2379
2589
  const sdk = await loadSDK3();
2380
- return mapToolsToSDK2(sdk, this.config.tools ?? [], this.config, this.sessionApprovals, this.config.permissionStore, signal);
2590
+ const tools = this.resolveTools(options);
2591
+ return mapToolsToSDK2(sdk, tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
2381
2592
  }
2382
2593
  // ─── executeRun ─────────────────────────────────────────────────
2383
- async executeRun(messages, _options, signal) {
2594
+ async executeRun(messages, options, signal) {
2384
2595
  this.checkAbort(signal);
2385
2596
  const sdk = await loadSDK3();
2386
- const model = await this.getModel();
2387
- const tools = await this.getSDKTools(signal);
2597
+ const model = await this.getModel(options);
2598
+ const tools = await this.getSDKTools(signal, options);
2388
2599
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2389
2600
  const sdkMessages = messagesToSDK(messages);
2390
2601
  const hasTools = Object.keys(tools).length > 0;
@@ -2440,10 +2651,10 @@ var init_vercel_ai = __esm({
2440
2651
  };
2441
2652
  }
2442
2653
  // ─── executeRunStructured ───────────────────────────────────────
2443
- async executeRunStructured(messages, schema, _options, signal) {
2654
+ async executeRunStructured(messages, schema, options, signal) {
2444
2655
  this.checkAbort(signal);
2445
2656
  const sdk = await loadSDK3();
2446
- const model = await this.getModel();
2657
+ const model = await this.getModel(options);
2447
2658
  const sdkMessages = messagesToSDK(messages);
2448
2659
  const jsonSchema = zodToJsonSchema(schema.schema);
2449
2660
  const result = await sdk.generateObject({
@@ -2485,11 +2696,11 @@ var init_vercel_ai = __esm({
2485
2696
  };
2486
2697
  }
2487
2698
  // ─── executeStream ──────────────────────────────────────────────
2488
- async *executeStream(messages, _options, signal) {
2699
+ async *executeStream(messages, options, signal) {
2489
2700
  this.checkAbort(signal);
2490
2701
  const sdk = await loadSDK3();
2491
- const model = await this.getModel();
2492
- const tools = await this.getSDKTools(signal);
2702
+ const model = await this.getModel(options);
2703
+ const tools = await this.getSDKTools(signal, options);
2493
2704
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2494
2705
  const sdkMessages = messagesToSDK(messages);
2495
2706
  const hasTools = Object.keys(tools).length > 0;
@@ -2535,9 +2746,11 @@ var init_vercel_ai = __esm({
2535
2746
  promptTokens: Number(totalUsage?.inputTokens ?? 0),
2536
2747
  completionTokens: Number(totalUsage?.outputTokens ?? 0)
2537
2748
  };
2749
+ const hasStreamed = finalText.length > 0;
2538
2750
  yield {
2539
2751
  type: "done",
2540
- finalOutput: finalText || null
2752
+ finalOutput: hasStreamed ? null : finalText || null,
2753
+ ...hasStreamed ? { streamed: true } : {}
2541
2754
  };
2542
2755
  } catch (e) {
2543
2756
  if (signal.aborted) throw new AbortError();
@@ -2566,16 +2779,33 @@ var init_vercel_ai = __esm({
2566
2779
  const baseUrl = (this.options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
2567
2780
  try {
2568
2781
  const res = await globalThis.fetch(`${baseUrl}/models`, {
2569
- headers: { Authorization: `Bearer ${this.options.apiKey}` }
2782
+ headers: {
2783
+ Authorization: `Bearer ${this.options.apiKey}`,
2784
+ // OpenRouter requires HTTP-Referer for API access
2785
+ "HTTP-Referer": "https://github.com/nicepkg/agent-sdk"
2786
+ }
2570
2787
  });
2571
2788
  if (!res.ok) {
2572
2789
  return [];
2573
2790
  }
2574
2791
  const body = await res.json();
2575
- if (!body.data || body.data.length === 0) {
2576
- return [];
2792
+ if (body.data && Array.isArray(body.data)) {
2793
+ return body.data.filter((m) => typeof m.id === "string").map((m) => ({
2794
+ id: m.id,
2795
+ ...typeof m.name === "string" && { name: m.name },
2796
+ ...typeof m.description === "string" && { description: m.description },
2797
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2798
+ }));
2577
2799
  }
2578
- return body.data.map((m) => ({ id: m.id }));
2800
+ if (Array.isArray(body)) {
2801
+ return body.filter((m) => typeof m.id === "string").map((m) => ({
2802
+ id: m.id,
2803
+ ...typeof m.name === "string" && { name: m.name },
2804
+ ...typeof m.description === "string" && { description: m.description },
2805
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2806
+ }));
2807
+ }
2808
+ return [];
2579
2809
  } catch {
2580
2810
  return [];
2581
2811
  }
@@ -2589,280 +2819,24 @@ var init_vercel_ai = __esm({
2589
2819
  try {
2590
2820
  await loadSDK3();
2591
2821
  } catch (e) {
2592
- errors.push(e instanceof Error ? e.message : String(e));
2593
- }
2594
- try {
2595
- await loadCompat();
2596
- } catch (e) {
2597
- errors.push(e instanceof Error ? e.message : String(e));
2598
- }
2599
- return { valid: errors.length === 0, errors };
2600
- }
2601
- async dispose() {
2602
- if (this.disposed) return;
2603
- this.disposed = true;
2604
- }
2605
- };
2606
- }
2607
- });
2608
-
2609
- // src/registry.ts
2610
- function registerBackend(name, factory) {
2611
- if (registry.has(name)) {
2612
- throw new BackendAlreadyRegisteredError(name);
2613
- }
2614
- registry.set(name, { factory, builtin: false });
2615
- }
2616
- function unregisterBackend(name) {
2617
- return registry.delete(name);
2618
- }
2619
- function hasBackend(name) {
2620
- return registry.has(name) || isBuiltinName(name);
2621
- }
2622
- function listBackends() {
2623
- const names = new Set(registry.keys());
2624
- for (const builtin of BUILTIN_BACKENDS) {
2625
- names.add(builtin);
2626
- }
2627
- return [...names];
2628
- }
2629
- function resetRegistry() {
2630
- registry.clear();
2631
- }
2632
- function isBuiltinName(name) {
2633
- return BUILTIN_BACKENDS.has(name);
2634
- }
2635
- async function loadBuiltinFactory(name) {
2636
- switch (name) {
2637
- case "copilot": {
2638
- const mod = await Promise.resolve().then(() => (init_copilot(), copilot_exports));
2639
- return (opts) => mod.createCopilotService(opts);
2640
- }
2641
- case "claude": {
2642
- const mod = await Promise.resolve().then(() => (init_claude(), claude_exports));
2643
- return (opts) => mod.createClaudeService(opts);
2644
- }
2645
- case "vercel-ai": {
2646
- const mod = await Promise.resolve().then(() => (init_vercel_ai(), vercel_ai_exports));
2647
- return (opts) => mod.createVercelAIService(opts);
2648
- }
2649
- }
2650
- }
2651
- async function createAgentService(name, options) {
2652
- const entry = registry.get(name);
2653
- if (entry) {
2654
- return entry.factory(options);
2655
- }
2656
- if (isBuiltinName(name)) {
2657
- const factory = await loadBuiltinFactory(name);
2658
- registry.set(name, { factory, builtin: true });
2659
- return factory(options);
2660
- }
2661
- throw new BackendNotFoundError(name);
2662
- }
2663
- var registry, BUILTIN_BACKENDS;
2664
- var init_registry = __esm({
2665
- "src/registry.ts"() {
2666
- init_errors();
2667
- registry = /* @__PURE__ */ new Map();
2668
- BUILTIN_BACKENDS = /* @__PURE__ */ new Set([
2669
- "copilot",
2670
- "claude",
2671
- "vercel-ai"
2672
- ]);
2673
- }
2674
- });
2675
-
2676
- // src/utils/messages.ts
2677
- function messagesToPrompt(messages) {
2678
- return messages.map((msg) => {
2679
- switch (msg.role) {
2680
- case "user":
2681
- return contentToText(msg.content);
2682
- case "assistant":
2683
- return contentToText(msg.content);
2684
- case "system":
2685
- return msg.content;
2686
- case "tool":
2687
- return msg.content ?? "";
2688
- }
2689
- }).filter(Boolean).join("\n\n");
2690
- }
2691
- function contentToText(content) {
2692
- return getTextContent(content);
2693
- }
2694
- function buildSystemPrompt(base, schemaInstruction) {
2695
- if (!schemaInstruction) return base;
2696
- return `${base}
2697
-
2698
- ${schemaInstruction}`;
2699
- }
2700
- var init_messages = __esm({
2701
- "src/utils/messages.ts"() {
2702
- init_types();
2703
- }
2704
- });
2705
- function createDefaultPermissionStore(projectDir) {
2706
- const sessionStore = new InMemoryPermissionStore();
2707
- const projectPath = projectDir ? path__namespace.join(projectDir, ".agent-sdk", "permissions.json") : path__namespace.join(process.cwd(), ".agent-sdk", "permissions.json");
2708
- const userPath = path__namespace.join(os__namespace.homedir(), ".agent-sdk", "permissions.json");
2709
- const projectStore = new FilePermissionStore(projectPath);
2710
- const userStore = new FilePermissionStore(userPath);
2711
- return new CompositePermissionStore(sessionStore, projectStore, userStore);
2712
- }
2713
- var InMemoryPermissionStore, FilePermissionStore, CompositePermissionStore;
2714
- var init_permission_store = __esm({
2715
- "src/permission-store.ts"() {
2716
- InMemoryPermissionStore = class {
2717
- approvals = /* @__PURE__ */ new Map();
2718
- async isApproved(toolName) {
2719
- return this.approvals.has(toolName);
2720
- }
2721
- async approve(toolName, scope) {
2722
- if (scope === "once") return;
2723
- this.approvals.set(toolName, scope);
2724
- }
2725
- async revoke(toolName) {
2726
- this.approvals.delete(toolName);
2727
- }
2728
- async clear() {
2729
- this.approvals.clear();
2730
- }
2731
- async dispose() {
2732
- this.approvals.clear();
2733
- }
2734
- };
2735
- FilePermissionStore = class {
2736
- filePath;
2737
- constructor(filePath) {
2738
- this.filePath = path__namespace.resolve(filePath);
2739
- }
2740
- async isApproved(toolName) {
2741
- const data = this.readFile();
2742
- return toolName in data.approvals;
2743
- }
2744
- async approve(toolName, scope) {
2745
- if (scope === "once") return;
2746
- const data = this.readFile();
2747
- data.approvals[toolName] = { scope, timestamp: Date.now() };
2748
- this.writeFileAtomic(data);
2749
- }
2750
- async revoke(toolName) {
2751
- const data = this.readFile();
2752
- delete data.approvals[toolName];
2753
- this.writeFileAtomic(data);
2754
- }
2755
- async clear() {
2756
- this.writeFileAtomic({ approvals: {} });
2757
- }
2758
- async dispose() {
2759
- }
2760
- readFile() {
2761
- try {
2762
- const raw = fs__namespace.readFileSync(this.filePath, "utf-8");
2763
- const parsed = JSON.parse(raw);
2764
- if (parsed && typeof parsed.approvals === "object") return parsed;
2765
- } catch {
2766
- }
2767
- return { approvals: {} };
2768
- }
2769
- writeFileAtomic(data) {
2770
- const dir = path__namespace.dirname(this.filePath);
2771
- fs__namespace.mkdirSync(dir, { recursive: true });
2772
- const tmpPath = this.filePath + `.tmp.${process.pid}.${Date.now()}`;
2773
- fs__namespace.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
2774
- fs__namespace.renameSync(tmpPath, this.filePath);
2775
- }
2776
- };
2777
- CompositePermissionStore = class {
2778
- sessionStore;
2779
- projectStore;
2780
- userStore;
2781
- constructor(sessionStore, projectStore, userStore) {
2782
- this.sessionStore = sessionStore;
2783
- this.projectStore = projectStore;
2784
- this.userStore = userStore ?? projectStore;
2785
- }
2786
- async isApproved(toolName) {
2787
- return await this.sessionStore.isApproved(toolName) || await this.projectStore.isApproved(toolName) || await this.userStore.isApproved(toolName);
2788
- }
2789
- async approve(toolName, scope) {
2790
- if (scope === "once") return;
2791
- if (scope === "session") {
2792
- await this.sessionStore.approve(toolName, scope);
2793
- } else if (scope === "project") {
2794
- await this.projectStore.approve(toolName, scope);
2795
- } else {
2796
- await this.userStore.approve(toolName, scope);
2822
+ errors.push(e instanceof Error ? e.message : String(e));
2797
2823
  }
2798
- }
2799
- async revoke(toolName) {
2800
- await this.sessionStore.revoke(toolName);
2801
- await this.projectStore.revoke(toolName);
2802
- await this.userStore.revoke(toolName);
2803
- }
2804
- async clear() {
2805
- await this.sessionStore.clear();
2806
- await this.projectStore.clear();
2807
- await this.userStore.clear();
2824
+ try {
2825
+ await loadCompat();
2826
+ } catch (e) {
2827
+ errors.push(e instanceof Error ? e.message : String(e));
2828
+ }
2829
+ return { valid: errors.length === 0, errors };
2808
2830
  }
2809
2831
  async dispose() {
2810
- await this.sessionStore.dispose();
2811
- await this.projectStore.dispose();
2812
- if (this.userStore !== this.projectStore) {
2813
- await this.userStore.dispose();
2814
- }
2832
+ if (this.disposed) return;
2833
+ this.disposed = true;
2815
2834
  }
2816
2835
  };
2817
2836
  }
2818
2837
  });
2819
2838
 
2820
- // src/index.ts
2821
- var src_exports = {};
2822
- __export(src_exports, {
2823
- AbortError: () => AbortError,
2824
- AgentSDKError: () => AgentSDKError,
2825
- BackendAlreadyRegisteredError: () => BackendAlreadyRegisteredError,
2826
- BackendNotFoundError: () => BackendNotFoundError,
2827
- BaseAgent: () => BaseAgent,
2828
- CompositePermissionStore: () => CompositePermissionStore,
2829
- DependencyError: () => DependencyError,
2830
- DisposedError: () => DisposedError,
2831
- FilePermissionStore: () => FilePermissionStore,
2832
- InMemoryPermissionStore: () => InMemoryPermissionStore,
2833
- ReentrancyError: () => ReentrancyError,
2834
- StructuredOutputError: () => StructuredOutputError,
2835
- SubprocessError: () => SubprocessError,
2836
- ToolExecutionError: () => ToolExecutionError,
2837
- buildSystemPrompt: () => buildSystemPrompt,
2838
- contentToText: () => contentToText,
2839
- createAgentService: () => createAgentService,
2840
- createDefaultPermissionStore: () => createDefaultPermissionStore,
2841
- getTextContent: () => getTextContent,
2842
- hasBackend: () => hasBackend,
2843
- isMultiPartContent: () => isMultiPartContent,
2844
- isTextContent: () => isTextContent,
2845
- isToolDefinition: () => isToolDefinition,
2846
- listBackends: () => listBackends,
2847
- messagesToPrompt: () => messagesToPrompt,
2848
- registerBackend: () => registerBackend,
2849
- resetRegistry: () => resetRegistry,
2850
- unregisterBackend: () => unregisterBackend,
2851
- zodToJsonSchema: () => zodToJsonSchema
2852
- });
2853
- var init_src = __esm({
2854
- "src/index.ts"() {
2855
- init_types();
2856
- init_errors();
2857
- init_registry();
2858
- init_base_agent();
2859
- init_schema();
2860
- init_messages();
2861
- init_permission_store();
2862
- }
2863
- });
2864
-
2865
- // src/chat/core.ts
2839
+ // src/chat/types.ts
2866
2840
  function createChatId() {
2867
2841
  return crypto.randomUUID();
2868
2842
  }
@@ -2873,6 +2847,20 @@ function toChatId(value) {
2873
2847
  }
2874
2848
  return value;
2875
2849
  }
2850
+ function createTextMessage(text, role = "user") {
2851
+ return {
2852
+ id: createChatId(),
2853
+ role,
2854
+ parts: [{ type: "text", text, status: "complete" }],
2855
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2856
+ status: "complete"
2857
+ };
2858
+ }
2859
+ function isObservableSession(session) {
2860
+ return "subscribe" in session && typeof session.subscribe === "function" && "getSnapshot" in session && typeof session.getSnapshot === "function";
2861
+ }
2862
+
2863
+ // src/chat/chat-utils.ts
2876
2864
  function getMessageText(message) {
2877
2865
  return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
2878
2866
  }
@@ -2882,6 +2870,8 @@ function getMessageToolCalls(message) {
2882
2870
  function getMessageReasoning(message) {
2883
2871
  return message.parts.filter((p) => p.type === "reasoning").map((p) => p.text).join("");
2884
2872
  }
2873
+
2874
+ // src/chat/guards.ts
2885
2875
  function isChatMessage(value) {
2886
2876
  if (typeof value !== "object" || value === null) return false;
2887
2877
  const obj = value;
@@ -2947,6 +2937,8 @@ function isChatEvent(value) {
2947
2937
  ];
2948
2938
  return validTypes.includes(obj.type);
2949
2939
  }
2940
+
2941
+ // src/chat/bridge.ts
2950
2942
  function agentEventToChatEvent(event, messageId) {
2951
2943
  switch (event.type) {
2952
2944
  case "text_delta":
@@ -2999,6 +2991,7 @@ function agentEventToChatEvent(event, messageId) {
2999
2991
  type: "error",
3000
2992
  error: event.error,
3001
2993
  recoverable: event.recoverable,
2994
+ code: event.code,
3002
2995
  messageId
3003
2996
  };
3004
2997
  case "heartbeat":
@@ -3045,11 +3038,13 @@ function chatEventToAgentEvent(event) {
3045
3038
  result: event.result
3046
3039
  };
3047
3040
  case "error":
3048
- return { type: "error", error: event.error, recoverable: event.recoverable };
3041
+ return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
3049
3042
  default:
3050
3043
  return null;
3051
3044
  }
3052
3045
  }
3046
+
3047
+ // src/chat/conversion.ts
3053
3048
  function toAgentMessage(message) {
3054
3049
  const textContent = getMessageText(message);
3055
3050
  const toolCallParts = getMessageToolCalls(message);
@@ -3220,6 +3215,56 @@ var ContextWindowManager = class {
3220
3215
  });
3221
3216
  return { ...result, messages: updatedMessages };
3222
3217
  }
3218
+ /**
3219
+ * Trim messages using real token usage data from the previous API call.
3220
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
3221
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
3222
+ *
3223
+ * @param messages - All messages in the session
3224
+ * @param lastPromptTokens - Real prompt tokens from the last API response
3225
+ * @param modelContextWindow - Model's total context window size in tokens
3226
+ * @returns Result with fitted messages and metadata
3227
+ */
3228
+ fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
3229
+ if (messages.length === 0) {
3230
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
3231
+ }
3232
+ const budget = modelContextWindow - this.config.reservedTokens;
3233
+ if (budget <= 0 || lastPromptTokens <= budget) {
3234
+ return {
3235
+ messages: [...messages],
3236
+ totalTokens: lastPromptTokens,
3237
+ removedCount: 0,
3238
+ wasTruncated: false
3239
+ };
3240
+ }
3241
+ const avgTokensPerMessage = lastPromptTokens / messages.length;
3242
+ const tokensToFree = lastPromptTokens - budget;
3243
+ const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
3244
+ const nonSystemIndices = [];
3245
+ for (let i = 0; i < messages.length; i++) {
3246
+ if (messages[i].role === "system") ; else {
3247
+ nonSystemIndices.push(i);
3248
+ }
3249
+ }
3250
+ const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
3251
+ const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
3252
+ const result = [];
3253
+ for (let i = 0; i < messages.length; i++) {
3254
+ if (!removedIndices.has(i)) {
3255
+ result.push(messages[i]);
3256
+ }
3257
+ }
3258
+ const estimatedTokens = Math.round(
3259
+ lastPromptTokens * (result.length / messages.length)
3260
+ );
3261
+ return {
3262
+ messages: result,
3263
+ totalTokens: estimatedTokens,
3264
+ removedCount: removableCount,
3265
+ wasTruncated: removableCount > 0
3266
+ };
3267
+ }
3223
3268
  /**
3224
3269
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
3225
3270
  * Always keeps the most recent user message.
@@ -3337,37 +3382,19 @@ var ContextWindowManager = class {
3337
3382
  };
3338
3383
 
3339
3384
  // src/chat/errors.ts
3385
+ init_errors2();
3340
3386
  init_errors();
3341
- var ChatErrorCode = /* @__PURE__ */ ((ChatErrorCode2) => {
3342
- ChatErrorCode2["NETWORK"] = "NETWORK";
3343
- ChatErrorCode2["TIMEOUT"] = "TIMEOUT";
3344
- ChatErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
3345
- ChatErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
3346
- ChatErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
3347
- ChatErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
3348
- ChatErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
3349
- ChatErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
3350
- ChatErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
3351
- ChatErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
3352
- ChatErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
3353
- ChatErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
3354
- ChatErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
3355
- ChatErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
3356
- ChatErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
3357
- ChatErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
3358
- ChatErrorCode2["DISPOSED"] = "DISPOSED";
3359
- ChatErrorCode2["ABORTED"] = "ABORTED";
3360
- ChatErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
3361
- ChatErrorCode2["REENTRANCY"] = "REENTRANCY";
3362
- return ChatErrorCode2;
3363
- })(ChatErrorCode || {});
3364
3387
  var ChatError = class extends AgentSDKError {
3365
3388
  code;
3366
3389
  retryable;
3367
3390
  retryAfter;
3368
3391
  timestamp;
3369
3392
  constructor(message, options) {
3370
- super(message, { cause: options.cause });
3393
+ super(message, {
3394
+ cause: options.cause,
3395
+ code: options.code,
3396
+ retryable: options.retryable
3397
+ });
3371
3398
  this.name = "ChatError";
3372
3399
  this.code = options.code;
3373
3400
  this.retryable = options.retryable ?? false;
@@ -3546,12 +3573,12 @@ function isRetryable(error) {
3546
3573
  return classified.retryable;
3547
3574
  }
3548
3575
  function sleep(ms, signal) {
3549
- return new Promise((resolve2, reject) => {
3576
+ return new Promise((resolve, reject) => {
3550
3577
  if (signal?.aborted) {
3551
3578
  reject(new ChatError("Retry aborted", { code: "ABORTED" /* ABORTED */ }));
3552
3579
  return;
3553
3580
  }
3554
- const timer = setTimeout(resolve2, ms);
3581
+ const timer = setTimeout(resolve, ms);
3555
3582
  signal?.addEventListener(
3556
3583
  "abort",
3557
3584
  () => {
@@ -3875,6 +3902,35 @@ var CancellableTimeout = class {
3875
3902
  }
3876
3903
  };
3877
3904
 
3905
+ // src/chat/listener-set.ts
3906
+ var ListenerSet = class {
3907
+ _listeners = /* @__PURE__ */ new Set();
3908
+ /** Add a listener. Returns an unsubscribe function. */
3909
+ add(callback) {
3910
+ this._listeners.add(callback);
3911
+ return () => {
3912
+ this._listeners.delete(callback);
3913
+ };
3914
+ }
3915
+ /** Notify all listeners with the given arguments. Errors are isolated per listener. */
3916
+ notify(...args) {
3917
+ for (const cb of this._listeners) {
3918
+ try {
3919
+ cb(...args);
3920
+ } catch {
3921
+ }
3922
+ }
3923
+ }
3924
+ /** Remove all listeners. */
3925
+ clear() {
3926
+ this._listeners.clear();
3927
+ }
3928
+ /** Current number of listeners. */
3929
+ get size() {
3930
+ return this._listeners.size;
3931
+ }
3932
+ };
3933
+
3878
3934
  // src/chat/runtime.ts
3879
3935
  var ChatRuntime = class {
3880
3936
  _state;
@@ -3886,20 +3942,19 @@ var ChatRuntime = class {
3886
3942
  _tools = /* @__PURE__ */ new Map();
3887
3943
  _retryConfig;
3888
3944
  _contextStats = /* @__PURE__ */ new Map();
3945
+ _sessionUsage = /* @__PURE__ */ new Map();
3946
+ _modelContextWindows = /* @__PURE__ */ new Map();
3889
3947
  _onContextTrimmed;
3890
3948
  _streamTimeoutMs;
3891
- _sessionListeners = /* @__PURE__ */ new Set();
3892
- _activeAdapter = null;
3893
- _currentBackend;
3894
- _currentModel;
3895
- _activeSessionId = null;
3949
+ _sessionListeners = new ListenerSet();
3950
+ _adapterPool = /* @__PURE__ */ new Map();
3951
+ _defaultBackend;
3896
3952
  _abortController = null;
3897
3953
  constructor(options) {
3898
3954
  this._state = new StateMachine("idle", RUNTIME_TRANSITIONS);
3899
3955
  this._guard = new ChatReentrancyGuard();
3900
3956
  this._backends = options.backends;
3901
- this._currentBackend = options.defaultBackend;
3902
- this._currentModel = options.defaultModel;
3957
+ this._defaultBackend = options.defaultBackend;
3903
3958
  this._sessionStore = options.sessionStore;
3904
3959
  this._contextConfig = options.context;
3905
3960
  this._middleware = [...options.middleware ?? []];
@@ -3912,6 +3967,11 @@ var ChatRuntime = class {
3912
3967
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
3913
3968
  );
3914
3969
  }
3970
+ if (options.tools) {
3971
+ for (const tool of options.tools) {
3972
+ this._tools.set(tool.name, tool);
3973
+ }
3974
+ }
3915
3975
  }
3916
3976
  // ── Lifecycle ──────────────────────────────────────────────
3917
3977
  get status() {
@@ -3923,24 +3983,23 @@ var ChatRuntime = class {
3923
3983
  this._abortController?.dispose();
3924
3984
  this._abortController = null;
3925
3985
  this._state.transition("disposed");
3926
- if (this._activeAdapter) {
3927
- await this._activeAdapter.dispose();
3928
- this._activeAdapter = null;
3986
+ for (const adapter of this._adapterPool.values()) {
3987
+ try {
3988
+ await adapter.dispose();
3989
+ } catch {
3990
+ }
3929
3991
  }
3992
+ this._adapterPool.clear();
3930
3993
  }
3931
3994
  // ── Sessions ───────────────────────────────────────────────
3932
- get activeSessionId() {
3933
- return this._activeSessionId;
3934
- }
3935
3995
  async createSession(options) {
3936
3996
  this.assertNotDisposed();
3937
3997
  const config = {
3938
- model: options.config?.model ?? this._currentModel ?? "",
3939
- backend: options.config?.backend ?? this._currentBackend,
3998
+ model: options.config?.model ?? "",
3999
+ backend: options.config?.backend ?? this._defaultBackend,
3940
4000
  ...options.config
3941
4001
  };
3942
4002
  const session = await this._sessionStore.createSession({ ...options, config });
3943
- this._activeSessionId = session.id;
3944
4003
  this._notifySessionChange();
3945
4004
  return session;
3946
4005
  }
@@ -3960,36 +4019,12 @@ var ChatRuntime = class {
3960
4019
  if (!session) return;
3961
4020
  await this._sessionStore.deleteSession(cid);
3962
4021
  this._contextStats.delete(cid);
3963
- if (this._activeSessionId === cid) {
3964
- this._activeSessionId = null;
3965
- }
3966
- this._notifySessionChange();
3967
- }
3968
- async archiveSession(id) {
3969
- this.assertNotDisposed();
3970
- const cid = toChatId(id);
3971
- await this._sessionStore.archiveSession(cid);
4022
+ this._sessionUsage.delete(cid);
3972
4023
  this._notifySessionChange();
3973
4024
  }
3974
- async switchSession(id) {
3975
- this.assertNotDisposed();
3976
- const cid = toChatId(id);
3977
- const session = await this._sessionStore.getSession(cid);
3978
- if (!session) {
3979
- throw new ChatError(
3980
- `Session "${id}" not found`,
3981
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
3982
- );
3983
- }
3984
- this._activeSessionId = session.id;
3985
- return session;
3986
- }
3987
4025
  // ── Messaging ──────────────────────────────────────────────
3988
4026
  async *send(sessionId, message, options) {
3989
- this.assertNotDisposed();
3990
- if (!message || message.trim().length === 0) {
3991
- throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
3992
- }
4027
+ this.validateSendInput(message, options);
3993
4028
  this._guard.acquire();
3994
4029
  const cid = toChatId(sessionId);
3995
4030
  this._abortController = new ChatAbortController(options?.signal);
@@ -3998,150 +4033,274 @@ var ChatRuntime = class {
3998
4033
  this._state.transition("idle");
3999
4034
  }
4000
4035
  this._state.transition("streaming");
4001
- const session = await this._sessionStore.getSession(cid);
4002
- if (!session) {
4003
- throw new ChatError(
4004
- `Session "${cid}" not found`,
4005
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4006
- );
4007
- }
4008
- const middlewareContext = {
4036
+ await this.loadSession(cid);
4037
+ const mwCtx = {
4009
4038
  sessionId: cid,
4010
4039
  signal: this._abortController.signal
4011
4040
  };
4012
- let userMessage = this.createUserMessage(message);
4013
- for (const mw of this._middleware) {
4014
- if (mw.onBeforeSend) {
4015
- userMessage = await mw.onBeforeSend(userMessage, middlewareContext);
4016
- }
4017
- }
4018
- await this._sessionStore.appendMessage(cid, userMessage);
4019
- const updatedSession = await this._sessionStore.getSession(cid);
4020
- let messagesToSend = updatedSession.messages;
4021
- if (this._contextConfig) {
4022
- const ctxManager = new ContextWindowManager(this._contextConfig);
4023
- const result = await ctxManager.fitMessagesAsync(messagesToSend);
4024
- this._contextStats.set(cid, {
4025
- totalTokens: result.totalTokens,
4026
- removedCount: result.removedCount,
4027
- wasTruncated: result.wasTruncated,
4028
- availableBudget: ctxManager.availableBudget
4029
- });
4030
- if (result.wasTruncated && this._onContextTrimmed) {
4031
- const keptIds = new Set(result.messages.map((m) => m.id));
4032
- const removed = messagesToSend.filter((m) => !keptIds.has(m.id));
4033
- if (removed.length > 0) {
4034
- try {
4035
- this._onContextTrimmed(cid, removed);
4036
- } catch {
4037
- }
4038
- }
4039
- }
4040
- messagesToSend = result.messages;
4041
+ const userMessage = await this.applyBeforeSendMiddleware(
4042
+ this.createUserMessage(message),
4043
+ mwCtx
4044
+ );
4045
+ if (userMessage === null) {
4046
+ this._state.transition("idle");
4047
+ return;
4041
4048
  }
4042
- const sessionForAdapter = {
4043
- ...updatedSession,
4044
- messages: messagesToSend
4045
- };
4046
- const adapter = await this.getOrCreateAdapterWithRetry();
4049
+ const updatedSession = await this.persistAndReload(cid, userMessage);
4050
+ const sessionForAdapter = await this.trimSessionContext(cid, updatedSession, options.model);
4051
+ const stream = await this.prepareEventStream(
4052
+ cid,
4053
+ sessionForAdapter,
4054
+ updatedSession,
4055
+ message,
4056
+ options
4057
+ );
4047
4058
  const accumulator = new MessageAccumulator();
4048
- const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4049
- sessionId: cid,
4050
- custom: updatedSession.metadata?.custom
4051
- }) : void 0;
4052
- const streamOptions = {
4053
- ...options,
4054
- signal: this._abortController.signal,
4055
- model: options?.model ?? this._currentModel,
4056
- tools: runtimeTools
4057
- };
4058
- const stream = await this.createStreamWithRetry(adapter, sessionForAdapter, message, streamOptions);
4059
4059
  const eventSource = this._streamTimeoutMs ? withStreamWatchdog(stream, { timeoutMs: this._streamTimeoutMs, signal: this._abortController.signal }) : stream;
4060
4060
  for await (const event of eventSource) {
4061
4061
  if (this._abortController.isAborted) break;
4062
4062
  this.feedAccumulator(accumulator, event);
4063
- let processedEvent = event;
4064
- for (const mw of this._middleware) {
4065
- if (mw.onEvent && processedEvent) {
4066
- processedEvent = await mw.onEvent(processedEvent, middlewareContext);
4067
- }
4068
- }
4069
- if (processedEvent) {
4070
- yield processedEvent;
4071
- }
4072
- }
4073
- if (this._state.current === "disposed") {
4074
- return;
4075
- }
4076
- let assistantMessage = accumulator.finalize();
4077
- for (const mw of this._middleware) {
4078
- if (mw.onAfterReceive) {
4079
- assistantMessage = await mw.onAfterReceive(assistantMessage, middlewareContext);
4063
+ if (event.type === "usage") {
4064
+ this._sessionUsage.set(cid, {
4065
+ promptTokens: event.promptTokens,
4066
+ completionTokens: event.completionTokens
4067
+ });
4068
+ this.updateContextStatsWithUsage(cid, event.promptTokens, event.completionTokens, options);
4080
4069
  }
4070
+ const processed = await this.applyOnEventMiddleware(event, mwCtx);
4071
+ if (processed) yield processed;
4081
4072
  }
4082
- await this._sessionStore.appendMessage(cid, assistantMessage);
4083
- this._notifySessionChange();
4073
+ if (this._state.current === "disposed") return;
4074
+ await this.finalizeAssistantMessage(cid, accumulator, mwCtx);
4084
4075
  this._state.transition("idle");
4085
4076
  } catch (error) {
4086
- let processedError = error instanceof Error ? error : new Error(String(error));
4087
- const middlewareContext = {
4088
- sessionId: cid,
4089
- signal: this._abortController?.signal ?? new AbortController().signal
4090
- };
4091
- for (const mw of this._middleware) {
4092
- if (mw.onError) {
4093
- const result = await mw.onError(processedError, middlewareContext);
4094
- if (result === null) {
4095
- if (this._state.canTransition("idle")) {
4096
- this._state.transition("idle");
4097
- }
4098
- return;
4099
- }
4100
- processedError = result;
4101
- }
4102
- }
4103
- if (this._state.canTransition("error")) {
4104
- this._state.transition("error");
4105
- }
4106
- throw processedError;
4077
+ const result = await this.handleSendError(error, cid);
4078
+ if (result !== null) throw result;
4107
4079
  } finally {
4108
4080
  this._guard.release();
4109
4081
  this._abortController?.dispose();
4110
4082
  this._abortController = null;
4111
4083
  }
4112
4084
  }
4113
- abort() {
4114
- this._abortController?.abort("User abort");
4115
- }
4116
- // ── Backend / Model ────────────────────────────────────────
4117
- get currentBackend() {
4118
- return this._currentBackend;
4119
- }
4120
- get currentModel() {
4121
- return this._currentModel;
4122
- }
4123
- async switchBackend(name) {
4085
+ // ── Send Pipeline Stages ──────────────────────────────────────
4086
+ /** Stage 1: Validate send inputs (message content + required fields). */
4087
+ validateSendInput(message, options) {
4124
4088
  this.assertNotDisposed();
4125
- if (!this._backends[name]) {
4089
+ if (!message || message.trim().length === 0) {
4090
+ throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
4091
+ }
4092
+ if (!options.model) {
4093
+ throw new ChatError(
4094
+ "options.model is required \u2014 caller must specify which model to use",
4095
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4096
+ );
4097
+ }
4098
+ if (!options.backend) {
4099
+ throw new ChatError(
4100
+ "options.backend is required \u2014 caller must specify which backend to use",
4101
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4102
+ );
4103
+ }
4104
+ if (!options.credentials) {
4126
4105
  throw new ChatError(
4127
- `Backend "${name}" not found in backends map`,
4106
+ "options.credentials is required \u2014 caller must provide authentication credentials",
4128
4107
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4129
4108
  );
4130
4109
  }
4131
- if (this._activeAdapter) {
4132
- await this._activeAdapter.dispose();
4133
- this._activeAdapter = null;
4110
+ }
4111
+ /** Stage 2: Load session from store. */
4112
+ async loadSession(cid) {
4113
+ const session = await this._sessionStore.getSession(cid);
4114
+ if (!session) {
4115
+ throw new ChatError(
4116
+ `Session "${cid}" not found`,
4117
+ { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4118
+ );
4119
+ }
4120
+ return session;
4121
+ }
4122
+ /** Stage 3: Apply onBeforeSend middleware pipeline. Returns null if middleware rejected the send. */
4123
+ async applyBeforeSendMiddleware(userMessage, ctx) {
4124
+ let msg = userMessage;
4125
+ for (const mw of this._middleware) {
4126
+ if (mw.onBeforeSend && msg) {
4127
+ msg = await mw.onBeforeSend(msg, ctx);
4128
+ if (msg === null) return null;
4129
+ }
4130
+ }
4131
+ return msg;
4132
+ }
4133
+ /** Stage 4: Persist user message and reload session with full history. */
4134
+ async persistAndReload(cid, userMessage) {
4135
+ await this._sessionStore.appendMessage(cid, userMessage);
4136
+ return await this._sessionStore.getSession(cid);
4137
+ }
4138
+ /** Stage 5: Auto-trim context window if configured. Returns session snapshot for adapter. */
4139
+ async trimSessionContext(cid, session, model) {
4140
+ if (!this._contextConfig) return session;
4141
+ const ctxManager = new ContextWindowManager(this._contextConfig);
4142
+ const lastUsage = this._sessionUsage.get(cid);
4143
+ const modelContextWindow = model ? this._modelContextWindows.get(model) : void 0;
4144
+ if (lastUsage && modelContextWindow) {
4145
+ const result2 = ctxManager.fitMessagesWithUsage(
4146
+ session.messages,
4147
+ lastUsage.promptTokens,
4148
+ modelContextWindow
4149
+ );
4150
+ this._contextStats.set(cid, {
4151
+ totalTokens: result2.totalTokens,
4152
+ removedCount: result2.removedCount,
4153
+ wasTruncated: result2.wasTruncated,
4154
+ availableBudget: Math.max(0, modelContextWindow - result2.totalTokens),
4155
+ realPromptTokens: lastUsage.promptTokens,
4156
+ realCompletionTokens: lastUsage.completionTokens,
4157
+ modelContextWindow
4158
+ });
4159
+ if (result2.wasTruncated && this._onContextTrimmed) {
4160
+ const keptIds = new Set(result2.messages.map((m) => m.id));
4161
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4162
+ if (removed.length > 0) {
4163
+ try {
4164
+ this._onContextTrimmed(cid, removed);
4165
+ } catch {
4166
+ }
4167
+ }
4168
+ }
4169
+ return { ...session, messages: result2.messages };
4170
+ }
4171
+ const result = await ctxManager.fitMessagesAsync(session.messages);
4172
+ this._contextStats.set(cid, {
4173
+ totalTokens: result.totalTokens,
4174
+ removedCount: result.removedCount,
4175
+ wasTruncated: result.wasTruncated,
4176
+ availableBudget: ctxManager.availableBudget,
4177
+ modelContextWindow
4178
+ });
4179
+ if (result.wasTruncated && this._onContextTrimmed) {
4180
+ const keptIds = new Set(result.messages.map((m) => m.id));
4181
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4182
+ if (removed.length > 0) {
4183
+ try {
4184
+ this._onContextTrimmed(cid, removed);
4185
+ } catch {
4186
+ }
4187
+ }
4188
+ }
4189
+ return { ...session, messages: result.messages };
4190
+ }
4191
+ /** Update context stats with real usage data from a usage event. */
4192
+ updateContextStatsWithUsage(cid, promptTokens, completionTokens, options) {
4193
+ const modelContextWindow = options.model ? this._modelContextWindows.get(options.model) : void 0;
4194
+ const existing = this._contextStats.get(cid);
4195
+ this._contextStats.set(cid, {
4196
+ totalTokens: promptTokens,
4197
+ removedCount: existing?.removedCount ?? 0,
4198
+ wasTruncated: existing?.wasTruncated ?? false,
4199
+ availableBudget: modelContextWindow ? Math.max(0, modelContextWindow - promptTokens) : existing?.availableBudget ?? 0,
4200
+ realPromptTokens: promptTokens,
4201
+ realCompletionTokens: completionTokens,
4202
+ modelContextWindow
4203
+ });
4204
+ }
4205
+ /** Stage 6: Prepare event stream — adapter with retry, tool injection. */
4206
+ async prepareEventStream(cid, sessionForAdapter, fullSession, message, options) {
4207
+ const adapter = await this.getOrCreateAdapterWithRetry(options.backend, options.credentials);
4208
+ const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4209
+ sessionId: cid,
4210
+ custom: fullSession.metadata?.custom
4211
+ }) : void 0;
4212
+ const streamOptions = {
4213
+ signal: this._abortController.signal,
4214
+ model: options.model,
4215
+ systemPrompt: options.systemPrompt,
4216
+ tools: runtimeTools
4217
+ };
4218
+ return this.createStreamWithRetry(
4219
+ adapter,
4220
+ sessionForAdapter,
4221
+ message,
4222
+ streamOptions,
4223
+ options.backend,
4224
+ options.credentials
4225
+ );
4226
+ }
4227
+ /** Stage 7: Apply onEvent middleware pipeline (sequential transform/suppress). */
4228
+ async applyOnEventMiddleware(event, ctx) {
4229
+ let processed = event;
4230
+ for (const mw of this._middleware) {
4231
+ if (mw.onEvent && processed) {
4232
+ processed = await mw.onEvent(processed, ctx);
4233
+ }
4234
+ }
4235
+ return processed;
4236
+ }
4237
+ /** Stage 8: Finalize accumulator, apply afterReceive middleware, persist assistant message. */
4238
+ async finalizeAssistantMessage(cid, accumulator, ctx) {
4239
+ let assistantMessage = accumulator.finalize();
4240
+ for (const mw of this._middleware) {
4241
+ if (mw.onAfterReceive) {
4242
+ assistantMessage = await mw.onAfterReceive(assistantMessage, ctx);
4243
+ }
4244
+ }
4245
+ await this._sessionStore.appendMessage(cid, assistantMessage);
4246
+ this._notifySessionChange();
4247
+ }
4248
+ /** Stage 9: Error handling — apply onError middleware, transition state. Returns null if suppressed. */
4249
+ async handleSendError(error, cid) {
4250
+ let processedError = error instanceof Error ? error : new Error(String(error));
4251
+ const ctx = {
4252
+ sessionId: cid,
4253
+ signal: this._abortController?.signal ?? new AbortController().signal
4254
+ };
4255
+ for (const mw of this._middleware) {
4256
+ if (mw.onError) {
4257
+ const result = await mw.onError(processedError, ctx);
4258
+ if (result === null) {
4259
+ if (this._state.canTransition("idle")) {
4260
+ this._state.transition("idle");
4261
+ }
4262
+ return null;
4263
+ }
4264
+ processedError = result;
4265
+ }
4266
+ }
4267
+ if (this._state.canTransition("error")) {
4268
+ this._state.transition("error");
4134
4269
  }
4135
- this._currentBackend = name;
4270
+ return processedError;
4271
+ }
4272
+ abort() {
4273
+ this._abortController?.abort("User abort");
4136
4274
  }
4137
- switchModel(model) {
4275
+ // ── Backend / Model ────────────────────────────────────────
4276
+ async listModels(options) {
4138
4277
  this.assertNotDisposed();
4139
- this._currentModel = model;
4278
+ let models = [];
4279
+ const firstAdapter = [...this._adapterPool.values()][0];
4280
+ if (firstAdapter) {
4281
+ try {
4282
+ models = await firstAdapter.listModels();
4283
+ } catch {
4284
+ return [];
4285
+ }
4286
+ } else if (options?.backend && options?.credentials) {
4287
+ try {
4288
+ const adapter = await this.getOrCreateAdapter(options.backend, options.credentials);
4289
+ models = await adapter.listModels();
4290
+ } catch {
4291
+ return [];
4292
+ }
4293
+ }
4294
+ for (const model of models) {
4295
+ if (model.contextWindow != null) {
4296
+ this._modelContextWindows.set(model.id, model.contextWindow);
4297
+ }
4298
+ }
4299
+ return models;
4140
4300
  }
4141
- async listModels() {
4301
+ async listBackends() {
4142
4302
  this.assertNotDisposed();
4143
- const adapter = await this.getOrCreateAdapter();
4144
- return adapter.listModels();
4303
+ return Object.keys(this._backends).map((name) => ({ name }));
4145
4304
  }
4146
4305
  // ── Tools ──────────────────────────────────────────────────
4147
4306
  get registeredTools() {
@@ -4166,37 +4325,46 @@ var ChatRuntime = class {
4166
4325
  if (idx >= 0) this._middleware.splice(idx, 1);
4167
4326
  }
4168
4327
  // ── Context Stats ─────────────────────────────────────────
4169
- getContextStats(sessionId) {
4328
+ async getContextStats(sessionId) {
4170
4329
  const cid = toChatId(sessionId);
4171
4330
  return this._contextStats.get(cid) ?? null;
4172
4331
  }
4173
4332
  // ── Session Subscription ──────────────────────────────────
4174
4333
  onSessionChange(callback) {
4175
- this._sessionListeners.add(callback);
4176
- return () => {
4177
- this._sessionListeners.delete(callback);
4178
- };
4334
+ return this._sessionListeners.add(callback);
4179
4335
  }
4180
4336
  _notifySessionChange() {
4181
- for (const cb of this._sessionListeners) {
4182
- try {
4183
- cb();
4184
- } catch {
4185
- }
4186
- }
4337
+ this._sessionListeners.notify();
4187
4338
  }
4188
4339
  // ── Private Helpers ────────────────────────────────────────
4189
- async getOrCreateAdapter() {
4190
- if (this._activeAdapter) return this._activeAdapter;
4191
- const factory = this._backends[this._currentBackend];
4340
+ async getOrCreateAdapter(backend, credentials) {
4341
+ const key = this.getPoolKey(backend, credentials);
4342
+ const existing = this._adapterPool.get(key);
4343
+ if (existing) return existing;
4344
+ for (const [oldKey, oldAdapter] of this._adapterPool) {
4345
+ if (oldKey.startsWith(backend + ":")) {
4346
+ try {
4347
+ await oldAdapter.dispose();
4348
+ } catch {
4349
+ }
4350
+ this._adapterPool.delete(oldKey);
4351
+ }
4352
+ }
4353
+ const factory = this._backends[backend];
4192
4354
  if (!factory) {
4193
4355
  throw new ChatError(
4194
- `Backend "${this._currentBackend}" not found`,
4356
+ `Backend "${backend}" not found`,
4195
4357
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4196
4358
  );
4197
4359
  }
4198
- this._activeAdapter = await factory();
4199
- return this._activeAdapter;
4360
+ const adapter = await factory(credentials);
4361
+ this._adapterPool.set(key, adapter);
4362
+ return adapter;
4363
+ }
4364
+ getPoolKey(backend, credentials) {
4365
+ const token = credentials.accessToken;
4366
+ const hash = token.length > 16 ? token.slice(0, 8) + token.slice(-8) : token;
4367
+ return `${backend}:${hash}`;
4200
4368
  }
4201
4369
  /** Wrap each tool's execute to inject ToolContext as 2nd argument */
4202
4370
  injectToolContext(tools, context) {
@@ -4228,17 +4396,25 @@ var ChatRuntime = class {
4228
4396
  }
4229
4397
  }
4230
4398
  /** Get or create adapter with retry on connection errors */
4231
- async getOrCreateAdapterWithRetry() {
4399
+ async getOrCreateAdapterWithRetry(backend, credentials) {
4232
4400
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4233
4401
  const delayMs = this._retryConfig?.delayMs ?? 0;
4234
4402
  let lastError;
4235
4403
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4236
4404
  try {
4237
- return await this.getOrCreateAdapter();
4405
+ return await this.getOrCreateAdapter(backend, credentials);
4238
4406
  } catch (err) {
4239
4407
  lastError = err instanceof Error ? err : new Error(String(err));
4240
4408
  if (attempt < maxAttempts) {
4241
- this._activeAdapter = null;
4409
+ const key = this.getPoolKey(backend, credentials);
4410
+ const old = this._adapterPool.get(key);
4411
+ if (old) {
4412
+ try {
4413
+ await old.dispose();
4414
+ } catch {
4415
+ }
4416
+ }
4417
+ this._adapterPool.delete(key);
4242
4418
  await delay(delayMs);
4243
4419
  }
4244
4420
  }
@@ -4251,7 +4427,7 @@ var ChatRuntime = class {
4251
4427
  * retries with a fresh adapter. Once first event is received,
4252
4428
  * the stream is committed (no more retries).
4253
4429
  */
4254
- async createStreamWithRetry(adapter, session, message, options) {
4430
+ async createStreamWithRetry(adapter, session, message, options, backend, credentials) {
4255
4431
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4256
4432
  const delayMs = this._retryConfig?.delayMs ?? 0;
4257
4433
  let lastError;
@@ -4272,13 +4448,14 @@ var ChatRuntime = class {
4272
4448
  } catch (err) {
4273
4449
  lastError = err instanceof Error ? err : new Error(String(err));
4274
4450
  if (attempt < maxAttempts) {
4275
- if (this._activeAdapter) {
4276
- await this._activeAdapter.dispose().catch(() => {
4277
- });
4451
+ try {
4452
+ await currentAdapter.dispose();
4453
+ } catch {
4278
4454
  }
4279
- this._activeAdapter = null;
4455
+ const key = this.getPoolKey(backend, credentials);
4456
+ this._adapterPool.delete(key);
4280
4457
  await delay(delayMs);
4281
- currentAdapter = await this.getOrCreateAdapter();
4458
+ currentAdapter = await this.getOrCreateAdapter(backend, credentials);
4282
4459
  }
4283
4460
  }
4284
4461
  }
@@ -4286,16 +4463,17 @@ var ChatRuntime = class {
4286
4463
  }
4287
4464
  };
4288
4465
  function delay(ms) {
4289
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4466
+ return new Promise((resolve) => setTimeout(resolve, ms));
4290
4467
  }
4291
4468
  function createChatRuntime(options) {
4292
4469
  return new ChatRuntime(options);
4293
4470
  }
4294
4471
 
4295
4472
  // src/chat/storage.ts
4473
+ init_errors2();
4296
4474
  init_errors();
4297
4475
  var StorageError = class extends AgentSDKError {
4298
- /** Machine-readable error code */
4476
+ /** Machine-readable error code from the unified ErrorCode enum */
4299
4477
  code;
4300
4478
  constructor(message, code) {
4301
4479
  super(message);
@@ -4332,7 +4510,7 @@ var InMemoryStorage = class {
4332
4510
  if (this.data.has(key)) {
4333
4511
  throw new StorageError(
4334
4512
  `Item with key "${key}" already exists`,
4335
- "DUPLICATE_KEY"
4513
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4336
4514
  );
4337
4515
  }
4338
4516
  this.data.set(key, structuredClone(item));
@@ -4342,7 +4520,7 @@ var InMemoryStorage = class {
4342
4520
  if (!this.data.has(key)) {
4343
4521
  throw new StorageError(
4344
4522
  `Item with key "${key}" not found`,
4345
- "NOT_FOUND"
4523
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4346
4524
  );
4347
4525
  }
4348
4526
  this.data.set(key, structuredClone(item));
@@ -4352,7 +4530,7 @@ var InMemoryStorage = class {
4352
4530
  if (!this.data.has(key)) {
4353
4531
  throw new StorageError(
4354
4532
  `Item with key "${key}" not found`,
4355
- "NOT_FOUND"
4533
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4356
4534
  );
4357
4535
  }
4358
4536
  this.data.delete(key);
@@ -4417,7 +4595,7 @@ var FileStorage = class {
4417
4595
  if (fs.existsSync(filePath)) {
4418
4596
  throw new StorageError(
4419
4597
  `Item with key "${key}" already exists`,
4420
- "DUPLICATE_KEY"
4598
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4421
4599
  );
4422
4600
  }
4423
4601
  this.writeFile(filePath, item);
@@ -4428,7 +4606,7 @@ var FileStorage = class {
4428
4606
  if (!fs.existsSync(filePath)) {
4429
4607
  throw new StorageError(
4430
4608
  `Item with key "${key}" not found`,
4431
- "NOT_FOUND"
4609
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4432
4610
  );
4433
4611
  }
4434
4612
  this.writeFile(filePath, item);
@@ -4439,7 +4617,7 @@ var FileStorage = class {
4439
4617
  if (!fs.existsSync(filePath)) {
4440
4618
  throw new StorageError(
4441
4619
  `Item with key "${key}" not found`,
4442
- "NOT_FOUND"
4620
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4443
4621
  );
4444
4622
  }
4445
4623
  fs.unlinkSync(filePath);
@@ -4485,12 +4663,12 @@ var FileStorage = class {
4485
4663
  if (error instanceof SyntaxError) {
4486
4664
  throw new StorageError(
4487
4665
  `Failed to parse file: ${filePath}`,
4488
- "SERIALIZATION_ERROR"
4666
+ "STORAGE_SERIALIZATION_ERROR" /* STORAGE_SERIALIZATION_ERROR */
4489
4667
  );
4490
4668
  }
4491
4669
  throw new StorageError(
4492
4670
  `Failed to read file: ${filePath}`,
4493
- "IO_ERROR"
4671
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4494
4672
  );
4495
4673
  }
4496
4674
  }
@@ -4501,13 +4679,14 @@ var FileStorage = class {
4501
4679
  } catch {
4502
4680
  throw new StorageError(
4503
4681
  `Failed to write file: ${filePath}`,
4504
- "IO_ERROR"
4682
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4505
4683
  );
4506
4684
  }
4507
4685
  }
4508
4686
  };
4509
4687
 
4510
4688
  // src/chat/sessions.ts
4689
+ init_errors();
4511
4690
  var BaseSessionStore = class {
4512
4691
  constructor(adapter) {
4513
4692
  this.adapter = adapter;
@@ -4546,7 +4725,7 @@ var BaseSessionStore = class {
4546
4725
  async updateTitle(id, title) {
4547
4726
  const session = await this.adapter.get(id);
4548
4727
  if (!session) {
4549
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4728
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4550
4729
  }
4551
4730
  session.title = title;
4552
4731
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4555,7 +4734,7 @@ var BaseSessionStore = class {
4555
4734
  async updateConfig(id, config) {
4556
4735
  const session = await this.adapter.get(id);
4557
4736
  if (!session) {
4558
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4737
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4559
4738
  }
4560
4739
  session.config = { ...session.config, ...config };
4561
4740
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4567,7 +4746,7 @@ var BaseSessionStore = class {
4567
4746
  async appendMessage(sessionId, message) {
4568
4747
  const session = await this.adapter.get(sessionId);
4569
4748
  if (!session) {
4570
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4749
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4571
4750
  }
4572
4751
  session.messages.push(structuredClone(message));
4573
4752
  session.metadata.messageCount = session.messages.length;
@@ -4578,7 +4757,7 @@ var BaseSessionStore = class {
4578
4757
  if (messages.length === 0) return;
4579
4758
  const session = await this.adapter.get(sessionId);
4580
4759
  if (!session) {
4581
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4760
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4582
4761
  }
4583
4762
  for (const msg of messages) {
4584
4763
  session.messages.push(structuredClone(msg));
@@ -4590,7 +4769,7 @@ var BaseSessionStore = class {
4590
4769
  async loadMessages(sessionId, options) {
4591
4770
  const session = await this.adapter.get(sessionId);
4592
4771
  if (!session) {
4593
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4772
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4594
4773
  }
4595
4774
  const total = session.messages.length;
4596
4775
  const offset = options?.offset ?? 0;
@@ -4602,24 +4781,6 @@ var BaseSessionStore = class {
4602
4781
  hasMore: offset + limit < total
4603
4782
  };
4604
4783
  }
4605
- async archiveSession(id) {
4606
- const session = await this.adapter.get(id);
4607
- if (!session) {
4608
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4609
- }
4610
- session.status = "archived";
4611
- session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4612
- await this.adapter.update(id, session);
4613
- }
4614
- async unarchiveSession(id) {
4615
- const session = await this.adapter.get(id);
4616
- if (!session) {
4617
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4618
- }
4619
- session.status = "active";
4620
- session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4621
- await this.adapter.update(id, session);
4622
- }
4623
4784
  async searchSessions(options) {
4624
4785
  const query = options.query.toLowerCase();
4625
4786
  const limit = options.limit ?? 20;
@@ -4641,15 +4802,6 @@ var BaseSessionStore = class {
4641
4802
  async clear() {
4642
4803
  return this.adapter.clear();
4643
4804
  }
4644
- // ── Deprecated Aliases ──────────────────────────────────────
4645
- /** @deprecated Use `appendMessage()` instead */
4646
- async addMessage(sessionId, message) {
4647
- return this.appendMessage(sessionId, message);
4648
- }
4649
- /** @deprecated Use `loadMessages()` instead */
4650
- async getMessages(sessionId, options) {
4651
- return this.loadMessages(sessionId, options);
4652
- }
4653
4805
  };
4654
4806
  var InMemorySessionStore = class extends BaseSessionStore {
4655
4807
  constructor() {
@@ -4662,28 +4814,59 @@ var FileSessionStore = class extends BaseSessionStore {
4662
4814
  }
4663
4815
  };
4664
4816
 
4817
+ // src/chat/backends/types.ts
4818
+ function isResumableBackend(adapter) {
4819
+ return "canResume" in adapter && typeof adapter.canResume === "function";
4820
+ }
4821
+
4665
4822
  // src/chat/backends/base.ts
4666
4823
  var BaseBackendAdapter = class {
4667
4824
  name;
4668
- _agentService;
4669
- _agent = null;
4825
+ _agentService = null;
4826
+ _agentServiceFactory = null;
4670
4827
  _disposed = false;
4671
4828
  _agentConfig;
4672
4829
  _ownsService;
4830
+ // Agent lifecycle: tracks current agent and the model it was created with.
4831
+ // For persistent sessions, reused across calls when model matches.
4832
+ // For non-persistent, recreated every call.
4833
+ _currentAgent = null;
4673
4834
  constructor(name, options) {
4674
4835
  this.name = name;
4675
4836
  this._agentConfig = options.agentConfig;
4676
4837
  if (options.agentService) {
4677
4838
  this._agentService = options.agentService;
4678
4839
  this._ownsService = false;
4840
+ } else if (options.agentServiceFactory) {
4841
+ this._agentServiceFactory = options.agentServiceFactory;
4842
+ this._ownsService = true;
4679
4843
  } else {
4680
4844
  this._agentService = this.createService();
4681
4845
  this._ownsService = true;
4682
4846
  }
4683
4847
  }
4684
4848
  get agentService() {
4849
+ if (!this._agentService) {
4850
+ if (this._agentServiceFactory) {
4851
+ this._agentService = this._agentServiceFactory();
4852
+ this._agentServiceFactory = null;
4853
+ } else {
4854
+ throw new ChatError("Agent service not available", {
4855
+ code: "BACKEND_NOT_INSTALLED" /* BACKEND_NOT_INSTALLED */
4856
+ });
4857
+ }
4858
+ }
4685
4859
  return this._agentService;
4686
4860
  }
4861
+ get currentModel() {
4862
+ return this._agentConfig.model;
4863
+ }
4864
+ /**
4865
+ * @deprecated No-op. Tools are passed per-call via SendMessageOptions.tools.
4866
+ * Kept for backward compatibility with code that calls setTools() directly.
4867
+ */
4868
+ setTools() {
4869
+ }
4687
4870
  async sendMessage(session, message, options) {
4688
4871
  this.assertNotDisposed();
4689
4872
  const events = this.streamMessage(session, message, options);
@@ -4721,9 +4904,13 @@ var BaseBackendAdapter = class {
4721
4904
  */
4722
4905
  async *streamAgentEvents(agent, messages, options) {
4723
4906
  const messageId = createChatId();
4907
+ const model = options?.model ?? this._agentConfig.model ?? "";
4724
4908
  const agentEvents = agent.streamWithContext(messages, {
4909
+ model,
4725
4910
  signal: options?.signal,
4726
- context: options?.context
4911
+ context: options?.context,
4912
+ tools: options?.tools,
4913
+ ...options?.systemPrompt ? { systemMessage: options.systemPrompt } : {}
4727
4914
  });
4728
4915
  yield { type: "message:start", messageId, role: "assistant" };
4729
4916
  let text = "";
@@ -4749,31 +4936,45 @@ var BaseBackendAdapter = class {
4749
4936
  }
4750
4937
  async listModels() {
4751
4938
  this.assertNotDisposed();
4752
- return this._agentService.listModels();
4939
+ return this.agentService.listModels();
4753
4940
  }
4754
4941
  async validate() {
4755
4942
  this.assertNotDisposed();
4756
- return this._agentService.validate();
4943
+ return this.agentService.validate();
4757
4944
  }
4758
4945
  async dispose() {
4759
4946
  if (this._disposed) return;
4760
4947
  this._disposed = true;
4761
- this._agent?.dispose();
4762
- this._agent = null;
4763
- if (this._ownsService) {
4948
+ if (this._currentAgent) {
4949
+ this._currentAgent.instance.dispose();
4950
+ this._currentAgent = null;
4951
+ }
4952
+ if (this._ownsService && this._agentService && typeof this._agentService.dispose === "function") {
4764
4953
  await this._agentService.dispose();
4765
4954
  }
4766
4955
  }
4767
- /** Get or create an agent, applying model override from options */
4956
+ /** Get or create an agent. Model is passed per-call via RunOptions.
4957
+ * Tools are passed per-call via SendMessageOptions — not baked into config.
4958
+ * For persistent sessions, reuses agent when model matches. */
4768
4959
  getOrCreateAgent(options) {
4769
- const config = options?.model ? { ...this._agentConfig, model: options.model } : this._agentConfig;
4770
- if (this._agentConfig.sessionMode === "persistent" && this._agent) {
4771
- return this._agent;
4960
+ const model = options?.model ?? this._agentConfig.model;
4961
+ if (this._agentConfig.sessionMode === "persistent" && this._currentAgent) {
4962
+ if (this._currentAgent.model === model) {
4963
+ return this._currentAgent.instance;
4964
+ }
4965
+ this._currentAgent.instance.dispose();
4966
+ this._currentAgent = null;
4772
4967
  }
4773
- const agent = this._agentService.createAgent(config);
4774
- if (this._agentConfig.sessionMode === "persistent") {
4775
- this._agent = agent;
4968
+ if (this._currentAgent) {
4969
+ this._currentAgent.instance.dispose();
4970
+ this._currentAgent = null;
4776
4971
  }
4972
+ const config = {
4973
+ ...this._agentConfig,
4974
+ ...model !== void 0 && { model }
4975
+ };
4976
+ const agent = this.agentService.createAgent(config);
4977
+ this._currentAgent = { instance: agent, model };
4777
4978
  return agent;
4778
4979
  }
4779
4980
  assertNotDisposed() {
@@ -4798,8 +4999,8 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
4798
4999
  this._copilotOptions = options.copilotOptions;
4799
5000
  }
4800
5001
  createService() {
4801
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4802
- return createAgentService2("copilot", this._copilotOptions);
5002
+ const { createCopilotService: createCopilotService2 } = (init_copilot(), __toCommonJS(copilot_exports));
5003
+ return createCopilotService2(this._copilotOptions || {});
4803
5004
  }
4804
5005
  get backendSessionId() {
4805
5006
  return this._backendSessionId;
@@ -4851,8 +5052,8 @@ var ClaudeChatAdapter = class extends BaseBackendAdapter {
4851
5052
  this._claudeOptions = options.claudeOptions;
4852
5053
  }
4853
5054
  createService() {
4854
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4855
- return createAgentService2("claude", this._claudeOptions);
5055
+ const { createClaudeService: createClaudeService2 } = (init_claude(), __toCommonJS(claude_exports));
5056
+ return createClaudeService2(this._claudeOptions || {});
4856
5057
  }
4857
5058
  get backendSessionId() {
4858
5059
  return this._backendSessionId;
@@ -4899,20 +5100,8 @@ var VercelAIChatAdapter = class extends BaseBackendAdapter {
4899
5100
  this._vercelOptions = options.vercelOptions;
4900
5101
  }
4901
5102
  createService() {
4902
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4903
- return createAgentService2("vercel-ai", this._vercelOptions);
4904
- }
4905
- get backendSessionId() {
4906
- return null;
4907
- }
4908
- canResume() {
4909
- return false;
4910
- }
4911
- async *resume(_session, _backendSessionId, _options) {
4912
- throw new ChatError(
4913
- "Vercel AI adapter does not support session resume (stateless)",
4914
- { code: "PROVIDER_ERROR" /* PROVIDER_ERROR */ }
4915
- );
5103
+ const { createVercelAIService: createVercelAIService2 } = (init_vercel_ai(), __toCommonJS(vercel_ai_exports));
5104
+ return createVercelAIService2(this._vercelOptions || {});
4916
5105
  }
4917
5106
  captureSessionId(_agent) {
4918
5107
  }
@@ -5089,9 +5278,9 @@ var InProcessChatTransport = class {
5089
5278
  send(event) {
5090
5279
  if (!this._open) return;
5091
5280
  if (this._resolve) {
5092
- const resolve2 = this._resolve;
5281
+ const resolve = this._resolve;
5093
5282
  this._resolve = null;
5094
- resolve2({ value: event, done: false });
5283
+ resolve({ value: event, done: false });
5095
5284
  } else {
5096
5285
  this._buffer.push(event);
5097
5286
  }
@@ -5100,9 +5289,9 @@ var InProcessChatTransport = class {
5100
5289
  if (!this._open) return;
5101
5290
  this._open = false;
5102
5291
  if (this._resolve) {
5103
- const resolve2 = this._resolve;
5292
+ const resolve = this._resolve;
5104
5293
  this._resolve = null;
5105
- resolve2({ value: void 0, done: true });
5294
+ resolve({ value: void 0, done: true });
5106
5295
  }
5107
5296
  }
5108
5297
  error(err) {
@@ -5114,9 +5303,9 @@ var InProcessChatTransport = class {
5114
5303
  recoverable: false
5115
5304
  };
5116
5305
  if (this._resolve) {
5117
- const resolve2 = this._resolve;
5306
+ const resolve = this._resolve;
5118
5307
  this._resolve = null;
5119
- resolve2({ value: errorEvent, done: false });
5308
+ resolve({ value: errorEvent, done: false });
5120
5309
  } else {
5121
5310
  this._error = err;
5122
5311
  }
@@ -5141,8 +5330,8 @@ var InProcessChatTransport = class {
5141
5330
  if (!this._open) {
5142
5331
  return Promise.resolve({ value: void 0, done: true });
5143
5332
  }
5144
- return new Promise((resolve2) => {
5145
- this._resolve = resolve2;
5333
+ return new Promise((resolve) => {
5334
+ this._resolve = resolve;
5146
5335
  });
5147
5336
  }
5148
5337
  };
@@ -5315,9 +5504,7 @@ var ChatEventBus = class extends TypedEventEmitter {
5315
5504
 
5316
5505
  exports.BaseBackendAdapter = BaseBackendAdapter;
5317
5506
  exports.ChatError = ChatError;
5318
- exports.ChatErrorCode = ChatErrorCode;
5319
5507
  exports.ChatEventBus = ChatEventBus;
5320
- exports.ChatSDKError = ChatError;
5321
5508
  exports.ClaudeChatAdapter = ClaudeChatAdapter;
5322
5509
  exports.ContextWindowManager = ContextWindowManager;
5323
5510
  exports.CopilotChatAdapter = CopilotChatAdapter;
@@ -5325,6 +5512,7 @@ exports.ExponentialBackoffStrategy = ExponentialBackoffStrategy;
5325
5512
  exports.FileSessionStore = FileSessionStore;
5326
5513
  exports.InMemorySessionStore = InMemorySessionStore;
5327
5514
  exports.InProcessChatTransport = InProcessChatTransport;
5515
+ exports.ListenerSet = ListenerSet;
5328
5516
  exports.MessageAccumulator = MessageAccumulator;
5329
5517
  exports.SSEChatTransport = SSEChatTransport;
5330
5518
  exports.TypedEventEmitter = TypedEventEmitter;
@@ -5335,6 +5523,7 @@ exports.agentEventToChatEvent = agentEventToChatEvent;
5335
5523
  exports.classifyError = classifyError;
5336
5524
  exports.createChatId = createChatId;
5337
5525
  exports.createChatRuntime = createChatRuntime;
5526
+ exports.createTextMessage = createTextMessage;
5338
5527
  exports.estimateTokens = estimateTokens;
5339
5528
  exports.fromAgentMessage = fromAgentMessage;
5340
5529
  exports.getMessageReasoning = getMessageReasoning;
@@ -5345,7 +5534,9 @@ exports.isChatMessage = isChatMessage;
5345
5534
  exports.isChatSession = isChatSession;
5346
5535
  exports.isFilePart = isFilePart;
5347
5536
  exports.isMessagePart = isMessagePart;
5537
+ exports.isObservableSession = isObservableSession;
5348
5538
  exports.isReasoningPart = isReasoningPart;
5539
+ exports.isResumableBackend = isResumableBackend;
5349
5540
  exports.isRetryable = isRetryable;
5350
5541
  exports.isSourcePart = isSourcePart;
5351
5542
  exports.isTextPart = isTextPart;