@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,8 +1,6 @@
1
- import * as fs from 'fs';
2
- import { existsSync, readdirSync, unlinkSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
- import * as path from 'path';
1
+ import { existsSync, mkdirSync } from 'fs';
2
+ import { readdir, unlink, mkdir, access, readFile, writeFile } from 'fs/promises';
4
3
  import { join } from 'path';
5
- import * as os from 'os';
6
4
 
7
5
  var __defProp = Object.defineProperty;
8
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -25,16 +23,100 @@ var __copyProps = (to, from, except, desc) => {
25
23
  };
26
24
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
25
 
28
- // src/errors.ts
29
- var AgentSDKError, ReentrancyError, DisposedError, BackendNotFoundError, BackendAlreadyRegisteredError, SubprocessError, DependencyError, AbortError, ToolExecutionError, StructuredOutputError;
26
+ // src/types/errors.ts
27
+ function isRecoverableErrorCode(code) {
28
+ return RECOVERABLE_CODES.has(code);
29
+ }
30
+ function classifyAgentError(error) {
31
+ const msg = (error instanceof Error ? error.message : error).toLowerCase();
32
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("timedout") || msg.includes("etimedout")) {
33
+ return "TIMEOUT" /* TIMEOUT */;
34
+ }
35
+ if (msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429") || msg.includes("too many requests")) {
36
+ return "RATE_LIMIT" /* RATE_LIMIT */;
37
+ }
38
+ if (msg.includes("unauthorized") || msg.includes("401") || msg.includes("auth") && (msg.includes("expired") || msg.includes("invalid") || msg.includes("denied") || msg.includes("failed"))) {
39
+ return "AUTH_EXPIRED" /* AUTH_EXPIRED */;
40
+ }
41
+ if (msg.includes("econnrefused") || msg.includes("econnreset") || msg.includes("enotfound") || msg.includes("network") || msg.includes("fetch failed") || msg.includes("socket hang up")) {
42
+ return "NETWORK" /* NETWORK */;
43
+ }
44
+ if (msg.includes("subprocess") || msg.includes("process exited") || msg.includes("spawn") || msg.includes("enoent") || msg.includes("killed")) {
45
+ return "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */;
46
+ }
47
+ if (msg.includes("abort") || msg.includes("cancel")) {
48
+ return "ABORTED" /* ABORTED */;
49
+ }
50
+ 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")) {
51
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
52
+ }
53
+ return "PROVIDER_ERROR" /* PROVIDER_ERROR */;
54
+ }
55
+ var ErrorCode, RECOVERABLE_CODES;
30
56
  var init_errors = __esm({
57
+ "src/types/errors.ts"() {
58
+ ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
59
+ ErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
60
+ ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
61
+ ErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
62
+ ErrorCode2["NETWORK"] = "NETWORK";
63
+ ErrorCode2["TIMEOUT"] = "TIMEOUT";
64
+ ErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
65
+ ErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
66
+ ErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
67
+ ErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
68
+ ErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
69
+ ErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
70
+ ErrorCode2["REENTRANCY"] = "REENTRANCY";
71
+ ErrorCode2["DISPOSED"] = "DISPOSED";
72
+ ErrorCode2["ABORTED"] = "ABORTED";
73
+ ErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
74
+ ErrorCode2["DEPENDENCY_MISSING"] = "DEPENDENCY_MISSING";
75
+ ErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
76
+ ErrorCode2["TOOL_EXECUTION"] = "TOOL_EXECUTION";
77
+ ErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
78
+ ErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
79
+ ErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
80
+ ErrorCode2["PROVIDER_NOT_FOUND"] = "PROVIDER_NOT_FOUND";
81
+ ErrorCode2["AUTH_REQUIRED"] = "AUTH_REQUIRED";
82
+ ErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
83
+ ErrorCode2["STORAGE_NOT_FOUND"] = "STORAGE_NOT_FOUND";
84
+ ErrorCode2["STORAGE_DUPLICATE_KEY"] = "STORAGE_DUPLICATE_KEY";
85
+ ErrorCode2["STORAGE_IO_ERROR"] = "STORAGE_IO_ERROR";
86
+ ErrorCode2["STORAGE_SERIALIZATION_ERROR"] = "STORAGE_SERIALIZATION_ERROR";
87
+ return ErrorCode2;
88
+ })(ErrorCode || {});
89
+ RECOVERABLE_CODES = /* @__PURE__ */ new Set([
90
+ "TIMEOUT" /* TIMEOUT */,
91
+ "RATE_LIMIT" /* RATE_LIMIT */,
92
+ "NETWORK" /* NETWORK */,
93
+ "TOOL_EXECUTION" /* TOOL_EXECUTION */,
94
+ "MODEL_OVERLOADED" /* MODEL_OVERLOADED */,
95
+ "PROVIDER_ERROR" /* PROVIDER_ERROR */
96
+ ]);
97
+ }
98
+ });
99
+
100
+ // src/errors.ts
101
+ var AgentSDKError, ReentrancyError, DisposedError, SubprocessError, DependencyError, AbortError, ToolExecutionError, ActivityTimeoutError;
102
+ var init_errors2 = __esm({
31
103
  "src/errors.ts"() {
104
+ init_errors();
32
105
  AgentSDKError = class extends Error {
33
106
  /** @internal Marker for cross-bundle identity checks */
34
107
  _agentSDKError = true;
108
+ /** Machine-readable error code. Prefer values from the ErrorCode enum. */
109
+ code;
110
+ /** Whether this error is safe to retry */
111
+ retryable;
112
+ /** HTTP status code hint for error classification */
113
+ httpStatus;
35
114
  constructor(message, options) {
36
115
  super(message, options);
37
116
  this.name = "AgentSDKError";
117
+ this.code = options?.code;
118
+ this.retryable = options?.retryable ?? false;
119
+ this.httpStatus = options?.httpStatus;
38
120
  }
39
121
  /** Check if an error is an AgentSDKError (works across bundled copies) */
40
122
  static is(error) {
@@ -43,83 +125,84 @@ var init_errors = __esm({
43
125
  };
44
126
  ReentrancyError = class extends AgentSDKError {
45
127
  constructor() {
46
- super("Agent is already running. Await the current run before starting another.");
128
+ super("Agent is already running. Await the current run before starting another.", {
129
+ code: "REENTRANCY" /* REENTRANCY */
130
+ });
47
131
  this.name = "ReentrancyError";
48
132
  }
49
133
  };
50
134
  DisposedError = class extends AgentSDKError {
51
135
  constructor(entity) {
52
- super(`${entity} has been disposed and cannot be used.`);
136
+ super(`${entity} has been disposed and cannot be used.`, {
137
+ code: "DISPOSED" /* DISPOSED */
138
+ });
53
139
  this.name = "DisposedError";
54
140
  }
55
141
  };
56
- BackendNotFoundError = class extends AgentSDKError {
57
- constructor(backend) {
58
- super(
59
- `Unknown backend: "${backend}". Built-in: copilot, claude, vercel-ai. Custom: use registerBackend() first.`
60
- );
61
- this.name = "BackendNotFoundError";
62
- }
63
- };
64
- BackendAlreadyRegisteredError = class extends AgentSDKError {
65
- constructor(backend) {
66
- super(`Backend "${backend}" is already registered. Use a different name or unregister first.`);
67
- this.name = "BackendAlreadyRegisteredError";
68
- }
69
- };
70
142
  SubprocessError = class extends AgentSDKError {
71
143
  constructor(message, options) {
72
- super(message, options);
144
+ super(message, { ...options, code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */ });
73
145
  this.name = "SubprocessError";
74
146
  }
75
147
  };
76
148
  DependencyError = class extends AgentSDKError {
77
149
  packageName;
78
150
  constructor(packageName) {
79
- super(`${packageName} is not installed. Install it: npm install ${packageName}`);
151
+ super(`${packageName} is not installed. Install it: npm install ${packageName}`, {
152
+ code: "DEPENDENCY_MISSING" /* DEPENDENCY_MISSING */
153
+ });
80
154
  this.name = "DependencyError";
81
155
  this.packageName = packageName;
82
156
  }
83
157
  };
84
158
  AbortError = class extends AgentSDKError {
85
159
  constructor() {
86
- super("Agent run was aborted.");
160
+ super("Agent run was aborted.", { code: "ABORTED" /* ABORTED */ });
87
161
  this.name = "AbortError";
88
162
  }
89
163
  };
90
164
  ToolExecutionError = class extends AgentSDKError {
91
165
  toolName;
92
166
  constructor(toolName, message, options) {
93
- super(`Tool "${toolName}" failed: ${message}`, options);
167
+ super(`Tool "${toolName}" failed: ${message}`, { ...options, code: "TOOL_EXECUTION" /* TOOL_EXECUTION */ });
94
168
  this.name = "ToolExecutionError";
95
169
  this.toolName = toolName;
96
170
  }
97
171
  };
98
- StructuredOutputError = class extends AgentSDKError {
99
- constructor(message, options) {
100
- super(`Structured output error: ${message}`, options);
101
- this.name = "StructuredOutputError";
172
+ ActivityTimeoutError = class extends AgentSDKError {
173
+ constructor(timeoutMs) {
174
+ super(`Stream activity timeout: no event received within ${timeoutMs}ms.`, {
175
+ code: "TIMEOUT" /* TIMEOUT */,
176
+ retryable: true
177
+ });
178
+ this.name = "ActivityTimeoutError";
102
179
  }
103
180
  };
104
181
  }
105
182
  });
106
183
 
107
- // src/types.ts
108
- function isToolDefinition(tool) {
109
- return "execute" in tool && typeof tool.execute === "function";
110
- }
111
- function isTextContent(content) {
112
- return typeof content === "string";
113
- }
114
- function isMultiPartContent(content) {
115
- return Array.isArray(content);
116
- }
184
+ // src/types/guards.ts
117
185
  function getTextContent(content) {
118
186
  if (typeof content === "string") return content;
119
187
  return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
120
188
  }
189
+ var init_guards = __esm({
190
+ "src/types/guards.ts"() {
191
+ }
192
+ });
193
+
194
+ // src/types/index.ts
121
195
  var init_types = __esm({
196
+ "src/types/index.ts"() {
197
+ init_errors();
198
+ init_guards();
199
+ }
200
+ });
201
+
202
+ // src/types.ts
203
+ var init_types2 = __esm({
122
204
  "src/types.ts"() {
205
+ init_types();
123
206
  }
124
207
  });
125
208
 
@@ -127,12 +210,15 @@ var init_types = __esm({
127
210
  var BaseAgent;
128
211
  var init_base_agent = __esm({
129
212
  "src/base-agent.ts"() {
213
+ init_errors2();
214
+ init_errors2();
130
215
  init_errors();
131
216
  BaseAgent = class {
132
217
  state = "idle";
133
218
  abortController = null;
134
219
  config;
135
220
  _cleanupExternalSignal = null;
221
+ _streamMiddleware = [];
136
222
  /** CLI session ID for persistent mode. Override in backends that support it. */
137
223
  get sessionId() {
138
224
  return void 0;
@@ -148,8 +234,11 @@ var init_base_agent = __esm({
148
234
  this.state = "running";
149
235
  try {
150
236
  const messages = [{ role: "user", content: prompt }];
151
- const result = await this.executeRun(messages, options, ac.signal);
152
- this.enrichAndNotifyUsage(result);
237
+ const result = await this.withRetry(
238
+ () => this.executeRun(messages, options, ac.signal),
239
+ options
240
+ );
241
+ this.enrichAndNotifyUsage(result, options);
153
242
  return result;
154
243
  } finally {
155
244
  this.cleanupRun();
@@ -161,8 +250,11 @@ var init_base_agent = __esm({
161
250
  const ac = this.createAbortController(options?.signal);
162
251
  this.state = "running";
163
252
  try {
164
- const result = await this.executeRun(messages, options, ac.signal);
165
- this.enrichAndNotifyUsage(result);
253
+ const result = await this.withRetry(
254
+ () => this.executeRun(messages, options, ac.signal),
255
+ options
256
+ );
257
+ this.enrichAndNotifyUsage(result, options);
166
258
  return result;
167
259
  } finally {
168
260
  this.cleanupRun();
@@ -175,13 +267,11 @@ var init_base_agent = __esm({
175
267
  this.state = "running";
176
268
  try {
177
269
  const messages = [{ role: "user", content: prompt }];
178
- const result = await this.executeRunStructured(
179
- messages,
180
- schema,
181
- options,
182
- ac.signal
270
+ const result = await this.withRetry(
271
+ () => this.executeRunStructured(messages, schema, options, ac.signal),
272
+ options
183
273
  );
184
- this.enrichAndNotifyUsage(result);
274
+ this.enrichAndNotifyUsage(result, options);
185
275
  return result;
186
276
  } finally {
187
277
  this.cleanupRun();
@@ -194,8 +284,10 @@ var init_base_agent = __esm({
194
284
  this.state = "streaming";
195
285
  try {
196
286
  const messages = [{ role: "user", content: prompt }];
197
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
198
- yield* this.heartbeatStream(enriched);
287
+ yield* this.streamWithRetry(
288
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
289
+ options
290
+ );
199
291
  } finally {
200
292
  this.cleanupRun();
201
293
  }
@@ -206,12 +298,37 @@ var init_base_agent = __esm({
206
298
  const ac = this.createAbortController(options?.signal);
207
299
  this.state = "streaming";
208
300
  try {
209
- const enriched = this.enrichStream(this.executeStream(messages, options, ac.signal));
210
- yield* this.heartbeatStream(enriched);
301
+ yield* this.streamWithRetry(
302
+ () => this.applyStreamPipeline(this.executeStream(messages, options, ac.signal), options, ac),
303
+ options
304
+ );
211
305
  } finally {
212
306
  this.cleanupRun();
213
307
  }
214
308
  }
309
+ /** Register a stream middleware. Applied in registration order after built-in transforms. */
310
+ addStreamMiddleware(middleware) {
311
+ this.guardDisposed();
312
+ this._streamMiddleware.push(middleware);
313
+ }
314
+ /** Apply built-in transforms (enrich→timeout→heartbeat) then custom middleware */
315
+ async *applyStreamPipeline(source, options, ac) {
316
+ let stream = this.enrichStream(source, options);
317
+ stream = this.activityTimeoutStream(stream, options?.activityTimeoutMs, ac);
318
+ stream = this.heartbeatStream(stream);
319
+ if (this._streamMiddleware.length > 0) {
320
+ const ctx = {
321
+ model: options.model,
322
+ backend: this.backendName,
323
+ abortController: ac,
324
+ config: Object.freeze({ ...this.config })
325
+ };
326
+ for (const mw of this._streamMiddleware) {
327
+ stream = mw(stream, ctx);
328
+ }
329
+ }
330
+ yield* stream;
331
+ }
215
332
  abort() {
216
333
  if (this.abortController) {
217
334
  this.abortController.abort();
@@ -234,26 +351,109 @@ var init_base_agent = __esm({
234
351
  this.abort();
235
352
  this.state = "disposed";
236
353
  }
354
+ // ─── Retry Logic ─────────────────────────────────────────────
355
+ /** Check if an error should be retried given the retry configuration. */
356
+ isRetryableError(error, retry) {
357
+ if (error instanceof AbortError || error instanceof ReentrancyError || error instanceof DisposedError) {
358
+ return false;
359
+ }
360
+ if (AgentSDKError.is(error)) {
361
+ if (retry.retryableErrors && retry.retryableErrors.length > 0 && error.code) {
362
+ return retry.retryableErrors.includes(error.code);
363
+ }
364
+ if (error.retryable) return true;
365
+ if (error.code) return isRecoverableErrorCode(error.code);
366
+ }
367
+ return false;
368
+ }
369
+ /** Execute a function with retry logic per RetryConfig. */
370
+ async withRetry(fn, options) {
371
+ const retry = options?.retry;
372
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
373
+ return fn();
374
+ }
375
+ const maxRetries = retry.maxRetries;
376
+ const initialDelay = retry.initialDelayMs ?? 1e3;
377
+ const multiplier = retry.backoffMultiplier ?? 2;
378
+ let lastError;
379
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
380
+ try {
381
+ return await fn();
382
+ } catch (err) {
383
+ lastError = err;
384
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
385
+ throw err;
386
+ }
387
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
388
+ await new Promise((resolve) => setTimeout(resolve, delay2));
389
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
390
+ throw err;
391
+ }
392
+ }
393
+ }
394
+ throw lastError;
395
+ }
396
+ /** Execute a stream factory with pre-stream retry: retries until first event, then committed. */
397
+ async *streamWithRetry(factory, options) {
398
+ const retry = options?.retry;
399
+ if (!retry || !retry.maxRetries || retry.maxRetries <= 0) {
400
+ yield* factory();
401
+ return;
402
+ }
403
+ const maxRetries = retry.maxRetries;
404
+ const initialDelay = retry.initialDelayMs ?? 1e3;
405
+ const multiplier = retry.backoffMultiplier ?? 2;
406
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
407
+ try {
408
+ const stream = factory();
409
+ const iterator = stream[Symbol.asyncIterator]();
410
+ const first = await iterator.next();
411
+ if (first.done) return;
412
+ yield first.value;
413
+ while (true) {
414
+ const next = await iterator.next();
415
+ if (next.done) break;
416
+ yield next.value;
417
+ }
418
+ return;
419
+ } catch (err) {
420
+ if (attempt >= maxRetries || !this.isRetryableError(err, retry)) {
421
+ throw err;
422
+ }
423
+ const delay2 = initialDelay * Math.pow(multiplier, attempt);
424
+ await new Promise((resolve) => setTimeout(resolve, delay2));
425
+ if (options?.signal?.aborted || this.abortController?.signal.aborted) {
426
+ throw err;
427
+ }
428
+ }
429
+ }
430
+ }
431
+ // ─── CallOptions Resolution ──────────────────────────────────
432
+ /** Resolve tools to use for this call (per-call override > config default) */
433
+ resolveTools(options) {
434
+ return options?.tools ?? this.config.tools ?? [];
435
+ }
237
436
  // ─── Usage Enrichment ───────────────────────────────────────────
238
437
  /** Enrich result usage with model/backend and fire onUsage callback */
239
- enrichAndNotifyUsage(result) {
438
+ enrichAndNotifyUsage(result, options) {
240
439
  if (result.usage) {
241
440
  result.usage = {
242
441
  ...result.usage,
243
- model: this.config.model,
442
+ model: options.model,
244
443
  backend: this.backendName
245
444
  };
246
445
  this.callOnUsage(result.usage);
247
446
  }
248
447
  }
249
448
  /** Wrap a stream to enrich usage_update events and fire onUsage callback */
250
- async *enrichStream(source) {
449
+ async *enrichStream(source, options) {
450
+ const model = options.model;
251
451
  for await (const event of source) {
252
452
  if (event.type === "usage_update") {
253
453
  const usage = {
254
454
  promptTokens: event.promptTokens,
255
455
  completionTokens: event.completionTokens,
256
- model: this.config.model,
456
+ model,
257
457
  backend: this.backendName
258
458
  };
259
459
  this.callOnUsage(usage);
@@ -289,9 +489,9 @@ var init_base_agent = __esm({
289
489
  let heartbeatResolve = null;
290
490
  const timer = setInterval(() => {
291
491
  if (heartbeatResolve) {
292
- const resolve2 = heartbeatResolve;
492
+ const resolve = heartbeatResolve;
293
493
  heartbeatResolve = null;
294
- resolve2();
494
+ resolve();
295
495
  }
296
496
  }, interval);
297
497
  try {
@@ -299,8 +499,8 @@ var init_base_agent = __esm({
299
499
  if (!pendingEvent) {
300
500
  pendingEvent = iterator.next();
301
501
  }
302
- const heartbeatPromise = new Promise((resolve2) => {
303
- heartbeatResolve = resolve2;
502
+ const heartbeatPromise = new Promise((resolve) => {
503
+ heartbeatResolve = resolve;
304
504
  });
305
505
  const eventDone = pendingEvent.then(
306
506
  (r) => ({ kind: "event", result: r })
@@ -323,6 +523,35 @@ var init_base_agent = __esm({
323
523
  heartbeatResolve = null;
324
524
  }
325
525
  }
526
+ // ─── Activity Timeout ────────────────────────────────────────
527
+ /** Wrap a stream to abort on inactivity. Resets timer on every event.
528
+ * When timeoutMs is not set, passes through directly. */
529
+ async *activityTimeoutStream(source, timeoutMs, ac) {
530
+ if (!timeoutMs || timeoutMs <= 0) {
531
+ yield* source;
532
+ return;
533
+ }
534
+ const iterator = source[Symbol.asyncIterator]();
535
+ let timerId;
536
+ try {
537
+ while (true) {
538
+ const timeoutPromise = new Promise((_, reject) => {
539
+ timerId = setTimeout(() => reject(new ActivityTimeoutError(timeoutMs)), timeoutMs);
540
+ });
541
+ const result = await Promise.race([iterator.next(), timeoutPromise]);
542
+ clearTimeout(timerId);
543
+ if (result.done) break;
544
+ yield result.value;
545
+ }
546
+ } catch (err) {
547
+ if (err instanceof ActivityTimeoutError) {
548
+ ac.abort(err);
549
+ }
550
+ throw err;
551
+ } finally {
552
+ clearTimeout(timerId);
553
+ }
554
+ }
326
555
  // ─── Guards ───────────────────────────────────────────────────
327
556
  guardReentrancy() {
328
557
  if (this.state === "running" || this.state === "streaming") {
@@ -427,6 +656,66 @@ var init_schema = __esm({
427
656
  }
428
657
  });
429
658
 
659
+ // src/backends/shared.ts
660
+ function extractLastUserPrompt(messages) {
661
+ for (let i = messages.length - 1; i >= 0; i--) {
662
+ const msg = messages[i];
663
+ if (msg.role === "user") {
664
+ return getTextContent(msg.content);
665
+ }
666
+ }
667
+ return "";
668
+ }
669
+ function serializeToolCall(tc) {
670
+ const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
671
+ return ` Tool call: ${tc.name}(${args})`;
672
+ }
673
+ function serializeToolResult(tr) {
674
+ const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
675
+ const prefix = tr.isError ? "[ERROR] " : "";
676
+ return ` ${tr.name} \u2192 ${prefix}${result}`;
677
+ }
678
+ function buildContextualPrompt(messages) {
679
+ if (messages.length <= 1) {
680
+ return extractLastUserPrompt(messages);
681
+ }
682
+ const history = messages.slice(0, -1).map((msg) => {
683
+ if (msg.role === "user") {
684
+ return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
685
+ }
686
+ if (msg.role === "tool" && msg.toolResults) {
687
+ const results = msg.toolResults.map(serializeToolResult).join("\n");
688
+ return `Tool results:
689
+ ${results}`;
690
+ }
691
+ if (msg.role === "assistant") {
692
+ const parts = [];
693
+ const thinking = msg.thinking;
694
+ if (thinking) {
695
+ parts.push(`[reasoning: ${thinking}]`);
696
+ }
697
+ const text2 = msg.content ? getTextContent(msg.content) : "";
698
+ if (text2) parts.push(text2);
699
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
700
+ parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
701
+ }
702
+ return `Assistant: ${parts.join("\n")}`;
703
+ }
704
+ const text = msg.content ? getTextContent(msg.content) : "";
705
+ return `${msg.role}: ${text}`;
706
+ }).join("\n");
707
+ const lastPrompt = extractLastUserPrompt(messages);
708
+ return `Conversation history:
709
+ ${history}
710
+
711
+ User: ${lastPrompt}`;
712
+ }
713
+ var init_shared = __esm({
714
+ "src/backends/shared.ts"() {
715
+ init_types2();
716
+ }
717
+ });
718
+
430
719
  // src/backends/copilot.ts
431
720
  var copilot_exports = {};
432
721
  __export(copilot_exports, {
@@ -435,10 +724,9 @@ __export(copilot_exports, {
435
724
  createCopilotService: () => createCopilotService
436
725
  });
437
726
  async function loadSDK() {
438
- if (sdkModule) return sdkModule;
727
+ if (_sdkMock) return _sdkMock;
439
728
  try {
440
- sdkModule = await import('@github/copilot-sdk');
441
- return sdkModule;
729
+ return await import('@github/copilot-sdk');
442
730
  } catch {
443
731
  throw new SubprocessError(
444
732
  "@github/copilot-sdk is not installed. Install it: npm install @github/copilot-sdk"
@@ -446,10 +734,10 @@ async function loadSDK() {
446
734
  }
447
735
  }
448
736
  function _injectSDK(mock) {
449
- sdkModule = mock;
737
+ _sdkMock = mock;
450
738
  }
451
739
  function _resetSDK() {
452
- sdkModule = null;
740
+ _sdkMock = null;
453
741
  }
454
742
  function mapToolsToSDK(tools) {
455
743
  return tools.map((tool) => ({
@@ -469,17 +757,6 @@ function convertParameters(params) {
469
757
  }
470
758
  return params;
471
759
  }
472
- async function mapToolsToSDKAsync(tools) {
473
- return tools.map((tool) => ({
474
- name: tool.name,
475
- description: tool.description,
476
- parameters: convertParameters(tool.parameters),
477
- handler: async (args) => {
478
- const result = await tool.execute(args);
479
- return typeof result === "string" ? result : JSON.stringify(result);
480
- }
481
- }));
482
- }
483
760
  function buildPermissionHandler(config) {
484
761
  const onPermission = config.supervisor?.onPermission;
485
762
  if (!onPermission) {
@@ -494,6 +771,7 @@ function buildPermissionHandler(config) {
494
771
  const unifiedRequest = {
495
772
  toolName,
496
773
  toolArgs: { ...request },
774
+ toolCallId: request.toolCallId,
497
775
  rawSDKRequest: request
498
776
  };
499
777
  const ac = new AbortController();
@@ -598,15 +876,21 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
598
876
  };
599
877
  case "session.error":
600
878
  console.error("[copilot] mapSessionEvent error:", JSON.stringify(data));
601
- return {
602
- type: "error",
603
- error: String(data.message ?? "Unknown error"),
604
- recoverable: false
605
- };
879
+ {
880
+ const errorMsg = String(data.message ?? "Unknown error");
881
+ const code = classifyAgentError(errorMsg);
882
+ return {
883
+ type: "error",
884
+ error: errorMsg,
885
+ recoverable: isRecoverableErrorCode(code),
886
+ code
887
+ };
888
+ }
606
889
  case "assistant.message": {
607
890
  const doneEvent = {
608
891
  type: "done",
609
- finalOutput: data.content ? String(data.content) : null
892
+ finalOutput: null,
893
+ streamed: true
610
894
  };
611
895
  if (thinkingTracker.endThinking()) {
612
896
  return [{ type: "thinking_end" }, doneEvent];
@@ -617,66 +901,13 @@ function mapSessionEvent(event, tracker, thinkingTracker) {
617
901
  return null;
618
902
  }
619
903
  }
620
- function extractLastUserPrompt(messages) {
621
- for (let i = messages.length - 1; i >= 0; i--) {
622
- const msg = messages[i];
623
- if (msg.role === "user") {
624
- return getTextContent(msg.content);
625
- }
626
- }
627
- return "";
628
- }
629
- function serializeToolCall(tc) {
630
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
631
- return ` Tool call: ${tc.name}(${args})`;
632
- }
633
- function serializeToolResult(tr) {
634
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
635
- const prefix = tr.isError ? "[ERROR] " : "";
636
- return ` ${tr.name} \u2192 ${prefix}${result}`;
637
- }
638
- function buildContextualPrompt(messages) {
639
- if (messages.length <= 1) {
640
- return extractLastUserPrompt(messages);
641
- }
642
- const history = messages.slice(0, -1).map((msg) => {
643
- if (msg.role === "user") {
644
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
645
- }
646
- if (msg.role === "tool" && msg.toolResults) {
647
- const results = msg.toolResults.map(serializeToolResult).join("\n");
648
- return `Tool results:
649
- ${results}`;
650
- }
651
- if (msg.role === "assistant") {
652
- const parts = [];
653
- const thinking = msg.thinking;
654
- if (thinking) {
655
- parts.push(`[reasoning: ${thinking}]`);
656
- }
657
- const text2 = msg.content ? getTextContent(msg.content) : "";
658
- if (text2) parts.push(text2);
659
- if (msg.toolCalls && msg.toolCalls.length > 0) {
660
- parts.push(msg.toolCalls.map(serializeToolCall).join("\n"));
661
- }
662
- return `Assistant: ${parts.join("\n")}`;
663
- }
664
- const text = msg.content ? getTextContent(msg.content) : "";
665
- return `${msg.role}: ${text}`;
666
- }).join("\n");
667
- const lastPrompt = extractLastUserPrompt(messages);
668
- return `Conversation history:
669
- ${history}
670
-
671
- User: ${lastPrompt}`;
672
- }
673
904
  function withTimeout(promise, ms, message) {
674
- return new Promise((resolve2, reject) => {
905
+ return new Promise((resolve, reject) => {
675
906
  const timer = setTimeout(() => reject(new SubprocessError(message)), ms);
676
907
  promise.then(
677
908
  (val) => {
678
909
  clearTimeout(timer);
679
- resolve2(val);
910
+ resolve(val);
680
911
  },
681
912
  (err) => {
682
913
  clearTimeout(timer);
@@ -688,14 +919,15 @@ function withTimeout(promise, ms, message) {
688
919
  function createCopilotService(options) {
689
920
  return new CopilotAgentService(options);
690
921
  }
691
- var sdkModule, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
922
+ var _sdkMock, ToolCallTracker, ThinkingTracker, CopilotAgent, CopilotAgentService;
692
923
  var init_copilot = __esm({
693
924
  "src/backends/copilot.ts"() {
694
- init_types();
925
+ init_types2();
695
926
  init_base_agent();
696
- init_errors();
927
+ init_errors2();
697
928
  init_schema();
698
- sdkModule = null;
929
+ init_shared();
930
+ _sdkMock = null;
699
931
  ToolCallTracker = class {
700
932
  map = /* @__PURE__ */ new Map();
701
933
  trackStart(toolCallId, toolName, args) {
@@ -743,6 +975,7 @@ var init_copilot = __esm({
743
975
  isPersistent;
744
976
  persistentSession = null;
745
977
  _sessionId;
978
+ _persistentModel;
746
979
  activeSession = null;
747
980
  _resumeSessionId;
748
981
  _toolsReady = null;
@@ -761,15 +994,15 @@ var init_copilot = __esm({
761
994
  },
762
995
  onPermissionRequest: buildPermissionHandler(config),
763
996
  onUserInputRequest: buildUserInputHandler(config),
764
- ...config.availableTools?.length ? { availableTools: config.availableTools } : {}
997
+ ...config.availableTools ? { availableTools: config.availableTools } : {}
765
998
  };
766
999
  this._toolsReady = this._initToolsAsync(config);
767
1000
  this._resumeSessionId = resumeSessionId;
768
1001
  }
769
- /** Pre-convert Zod schemas to JSON Schema asynchronously.
1002
+ /** Pre-convert Zod schemas to JSON Schema.
770
1003
  * Updates sdkTools and sessionConfig.tools before first session creation. */
771
1004
  async _initToolsAsync(config) {
772
- this.sdkTools = await mapToolsToSDKAsync(config.tools ?? []);
1005
+ this.sdkTools = mapToolsToSDK(config.tools ?? []);
773
1006
  this.sessionConfig.tools = this.sdkTools;
774
1007
  }
775
1008
  get sessionId() {
@@ -793,47 +1026,63 @@ var init_copilot = __esm({
793
1026
  });
794
1027
  this.persistentSession = null;
795
1028
  this._sessionId = void 0;
1029
+ this._persistentModel = void 0;
796
1030
  }
797
1031
  }
798
- async getOrCreateSession(streaming) {
1032
+ async getOrCreateSession(streaming, options) {
799
1033
  if (this.isPersistent && this.persistentSession) {
800
- return { session: this.persistentSession, isNew: false };
1034
+ if (options.model !== this._persistentModel) {
1035
+ this.persistentSession.destroy().catch(() => {
1036
+ });
1037
+ this.persistentSession = null;
1038
+ this._sessionId = void 0;
1039
+ } else {
1040
+ return { session: this.persistentSession, isNew: false };
1041
+ }
801
1042
  }
802
1043
  if (this._toolsReady) {
803
1044
  await this._toolsReady;
804
1045
  this._toolsReady = null;
805
1046
  }
1047
+ const sessionConfig = { ...this.sessionConfig };
1048
+ sessionConfig.model = options.model;
1049
+ const resolvedTools = this.resolveTools(options);
1050
+ if (options?.tools) {
1051
+ sessionConfig.tools = mapToolsToSDK(resolvedTools);
1052
+ }
806
1053
  const client = await this.getClient();
807
1054
  if (this._resumeSessionId) {
808
1055
  const storedId = this._resumeSessionId;
809
1056
  this._resumeSessionId = void 0;
810
1057
  try {
811
1058
  const session2 = await client.resumeSession(storedId, {
812
- ...this.sessionConfig,
1059
+ ...sessionConfig,
813
1060
  streaming: this.isPersistent ? true : streaming
814
1061
  });
815
1062
  if (this.isPersistent) {
816
1063
  this.persistentSession = session2;
817
1064
  this._sessionId = session2.sessionId;
1065
+ this._persistentModel = options.model;
818
1066
  }
819
1067
  return { session: session2, isNew: false };
820
1068
  } catch {
821
1069
  }
822
1070
  }
823
1071
  const session = await client.createSession({
824
- ...this.sessionConfig,
1072
+ ...sessionConfig,
825
1073
  streaming: this.isPersistent ? true : streaming
826
1074
  });
827
1075
  if (this.isPersistent) {
828
1076
  this.persistentSession = session;
829
1077
  this._sessionId = session.sessionId;
1078
+ this._persistentModel = options.model;
830
1079
  }
831
1080
  return { session, isNew: true };
832
1081
  }
833
1082
  // ─── executeRun ─────────────────────────────────────────────────
834
- async executeRun(messages, _options, signal) {
1083
+ async executeRun(messages, options, signal) {
835
1084
  this.checkAbort(signal);
836
- const { session, isNew: isNewSession } = await this.getOrCreateSession(false);
1085
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(false, options);
837
1086
  this.activeSession = session;
838
1087
  const prompt = this.isPersistent && !isNewSession ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
839
1088
  const tracker = new ToolCallTracker();
@@ -930,9 +1179,9 @@ You MUST respond with ONLY valid JSON matching this schema:
930
1179
  };
931
1180
  }
932
1181
  // ─── executeStream ──────────────────────────────────────────────
933
- async *executeStream(messages, _options, signal) {
1182
+ async *executeStream(messages, options, signal) {
934
1183
  this.checkAbort(signal);
935
- const { session, isNew: isNewSession } = await this.getOrCreateSession(true);
1184
+ const { session, isNew: isNewSession } = await this.getOrCreateSession(true, options);
936
1185
  this.activeSession = session;
937
1186
  if (isNewSession) {
938
1187
  yield this.emitSessionInfo(session.sessionId);
@@ -949,8 +1198,8 @@ You MUST respond with ONLY valid JSON matching this schema:
949
1198
  notify = null;
950
1199
  }
951
1200
  };
952
- const waitForItem = () => new Promise((resolve2) => {
953
- notify = resolve2;
1201
+ const waitForItem = () => new Promise((resolve) => {
1202
+ notify = resolve;
954
1203
  });
955
1204
  const unsubscribe = session.on((event) => {
956
1205
  const mapped = mapSessionEvent(event, tracker, thinkingTracker);
@@ -1044,7 +1293,11 @@ You MUST respond with ONLY valid JSON matching this schema:
1044
1293
  githubToken: this.options.githubToken,
1045
1294
  useLoggedInUser: this.options.useLoggedInUser ?? !this.options.githubToken,
1046
1295
  ...this.options.cliArgs ? { cliArgs: this.options.cliArgs } : {},
1047
- ...this.options.env ? { env: { ...process.env, ...this.options.env } } : {}
1296
+ env: {
1297
+ ...process.env,
1298
+ ...this.options.githubToken ? { GITHUB_TOKEN: this.options.githubToken } : {},
1299
+ ...this.options.env
1300
+ }
1048
1301
  });
1049
1302
  const startupTimeout = this.options.startupTimeoutMs ?? 3e4;
1050
1303
  await withTimeout(client.start(), startupTimeout, "CLI startup timed out");
@@ -1078,7 +1331,10 @@ You MUST respond with ONLY valid JSON matching this schema:
1078
1331
  return models.map((m) => ({
1079
1332
  id: m.id,
1080
1333
  name: m.name,
1081
- provider: "copilot"
1334
+ provider: "copilot",
1335
+ ...m.capabilities?.limits?.max_context_window_tokens != null && {
1336
+ contextWindow: m.capabilities.limits.max_context_window_tokens
1337
+ }
1082
1338
  }));
1083
1339
  }
1084
1340
  async validate() {
@@ -1131,10 +1387,9 @@ function stripMcpPrefix(name) {
1131
1387
  return name.startsWith(MCP_TOOL_PREFIX) ? name.slice(MCP_TOOL_PREFIX.length) : name;
1132
1388
  }
1133
1389
  async function loadSDK2() {
1134
- if (sdkModule2) return sdkModule2;
1390
+ if (_sdkMock2) return _sdkMock2;
1135
1391
  try {
1136
- sdkModule2 = await import('@anthropic-ai/claude-agent-sdk');
1137
- return sdkModule2;
1392
+ return await import('@anthropic-ai/claude-agent-sdk');
1138
1393
  } catch {
1139
1394
  throw new SubprocessError(
1140
1395
  "@anthropic-ai/claude-agent-sdk is not installed. Install it: npm install @anthropic-ai/claude-agent-sdk"
@@ -1142,13 +1397,32 @@ async function loadSDK2() {
1142
1397
  }
1143
1398
  }
1144
1399
  function _injectSDK2(mock) {
1145
- sdkModule2 = mock;
1400
+ _sdkMock2 = mock;
1146
1401
  }
1147
1402
  function _resetSDK2() {
1148
- sdkModule2 = null;
1403
+ _sdkMock2 = null;
1404
+ }
1405
+ function normalizeAskUserInput(args) {
1406
+ if (typeof args.question === "string") {
1407
+ return {
1408
+ question: args.question,
1409
+ choices: Array.isArray(args.choices) ? args.choices : void 0,
1410
+ allowFreeform: args.allowFreeform !== false
1411
+ };
1412
+ }
1413
+ const questions = args.questions;
1414
+ if (questions && questions.length > 0) {
1415
+ const first = questions[0];
1416
+ return {
1417
+ question: first.question,
1418
+ choices: first.options?.map((o) => o.label),
1419
+ allowFreeform: true
1420
+ };
1421
+ }
1422
+ return { question: JSON.stringify(args), allowFreeform: true };
1149
1423
  }
1150
- function buildMcpServer(sdk, tools, toolResultCapture) {
1151
- if (tools.length === 0) return void 0;
1424
+ function buildMcpServer(sdk, tools, toolResultCapture, onAskUser) {
1425
+ if (tools.length === 0 && !onAskUser) return void 0;
1152
1426
  const mcpTools = tools.map((tool) => {
1153
1427
  const zodSchema = tool.parameters;
1154
1428
  const inputSchema = zodSchema.shape ?? zodToJsonSchema(tool.parameters);
@@ -1172,6 +1446,39 @@ function buildMcpServer(sdk, tools, toolResultCapture) {
1172
1446
  }
1173
1447
  );
1174
1448
  });
1449
+ if (onAskUser) {
1450
+ const askUserTool = sdk.tool(
1451
+ "ask_user",
1452
+ "Ask the user a question and wait for their response",
1453
+ {
1454
+ question: { type: "string", description: "The question to ask the user" },
1455
+ choices: {
1456
+ type: "array",
1457
+ items: { type: "string" },
1458
+ description: "Optional list of choices for multiple choice"
1459
+ },
1460
+ questions: {
1461
+ type: "array",
1462
+ items: {
1463
+ type: "object",
1464
+ properties: {
1465
+ question: { type: "string" },
1466
+ options: { type: "array", items: { type: "object", properties: { label: { type: "string" } } } }
1467
+ }
1468
+ },
1469
+ description: "Alternative nested question format"
1470
+ }
1471
+ },
1472
+ async (args) => {
1473
+ const normalized = normalizeAskUserInput(args);
1474
+ const response = await onAskUser(normalized, AbortSignal.timeout(3e5));
1475
+ return {
1476
+ content: [{ type: "text", text: response.answer }]
1477
+ };
1478
+ }
1479
+ );
1480
+ mcpTools.push(askUserTool);
1481
+ }
1175
1482
  return sdk.createSdkMcpServer({
1176
1483
  name: MCP_SERVER_NAME,
1177
1484
  version: "1.0.0",
@@ -1221,6 +1528,7 @@ function buildCanUseTool(config) {
1221
1528
  const unifiedRequest = {
1222
1529
  toolName,
1223
1530
  toolArgs: input,
1531
+ toolCallId: options.toolUseID,
1224
1532
  suggestedScope: extractSuggestedScope(options.suggestions),
1225
1533
  rawSDKRequest: { toolName, input, ...options }
1226
1534
  };
@@ -1273,6 +1581,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1273
1581
  if (block.type === "tool_use") {
1274
1582
  const toolCallId = String(block.id ?? "");
1275
1583
  const toolName = stripMcpPrefix(block.name ?? "unknown");
1584
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1276
1585
  if (toolCallTracker) {
1277
1586
  toolCallTracker.trackStart(toolCallId, toolName);
1278
1587
  }
@@ -1296,6 +1605,7 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1296
1605
  case "tool_use_summary": {
1297
1606
  const summary = msg.summary;
1298
1607
  const toolName = stripMcpPrefix(msg.tool_name ?? "unknown");
1608
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) return null;
1299
1609
  const precedingIds = msg.preceding_tool_use_ids;
1300
1610
  let toolCallId = "";
1301
1611
  if (precedingIds && precedingIds.length > 0) {
@@ -1349,10 +1659,13 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1349
1659
  }
1350
1660
  if (msg.is_error) {
1351
1661
  const r = msg;
1662
+ const errorMsg = r.errors?.join("; ") ?? "Unknown error";
1663
+ const code = classifyAgentError(errorMsg);
1352
1664
  return {
1353
1665
  type: "error",
1354
- error: r.errors?.join("; ") ?? "Unknown error",
1355
- recoverable: false
1666
+ error: errorMsg,
1667
+ recoverable: isRecoverableErrorCode(code),
1668
+ code
1356
1669
  };
1357
1670
  }
1358
1671
  return null;
@@ -1361,72 +1674,21 @@ function mapSDKMessage(msg, thinkingBlockIndices, toolCallTracker) {
1361
1674
  return null;
1362
1675
  }
1363
1676
  }
1364
- function extractLastUserPrompt2(messages) {
1365
- for (let i = messages.length - 1; i >= 0; i--) {
1366
- const msg = messages[i];
1367
- if (msg.role === "user") {
1368
- return getTextContent(msg.content);
1369
- }
1370
- }
1371
- return "";
1372
- }
1373
- function serializeToolCall2(tc) {
1374
- const args = typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args);
1375
- return ` Tool call: ${tc.name}(${args})`;
1376
- }
1377
- function serializeToolResult2(tr) {
1378
- const result = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result);
1379
- const prefix = tr.isError ? "[ERROR] " : "";
1380
- return ` ${tr.name} \u2192 ${prefix}${result}`;
1677
+ function createClaudeService(options) {
1678
+ return new ClaudeAgentService(options);
1381
1679
  }
1382
- function buildContextualPrompt2(messages) {
1383
- if (messages.length <= 1) {
1384
- return extractLastUserPrompt2(messages);
1385
- }
1386
- const history = messages.slice(0, -1).map((msg) => {
1387
- if (msg.role === "user") {
1388
- return `User: ${msg.content ? getTextContent(msg.content) : ""}`;
1389
- }
1390
- if (msg.role === "tool" && msg.toolResults) {
1391
- const results = msg.toolResults.map(serializeToolResult2).join("\n");
1392
- return `Tool results:
1393
- ${results}`;
1394
- }
1395
- if (msg.role === "assistant") {
1396
- const parts = [];
1397
- const thinking = msg.thinking;
1398
- if (thinking) {
1399
- parts.push(`[reasoning: ${thinking}]`);
1400
- }
1401
- const text2 = msg.content ? getTextContent(msg.content) : "";
1402
- if (text2) parts.push(text2);
1403
- if (msg.toolCalls && msg.toolCalls.length > 0) {
1404
- parts.push(msg.toolCalls.map(serializeToolCall2).join("\n"));
1405
- }
1406
- return `Assistant: ${parts.join("\n")}`;
1407
- }
1408
- const text = msg.content ? getTextContent(msg.content) : "";
1409
- return `${msg.role}: ${text}`;
1410
- }).join("\n");
1411
- const lastPrompt = extractLastUserPrompt2(messages);
1412
- return `Conversation history:
1413
- ${history}
1414
-
1415
- User: ${lastPrompt}`;
1416
- }
1417
- function createClaudeService(options) {
1418
- return new ClaudeAgentService(options);
1419
- }
1420
- var MCP_SERVER_NAME, MCP_TOOL_PREFIX, sdkModule2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1680
+ var MCP_SERVER_NAME, MCP_TOOL_PREFIX, CLAUDE_INTERNAL_TOOL_NAMES, _sdkMock2, ANTHROPIC_MODELS_URL, ANTHROPIC_API_VERSION, ANTHROPIC_OAUTH_BETA, ClaudeToolCallTracker, ClaudeAgent, ClaudeAgentService;
1421
1681
  var init_claude = __esm({
1422
1682
  "src/backends/claude.ts"() {
1423
- init_types();
1683
+ init_types2();
1424
1684
  init_base_agent();
1425
- init_errors();
1685
+ init_errors2();
1426
1686
  init_schema();
1687
+ init_shared();
1427
1688
  MCP_SERVER_NAME = "agent-sdk-tools";
1428
1689
  MCP_TOOL_PREFIX = `mcp__${MCP_SERVER_NAME}__`;
1429
- sdkModule2 = null;
1690
+ CLAUDE_INTERNAL_TOOL_NAMES = /* @__PURE__ */ new Set(["AskUserQuestion"]);
1691
+ _sdkMock2 = null;
1430
1692
  ANTHROPIC_MODELS_URL = "https://api.anthropic.com/v1/models";
1431
1693
  ANTHROPIC_API_VERSION = "2023-06-01";
1432
1694
  ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
@@ -1471,11 +1733,6 @@ var init_claude = __esm({
1471
1733
  if (options.resumeSessionId) {
1472
1734
  this._sessionId = options.resumeSessionId;
1473
1735
  }
1474
- if (config.supervisor?.onAskUser) {
1475
- console.warn(
1476
- "[agent-sdk/claude] supervisor.onAskUser is not supported by the Claude CLI backend. User interaction requests from the model will not be forwarded."
1477
- );
1478
- }
1479
1736
  }
1480
1737
  get sessionId() {
1481
1738
  return this._sessionId;
@@ -1499,12 +1756,12 @@ var init_claude = __esm({
1499
1756
  const transcriptPath = home ? `${home}/.claude/projects/.session/sessions/${sessionId}/conversation.jsonl` : void 0;
1500
1757
  return { type: "session_info", sessionId, transcriptPath, backend: "claude" };
1501
1758
  }
1502
- buildQueryOptions(signal) {
1759
+ buildQueryOptions(signal, options) {
1503
1760
  const ac = new AbortController();
1504
1761
  signal.addEventListener("abort", () => ac.abort(), { once: true });
1505
1762
  const opts = {
1506
1763
  abortController: ac,
1507
- model: this.config.model,
1764
+ model: options.model,
1508
1765
  maxTurns: this.options.maxTurns,
1509
1766
  cwd: this.options.workingDirectory,
1510
1767
  pathToClaudeCodeExecutable: this.options.cliPath,
@@ -1534,25 +1791,85 @@ var init_claude = __esm({
1534
1791
  return opts;
1535
1792
  }
1536
1793
  async buildMcpConfig(opts, toolResultCapture) {
1537
- if (this.tools.length === 0) return opts;
1794
+ const onAskUser = this.config.supervisor?.onAskUser;
1795
+ if (this.tools.length === 0 && !onAskUser) return opts;
1538
1796
  const sdk = await loadSDK2();
1539
- const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture);
1797
+ const mcpServer = buildMcpServer(sdk, this.tools, toolResultCapture, onAskUser);
1540
1798
  if (mcpServer) {
1541
1799
  opts.mcpServers = {
1542
1800
  [MCP_SERVER_NAME]: mcpServer
1543
1801
  };
1544
1802
  const mcpToolNames = this.tools.map((t) => mcpToolName(t.name));
1803
+ if (onAskUser) {
1804
+ mcpToolNames.push(mcpToolName("ask_user"));
1805
+ }
1545
1806
  opts.allowedTools = [...opts.allowedTools ?? [], ...mcpToolNames];
1546
1807
  }
1808
+ if (onAskUser) {
1809
+ opts.disallowedTools = [...opts.disallowedTools ?? [], "AskUserQuestion"];
1810
+ }
1547
1811
  return opts;
1548
1812
  }
1813
+ // ─── Retry Helpers (shared across executeRun/RunStructured/Stream) ──
1814
+ /** Setup a retry query: clear session, rebuild with full history */
1815
+ async prepareRetryQuery(sdk, messages, signal, options, toolResultCapture, modifyOpts) {
1816
+ this.clearPersistentSession();
1817
+ const retryPrompt = buildContextualPrompt(messages);
1818
+ let retryOpts = this.buildQueryOptions(signal, options);
1819
+ toolResultCapture.clear();
1820
+ retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1821
+ modifyOpts?.(retryOpts);
1822
+ const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1823
+ this.activeQuery = retryQ;
1824
+ return retryQ;
1825
+ }
1826
+ /** Extract tool_use blocks from an assistant SDK message into toolCalls array */
1827
+ collectToolCallsFromMessage(msg, toolCalls, toolResultCapture) {
1828
+ if (msg.type !== "assistant") return;
1829
+ const betaMessage = msg.message;
1830
+ if (!betaMessage?.content) return;
1831
+ for (const block of betaMessage.content) {
1832
+ if (block.type === "tool_use") {
1833
+ const toolName = stripMcpPrefix(block.name ?? "unknown");
1834
+ if (CLAUDE_INTERNAL_TOOL_NAMES.has(toolName)) continue;
1835
+ toolCalls.push({
1836
+ toolName,
1837
+ args: block.input ?? {},
1838
+ result: toolResultCapture.get(toolName) ?? null,
1839
+ approved: true
1840
+ });
1841
+ }
1842
+ }
1843
+ }
1844
+ /** Back-fill tool results from capture map on summary/result messages */
1845
+ backfillToolResults(msg, toolCalls, toolResultCapture) {
1846
+ if (msg.type !== "tool_use_summary" && msg.type !== "result") return;
1847
+ for (const tc of toolCalls) {
1848
+ if (tc.result === null) {
1849
+ const captured = toolResultCapture.get(tc.toolName);
1850
+ if (captured !== void 0) tc.result = captured;
1851
+ }
1852
+ }
1853
+ }
1854
+ /** Wrap retry inner loop with shared error handling */
1855
+ async withRetryErrorHandling(signal, fn) {
1856
+ try {
1857
+ return await fn();
1858
+ } catch (retryError) {
1859
+ if (this.isPersistent) this.clearPersistentSession();
1860
+ if (signal.aborted) throw new AbortError();
1861
+ throw retryError;
1862
+ } finally {
1863
+ this.activeQuery = null;
1864
+ }
1865
+ }
1549
1866
  // ─── executeRun ─────────────────────────────────────────────────
1550
- async executeRun(messages, _options, signal) {
1867
+ async executeRun(messages, options, signal) {
1551
1868
  this.checkAbort(signal);
1552
1869
  const sdk = await loadSDK2();
1553
1870
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1554
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1555
- let opts = this.buildQueryOptions(signal);
1871
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1872
+ let opts = this.buildQueryOptions(signal, options);
1556
1873
  const toolResultCapture = /* @__PURE__ */ new Map();
1557
1874
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1558
1875
  const q = sdk.query({ prompt, options: opts });
@@ -1562,30 +1879,8 @@ var init_claude = __esm({
1562
1879
  let usage;
1563
1880
  try {
1564
1881
  for await (const msg of q) {
1565
- if (msg.type === "assistant") {
1566
- const betaMessage = msg.message;
1567
- if (betaMessage?.content) {
1568
- for (const block of betaMessage.content) {
1569
- if (block.type === "tool_use") {
1570
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1571
- toolCalls.push({
1572
- toolName,
1573
- args: block.input ?? {},
1574
- result: toolResultCapture.get(toolName) ?? null,
1575
- approved: true
1576
- });
1577
- }
1578
- }
1579
- }
1580
- }
1581
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1582
- for (const tc of toolCalls) {
1583
- if (tc.result === null) {
1584
- const captured = toolResultCapture.get(tc.toolName);
1585
- if (captured !== void 0) tc.result = captured;
1586
- }
1587
- }
1588
- }
1882
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1883
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1589
1884
  if (msg.type === "result") {
1590
1885
  if (msg.subtype === "success") {
1591
1886
  const r = msg;
@@ -1605,41 +1900,13 @@ var init_claude = __esm({
1605
1900
  } catch (e) {
1606
1901
  if (signal.aborted) throw new AbortError();
1607
1902
  if (isResuming && this.isPersistent) {
1608
- this.clearPersistentSession();
1609
- const retryPrompt = buildContextualPrompt2(messages);
1610
- let retryOpts = this.buildQueryOptions(signal);
1611
- toolResultCapture.clear();
1612
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1613
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1614
- this.activeQuery = retryQ;
1903
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1615
1904
  toolCalls.length = 0;
1616
1905
  output = null;
1617
- try {
1906
+ return this.withRetryErrorHandling(signal, async () => {
1618
1907
  for await (const msg of retryQ) {
1619
- if (msg.type === "assistant") {
1620
- const betaMessage = msg.message;
1621
- if (betaMessage?.content) {
1622
- for (const block of betaMessage.content) {
1623
- if (block.type === "tool_use") {
1624
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1625
- toolCalls.push({
1626
- toolName,
1627
- args: block.input ?? {},
1628
- result: toolResultCapture.get(toolName) ?? null,
1629
- approved: true
1630
- });
1631
- }
1632
- }
1633
- }
1634
- }
1635
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1636
- for (const tc of toolCalls) {
1637
- if (tc.result === null) {
1638
- const captured = toolResultCapture.get(tc.toolName);
1639
- if (captured !== void 0) tc.result = captured;
1640
- }
1641
- }
1642
- }
1908
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1909
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1643
1910
  if (msg.type === "result") {
1644
1911
  if (msg.subtype === "success") {
1645
1912
  const r = msg;
@@ -1656,23 +1923,17 @@ var init_claude = __esm({
1656
1923
  }
1657
1924
  }
1658
1925
  }
1659
- } catch (retryError) {
1660
- if (this.isPersistent) this.clearPersistentSession();
1661
- if (signal.aborted) throw new AbortError();
1662
- throw retryError;
1663
- } finally {
1664
- this.activeQuery = null;
1665
- }
1666
- return {
1667
- output,
1668
- structuredOutput: void 0,
1669
- toolCalls,
1670
- messages: [
1671
- ...messages,
1672
- ...output !== null ? [{ role: "assistant", content: output }] : []
1673
- ],
1674
- usage
1675
- };
1926
+ return {
1927
+ output,
1928
+ structuredOutput: void 0,
1929
+ toolCalls,
1930
+ messages: [
1931
+ ...messages,
1932
+ ...output !== null ? [{ role: "assistant", content: output }] : []
1933
+ ],
1934
+ usage
1935
+ };
1936
+ });
1676
1937
  }
1677
1938
  if (this.isPersistent) this.clearPersistentSession();
1678
1939
  throw e;
@@ -1691,12 +1952,12 @@ var init_claude = __esm({
1691
1952
  };
1692
1953
  }
1693
1954
  // ─── executeRunStructured ───────────────────────────────────────
1694
- async executeRunStructured(messages, schema, _options, signal) {
1955
+ async executeRunStructured(messages, schema, options, signal) {
1695
1956
  this.checkAbort(signal);
1696
1957
  const sdk = await loadSDK2();
1697
1958
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1698
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1699
- let opts = this.buildQueryOptions(signal);
1959
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
1960
+ let opts = this.buildQueryOptions(signal, options);
1700
1961
  const toolResultCapture = /* @__PURE__ */ new Map();
1701
1962
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1702
1963
  const jsonSchema = zodToJsonSchema(schema.schema);
@@ -1712,30 +1973,8 @@ var init_claude = __esm({
1712
1973
  let usage;
1713
1974
  try {
1714
1975
  for await (const msg of q) {
1715
- if (msg.type === "assistant") {
1716
- const betaMessage = msg.message;
1717
- if (betaMessage?.content) {
1718
- for (const block of betaMessage.content) {
1719
- if (block.type === "tool_use") {
1720
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1721
- toolCalls.push({
1722
- toolName,
1723
- args: block.input ?? {},
1724
- result: toolResultCapture.get(toolName) ?? null,
1725
- approved: true
1726
- });
1727
- }
1728
- }
1729
- }
1730
- }
1731
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1732
- for (const tc of toolCalls) {
1733
- if (tc.result === null) {
1734
- const captured = toolResultCapture.get(tc.toolName);
1735
- if (captured !== void 0) tc.result = captured;
1736
- }
1737
- }
1738
- }
1976
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
1977
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1739
1978
  if (msg.type === "result" && msg.subtype === "success") {
1740
1979
  const r = msg;
1741
1980
  output = r.result;
@@ -1770,46 +2009,23 @@ var init_claude = __esm({
1770
2009
  } catch (e) {
1771
2010
  if (signal.aborted) throw new AbortError();
1772
2011
  if (isResuming && this.isPersistent) {
1773
- this.clearPersistentSession();
1774
- const retryPrompt = buildContextualPrompt2(messages);
1775
- let retryOpts = this.buildQueryOptions(signal);
1776
- toolResultCapture.clear();
1777
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1778
- retryOpts.outputFormat = {
1779
- type: "json_schema",
1780
- schema: jsonSchema
1781
- };
1782
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1783
- this.activeQuery = retryQ;
2012
+ const retryQ = await this.prepareRetryQuery(
2013
+ sdk,
2014
+ messages,
2015
+ signal,
2016
+ options,
2017
+ toolResultCapture,
2018
+ (opts2) => {
2019
+ opts2.outputFormat = { type: "json_schema", schema: jsonSchema };
2020
+ }
2021
+ );
1784
2022
  toolCalls.length = 0;
1785
2023
  output = null;
1786
2024
  structuredOutput = void 0;
1787
- try {
2025
+ return this.withRetryErrorHandling(signal, async () => {
1788
2026
  for await (const msg of retryQ) {
1789
- if (msg.type === "assistant") {
1790
- const betaMessage = msg.message;
1791
- if (betaMessage?.content) {
1792
- for (const block of betaMessage.content) {
1793
- if (block.type === "tool_use") {
1794
- const toolName = stripMcpPrefix(block.name ?? "unknown");
1795
- toolCalls.push({
1796
- toolName,
1797
- args: block.input ?? {},
1798
- result: toolResultCapture.get(toolName) ?? null,
1799
- approved: true
1800
- });
1801
- }
1802
- }
1803
- }
1804
- }
1805
- if (msg.type === "tool_use_summary" || msg.type === "result") {
1806
- for (const tc of toolCalls) {
1807
- if (tc.result === null) {
1808
- const captured = toolResultCapture.get(tc.toolName);
1809
- if (captured !== void 0) tc.result = captured;
1810
- }
1811
- }
1812
- }
2027
+ this.collectToolCallsFromMessage(msg, toolCalls, toolResultCapture);
2028
+ this.backfillToolResults(msg, toolCalls, toolResultCapture);
1813
2029
  if (msg.type === "result" && msg.subtype === "success") {
1814
2030
  const r = msg;
1815
2031
  output = r.result;
@@ -1841,23 +2057,17 @@ var init_claude = __esm({
1841
2057
  );
1842
2058
  }
1843
2059
  }
1844
- } catch (retryError) {
1845
- if (this.isPersistent) this.clearPersistentSession();
1846
- if (signal.aborted) throw new AbortError();
1847
- throw retryError;
1848
- } finally {
1849
- this.activeQuery = null;
1850
- }
1851
- return {
1852
- output,
1853
- structuredOutput,
1854
- toolCalls,
1855
- messages: [
1856
- ...messages,
1857
- ...output !== null ? [{ role: "assistant", content: output }] : []
1858
- ],
1859
- usage
1860
- };
2060
+ return {
2061
+ output,
2062
+ structuredOutput,
2063
+ toolCalls,
2064
+ messages: [
2065
+ ...messages,
2066
+ ...output !== null ? [{ role: "assistant", content: output }] : []
2067
+ ],
2068
+ usage
2069
+ };
2070
+ });
1861
2071
  }
1862
2072
  if (this.isPersistent) this.clearPersistentSession();
1863
2073
  throw e;
@@ -1876,12 +2086,12 @@ var init_claude = __esm({
1876
2086
  };
1877
2087
  }
1878
2088
  // ─── executeStream ──────────────────────────────────────────────
1879
- async *executeStream(messages, _options, signal) {
2089
+ async *executeStream(messages, options, signal) {
1880
2090
  this.checkAbort(signal);
1881
2091
  const sdk = await loadSDK2();
1882
2092
  const isResuming = this.isPersistent && this._sessionId !== void 0;
1883
- const prompt = isResuming ? extractLastUserPrompt2(messages) : buildContextualPrompt2(messages);
1884
- let opts = this.buildQueryOptions(signal);
2093
+ const prompt = isResuming ? extractLastUserPrompt(messages) : buildContextualPrompt(messages);
2094
+ let opts = this.buildQueryOptions(signal, options);
1885
2095
  const toolResultCapture = /* @__PURE__ */ new Map();
1886
2096
  opts = await this.buildMcpConfig(opts, toolResultCapture);
1887
2097
  const q = sdk.query({ prompt, options: opts });
@@ -1889,6 +2099,7 @@ var init_claude = __esm({
1889
2099
  const thinkingBlockIndices = /* @__PURE__ */ new Set();
1890
2100
  const toolCallTracker = new ClaudeToolCallTracker();
1891
2101
  const pendingStreamToolCalls = /* @__PURE__ */ new Map();
2102
+ let hasStreamedText = false;
1892
2103
  try {
1893
2104
  for await (const msg of q) {
1894
2105
  if (signal.aborted) throw new AbortError();
@@ -1906,6 +2117,7 @@ var init_claude = __esm({
1906
2117
  } else if (e.type === "tool_call_end") {
1907
2118
  pendingStreamToolCalls.delete(e.toolCallId);
1908
2119
  }
2120
+ if (e.type === "text_delta") hasStreamedText = true;
1909
2121
  yield e;
1910
2122
  }
1911
2123
  }
@@ -1929,22 +2141,21 @@ var init_claude = __esm({
1929
2141
  }
1930
2142
  yield this.emitSessionInfo(r.session_id);
1931
2143
  }
1932
- yield { type: "done", finalOutput: r.result };
2144
+ yield {
2145
+ type: "done",
2146
+ finalOutput: hasStreamedText ? null : r.result,
2147
+ ...hasStreamedText ? { streamed: true } : {}
2148
+ };
1933
2149
  }
1934
2150
  }
1935
2151
  } catch (e) {
1936
2152
  if (signal.aborted) throw new AbortError();
1937
2153
  if (isResuming && this.isPersistent) {
1938
- this.clearPersistentSession();
1939
- const retryPrompt = buildContextualPrompt2(messages);
1940
- let retryOpts = this.buildQueryOptions(signal);
1941
- toolResultCapture.clear();
1942
- retryOpts = await this.buildMcpConfig(retryOpts, toolResultCapture);
1943
- const retryQ = sdk.query({ prompt: retryPrompt, options: retryOpts });
1944
- this.activeQuery = retryQ;
2154
+ const retryQ = await this.prepareRetryQuery(sdk, messages, signal, options, toolResultCapture);
1945
2155
  const retryThinkingBlockIndices = /* @__PURE__ */ new Set();
1946
2156
  const retryToolCallTracker = new ClaudeToolCallTracker();
1947
2157
  const retryPendingToolCalls = /* @__PURE__ */ new Map();
2158
+ let retryHasStreamedText = false;
1948
2159
  try {
1949
2160
  for await (const msg of retryQ) {
1950
2161
  if (signal.aborted) throw new AbortError();
@@ -1962,6 +2173,7 @@ var init_claude = __esm({
1962
2173
  } else if (ev.type === "tool_call_end") {
1963
2174
  retryPendingToolCalls.delete(ev.toolCallId);
1964
2175
  }
2176
+ if (ev.type === "text_delta") retryHasStreamedText = true;
1965
2177
  yield ev;
1966
2178
  }
1967
2179
  }
@@ -1985,7 +2197,11 @@ var init_claude = __esm({
1985
2197
  }
1986
2198
  yield this.emitSessionInfo(r.session_id);
1987
2199
  }
1988
- yield { type: "done", finalOutput: r.result };
2200
+ yield {
2201
+ type: "done",
2202
+ finalOutput: retryHasStreamedText ? null : r.result,
2203
+ ...retryHasStreamedText ? { streamed: true } : {}
2204
+ };
1989
2205
  }
1990
2206
  }
1991
2207
  } catch (retryError) {
@@ -2047,7 +2263,8 @@ var init_claude = __esm({
2047
2263
  this.cachedModels = body.data.map((m) => ({
2048
2264
  id: m.id,
2049
2265
  name: m.display_name,
2050
- provider: "claude"
2266
+ provider: "claude",
2267
+ ...m.max_input_tokens != null && { contextWindow: m.max_input_tokens }
2051
2268
  }));
2052
2269
  return this.cachedModels;
2053
2270
  }
@@ -2105,32 +2322,30 @@ __export(vercel_ai_exports, {
2105
2322
  createVercelAIService: () => createVercelAIService
2106
2323
  });
2107
2324
  async function loadSDK3() {
2108
- if (sdkModule3) return sdkModule3;
2325
+ if (_sdkMock3) return _sdkMock3;
2109
2326
  try {
2110
- sdkModule3 = await import('ai');
2111
- return sdkModule3;
2327
+ return await import('ai');
2112
2328
  } catch {
2113
2329
  throw new DependencyError("ai");
2114
2330
  }
2115
2331
  }
2116
2332
  async function loadCompat() {
2117
- if (compatModule) return compatModule;
2333
+ if (_compatMock) return _compatMock;
2118
2334
  try {
2119
- compatModule = await import('@ai-sdk/openai-compatible');
2120
- return compatModule;
2335
+ return await import('@ai-sdk/openai-compatible');
2121
2336
  } catch {
2122
2337
  throw new DependencyError("@ai-sdk/openai-compatible");
2123
2338
  }
2124
2339
  }
2125
2340
  function _injectSDK3(mock) {
2126
- sdkModule3 = mock;
2341
+ _sdkMock3 = mock;
2127
2342
  }
2128
2343
  function _injectCompat(mock) {
2129
- compatModule = mock;
2344
+ _compatMock = mock;
2130
2345
  }
2131
2346
  function _resetSDK3() {
2132
- sdkModule3 = null;
2133
- compatModule = null;
2347
+ _sdkMock3 = null;
2348
+ _compatMock = null;
2134
2349
  }
2135
2350
  function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, signal) {
2136
2351
  const toolMap = {};
@@ -2173,13 +2388,14 @@ function mapToolsToSDK2(sdk, tools, config, sessionApprovals, permissionStore, s
2173
2388
  return toolMap;
2174
2389
  }
2175
2390
  function wrapToolExecute(ourTool, supervisor, sessionApprovals, permissionStore, signal) {
2176
- return async (args) => {
2391
+ return async (args, options) => {
2177
2392
  if (ourTool.needsApproval && supervisor?.onPermission) {
2178
2393
  const storeApproved = permissionStore && await permissionStore.isApproved(ourTool.name);
2179
2394
  if (!storeApproved && !sessionApprovals.has(ourTool.name)) {
2180
2395
  const request = {
2181
2396
  toolName: ourTool.name,
2182
- toolArgs: args ?? {}
2397
+ toolArgs: args ?? {},
2398
+ toolCallId: options?.toolCallId
2183
2399
  };
2184
2400
  const decision = await supervisor.onPermission(
2185
2401
  request,
@@ -2286,7 +2502,8 @@ function mapStreamPart(part) {
2286
2502
  return {
2287
2503
  type: "error",
2288
2504
  error: p.error instanceof Error ? p.error.message : String(p.error ?? "Tool execution failed"),
2289
- recoverable: true
2505
+ recoverable: true,
2506
+ code: "TOOL_EXECUTION" /* TOOL_EXECUTION */
2290
2507
  };
2291
2508
  }
2292
2509
  case "reasoning-start":
@@ -2307,10 +2524,13 @@ function mapStreamPart(part) {
2307
2524
  }
2308
2525
  case "error": {
2309
2526
  const p = part;
2527
+ const errorMsg = p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error");
2528
+ const code = classifyAgentError(errorMsg);
2310
2529
  return {
2311
2530
  type: "error",
2312
- error: p.error instanceof Error ? p.error.message : String(p.error ?? "Unknown error"),
2313
- recoverable: false
2531
+ error: errorMsg,
2532
+ recoverable: isRecoverableErrorCode(code),
2533
+ code
2314
2534
  };
2315
2535
  }
2316
2536
  default:
@@ -2320,15 +2540,15 @@ function mapStreamPart(part) {
2320
2540
  function createVercelAIService(options) {
2321
2541
  return new VercelAIAgentService(options);
2322
2542
  }
2323
- var sdkModule3, compatModule, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2543
+ var _sdkMock3, _compatMock, DEFAULT_BASE_URL, DEFAULT_PROVIDER, DEFAULT_MAX_TURNS, VercelAIAgent, VercelAIAgentService;
2324
2544
  var init_vercel_ai = __esm({
2325
2545
  "src/backends/vercel-ai.ts"() {
2326
- init_types();
2546
+ init_types2();
2327
2547
  init_base_agent();
2328
- init_errors();
2548
+ init_errors2();
2329
2549
  init_schema();
2330
- sdkModule3 = null;
2331
- compatModule = null;
2550
+ _sdkMock3 = null;
2551
+ _compatMock = null;
2332
2552
  DEFAULT_BASE_URL = "https://openrouter.ai/api/v1";
2333
2553
  DEFAULT_PROVIDER = "openrouter";
2334
2554
  DEFAULT_MAX_TURNS = 10;
@@ -2341,28 +2561,33 @@ var init_vercel_ai = __esm({
2341
2561
  super(config);
2342
2562
  this.backendOptions = backendOptions;
2343
2563
  }
2344
- async getModel() {
2345
- if (this.model) return this.model;
2564
+ async getModel(options) {
2565
+ const requestedModel = options.model;
2566
+ const defaultModel = this.config.model;
2567
+ if (requestedModel === defaultModel && this.model) return this.model;
2346
2568
  const compat = await loadCompat();
2347
2569
  const provider = compat.createOpenAICompatible({
2348
2570
  name: this.backendOptions.provider ?? DEFAULT_PROVIDER,
2349
2571
  baseURL: this.backendOptions.baseUrl ?? DEFAULT_BASE_URL,
2350
2572
  apiKey: this.backendOptions.apiKey
2351
2573
  });
2352
- const modelId = this.config.model ?? "anthropic/claude-sonnet-4-5";
2353
- this.model = provider.chatModel(modelId);
2354
- return this.model;
2574
+ const model = provider.chatModel(requestedModel);
2575
+ if (requestedModel === defaultModel) {
2576
+ this.model = model;
2577
+ }
2578
+ return model;
2355
2579
  }
2356
- async getSDKTools(signal) {
2580
+ async getSDKTools(signal, options) {
2357
2581
  const sdk = await loadSDK3();
2358
- return mapToolsToSDK2(sdk, this.config.tools ?? [], this.config, this.sessionApprovals, this.config.permissionStore, signal);
2582
+ const tools = this.resolveTools(options);
2583
+ return mapToolsToSDK2(sdk, tools, this.config, this.sessionApprovals, this.config.permissionStore, signal);
2359
2584
  }
2360
2585
  // ─── executeRun ─────────────────────────────────────────────────
2361
- async executeRun(messages, _options, signal) {
2586
+ async executeRun(messages, options, signal) {
2362
2587
  this.checkAbort(signal);
2363
2588
  const sdk = await loadSDK3();
2364
- const model = await this.getModel();
2365
- const tools = await this.getSDKTools(signal);
2589
+ const model = await this.getModel(options);
2590
+ const tools = await this.getSDKTools(signal, options);
2366
2591
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2367
2592
  const sdkMessages = messagesToSDK(messages);
2368
2593
  const hasTools = Object.keys(tools).length > 0;
@@ -2418,10 +2643,10 @@ var init_vercel_ai = __esm({
2418
2643
  };
2419
2644
  }
2420
2645
  // ─── executeRunStructured ───────────────────────────────────────
2421
- async executeRunStructured(messages, schema, _options, signal) {
2646
+ async executeRunStructured(messages, schema, options, signal) {
2422
2647
  this.checkAbort(signal);
2423
2648
  const sdk = await loadSDK3();
2424
- const model = await this.getModel();
2649
+ const model = await this.getModel(options);
2425
2650
  const sdkMessages = messagesToSDK(messages);
2426
2651
  const jsonSchema = zodToJsonSchema(schema.schema);
2427
2652
  const result = await sdk.generateObject({
@@ -2463,11 +2688,11 @@ var init_vercel_ai = __esm({
2463
2688
  };
2464
2689
  }
2465
2690
  // ─── executeStream ──────────────────────────────────────────────
2466
- async *executeStream(messages, _options, signal) {
2691
+ async *executeStream(messages, options, signal) {
2467
2692
  this.checkAbort(signal);
2468
2693
  const sdk = await loadSDK3();
2469
- const model = await this.getModel();
2470
- const tools = await this.getSDKTools(signal);
2694
+ const model = await this.getModel(options);
2695
+ const tools = await this.getSDKTools(signal, options);
2471
2696
  const maxTurns = this.config.maxTurns ?? DEFAULT_MAX_TURNS;
2472
2697
  const sdkMessages = messagesToSDK(messages);
2473
2698
  const hasTools = Object.keys(tools).length > 0;
@@ -2492,6 +2717,7 @@ var init_vercel_ai = __esm({
2492
2717
  }
2493
2718
  });
2494
2719
  let finalText = "";
2720
+ let lastFinishReason;
2495
2721
  try {
2496
2722
  for await (const part of result.fullStream) {
2497
2723
  if (signal.aborted) throw new AbortError();
@@ -2502,10 +2728,15 @@ var init_vercel_ai = __esm({
2502
2728
  }
2503
2729
  if (part.type === "finish-step") {
2504
2730
  const p = part;
2731
+ lastFinishReason = p.finishReason;
2505
2732
  if (p.finishReason === "tool-calls") {
2506
2733
  finalText = "";
2507
2734
  }
2508
2735
  }
2736
+ if (part.type === "finish") {
2737
+ const p = part;
2738
+ lastFinishReason = p.finishReason;
2739
+ }
2509
2740
  }
2510
2741
  const totalUsage = await result.totalUsage;
2511
2742
  yield {
@@ -2513,9 +2744,12 @@ var init_vercel_ai = __esm({
2513
2744
  promptTokens: Number(totalUsage?.inputTokens ?? 0),
2514
2745
  completionTokens: Number(totalUsage?.outputTokens ?? 0)
2515
2746
  };
2747
+ const hasStreamed = finalText.length > 0;
2516
2748
  yield {
2517
2749
  type: "done",
2518
- finalOutput: finalText || null
2750
+ finalOutput: hasStreamed ? null : finalText || null,
2751
+ ...hasStreamed ? { streamed: true } : {},
2752
+ ...lastFinishReason ? { finishReason: lastFinishReason } : {}
2519
2753
  };
2520
2754
  } catch (e) {
2521
2755
  if (signal.aborted) throw new AbortError();
@@ -2544,16 +2778,33 @@ var init_vercel_ai = __esm({
2544
2778
  const baseUrl = (this.options.baseUrl || "https://api.openai.com/v1").replace(/\/+$/, "");
2545
2779
  try {
2546
2780
  const res = await globalThis.fetch(`${baseUrl}/models`, {
2547
- headers: { Authorization: `Bearer ${this.options.apiKey}` }
2781
+ headers: {
2782
+ Authorization: `Bearer ${this.options.apiKey}`,
2783
+ // OpenRouter requires HTTP-Referer for API access
2784
+ "HTTP-Referer": "https://github.com/nicepkg/agent-sdk"
2785
+ }
2548
2786
  });
2549
2787
  if (!res.ok) {
2550
2788
  return [];
2551
2789
  }
2552
2790
  const body = await res.json();
2553
- if (!body.data || body.data.length === 0) {
2554
- return [];
2791
+ if (body.data && Array.isArray(body.data)) {
2792
+ return body.data.filter((m) => typeof m.id === "string").map((m) => ({
2793
+ id: m.id,
2794
+ ...typeof m.name === "string" && { name: m.name },
2795
+ ...typeof m.description === "string" && { description: m.description },
2796
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2797
+ }));
2798
+ }
2799
+ if (Array.isArray(body)) {
2800
+ return body.filter((m) => typeof m.id === "string").map((m) => ({
2801
+ id: m.id,
2802
+ ...typeof m.name === "string" && { name: m.name },
2803
+ ...typeof m.description === "string" && { description: m.description },
2804
+ ...typeof m.context_length === "number" && { contextWindow: m.context_length }
2805
+ }));
2555
2806
  }
2556
- return body.data.map((m) => ({ id: m.id }));
2807
+ return [];
2557
2808
  } catch {
2558
2809
  return [];
2559
2810
  }
@@ -2584,263 +2835,7 @@ var init_vercel_ai = __esm({
2584
2835
  }
2585
2836
  });
2586
2837
 
2587
- // src/registry.ts
2588
- function registerBackend(name, factory) {
2589
- if (registry.has(name)) {
2590
- throw new BackendAlreadyRegisteredError(name);
2591
- }
2592
- registry.set(name, { factory, builtin: false });
2593
- }
2594
- function unregisterBackend(name) {
2595
- return registry.delete(name);
2596
- }
2597
- function hasBackend(name) {
2598
- return registry.has(name) || isBuiltinName(name);
2599
- }
2600
- function listBackends() {
2601
- const names = new Set(registry.keys());
2602
- for (const builtin of BUILTIN_BACKENDS) {
2603
- names.add(builtin);
2604
- }
2605
- return [...names];
2606
- }
2607
- function resetRegistry() {
2608
- registry.clear();
2609
- }
2610
- function isBuiltinName(name) {
2611
- return BUILTIN_BACKENDS.has(name);
2612
- }
2613
- async function loadBuiltinFactory(name) {
2614
- switch (name) {
2615
- case "copilot": {
2616
- const mod = await Promise.resolve().then(() => (init_copilot(), copilot_exports));
2617
- return (opts) => mod.createCopilotService(opts);
2618
- }
2619
- case "claude": {
2620
- const mod = await Promise.resolve().then(() => (init_claude(), claude_exports));
2621
- return (opts) => mod.createClaudeService(opts);
2622
- }
2623
- case "vercel-ai": {
2624
- const mod = await Promise.resolve().then(() => (init_vercel_ai(), vercel_ai_exports));
2625
- return (opts) => mod.createVercelAIService(opts);
2626
- }
2627
- }
2628
- }
2629
- async function createAgentService(name, options) {
2630
- const entry = registry.get(name);
2631
- if (entry) {
2632
- return entry.factory(options);
2633
- }
2634
- if (isBuiltinName(name)) {
2635
- const factory = await loadBuiltinFactory(name);
2636
- registry.set(name, { factory, builtin: true });
2637
- return factory(options);
2638
- }
2639
- throw new BackendNotFoundError(name);
2640
- }
2641
- var registry, BUILTIN_BACKENDS;
2642
- var init_registry = __esm({
2643
- "src/registry.ts"() {
2644
- init_errors();
2645
- registry = /* @__PURE__ */ new Map();
2646
- BUILTIN_BACKENDS = /* @__PURE__ */ new Set([
2647
- "copilot",
2648
- "claude",
2649
- "vercel-ai"
2650
- ]);
2651
- }
2652
- });
2653
-
2654
- // src/utils/messages.ts
2655
- function messagesToPrompt(messages) {
2656
- return messages.map((msg) => {
2657
- switch (msg.role) {
2658
- case "user":
2659
- return contentToText(msg.content);
2660
- case "assistant":
2661
- return contentToText(msg.content);
2662
- case "system":
2663
- return msg.content;
2664
- case "tool":
2665
- return msg.content ?? "";
2666
- }
2667
- }).filter(Boolean).join("\n\n");
2668
- }
2669
- function contentToText(content) {
2670
- return getTextContent(content);
2671
- }
2672
- function buildSystemPrompt(base, schemaInstruction) {
2673
- if (!schemaInstruction) return base;
2674
- return `${base}
2675
-
2676
- ${schemaInstruction}`;
2677
- }
2678
- var init_messages = __esm({
2679
- "src/utils/messages.ts"() {
2680
- init_types();
2681
- }
2682
- });
2683
- function createDefaultPermissionStore(projectDir) {
2684
- const sessionStore = new InMemoryPermissionStore();
2685
- const projectPath = projectDir ? path.join(projectDir, ".agent-sdk", "permissions.json") : path.join(process.cwd(), ".agent-sdk", "permissions.json");
2686
- const userPath = path.join(os.homedir(), ".agent-sdk", "permissions.json");
2687
- const projectStore = new FilePermissionStore(projectPath);
2688
- const userStore = new FilePermissionStore(userPath);
2689
- return new CompositePermissionStore(sessionStore, projectStore, userStore);
2690
- }
2691
- var InMemoryPermissionStore, FilePermissionStore, CompositePermissionStore;
2692
- var init_permission_store = __esm({
2693
- "src/permission-store.ts"() {
2694
- InMemoryPermissionStore = class {
2695
- approvals = /* @__PURE__ */ new Map();
2696
- async isApproved(toolName) {
2697
- return this.approvals.has(toolName);
2698
- }
2699
- async approve(toolName, scope) {
2700
- if (scope === "once") return;
2701
- this.approvals.set(toolName, scope);
2702
- }
2703
- async revoke(toolName) {
2704
- this.approvals.delete(toolName);
2705
- }
2706
- async clear() {
2707
- this.approvals.clear();
2708
- }
2709
- async dispose() {
2710
- this.approvals.clear();
2711
- }
2712
- };
2713
- FilePermissionStore = class {
2714
- filePath;
2715
- constructor(filePath) {
2716
- this.filePath = path.resolve(filePath);
2717
- }
2718
- async isApproved(toolName) {
2719
- const data = this.readFile();
2720
- return toolName in data.approvals;
2721
- }
2722
- async approve(toolName, scope) {
2723
- if (scope === "once") return;
2724
- const data = this.readFile();
2725
- data.approvals[toolName] = { scope, timestamp: Date.now() };
2726
- this.writeFileAtomic(data);
2727
- }
2728
- async revoke(toolName) {
2729
- const data = this.readFile();
2730
- delete data.approvals[toolName];
2731
- this.writeFileAtomic(data);
2732
- }
2733
- async clear() {
2734
- this.writeFileAtomic({ approvals: {} });
2735
- }
2736
- async dispose() {
2737
- }
2738
- readFile() {
2739
- try {
2740
- const raw = fs.readFileSync(this.filePath, "utf-8");
2741
- const parsed = JSON.parse(raw);
2742
- if (parsed && typeof parsed.approvals === "object") return parsed;
2743
- } catch {
2744
- }
2745
- return { approvals: {} };
2746
- }
2747
- writeFileAtomic(data) {
2748
- const dir = path.dirname(this.filePath);
2749
- fs.mkdirSync(dir, { recursive: true });
2750
- const tmpPath = this.filePath + `.tmp.${process.pid}.${Date.now()}`;
2751
- fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
2752
- fs.renameSync(tmpPath, this.filePath);
2753
- }
2754
- };
2755
- CompositePermissionStore = class {
2756
- sessionStore;
2757
- projectStore;
2758
- userStore;
2759
- constructor(sessionStore, projectStore, userStore) {
2760
- this.sessionStore = sessionStore;
2761
- this.projectStore = projectStore;
2762
- this.userStore = userStore ?? projectStore;
2763
- }
2764
- async isApproved(toolName) {
2765
- return await this.sessionStore.isApproved(toolName) || await this.projectStore.isApproved(toolName) || await this.userStore.isApproved(toolName);
2766
- }
2767
- async approve(toolName, scope) {
2768
- if (scope === "once") return;
2769
- if (scope === "session") {
2770
- await this.sessionStore.approve(toolName, scope);
2771
- } else if (scope === "project") {
2772
- await this.projectStore.approve(toolName, scope);
2773
- } else {
2774
- await this.userStore.approve(toolName, scope);
2775
- }
2776
- }
2777
- async revoke(toolName) {
2778
- await this.sessionStore.revoke(toolName);
2779
- await this.projectStore.revoke(toolName);
2780
- await this.userStore.revoke(toolName);
2781
- }
2782
- async clear() {
2783
- await this.sessionStore.clear();
2784
- await this.projectStore.clear();
2785
- await this.userStore.clear();
2786
- }
2787
- async dispose() {
2788
- await this.sessionStore.dispose();
2789
- await this.projectStore.dispose();
2790
- if (this.userStore !== this.projectStore) {
2791
- await this.userStore.dispose();
2792
- }
2793
- }
2794
- };
2795
- }
2796
- });
2797
-
2798
- // src/index.ts
2799
- var src_exports = {};
2800
- __export(src_exports, {
2801
- AbortError: () => AbortError,
2802
- AgentSDKError: () => AgentSDKError,
2803
- BackendAlreadyRegisteredError: () => BackendAlreadyRegisteredError,
2804
- BackendNotFoundError: () => BackendNotFoundError,
2805
- BaseAgent: () => BaseAgent,
2806
- CompositePermissionStore: () => CompositePermissionStore,
2807
- DependencyError: () => DependencyError,
2808
- DisposedError: () => DisposedError,
2809
- FilePermissionStore: () => FilePermissionStore,
2810
- InMemoryPermissionStore: () => InMemoryPermissionStore,
2811
- ReentrancyError: () => ReentrancyError,
2812
- StructuredOutputError: () => StructuredOutputError,
2813
- SubprocessError: () => SubprocessError,
2814
- ToolExecutionError: () => ToolExecutionError,
2815
- buildSystemPrompt: () => buildSystemPrompt,
2816
- contentToText: () => contentToText,
2817
- createAgentService: () => createAgentService,
2818
- createDefaultPermissionStore: () => createDefaultPermissionStore,
2819
- getTextContent: () => getTextContent,
2820
- hasBackend: () => hasBackend,
2821
- isMultiPartContent: () => isMultiPartContent,
2822
- isTextContent: () => isTextContent,
2823
- isToolDefinition: () => isToolDefinition,
2824
- listBackends: () => listBackends,
2825
- messagesToPrompt: () => messagesToPrompt,
2826
- registerBackend: () => registerBackend,
2827
- resetRegistry: () => resetRegistry,
2828
- unregisterBackend: () => unregisterBackend,
2829
- zodToJsonSchema: () => zodToJsonSchema
2830
- });
2831
- var init_src = __esm({
2832
- "src/index.ts"() {
2833
- init_types();
2834
- init_errors();
2835
- init_registry();
2836
- init_base_agent();
2837
- init_schema();
2838
- init_messages();
2839
- init_permission_store();
2840
- }
2841
- });
2842
-
2843
- // src/chat/core.ts
2838
+ // src/chat/types.ts
2844
2839
  function createChatId() {
2845
2840
  return crypto.randomUUID();
2846
2841
  }
@@ -2851,6 +2846,20 @@ function toChatId(value) {
2851
2846
  }
2852
2847
  return value;
2853
2848
  }
2849
+ function createTextMessage(text, role = "user") {
2850
+ return {
2851
+ id: createChatId(),
2852
+ role,
2853
+ parts: [{ type: "text", text, status: "complete" }],
2854
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2855
+ status: "complete"
2856
+ };
2857
+ }
2858
+ function isObservableSession(session) {
2859
+ return "subscribe" in session && typeof session.subscribe === "function" && "getSnapshot" in session && typeof session.getSnapshot === "function";
2860
+ }
2861
+
2862
+ // src/chat/chat-utils.ts
2854
2863
  function getMessageText(message) {
2855
2864
  return message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
2856
2865
  }
@@ -2860,6 +2869,8 @@ function getMessageToolCalls(message) {
2860
2869
  function getMessageReasoning(message) {
2861
2870
  return message.parts.filter((p) => p.type === "reasoning").map((p) => p.text).join("");
2862
2871
  }
2872
+
2873
+ // src/chat/guards.ts
2863
2874
  function isChatMessage(value) {
2864
2875
  if (typeof value !== "object" || value === null) return false;
2865
2876
  const obj = value;
@@ -2900,31 +2911,33 @@ function isFilePart(value) {
2900
2911
  const obj = value;
2901
2912
  return obj.type === "file" && typeof obj.name === "string" && typeof obj.mimeType === "string";
2902
2913
  }
2914
+ var VALID_CHAT_EVENT_TYPES = /* @__PURE__ */ new Set([
2915
+ "message:start",
2916
+ "message:delta",
2917
+ "message:complete",
2918
+ "tool:start",
2919
+ "tool:complete",
2920
+ "thinking:start",
2921
+ "thinking:delta",
2922
+ "thinking:end",
2923
+ "permission:request",
2924
+ "permission:response",
2925
+ "usage",
2926
+ "session:created",
2927
+ "session:updated",
2928
+ "error",
2929
+ "typing:start",
2930
+ "typing:end",
2931
+ "heartbeat",
2932
+ "done"
2933
+ ]);
2903
2934
  function isChatEvent(value) {
2904
2935
  if (typeof value !== "object" || value === null) return false;
2905
2936
  const obj = value;
2906
- const validTypes = [
2907
- "message:start",
2908
- "message:delta",
2909
- "message:complete",
2910
- "tool:start",
2911
- "tool:complete",
2912
- "thinking:start",
2913
- "thinking:delta",
2914
- "thinking:end",
2915
- "permission:request",
2916
- "permission:response",
2917
- "usage",
2918
- "session:created",
2919
- "session:updated",
2920
- "error",
2921
- "typing:start",
2922
- "typing:end",
2923
- "heartbeat",
2924
- "done"
2925
- ];
2926
- return validTypes.includes(obj.type);
2937
+ return VALID_CHAT_EVENT_TYPES.has(obj.type);
2927
2938
  }
2939
+
2940
+ // src/chat/bridge.ts
2928
2941
  function agentEventToChatEvent(event, messageId) {
2929
2942
  switch (event.type) {
2930
2943
  case "text_delta":
@@ -2977,6 +2990,7 @@ function agentEventToChatEvent(event, messageId) {
2977
2990
  type: "error",
2978
2991
  error: event.error,
2979
2992
  recoverable: event.recoverable,
2993
+ code: event.code,
2980
2994
  messageId
2981
2995
  };
2982
2996
  case "heartbeat":
@@ -2984,8 +2998,9 @@ function agentEventToChatEvent(event, messageId) {
2984
2998
  case "ask_user":
2985
2999
  case "ask_user_response":
2986
3000
  case "session_info":
2987
- case "done":
2988
3001
  return null;
3002
+ case "done":
3003
+ return { type: "done", finalOutput: event.finalOutput ?? void 0, finishReason: event.finishReason };
2989
3004
  default:
2990
3005
  return null;
2991
3006
  }
@@ -3023,27 +3038,37 @@ function chatEventToAgentEvent(event) {
3023
3038
  result: event.result
3024
3039
  };
3025
3040
  case "error":
3026
- return { type: "error", error: event.error, recoverable: event.recoverable };
3041
+ return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
3027
3042
  default:
3028
3043
  return null;
3029
3044
  }
3030
3045
  }
3046
+
3047
+ // src/chat/conversion.ts
3031
3048
  function toAgentMessage(message) {
3049
+ return toAgentMessages(message)[0];
3050
+ }
3051
+ function toAgentMessages(message) {
3032
3052
  const textContent = getMessageText(message);
3033
3053
  const toolCallParts = getMessageToolCalls(message);
3034
3054
  switch (message.role) {
3035
3055
  case "user":
3036
- return { role: "user", content: textContent };
3056
+ return [{ role: "user", content: textContent }];
3037
3057
  case "assistant": {
3038
3058
  const toolCalls = toolCallParts.length > 0 ? toolCallParts.map((p) => ({ id: p.toolCallId, name: p.name, args: p.args })) : void 0;
3039
- return {
3059
+ const assistantMsg = {
3040
3060
  role: "assistant",
3041
3061
  content: textContent,
3042
3062
  toolCalls
3043
3063
  };
3064
+ const toolResults = extractToolResults(message);
3065
+ if (toolResults.length > 0) {
3066
+ return [assistantMsg, { role: "tool", toolResults }];
3067
+ }
3068
+ return [assistantMsg];
3044
3069
  }
3045
3070
  case "system":
3046
- return { role: "system", content: textContent };
3071
+ return [{ role: "system", content: textContent }];
3047
3072
  }
3048
3073
  }
3049
3074
  function fromAgentMessage(message, id) {
@@ -3089,6 +3114,14 @@ function fromAgentMessage(message, id) {
3089
3114
  status: "complete"
3090
3115
  };
3091
3116
  }
3117
+ function extractToolResults(message) {
3118
+ return getMessageToolCalls(message).filter((p) => p.result !== void 0).map((p) => ({
3119
+ toolCallId: p.toolCallId,
3120
+ name: p.name,
3121
+ result: p.result,
3122
+ isError: p.status === "error" ? true : void 0
3123
+ }));
3124
+ }
3092
3125
 
3093
3126
  // src/chat/context.ts
3094
3127
  function estimateTokens(message, options) {
@@ -3198,6 +3231,56 @@ var ContextWindowManager = class {
3198
3231
  });
3199
3232
  return { ...result, messages: updatedMessages };
3200
3233
  }
3234
+ /**
3235
+ * Trim messages using real token usage data from the previous API call.
3236
+ * Uses average-based algorithm: `avgTokensPerMessage = lastPromptTokens / messageCount`.
3237
+ * Removes oldest non-system messages until freed budget brings usage under modelContextWindow.
3238
+ *
3239
+ * @param messages - All messages in the session
3240
+ * @param lastPromptTokens - Real prompt tokens from the last API response
3241
+ * @param modelContextWindow - Model's total context window size in tokens
3242
+ * @returns Result with fitted messages and metadata
3243
+ */
3244
+ fitMessagesWithUsage(messages, lastPromptTokens, modelContextWindow) {
3245
+ if (messages.length === 0) {
3246
+ return { messages: [], totalTokens: 0, removedCount: 0, wasTruncated: false };
3247
+ }
3248
+ const budget = modelContextWindow - this.config.reservedTokens;
3249
+ if (budget <= 0 || lastPromptTokens <= budget) {
3250
+ return {
3251
+ messages: [...messages],
3252
+ totalTokens: lastPromptTokens,
3253
+ removedCount: 0,
3254
+ wasTruncated: false
3255
+ };
3256
+ }
3257
+ const avgTokensPerMessage = lastPromptTokens / messages.length;
3258
+ const tokensToFree = lastPromptTokens - budget;
3259
+ const messagesToRemove = Math.ceil(tokensToFree / avgTokensPerMessage);
3260
+ const nonSystemIndices = [];
3261
+ for (let i = 0; i < messages.length; i++) {
3262
+ if (messages[i].role === "system") ; else {
3263
+ nonSystemIndices.push(i);
3264
+ }
3265
+ }
3266
+ const removableCount = Math.min(messagesToRemove, nonSystemIndices.length);
3267
+ const removedIndices = new Set(nonSystemIndices.slice(0, removableCount));
3268
+ const result = [];
3269
+ for (let i = 0; i < messages.length; i++) {
3270
+ if (!removedIndices.has(i)) {
3271
+ result.push(messages[i]);
3272
+ }
3273
+ }
3274
+ const estimatedTokens = Math.round(
3275
+ lastPromptTokens * (result.length / messages.length)
3276
+ );
3277
+ return {
3278
+ messages: result,
3279
+ totalTokens: estimatedTokens,
3280
+ removedCount: removableCount,
3281
+ wasTruncated: removableCount > 0
3282
+ };
3283
+ }
3201
3284
  /**
3202
3285
  * Truncate oldest: keeps system messages, removes oldest non-system messages first.
3203
3286
  * Always keeps the most recent user message.
@@ -3315,37 +3398,19 @@ var ContextWindowManager = class {
3315
3398
  };
3316
3399
 
3317
3400
  // src/chat/errors.ts
3401
+ init_errors2();
3318
3402
  init_errors();
3319
- var ChatErrorCode = /* @__PURE__ */ ((ChatErrorCode2) => {
3320
- ChatErrorCode2["NETWORK"] = "NETWORK";
3321
- ChatErrorCode2["TIMEOUT"] = "TIMEOUT";
3322
- ChatErrorCode2["AUTH_EXPIRED"] = "AUTH_EXPIRED";
3323
- ChatErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
3324
- ChatErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
3325
- ChatErrorCode2["PROVIDER_ERROR"] = "PROVIDER_ERROR";
3326
- ChatErrorCode2["MODEL_NOT_FOUND"] = "MODEL_NOT_FOUND";
3327
- ChatErrorCode2["MODEL_OVERLOADED"] = "MODEL_OVERLOADED";
3328
- ChatErrorCode2["CONTEXT_OVERFLOW"] = "CONTEXT_OVERFLOW";
3329
- ChatErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
3330
- ChatErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
3331
- ChatErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
3332
- ChatErrorCode2["BACKEND_NOT_INSTALLED"] = "BACKEND_NOT_INSTALLED";
3333
- ChatErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
3334
- ChatErrorCode2["STORAGE_ERROR"] = "STORAGE_ERROR";
3335
- ChatErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
3336
- ChatErrorCode2["DISPOSED"] = "DISPOSED";
3337
- ChatErrorCode2["ABORTED"] = "ABORTED";
3338
- ChatErrorCode2["INVALID_TRANSITION"] = "INVALID_TRANSITION";
3339
- ChatErrorCode2["REENTRANCY"] = "REENTRANCY";
3340
- return ChatErrorCode2;
3341
- })(ChatErrorCode || {});
3342
3403
  var ChatError = class extends AgentSDKError {
3343
3404
  code;
3344
3405
  retryable;
3345
3406
  retryAfter;
3346
3407
  timestamp;
3347
3408
  constructor(message, options) {
3348
- super(message, { cause: options.cause });
3409
+ super(message, {
3410
+ cause: options.cause,
3411
+ code: options.code,
3412
+ retryable: options.retryable
3413
+ });
3349
3414
  this.name = "ChatError";
3350
3415
  this.code = options.code;
3351
3416
  this.retryable = options.retryable ?? false;
@@ -3524,12 +3589,12 @@ function isRetryable(error) {
3524
3589
  return classified.retryable;
3525
3590
  }
3526
3591
  function sleep(ms, signal) {
3527
- return new Promise((resolve2, reject) => {
3592
+ return new Promise((resolve, reject) => {
3528
3593
  if (signal?.aborted) {
3529
3594
  reject(new ChatError("Retry aborted", { code: "ABORTED" /* ABORTED */ }));
3530
3595
  return;
3531
3596
  }
3532
- const timer = setTimeout(resolve2, ms);
3597
+ const timer = setTimeout(resolve, ms);
3533
3598
  signal?.addEventListener(
3534
3599
  "abort",
3535
3600
  () => {
@@ -3853,6 +3918,35 @@ var CancellableTimeout = class {
3853
3918
  }
3854
3919
  };
3855
3920
 
3921
+ // src/chat/listener-set.ts
3922
+ var ListenerSet = class {
3923
+ _listeners = /* @__PURE__ */ new Set();
3924
+ /** Add a listener. Returns an unsubscribe function. */
3925
+ add(callback) {
3926
+ this._listeners.add(callback);
3927
+ return () => {
3928
+ this._listeners.delete(callback);
3929
+ };
3930
+ }
3931
+ /** Notify all listeners with the given arguments. Errors are isolated per listener. */
3932
+ notify(...args) {
3933
+ for (const cb of this._listeners) {
3934
+ try {
3935
+ cb(...args);
3936
+ } catch {
3937
+ }
3938
+ }
3939
+ }
3940
+ /** Remove all listeners. */
3941
+ clear() {
3942
+ this._listeners.clear();
3943
+ }
3944
+ /** Current number of listeners. */
3945
+ get size() {
3946
+ return this._listeners.size;
3947
+ }
3948
+ };
3949
+
3856
3950
  // src/chat/runtime.ts
3857
3951
  var ChatRuntime = class {
3858
3952
  _state;
@@ -3860,26 +3954,29 @@ var ChatRuntime = class {
3860
3954
  _backends;
3861
3955
  _sessionStore;
3862
3956
  _contextConfig;
3957
+ _ctxManager;
3863
3958
  _middleware;
3864
3959
  _tools = /* @__PURE__ */ new Map();
3865
3960
  _retryConfig;
3866
3961
  _contextStats = /* @__PURE__ */ new Map();
3962
+ _sessionUsage = /* @__PURE__ */ new Map();
3963
+ _modelContextWindows = /* @__PURE__ */ new Map();
3867
3964
  _onContextTrimmed;
3868
3965
  _streamTimeoutMs;
3869
- _sessionListeners = /* @__PURE__ */ new Set();
3870
- _activeAdapter = null;
3871
- _currentBackend;
3872
- _currentModel;
3873
- _activeSessionId = null;
3966
+ _sessionListeners = new ListenerSet();
3967
+ _adapterPool = /* @__PURE__ */ new Map();
3968
+ _defaultBackend;
3874
3969
  _abortController = null;
3875
3970
  constructor(options) {
3876
3971
  this._state = new StateMachine("idle", RUNTIME_TRANSITIONS);
3877
3972
  this._guard = new ChatReentrancyGuard();
3878
3973
  this._backends = options.backends;
3879
- this._currentBackend = options.defaultBackend;
3880
- this._currentModel = options.defaultModel;
3974
+ this._defaultBackend = options.defaultBackend;
3881
3975
  this._sessionStore = options.sessionStore;
3882
3976
  this._contextConfig = options.context;
3977
+ if (this._contextConfig) {
3978
+ this._ctxManager = new ContextWindowManager(this._contextConfig);
3979
+ }
3883
3980
  this._middleware = [...options.middleware ?? []];
3884
3981
  this._retryConfig = options.retryConfig;
3885
3982
  this._onContextTrimmed = options.onContextTrimmed;
@@ -3890,6 +3987,11 @@ var ChatRuntime = class {
3890
3987
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
3891
3988
  );
3892
3989
  }
3990
+ if (options.tools) {
3991
+ for (const tool of options.tools) {
3992
+ this._tools.set(tool.name, tool);
3993
+ }
3994
+ }
3893
3995
  }
3894
3996
  // ── Lifecycle ──────────────────────────────────────────────
3895
3997
  get status() {
@@ -3901,24 +4003,23 @@ var ChatRuntime = class {
3901
4003
  this._abortController?.dispose();
3902
4004
  this._abortController = null;
3903
4005
  this._state.transition("disposed");
3904
- if (this._activeAdapter) {
3905
- await this._activeAdapter.dispose();
3906
- this._activeAdapter = null;
3907
- }
3908
- }
3909
- // ── Sessions ───────────────────────────────────────────────
3910
- get activeSessionId() {
3911
- return this._activeSessionId;
4006
+ for (const adapter of this._adapterPool.values()) {
4007
+ try {
4008
+ await adapter.dispose();
4009
+ } catch {
4010
+ }
4011
+ }
4012
+ this._adapterPool.clear();
3912
4013
  }
4014
+ // ── Sessions ───────────────────────────────────────────────
3913
4015
  async createSession(options) {
3914
4016
  this.assertNotDisposed();
3915
4017
  const config = {
3916
- model: options.config?.model ?? this._currentModel ?? "",
3917
- backend: options.config?.backend ?? this._currentBackend,
4018
+ model: options.config?.model ?? "",
4019
+ backend: options.config?.backend ?? this._defaultBackend,
3918
4020
  ...options.config
3919
4021
  };
3920
4022
  const session = await this._sessionStore.createSession({ ...options, config });
3921
- this._activeSessionId = session.id;
3922
4023
  this._notifySessionChange();
3923
4024
  return session;
3924
4025
  }
@@ -3938,36 +4039,12 @@ var ChatRuntime = class {
3938
4039
  if (!session) return;
3939
4040
  await this._sessionStore.deleteSession(cid);
3940
4041
  this._contextStats.delete(cid);
3941
- if (this._activeSessionId === cid) {
3942
- this._activeSessionId = null;
3943
- }
3944
- this._notifySessionChange();
3945
- }
3946
- async archiveSession(id) {
3947
- this.assertNotDisposed();
3948
- const cid = toChatId(id);
3949
- await this._sessionStore.archiveSession(cid);
4042
+ this._sessionUsage.delete(cid);
3950
4043
  this._notifySessionChange();
3951
4044
  }
3952
- async switchSession(id) {
3953
- this.assertNotDisposed();
3954
- const cid = toChatId(id);
3955
- const session = await this._sessionStore.getSession(cid);
3956
- if (!session) {
3957
- throw new ChatError(
3958
- `Session "${id}" not found`,
3959
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
3960
- );
3961
- }
3962
- this._activeSessionId = session.id;
3963
- return session;
3964
- }
3965
4045
  // ── Messaging ──────────────────────────────────────────────
3966
4046
  async *send(sessionId, message, options) {
3967
- this.assertNotDisposed();
3968
- if (!message || message.trim().length === 0) {
3969
- throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
3970
- }
4047
+ this.validateSendInput(message, options);
3971
4048
  this._guard.acquire();
3972
4049
  const cid = toChatId(sessionId);
3973
4050
  this._abortController = new ChatAbortController(options?.signal);
@@ -3976,150 +4053,274 @@ var ChatRuntime = class {
3976
4053
  this._state.transition("idle");
3977
4054
  }
3978
4055
  this._state.transition("streaming");
3979
- const session = await this._sessionStore.getSession(cid);
3980
- if (!session) {
3981
- throw new ChatError(
3982
- `Session "${cid}" not found`,
3983
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
3984
- );
3985
- }
3986
- const middlewareContext = {
4056
+ await this.loadSession(cid);
4057
+ const mwCtx = {
3987
4058
  sessionId: cid,
3988
4059
  signal: this._abortController.signal
3989
4060
  };
3990
- let userMessage = this.createUserMessage(message);
3991
- for (const mw of this._middleware) {
3992
- if (mw.onBeforeSend) {
3993
- userMessage = await mw.onBeforeSend(userMessage, middlewareContext);
3994
- }
3995
- }
3996
- await this._sessionStore.appendMessage(cid, userMessage);
3997
- const updatedSession = await this._sessionStore.getSession(cid);
3998
- let messagesToSend = updatedSession.messages;
3999
- if (this._contextConfig) {
4000
- const ctxManager = new ContextWindowManager(this._contextConfig);
4001
- const result = await ctxManager.fitMessagesAsync(messagesToSend);
4002
- this._contextStats.set(cid, {
4003
- totalTokens: result.totalTokens,
4004
- removedCount: result.removedCount,
4005
- wasTruncated: result.wasTruncated,
4006
- availableBudget: ctxManager.availableBudget
4007
- });
4008
- if (result.wasTruncated && this._onContextTrimmed) {
4009
- const keptIds = new Set(result.messages.map((m) => m.id));
4010
- const removed = messagesToSend.filter((m) => !keptIds.has(m.id));
4011
- if (removed.length > 0) {
4012
- try {
4013
- this._onContextTrimmed(cid, removed);
4014
- } catch {
4015
- }
4016
- }
4017
- }
4018
- messagesToSend = result.messages;
4061
+ const userMessage = await this.applyBeforeSendMiddleware(
4062
+ this.createUserMessage(message),
4063
+ mwCtx
4064
+ );
4065
+ if (userMessage === null) {
4066
+ this._state.transition("idle");
4067
+ return;
4019
4068
  }
4020
- const sessionForAdapter = {
4021
- ...updatedSession,
4022
- messages: messagesToSend
4023
- };
4024
- const adapter = await this.getOrCreateAdapterWithRetry();
4069
+ const updatedSession = await this.persistAndReload(cid, userMessage);
4070
+ const sessionForAdapter = await this.trimSessionContext(cid, updatedSession, options.model);
4071
+ const stream = await this.prepareEventStream(
4072
+ cid,
4073
+ sessionForAdapter,
4074
+ updatedSession,
4075
+ message,
4076
+ options
4077
+ );
4025
4078
  const accumulator = new MessageAccumulator();
4026
- const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4027
- sessionId: cid,
4028
- custom: updatedSession.metadata?.custom
4029
- }) : void 0;
4030
- const streamOptions = {
4031
- ...options,
4032
- signal: this._abortController.signal,
4033
- model: options?.model ?? this._currentModel,
4034
- tools: runtimeTools
4035
- };
4036
- const stream = await this.createStreamWithRetry(adapter, sessionForAdapter, message, streamOptions);
4037
4079
  const eventSource = this._streamTimeoutMs ? withStreamWatchdog(stream, { timeoutMs: this._streamTimeoutMs, signal: this._abortController.signal }) : stream;
4038
4080
  for await (const event of eventSource) {
4039
4081
  if (this._abortController.isAborted) break;
4040
4082
  this.feedAccumulator(accumulator, event);
4041
- let processedEvent = event;
4042
- for (const mw of this._middleware) {
4043
- if (mw.onEvent && processedEvent) {
4044
- processedEvent = await mw.onEvent(processedEvent, middlewareContext);
4045
- }
4046
- }
4047
- if (processedEvent) {
4048
- yield processedEvent;
4049
- }
4050
- }
4051
- if (this._state.current === "disposed") {
4052
- return;
4053
- }
4054
- let assistantMessage = accumulator.finalize();
4055
- for (const mw of this._middleware) {
4056
- if (mw.onAfterReceive) {
4057
- assistantMessage = await mw.onAfterReceive(assistantMessage, middlewareContext);
4083
+ if (event.type === "usage") {
4084
+ this._sessionUsage.set(cid, {
4085
+ promptTokens: event.promptTokens,
4086
+ completionTokens: event.completionTokens
4087
+ });
4088
+ this.updateContextStatsWithUsage(cid, event.promptTokens, event.completionTokens, options);
4058
4089
  }
4090
+ const processed = await this.applyOnEventMiddleware(event, mwCtx);
4091
+ if (processed) yield processed;
4059
4092
  }
4060
- await this._sessionStore.appendMessage(cid, assistantMessage);
4061
- this._notifySessionChange();
4093
+ if (this._state.current === "disposed") return;
4094
+ await this.finalizeAssistantMessage(cid, accumulator, mwCtx);
4062
4095
  this._state.transition("idle");
4063
4096
  } catch (error) {
4064
- let processedError = error instanceof Error ? error : new Error(String(error));
4065
- const middlewareContext = {
4066
- sessionId: cid,
4067
- signal: this._abortController?.signal ?? new AbortController().signal
4068
- };
4069
- for (const mw of this._middleware) {
4070
- if (mw.onError) {
4071
- const result = await mw.onError(processedError, middlewareContext);
4072
- if (result === null) {
4073
- if (this._state.canTransition("idle")) {
4074
- this._state.transition("idle");
4075
- }
4076
- return;
4077
- }
4078
- processedError = result;
4079
- }
4080
- }
4081
- if (this._state.canTransition("error")) {
4082
- this._state.transition("error");
4083
- }
4084
- throw processedError;
4097
+ const result = await this.handleSendError(error, cid);
4098
+ if (result !== null) throw result;
4085
4099
  } finally {
4086
4100
  this._guard.release();
4087
4101
  this._abortController?.dispose();
4088
4102
  this._abortController = null;
4089
4103
  }
4090
4104
  }
4091
- abort() {
4092
- this._abortController?.abort("User abort");
4093
- }
4094
- // ── Backend / Model ────────────────────────────────────────
4095
- get currentBackend() {
4096
- return this._currentBackend;
4097
- }
4098
- get currentModel() {
4099
- return this._currentModel;
4100
- }
4101
- async switchBackend(name) {
4105
+ // ── Send Pipeline Stages ──────────────────────────────────────
4106
+ /** Stage 1: Validate send inputs (message content + required fields). */
4107
+ validateSendInput(message, options) {
4102
4108
  this.assertNotDisposed();
4103
- if (!this._backends[name]) {
4109
+ if (!message || message.trim().length === 0) {
4110
+ throw new ChatError("Message cannot be empty", { code: "INVALID_INPUT" /* INVALID_INPUT */ });
4111
+ }
4112
+ if (!options.model) {
4113
+ throw new ChatError(
4114
+ "options.model is required \u2014 caller must specify which model to use",
4115
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4116
+ );
4117
+ }
4118
+ if (!options.backend) {
4119
+ throw new ChatError(
4120
+ "options.backend is required \u2014 caller must specify which backend to use",
4121
+ { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4122
+ );
4123
+ }
4124
+ if (!options.credentials) {
4104
4125
  throw new ChatError(
4105
- `Backend "${name}" not found in backends map`,
4126
+ "options.credentials is required \u2014 caller must provide authentication credentials",
4106
4127
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4107
4128
  );
4108
4129
  }
4109
- if (this._activeAdapter) {
4110
- await this._activeAdapter.dispose();
4111
- this._activeAdapter = null;
4130
+ }
4131
+ /** Stage 2: Load session from store. */
4132
+ async loadSession(cid) {
4133
+ const session = await this._sessionStore.getSession(cid);
4134
+ if (!session) {
4135
+ throw new ChatError(
4136
+ `Session "${cid}" not found`,
4137
+ { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4138
+ );
4112
4139
  }
4113
- this._currentBackend = name;
4140
+ return session;
4114
4141
  }
4115
- switchModel(model) {
4142
+ /** Stage 3: Apply onBeforeSend middleware pipeline. Returns null if middleware rejected the send. */
4143
+ async applyBeforeSendMiddleware(userMessage, ctx) {
4144
+ let msg = userMessage;
4145
+ for (const mw of this._middleware) {
4146
+ if (mw.onBeforeSend && msg) {
4147
+ msg = await mw.onBeforeSend(msg, ctx);
4148
+ if (msg === null) return null;
4149
+ }
4150
+ }
4151
+ return msg;
4152
+ }
4153
+ /** Stage 4: Persist user message and reload session with full history. */
4154
+ async persistAndReload(cid, userMessage) {
4155
+ await this._sessionStore.appendMessage(cid, userMessage);
4156
+ return await this._sessionStore.getSession(cid);
4157
+ }
4158
+ /** Stage 5: Auto-trim context window if configured. Returns session snapshot for adapter. */
4159
+ async trimSessionContext(cid, session, model) {
4160
+ if (!this._ctxManager) return session;
4161
+ const ctxManager = this._ctxManager;
4162
+ const lastUsage = this._sessionUsage.get(cid);
4163
+ const modelContextWindow = model ? this._modelContextWindows.get(model) : void 0;
4164
+ if (lastUsage && modelContextWindow) {
4165
+ const result2 = ctxManager.fitMessagesWithUsage(
4166
+ session.messages,
4167
+ lastUsage.promptTokens,
4168
+ modelContextWindow
4169
+ );
4170
+ this._contextStats.set(cid, {
4171
+ totalTokens: result2.totalTokens,
4172
+ removedCount: result2.removedCount,
4173
+ wasTruncated: result2.wasTruncated,
4174
+ availableBudget: Math.max(0, modelContextWindow - result2.totalTokens),
4175
+ realPromptTokens: lastUsage.promptTokens,
4176
+ realCompletionTokens: lastUsage.completionTokens,
4177
+ modelContextWindow
4178
+ });
4179
+ if (result2.wasTruncated && this._onContextTrimmed) {
4180
+ const keptIds = new Set(result2.messages.map((m) => m.id));
4181
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4182
+ if (removed.length > 0) {
4183
+ try {
4184
+ this._onContextTrimmed(cid, removed);
4185
+ } catch {
4186
+ }
4187
+ }
4188
+ }
4189
+ return { ...session, messages: result2.messages };
4190
+ }
4191
+ const result = await ctxManager.fitMessagesAsync(session.messages);
4192
+ this._contextStats.set(cid, {
4193
+ totalTokens: result.totalTokens,
4194
+ removedCount: result.removedCount,
4195
+ wasTruncated: result.wasTruncated,
4196
+ availableBudget: ctxManager.availableBudget,
4197
+ modelContextWindow
4198
+ });
4199
+ if (result.wasTruncated && this._onContextTrimmed) {
4200
+ const keptIds = new Set(result.messages.map((m) => m.id));
4201
+ const removed = session.messages.filter((m) => !keptIds.has(m.id));
4202
+ if (removed.length > 0) {
4203
+ try {
4204
+ this._onContextTrimmed(cid, removed);
4205
+ } catch {
4206
+ }
4207
+ }
4208
+ }
4209
+ return { ...session, messages: result.messages };
4210
+ }
4211
+ /** Update context stats with real usage data from a usage event. */
4212
+ updateContextStatsWithUsage(cid, promptTokens, completionTokens, options) {
4213
+ const modelContextWindow = options.model ? this._modelContextWindows.get(options.model) : void 0;
4214
+ const existing = this._contextStats.get(cid);
4215
+ this._contextStats.set(cid, {
4216
+ totalTokens: promptTokens,
4217
+ removedCount: existing?.removedCount ?? 0,
4218
+ wasTruncated: existing?.wasTruncated ?? false,
4219
+ availableBudget: modelContextWindow ? Math.max(0, modelContextWindow - promptTokens) : existing?.availableBudget ?? 0,
4220
+ realPromptTokens: promptTokens,
4221
+ realCompletionTokens: completionTokens,
4222
+ modelContextWindow
4223
+ });
4224
+ }
4225
+ /** Stage 6: Prepare event stream — adapter with retry, tool injection. */
4226
+ async prepareEventStream(cid, sessionForAdapter, fullSession, message, options) {
4227
+ const adapter = await this.getOrCreateAdapterWithRetry(options.backend, options.credentials);
4228
+ const runtimeTools = this._tools.size > 0 ? this.injectToolContext([...this._tools.values()], {
4229
+ sessionId: cid,
4230
+ custom: fullSession.metadata?.custom
4231
+ }) : void 0;
4232
+ const streamOptions = {
4233
+ signal: this._abortController.signal,
4234
+ model: options.model,
4235
+ systemPrompt: options.systemPrompt,
4236
+ tools: runtimeTools
4237
+ };
4238
+ return this.createStreamWithRetry(
4239
+ adapter,
4240
+ sessionForAdapter,
4241
+ message,
4242
+ streamOptions,
4243
+ options.backend,
4244
+ options.credentials
4245
+ );
4246
+ }
4247
+ /** Stage 7: Apply onEvent middleware pipeline (sequential transform/suppress). */
4248
+ async applyOnEventMiddleware(event, ctx) {
4249
+ let processed = event;
4250
+ for (const mw of this._middleware) {
4251
+ if (mw.onEvent && processed) {
4252
+ processed = await mw.onEvent(processed, ctx);
4253
+ }
4254
+ }
4255
+ return processed;
4256
+ }
4257
+ /** Stage 8: Finalize accumulator, apply afterReceive middleware, persist assistant message. */
4258
+ async finalizeAssistantMessage(cid, accumulator, ctx) {
4259
+ let assistantMessage = accumulator.finalize();
4260
+ for (const mw of this._middleware) {
4261
+ if (mw.onAfterReceive) {
4262
+ assistantMessage = await mw.onAfterReceive(assistantMessage, ctx);
4263
+ }
4264
+ }
4265
+ await this._sessionStore.appendMessage(cid, assistantMessage);
4266
+ this._notifySessionChange();
4267
+ }
4268
+ /** Stage 9: Error handling — apply onError middleware, transition state. Returns null if suppressed. */
4269
+ async handleSendError(error, cid) {
4270
+ let processedError = error instanceof Error ? error : new Error(String(error));
4271
+ const ctx = {
4272
+ sessionId: cid,
4273
+ signal: this._abortController?.signal ?? new AbortController().signal
4274
+ };
4275
+ for (const mw of this._middleware) {
4276
+ if (mw.onError) {
4277
+ const result = await mw.onError(processedError, ctx);
4278
+ if (result === null) {
4279
+ if (this._state.canTransition("idle")) {
4280
+ this._state.transition("idle");
4281
+ }
4282
+ return null;
4283
+ }
4284
+ processedError = result;
4285
+ }
4286
+ }
4287
+ if (this._state.canTransition("error")) {
4288
+ this._state.transition("error");
4289
+ }
4290
+ return processedError;
4291
+ }
4292
+ abort() {
4293
+ this._abortController?.abort("User abort");
4294
+ }
4295
+ // ── Backend / Model ────────────────────────────────────────
4296
+ async listModels(options) {
4116
4297
  this.assertNotDisposed();
4117
- this._currentModel = model;
4298
+ let models = [];
4299
+ const firstAdapter = [...this._adapterPool.values()][0];
4300
+ if (firstAdapter) {
4301
+ try {
4302
+ models = await firstAdapter.listModels();
4303
+ } catch {
4304
+ return [];
4305
+ }
4306
+ } else if (options?.backend && options?.credentials) {
4307
+ try {
4308
+ const adapter = await this.getOrCreateAdapter(options.backend, options.credentials);
4309
+ models = await adapter.listModels();
4310
+ } catch {
4311
+ return [];
4312
+ }
4313
+ }
4314
+ for (const model of models) {
4315
+ if (model.contextWindow != null) {
4316
+ this._modelContextWindows.set(model.id, model.contextWindow);
4317
+ }
4318
+ }
4319
+ return models;
4118
4320
  }
4119
- async listModels() {
4321
+ async listBackends() {
4120
4322
  this.assertNotDisposed();
4121
- const adapter = await this.getOrCreateAdapter();
4122
- return adapter.listModels();
4323
+ return Object.keys(this._backends).map((name) => ({ name }));
4123
4324
  }
4124
4325
  // ── Tools ──────────────────────────────────────────────────
4125
4326
  get registeredTools() {
@@ -4144,37 +4345,46 @@ var ChatRuntime = class {
4144
4345
  if (idx >= 0) this._middleware.splice(idx, 1);
4145
4346
  }
4146
4347
  // ── Context Stats ─────────────────────────────────────────
4147
- getContextStats(sessionId) {
4348
+ async getContextStats(sessionId) {
4148
4349
  const cid = toChatId(sessionId);
4149
4350
  return this._contextStats.get(cid) ?? null;
4150
4351
  }
4151
4352
  // ── Session Subscription ──────────────────────────────────
4152
4353
  onSessionChange(callback) {
4153
- this._sessionListeners.add(callback);
4154
- return () => {
4155
- this._sessionListeners.delete(callback);
4156
- };
4354
+ return this._sessionListeners.add(callback);
4157
4355
  }
4158
4356
  _notifySessionChange() {
4159
- for (const cb of this._sessionListeners) {
4160
- try {
4161
- cb();
4162
- } catch {
4163
- }
4164
- }
4357
+ this._sessionListeners.notify();
4165
4358
  }
4166
4359
  // ── Private Helpers ────────────────────────────────────────
4167
- async getOrCreateAdapter() {
4168
- if (this._activeAdapter) return this._activeAdapter;
4169
- const factory = this._backends[this._currentBackend];
4360
+ async getOrCreateAdapter(backend, credentials) {
4361
+ const key = this.getPoolKey(backend, credentials);
4362
+ const existing = this._adapterPool.get(key);
4363
+ if (existing) return existing;
4364
+ for (const [oldKey, oldAdapter] of this._adapterPool) {
4365
+ if (oldKey.startsWith(backend + ":")) {
4366
+ try {
4367
+ await oldAdapter.dispose();
4368
+ } catch {
4369
+ }
4370
+ this._adapterPool.delete(oldKey);
4371
+ }
4372
+ }
4373
+ const factory = this._backends[backend];
4170
4374
  if (!factory) {
4171
4375
  throw new ChatError(
4172
- `Backend "${this._currentBackend}" not found`,
4376
+ `Backend "${backend}" not found`,
4173
4377
  { code: "INVALID_INPUT" /* INVALID_INPUT */ }
4174
4378
  );
4175
4379
  }
4176
- this._activeAdapter = await factory();
4177
- return this._activeAdapter;
4380
+ const adapter = await factory(credentials);
4381
+ this._adapterPool.set(key, adapter);
4382
+ return adapter;
4383
+ }
4384
+ getPoolKey(backend, credentials) {
4385
+ const token = credentials.accessToken;
4386
+ const hash = token.length > 16 ? token.slice(0, 8) + token.slice(-8) : token;
4387
+ return `${backend}:${hash}`;
4178
4388
  }
4179
4389
  /** Wrap each tool's execute to inject ToolContext as 2nd argument */
4180
4390
  injectToolContext(tools, context) {
@@ -4206,17 +4416,25 @@ var ChatRuntime = class {
4206
4416
  }
4207
4417
  }
4208
4418
  /** Get or create adapter with retry on connection errors */
4209
- async getOrCreateAdapterWithRetry() {
4419
+ async getOrCreateAdapterWithRetry(backend, credentials) {
4210
4420
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4211
4421
  const delayMs = this._retryConfig?.delayMs ?? 0;
4212
4422
  let lastError;
4213
4423
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4214
4424
  try {
4215
- return await this.getOrCreateAdapter();
4425
+ return await this.getOrCreateAdapter(backend, credentials);
4216
4426
  } catch (err) {
4217
4427
  lastError = err instanceof Error ? err : new Error(String(err));
4218
4428
  if (attempt < maxAttempts) {
4219
- this._activeAdapter = null;
4429
+ const key = this.getPoolKey(backend, credentials);
4430
+ const old = this._adapterPool.get(key);
4431
+ if (old) {
4432
+ try {
4433
+ await old.dispose();
4434
+ } catch {
4435
+ }
4436
+ }
4437
+ this._adapterPool.delete(key);
4220
4438
  await delay(delayMs);
4221
4439
  }
4222
4440
  }
@@ -4229,7 +4447,7 @@ var ChatRuntime = class {
4229
4447
  * retries with a fresh adapter. Once first event is received,
4230
4448
  * the stream is committed (no more retries).
4231
4449
  */
4232
- async createStreamWithRetry(adapter, session, message, options) {
4450
+ async createStreamWithRetry(adapter, session, message, options, backend, credentials) {
4233
4451
  const maxAttempts = this._retryConfig?.maxAttempts ?? 1;
4234
4452
  const delayMs = this._retryConfig?.delayMs ?? 0;
4235
4453
  let lastError;
@@ -4250,13 +4468,14 @@ var ChatRuntime = class {
4250
4468
  } catch (err) {
4251
4469
  lastError = err instanceof Error ? err : new Error(String(err));
4252
4470
  if (attempt < maxAttempts) {
4253
- if (this._activeAdapter) {
4254
- await this._activeAdapter.dispose().catch(() => {
4255
- });
4471
+ try {
4472
+ await currentAdapter.dispose();
4473
+ } catch {
4256
4474
  }
4257
- this._activeAdapter = null;
4475
+ const key = this.getPoolKey(backend, credentials);
4476
+ this._adapterPool.delete(key);
4258
4477
  await delay(delayMs);
4259
- currentAdapter = await this.getOrCreateAdapter();
4478
+ currentAdapter = await this.getOrCreateAdapter(backend, credentials);
4260
4479
  }
4261
4480
  }
4262
4481
  }
@@ -4264,16 +4483,17 @@ var ChatRuntime = class {
4264
4483
  }
4265
4484
  };
4266
4485
  function delay(ms) {
4267
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4486
+ return new Promise((resolve) => setTimeout(resolve, ms));
4268
4487
  }
4269
4488
  function createChatRuntime(options) {
4270
4489
  return new ChatRuntime(options);
4271
4490
  }
4272
4491
 
4273
4492
  // src/chat/storage.ts
4493
+ init_errors2();
4274
4494
  init_errors();
4275
4495
  var StorageError = class extends AgentSDKError {
4276
- /** Machine-readable error code */
4496
+ /** Machine-readable error code from the unified ErrorCode enum */
4277
4497
  code;
4278
4498
  constructor(message, code) {
4279
4499
  super(message);
@@ -4310,7 +4530,7 @@ var InMemoryStorage = class {
4310
4530
  if (this.data.has(key)) {
4311
4531
  throw new StorageError(
4312
4532
  `Item with key "${key}" already exists`,
4313
- "DUPLICATE_KEY"
4533
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4314
4534
  );
4315
4535
  }
4316
4536
  this.data.set(key, structuredClone(item));
@@ -4320,7 +4540,7 @@ var InMemoryStorage = class {
4320
4540
  if (!this.data.has(key)) {
4321
4541
  throw new StorageError(
4322
4542
  `Item with key "${key}" not found`,
4323
- "NOT_FOUND"
4543
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4324
4544
  );
4325
4545
  }
4326
4546
  this.data.set(key, structuredClone(item));
@@ -4330,7 +4550,7 @@ var InMemoryStorage = class {
4330
4550
  if (!this.data.has(key)) {
4331
4551
  throw new StorageError(
4332
4552
  `Item with key "${key}" not found`,
4333
- "NOT_FOUND"
4553
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4334
4554
  );
4335
4555
  }
4336
4556
  this.data.delete(key);
@@ -4354,25 +4574,25 @@ var FileStorage = class {
4354
4574
  constructor(options) {
4355
4575
  this.directory = options.directory;
4356
4576
  this.extension = options.extension ?? ".json";
4357
- this.ensureDirectory();
4577
+ this.ensureDirectorySync();
4358
4578
  }
4359
4579
  /** @inheritdoc */
4360
4580
  async get(key) {
4361
4581
  const filePath = this.keyToPath(key);
4362
- if (!existsSync(filePath)) {
4582
+ if (!await this.fileExists(filePath)) {
4363
4583
  return null;
4364
4584
  }
4365
- return this.readFile(filePath);
4585
+ return this.readJsonFile(filePath);
4366
4586
  }
4367
4587
  /** @inheritdoc */
4368
4588
  async list(options) {
4369
- this.ensureDirectory();
4370
- const files = readdirSync(this.directory).filter(
4589
+ await this.ensureDirectoryAsync();
4590
+ const files = (await readdir(this.directory)).filter(
4371
4591
  (f) => f.endsWith(this.extension)
4372
4592
  );
4373
4593
  let items = [];
4374
4594
  for (const file of files) {
4375
- const item = this.readFile(join(this.directory, file));
4595
+ const item = await this.readJsonFile(join(this.directory, file));
4376
4596
  items.push(item);
4377
4597
  }
4378
4598
  if (options?.filter) {
@@ -4392,55 +4612,55 @@ var FileStorage = class {
4392
4612
  /** @inheritdoc */
4393
4613
  async create(key, item) {
4394
4614
  const filePath = this.keyToPath(key);
4395
- if (existsSync(filePath)) {
4615
+ if (await this.fileExists(filePath)) {
4396
4616
  throw new StorageError(
4397
4617
  `Item with key "${key}" already exists`,
4398
- "DUPLICATE_KEY"
4618
+ "STORAGE_DUPLICATE_KEY" /* STORAGE_DUPLICATE_KEY */
4399
4619
  );
4400
4620
  }
4401
- this.writeFile(filePath, item);
4621
+ await this.writeJsonFile(filePath, item);
4402
4622
  }
4403
4623
  /** @inheritdoc */
4404
4624
  async update(key, item) {
4405
4625
  const filePath = this.keyToPath(key);
4406
- if (!existsSync(filePath)) {
4626
+ if (!await this.fileExists(filePath)) {
4407
4627
  throw new StorageError(
4408
4628
  `Item with key "${key}" not found`,
4409
- "NOT_FOUND"
4629
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4410
4630
  );
4411
4631
  }
4412
- this.writeFile(filePath, item);
4632
+ await this.writeJsonFile(filePath, item);
4413
4633
  }
4414
4634
  /** @inheritdoc */
4415
4635
  async delete(key) {
4416
4636
  const filePath = this.keyToPath(key);
4417
- if (!existsSync(filePath)) {
4637
+ if (!await this.fileExists(filePath)) {
4418
4638
  throw new StorageError(
4419
4639
  `Item with key "${key}" not found`,
4420
- "NOT_FOUND"
4640
+ "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */
4421
4641
  );
4422
4642
  }
4423
- unlinkSync(filePath);
4643
+ await unlink(filePath);
4424
4644
  }
4425
4645
  /** @inheritdoc */
4426
4646
  async has(key) {
4427
- return existsSync(this.keyToPath(key));
4647
+ return this.fileExists(this.keyToPath(key));
4428
4648
  }
4429
4649
  /** @inheritdoc */
4430
4650
  async count() {
4431
- this.ensureDirectory();
4432
- return readdirSync(this.directory).filter(
4651
+ await this.ensureDirectoryAsync();
4652
+ return (await readdir(this.directory)).filter(
4433
4653
  (f) => f.endsWith(this.extension)
4434
4654
  ).length;
4435
4655
  }
4436
4656
  /** @inheritdoc */
4437
4657
  async clear() {
4438
- this.ensureDirectory();
4439
- const files = readdirSync(this.directory).filter(
4658
+ await this.ensureDirectoryAsync();
4659
+ const files = (await readdir(this.directory)).filter(
4440
4660
  (f) => f.endsWith(this.extension)
4441
4661
  );
4442
4662
  for (const file of files) {
4443
- unlinkSync(join(this.directory, file));
4663
+ await unlink(join(this.directory, file));
4444
4664
  }
4445
4665
  }
4446
4666
  keyToPath(key) {
@@ -4450,42 +4670,58 @@ var FileStorage = class {
4450
4670
  );
4451
4671
  return join(this.directory, `${safeKey}${this.extension}`);
4452
4672
  }
4453
- ensureDirectory() {
4673
+ /** Sync directory init — used only in constructor (one-time). */
4674
+ ensureDirectorySync() {
4454
4675
  if (!existsSync(this.directory)) {
4455
4676
  mkdirSync(this.directory, { recursive: true });
4456
4677
  }
4457
4678
  }
4458
- readFile(filePath) {
4679
+ /** Async directory init — used in operations. */
4680
+ async ensureDirectoryAsync() {
4681
+ if (!await this.fileExists(this.directory)) {
4682
+ await mkdir(this.directory, { recursive: true });
4683
+ }
4684
+ }
4685
+ async fileExists(filePath) {
4459
4686
  try {
4460
- const content = readFileSync(filePath, "utf-8");
4687
+ await access(filePath);
4688
+ return true;
4689
+ } catch {
4690
+ return false;
4691
+ }
4692
+ }
4693
+ async readJsonFile(filePath) {
4694
+ try {
4695
+ const content = await readFile(filePath, "utf-8");
4461
4696
  return JSON.parse(content);
4462
4697
  } catch (error) {
4463
4698
  if (error instanceof SyntaxError) {
4464
4699
  throw new StorageError(
4465
4700
  `Failed to parse file: ${filePath}`,
4466
- "SERIALIZATION_ERROR"
4701
+ "STORAGE_SERIALIZATION_ERROR" /* STORAGE_SERIALIZATION_ERROR */
4467
4702
  );
4468
4703
  }
4469
4704
  throw new StorageError(
4470
4705
  `Failed to read file: ${filePath}`,
4471
- "IO_ERROR"
4706
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4472
4707
  );
4473
4708
  }
4474
4709
  }
4475
- writeFile(filePath, item) {
4710
+ async writeJsonFile(filePath, item) {
4476
4711
  try {
4477
4712
  const content = JSON.stringify(item, null, 2);
4478
- writeFileSync(filePath, content, "utf-8");
4713
+ await writeFile(filePath, content, "utf-8");
4479
4714
  } catch {
4480
4715
  throw new StorageError(
4481
4716
  `Failed to write file: ${filePath}`,
4482
- "IO_ERROR"
4717
+ "STORAGE_IO_ERROR" /* STORAGE_IO_ERROR */
4483
4718
  );
4484
4719
  }
4485
4720
  }
4486
4721
  };
4487
4722
 
4488
4723
  // src/chat/sessions.ts
4724
+ init_errors();
4489
4725
  var BaseSessionStore = class {
4490
4726
  constructor(adapter) {
4491
4727
  this.adapter = adapter;
@@ -4524,7 +4760,7 @@ var BaseSessionStore = class {
4524
4760
  async updateTitle(id, title) {
4525
4761
  const session = await this.adapter.get(id);
4526
4762
  if (!session) {
4527
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4763
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4528
4764
  }
4529
4765
  session.title = title;
4530
4766
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4533,7 +4769,7 @@ var BaseSessionStore = class {
4533
4769
  async updateConfig(id, config) {
4534
4770
  const session = await this.adapter.get(id);
4535
4771
  if (!session) {
4536
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4772
+ throw new StorageError(`Session "${id}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4537
4773
  }
4538
4774
  session.config = { ...session.config, ...config };
4539
4775
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -4545,7 +4781,7 @@ var BaseSessionStore = class {
4545
4781
  async appendMessage(sessionId, message) {
4546
4782
  const session = await this.adapter.get(sessionId);
4547
4783
  if (!session) {
4548
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4784
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4549
4785
  }
4550
4786
  session.messages.push(structuredClone(message));
4551
4787
  session.metadata.messageCount = session.messages.length;
@@ -4556,7 +4792,7 @@ var BaseSessionStore = class {
4556
4792
  if (messages.length === 0) return;
4557
4793
  const session = await this.adapter.get(sessionId);
4558
4794
  if (!session) {
4559
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4795
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4560
4796
  }
4561
4797
  for (const msg of messages) {
4562
4798
  session.messages.push(structuredClone(msg));
@@ -4568,7 +4804,7 @@ var BaseSessionStore = class {
4568
4804
  async loadMessages(sessionId, options) {
4569
4805
  const session = await this.adapter.get(sessionId);
4570
4806
  if (!session) {
4571
- throw new StorageError(`Session "${sessionId}" not found`, "NOT_FOUND");
4807
+ throw new StorageError(`Session "${sessionId}" not found`, "STORAGE_NOT_FOUND" /* STORAGE_NOT_FOUND */);
4572
4808
  }
4573
4809
  const total = session.messages.length;
4574
4810
  const offset = options?.offset ?? 0;
@@ -4580,24 +4816,6 @@ var BaseSessionStore = class {
4580
4816
  hasMore: offset + limit < total
4581
4817
  };
4582
4818
  }
4583
- async archiveSession(id) {
4584
- const session = await this.adapter.get(id);
4585
- if (!session) {
4586
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4587
- }
4588
- session.status = "archived";
4589
- session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4590
- await this.adapter.update(id, session);
4591
- }
4592
- async unarchiveSession(id) {
4593
- const session = await this.adapter.get(id);
4594
- if (!session) {
4595
- throw new StorageError(`Session "${id}" not found`, "NOT_FOUND");
4596
- }
4597
- session.status = "active";
4598
- session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4599
- await this.adapter.update(id, session);
4600
- }
4601
4819
  async searchSessions(options) {
4602
4820
  const query = options.query.toLowerCase();
4603
4821
  const limit = options.limit ?? 20;
@@ -4619,15 +4837,6 @@ var BaseSessionStore = class {
4619
4837
  async clear() {
4620
4838
  return this.adapter.clear();
4621
4839
  }
4622
- // ── Deprecated Aliases ──────────────────────────────────────
4623
- /** @deprecated Use `appendMessage()` instead */
4624
- async addMessage(sessionId, message) {
4625
- return this.appendMessage(sessionId, message);
4626
- }
4627
- /** @deprecated Use `loadMessages()` instead */
4628
- async getMessages(sessionId, options) {
4629
- return this.loadMessages(sessionId, options);
4630
- }
4631
4840
  };
4632
4841
  var InMemorySessionStore = class extends BaseSessionStore {
4633
4842
  constructor() {
@@ -4640,28 +4849,59 @@ var FileSessionStore = class extends BaseSessionStore {
4640
4849
  }
4641
4850
  };
4642
4851
 
4852
+ // src/chat/backends/types.ts
4853
+ function isResumableBackend(adapter) {
4854
+ return "canResume" in adapter && typeof adapter.canResume === "function";
4855
+ }
4856
+
4643
4857
  // src/chat/backends/base.ts
4644
4858
  var BaseBackendAdapter = class {
4645
4859
  name;
4646
- _agentService;
4647
- _agent = null;
4860
+ _agentService = null;
4861
+ _agentServiceFactory = null;
4648
4862
  _disposed = false;
4649
4863
  _agentConfig;
4650
4864
  _ownsService;
4865
+ // Agent lifecycle: tracks current agent and the model it was created with.
4866
+ // For persistent sessions, reused across calls when model matches.
4867
+ // For non-persistent, recreated every call.
4868
+ _currentAgent = null;
4651
4869
  constructor(name, options) {
4652
4870
  this.name = name;
4653
4871
  this._agentConfig = options.agentConfig;
4654
4872
  if (options.agentService) {
4655
4873
  this._agentService = options.agentService;
4656
4874
  this._ownsService = false;
4875
+ } else if (options.agentServiceFactory) {
4876
+ this._agentServiceFactory = options.agentServiceFactory;
4877
+ this._ownsService = true;
4657
4878
  } else {
4658
4879
  this._agentService = this.createService();
4659
4880
  this._ownsService = true;
4660
4881
  }
4661
4882
  }
4662
4883
  get agentService() {
4884
+ if (!this._agentService) {
4885
+ if (this._agentServiceFactory) {
4886
+ this._agentService = this._agentServiceFactory();
4887
+ this._agentServiceFactory = null;
4888
+ } else {
4889
+ throw new ChatError("Agent service not available", {
4890
+ code: "BACKEND_NOT_INSTALLED" /* BACKEND_NOT_INSTALLED */
4891
+ });
4892
+ }
4893
+ }
4663
4894
  return this._agentService;
4664
4895
  }
4896
+ get currentModel() {
4897
+ return this._agentConfig.model;
4898
+ }
4899
+ /**
4900
+ * @deprecated No-op. Tools are passed per-call via SendMessageOptions.tools.
4901
+ * Kept for backward compatibility with code that calls setTools() directly.
4902
+ */
4903
+ setTools() {
4904
+ }
4665
4905
  async sendMessage(session, message, options) {
4666
4906
  this.assertNotDisposed();
4667
4907
  const events = this.streamMessage(session, message, options);
@@ -4689,7 +4929,7 @@ var BaseBackendAdapter = class {
4689
4929
  async *streamMessage(session, message, options) {
4690
4930
  this.assertNotDisposed();
4691
4931
  const agent = this.getOrCreateAgent(options);
4692
- const messages = session.messages.map(toAgentMessage);
4932
+ const messages = session.messages.flatMap(toAgentMessages);
4693
4933
  messages.push({ role: "user", content: message });
4694
4934
  yield* this.streamAgentEvents(agent, messages, options);
4695
4935
  }
@@ -4699,9 +4939,13 @@ var BaseBackendAdapter = class {
4699
4939
  */
4700
4940
  async *streamAgentEvents(agent, messages, options) {
4701
4941
  const messageId = createChatId();
4942
+ const model = options?.model ?? this._agentConfig.model ?? "";
4702
4943
  const agentEvents = agent.streamWithContext(messages, {
4944
+ model,
4703
4945
  signal: options?.signal,
4704
- context: options?.context
4946
+ context: options?.context,
4947
+ tools: options?.tools,
4948
+ ...options?.systemPrompt ? { systemMessage: options.systemPrompt } : {}
4705
4949
  });
4706
4950
  yield { type: "message:start", messageId, role: "assistant" };
4707
4951
  let text = "";
@@ -4727,31 +4971,46 @@ var BaseBackendAdapter = class {
4727
4971
  }
4728
4972
  async listModels() {
4729
4973
  this.assertNotDisposed();
4730
- return this._agentService.listModels();
4974
+ return this.agentService.listModels();
4731
4975
  }
4732
4976
  async validate() {
4733
4977
  this.assertNotDisposed();
4734
- return this._agentService.validate();
4978
+ return this.agentService.validate();
4735
4979
  }
4736
4980
  async dispose() {
4737
4981
  if (this._disposed) return;
4738
4982
  this._disposed = true;
4739
- this._agent?.dispose();
4740
- this._agent = null;
4741
- if (this._ownsService) {
4983
+ if (this._currentAgent) {
4984
+ this._currentAgent.instance.dispose();
4985
+ this._currentAgent = null;
4986
+ }
4987
+ if (this._ownsService && this._agentService && typeof this._agentService.dispose === "function") {
4742
4988
  await this._agentService.dispose();
4743
4989
  }
4744
4990
  }
4745
- /** Get or create an agent, applying model override from options */
4991
+ /** Get or create an agent. Model is passed per-call via RunOptions.
4992
+ * Tools are passed per-call via SendMessageOptions — not baked into config.
4993
+ * For persistent sessions, reuses agent when model matches. */
4746
4994
  getOrCreateAgent(options) {
4747
- const config = options?.model ? { ...this._agentConfig, model: options.model } : this._agentConfig;
4748
- if (this._agentConfig.sessionMode === "persistent" && this._agent) {
4749
- return this._agent;
4995
+ const model = options?.model ?? this._agentConfig.model;
4996
+ if (this._agentConfig.sessionMode === "persistent" && this._currentAgent) {
4997
+ if (this._currentAgent.model === model) {
4998
+ return this._currentAgent.instance;
4999
+ }
5000
+ this._currentAgent.instance.dispose();
5001
+ this._currentAgent = null;
4750
5002
  }
4751
- const agent = this._agentService.createAgent(config);
4752
- if (this._agentConfig.sessionMode === "persistent") {
4753
- this._agent = agent;
5003
+ if (this._currentAgent) {
5004
+ this._currentAgent.instance.dispose();
5005
+ this._currentAgent = null;
4754
5006
  }
5007
+ const config = {
5008
+ ...this._agentConfig,
5009
+ ...model !== void 0 && { model },
5010
+ ...options?.tools?.length ? { tools: options.tools } : {}
5011
+ };
5012
+ const agent = this.agentService.createAgent(config);
5013
+ this._currentAgent = { instance: agent, model };
4755
5014
  return agent;
4756
5015
  }
4757
5016
  assertNotDisposed() {
@@ -4763,21 +5022,15 @@ var BaseBackendAdapter = class {
4763
5022
  }
4764
5023
  };
4765
5024
 
4766
- // src/chat/backends/copilot.ts
4767
- var CopilotChatAdapter = class extends BaseBackendAdapter {
5025
+ // src/chat/backends/resumable.ts
5026
+ var ResumableChatAdapter = class extends BaseBackendAdapter {
4768
5027
  _backendSessionId = null;
4769
- _copilotOptions;
4770
- constructor(options) {
5028
+ constructor(name, options) {
4771
5029
  const agentConfig = {
4772
5030
  ...options.agentConfig,
4773
5031
  sessionMode: "persistent"
4774
5032
  };
4775
- super("copilot", { ...options, agentConfig });
4776
- this._copilotOptions = options.copilotOptions;
4777
- }
4778
- createService() {
4779
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4780
- return createAgentService2("copilot", this._copilotOptions);
5033
+ super(name, { ...options, agentConfig });
4781
5034
  }
4782
5035
  get backendSessionId() {
4783
5036
  return this._backendSessionId;
@@ -4806,7 +5059,7 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
4806
5059
  { code: "SESSION_EXPIRED" /* SESSION_EXPIRED */ }
4807
5060
  );
4808
5061
  }
4809
- const messages = session.messages.map(toAgentMessage);
5062
+ const messages = session.messages.flatMap(toAgentMessages);
4810
5063
  yield* this.streamAgentEvents(agent, messages, options);
4811
5064
  }
4812
5065
  captureSessionId(agent) {
@@ -4816,81 +5069,325 @@ var CopilotChatAdapter = class extends BaseBackendAdapter {
4816
5069
  }
4817
5070
  };
4818
5071
 
5072
+ // src/chat/backends/copilot.ts
5073
+ var CopilotChatAdapter = class extends ResumableChatAdapter {
5074
+ _copilotOptions;
5075
+ constructor(options) {
5076
+ super("copilot", options);
5077
+ this._copilotOptions = options.copilotOptions;
5078
+ }
5079
+ createService() {
5080
+ const { createCopilotService: createCopilotService2 } = (init_copilot(), __toCommonJS(copilot_exports));
5081
+ return createCopilotService2(this._copilotOptions || {});
5082
+ }
5083
+ };
5084
+
4819
5085
  // src/chat/backends/claude.ts
4820
- var ClaudeChatAdapter = class extends BaseBackendAdapter {
4821
- _backendSessionId = null;
5086
+ var ClaudeChatAdapter = class extends ResumableChatAdapter {
4822
5087
  _claudeOptions;
4823
5088
  constructor(options) {
4824
- const agentConfig = {
4825
- ...options.agentConfig,
4826
- sessionMode: "persistent"
4827
- };
4828
- super("claude", { ...options, agentConfig });
5089
+ super("claude", options);
4829
5090
  this._claudeOptions = options.claudeOptions;
4830
5091
  }
4831
5092
  createService() {
4832
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4833
- return createAgentService2("claude", this._claudeOptions);
5093
+ const { createClaudeService: createClaudeService2 } = (init_claude(), __toCommonJS(claude_exports));
5094
+ return createClaudeService2(this._claudeOptions || {});
4834
5095
  }
4835
- get backendSessionId() {
4836
- return this._backendSessionId;
5096
+ };
5097
+
5098
+ // src/chat/backends/vercel-ai.ts
5099
+ var VercelAIChatAdapter = class extends BaseBackendAdapter {
5100
+ _vercelOptions;
5101
+ constructor(options) {
5102
+ super("vercel-ai", options);
5103
+ this._vercelOptions = options.vercelOptions;
4837
5104
  }
4838
- canResume() {
4839
- return this._backendSessionId !== null;
5105
+ createService() {
5106
+ const { createVercelAIService: createVercelAIService2 } = (init_vercel_ai(), __toCommonJS(vercel_ai_exports));
5107
+ return createVercelAIService2(this._vercelOptions || {});
4840
5108
  }
4841
- async *resume(session, backendSessionId, options) {
4842
- this.assertNotDisposed();
4843
- if (!backendSessionId) {
4844
- throw new ChatError("Backend session ID is required for resume", {
4845
- code: "INVALID_INPUT" /* INVALID_INPUT */
5109
+ captureSessionId(_agent) {
5110
+ }
5111
+ };
5112
+
5113
+ // src/backends/mock-llm.ts
5114
+ init_base_agent();
5115
+ init_errors2();
5116
+ function extractPrompt(messages) {
5117
+ for (let i = messages.length - 1; i >= 0; i--) {
5118
+ const msg = messages[i];
5119
+ if (msg.role === "user") {
5120
+ return typeof msg.content === "string" ? msg.content : msg.content.filter((p) => p.type === "text").map((p) => p.text).join("");
5121
+ }
5122
+ }
5123
+ return "";
5124
+ }
5125
+ function resolveResponse(mode, messages, callIndex) {
5126
+ switch (mode.type) {
5127
+ case "echo":
5128
+ return extractPrompt(messages);
5129
+ case "static":
5130
+ return mode.response;
5131
+ case "scripted": {
5132
+ if (mode.loop) {
5133
+ return mode.responses[callIndex % mode.responses.length];
5134
+ }
5135
+ if (callIndex < mode.responses.length) {
5136
+ return mode.responses[callIndex];
5137
+ }
5138
+ return mode.responses[mode.responses.length - 1];
5139
+ }
5140
+ case "error":
5141
+ throw new AgentSDKError(mode.error, {
5142
+ code: mode.code ?? "backend_error",
5143
+ retryable: mode.recoverable ?? false
4846
5144
  });
5145
+ }
5146
+ }
5147
+ async function applyLatency(latency, signal) {
5148
+ if (!latency) return;
5149
+ const ms = latency.type === "fixed" ? latency.ms : latency.minMs + Math.random() * (latency.maxMs - latency.minMs);
5150
+ if (ms <= 0) return;
5151
+ await new Promise((resolve, reject) => {
5152
+ const timer = setTimeout(resolve, ms);
5153
+ const onAbort = () => {
5154
+ clearTimeout(timer);
5155
+ reject(new Error("aborted"));
5156
+ };
5157
+ if (signal.aborted) {
5158
+ clearTimeout(timer);
5159
+ reject(new Error("aborted"));
5160
+ return;
4847
5161
  }
4848
- const agent = this.getOrCreateAgent(options);
4849
- const currentSessionId = agent.sessionId;
4850
- if (!currentSessionId) {
4851
- throw new ChatError(
4852
- `No active session to resume (requested: ${backendSessionId})`,
4853
- { code: "SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */ }
4854
- );
5162
+ signal.addEventListener("abort", onAbort, { once: true });
5163
+ });
5164
+ }
5165
+ function chunkText(text, streaming) {
5166
+ if (streaming?.chunkSize && streaming.chunkSize > 0) {
5167
+ const chunks = [];
5168
+ for (let i = 0; i < text.length; i += streaming.chunkSize) {
5169
+ chunks.push(text.slice(i, i + streaming.chunkSize));
4855
5170
  }
4856
- if (currentSessionId !== backendSessionId) {
4857
- throw new ChatError(
4858
- `Session expired: expected ${backendSessionId}, got ${currentSessionId}`,
4859
- { code: "SESSION_EXPIRED" /* SESSION_EXPIRED */ }
4860
- );
5171
+ return chunks;
5172
+ }
5173
+ return text.split(/(\s+)/).filter(Boolean);
5174
+ }
5175
+ async function chunkDelay(streaming, signal) {
5176
+ const ms = streaming?.chunkDelayMs;
5177
+ if (!ms || ms <= 0) return;
5178
+ await new Promise((resolve, reject) => {
5179
+ const timer = setTimeout(resolve, ms);
5180
+ const onAbort = () => {
5181
+ clearTimeout(timer);
5182
+ reject(new Error("aborted"));
5183
+ };
5184
+ if (signal.aborted) {
5185
+ clearTimeout(timer);
5186
+ reject(new Error("aborted"));
5187
+ return;
4861
5188
  }
4862
- const messages = session.messages.map(toAgentMessage);
4863
- yield* this.streamAgentEvents(agent, messages, options);
5189
+ signal.addEventListener("abort", onAbort, { once: true });
5190
+ });
5191
+ }
5192
+ var MockLLMAgent = class extends BaseAgent {
5193
+ backendName = "mock-llm";
5194
+ mode;
5195
+ latency;
5196
+ streaming;
5197
+ finishReason;
5198
+ permissions;
5199
+ toolCallConfigs;
5200
+ configuredStructuredOutput;
5201
+ callIndex = 0;
5202
+ constructor(config, options) {
5203
+ super(config);
5204
+ this.mode = options.mode ?? { type: "echo" };
5205
+ this.latency = options.latency;
5206
+ this.streaming = options.streaming;
5207
+ this.finishReason = options.finishReason ?? "stop";
5208
+ this.permissions = options.permissions;
5209
+ this.toolCallConfigs = options.toolCalls ?? [];
5210
+ this.configuredStructuredOutput = options.structuredOutput;
5211
+ }
5212
+ async executeRun(messages, _options, signal) {
5213
+ this.checkAbort(signal);
5214
+ await applyLatency(this.latency, signal);
5215
+ this.checkAbort(signal);
5216
+ const idx = this.callIndex++;
5217
+ const output = resolveResponse(this.mode, messages, idx);
5218
+ const toolCalls = this.toolCallConfigs.map((tc) => ({
5219
+ toolName: tc.toolName,
5220
+ args: tc.args ?? {},
5221
+ result: tc.result ?? null,
5222
+ approved: true
5223
+ }));
5224
+ return {
5225
+ output,
5226
+ structuredOutput: void 0,
5227
+ toolCalls,
5228
+ messages: [
5229
+ ...messages,
5230
+ { role: "assistant", content: output }
5231
+ ],
5232
+ usage: { promptTokens: 10, completionTokens: output.length }
5233
+ };
4864
5234
  }
4865
- captureSessionId(agent) {
4866
- if (agent.sessionId) {
4867
- this._backendSessionId = agent.sessionId;
5235
+ async executeRunStructured(messages, _schema, _options, signal) {
5236
+ this.checkAbort(signal);
5237
+ await applyLatency(this.latency, signal);
5238
+ this.checkAbort(signal);
5239
+ const idx = this.callIndex++;
5240
+ const output = resolveResponse(this.mode, messages, idx);
5241
+ let parsed;
5242
+ if (this.configuredStructuredOutput !== void 0) {
5243
+ parsed = this.configuredStructuredOutput;
5244
+ } else {
5245
+ try {
5246
+ parsed = JSON.parse(output);
5247
+ } catch {
5248
+ parsed = output;
5249
+ }
5250
+ }
5251
+ return {
5252
+ output,
5253
+ structuredOutput: parsed,
5254
+ toolCalls: [],
5255
+ messages: [
5256
+ ...messages,
5257
+ { role: "assistant", content: output }
5258
+ ],
5259
+ usage: { promptTokens: 10, completionTokens: output.length }
5260
+ };
5261
+ }
5262
+ async *executeStream(messages, _options, signal) {
5263
+ this.checkAbort(signal);
5264
+ await applyLatency(this.latency, signal);
5265
+ this.checkAbort(signal);
5266
+ if (this.permissions) {
5267
+ yield* this.simulatePermissions(signal);
5268
+ }
5269
+ if (this.toolCallConfigs.length > 0) {
5270
+ yield* this.simulateToolCalls(signal);
5271
+ }
5272
+ const idx = this.callIndex++;
5273
+ const output = resolveResponse(this.mode, messages, idx);
5274
+ const chunks = chunkText(output, this.streaming);
5275
+ for (let i = 0; i < chunks.length; i++) {
5276
+ this.checkAbort(signal);
5277
+ if (i > 0) {
5278
+ await chunkDelay(this.streaming, signal);
5279
+ }
5280
+ yield { type: "text_delta", text: chunks[i] };
5281
+ }
5282
+ yield {
5283
+ type: "usage_update",
5284
+ promptTokens: 10,
5285
+ completionTokens: output.length
5286
+ };
5287
+ yield {
5288
+ type: "done",
5289
+ finalOutput: output,
5290
+ finishReason: this.finishReason
5291
+ };
5292
+ }
5293
+ async *simulateToolCalls(signal) {
5294
+ for (let i = 0; i < this.toolCallConfigs.length; i++) {
5295
+ this.checkAbort(signal);
5296
+ const tc = this.toolCallConfigs[i];
5297
+ const toolCallId = tc.toolCallId ?? `mock-tc-${i}`;
5298
+ yield {
5299
+ type: "tool_call_start",
5300
+ toolCallId,
5301
+ toolName: tc.toolName,
5302
+ args: tc.args ?? {}
5303
+ };
5304
+ yield {
5305
+ type: "tool_call_end",
5306
+ toolCallId,
5307
+ toolName: tc.toolName,
5308
+ result: tc.result ?? null
5309
+ };
5310
+ }
5311
+ }
5312
+ async *simulatePermissions(signal) {
5313
+ const perms = this.permissions;
5314
+ for (const toolName of perms.toolNames) {
5315
+ this.checkAbort(signal);
5316
+ const request = {
5317
+ toolName,
5318
+ toolArgs: {}
5319
+ };
5320
+ yield { type: "permission_request", request };
5321
+ if (perms.denyTools?.includes(toolName)) {
5322
+ yield {
5323
+ type: "permission_response",
5324
+ toolName,
5325
+ decision: { allowed: false, reason: "Denied by mock configuration" }
5326
+ };
5327
+ } else if (perms.autoApprove) {
5328
+ yield {
5329
+ type: "permission_response",
5330
+ toolName,
5331
+ decision: { allowed: true, scope: "once" }
5332
+ };
5333
+ } else {
5334
+ const supervisor = this.getConfig().supervisor;
5335
+ if (supervisor?.onPermission) {
5336
+ const decision = await supervisor.onPermission(request, signal);
5337
+ yield { type: "permission_response", toolName, decision };
5338
+ } else {
5339
+ yield {
5340
+ type: "permission_response",
5341
+ toolName,
5342
+ decision: { allowed: true, scope: "once" }
5343
+ };
5344
+ }
5345
+ }
4868
5346
  }
4869
5347
  }
4870
5348
  };
4871
-
4872
- // src/chat/backends/vercel-ai.ts
4873
- var VercelAIChatAdapter = class extends BaseBackendAdapter {
4874
- _vercelOptions;
4875
- constructor(options) {
4876
- super("vercel-ai", options);
4877
- this._vercelOptions = options.vercelOptions;
5349
+ var MockLLMService = class {
5350
+ name = "mock-llm";
5351
+ options;
5352
+ models;
5353
+ constructor(options = {}) {
5354
+ this.options = options;
5355
+ this.models = (options.models ?? [
5356
+ { id: "mock-fast", name: "Mock Fast" },
5357
+ { id: "mock-quality", name: "Mock Quality" }
5358
+ ]).map((m) => ({
5359
+ id: m.id,
5360
+ name: m.name,
5361
+ description: m.description
5362
+ }));
4878
5363
  }
4879
- createService() {
4880
- const { createAgentService: createAgentService2 } = (init_src(), __toCommonJS(src_exports));
4881
- return createAgentService2("vercel-ai", this._vercelOptions);
5364
+ createAgent(config) {
5365
+ return new MockLLMAgent(config, this.options);
4882
5366
  }
4883
- get backendSessionId() {
4884
- return null;
5367
+ async listModels() {
5368
+ return this.models;
4885
5369
  }
4886
- canResume() {
4887
- return false;
5370
+ async validate() {
5371
+ return { valid: true, errors: [] };
4888
5372
  }
4889
- async *resume(_session, _backendSessionId, _options) {
4890
- throw new ChatError(
4891
- "Vercel AI adapter does not support session resume (stateless)",
4892
- { code: "PROVIDER_ERROR" /* PROVIDER_ERROR */ }
4893
- );
5373
+ async dispose() {
5374
+ }
5375
+ };
5376
+ function createMockLLMService(options = {}) {
5377
+ return new MockLLMService(options);
5378
+ }
5379
+
5380
+ // src/chat/backends/mock-llm.ts
5381
+ var MockLLMChatAdapter = class extends BaseBackendAdapter {
5382
+ constructor(options) {
5383
+ const mockOpts = options.mockOptions;
5384
+ super("mock-llm", {
5385
+ ...options,
5386
+ agentServiceFactory: () => createMockLLMService(mockOpts || {})
5387
+ });
5388
+ }
5389
+ createService() {
5390
+ return createMockLLMService({});
4894
5391
  }
4895
5392
  captureSessionId(_agent) {
4896
5393
  }
@@ -4971,16 +5468,22 @@ var SSEChatTransport = class {
4971
5468
  };
4972
5469
  async function streamToTransport(events, transport) {
4973
5470
  try {
4974
- let accumulatedText = "";
5471
+ const textChunks = [];
5472
+ let finishReason;
4975
5473
  for await (const event of events) {
4976
5474
  if (!transport.isOpen) break;
5475
+ if (event.type === "done") {
5476
+ finishReason = event.finishReason;
5477
+ continue;
5478
+ }
4977
5479
  transport.send(event);
4978
5480
  if (event.type === "message:delta") {
4979
- accumulatedText += event.text;
5481
+ textChunks.push(event.text);
4980
5482
  }
4981
5483
  }
4982
5484
  if (transport.isOpen) {
4983
- transport.send({ type: "done", finalOutput: accumulatedText || void 0 });
5485
+ const finalOutput = textChunks.length > 0 ? textChunks.join("") : void 0;
5486
+ transport.send({ type: "done", finalOutput, finishReason });
4984
5487
  }
4985
5488
  transport.close();
4986
5489
  } catch (err) {
@@ -5067,9 +5570,9 @@ var InProcessChatTransport = class {
5067
5570
  send(event) {
5068
5571
  if (!this._open) return;
5069
5572
  if (this._resolve) {
5070
- const resolve2 = this._resolve;
5573
+ const resolve = this._resolve;
5071
5574
  this._resolve = null;
5072
- resolve2({ value: event, done: false });
5575
+ resolve({ value: event, done: false });
5073
5576
  } else {
5074
5577
  this._buffer.push(event);
5075
5578
  }
@@ -5078,9 +5581,9 @@ var InProcessChatTransport = class {
5078
5581
  if (!this._open) return;
5079
5582
  this._open = false;
5080
5583
  if (this._resolve) {
5081
- const resolve2 = this._resolve;
5584
+ const resolve = this._resolve;
5082
5585
  this._resolve = null;
5083
- resolve2({ value: void 0, done: true });
5586
+ resolve({ value: void 0, done: true });
5084
5587
  }
5085
5588
  }
5086
5589
  error(err) {
@@ -5092,9 +5595,9 @@ var InProcessChatTransport = class {
5092
5595
  recoverable: false
5093
5596
  };
5094
5597
  if (this._resolve) {
5095
- const resolve2 = this._resolve;
5598
+ const resolve = this._resolve;
5096
5599
  this._resolve = null;
5097
- resolve2({ value: errorEvent, done: false });
5600
+ resolve({ value: errorEvent, done: false });
5098
5601
  } else {
5099
5602
  this._error = err;
5100
5603
  }
@@ -5119,8 +5622,8 @@ var InProcessChatTransport = class {
5119
5622
  if (!this._open) {
5120
5623
  return Promise.resolve({ value: void 0, done: true });
5121
5624
  }
5122
- return new Promise((resolve2) => {
5123
- this._resolve = resolve2;
5625
+ return new Promise((resolve) => {
5626
+ this._resolve = resolve;
5124
5627
  });
5125
5628
  }
5126
5629
  };
@@ -5291,6 +5794,6 @@ var ChatEventBus = class extends TypedEventEmitter {
5291
5794
  }
5292
5795
  };
5293
5796
 
5294
- export { BaseBackendAdapter, ChatError, ChatErrorCode, ChatEventBus, ChatError as ChatSDKError, ClaudeChatAdapter, ContextWindowManager, CopilotChatAdapter, ExponentialBackoffStrategy, FileSessionStore, InMemorySessionStore, InProcessChatTransport, MessageAccumulator, SSEChatTransport, TypedEventEmitter, VercelAIChatAdapter, WsChatTransport, adaptAgentEvents, agentEventToChatEvent, classifyError, createChatId, createChatRuntime, estimateTokens, fromAgentMessage, getMessageReasoning, getMessageText, getMessageToolCalls, isChatEvent, isChatMessage, isChatSession, isFilePart, isMessagePart, isReasoningPart, isRetryable, isSourcePart, isTextPart, isToolCallPart, streamToTransport, toAgentMessage, toChatId, withRetry, withStreamWatchdog };
5797
+ export { BaseBackendAdapter, ChatError, ChatEventBus, ClaudeChatAdapter, ContextWindowManager, CopilotChatAdapter, ErrorCode, ExponentialBackoffStrategy, FileSessionStore, InMemorySessionStore, InProcessChatTransport, ListenerSet, MessageAccumulator, MockLLMChatAdapter, SSEChatTransport, TypedEventEmitter, VercelAIChatAdapter, WsChatTransport, adaptAgentEvents, agentEventToChatEvent, classifyError, createChatId, createChatRuntime, createTextMessage, estimateTokens, fromAgentMessage, getMessageReasoning, getMessageText, getMessageToolCalls, isChatEvent, isChatMessage, isChatSession, isFilePart, isMessagePart, isObservableSession, isReasoningPart, isResumableBackend, isRetryable, isSourcePart, isTextPart, isToolCallPart, streamToTransport, toAgentMessage, toAgentMessages, toChatId, withRetry, withStreamWatchdog };
5295
5798
  //# sourceMappingURL=index.js.map
5296
5799
  //# sourceMappingURL=index.js.map