@witqq/agent-sdk 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/{types-CqvUAYxt.d.ts → agent-C6H2CgJA.d.cts} +139 -102
  2. package/dist/{types-CqvUAYxt.d.cts → agent-F7oB6eKp.d.ts} +139 -102
  3. package/dist/auth/index.cjs +72 -1
  4. package/dist/auth/index.cjs.map +1 -1
  5. package/dist/auth/index.d.cts +21 -154
  6. package/dist/auth/index.d.ts +21 -154
  7. package/dist/auth/index.js +72 -1
  8. package/dist/auth/index.js.map +1 -1
  9. package/dist/backends/claude.cjs +480 -261
  10. package/dist/backends/claude.cjs.map +1 -1
  11. package/dist/backends/claude.d.cts +3 -1
  12. package/dist/backends/claude.d.ts +3 -1
  13. package/dist/backends/claude.js +480 -261
  14. package/dist/backends/claude.js.map +1 -1
  15. package/dist/backends/copilot.cjs +337 -112
  16. package/dist/backends/copilot.cjs.map +1 -1
  17. package/dist/backends/copilot.d.cts +12 -4
  18. package/dist/backends/copilot.d.ts +12 -4
  19. package/dist/backends/copilot.js +337 -112
  20. package/dist/backends/copilot.js.map +1 -1
  21. package/dist/backends/mock-llm.cjs +719 -0
  22. package/dist/backends/mock-llm.cjs.map +1 -0
  23. package/dist/backends/mock-llm.d.cts +37 -0
  24. package/dist/backends/mock-llm.d.ts +37 -0
  25. package/dist/backends/mock-llm.js +717 -0
  26. package/dist/backends/mock-llm.js.map +1 -0
  27. package/dist/backends/vercel-ai.cjs +301 -61
  28. package/dist/backends/vercel-ai.cjs.map +1 -1
  29. package/dist/backends/vercel-ai.d.cts +3 -1
  30. package/dist/backends/vercel-ai.d.ts +3 -1
  31. package/dist/backends/vercel-ai.js +301 -61
  32. package/dist/backends/vercel-ai.js.map +1 -1
  33. package/dist/backends-Cno0gZjy.d.cts +114 -0
  34. package/dist/backends-Cno0gZjy.d.ts +114 -0
  35. package/dist/chat/accumulator.cjs +1 -1
  36. package/dist/chat/accumulator.cjs.map +1 -1
  37. package/dist/chat/accumulator.d.cts +5 -2
  38. package/dist/chat/accumulator.d.ts +5 -2
  39. package/dist/chat/accumulator.js +1 -1
  40. package/dist/chat/accumulator.js.map +1 -1
  41. package/dist/chat/backends.cjs +1084 -821
  42. package/dist/chat/backends.cjs.map +1 -1
  43. package/dist/chat/backends.d.cts +10 -6
  44. package/dist/chat/backends.d.ts +10 -6
  45. package/dist/chat/backends.js +1082 -800
  46. package/dist/chat/backends.js.map +1 -1
  47. package/dist/chat/context.cjs +50 -0
  48. package/dist/chat/context.cjs.map +1 -1
  49. package/dist/chat/context.d.cts +27 -3
  50. package/dist/chat/context.d.ts +27 -3
  51. package/dist/chat/context.js +50 -0
  52. package/dist/chat/context.js.map +1 -1
  53. package/dist/chat/core.cjs +60 -27
  54. package/dist/chat/core.cjs.map +1 -1
  55. package/dist/chat/core.d.cts +41 -382
  56. package/dist/chat/core.d.ts +41 -382
  57. package/dist/chat/core.js +58 -28
  58. package/dist/chat/core.js.map +1 -1
  59. package/dist/chat/errors.cjs +48 -26
  60. package/dist/chat/errors.cjs.map +1 -1
  61. package/dist/chat/errors.d.cts +6 -31
  62. package/dist/chat/errors.d.ts +6 -31
  63. package/dist/chat/errors.js +48 -25
  64. package/dist/chat/errors.js.map +1 -1
  65. package/dist/chat/events.cjs.map +1 -1
  66. package/dist/chat/events.d.cts +6 -2
  67. package/dist/chat/events.d.ts +6 -2
  68. package/dist/chat/events.js.map +1 -1
  69. package/dist/chat/index.cjs +1612 -1125
  70. package/dist/chat/index.cjs.map +1 -1
  71. package/dist/chat/index.d.cts +35 -10
  72. package/dist/chat/index.d.ts +35 -10
  73. package/dist/chat/index.js +1600 -1097
  74. package/dist/chat/index.js.map +1 -1
  75. package/dist/chat/react/theme.css +2517 -0
  76. package/dist/chat/react.cjs +2212 -1158
  77. package/dist/chat/react.cjs.map +1 -1
  78. package/dist/chat/react.d.cts +665 -122
  79. package/dist/chat/react.d.ts +665 -122
  80. package/dist/chat/react.js +2191 -1156
  81. package/dist/chat/react.js.map +1 -1
  82. package/dist/chat/runtime.cjs +405 -186
  83. package/dist/chat/runtime.cjs.map +1 -1
  84. package/dist/chat/runtime.d.cts +92 -28
  85. package/dist/chat/runtime.d.ts +92 -28
  86. package/dist/chat/runtime.js +405 -186
  87. package/dist/chat/runtime.js.map +1 -1
  88. package/dist/chat/server.cjs +2247 -212
  89. package/dist/chat/server.cjs.map +1 -1
  90. package/dist/chat/server.d.cts +451 -90
  91. package/dist/chat/server.d.ts +451 -90
  92. package/dist/chat/server.js +2234 -213
  93. package/dist/chat/server.js.map +1 -1
  94. package/dist/chat/sessions.cjs +64 -66
  95. package/dist/chat/sessions.cjs.map +1 -1
  96. package/dist/chat/sessions.d.cts +37 -118
  97. package/dist/chat/sessions.d.ts +37 -118
  98. package/dist/chat/sessions.js +65 -67
  99. package/dist/chat/sessions.js.map +1 -1
  100. package/dist/chat/sqlite.cjs +536 -0
  101. package/dist/chat/sqlite.cjs.map +1 -0
  102. package/dist/chat/sqlite.d.cts +164 -0
  103. package/dist/chat/sqlite.d.ts +164 -0
  104. package/dist/chat/sqlite.js +527 -0
  105. package/dist/chat/sqlite.js.map +1 -0
  106. package/dist/chat/state.cjs +14 -1
  107. package/dist/chat/state.cjs.map +1 -1
  108. package/dist/chat/state.d.cts +5 -2
  109. package/dist/chat/state.d.ts +5 -2
  110. package/dist/chat/state.js +14 -1
  111. package/dist/chat/state.js.map +1 -1
  112. package/dist/chat/storage.cjs +58 -33
  113. package/dist/chat/storage.cjs.map +1 -1
  114. package/dist/chat/storage.d.cts +18 -8
  115. package/dist/chat/storage.d.ts +18 -8
  116. package/dist/chat/storage.js +59 -34
  117. package/dist/chat/storage.js.map +1 -1
  118. package/dist/errors-C-so0M4t.d.cts +33 -0
  119. package/dist/errors-C-so0M4t.d.ts +33 -0
  120. package/dist/errors-CmVvczxZ.d.cts +28 -0
  121. package/dist/errors-CmVvczxZ.d.ts +28 -0
  122. package/dist/{in-process-transport-C2oPTYs6.d.ts → in-process-transport-7EIit9Xk.d.ts} +72 -33
  123. package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-Ct9YcX8I.d.cts} +72 -33
  124. package/dist/index.cjs +354 -60
  125. package/dist/index.cjs.map +1 -1
  126. package/dist/index.d.cts +294 -123
  127. package/dist/index.d.ts +294 -123
  128. package/dist/index.js +347 -60
  129. package/dist/index.js.map +1 -1
  130. package/dist/provider-types-PTSlRPNB.d.cts +39 -0
  131. package/dist/provider-types-PTSlRPNB.d.ts +39 -0
  132. package/dist/refresh-manager-B81PpYBr.d.cts +153 -0
  133. package/dist/refresh-manager-Dlv_iNZi.d.ts +153 -0
  134. package/dist/testing.cjs +1107 -0
  135. package/dist/testing.cjs.map +1 -0
  136. package/dist/testing.d.cts +144 -0
  137. package/dist/testing.d.ts +144 -0
  138. package/dist/testing.js +1101 -0
  139. package/dist/testing.js.map +1 -0
  140. package/dist/token-store-CSUBgYwn.d.ts +48 -0
  141. package/dist/token-store-CuC4hB9Z.d.cts +48 -0
  142. package/dist/{transport-DX1Nhm4N.d.cts → transport-DLWCN18G.d.cts} +5 -4
  143. package/dist/{transport-D1OaUgRk.d.ts → transport-DsuS-GeM.d.ts} +5 -4
  144. package/dist/{types-CGF7AEX1.d.cts → types-4vbcmPTp.d.cts} +4 -2
  145. package/dist/{types-Bh5AhqD-.d.ts → types-BxggH0Yh.d.ts} +4 -2
  146. package/dist/types-DgtI1hzh.d.ts +364 -0
  147. package/dist/types-DkSXALKg.d.cts +364 -0
  148. package/package.json +41 -5
  149. package/LICENSE +0 -21
  150. package/README.md +0 -948
  151. package/dist/errors-BDLbNu9w.d.cts +0 -13
  152. package/dist/errors-BDLbNu9w.d.ts +0 -13
  153. package/dist/types-DLZzlJxt.d.ts +0 -39
  154. package/dist/types-tE0CXwBl.d.cts +0 -39
@@ -1,30 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var fs = require('fs');
4
+ var promises = require('fs/promises');
4
5
  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
6
 
29
7
  var __defProp = Object.defineProperty;
30
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -47,16 +25,100 @@ var __copyProps = (to, from, except, desc) => {
47
25
  };
48
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
49
27
 
50
- // src/errors.ts
51
- var AgentSDKError, ReentrancyError, DisposedError, BackendNotFoundError, BackendAlreadyRegisteredError, SubprocessError, DependencyError, AbortError, ToolExecutionError, StructuredOutputError;
28
+ // src/types/errors.ts
29
+ function isRecoverableErrorCode(code) {
30
+ return RECOVERABLE_CODES.has(code);
31
+ }
32
+ function classifyAgentError(error) {
33
+ const msg = (error instanceof Error ? error.message : error).toLowerCase();
34
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("timedout") || msg.includes("etimedout")) {
35
+ return "TIMEOUT" /* TIMEOUT */;
36
+ }
37
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429") || msg.includes("too many requests")) {
38
+ return "RATE_LIMIT" /* RATE_LIMIT */;
39
+ }
40
+ if (msg.includes("unauthorized") || msg.includes("401") || msg.includes("auth") && (msg.includes("expired") || msg.includes("invalid") || msg.includes("denied") || msg.includes("failed"))) {
41
+ return "AUTH_EXPIRED" /* AUTH_EXPIRED */;
42
+ }
43
+ if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("network") || msg.includes("fetch failed") || msg.includes("socket hang up")) {
44
+ return "NETWORK" /* NETWORK */;
45
+ }
46
+ if (msg.includes("subprocess") || msg.includes("process exited") || msg.includes("spawn") || msg.includes("enoent") || msg.includes("killed")) {
47
+ return "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */;
48
+ }
49
+ if (msg.includes("abort") || msg.includes("cancel")) {
50
+ return "ABORTED" /* ABORTED */;
51
+ }
52
+ 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")) {
53
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
54
+ }
55
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
56
+ }
57
+ exports.ErrorCode = void 0; var RECOVERABLE_CODES;
52
58
  var init_errors = __esm({
59
+ "src/types/errors.ts"() {
60
+ exports.ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
61
+ ErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
62
+ ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
63
+ ErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
64
+ ErrorCode2["NETWORK"] = "NETWORK";
65
+ ErrorCode2["TIMEOUT"] = "TIMEOUT";
66
+ ErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
67
+ ErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
68
+ ErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
69
+ ErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
70
+ ErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
71
+ ErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
72
+ ErrorCode2["REENTRANCY"] = "REENTRANCY";
73
+ ErrorCode2["DISPOSED"] = "DISPOSED";
74
+ ErrorCode2["ABORTED"] = "ABORTED";
75
+ ErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
76
+ ErrorCode2["DEPENDENCY_MISSING"] = "DEPENDENCY_MISSING";
77
+ ErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
78
+ ErrorCode2["TOOL_EXECUTION"] = "TOOL_EXECUTION";
79
+ ErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
80
+ ErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
81
+ ErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
82
+ ErrorCode2["PROVIDER_NOT_FOUND"] = "PROVIDER_NOT_FOUND";
83
+ ErrorCode2["AUTH_REQUIRED"] = "AUTH_REQUIRED";
84
+ ErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
85
+ ErrorCode2["STORAGE_NOT_FOUND"] = "STORAGE_NOT_FOUND";
86
+ ErrorCode2["STORAGE_DUPLICATE_KEY"] = "STORAGE_DUPLICATE_KEY";
87
+ ErrorCode2["STORAGE_IO_ERROR"] = "STORAGE_IO_ERROR";
88
+ ErrorCode2["STORAGE_SERIALIZATION_ERROR"] = "STORAGE_SERIALIZATION_ERROR";
89
+ return ErrorCode2;
90
+ })(exports.ErrorCode || {});
91
+ RECOVERABLE_CODES = /* @__PURE__ */ new Set([
92
+ "TIMEOUT" /* TIMEOUT */,
93
+ "RATE_LIMIT" /* RATE_LIMIT */,
94
+ "NETWORK" /* NETWORK */,
95
+ "TOOL_EXECUTION" /* TOOL_EXECUTION */,
96
+ "MODEL_OVERLOADED" /* MODEL_OVERLOADED */,
97
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
98
+ ]);
99
+ }
100
+ });
101
+
102
+ // src/errors.ts
103
+ var AgentSDKError, ReentrancyError, DisposedError, SubprocessError, DependencyError, AbortError, ToolExecutionError, ActivityTimeoutError;
104
+ var init_errors2 = __esm({
53
105
  "src/errors.ts"() {
106
+ init_errors();
54
107
  AgentSDKError = class extends Error {
55
108
  /** @internal Marker for cross-bundle identity checks */
56
109
  _agentSDKError = true;
110
+ /** Machine-readable error code. Prefer values from the ErrorCode enum. */
111
+ code;
112
+ /** Whether this error is safe to retry */
113
+ retryable;
114
+ /** HTTP status code hint for error classification */
115
+ httpStatus;
57
116
  constructor(message, options) {
58
117
  super(message, options);
59
118
  this.name = "AgentSDKError";
119
+ this.code = options?.code;
120
+ this.retryable = options?.retryable ?? false;
121
+ this.httpStatus = options?.httpStatus;
60
122
  }
61
123
  /** Check if an error is an AgentSDKError (works across bundled copies) */
62
124
  static is(error) {
@@ -65,83 +127,84 @@ var init_errors = __esm({
65
127
  };
66
128
  ReentrancyError = class extends AgentSDKError {
67
129
  constructor() {
68
- super("Agent is already running. Await the current run before starting another.");
130
+ super("Agent is already running. Await the current run before starting another.", {
131
+ code: "REENTRANCY" /* REENTRANCY */
132
+ });
69
133
  this.name = "ReentrancyError";
70
134
  }
71
135
  };
72
136
  DisposedError = class extends AgentSDKError {
73
137
  constructor(entity) {
74
- super(`${entity} has been disposed and cannot be used.`);
138
+ super(`${entity} has been disposed and cannot be used.`, {
139
+ code: "DISPOSED" /* DISPOSED */
140
+ });
75
141
  this.name = "DisposedError";
76
142
  }
77
143
  };
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
144
  SubprocessError = class extends AgentSDKError {
93
145
  constructor(message, options) {
94
- super(message, options);
146
+ super(message, { ...options, code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */ });
95
147
  this.name = "SubprocessError";
96
148
  }
97
149
  };
98
150
  DependencyError = class extends AgentSDKError {
99
151
  packageName;
100
152
  constructor(packageName) {
101
- super(`${packageName} is not installed. Install it: npm install ${packageName}`);
153
+ super(`${packageName} is not installed. Install it: npm install ${packageName}`, {
154
+ code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */
155
+ });
102
156
  this.name = "DependencyError";
103
157
  this.packageName = packageName;
104
158
  }
105
159
  };
106
160
  AbortError = class extends AgentSDKError {
107
161
  constructor() {
108
- super("Agent run was aborted.");
162
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
109
163
  this.name = "AbortError";
110
164
  }
111
165
  };
112
166
  ToolExecutionError = class extends AgentSDKError {
113
167
  toolName;
114
168
  constructor(toolName, message, options) {
115
- super(`Tool "${toolName}" failed: ${message}`, options);
169
+ super(`Tool "${toolName}" failed: ${message}`, { ...options, code: "TOOL_EXECUTION" /* TOOL_EXECUTION */ });
116
170
  this.name = "ToolExecutionError";
117
171
  this.toolName = toolName;
118
172
  }
119
173
  };
120
- StructuredOutputError = class extends AgentSDKError {
121
- constructor(message, options) {
122
- super(`Structured output error: ${message}`, options);
123
- this.name = "StructuredOutputError";
174
+ ActivityTimeoutError = class extends AgentSDKError {
175
+ constructor(timeoutMs) {
176
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
177
+ code: "TIMEOUT" /* TIMEOUT */,
178
+ retryable: true
179
+ });
180
+ this.name = "ActivityTimeoutError";
124
181
  }
125
182
  };
126
183
  }
127
184
  });
128
185
 
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
- }
186
+ // src/types/guards.ts
139
187
  function getTextContent(content) {
140
188
  if (typeof content === "string") return content;
141
189
  return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
142
190
  }
191
+ var init_guards = __esm({
192
+ "src/types/guards.ts"() {
193
+ }
194
+ });
195
+
196
+ // src/types/index.ts
143
197
  var init_types = __esm({
198
+ "src/types/index.ts"() {
199
+ init_errors();
200
+ init_guards();
201
+ }
202
+ });
203
+
204
+ // src/types.ts
205
+ var init_types2 = __esm({
144
206
  "src/types.ts"() {
207
+ init_types();
145
208
  }
146
209
  });
147
210
 
@@ -149,12 +212,15 @@ var init_types = __esm({
149
212
  var BaseAgent;
150
213
  var init_base_agent = __esm({
151
214
  "src/base-agent.ts"() {
215
+ init_errors2();
216
+ init_errors2();
152
217
  init_errors();
153
218
  BaseAgent = class {
154
219
  state = "idle";
155
220
  abortController = null;
156
221
  config;
157
222
  _cleanupExternalSignal = null;
223
+ _streamMiddleware = [];
158
224
  /** CLI session ID for persistent mode. Override in backends that support it. */
159
225
  get sessionId() {
160
226
  return void 0;
@@ -170,8 +236,11 @@ var init_base_agent = __esm({
170
236
  this.state = "running";
171
237
  try {
172
238
  const messages = [{ role: "user", content: prompt }];
173
- const result = await this.executeRun(messages, options, ac.signal);
174
- this.enrichAndNotifyUsage(result);
239
+ const result = await this.withRetry(
240
+ () => this.executeRun(messages, options, ac.signal),
241
+ options
242
+ );
243
+ this.enrichAndNotifyUsage(result, options);
175
244
  return result;
176
245
  } finally {
177
246
  this.cleanupRun();
@@ -183,8 +252,11 @@ var init_base_agent = __esm({
183
252
  const ac = this.createAbortController(options?.signal);
184
253
  this.state = "running";
185
254
  try {
186
- const result = await this.executeRun(messages, options, ac.signal);
187
- this.enrichAndNotifyUsage(result);
255
+ const result = await this.withRetry(
256
+ () => this.executeRun(messages, options, ac.signal),
257
+ options
258
+ );
259
+ this.enrichAndNotifyUsage(result, options);
188
260
  return result;
189
261
  } finally {
190
262
  this.cleanupRun();
@@ -197,13 +269,11 @@ var init_base_agent = __esm({
197
269
  this.state = "running";
198
270
  try {
199
271
  const messages = [{ role: "user", content: prompt }];
200
- const result = await this.executeRunStructured(
201
- messages,
202
- schema,
203
- options,
204
- ac.signal
272
+ const result = await this.withRetry(
273
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
274
+ options
205
275
  );
206
- this.enrichAndNotifyUsage(result);
276
+ this.enrichAndNotifyUsage(result, options);
207
277
  return result;
208
278
  } finally {
209
279
  this.cleanupRun();
@@ -216,8 +286,10 @@ var init_base_agent = __esm({
216
286
  this.state = "streaming";
217
287
  try {
218
288
  const messages = [{ role: "user", content: prompt }];
219
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
220
- yield* this.heartbeatStream(enriched);
289
+ yield* this.streamWithRetry(
290
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
291
+ options
292
+ );
221
293
  } finally {
222
294
  this.cleanupRun();
223
295
  }
@@ -228,12 +300,37 @@ var init_base_agent = __esm({
228
300
  const ac = this.createAbortController(options?.signal);
229
301
  this.state = "streaming";
230
302
  try {
231
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
232
- yield* this.heartbeatStream(enriched);
303
+ yield* this.streamWithRetry(
304
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
305
+ options
306
+ );
233
307
  } finally {
234
308
  this.cleanupRun();
235
309
  }
236
310
  }
311
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
312
+ addStreamMiddleware(middleware) {
313
+ this.guardDisposed();
314
+ this._streamMiddleware.push(middleware);
315
+ }
316
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
317
+ async *applyStreamPipeline(source, options, ac) {
318
+ let stream = this.enrichStream(source, options);
319
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
320
+ stream = this.heartbeatStream(stream);
321
+ if (this._streamMiddleware.length > 0) {
322
+ const ctx = {
323
+ model: options.model,
324
+ backend: this.backendName,
325
+ abortController: ac,
326
+ config: Object.freeze({ ...this.config })
327
+ };
328
+ for (const mw of this._streamMiddleware) {
329
+ stream = mw(stream, ctx);
330
+ }
331
+ }
332
+ yield* stream;
333
+ }
237
334
  abort() {
238
335
  if (this.abortController) {
239
336
  this.abortController.abort();
@@ -256,26 +353,109 @@ var init_base_agent = __esm({
256
353
  this.abort();
257
354
  this.state = "disposed";
258
355
  }
356
+ // ─── Retry Logic ─────────────────────────────────────────────
357
+ /** Check if an error should be retried given the retry configuration. */
358
+ isRetryableError(error, retry) {
359
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
360
+ return false;
361
+ }
362
+ if (AgentSDKError.is(error)) {
363
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
364
+ return retry.retryableErrors.includes(error.code);
365
+ }
366
+ if (error.retryable) return true;
367
+ if (error.code) return isRecoverableErrorCode(error.code);
368
+ }
369
+ return false;
370
+ }
371
+ /** Execute a function with retry logic per RetryConfig. */
372
+ async withRetry(fn, options) {
373
+ const retry = options?.retry;
374
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
375
+ return fn();
376
+ }
377
+ const maxRetries = retry.maxRetries;
378
+ const initialDelay = retry.initialDelayMs ?? 1e3;
379
+ const multiplier = retry.backoffMultiplier ?? 2;
380
+ let lastError;
381
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
382
+ try {
383
+ return await fn();
384
+ } catch (err) {
385
+ lastError = err;
386
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
387
+ throw err;
388
+ }
389
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
390
+ await new Promise((resolve) => setTimeout(resolve, delay2));
391
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
392
+ throw err;
393
+ }
394
+ }
395
+ }
396
+ throw lastError;
397
+ }
398
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
399
+ async *streamWithRetry(factory, options) {
400
+ const retry = options?.retry;
401
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
402
+ yield* factory();
403
+ return;
404
+ }
405
+ const maxRetries = retry.maxRetries;
406
+ const initialDelay = retry.initialDelayMs ?? 1e3;
407
+ const multiplier = retry.backoffMultiplier ?? 2;
408
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
409
+ try {
410
+ const stream = factory();
411
+ const iterator = stream[Symbol.asyncIterator]();
412
+ const first = await iterator.next();
413
+ if (first.done) return;
414
+ yield first.value;
415
+ while (true) {
416
+ const next = await iterator.next();
417
+ if (next.done) break;
418
+ yield next.value;
419
+ }
420
+ return;
421
+ } catch (err) {
422
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
423
+ throw err;
424
+ }
425
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
426
+ await new Promise((resolve) => setTimeout(resolve, delay2));
427
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
428
+ throw err;
429
+ }
430
+ }
431
+ }
432
+ }
433
+ // ─── CallOptions Resolution ──────────────────────────────────
434
+ /** Resolve tools to use for this call (per-call override > config default) */
435
+ resolveTools(options) {
436
+ return options?.tools ?? this.config.tools ?? [];
437
+ }
259
438
  // ─── Usage Enrichment ───────────────────────────────────────────
260
439
  /** Enrich result usage with model/backend and fire onUsage callback */
261
- enrichAndNotifyUsage(result) {
440
+ enrichAndNotifyUsage(result, options) {
262
441
  if (result.usage) {
263
442
  result.usage = {
264
443
  ...result.usage,
265
- model: this.config.model,
444
+ model: options.model,
266
445
  backend: this.backendName
267
446
  };
268
447
  this.callOnUsage(result.usage);
269
448
  }
270
449
  }
271
450
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
272
- async *enrichStream(source) {
451
+ async *enrichStream(source, options) {
452
+ const model = options.model;
273
453
  for await (const event of source) {
274
454
  if (event.type === "usage_update") {
275
455
  const usage = {
276
456
  promptTokens: event.promptTokens,
277
457
  completionTokens: event.completionTokens,
278
- model: this.config.model,
458
+ model,
279
459
  backend: this.backendName
280
460
  };
281
461
  this.callOnUsage(usage);
@@ -311,9 +491,9 @@ var init_base_agent = __esm({
311
491
  let heartbeatResolve = null;
312
492
  const timer = setInterval(() => {
313
493
  if (heartbeatResolve) {
314
- const resolve2 = heartbeatResolve;
494
+ const resolve = heartbeatResolve;
315
495
  heartbeatResolve = null;
316
- resolve2();
496
+ resolve();
317
497
  }
318
498
  }, interval);
319
499
  try {
@@ -321,8 +501,8 @@ var init_base_agent = __esm({
321
501
  if (!pendingEvent) {
322
502
  pendingEvent = iterator.next();
323
503
  }
324
- const heartbeatPromise = new Promise((resolve2) => {
325
- heartbeatResolve = resolve2;
504
+ const heartbeatPromise = new Promise((resolve) => {
505
+ heartbeatResolve = resolve;
326
506
  });
327
507
  const eventDone = pendingEvent.then(
328
508
  (r) => ({ kind: "event", result: r })
@@ -345,6 +525,35 @@ var init_base_agent = __esm({
345
525
  heartbeatResolve = null;
346
526
  }
347
527
  }
528
+ // ─── Activity Timeout ────────────────────────────────────────
529
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
530
+ * When timeoutMs is not set, passes through directly. */
531
+ async *activityTimeoutStream(source, timeoutMs, ac) {
532
+ if (!timeoutMs || timeoutMs <= 0) {
533
+ yield* source;
534
+ return;
535
+ }
536
+ const iterator = source[Symbol.asyncIterator]();
537
+ let timerId;
538
+ try {
539
+ while (true) {
540
+ const timeoutPromise = new Promise((_, reject) => {
541
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
542
+ });
543
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
544
+ clearTimeout(timerId);
545
+ if (result.done) break;
546
+ yield result.value;
547
+ }
548
+ } catch (err) {
549
+ if (err instanceof ActivityTimeoutError) {
550
+ ac.abort(err);
551
+ }
552
+ throw err;
553
+ } finally {
554
+ clearTimeout(timerId);
555
+ }
556
+ }
348
557
  // ─── Guards ───────────────────────────────────────────────────
349
558
  guardReentrancy() {
350
559
  if (this.state === "running" || this.state === "streaming") {
@@ -449,6 +658,66 @@ var init_schema = __esm({
449
658
  }
450
659
  });
451
660
 
661
+ // src/backends/shared.ts
662
+ function extractLastUserPrompt(messages) {
663
+ for (let i = messages.length - 1; i >= 0; i--) {
664
+ const msg = messages[i];
665
+ if (msg.role === "user") {
666
+ return getTextContent(msg.content);
667
+ }
668
+ }
669
+ return "";
670
+ }
671
+ function serializeToolCall(tc) {
672
+ const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
673
+ return ` Tool call: ${tc.name}(${args})`;
674
+ }
675
+ function serializeToolResult(tr) {
676
+ const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
677
+ const prefix = tr.isError ? "[ERROR] " : "";
678
+ return ` ${tr.name} \u2192 ${prefix}${result}`;
679
+ }
680
+ function buildContextualPrompt(messages) {
681
+ if (messages.length <= 1) {
682
+ return extractLastUserPrompt(messages);
683
+ }
684
+ const history = messages.slice(0, -1).map((msg) => {
685
+ if (msg.role === "user") {
686
+ return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
687
+ }
688
+ if (msg.role === "tool" && msg.toolResults) {
689
+ const results = msg.toolResults.map(serializeToolResult).join("\n");
690
+ return `Tool results:
691
+ ${results}`;
692
+ }
693
+ if (msg.role === "assistant") {
694
+ const parts = [];
695
+ const thinking = msg.thinking;
696
+ if (thinking) {
697
+ parts.push(`[reasoning: ${thinking}]`);
698
+ }
699
+ const text2 = msg.content ? getTextContent(msg.content) : "";
700
+ if (text2) parts.push(text2);
701
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
702
+ parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
703
+ }
704
+ return `Assistant: ${parts.join("\n")}`;
705
+ }
706
+ const text = msg.content ? getTextContent(msg.content) : "";
707
+ return `${msg.role}: ${text}`;
708
+ }).join("\n");
709
+ const lastPrompt = extractLastUserPrompt(messages);
710
+ return `Conversation history:
711
+ ${history}
712
+
713
+ User: ${lastPrompt}`;
714
+ }
715
+ var init_shared = __esm({
716
+ "src/backends/shared.ts"() {
717
+ init_types2();
718
+ }
719
+ });
720
+
452
721
  // src/backends/copilot.ts
453
722
  var copilot_exports = {};
454
723
  __export(copilot_exports, {
@@ -457,10 +726,9 @@ __export(copilot_exports, {
457
726
  createCopilotService: () => createCopilotService
458
727
  });
459
728
  async function loadSDK() {
460
- if (sdkModule) return sdkModule;
729
+ if (_sdkMock) return _sdkMock;
461
730
  try {
462
- sdkModule = await import('@github/copilot-sdk');
463
- return sdkModule;
731
+ return await import('@github/copilot-sdk');
464
732
  } catch {
465
733
  throw new SubprocessError(
466
734
  "@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
@@ -468,10 +736,10 @@ async function loadSDK() {
468
736
  }
469
737
  }
470
738
  function _injectSDK(mock) {
471
- sdkModule = mock;
739
+ _sdkMock = mock;
472
740
  }
473
741
  function _resetSDK() {
474
- sdkModule = null;
742
+ _sdkMock = null;
475
743
  }
476
744
  function mapToolsToSDK(tools) {
477
745
  return tools.map((tool) => ({
@@ -491,17 +759,6 @@ function convertParameters(params) {
491
759
  }
492
760
  return params;
493
761
  }
494
- async function mapToolsToSDKAsync(tools) {
495
- return tools.map((tool) => ({
496
- name: tool.name,
497
- description: tool.description,
498
- parameters: convertParameters(tool.parameters),
499
- handler: async (args) => {
500
- const result = await tool.execute(args);
501
- return typeof result === "string" ? result : JSON.stringify(result);
502
- }
503
- }));
504
- }
505
762
  function buildPermissionHandler(config) {
506
763
  const onPermission = config.supervisor?.onPermission;
507
764
  if (!onPermission) {
@@ -516,6 +773,7 @@ function buildPermissionHandler(config) {
516
773
  const unifiedRequest = {
517
774
  toolName,
518
775
  toolArgs: { ...request },
776
+ toolCallId: request.toolCallId,
519
777
  rawSDKRequest: request
520
778
  };
521
779
  const ac = new AbortController();
@@ -620,15 +878,21 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
620
878
  };
621
879
  case "session.error":
622
880
  console.error("[copilot] mapSessionEvent error:", JSON.stringify(data));
623
- return {
624
- type: "error",
625
- error: String(data.message ?? "Unknown error"),
626
- recoverable: false
627
- };
881
+ {
882
+ const errorMsg = String(data.message ?? "Unknown error");
883
+ const code = classifyAgentError(errorMsg);
884
+ return {
885
+ type: "error",
886
+ error: errorMsg,
887
+ recoverable: isRecoverableErrorCode(code),
888
+ code
889
+ };
890
+ }
628
891
  case "assistant.message": {
629
892
  const doneEvent = {
630
893
  type: "done",
631
- finalOutput: data.content ? String(data.content) : null
894
+ finalOutput: null,
895
+ streamed: true
632
896
  };
633
897
  if (thinkingTracker.endThinking()) {
634
898
  return [{ type: "thinking_end" }, doneEvent];
@@ -639,66 +903,13 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
639
903
  return null;
640
904
  }
641
905
  }
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
906
  function withTimeout(promise, ms, message) {
696
- return new Promise((resolve2, reject) => {
907
+ return new Promise((resolve, reject) => {
697
908
  const timer = setTimeout(() => reject(new SubprocessError(message)), ms);
698
909
  promise.then(
699
910
  (val) => {
700
911
  clearTimeout(timer);
701
- resolve2(val);
912
+ resolve(val);
702
913
  },
703
914
  (err) => {
704
915
  clearTimeout(timer);
@@ -710,14 +921,15 @@ function withTimeout(promise, ms, message) {
710
921
  function createCopilotService(options) {
711
922
  return new CopilotAgentService(options);
712
923
  }
713
- var sdkModule, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
924
+ var _sdkMock, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
714
925
  var init_copilot = __esm({
715
926
  "src/backends/copilot.ts"() {
716
- init_types();
927
+ init_types2();
717
928
  init_base_agent();
718
- init_errors();
929
+ init_errors2();
719
930
  init_schema();
720
- sdkModule = null;
931
+ init_shared();
932
+ _sdkMock = null;
721
933
  ToolCallTracker = class {
722
934
  map = /* @__PURE__ */ new Map();
723
935
  trackStart(toolCallId, toolName, args) {
@@ -765,6 +977,7 @@ var init_copilot = __esm({
765
977
  isPersistent;
766
978
  persistentSession = null;
767
979
  _sessionId;
980
+ _persistentModel;
768
981
  activeSession = null;
769
982
  _resumeSessionId;
770
983
  _toolsReady = null;
@@ -783,15 +996,15 @@ var init_copilot = __esm({
783
996
  },
784
997
  onPermissionRequest: buildPermissionHandler(config),
785
998
  onUserInputRequest: buildUserInputHandler(config),
786
- ...config.availableTools?.length ? { availableTools: config.availableTools } : {}
999
+ ...config.availableTools ? { availableTools: config.availableTools } : {}
787
1000
  };
788
1001
  this._toolsReady = this._initToolsAsync(config);
789
1002
  this._resumeSessionId = resumeSessionId;
790
1003
  }
791
- /** Pre-convert Zod schemas to JSON Schema asynchronously.
1004
+ /** Pre-convert Zod schemas to JSON Schema.
792
1005
  * Updates sdkTools and sessionConfig.tools before first session creation. */
793
1006
  async _initToolsAsync(config) {
794
- this.sdkTools = await mapToolsToSDKAsync(config.tools ?? []);
1007
+ this.sdkTools = mapToolsToSDK(config.tools ?? []);
795
1008
  this.sessionConfig.tools = this.sdkTools;
796
1009
  }
797
1010
  get sessionId() {
@@ -815,47 +1028,63 @@ var init_copilot = __esm({
815
1028
  });
816
1029
  this.persistentSession = null;
817
1030
  this._sessionId = void 0;
1031
+ this._persistentModel = void 0;
818
1032
  }
819
1033
  }
820
- async getOrCreateSession(streaming) {
1034
+ async getOrCreateSession(streaming, options) {
821
1035
  if (this.isPersistent && this.persistentSession) {
822
- return { session: this.persistentSession, isNew: false };
1036
+ if (options.model !== this._persistentModel) {
1037
+ this.persistentSession.destroy().catch(() => {
1038
+ });
1039
+ this.persistentSession = null;
1040
+ this._sessionId = void 0;
1041
+ } else {
1042
+ return { session: this.persistentSession, isNew: false };
1043
+ }
823
1044
  }
824
1045
  if (this._toolsReady) {
825
1046
  await this._toolsReady;
826
1047
  this._toolsReady = null;
827
1048
  }
1049
+ const sessionConfig = { ...this.sessionConfig };
1050
+ sessionConfig.model = options.model;
1051
+ const resolvedTools = this.resolveTools(options);
1052
+ if (options?.tools) {
1053
+ sessionConfig.tools = mapToolsToSDK(resolvedTools);
1054
+ }
828
1055
  const client = await this.getClient();
829
1056
  if (this._resumeSessionId) {
830
1057
  const storedId = this._resumeSessionId;
831
1058
  this._resumeSessionId = void 0;
832
1059
  try {
833
1060
  const session2 = await client.resumeSession(storedId, {
834
- ...this.sessionConfig,
1061
+ ...sessionConfig,
835
1062
  streaming: this.isPersistent ? true : streaming
836
1063
  });
837
1064
  if (this.isPersistent) {
838
1065
  this.persistentSession = session2;
839
1066
  this._sessionId = session2.sessionId;
1067
+ this._persistentModel = options.model;
840
1068
  }
841
1069
  return { session: session2, isNew: false };
842
1070
  } catch {
843
1071
  }
844
1072
  }
845
1073
  const session = await client.createSession({
846
- ...this.sessionConfig,
1074
+ ...sessionConfig,
847
1075
  streaming: this.isPersistent ? true : streaming
848
1076
  });
849
1077
  if (this.isPersistent) {
850
1078
  this.persistentSession = session;
851
1079
  this._sessionId = session.sessionId;
1080
+ this._persistentModel = options.model;
852
1081
  }
853
1082
  return { session, isNew: true };
854
1083
  }
855
1084
  // ─── executeRun ─────────────────────────────────────────────────
856
- async executeRun(messages, _options, signal) {
1085
+ async executeRun(messages, options, signal) {
857
1086
  this.checkAbort(signal);
858
- const { session, isNew: isNewSession } = await this.getOrCreateSession(false);
1087
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(false, options);
859
1088
  this.activeSession = session;
860
1089
  const prompt = this.isPersistent && !isNewSession ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
861
1090
  const tracker = new ToolCallTracker();
@@ -952,9 +1181,9 @@ You MUST respond with ONLY valid JSON matching this schema:
952
1181
  };
953
1182
  }
954
1183
  // ─── executeStream ──────────────────────────────────────────────
955
- async *executeStream(messages, _options, signal) {
1184
+ async *executeStream(messages, options, signal) {
956
1185
  this.checkAbort(signal);
957
- const { session, isNew: isNewSession } = await this.getOrCreateSession(true);
1186
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(true, options);
958
1187
  this.activeSession = session;
959
1188
  if (isNewSession) {
960
1189
  yield this.emitSessionInfo(session.sessionId);
@@ -971,8 +1200,8 @@ You MUST respond with ONLY valid JSON matching this schema:
971
1200
  notify = null;
972
1201
  }
973
1202
  };
974
- const waitForItem = () => new Promise((resolve2) => {
975
- notify = resolve2;
1203
+ const waitForItem = () => new Promise((resolve) => {
1204
+ notify = resolve;
976
1205
  });
977
1206
  const unsubscribe = session.on((event) => {
978
1207
  const mapped = mapSessionEvent(event, tracker, thinkingTracker);
@@ -1066,7 +1295,11 @@ You MUST respond with ONLY valid JSON matching this schema:
1066
1295
  githubToken: this.options.githubToken,
1067
1296
  useLoggedInUser: this.options.useLoggedInUser ?? !this.options.githubToken,
1068
1297
  ...this.options.cliArgs ? { cliArgs: this.options.cliArgs } : {},
1069
- ...this.options.env ? { env: { ...process.env, ...this.options.env } } : {}
1298
+ env: {
1299
+ ...process.env,
1300
+ ...this.options.githubToken ? { GITHUB_TOKEN: this.options.githubToken } : {},
1301
+ ...this.options.env
1302
+ }
1070
1303
  });
1071
1304
  const startupTimeout = this.options.startupTimeoutMs ?? 3e4;
1072
1305
  await withTimeout(client.start(), startupTimeout, "CLI startup timed out");
@@ -1100,7 +1333,10 @@ You MUST respond with ONLY valid JSON matching this schema:
1100
1333
  return models.map((m) => ({
1101
1334
  id: m.id,
1102
1335
  name: m.name,
1103
- provider: "copilot"
1336
+ provider: "copilot",
1337
+ ...m.capabilities?.limits?.max_context_window_tokens != null && {
1338
+ contextWindow: m.capabilities.limits.max_context_window_tokens
1339
+ }
1104
1340
  }));
1105
1341
  }
1106
1342
  async validate() {
@@ -1153,10 +1389,9 @@ function stripMcpPrefix(name) {
1153
1389
  return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
1154
1390
  }
1155
1391
  async function loadSDK2() {
1156
- if (sdkModule2) return sdkModule2;
1392
+ if (_sdkMock2) return _sdkMock2;
1157
1393
  try {
1158
- sdkModule2 = await import('@anthropic-ai/claude-agent-sdk');
1159
- return sdkModule2;
1394
+ return await import('@anthropic-ai/claude-agent-sdk');
1160
1395
  } catch {
1161
1396
  throw new SubprocessError(
1162
1397
  "@anthropic-ai/claude-agent-sdk is not installed. Install it: npm install @anthropic-ai/claude-agent-sdk"
@@ -1164,13 +1399,32 @@ async function loadSDK2() {
1164
1399
  }
1165
1400
  }
1166
1401
  function _injectSDK2(mock) {
1167
- sdkModule2 = mock;
1402
+ _sdkMock2 = mock;
1168
1403
  }
1169
1404
  function _resetSDK2() {
1170
- sdkModule2 = null;
1405
+ _sdkMock2 = null;
1406
+ }
1407
+ function normalizeAskUserInput(args) {
1408
+ if (typeof args.question === "string") {
1409
+ return {
1410
+ question: args.question,
1411
+ choices: Array.isArray(args.choices) ? args.choices : void 0,
1412
+ allowFreeform: args.allowFreeform !== false
1413
+ };
1414
+ }
1415
+ const questions = args.questions;
1416
+ if (questions && questions.length > 0) {
1417
+ const first = questions[0];
1418
+ return {
1419
+ question: first.question,
1420
+ choices: first.options?.map((o) => o.label),
1421
+ allowFreeform: true
1422
+ };
1423
+ }
1424
+ return { question: JSON.stringify(args), allowFreeform: true };
1171
1425
  }
1172
- function buildMcpServer(sdk, tools, toolResultCapture) {
1173
- if (tools.length === 0) return void 0;
1426
+ function buildMcpServer(sdk, tools, toolResultCapture, onAskUser) {
1427
+ if (tools.length === 0 && !onAskUser) return void 0;
1174
1428
  const mcpTools = tools.map((tool) => {
1175
1429
  const zodSchema = tool.parameters;
1176
1430
  const inputSchema = zodSchema.shape ?? zodToJsonSchema(tool.parameters);
@@ -1194,6 +1448,39 @@ function buildMcpServer(sdk, tools, toolResultCapture) {
1194
1448
  }
1195
1449
  );
1196
1450
  });
1451
+ if (onAskUser) {
1452
+ const askUserTool = sdk.tool(
1453
+ "ask_user",
1454
+ "Ask the user a question and wait for their response",
1455
+ {
1456
+ question: { type: "string", description: "The question to ask the user" },
1457
+ choices: {
1458
+ type: "array",
1459
+ items: { type: "string" },
1460
+ description: "Optional list of choices for multiple choice"
1461
+ },
1462
+ questions: {
1463
+ type: "array",
1464
+ items: {
1465
+ type: "object",
1466
+ properties: {
1467
+ question: { type: "string" },
1468
+ options: { type: "array", items: { type: "object", properties: { label: { type: "string" } } } }
1469
+ }
1470
+ },
1471
+ description: "Alternative nested question format"
1472
+ }
1473
+ },
1474
+ async (args) => {
1475
+ const normalized = normalizeAskUserInput(args);
1476
+ const response = await onAskUser(normalized, AbortSignal.timeout(3e5));
1477
+ return {
1478
+ content: [{ type: "text", text: response.answer }]
1479
+ };
1480
+ }
1481
+ );
1482
+ mcpTools.push(askUserTool);
1483
+ }
1197
1484
  return sdk.createSdkMcpServer({
1198
1485
  name: MCP_SERVER_NAME,
1199
1486
  version: "1.0.0",
@@ -1243,6 +1530,7 @@ function buildCanUseTool(config) {
1243
1530
  const unifiedRequest = {
1244
1531
  toolName,
1245
1532
  toolArgs: input,
1533
+ toolCallId: options.toolUseID,
1246
1534
  suggestedScope: extractSuggestedScope(options.suggestions),
1247
1535
  rawSDKRequest: { toolName, input, ...options }
1248
1536
  };
@@ -1295,6 +1583,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1295
1583
  if (block.type === "tool_use") {
1296
1584
  const toolCallId = String(block.id ?? "");
1297
1585
  const toolName = stripMcpPrefix(block.name ?? "unknown");
1586
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1298
1587
  if (toolCallTracker) {
1299
1588
  toolCallTracker.trackStart(toolCallId, toolName);
1300
1589
  }
@@ -1318,6 +1607,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1318
1607
  case "tool_use_summary": {
1319
1608
  const summary = msg.summary;
1320
1609
  const toolName = stripMcpPrefix(msg.tool_name ?? "unknown");
1610
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) return null;
1321
1611
  const precedingIds = msg.preceding_tool_use_ids;
1322
1612
  let toolCallId = "";
1323
1613
  if (precedingIds && precedingIds.length > 0) {
@@ -1371,10 +1661,13 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1371
1661
  }
1372
1662
  if (msg.is_error) {
1373
1663
  const r = msg;
1664
+ const errorMsg = r.errors?.join("; ") ?? "Unknown error";
1665
+ const code = classifyAgentError(errorMsg);
1374
1666
  return {
1375
1667
  type: "error",
1376
- error: r.errors?.join("; ") ?? "Unknown error",
1377
- recoverable: false
1668
+ error: errorMsg,
1669
+ recoverable: isRecoverableErrorCode(code),
1670
+ code
1378
1671
  };
1379
1672
  }
1380
1673
  return null;
@@ -1383,72 +1676,21 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1383
1676
  return null;
1384
1677
  }
1385
1678
  }
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 "";
1679
+ function createClaudeService(options) {
1680
+ return new ClaudeAgentService(options);
1394
1681
  }
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
- function createClaudeService(options) {
1440
- return new ClaudeAgentService(options);
1441
- }
1442
- var MCP_SERVER_NAME, MCP_TOOL_PREFIX, sdkModule2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1682
+ 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
1683
  var init_claude = __esm({
1444
1684
  "src/backends/claude.ts"() {
1445
- init_types();
1685
+ init_types2();
1446
1686
  init_base_agent();
1447
- init_errors();
1687
+ init_errors2();
1448
1688
  init_schema();
1689
+ init_shared();
1449
1690
  MCP_SERVER_NAME = "agent-sdk-tools";
1450
1691
  MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
1451
- sdkModule2 = null;
1692
+ CLAUDE_INTERNAL_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion"]);
1693
+ _sdkMock2 = null;
1452
1694
  ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
1453
1695
  ANTHROPIC_API_VERSION = "2023-06-01";
1454
1696
  ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
@@ -1493,11 +1735,6 @@ var init_claude = __esm({
1493
1735
  if (options.resumeSessionId) {
1494
1736
  this._sessionId = options.resumeSessionId;
1495
1737
  }
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
1738
  }
1502
1739
  get sessionId() {
1503
1740
  return this._sessionId;
@@ -1521,12 +1758,12 @@ var init_claude = __esm({
1521
1758
  const transcriptPath = home ? `${home}/.claude/projects/.session/sessions/${sessionId}/conversation.jsonl` : void 0;
1522
1759
  return { type: "session_info", sessionId, transcriptPath, backend: "claude" };
1523
1760
  }
1524
- buildQueryOptions(signal) {
1761
+ buildQueryOptions(signal, options) {
1525
1762
  const ac = new AbortController();
1526
1763
  signal.addEventListener("abort", () => ac.abort(), { once: true });
1527
1764
  const opts = {
1528
1765
  abortController: ac,
1529
- model: this.config.model,
1766
+ model: options.model,
1530
1767
  maxTurns: this.options.maxTurns,
1531
1768
  cwd: this.options.workingDirectory,
1532
1769
  pathToClaudeCodeExecutable: this.options.cliPath,
@@ -1556,25 +1793,85 @@ var init_claude = __esm({
1556
1793
  return opts;
1557
1794
  }
1558
1795
  async buildMcpConfig(opts, toolResultCapture) {
1559
- if (this.tools.length === 0) return opts;
1796
+ const onAskUser = this.config.supervisor?.onAskUser;
1797
+ if (this.tools.length === 0 && !onAskUser) return opts;
1560
1798
  const sdk = await loadSDK2();
1561
- const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture);
1799
+ const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture, onAskUser);
1562
1800
  if (mcpServer) {
1563
1801
  opts.mcpServers = {
1564
1802
  [MCP_SERVER_NAME]: mcpServer
1565
1803
  };
1566
1804
  const mcpToolNames = this.tools.map((t) => mcpToolName(t.name));
1805
+ if (onAskUser) {
1806
+ mcpToolNames.push(mcpToolName("ask_user"));
1807
+ }
1567
1808
  opts.allowedTools = [...opts.allowedTools ?? [], ...mcpToolNames];
1568
1809
  }
1810
+ if (onAskUser) {
1811
+ opts.disallowedTools = [...opts.disallowedTools ?? [], "AskUserQuestion"];
1812
+ }
1569
1813
  return opts;
1570
1814
  }
1815
+ // ─── Retry Helpers (shared across executeRun/RunStructured/Stream) ──
1816
+ /** Setup a retry query: clear session, rebuild with full history */
1817
+ async prepareRetryQuery(sdk, messages, signal, options, toolResultCapture, modifyOpts) {
1818
+ this.clearPersistentSession();
1819
+ const retryPrompt = buildContextualPrompt(messages);
1820
+ let retryOpts = this.buildQueryOptions(signal, options);
1821
+ toolResultCapture.clear();
1822
+ retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1823
+ modifyOpts?.(retryOpts);
1824
+ const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1825
+ this.activeQuery = retryQ;
1826
+ return retryQ;
1827
+ }
1828
+ /** Extract tool_use blocks from an assistant SDK message into toolCalls array */
1829
+ collectToolCallsFromMessage(msg, toolCalls, toolResultCapture) {
1830
+ if (msg.type !== "assistant") return;
1831
+ const betaMessage = msg.message;
1832
+ if (!betaMessage?.content) return;
1833
+ for (const block of betaMessage.content) {
1834
+ if (block.type === "tool_use") {
1835
+ const toolName = stripMcpPrefix(block.name ?? "unknown");
1836
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1837
+ toolCalls.push({
1838
+ toolName,
1839
+ args: block.input ?? {},
1840
+ result: toolResultCapture.get(toolName) ?? null,
1841
+ approved: true
1842
+ });
1843
+ }
1844
+ }
1845
+ }
1846
+ /** Back-fill tool results from capture map on summary/result messages */
1847
+ backfillToolResults(msg, toolCalls, toolResultCapture) {
1848
+ if (msg.type !== "tool_use_summary" && msg.type !== "result") return;
1849
+ for (const tc of toolCalls) {
1850
+ if (tc.result === null) {
1851
+ const captured = toolResultCapture.get(tc.toolName);
1852
+ if (captured !== void 0) tc.result = captured;
1853
+ }
1854
+ }
1855
+ }
1856
+ /** Wrap retry inner loop with shared error handling */
1857
+ async withRetryErrorHandling(signal, fn) {
1858
+ try {
1859
+ return await fn();
1860
+ } catch (retryError) {
1861
+ if (this.isPersistent) this.clearPersistentSession();
1862
+ if (signal.aborted) throw new AbortError();
1863
+ throw retryError;
1864
+ } finally {
1865
+ this.activeQuery = null;
1866
+ }
1867
+ }
1571
1868
  // ─── executeRun ─────────────────────────────────────────────────
1572
- async executeRun(messages, _options, signal) {
1869
+ async executeRun(messages, options, signal) {
1573
1870
  this.checkAbort(signal);
1574
1871
  const sdk = await loadSDK2();
1575
1872
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1576
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1577
- let opts = this.buildQueryOptions(signal);
1873
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1874
+ let opts = this.buildQueryOptions(signal, options);
1578
1875
  const toolResultCapture = /* @__PURE__ */ new Map();
1579
1876
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1580
1877
  const q = sdk.query({ prompt, options: opts });
@@ -1584,30 +1881,8 @@ var init_claude = __esm({
1584
1881
  let usage;
1585
1882
  try {
1586
1883
  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
- }
1884
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1885
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1611
1886
  if (msg.type === "result") {
1612
1887
  if (msg.subtype === "success") {
1613
1888
  const r = msg;
@@ -1627,41 +1902,13 @@ var init_claude = __esm({
1627
1902
  } catch (e) {
1628
1903
  if (signal.aborted) throw new AbortError();
1629
1904
  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;
1905
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1637
1906
  toolCalls.length = 0;
1638
1907
  output = null;
1639
- try {
1908
+ return this.withRetryErrorHandling(signal, async () => {
1640
1909
  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
- }
1910
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1911
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1665
1912
  if (msg.type === "result") {
1666
1913
  if (msg.subtype === "success") {
1667
1914
  const r = msg;
@@ -1678,23 +1925,17 @@ var init_claude = __esm({
1678
1925
  }
1679
1926
  }
1680
1927
  }
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
- };
1928
+ return {
1929
+ output,
1930
+ structuredOutput: void 0,
1931
+ toolCalls,
1932
+ messages: [
1933
+ ...messages,
1934
+ ...output !== null ? [{ role: "assistant", content: output }] : []
1935
+ ],
1936
+ usage
1937
+ };
1938
+ });
1698
1939
  }
1699
1940
  if (this.isPersistent) this.clearPersistentSession();
1700
1941
  throw e;
@@ -1713,12 +1954,12 @@ var init_claude = __esm({
1713
1954
  };
1714
1955
  }
1715
1956
  // ─── executeRunStructured ───────────────────────────────────────
1716
- async executeRunStructured(messages, schema, _options, signal) {
1957
+ async executeRunStructured(messages, schema, options, signal) {
1717
1958
  this.checkAbort(signal);
1718
1959
  const sdk = await loadSDK2();
1719
1960
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1720
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1721
- let opts = this.buildQueryOptions(signal);
1961
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1962
+ let opts = this.buildQueryOptions(signal, options);
1722
1963
  const toolResultCapture = /* @__PURE__ */ new Map();
1723
1964
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1724
1965
  const jsonSchema = zodToJsonSchema(schema.schema);
@@ -1734,30 +1975,8 @@ var init_claude = __esm({
1734
1975
  let usage;
1735
1976
  try {
1736
1977
  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
- }
1978
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1979
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1761
1980
  if (msg.type === "result" && msg.subtype === "success") {
1762
1981
  const r = msg;
1763
1982
  output = r.result;
@@ -1792,46 +2011,23 @@ var init_claude = __esm({
1792
2011
  } catch (e) {
1793
2012
  if (signal.aborted) throw new AbortError();
1794
2013
  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;
2014
+ const retryQ = await this.prepareRetryQuery(
2015
+ sdk,
2016
+ messages,
2017
+ signal,
2018
+ options,
2019
+ toolResultCapture,
2020
+ (opts2) => {
2021
+ opts2.outputFormat = { type: "json_schema", schema: jsonSchema };
2022
+ }
2023
+ );
1806
2024
  toolCalls.length = 0;
1807
2025
  output = null;
1808
2026
  structuredOutput = void 0;
1809
- try {
2027
+ return this.withRetryErrorHandling(signal, async () => {
1810
2028
  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
- }
2029
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
2030
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1835
2031
  if (msg.type === "result" && msg.subtype === "success") {
1836
2032
  const r = msg;
1837
2033
  output = r.result;
@@ -1863,23 +2059,17 @@ var init_claude = __esm({
1863
2059
  );
1864
2060
  }
1865
2061
  }
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
- };
2062
+ return {
2063
+ output,
2064
+ structuredOutput,
2065
+ toolCalls,
2066
+ messages: [
2067
+ ...messages,
2068
+ ...output !== null ? [{ role: "assistant", content: output }] : []
2069
+ ],
2070
+ usage
2071
+ };
2072
+ });
1883
2073
  }
1884
2074
  if (this.isPersistent) this.clearPersistentSession();
1885
2075
  throw e;
@@ -1898,12 +2088,12 @@ var init_claude = __esm({
1898
2088
  };
1899
2089
  }
1900
2090
  // ─── executeStream ──────────────────────────────────────────────
1901
- async *executeStream(messages, _options, signal) {
2091
+ async *executeStream(messages, options, signal) {
1902
2092
  this.checkAbort(signal);
1903
2093
  const sdk = await loadSDK2();
1904
2094
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1905
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1906
- let opts = this.buildQueryOptions(signal);
2095
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
2096
+ let opts = this.buildQueryOptions(signal, options);
1907
2097
  const toolResultCapture = /* @__PURE__ */ new Map();
1908
2098
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1909
2099
  const q = sdk.query({ prompt, options: opts });
@@ -1911,6 +2101,7 @@ var init_claude = __esm({
1911
2101
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
1912
2102
  const toolCallTracker = new ClaudeToolCallTracker();
1913
2103
  const pendingStreamToolCalls = /* @__PURE__ */ new Map();
2104
+ let hasStreamedText = false;
1914
2105
  try {
1915
2106
  for await (const msg of q) {
1916
2107
  if (signal.aborted) throw new AbortError();
@@ -1928,6 +2119,7 @@ var init_claude = __esm({
1928
2119
  } else if (e.type === "tool_call_end") {
1929
2120
  pendingStreamToolCalls.delete(e.toolCallId);
1930
2121
  }
2122
+ if (e.type === "text_delta") hasStreamedText = true;
1931
2123
  yield e;
1932
2124
  }
1933
2125
  }
@@ -1951,22 +2143,21 @@ var init_claude = __esm({
1951
2143
  }
1952
2144
  yield this.emitSessionInfo(r.session_id);
1953
2145
  }
1954
- yield { type: "done", finalOutput: r.result };
2146
+ yield {
2147
+ type: "done",
2148
+ finalOutput: hasStreamedText ? null : r.result,
2149
+ ...hasStreamedText ? { streamed: true } : {}
2150
+ };
1955
2151
  }
1956
2152
  }
1957
2153
  } catch (e) {
1958
2154
  if (signal.aborted) throw new AbortError();
1959
2155
  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;
2156
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1967
2157
  const retryThinkingBlockIndices = /* @__PURE__ */ new Set();
1968
2158
  const retryToolCallTracker = new ClaudeToolCallTracker();
1969
2159
  const retryPendingToolCalls = /* @__PURE__ */ new Map();
2160
+ let retryHasStreamedText = false;
1970
2161
  try {
1971
2162
  for await (const msg of retryQ) {
1972
2163
  if (signal.aborted) throw new AbortError();
@@ -1984,6 +2175,7 @@ var init_claude = __esm({
1984
2175
  } else if (ev.type === "tool_call_end") {
1985
2176
  retryPendingToolCalls.delete(ev.toolCallId);
1986
2177
  }
2178
+ if (ev.type === "text_delta") retryHasStreamedText = true;
1987
2179
  yield ev;
1988
2180
  }
1989
2181
  }
@@ -2007,7 +2199,11 @@ var init_claude = __esm({
2007
2199
  }
2008
2200
  yield this.emitSessionInfo(r.session_id);
2009
2201
  }
2010
- yield { type: "done", finalOutput: r.result };
2202
+ yield {
2203
+ type: "done",
2204
+ finalOutput: retryHasStreamedText ? null : r.result,
2205
+ ...retryHasStreamedText ? { streamed: true } : {}
2206
+ };
2011
2207
  }
2012
2208
  }
2013
2209
  } catch (retryError) {
@@ -2069,7 +2265,8 @@ var init_claude = __esm({
2069
2265
  this.cachedModels = body.data.map((m) => ({
2070
2266
  id: m.id,
2071
2267
  name: m.display_name,
2072
- provider: "claude"
2268
+ provider: "claude",
2269
+ ...m.max_input_tokens != null && { contextWindow: m.max_input_tokens }
2073
2270
  }));
2074
2271
  return this.cachedModels;
2075
2272
  }
@@ -2127,32 +2324,30 @@ __export(vercel_ai_exports, {
2127
2324
  createVercelAIService: () => createVercelAIService
2128
2325
  });
2129
2326
  async function loadSDK3() {
2130
- if (sdkModule3) return sdkModule3;
2327
+ if (_sdkMock3) return _sdkMock3;
2131
2328
  try {
2132
- sdkModule3 = await import('ai');
2133
- return sdkModule3;
2329
+ return await import('ai');
2134
2330
  } catch {
2135
2331
  throw new DependencyError("ai");
2136
2332
  }
2137
2333
  }
2138
2334
  async function loadCompat() {
2139
- if (compatModule) return compatModule;
2335
+ if (_compatMock) return _compatMock;
2140
2336
  try {
2141
- compatModule = await import('@ai-sdk/openai-compatible');
2142
- return compatModule;
2337
+ return await import('@ai-sdk/openai-compatible');
2143
2338
  } catch {
2144
2339
  throw new DependencyError("@ai-sdk/openai-compatible");
2145
2340
  }
2146
2341
  }
2147
2342
  function _injectSDK3(mock) {
2148
- sdkModule3 = mock;
2343
+ _sdkMock3 = mock;
2149
2344
  }
2150
2345
  function _injectCompat(mock) {
2151
- compatModule = mock;
2346
+ _compatMock = mock;
2152
2347
  }
2153
2348
  function _resetSDK3() {
2154
- sdkModule3 = null;
2155
- compatModule = null;
2349
+ _sdkMock3 = null;
2350
+ _compatMock = null;
2156
2351
  }
2157
2352
  function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, signal) {
2158
2353
  const toolMap = {};
@@ -2195,13 +2390,14 @@ function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, s
2195
2390
  return toolMap;
2196
2391
  }
2197
2392
  function wrapToolExecute(ourTool, supervisor, sessionApprovals, permissionStore, signal) {
2198
- return async (args) => {
2393
+ return async (args, options) => {
2199
2394
  if (ourTool.needsApproval && supervisor?.onPermission) {
2200
2395
  const storeApproved = permissionStore && await permissionStore.isApproved(ourTool.name);
2201
2396
  if (!storeApproved && !sessionApprovals.has(ourTool.name)) {
2202
2397
  const request = {
2203
2398
  toolName: ourTool.name,
2204
- toolArgs: args ?? {}
2399
+ toolArgs: args ?? {},
2400
+ toolCallId: options?.toolCallId
2205
2401
  };
2206
2402
  const decision = await supervisor.onPermission(
2207
2403
  request,
@@ -2308,7 +2504,8 @@ function mapStreamPart(part) {
2308
2504
  return {
2309
2505
  type: "error",
2310
2506
  error: p.error instanceof Error ? p.error.message : String(p.error ?? "Tool execution failed"),
2311
- recoverable: true
2507
+ recoverable: true,
2508
+ code: "TOOL_EXECUTION" /* TOOL_EXECUTION */
2312
2509
  };
2313
2510
  }
2314
2511
  case "reasoning-start":
@@ -2329,10 +2526,13 @@ function mapStreamPart(part) {
2329
2526
  }
2330
2527
  case "error": {
2331
2528
  const p = part;
2529
+ const errorMsg = p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error");
2530
+ const code = classifyAgentError(errorMsg);
2332
2531
  return {
2333
2532
  type: "error",
2334
- error: p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error"),
2335
- recoverable: false
2533
+ error: errorMsg,
2534
+ recoverable: isRecoverableErrorCode(code),
2535
+ code
2336
2536
  };
2337
2537
  }
2338
2538
  default:
@@ -2342,15 +2542,15 @@ function mapStreamPart(part) {
2342
2542
  function createVercelAIService(options) {
2343
2543
  return new VercelAIAgentService(options);
2344
2544
  }
2345
- var sdkModule3, compatModule, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2545
+ var _sdkMock3, _compatMock, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2346
2546
  var init_vercel_ai = __esm({
2347
2547
  "src/backends/vercel-ai.ts"() {
2348
- init_types();
2548
+ init_types2();
2349
2549
  init_base_agent();
2350
- init_errors();
2550
+ init_errors2();
2351
2551
  init_schema();
2352
- sdkModule3 = null;
2353
- compatModule = null;
2552
+ _sdkMock3 = null;
2553
+ _compatMock = null;
2354
2554
  DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
2355
2555
  DEFAULT_PROVIDER = "openrouter";
2356
2556
  DEFAULT_MAX_TURNS = 10;
@@ -2363,28 +2563,33 @@ var init_vercel_ai = __esm({
2363
2563
  super(config);
2364
2564
  this.backendOptions = backendOptions;
2365
2565
  }
2366
- async getModel() {
2367
- if (this.model) return this.model;
2566
+ async getModel(options) {
2567
+ const requestedModel = options.model;
2568
+ const defaultModel = this.config.model;
2569
+ if (requestedModel === defaultModel && this.model) return this.model;
2368
2570
  const compat = await loadCompat();
2369
2571
  const provider = compat.createOpenAICompatible({
2370
2572
  name: this.backendOptions.provider ?? DEFAULT_PROVIDER,
2371
2573
  baseURL: this.backendOptions.baseUrl ?? DEFAULT_BASE_URL,
2372
2574
  apiKey: this.backendOptions.apiKey
2373
2575
  });
2374
- const modelId = this.config.model ?? "anthropic/claude-sonnet-4-5";
2375
- this.model = provider.chatModel(modelId);
2376
- return this.model;
2576
+ const model = provider.chatModel(requestedModel);
2577
+ if (requestedModel === defaultModel) {
2578
+ this.model = model;
2579
+ }
2580
+ return model;
2377
2581
  }
2378
- async getSDKTools(signal) {
2582
+ async getSDKTools(signal, options) {
2379
2583
  const sdk = await loadSDK3();
2380
- return mapToolsToSDK2(sdk, this.config.tools ?? [], this.config, this.sessionApprovals, this.config.permissionStore, signal);
2584
+ const tools = this.resolveTools(options);
2585
+ return mapToolsToSDK2(sdk, tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
2381
2586
  }
2382
2587
  // ─── executeRun ─────────────────────────────────────────────────
2383
- async executeRun(messages, _options, signal) {
2588
+ async executeRun(messages, options, signal) {
2384
2589
  this.checkAbort(signal);
2385
2590
  const sdk = await loadSDK3();
2386
- const model = await this.getModel();
2387
- const tools = await this.getSDKTools(signal);
2591
+ const model = await this.getModel(options);
2592
+ const tools = await this.getSDKTools(signal, options);
2388
2593
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2389
2594
  const sdkMessages = messagesToSDK(messages);
2390
2595
  const hasTools = Object.keys(tools).length > 0;
@@ -2440,10 +2645,10 @@ var init_vercel_ai = __esm({
2440
2645
  };
2441
2646
  }
2442
2647
  // ─── executeRunStructured ───────────────────────────────────────
2443
- async executeRunStructured(messages, schema, _options, signal) {
2648
+ async executeRunStructured(messages, schema, options, signal) {
2444
2649
  this.checkAbort(signal);
2445
2650
  const sdk = await loadSDK3();
2446
- const model = await this.getModel();
2651
+ const model = await this.getModel(options);
2447
2652
  const sdkMessages = messagesToSDK(messages);
2448
2653
  const jsonSchema = zodToJsonSchema(schema.schema);
2449
2654
  const result = await sdk.generateObject({
@@ -2485,11 +2690,11 @@ var init_vercel_ai = __esm({
2485
2690
  };
2486
2691
  }
2487
2692
  // ─── executeStream ──────────────────────────────────────────────
2488
- async *executeStream(messages, _options, signal) {
2693
+ async *executeStream(messages, options, signal) {
2489
2694
  this.checkAbort(signal);
2490
2695
  const sdk = await loadSDK3();
2491
- const model = await this.getModel();
2492
- const tools = await this.getSDKTools(signal);
2696
+ const model = await this.getModel(options);
2697
+ const tools = await this.getSDKTools(signal, options);
2493
2698
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2494
2699
  const sdkMessages = messagesToSDK(messages);
2495
2700
  const hasTools = Object.keys(tools).length > 0;
@@ -2514,6 +2719,7 @@ var init_vercel_ai = __esm({
2514
2719
  }
2515
2720
  });
2516
2721
  let finalText = "";
2722
+ let lastFinishReason;
2517
2723
  try {
2518
2724
  for await (const part of result.fullStream) {
2519
2725
  if (signal.aborted) throw new AbortError();
@@ -2524,10 +2730,15 @@ var init_vercel_ai = __esm({
2524
2730
  }
2525
2731
  if (part.type === "finish-step") {
2526
2732
  const p = part;
2733
+ lastFinishReason = p.finishReason;
2527
2734
  if (p.finishReason === "tool-calls") {
2528
2735
  finalText = "";
2529
2736
  }
2530
2737
  }
2738
+ if (part.type === "finish") {
2739
+ const p = part;
2740
+ lastFinishReason = p.finishReason;
2741
+ }
2531
2742
  }
2532
2743
  const totalUsage = await result.totalUsage;
2533
2744
  yield {
@@ -2535,9 +2746,12 @@ 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 } : {},
2754
+ ...lastFinishReason ? { finishReason: lastFinishReason } : {}
2541
2755
  };
2542
2756
  } catch (e) {
2543
2757
  if (signal.aborted) throw new AbortError();
@@ -2566,16 +2780,33 @@ var init_vercel_ai = __esm({
2566
2780
  const baseUrl = (this.options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
2567
2781
  try {
2568
2782
  const res = await globalThis.fetch(`${baseUrl}/models`, {
2569
- headers: { Authorization: `Bearer ${this.options.apiKey}` }
2783
+ headers: {
2784
+ Authorization: `Bearer ${this.options.apiKey}`,
2785
+ // OpenRouter requires HTTP-Referer for API access
2786
+ "HTTP-Referer": "https://github.com/nicepkg/agent-sdk"
2787
+ }
2570
2788
  });
2571
2789
  if (!res.ok) {
2572
2790
  return [];
2573
2791
  }
2574
2792
  const body = await res.json();
2575
- if (!body.data || body.data.length === 0) {
2576
- return [];
2793
+ if (body.data && Array.isArray(body.data)) {
2794
+ return body.data.filter((m) => typeof m.id === "string").map((m) => ({
2795
+ id: m.id,
2796
+ ...typeof m.name === "string" && { name: m.name },
2797
+ ...typeof m.description === "string" && { description: m.description },
2798
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2799
+ }));
2577
2800
  }
2578
- return body.data.map((m) => ({ id: m.id }));
2801
+ if (Array.isArray(body)) {
2802
+ return body.filter((m) => typeof m.id === "string").map((m) => ({
2803
+ id: m.id,
2804
+ ...typeof m.name === "string" && { name: m.name },
2805
+ ...typeof m.description === "string" && { description: m.description },
2806
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2807
+ }));
2808
+ }
2809
+ return [];
2579
2810
  } catch {
2580
2811
  return [];
2581
2812
  }
@@ -2606,263 +2837,7 @@ var init_vercel_ai = __esm({
2606
2837
  }
2607
2838
  });
2608
2839
 
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);
2797
- }
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();
2808
- }
2809
- async dispose() {
2810
- await this.sessionStore.dispose();
2811
- await this.projectStore.dispose();
2812
- if (this.userStore !== this.projectStore) {
2813
- await this.userStore.dispose();
2814
- }
2815
- }
2816
- };
2817
- }
2818
- });
2819
-
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
2840
+ // src/chat/types.ts
2866
2841
  function createChatId() {
2867
2842
  return crypto.randomUUID();
2868
2843
  }
@@ -2873,6 +2848,20 @@ function toChatId(value) {
2873
2848
  }
2874
2849
  return value;
2875
2850
  }
2851
+ function createTextMessage(text, role = "user") {
2852
+ return {
2853
+ id: createChatId(),
2854
+ role,
2855
+ parts: [{ type: "text", text, status: "complete" }],
2856
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2857
+ status: "complete"
2858
+ };
2859
+ }
2860
+ function isObservableSession(session) {
2861
+ return "subscribe" in session && typeof session.subscribe === "function" && "getSnapshot" in session && typeof session.getSnapshot === "function";
2862
+ }
2863
+
2864
+ // src/chat/chat-utils.ts
2876
2865
  function getMessageText(message) {
2877
2866
  return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
2878
2867
  }
@@ -2882,6 +2871,8 @@ function getMessageToolCalls(message) {
2882
2871
  function getMessageReasoning(message) {
2883
2872
  return message.parts.filter((p) => p.type === "reasoning").map((p) => p.text).join("");
2884
2873
  }
2874
+
2875
+ // src/chat/guards.ts
2885
2876
  function isChatMessage(value) {
2886
2877
  if (typeof value !== "object" || value === null) return false;
2887
2878
  const obj = value;
@@ -2922,31 +2913,33 @@ function isFilePart(value) {
2922
2913
  const obj = value;
2923
2914
  return obj.type === "file" && typeof obj.name === "string" && typeof obj.mimeType === "string";
2924
2915
  }
2916
+ var VALID_CHAT_EVENT_TYPES = /* @__PURE__ */ new Set([
2917
+ "message:start",
2918
+ "message:delta",
2919
+ "message:complete",
2920
+ "tool:start",
2921
+ "tool:complete",
2922
+ "thinking:start",
2923
+ "thinking:delta",
2924
+ "thinking:end",
2925
+ "permission:request",
2926
+ "permission:response",
2927
+ "usage",
2928
+ "session:created",
2929
+ "session:updated",
2930
+ "error",
2931
+ "typing:start",
2932
+ "typing:end",
2933
+ "heartbeat",
2934
+ "done"
2935
+ ]);
2925
2936
  function isChatEvent(value) {
2926
2937
  if (typeof value !== "object" || value === null) return false;
2927
2938
  const obj = value;
2928
- const validTypes = [
2929
- "message:start",
2930
- "message:delta",
2931
- "message:complete",
2932
- "tool:start",
2933
- "tool:complete",
2934
- "thinking:start",
2935
- "thinking:delta",
2936
- "thinking:end",
2937
- "permission:request",
2938
- "permission:response",
2939
- "usage",
2940
- "session:created",
2941
- "session:updated",
2942
- "error",
2943
- "typing:start",
2944
- "typing:end",
2945
- "heartbeat",
2946
- "done"
2947
- ];
2948
- return validTypes.includes(obj.type);
2939
+ return VALID_CHAT_EVENT_TYPES.has(obj.type);
2949
2940
  }
2941
+
2942
+ // src/chat/bridge.ts
2950
2943
  function agentEventToChatEvent(event, messageId) {
2951
2944
  switch (event.type) {
2952
2945
  case "text_delta":
@@ -2999,6 +2992,7 @@ function agentEventToChatEvent(event, messageId) {
2999
2992
  type: "error",
3000
2993
  error: event.error,
3001
2994
  recoverable: event.recoverable,
2995
+ code: event.code,
3002
2996
  messageId
3003
2997
  };
3004
2998
  case "heartbeat":
@@ -3006,8 +3000,9 @@ function agentEventToChatEvent(event, messageId) {
3006
3000
  case "ask_user":
3007
3001
  case "ask_user_response":
3008
3002
  case "session_info":
3009
- case "done":
3010
3003
  return null;
3004
+ case "done":
3005
+ return { type: "done", finalOutput: event.finalOutput ?? void 0, finishReason: event.finishReason };
3011
3006
  default:
3012
3007
  return null;
3013
3008
  }
@@ -3045,27 +3040,37 @@ function chatEventToAgentEvent(event) {
3045
3040
  result: event.result
3046
3041
  };
3047
3042
  case "error":
3048
- return { type: "error", error: event.error, recoverable: event.recoverable };
3043
+ return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
3049
3044
  default:
3050
3045
  return null;
3051
3046
  }
3052
3047
  }
3048
+
3049
+ // src/chat/conversion.ts
3053
3050
  function toAgentMessage(message) {
3051
+ return toAgentMessages(message)[0];
3052
+ }
3053
+ function toAgentMessages(message) {
3054
3054
  const textContent = getMessageText(message);
3055
3055
  const toolCallParts = getMessageToolCalls(message);
3056
3056
  switch (message.role) {
3057
3057
  case "user":
3058
- return { role: "user", content: textContent };
3058
+ return [{ role: "user", content: textContent }];
3059
3059
  case "assistant": {
3060
3060
  const toolCalls = toolCallParts.length > 0 ? toolCallParts.map((p) => ({ id: p.toolCallId, name: p.name, args: p.args })) : void 0;
3061
- return {
3061
+ const assistantMsg = {
3062
3062
  role: "assistant",
3063
3063
  content: textContent,
3064
3064
  toolCalls
3065
3065
  };
3066
+ const toolResults = extractToolResults(message);
3067
+ if (toolResults.length > 0) {
3068
+ return [assistantMsg, { role: "tool", toolResults }];
3069
+ }
3070
+ return [assistantMsg];
3066
3071
  }
3067
3072
  case "system":
3068
- return { role: "system", content: textContent };
3073
+ return [{ role: "system", content: textContent }];
3069
3074
  }
3070
3075
  }
3071
3076
  function fromAgentMessage(message, id) {
@@ -3111,6 +3116,14 @@ function fromAgentMessage(message, id) {
3111
3116
  status: "complete"
3112
3117
  };
3113
3118
  }
3119
+ function extractToolResults(message) {
3120
+ return getMessageToolCalls(message).filter((p) => p.result !== void 0).map((p) => ({
3121
+ toolCallId: p.toolCallId,
3122
+ name: p.name,
3123
+ result: p.result,
3124
+ isError: p.status === "error" ? true : void 0
3125
+ }));
3126
+ }
3114
3127
 
3115
3128
  // src/chat/context.ts
3116
3129
  function estimateTokens(message, options) {
@@ -3220,6 +3233,56 @@ var ContextWindowManager = class {
3220
3233
  });
3221
3234
  return { ...result, messages: updatedMessages };
3222
3235
  }
3236
+ /**
3237
+ * Trim messages using real token usage data from the previous API call.
3238
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
3239
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
3240
+ *
3241
+ * @param messages - All messages in the session
3242
+ * @param lastPromptTokens - Real prompt tokens from the last API response
3243
+ * @param modelContextWindow - Model's total context window size in tokens
3244
+ * @returns Result with fitted messages and metadata
3245
+ */
3246
+ fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
3247
+ if (messages.length === 0) {
3248
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
3249
+ }
3250
+ const budget = modelContextWindow - this.config.reservedTokens;
3251
+ if (budget <= 0 || lastPromptTokens <= budget) {
3252
+ return {
3253
+ messages: [...messages],
3254
+ totalTokens: lastPromptTokens,
3255
+ removedCount: 0,
3256
+ wasTruncated: false
3257
+ };
3258
+ }
3259
+ const avgTokensPerMessage = lastPromptTokens / messages.length;
3260
+ const tokensToFree = lastPromptTokens - budget;
3261
+ const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
3262
+ const nonSystemIndices = [];
3263
+ for (let i = 0; i < messages.length; i++) {
3264
+ if (messages[i].role === "system") ; else {
3265
+ nonSystemIndices.push(i);
3266
+ }
3267
+ }
3268
+ const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
3269
+ const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
3270
+ const result = [];
3271
+ for (let i = 0; i < messages.length; i++) {
3272
+ if (!removedIndices.has(i)) {
3273
+ result.push(messages[i]);
3274
+ }
3275
+ }
3276
+ const estimatedTokens = Math.round(
3277
+ lastPromptTokens * (result.length / messages.length)
3278
+ );
3279
+ return {
3280
+ messages: result,
3281
+ totalTokens: estimatedTokens,
3282
+ removedCount: removableCount,
3283
+ wasTruncated: removableCount > 0
3284
+ };
3285
+ }
3223
3286
  /**
3224
3287
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
3225
3288
  * Always keeps the most recent user message.
@@ -3337,37 +3400,19 @@ var ContextWindowManager = class {
3337
3400
  };
3338
3401
 
3339
3402
  // src/chat/errors.ts
3403
+ init_errors2();
3340
3404
  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
3405
  var ChatError = class extends AgentSDKError {
3365
3406
  code;
3366
3407
  retryable;
3367
3408
  retryAfter;
3368
3409
  timestamp;
3369
3410
  constructor(message, options) {
3370
- super(message, { cause: options.cause });
3411
+ super(message, {
3412
+ cause: options.cause,
3413
+ code: options.code,
3414
+ retryable: options.retryable
3415
+ });
3371
3416
  this.name = "ChatError";
3372
3417
  this.code = options.code;
3373
3418
  this.retryable = options.retryable ?? false;
@@ -3546,12 +3591,12 @@ function isRetryable(error) {
3546
3591
  return classified.retryable;
3547
3592
  }
3548
3593
  function sleep(ms, signal) {
3549
- return new Promise((resolve2, reject) => {
3594
+ return new Promise((resolve, reject) => {
3550
3595
  if (signal?.aborted) {
3551
3596
  reject(new ChatError("Retry aborted", { code: "ABORTED" /* ABORTED */ }));
3552
3597
  return;
3553
3598
  }
3554
- const timer = setTimeout(resolve2, ms);
3599
+ const timer = setTimeout(resolve, ms);
3555
3600
  signal?.addEventListener(
3556
3601
  "abort",
3557
3602
  () => {
@@ -3875,33 +3920,65 @@ var CancellableTimeout = class {
3875
3920
  }
3876
3921
  };
3877
3922
 
3878
- // src/chat/runtime.ts
3879
- var ChatRuntime = class {
3880
- _state;
3881
- _guard;
3882
- _backends;
3883
- _sessionStore;
3884
- _contextConfig;
3885
- _middleware;
3886
- _tools = /* @__PURE__ */ new Map();
3887
- _retryConfig;
3923
+ // src/chat/listener-set.ts
3924
+ var ListenerSet = class {
3925
+ _listeners = /* @__PURE__ */ new Set();
3926
+ /** Add a listener. Returns an unsubscribe function. */
3927
+ add(callback) {
3928
+ this._listeners.add(callback);
3929
+ return () => {
3930
+ this._listeners.delete(callback);
3931
+ };
3932
+ }
3933
+ /** Notify all listeners with the given arguments. Errors are isolated per listener. */
3934
+ notify(...args) {
3935
+ for (const cb of this._listeners) {
3936
+ try {
3937
+ cb(...args);
3938
+ } catch {
3939
+ }
3940
+ }
3941
+ }
3942
+ /** Remove all listeners. */
3943
+ clear() {
3944
+ this._listeners.clear();
3945
+ }
3946
+ /** Current number of listeners. */
3947
+ get size() {
3948
+ return this._listeners.size;
3949
+ }
3950
+ };
3951
+
3952
+ // src/chat/runtime.ts
3953
+ var ChatRuntime = class {
3954
+ _state;
3955
+ _guard;
3956
+ _backends;
3957
+ _sessionStore;
3958
+ _contextConfig;
3959
+ _ctxManager;
3960
+ _middleware;
3961
+ _tools = /* @__PURE__ */ new Map();
3962
+ _retryConfig;
3888
3963
  _contextStats = /* @__PURE__ */ new Map();
3964
+ _sessionUsage = /* @__PURE__ */ new Map();
3965
+ _modelContextWindows = /* @__PURE__ */ new Map();
3889
3966
  _onContextTrimmed;
3890
3967
  _streamTimeoutMs;
3891
- _sessionListeners = /* @__PURE__ */ new Set();
3892
- _activeAdapter = null;
3893
- _currentBackend;
3894
- _currentModel;
3895
- _activeSessionId = null;
3968
+ _sessionListeners = new ListenerSet();
3969
+ _adapterPool = /* @__PURE__ */ new Map();
3970
+ _defaultBackend;
3896
3971
  _abortController = null;
3897
3972
  constructor(options) {
3898
3973
  this._state = new StateMachine("idle", RUNTIME_TRANSITIONS);
3899
3974
  this._guard = new ChatReentrancyGuard();
3900
3975
  this._backends = options.backends;
3901
- this._currentBackend = options.defaultBackend;
3902
- this._currentModel = options.defaultModel;
3976
+ this._defaultBackend = options.defaultBackend;
3903
3977
  this._sessionStore = options.sessionStore;
3904
3978
  this._contextConfig = options.context;
3979
+ if (this._contextConfig) {
3980
+ this._ctxManager = new ContextWindowManager(this._contextConfig);
3981
+ }
3905
3982
  this._middleware = [...options.middleware ?? []];
3906
3983
  this._retryConfig = options.retryConfig;
3907
3984
  this._onContextTrimmed = options.onContextTrimmed;
@@ -3912,6 +3989,11 @@ var ChatRuntime = class {
3912
3989
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
3913
3990
  );
3914
3991
  }
3992
+ if (options.tools) {
3993
+ for (const tool of options.tools) {
3994
+ this._tools.set(tool.name, tool);
3995
+ }
3996
+ }
3915
3997
  }
3916
3998
  // ── Lifecycle ──────────────────────────────────────────────
3917
3999
  get status() {
@@ -3923,24 +4005,23 @@ var ChatRuntime = class {
3923
4005
  this._abortController?.dispose();
3924
4006
  this._abortController = null;
3925
4007
  this._state.transition("disposed");
3926
- if (this._activeAdapter) {
3927
- await this._activeAdapter.dispose();
3928
- this._activeAdapter = null;
4008
+ for (const adapter of this._adapterPool.values()) {
4009
+ try {
4010
+ await adapter.dispose();
4011
+ } catch {
4012
+ }
3929
4013
  }
4014
+ this._adapterPool.clear();
3930
4015
  }
3931
4016
  // ── Sessions ───────────────────────────────────────────────
3932
- get activeSessionId() {
3933
- return this._activeSessionId;
3934
- }
3935
4017
  async createSession(options) {
3936
4018
  this.assertNotDisposed();
3937
4019
  const config = {
3938
- model: options.config?.model ?? this._currentModel ?? "",
3939
- backend: options.config?.backend ?? this._currentBackend,
4020
+ model: options.config?.model ?? "",
4021
+ backend: options.config?.backend ?? this._defaultBackend,
3940
4022
  ...options.config
3941
4023
  };
3942
4024
  const session = await this._sessionStore.createSession({ ...options, config });
3943
- this._activeSessionId = session.id;
3944
4025
  this._notifySessionChange();
3945
4026
  return session;
3946
4027
  }
@@ -3960,36 +4041,12 @@ var ChatRuntime = class {
3960
4041
  if (!session) return;
3961
4042
  await this._sessionStore.deleteSession(cid);
3962
4043
  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);
4044
+ this._sessionUsage.delete(cid);
3972
4045
  this._notifySessionChange();
3973
4046
  }
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
4047
  // ── Messaging ──────────────────────────────────────────────
3988
4048
  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
- }
4049
+ this.validateSendInput(message, options);
3993
4050
  this._guard.acquire();
3994
4051
  const cid = toChatId(sessionId);
3995
4052
  this._abortController = new ChatAbortController(options?.signal);
@@ -3998,150 +4055,274 @@ var ChatRuntime = class {
3998
4055
  this._state.transition("idle");
3999
4056
  }
4000
4057
  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 = {
4058
+ await this.loadSession(cid);
4059
+ const mwCtx = {
4009
4060
  sessionId: cid,
4010
4061
  signal: this._abortController.signal
4011
4062
  };
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;
4063
+ const userMessage = await this.applyBeforeSendMiddleware(
4064
+ this.createUserMessage(message),
4065
+ mwCtx
4066
+ );
4067
+ if (userMessage === null) {
4068
+ this._state.transition("idle");
4069
+ return;
4041
4070
  }
4042
- const sessionForAdapter = {
4043
- ...updatedSession,
4044
- messages: messagesToSend
4045
- };
4046
- const adapter = await this.getOrCreateAdapterWithRetry();
4071
+ const updatedSession = await this.persistAndReload(cid, userMessage);
4072
+ const sessionForAdapter = await this.trimSessionContext(cid, updatedSession, options.model);
4073
+ const stream = await this.prepareEventStream(
4074
+ cid,
4075
+ sessionForAdapter,
4076
+ updatedSession,
4077
+ message,
4078
+ options
4079
+ );
4047
4080
  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
4081
  const eventSource = this._streamTimeoutMs ? withStreamWatchdog(stream, { timeoutMs: this._streamTimeoutMs, signal: this._abortController.signal }) : stream;
4060
4082
  for await (const event of eventSource) {
4061
4083
  if (this._abortController.isAborted) break;
4062
4084
  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);
4085
+ if (event.type === "usage") {
4086
+ this._sessionUsage.set(cid, {
4087
+ promptTokens: event.promptTokens,
4088
+ completionTokens: event.completionTokens
4089
+ });
4090
+ this.updateContextStatsWithUsage(cid, event.promptTokens, event.completionTokens, options);
4080
4091
  }
4092
+ const processed = await this.applyOnEventMiddleware(event, mwCtx);
4093
+ if (processed) yield processed;
4081
4094
  }
4082
- await this._sessionStore.appendMessage(cid, assistantMessage);
4083
- this._notifySessionChange();
4095
+ if (this._state.current === "disposed") return;
4096
+ await this.finalizeAssistantMessage(cid, accumulator, mwCtx);
4084
4097
  this._state.transition("idle");
4085
4098
  } 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;
4099
+ const result = await this.handleSendError(error, cid);
4100
+ if (result !== null) throw result;
4107
4101
  } finally {
4108
4102
  this._guard.release();
4109
4103
  this._abortController?.dispose();
4110
4104
  this._abortController = null;
4111
4105
  }
4112
4106
  }
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) {
4107
+ // ── Send Pipeline Stages ──────────────────────────────────────
4108
+ /** Stage 1: Validate send inputs (message content + required fields). */
4109
+ validateSendInput(message, options) {
4124
4110
  this.assertNotDisposed();
4125
- if (!this._backends[name]) {
4111
+ if (!message || message.trim().length === 0) {
4112
+ throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
4113
+ }
4114
+ if (!options.model) {
4115
+ throw new ChatError(
4116
+ "options.model is required \u2014 caller must specify which model to use",
4117
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4118
+ );
4119
+ }
4120
+ if (!options.backend) {
4126
4121
  throw new ChatError(
4127
- `Backend "${name}" not found in backends map`,
4122
+ "options.backend is required \u2014 caller must specify which backend to use",
4128
4123
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4129
4124
  );
4130
4125
  }
4131
- if (this._activeAdapter) {
4132
- await this._activeAdapter.dispose();
4133
- this._activeAdapter = null;
4126
+ if (!options.credentials) {
4127
+ throw new ChatError(
4128
+ "options.credentials is required \u2014 caller must provide authentication credentials",
4129
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4130
+ );
4131
+ }
4132
+ }
4133
+ /** Stage 2: Load session from store. */
4134
+ async loadSession(cid) {
4135
+ const session = await this._sessionStore.getSession(cid);
4136
+ if (!session) {
4137
+ throw new ChatError(
4138
+ `Session "${cid}" not found`,
4139
+ { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4140
+ );
4141
+ }
4142
+ return session;
4143
+ }
4144
+ /** Stage 3: Apply onBeforeSend middleware pipeline. Returns null if middleware rejected the send. */
4145
+ async applyBeforeSendMiddleware(userMessage, ctx) {
4146
+ let msg = userMessage;
4147
+ for (const mw of this._middleware) {
4148
+ if (mw.onBeforeSend && msg) {
4149
+ msg = await mw.onBeforeSend(msg, ctx);
4150
+ if (msg === null) return null;
4151
+ }
4152
+ }
4153
+ return msg;
4154
+ }
4155
+ /** Stage 4: Persist user message and reload session with full history. */
4156
+ async persistAndReload(cid, userMessage) {
4157
+ await this._sessionStore.appendMessage(cid, userMessage);
4158
+ return await this._sessionStore.getSession(cid);
4159
+ }
4160
+ /** Stage 5: Auto-trim context window if configured. Returns session snapshot for adapter. */
4161
+ async trimSessionContext(cid, session, model) {
4162
+ if (!this._ctxManager) return session;
4163
+ const ctxManager = this._ctxManager;
4164
+ const lastUsage = this._sessionUsage.get(cid);
4165
+ const modelContextWindow = model ? this._modelContextWindows.get(model) : void 0;
4166
+ if (lastUsage && modelContextWindow) {
4167
+ const result2 = ctxManager.fitMessagesWithUsage(
4168
+ session.messages,
4169
+ lastUsage.promptTokens,
4170
+ modelContextWindow
4171
+ );
4172
+ this._contextStats.set(cid, {
4173
+ totalTokens: result2.totalTokens,
4174
+ removedCount: result2.removedCount,
4175
+ wasTruncated: result2.wasTruncated,
4176
+ availableBudget: Math.max(0, modelContextWindow - result2.totalTokens),
4177
+ realPromptTokens: lastUsage.promptTokens,
4178
+ realCompletionTokens: lastUsage.completionTokens,
4179
+ modelContextWindow
4180
+ });
4181
+ if (result2.wasTruncated && this._onContextTrimmed) {
4182
+ const keptIds = new Set(result2.messages.map((m) => m.id));
4183
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4184
+ if (removed.length > 0) {
4185
+ try {
4186
+ this._onContextTrimmed(cid, removed);
4187
+ } catch {
4188
+ }
4189
+ }
4190
+ }
4191
+ return { ...session, messages: result2.messages };
4192
+ }
4193
+ const result = await ctxManager.fitMessagesAsync(session.messages);
4194
+ this._contextStats.set(cid, {
4195
+ totalTokens: result.totalTokens,
4196
+ removedCount: result.removedCount,
4197
+ wasTruncated: result.wasTruncated,
4198
+ availableBudget: ctxManager.availableBudget,
4199
+ modelContextWindow
4200
+ });
4201
+ if (result.wasTruncated && this._onContextTrimmed) {
4202
+ const keptIds = new Set(result.messages.map((m) => m.id));
4203
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4204
+ if (removed.length > 0) {
4205
+ try {
4206
+ this._onContextTrimmed(cid, removed);
4207
+ } catch {
4208
+ }
4209
+ }
4210
+ }
4211
+ return { ...session, messages: result.messages };
4212
+ }
4213
+ /** Update context stats with real usage data from a usage event. */
4214
+ updateContextStatsWithUsage(cid, promptTokens, completionTokens, options) {
4215
+ const modelContextWindow = options.model ? this._modelContextWindows.get(options.model) : void 0;
4216
+ const existing = this._contextStats.get(cid);
4217
+ this._contextStats.set(cid, {
4218
+ totalTokens: promptTokens,
4219
+ removedCount: existing?.removedCount ?? 0,
4220
+ wasTruncated: existing?.wasTruncated ?? false,
4221
+ availableBudget: modelContextWindow ? Math.max(0, modelContextWindow - promptTokens) : existing?.availableBudget ?? 0,
4222
+ realPromptTokens: promptTokens,
4223
+ realCompletionTokens: completionTokens,
4224
+ modelContextWindow
4225
+ });
4226
+ }
4227
+ /** Stage 6: Prepare event stream — adapter with retry, tool injection. */
4228
+ async prepareEventStream(cid, sessionForAdapter, fullSession, message, options) {
4229
+ const adapter = await this.getOrCreateAdapterWithRetry(options.backend, options.credentials);
4230
+ const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4231
+ sessionId: cid,
4232
+ custom: fullSession.metadata?.custom
4233
+ }) : void 0;
4234
+ const streamOptions = {
4235
+ signal: this._abortController.signal,
4236
+ model: options.model,
4237
+ systemPrompt: options.systemPrompt,
4238
+ tools: runtimeTools
4239
+ };
4240
+ return this.createStreamWithRetry(
4241
+ adapter,
4242
+ sessionForAdapter,
4243
+ message,
4244
+ streamOptions,
4245
+ options.backend,
4246
+ options.credentials
4247
+ );
4248
+ }
4249
+ /** Stage 7: Apply onEvent middleware pipeline (sequential transform/suppress). */
4250
+ async applyOnEventMiddleware(event, ctx) {
4251
+ let processed = event;
4252
+ for (const mw of this._middleware) {
4253
+ if (mw.onEvent && processed) {
4254
+ processed = await mw.onEvent(processed, ctx);
4255
+ }
4256
+ }
4257
+ return processed;
4258
+ }
4259
+ /** Stage 8: Finalize accumulator, apply afterReceive middleware, persist assistant message. */
4260
+ async finalizeAssistantMessage(cid, accumulator, ctx) {
4261
+ let assistantMessage = accumulator.finalize();
4262
+ for (const mw of this._middleware) {
4263
+ if (mw.onAfterReceive) {
4264
+ assistantMessage = await mw.onAfterReceive(assistantMessage, ctx);
4265
+ }
4266
+ }
4267
+ await this._sessionStore.appendMessage(cid, assistantMessage);
4268
+ this._notifySessionChange();
4269
+ }
4270
+ /** Stage 9: Error handling — apply onError middleware, transition state. Returns null if suppressed. */
4271
+ async handleSendError(error, cid) {
4272
+ let processedError = error instanceof Error ? error : new Error(String(error));
4273
+ const ctx = {
4274
+ sessionId: cid,
4275
+ signal: this._abortController?.signal ?? new AbortController().signal
4276
+ };
4277
+ for (const mw of this._middleware) {
4278
+ if (mw.onError) {
4279
+ const result = await mw.onError(processedError, ctx);
4280
+ if (result === null) {
4281
+ if (this._state.canTransition("idle")) {
4282
+ this._state.transition("idle");
4283
+ }
4284
+ return null;
4285
+ }
4286
+ processedError = result;
4287
+ }
4288
+ }
4289
+ if (this._state.canTransition("error")) {
4290
+ this._state.transition("error");
4134
4291
  }
4135
- this._currentBackend = name;
4292
+ return processedError;
4136
4293
  }
4137
- switchModel(model) {
4294
+ abort() {
4295
+ this._abortController?.abort("User abort");
4296
+ }
4297
+ // ── Backend / Model ────────────────────────────────────────
4298
+ async listModels(options) {
4138
4299
  this.assertNotDisposed();
4139
- this._currentModel = model;
4300
+ let models = [];
4301
+ const firstAdapter = [...this._adapterPool.values()][0];
4302
+ if (firstAdapter) {
4303
+ try {
4304
+ models = await firstAdapter.listModels();
4305
+ } catch {
4306
+ return [];
4307
+ }
4308
+ } else if (options?.backend && options?.credentials) {
4309
+ try {
4310
+ const adapter = await this.getOrCreateAdapter(options.backend, options.credentials);
4311
+ models = await adapter.listModels();
4312
+ } catch {
4313
+ return [];
4314
+ }
4315
+ }
4316
+ for (const model of models) {
4317
+ if (model.contextWindow != null) {
4318
+ this._modelContextWindows.set(model.id, model.contextWindow);
4319
+ }
4320
+ }
4321
+ return models;
4140
4322
  }
4141
- async listModels() {
4323
+ async listBackends() {
4142
4324
  this.assertNotDisposed();
4143
- const adapter = await this.getOrCreateAdapter();
4144
- return adapter.listModels();
4325
+ return Object.keys(this._backends).map((name) => ({ name }));
4145
4326
  }
4146
4327
  // ── Tools ──────────────────────────────────────────────────
4147
4328
  get registeredTools() {
@@ -4166,37 +4347,46 @@ var ChatRuntime = class {
4166
4347
  if (idx >= 0) this._middleware.splice(idx, 1);
4167
4348
  }
4168
4349
  // ── Context Stats ─────────────────────────────────────────
4169
- getContextStats(sessionId) {
4350
+ async getContextStats(sessionId) {
4170
4351
  const cid = toChatId(sessionId);
4171
4352
  return this._contextStats.get(cid) ?? null;
4172
4353
  }
4173
4354
  // ── Session Subscription ──────────────────────────────────
4174
4355
  onSessionChange(callback) {
4175
- this._sessionListeners.add(callback);
4176
- return () => {
4177
- this._sessionListeners.delete(callback);
4178
- };
4356
+ return this._sessionListeners.add(callback);
4179
4357
  }
4180
4358
  _notifySessionChange() {
4181
- for (const cb of this._sessionListeners) {
4182
- try {
4183
- cb();
4184
- } catch {
4185
- }
4186
- }
4359
+ this._sessionListeners.notify();
4187
4360
  }
4188
4361
  // ── Private Helpers ────────────────────────────────────────
4189
- async getOrCreateAdapter() {
4190
- if (this._activeAdapter) return this._activeAdapter;
4191
- const factory = this._backends[this._currentBackend];
4362
+ async getOrCreateAdapter(backend, credentials) {
4363
+ const key = this.getPoolKey(backend, credentials);
4364
+ const existing = this._adapterPool.get(key);
4365
+ if (existing) return existing;
4366
+ for (const [oldKey, oldAdapter] of this._adapterPool) {
4367
+ if (oldKey.startsWith(backend + ":")) {
4368
+ try {
4369
+ await oldAdapter.dispose();
4370
+ } catch {
4371
+ }
4372
+ this._adapterPool.delete(oldKey);
4373
+ }
4374
+ }
4375
+ const factory = this._backends[backend];
4192
4376
  if (!factory) {
4193
4377
  throw new ChatError(
4194
- `Backend "${this._currentBackend}" not found`,
4378
+ `Backend "${backend}" not found`,
4195
4379
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4196
4380
  );
4197
4381
  }
4198
- this._activeAdapter = await factory();
4199
- return this._activeAdapter;
4382
+ const adapter = await factory(credentials);
4383
+ this._adapterPool.set(key, adapter);
4384
+ return adapter;
4385
+ }
4386
+ getPoolKey(backend, credentials) {
4387
+ const token = credentials.accessToken;
4388
+ const hash = token.length > 16 ? token.slice(0, 8) + token.slice(-8) : token;
4389
+ return `${backend}:${hash}`;
4200
4390
  }
4201
4391
  /** Wrap each tool's execute to inject ToolContext as 2nd argument */
4202
4392
  injectToolContext(tools, context) {
@@ -4228,17 +4418,25 @@ var ChatRuntime = class {
4228
4418
  }
4229
4419
  }
4230
4420
  /** Get or create adapter with retry on connection errors */
4231
- async getOrCreateAdapterWithRetry() {
4421
+ async getOrCreateAdapterWithRetry(backend, credentials) {
4232
4422
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4233
4423
  const delayMs = this._retryConfig?.delayMs ?? 0;
4234
4424
  let lastError;
4235
4425
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4236
4426
  try {
4237
- return await this.getOrCreateAdapter();
4427
+ return await this.getOrCreateAdapter(backend, credentials);
4238
4428
  } catch (err) {
4239
4429
  lastError = err instanceof Error ? err : new Error(String(err));
4240
4430
  if (attempt < maxAttempts) {
4241
- this._activeAdapter = null;
4431
+ const key = this.getPoolKey(backend, credentials);
4432
+ const old = this._adapterPool.get(key);
4433
+ if (old) {
4434
+ try {
4435
+ await old.dispose();
4436
+ } catch {
4437
+ }
4438
+ }
4439
+ this._adapterPool.delete(key);
4242
4440
  await delay(delayMs);
4243
4441
  }
4244
4442
  }
@@ -4251,7 +4449,7 @@ var ChatRuntime = class {
4251
4449
  * retries with a fresh adapter. Once first event is received,
4252
4450
  * the stream is committed (no more retries).
4253
4451
  */
4254
- async createStreamWithRetry(adapter, session, message, options) {
4452
+ async createStreamWithRetry(adapter, session, message, options, backend, credentials) {
4255
4453
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4256
4454
  const delayMs = this._retryConfig?.delayMs ?? 0;
4257
4455
  let lastError;
@@ -4272,13 +4470,14 @@ var ChatRuntime = class {
4272
4470
  } catch (err) {
4273
4471
  lastError = err instanceof Error ? err : new Error(String(err));
4274
4472
  if (attempt < maxAttempts) {
4275
- if (this._activeAdapter) {
4276
- await this._activeAdapter.dispose().catch(() => {
4277
- });
4473
+ try {
4474
+ await currentAdapter.dispose();
4475
+ } catch {
4278
4476
  }
4279
- this._activeAdapter = null;
4477
+ const key = this.getPoolKey(backend, credentials);
4478
+ this._adapterPool.delete(key);
4280
4479
  await delay(delayMs);
4281
- currentAdapter = await this.getOrCreateAdapter();
4480
+ currentAdapter = await this.getOrCreateAdapter(backend, credentials);
4282
4481
  }
4283
4482
  }
4284
4483
  }
@@ -4286,16 +4485,17 @@ var ChatRuntime = class {
4286
4485
  }
4287
4486
  };
4288
4487
  function delay(ms) {
4289
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4488
+ return new Promise((resolve) => setTimeout(resolve, ms));
4290
4489
  }
4291
4490
  function createChatRuntime(options) {
4292
4491
  return new ChatRuntime(options);
4293
4492
  }
4294
4493
 
4295
4494
  // src/chat/storage.ts
4495
+ init_errors2();
4296
4496
  init_errors();
4297
4497
  var StorageError = class extends AgentSDKError {
4298
- /** Machine-readable error code */
4498
+ /** Machine-readable error code from the unified ErrorCode enum */
4299
4499
  code;
4300
4500
  constructor(message, code) {
4301
4501
  super(message);
@@ -4332,7 +4532,7 @@ var InMemoryStorage = class {
4332
4532
  if (this.data.has(key)) {
4333
4533
  throw new StorageError(
4334
4534
  `Item with key "${key}" already exists`,
4335
- "DUPLICATE_KEY"
4535
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4336
4536
  );
4337
4537
  }
4338
4538
  this.data.set(key, structuredClone(item));
@@ -4342,7 +4542,7 @@ var InMemoryStorage = class {
4342
4542
  if (!this.data.has(key)) {
4343
4543
  throw new StorageError(
4344
4544
  `Item with key "${key}" not found`,
4345
- "NOT_FOUND"
4545
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4346
4546
  );
4347
4547
  }
4348
4548
  this.data.set(key, structuredClone(item));
@@ -4352,7 +4552,7 @@ var InMemoryStorage = class {
4352
4552
  if (!this.data.has(key)) {
4353
4553
  throw new StorageError(
4354
4554
  `Item with key "${key}" not found`,
4355
- "NOT_FOUND"
4555
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4356
4556
  );
4357
4557
  }
4358
4558
  this.data.delete(key);
@@ -4376,25 +4576,25 @@ var FileStorage = class {
4376
4576
  constructor(options) {
4377
4577
  this.directory = options.directory;
4378
4578
  this.extension = options.extension ?? ".json";
4379
- this.ensureDirectory();
4579
+ this.ensureDirectorySync();
4380
4580
  }
4381
4581
  /** @inheritdoc */
4382
4582
  async get(key) {
4383
4583
  const filePath = this.keyToPath(key);
4384
- if (!fs.existsSync(filePath)) {
4584
+ if (!await this.fileExists(filePath)) {
4385
4585
  return null;
4386
4586
  }
4387
- return this.readFile(filePath);
4587
+ return this.readJsonFile(filePath);
4388
4588
  }
4389
4589
  /** @inheritdoc */
4390
4590
  async list(options) {
4391
- this.ensureDirectory();
4392
- const files = fs.readdirSync(this.directory).filter(
4591
+ await this.ensureDirectoryAsync();
4592
+ const files = (await promises.readdir(this.directory)).filter(
4393
4593
  (f) => f.endsWith(this.extension)
4394
4594
  );
4395
4595
  let items = [];
4396
4596
  for (const file of files) {
4397
- const item = this.readFile(path.join(this.directory, file));
4597
+ const item = await this.readJsonFile(path.join(this.directory, file));
4398
4598
  items.push(item);
4399
4599
  }
4400
4600
  if (options?.filter) {
@@ -4414,55 +4614,55 @@ var FileStorage = class {
4414
4614
  /** @inheritdoc */
4415
4615
  async create(key, item) {
4416
4616
  const filePath = this.keyToPath(key);
4417
- if (fs.existsSync(filePath)) {
4617
+ if (await this.fileExists(filePath)) {
4418
4618
  throw new StorageError(
4419
4619
  `Item with key "${key}" already exists`,
4420
- "DUPLICATE_KEY"
4620
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4421
4621
  );
4422
4622
  }
4423
- this.writeFile(filePath, item);
4623
+ await this.writeJsonFile(filePath, item);
4424
4624
  }
4425
4625
  /** @inheritdoc */
4426
4626
  async update(key, item) {
4427
4627
  const filePath = this.keyToPath(key);
4428
- if (!fs.existsSync(filePath)) {
4628
+ if (!await this.fileExists(filePath)) {
4429
4629
  throw new StorageError(
4430
4630
  `Item with key "${key}" not found`,
4431
- "NOT_FOUND"
4631
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4432
4632
  );
4433
4633
  }
4434
- this.writeFile(filePath, item);
4634
+ await this.writeJsonFile(filePath, item);
4435
4635
  }
4436
4636
  /** @inheritdoc */
4437
4637
  async delete(key) {
4438
4638
  const filePath = this.keyToPath(key);
4439
- if (!fs.existsSync(filePath)) {
4639
+ if (!await this.fileExists(filePath)) {
4440
4640
  throw new StorageError(
4441
4641
  `Item with key "${key}" not found`,
4442
- "NOT_FOUND"
4642
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4443
4643
  );
4444
4644
  }
4445
- fs.unlinkSync(filePath);
4645
+ await promises.unlink(filePath);
4446
4646
  }
4447
4647
  /** @inheritdoc */
4448
4648
  async has(key) {
4449
- return fs.existsSync(this.keyToPath(key));
4649
+ return this.fileExists(this.keyToPath(key));
4450
4650
  }
4451
4651
  /** @inheritdoc */
4452
4652
  async count() {
4453
- this.ensureDirectory();
4454
- return fs.readdirSync(this.directory).filter(
4653
+ await this.ensureDirectoryAsync();
4654
+ return (await promises.readdir(this.directory)).filter(
4455
4655
  (f) => f.endsWith(this.extension)
4456
4656
  ).length;
4457
4657
  }
4458
4658
  /** @inheritdoc */
4459
4659
  async clear() {
4460
- this.ensureDirectory();
4461
- const files = fs.readdirSync(this.directory).filter(
4660
+ await this.ensureDirectoryAsync();
4661
+ const files = (await promises.readdir(this.directory)).filter(
4462
4662
  (f) => f.endsWith(this.extension)
4463
4663
  );
4464
4664
  for (const file of files) {
4465
- fs.unlinkSync(path.join(this.directory, file));
4665
+ await promises.unlink(path.join(this.directory, file));
4466
4666
  }
4467
4667
  }
4468
4668
  keyToPath(key) {
@@ -4472,42 +4672,58 @@ var FileStorage = class {
4472
4672
  );
4473
4673
  return path.join(this.directory, `${safeKey}${this.extension}`);
4474
4674
  }
4475
- ensureDirectory() {
4675
+ /** Sync directory init — used only in constructor (one-time). */
4676
+ ensureDirectorySync() {
4476
4677
  if (!fs.existsSync(this.directory)) {
4477
4678
  fs.mkdirSync(this.directory, { recursive: true });
4478
4679
  }
4479
4680
  }
4480
- readFile(filePath) {
4681
+ /** Async directory init — used in operations. */
4682
+ async ensureDirectoryAsync() {
4683
+ if (!await this.fileExists(this.directory)) {
4684
+ await promises.mkdir(this.directory, { recursive: true });
4685
+ }
4686
+ }
4687
+ async fileExists(filePath) {
4688
+ try {
4689
+ await promises.access(filePath);
4690
+ return true;
4691
+ } catch {
4692
+ return false;
4693
+ }
4694
+ }
4695
+ async readJsonFile(filePath) {
4481
4696
  try {
4482
- const content = fs.readFileSync(filePath, "utf-8");
4697
+ const content = await promises.readFile(filePath, "utf-8");
4483
4698
  return JSON.parse(content);
4484
4699
  } catch (error) {
4485
4700
  if (error instanceof SyntaxError) {
4486
4701
  throw new StorageError(
4487
4702
  `Failed to parse file: ${filePath}`,
4488
- "SERIALIZATION_ERROR"
4703
+ "STORAGE_SERIALIZATION_ERROR" /* STORAGE_SERIALIZATION_ERROR */
4489
4704
  );
4490
4705
  }
4491
4706
  throw new StorageError(
4492
4707
  `Failed to read file: ${filePath}`,
4493
- "IO_ERROR"
4708
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4494
4709
  );
4495
4710
  }
4496
4711
  }
4497
- writeFile(filePath, item) {
4712
+ async writeJsonFile(filePath, item) {
4498
4713
  try {
4499
4714
  const content = JSON.stringify(item, null, 2);
4500
- fs.writeFileSync(filePath, content, "utf-8");
4715
+ await promises.writeFile(filePath, content, "utf-8");
4501
4716
  } catch {
4502
4717
  throw new StorageError(
4503
4718
  `Failed to write file: ${filePath}`,
4504
- "IO_ERROR"
4719
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4505
4720
  );
4506
4721
  }
4507
4722
  }
4508
4723
  };
4509
4724
 
4510
4725
  // src/chat/sessions.ts
4726
+ init_errors();
4511
4727
  var BaseSessionStore = class {
4512
4728
  constructor(adapter) {
4513
4729
  this.adapter = adapter;
@@ -4546,7 +4762,7 @@ var BaseSessionStore = class {
4546
4762
  async updateTitle(id, title) {
4547
4763
  const session = await this.adapter.get(id);
4548
4764
  if (!session) {
4549
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4765
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4550
4766
  }
4551
4767
  session.title = title;
4552
4768
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4555,7 +4771,7 @@ var BaseSessionStore = class {
4555
4771
  async updateConfig(id, config) {
4556
4772
  const session = await this.adapter.get(id);
4557
4773
  if (!session) {
4558
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4774
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4559
4775
  }
4560
4776
  session.config = { ...session.config, ...config };
4561
4777
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4567,7 +4783,7 @@ var BaseSessionStore = class {
4567
4783
  async appendMessage(sessionId, message) {
4568
4784
  const session = await this.adapter.get(sessionId);
4569
4785
  if (!session) {
4570
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4786
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4571
4787
  }
4572
4788
  session.messages.push(structuredClone(message));
4573
4789
  session.metadata.messageCount = session.messages.length;
@@ -4578,7 +4794,7 @@ var BaseSessionStore = class {
4578
4794
  if (messages.length === 0) return;
4579
4795
  const session = await this.adapter.get(sessionId);
4580
4796
  if (!session) {
4581
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4797
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4582
4798
  }
4583
4799
  for (const msg of messages) {
4584
4800
  session.messages.push(structuredClone(msg));
@@ -4590,7 +4806,7 @@ var BaseSessionStore = class {
4590
4806
  async loadMessages(sessionId, options) {
4591
4807
  const session = await this.adapter.get(sessionId);
4592
4808
  if (!session) {
4593
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4809
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4594
4810
  }
4595
4811
  const total = session.messages.length;
4596
4812
  const offset = options?.offset ?? 0;
@@ -4602,24 +4818,6 @@ var BaseSessionStore = class {
4602
4818
  hasMore: offset + limit < total
4603
4819
  };
4604
4820
  }
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
4821
  async searchSessions(options) {
4624
4822
  const query = options.query.toLowerCase();
4625
4823
  const limit = options.limit ?? 20;
@@ -4641,15 +4839,6 @@ var BaseSessionStore = class {
4641
4839
  async clear() {
4642
4840
  return this.adapter.clear();
4643
4841
  }
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
4842
  };
4654
4843
  var InMemorySessionStore = class extends BaseSessionStore {
4655
4844
  constructor() {
@@ -4662,28 +4851,59 @@ var FileSessionStore = class extends BaseSessionStore {
4662
4851
  }
4663
4852
  };
4664
4853
 
4854
+ // src/chat/backends/types.ts
4855
+ function isResumableBackend(adapter) {
4856
+ return "canResume" in adapter && typeof adapter.canResume === "function";
4857
+ }
4858
+
4665
4859
  // src/chat/backends/base.ts
4666
4860
  var BaseBackendAdapter = class {
4667
4861
  name;
4668
- _agentService;
4669
- _agent = null;
4862
+ _agentService = null;
4863
+ _agentServiceFactory = null;
4670
4864
  _disposed = false;
4671
4865
  _agentConfig;
4672
4866
  _ownsService;
4867
+ // Agent lifecycle: tracks current agent and the model it was created with.
4868
+ // For persistent sessions, reused across calls when model matches.
4869
+ // For non-persistent, recreated every call.
4870
+ _currentAgent = null;
4673
4871
  constructor(name, options) {
4674
4872
  this.name = name;
4675
4873
  this._agentConfig = options.agentConfig;
4676
4874
  if (options.agentService) {
4677
4875
  this._agentService = options.agentService;
4678
4876
  this._ownsService = false;
4877
+ } else if (options.agentServiceFactory) {
4878
+ this._agentServiceFactory = options.agentServiceFactory;
4879
+ this._ownsService = true;
4679
4880
  } else {
4680
4881
  this._agentService = this.createService();
4681
4882
  this._ownsService = true;
4682
4883
  }
4683
4884
  }
4684
4885
  get agentService() {
4886
+ if (!this._agentService) {
4887
+ if (this._agentServiceFactory) {
4888
+ this._agentService = this._agentServiceFactory();
4889
+ this._agentServiceFactory = null;
4890
+ } else {
4891
+ throw new ChatError("Agent service not available", {
4892
+ code: "BACKEND_NOT_INSTALLED" /* BACKEND_NOT_INSTALLED */
4893
+ });
4894
+ }
4895
+ }
4685
4896
  return this._agentService;
4686
4897
  }
4898
+ get currentModel() {
4899
+ return this._agentConfig.model;
4900
+ }
4901
+ /**
4902
+ * @deprecated No-op. Tools are passed per-call via SendMessageOptions.tools.
4903
+ * Kept for backward compatibility with code that calls setTools() directly.
4904
+ */
4905
+ setTools() {
4906
+ }
4687
4907
  async sendMessage(session, message, options) {
4688
4908
  this.assertNotDisposed();
4689
4909
  const events = this.streamMessage(session, message, options);
@@ -4711,7 +4931,7 @@ var BaseBackendAdapter = class {
4711
4931
  async *streamMessage(session, message, options) {
4712
4932
  this.assertNotDisposed();
4713
4933
  const agent = this.getOrCreateAgent(options);
4714
- const messages = session.messages.map(toAgentMessage);
4934
+ const messages = session.messages.flatMap(toAgentMessages);
4715
4935
  messages.push({ role: "user", content: message });
4716
4936
  yield* this.streamAgentEvents(agent, messages, options);
4717
4937
  }
@@ -4721,9 +4941,13 @@ var BaseBackendAdapter = class {
4721
4941
  */
4722
4942
  async *streamAgentEvents(agent, messages, options) {
4723
4943
  const messageId = createChatId();
4944
+ const model = options?.model ?? this._agentConfig.model ?? "";
4724
4945
  const agentEvents = agent.streamWithContext(messages, {
4946
+ model,
4725
4947
  signal: options?.signal,
4726
- context: options?.context
4948
+ context: options?.context,
4949
+ tools: options?.tools,
4950
+ ...options?.systemPrompt ? { systemMessage: options.systemPrompt } : {}
4727
4951
  });
4728
4952
  yield { type: "message:start", messageId, role: "assistant" };
4729
4953
  let text = "";
@@ -4749,31 +4973,46 @@ var BaseBackendAdapter = class {
4749
4973
  }
4750
4974
  async listModels() {
4751
4975
  this.assertNotDisposed();
4752
- return this._agentService.listModels();
4976
+ return this.agentService.listModels();
4753
4977
  }
4754
4978
  async validate() {
4755
4979
  this.assertNotDisposed();
4756
- return this._agentService.validate();
4980
+ return this.agentService.validate();
4757
4981
  }
4758
4982
  async dispose() {
4759
4983
  if (this._disposed) return;
4760
4984
  this._disposed = true;
4761
- this._agent?.dispose();
4762
- this._agent = null;
4763
- if (this._ownsService) {
4985
+ if (this._currentAgent) {
4986
+ this._currentAgent.instance.dispose();
4987
+ this._currentAgent = null;
4988
+ }
4989
+ if (this._ownsService && this._agentService && typeof this._agentService.dispose === "function") {
4764
4990
  await this._agentService.dispose();
4765
4991
  }
4766
4992
  }
4767
- /** Get or create an agent, applying model override from options */
4993
+ /** Get or create an agent. Model is passed per-call via RunOptions.
4994
+ * Tools are passed per-call via SendMessageOptions — not baked into config.
4995
+ * For persistent sessions, reuses agent when model matches. */
4768
4996
  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;
4997
+ const model = options?.model ?? this._agentConfig.model;
4998
+ if (this._agentConfig.sessionMode === "persistent" && this._currentAgent) {
4999
+ if (this._currentAgent.model === model) {
5000
+ return this._currentAgent.instance;
5001
+ }
5002
+ this._currentAgent.instance.dispose();
5003
+ this._currentAgent = null;
4772
5004
  }
4773
- const agent = this._agentService.createAgent(config);
4774
- if (this._agentConfig.sessionMode === "persistent") {
4775
- this._agent = agent;
5005
+ if (this._currentAgent) {
5006
+ this._currentAgent.instance.dispose();
5007
+ this._currentAgent = null;
4776
5008
  }
5009
+ const config = {
5010
+ ...this._agentConfig,
5011
+ ...model !== void 0 && { model },
5012
+ ...options?.tools?.length ? { tools: options.tools } : {}
5013
+ };
5014
+ const agent = this.agentService.createAgent(config);
5015
+ this._currentAgent = { instance: agent, model };
4777
5016
  return agent;
4778
5017
  }
4779
5018
  assertNotDisposed() {
@@ -4785,21 +5024,15 @@ var BaseBackendAdapter = class {
4785
5024
  }
4786
5025
  };
4787
5026
 
4788
- // src/chat/backends/copilot.ts
4789
- var CopilotChatAdapter = class extends BaseBackendAdapter {
5027
+ // src/chat/backends/resumable.ts
5028
+ var ResumableChatAdapter = class extends BaseBackendAdapter {
4790
5029
  _backendSessionId = null;
4791
- _copilotOptions;
4792
- constructor(options) {
5030
+ constructor(name, options) {
4793
5031
  const agentConfig = {
4794
5032
  ...options.agentConfig,
4795
5033
  sessionMode: "persistent"
4796
5034
  };
4797
- super("copilot", { ...options, agentConfig });
4798
- this._copilotOptions = options.copilotOptions;
4799
- }
4800
- createService() {
4801
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4802
- return createAgentService2("copilot", this._copilotOptions);
5035
+ super(name, { ...options, agentConfig });
4803
5036
  }
4804
5037
  get backendSessionId() {
4805
5038
  return this._backendSessionId;
@@ -4828,7 +5061,7 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
4828
5061
  { code: "SESSION_EXPIRED" /* SESSION_EXPIRED */ }
4829
5062
  );
4830
5063
  }
4831
- const messages = session.messages.map(toAgentMessage);
5064
+ const messages = session.messages.flatMap(toAgentMessages);
4832
5065
  yield* this.streamAgentEvents(agent, messages, options);
4833
5066
  }
4834
5067
  captureSessionId(agent) {
@@ -4838,81 +5071,325 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
4838
5071
  }
4839
5072
  };
4840
5073
 
5074
+ // src/chat/backends/copilot.ts
5075
+ var CopilotChatAdapter = class extends ResumableChatAdapter {
5076
+ _copilotOptions;
5077
+ constructor(options) {
5078
+ super("copilot", options);
5079
+ this._copilotOptions = options.copilotOptions;
5080
+ }
5081
+ createService() {
5082
+ const { createCopilotService: createCopilotService2 } = (init_copilot(), __toCommonJS(copilot_exports));
5083
+ return createCopilotService2(this._copilotOptions || {});
5084
+ }
5085
+ };
5086
+
4841
5087
  // src/chat/backends/claude.ts
4842
- var ClaudeChatAdapter = class extends BaseBackendAdapter {
4843
- _backendSessionId = null;
5088
+ var ClaudeChatAdapter = class extends ResumableChatAdapter {
4844
5089
  _claudeOptions;
4845
5090
  constructor(options) {
4846
- const agentConfig = {
4847
- ...options.agentConfig,
4848
- sessionMode: "persistent"
4849
- };
4850
- super("claude", { ...options, agentConfig });
5091
+ super("claude", options);
4851
5092
  this._claudeOptions = options.claudeOptions;
4852
5093
  }
4853
5094
  createService() {
4854
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4855
- return createAgentService2("claude", this._claudeOptions);
5095
+ const { createClaudeService: createClaudeService2 } = (init_claude(), __toCommonJS(claude_exports));
5096
+ return createClaudeService2(this._claudeOptions || {});
4856
5097
  }
4857
- get backendSessionId() {
4858
- return this._backendSessionId;
5098
+ };
5099
+
5100
+ // src/chat/backends/vercel-ai.ts
5101
+ var VercelAIChatAdapter = class extends BaseBackendAdapter {
5102
+ _vercelOptions;
5103
+ constructor(options) {
5104
+ super("vercel-ai", options);
5105
+ this._vercelOptions = options.vercelOptions;
4859
5106
  }
4860
- canResume() {
4861
- return this._backendSessionId !== null;
5107
+ createService() {
5108
+ const { createVercelAIService: createVercelAIService2 } = (init_vercel_ai(), __toCommonJS(vercel_ai_exports));
5109
+ return createVercelAIService2(this._vercelOptions || {});
4862
5110
  }
4863
- async *resume(session, backendSessionId, options) {
4864
- this.assertNotDisposed();
4865
- if (!backendSessionId) {
4866
- throw new ChatError("Backend session ID is required for resume", {
4867
- code: "INVALID_INPUT" /* INVALID_INPUT */
5111
+ captureSessionId(_agent) {
5112
+ }
5113
+ };
5114
+
5115
+ // src/backends/mock-llm.ts
5116
+ init_base_agent();
5117
+ init_errors2();
5118
+ function extractPrompt(messages) {
5119
+ for (let i = messages.length - 1; i >= 0; i--) {
5120
+ const msg = messages[i];
5121
+ if (msg.role === "user") {
5122
+ return typeof msg.content === "string" ? msg.content : msg.content.filter((p) => p.type === "text").map((p) => p.text).join("");
5123
+ }
5124
+ }
5125
+ return "";
5126
+ }
5127
+ function resolveResponse(mode, messages, callIndex) {
5128
+ switch (mode.type) {
5129
+ case "echo":
5130
+ return extractPrompt(messages);
5131
+ case "static":
5132
+ return mode.response;
5133
+ case "scripted": {
5134
+ if (mode.loop) {
5135
+ return mode.responses[callIndex % mode.responses.length];
5136
+ }
5137
+ if (callIndex < mode.responses.length) {
5138
+ return mode.responses[callIndex];
5139
+ }
5140
+ return mode.responses[mode.responses.length - 1];
5141
+ }
5142
+ case "error":
5143
+ throw new AgentSDKError(mode.error, {
5144
+ code: mode.code ?? "backend_error",
5145
+ retryable: mode.recoverable ?? false
4868
5146
  });
5147
+ }
5148
+ }
5149
+ async function applyLatency(latency, signal) {
5150
+ if (!latency) return;
5151
+ const ms = latency.type === "fixed" ? latency.ms : latency.minMs + Math.random() * (latency.maxMs - latency.minMs);
5152
+ if (ms <= 0) return;
5153
+ await new Promise((resolve, reject) => {
5154
+ const timer = setTimeout(resolve, ms);
5155
+ const onAbort = () => {
5156
+ clearTimeout(timer);
5157
+ reject(new Error("aborted"));
5158
+ };
5159
+ if (signal.aborted) {
5160
+ clearTimeout(timer);
5161
+ reject(new Error("aborted"));
5162
+ return;
4869
5163
  }
4870
- const agent = this.getOrCreateAgent(options);
4871
- const currentSessionId = agent.sessionId;
4872
- if (!currentSessionId) {
4873
- throw new ChatError(
4874
- `No active session to resume (requested: ${backendSessionId})`,
4875
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4876
- );
5164
+ signal.addEventListener("abort", onAbort, { once: true });
5165
+ });
5166
+ }
5167
+ function chunkText(text, streaming) {
5168
+ if (streaming?.chunkSize && streaming.chunkSize > 0) {
5169
+ const chunks = [];
5170
+ for (let i = 0; i < text.length; i += streaming.chunkSize) {
5171
+ chunks.push(text.slice(i, i + streaming.chunkSize));
4877
5172
  }
4878
- if (currentSessionId !== backendSessionId) {
4879
- throw new ChatError(
4880
- `Session expired: expected ${backendSessionId}, got ${currentSessionId}`,
4881
- { code: "SESSION_EXPIRED" /* SESSION_EXPIRED */ }
4882
- );
5173
+ return chunks;
5174
+ }
5175
+ return text.split(/(\s+)/).filter(Boolean);
5176
+ }
5177
+ async function chunkDelay(streaming, signal) {
5178
+ const ms = streaming?.chunkDelayMs;
5179
+ if (!ms || ms <= 0) return;
5180
+ await new Promise((resolve, reject) => {
5181
+ const timer = setTimeout(resolve, ms);
5182
+ const onAbort = () => {
5183
+ clearTimeout(timer);
5184
+ reject(new Error("aborted"));
5185
+ };
5186
+ if (signal.aborted) {
5187
+ clearTimeout(timer);
5188
+ reject(new Error("aborted"));
5189
+ return;
4883
5190
  }
4884
- const messages = session.messages.map(toAgentMessage);
4885
- yield* this.streamAgentEvents(agent, messages, options);
5191
+ signal.addEventListener("abort", onAbort, { once: true });
5192
+ });
5193
+ }
5194
+ var MockLLMAgent = class extends BaseAgent {
5195
+ backendName = "mock-llm";
5196
+ mode;
5197
+ latency;
5198
+ streaming;
5199
+ finishReason;
5200
+ permissions;
5201
+ toolCallConfigs;
5202
+ configuredStructuredOutput;
5203
+ callIndex = 0;
5204
+ constructor(config, options) {
5205
+ super(config);
5206
+ this.mode = options.mode ?? { type: "echo" };
5207
+ this.latency = options.latency;
5208
+ this.streaming = options.streaming;
5209
+ this.finishReason = options.finishReason ?? "stop";
5210
+ this.permissions = options.permissions;
5211
+ this.toolCallConfigs = options.toolCalls ?? [];
5212
+ this.configuredStructuredOutput = options.structuredOutput;
5213
+ }
5214
+ async executeRun(messages, _options, signal) {
5215
+ this.checkAbort(signal);
5216
+ await applyLatency(this.latency, signal);
5217
+ this.checkAbort(signal);
5218
+ const idx = this.callIndex++;
5219
+ const output = resolveResponse(this.mode, messages, idx);
5220
+ const toolCalls = this.toolCallConfigs.map((tc) => ({
5221
+ toolName: tc.toolName,
5222
+ args: tc.args ?? {},
5223
+ result: tc.result ?? null,
5224
+ approved: true
5225
+ }));
5226
+ return {
5227
+ output,
5228
+ structuredOutput: void 0,
5229
+ toolCalls,
5230
+ messages: [
5231
+ ...messages,
5232
+ { role: "assistant", content: output }
5233
+ ],
5234
+ usage: { promptTokens: 10, completionTokens: output.length }
5235
+ };
4886
5236
  }
4887
- captureSessionId(agent) {
4888
- if (agent.sessionId) {
4889
- this._backendSessionId = agent.sessionId;
5237
+ async executeRunStructured(messages, _schema, _options, signal) {
5238
+ this.checkAbort(signal);
5239
+ await applyLatency(this.latency, signal);
5240
+ this.checkAbort(signal);
5241
+ const idx = this.callIndex++;
5242
+ const output = resolveResponse(this.mode, messages, idx);
5243
+ let parsed;
5244
+ if (this.configuredStructuredOutput !== void 0) {
5245
+ parsed = this.configuredStructuredOutput;
5246
+ } else {
5247
+ try {
5248
+ parsed = JSON.parse(output);
5249
+ } catch {
5250
+ parsed = output;
5251
+ }
5252
+ }
5253
+ return {
5254
+ output,
5255
+ structuredOutput: parsed,
5256
+ toolCalls: [],
5257
+ messages: [
5258
+ ...messages,
5259
+ { role: "assistant", content: output }
5260
+ ],
5261
+ usage: { promptTokens: 10, completionTokens: output.length }
5262
+ };
5263
+ }
5264
+ async *executeStream(messages, _options, signal) {
5265
+ this.checkAbort(signal);
5266
+ await applyLatency(this.latency, signal);
5267
+ this.checkAbort(signal);
5268
+ if (this.permissions) {
5269
+ yield* this.simulatePermissions(signal);
5270
+ }
5271
+ if (this.toolCallConfigs.length > 0) {
5272
+ yield* this.simulateToolCalls(signal);
5273
+ }
5274
+ const idx = this.callIndex++;
5275
+ const output = resolveResponse(this.mode, messages, idx);
5276
+ const chunks = chunkText(output, this.streaming);
5277
+ for (let i = 0; i < chunks.length; i++) {
5278
+ this.checkAbort(signal);
5279
+ if (i > 0) {
5280
+ await chunkDelay(this.streaming, signal);
5281
+ }
5282
+ yield { type: "text_delta", text: chunks[i] };
5283
+ }
5284
+ yield {
5285
+ type: "usage_update",
5286
+ promptTokens: 10,
5287
+ completionTokens: output.length
5288
+ };
5289
+ yield {
5290
+ type: "done",
5291
+ finalOutput: output,
5292
+ finishReason: this.finishReason
5293
+ };
5294
+ }
5295
+ async *simulateToolCalls(signal) {
5296
+ for (let i = 0; i < this.toolCallConfigs.length; i++) {
5297
+ this.checkAbort(signal);
5298
+ const tc = this.toolCallConfigs[i];
5299
+ const toolCallId = tc.toolCallId ?? `mock-tc-${i}`;
5300
+ yield {
5301
+ type: "tool_call_start",
5302
+ toolCallId,
5303
+ toolName: tc.toolName,
5304
+ args: tc.args ?? {}
5305
+ };
5306
+ yield {
5307
+ type: "tool_call_end",
5308
+ toolCallId,
5309
+ toolName: tc.toolName,
5310
+ result: tc.result ?? null
5311
+ };
5312
+ }
5313
+ }
5314
+ async *simulatePermissions(signal) {
5315
+ const perms = this.permissions;
5316
+ for (const toolName of perms.toolNames) {
5317
+ this.checkAbort(signal);
5318
+ const request = {
5319
+ toolName,
5320
+ toolArgs: {}
5321
+ };
5322
+ yield { type: "permission_request", request };
5323
+ if (perms.denyTools?.includes(toolName)) {
5324
+ yield {
5325
+ type: "permission_response",
5326
+ toolName,
5327
+ decision: { allowed: false, reason: "Denied by mock configuration" }
5328
+ };
5329
+ } else if (perms.autoApprove) {
5330
+ yield {
5331
+ type: "permission_response",
5332
+ toolName,
5333
+ decision: { allowed: true, scope: "once" }
5334
+ };
5335
+ } else {
5336
+ const supervisor = this.getConfig().supervisor;
5337
+ if (supervisor?.onPermission) {
5338
+ const decision = await supervisor.onPermission(request, signal);
5339
+ yield { type: "permission_response", toolName, decision };
5340
+ } else {
5341
+ yield {
5342
+ type: "permission_response",
5343
+ toolName,
5344
+ decision: { allowed: true, scope: "once" }
5345
+ };
5346
+ }
5347
+ }
4890
5348
  }
4891
5349
  }
4892
5350
  };
4893
-
4894
- // src/chat/backends/vercel-ai.ts
4895
- var VercelAIChatAdapter = class extends BaseBackendAdapter {
4896
- _vercelOptions;
4897
- constructor(options) {
4898
- super("vercel-ai", options);
4899
- this._vercelOptions = options.vercelOptions;
5351
+ var MockLLMService = class {
5352
+ name = "mock-llm";
5353
+ options;
5354
+ models;
5355
+ constructor(options = {}) {
5356
+ this.options = options;
5357
+ this.models = (options.models ?? [
5358
+ { id: "mock-fast", name: "Mock Fast" },
5359
+ { id: "mock-quality", name: "Mock Quality" }
5360
+ ]).map((m) => ({
5361
+ id: m.id,
5362
+ name: m.name,
5363
+ description: m.description
5364
+ }));
4900
5365
  }
4901
- createService() {
4902
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4903
- return createAgentService2("vercel-ai", this._vercelOptions);
5366
+ createAgent(config) {
5367
+ return new MockLLMAgent(config, this.options);
4904
5368
  }
4905
- get backendSessionId() {
4906
- return null;
5369
+ async listModels() {
5370
+ return this.models;
4907
5371
  }
4908
- canResume() {
4909
- return false;
5372
+ async validate() {
5373
+ return { valid: true, errors: [] };
4910
5374
  }
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
- );
5375
+ async dispose() {
5376
+ }
5377
+ };
5378
+ function createMockLLMService(options = {}) {
5379
+ return new MockLLMService(options);
5380
+ }
5381
+
5382
+ // src/chat/backends/mock-llm.ts
5383
+ var MockLLMChatAdapter = class extends BaseBackendAdapter {
5384
+ constructor(options) {
5385
+ const mockOpts = options.mockOptions;
5386
+ super("mock-llm", {
5387
+ ...options,
5388
+ agentServiceFactory: () => createMockLLMService(mockOpts || {})
5389
+ });
5390
+ }
5391
+ createService() {
5392
+ return createMockLLMService({});
4916
5393
  }
4917
5394
  captureSessionId(_agent) {
4918
5395
  }
@@ -4993,16 +5470,22 @@ var SSEChatTransport = class {
4993
5470
  };
4994
5471
  async function streamToTransport(events, transport) {
4995
5472
  try {
4996
- let accumulatedText = "";
5473
+ const textChunks = [];
5474
+ let finishReason;
4997
5475
  for await (const event of events) {
4998
5476
  if (!transport.isOpen) break;
5477
+ if (event.type === "done") {
5478
+ finishReason = event.finishReason;
5479
+ continue;
5480
+ }
4999
5481
  transport.send(event);
5000
5482
  if (event.type === "message:delta") {
5001
- accumulatedText += event.text;
5483
+ textChunks.push(event.text);
5002
5484
  }
5003
5485
  }
5004
5486
  if (transport.isOpen) {
5005
- transport.send({ type: "done", finalOutput: accumulatedText || void 0 });
5487
+ const finalOutput = textChunks.length > 0 ? textChunks.join("") : void 0;
5488
+ transport.send({ type: "done", finalOutput, finishReason });
5006
5489
  }
5007
5490
  transport.close();
5008
5491
  } catch (err) {
@@ -5089,9 +5572,9 @@ var InProcessChatTransport = class {
5089
5572
  send(event) {
5090
5573
  if (!this._open) return;
5091
5574
  if (this._resolve) {
5092
- const resolve2 = this._resolve;
5575
+ const resolve = this._resolve;
5093
5576
  this._resolve = null;
5094
- resolve2({ value: event, done: false });
5577
+ resolve({ value: event, done: false });
5095
5578
  } else {
5096
5579
  this._buffer.push(event);
5097
5580
  }
@@ -5100,9 +5583,9 @@ var InProcessChatTransport = class {
5100
5583
  if (!this._open) return;
5101
5584
  this._open = false;
5102
5585
  if (this._resolve) {
5103
- const resolve2 = this._resolve;
5586
+ const resolve = this._resolve;
5104
5587
  this._resolve = null;
5105
- resolve2({ value: void 0, done: true });
5588
+ resolve({ value: void 0, done: true });
5106
5589
  }
5107
5590
  }
5108
5591
  error(err) {
@@ -5114,9 +5597,9 @@ var InProcessChatTransport = class {
5114
5597
  recoverable: false
5115
5598
  };
5116
5599
  if (this._resolve) {
5117
- const resolve2 = this._resolve;
5600
+ const resolve = this._resolve;
5118
5601
  this._resolve = null;
5119
- resolve2({ value: errorEvent, done: false });
5602
+ resolve({ value: errorEvent, done: false });
5120
5603
  } else {
5121
5604
  this._error = err;
5122
5605
  }
@@ -5141,8 +5624,8 @@ var InProcessChatTransport = class {
5141
5624
  if (!this._open) {
5142
5625
  return Promise.resolve({ value: void 0, done: true });
5143
5626
  }
5144
- return new Promise((resolve2) => {
5145
- this._resolve = resolve2;
5627
+ return new Promise((resolve) => {
5628
+ this._resolve = resolve;
5146
5629
  });
5147
5630
  }
5148
5631
  };
@@ -5315,9 +5798,7 @@ var ChatEventBus = class extends TypedEventEmitter {
5315
5798
 
5316
5799
  exports.BaseBackendAdapter = BaseBackendAdapter;
5317
5800
  exports.ChatError = ChatError;
5318
- exports.ChatErrorCode = ChatErrorCode;
5319
5801
  exports.ChatEventBus = ChatEventBus;
5320
- exports.ChatSDKError = ChatError;
5321
5802
  exports.ClaudeChatAdapter = ClaudeChatAdapter;
5322
5803
  exports.ContextWindowManager = ContextWindowManager;
5323
5804
  exports.CopilotChatAdapter = CopilotChatAdapter;
@@ -5325,7 +5806,9 @@ exports.ExponentialBackoffStrategy = ExponentialBackoffStrategy;
5325
5806
  exports.FileSessionStore = FileSessionStore;
5326
5807
  exports.InMemorySessionStore = InMemorySessionStore;
5327
5808
  exports.InProcessChatTransport = InProcessChatTransport;
5809
+ exports.ListenerSet = ListenerSet;
5328
5810
  exports.MessageAccumulator = MessageAccumulator;
5811
+ exports.MockLLMChatAdapter = MockLLMChatAdapter;
5329
5812
  exports.SSEChatTransport = SSEChatTransport;
5330
5813
  exports.TypedEventEmitter = TypedEventEmitter;
5331
5814
  exports.VercelAIChatAdapter = VercelAIChatAdapter;
@@ -5335,6 +5818,7 @@ exports.agentEventToChatEvent = agentEventToChatEvent;
5335
5818
  exports.classifyError = classifyError;
5336
5819
  exports.createChatId = createChatId;
5337
5820
  exports.createChatRuntime = createChatRuntime;
5821
+ exports.createTextMessage = createTextMessage;
5338
5822
  exports.estimateTokens = estimateTokens;
5339
5823
  exports.fromAgentMessage = fromAgentMessage;
5340
5824
  exports.getMessageReasoning = getMessageReasoning;
@@ -5345,13 +5829,16 @@ exports.isChatMessage = isChatMessage;
5345
5829
  exports.isChatSession = isChatSession;
5346
5830
  exports.isFilePart = isFilePart;
5347
5831
  exports.isMessagePart = isMessagePart;
5832
+ exports.isObservableSession = isObservableSession;
5348
5833
  exports.isReasoningPart = isReasoningPart;
5834
+ exports.isResumableBackend = isResumableBackend;
5349
5835
  exports.isRetryable = isRetryable;
5350
5836
  exports.isSourcePart = isSourcePart;
5351
5837
  exports.isTextPart = isTextPart;
5352
5838
  exports.isToolCallPart = isToolCallPart;
5353
5839
  exports.streamToTransport = streamToTransport;
5354
5840
  exports.toAgentMessage = toAgentMessage;
5841
+ exports.toAgentMessages = toAgentMessages;
5355
5842
  exports.toChatId = toChatId;
5356
5843
  exports.withRetry = withRetry;
5357
5844
  exports.withStreamWatchdog = withStreamWatchdog;