@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,598 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var crypto$1 = require('crypto');
4
3
  var react = require('react');
5
4
 
6
- var __defProp = Object.defineProperty;
7
- var __getOwnPropNames = Object.getOwnPropertyNames;
8
- var __esm = (fn, res) => function __init() {
9
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
- };
11
- var __export = (target, all) => {
12
- for (var name in all)
13
- __defProp(target, name, { get: all[name], enumerable: true });
14
- };
15
-
16
- // src/errors.ts
17
- var AgentSDKError;
18
- var init_errors = __esm({
19
- "src/errors.ts"() {
20
- AgentSDKError = class extends Error {
21
- /** @internal Marker for cross-bundle identity checks */
22
- _agentSDKError = true;
23
- constructor(message, options) {
24
- super(message, options);
25
- this.name = "AgentSDKError";
26
- }
27
- /** Check if an error is an AgentSDKError (works across bundled copies) */
28
- static is(error) {
29
- return error instanceof Error && "_agentSDKError" in error && error._agentSDKError === true;
30
- }
31
- };
32
- }
33
- });
34
-
35
- // src/auth/types.ts
36
- var AuthError, DeviceCodeExpiredError, AccessDeniedError, TokenExchangeError;
37
- var init_types = __esm({
38
- "src/auth/types.ts"() {
39
- init_errors();
40
- AuthError = class extends AgentSDKError {
41
- constructor(message, options) {
42
- super(message, options);
43
- this.name = "AuthError";
44
- }
45
- };
46
- DeviceCodeExpiredError = class extends AuthError {
47
- constructor() {
48
- super("Device code expired. Please restart the auth flow.");
49
- this.name = "DeviceCodeExpiredError";
50
- }
51
- };
52
- AccessDeniedError = class extends AuthError {
53
- constructor() {
54
- super("Access was denied by the user.");
55
- this.name = "AccessDeniedError";
56
- }
57
- };
58
- TokenExchangeError = class extends AuthError {
59
- constructor(message, options) {
60
- super(message, options);
61
- this.name = "TokenExchangeError";
62
- }
63
- };
64
- }
65
- });
66
-
67
- // src/auth/copilot-auth.ts
68
- var CLIENT_ID, DEVICE_CODE_URL, ACCESS_TOKEN_URL, USER_API_URL, DEFAULT_SCOPES, GRANT_TYPE, CopilotAuth;
69
- var init_copilot_auth = __esm({
70
- "src/auth/copilot-auth.ts"() {
71
- init_types();
72
- CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
73
- DEVICE_CODE_URL = "https://github.com/login/device/code";
74
- ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
75
- USER_API_URL = "https://api.github.com/user";
76
- DEFAULT_SCOPES = "read:user,read:org,repo,gist";
77
- GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
78
- CopilotAuth = class {
79
- fetchFn;
80
- /** @param options - Optional configuration with custom fetch for testing */
81
- constructor(options) {
82
- this.fetchFn = options?.fetch ?? globalThis.fetch;
83
- }
84
- /**
85
- * Start the GitHub Device Flow.
86
- * Returns a device code result with user code, verification URL,
87
- * and a `waitForToken()` function that polls until the user authorizes.
88
- *
89
- * @param options - Optional scopes and abort signal
90
- * @returns Device flow result with user code, verification URL, and waitForToken poller
91
- * @throws {AuthError} If the device code request fails
92
- * @throws {DeviceCodeExpiredError} If the device code expires before user authorizes
93
- * @throws {AccessDeniedError} If the user denies access
94
- *
95
- * @example
96
- * ```ts
97
- * const auth = new CopilotAuth();
98
- * const { userCode, verificationUrl, waitForToken } = await auth.startDeviceFlow();
99
- * console.log(`Open ${verificationUrl} and enter code: ${userCode}`);
100
- * const token = await waitForToken();
101
- * ```
102
- */
103
- async startDeviceFlow(options) {
104
- const scopes = options?.scopes ?? DEFAULT_SCOPES;
105
- const response = await this.fetchFn(DEVICE_CODE_URL, {
106
- method: "POST",
107
- headers: {
108
- "Content-Type": "application/x-www-form-urlencoded",
109
- Accept: "application/json"
110
- },
111
- body: new URLSearchParams({
112
- client_id: CLIENT_ID,
113
- scope: scopes
114
- }),
115
- signal: options?.signal
116
- });
117
- if (!response.ok) {
118
- throw new AuthError(
119
- `Failed to request device code: ${response.status} ${response.statusText}`
120
- );
121
- }
122
- const data = await response.json();
123
- return {
124
- userCode: data.user_code,
125
- verificationUrl: data.verification_uri,
126
- waitForToken: (signal) => this.pollForToken(data.device_code, data.interval, signal)
127
- };
128
- }
129
- async pollForToken(deviceCode, interval, signal) {
130
- let pollIntervalMs = interval * 1e3;
131
- while (true) {
132
- if (signal?.aborted) {
133
- throw new AuthError("Authentication was aborted.");
134
- }
135
- await this.delay(pollIntervalMs, signal);
136
- const response = await this.fetchFn(ACCESS_TOKEN_URL, {
137
- method: "POST",
138
- headers: {
139
- "Content-Type": "application/x-www-form-urlencoded",
140
- Accept: "application/json"
141
- },
142
- body: new URLSearchParams({
143
- client_id: CLIENT_ID,
144
- device_code: deviceCode,
145
- grant_type: GRANT_TYPE
146
- }),
147
- signal
148
- });
149
- if (!response.ok) {
150
- throw new AuthError(
151
- `Token poll failed: ${response.status} ${response.statusText}`
152
- );
153
- }
154
- const data = await response.json();
155
- if (data.access_token) {
156
- const token = {
157
- accessToken: data.access_token,
158
- tokenType: data.token_type ?? "bearer",
159
- obtainedAt: Date.now()
160
- };
161
- try {
162
- const login = await this.fetchUserLogin(data.access_token, signal);
163
- if (login) token.login = login;
164
- } catch {
165
- }
166
- return token;
167
- }
168
- if (data.error === "authorization_pending") {
169
- continue;
170
- }
171
- if (data.error === "slow_down") {
172
- pollIntervalMs = (data.interval ?? interval + 5) * 1e3;
173
- continue;
174
- }
175
- if (data.error === "expired_token") {
176
- throw new DeviceCodeExpiredError();
177
- }
178
- if (data.error === "access_denied") {
179
- throw new AccessDeniedError();
180
- }
181
- throw new AuthError(
182
- data.error_description ?? `Unexpected error: ${data.error}`
183
- );
184
- }
185
- }
186
- async fetchUserLogin(token, signal) {
187
- const response = await this.fetchFn(USER_API_URL, {
188
- headers: {
189
- Authorization: `Bearer ${token}`,
190
- Accept: "application/json"
191
- },
192
- signal
193
- });
194
- if (!response.ok) return void 0;
195
- const user = await response.json();
196
- return user.login;
197
- }
198
- delay(ms, signal) {
199
- return new Promise((resolve, reject) => {
200
- const timer = setTimeout(resolve, ms);
201
- signal?.addEventListener(
202
- "abort",
203
- () => {
204
- clearTimeout(timer);
205
- reject(new AuthError("Authentication was aborted."));
206
- },
207
- { once: true }
208
- );
209
- });
210
- }
211
- };
212
- }
213
- });
214
- function defaultRandomBytes(size) {
215
- return new Uint8Array(crypto$1.randomBytes(size));
216
- }
217
- function base64Encode(bytes) {
218
- return Buffer.from(bytes).toString("base64");
219
- }
220
- function hexEncode(bytes) {
221
- return Buffer.from(bytes).toString("hex");
222
- }
223
- var CLIENT_ID2, AUTHORIZE_URL, TOKEN_URL, DEFAULT_REDIRECT_URI, DEFAULT_SCOPES2, ClaudeAuth;
224
- var init_claude_auth = __esm({
225
- "src/auth/claude-auth.ts"() {
226
- init_types();
227
- CLIENT_ID2 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
228
- AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
229
- TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
230
- DEFAULT_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
231
- DEFAULT_SCOPES2 = "user:profile user:inference user:sessions:claude_code user:mcp_servers";
232
- ClaudeAuth = class _ClaudeAuth {
233
- fetchFn;
234
- randomBytes;
235
- /**
236
- * @param options - Optional configuration with custom fetch and random bytes for testing
237
- */
238
- constructor(options) {
239
- this.fetchFn = options?.fetch ?? globalThis.fetch;
240
- this.randomBytes = options?.randomBytes ?? defaultRandomBytes;
241
- }
242
- /**
243
- * Start the Claude OAuth+PKCE flow.
244
- * Generates PKCE code verifier/challenge and returns an authorize URL
245
- * plus a `completeAuth(code)` function for token exchange.
246
- *
247
- * @param options - Redirect URI and optional scopes
248
- * @returns OAuth flow result with authorize URL and completeAuth function
249
- * @throws {AuthError} If PKCE generation fails
250
- *
251
- * @example
252
- * ```ts
253
- * const auth = new ClaudeAuth();
254
- * const { authorizeUrl, completeAuth } = auth.startOAuthFlow({
255
- * redirectUri: "https://platform.claude.com/oauth/code/callback",
256
- * });
257
- * console.log(`Open: ${authorizeUrl}`);
258
- * const token = await completeAuth(authorizationCode);
259
- * ```
260
- */
261
- startOAuthFlow(options) {
262
- const redirectUri = options?.redirectUri ?? DEFAULT_REDIRECT_URI;
263
- const scopes = options?.scopes ?? DEFAULT_SCOPES2;
264
- const codeVerifier = this.generateCodeVerifier();
265
- const state = this.generateState();
266
- const authorizeUrl = this.buildAuthorizeUrl(
267
- redirectUri,
268
- scopes,
269
- codeVerifier,
270
- state
271
- );
272
- return {
273
- authorizeUrl,
274
- completeAuth: (codeOrUrl) => {
275
- const code = _ClaudeAuth.extractCode(codeOrUrl);
276
- return this.exchangeCode(code, codeVerifier, state, redirectUri);
277
- }
278
- };
279
- }
280
- /**
281
- * Extract an authorization code from user input.
282
- * Accepts a raw code string or a full redirect URL containing a `code` query parameter.
283
- *
284
- * @param input - Raw authorization code or redirect URL
285
- * @returns The extracted authorization code
286
- *
287
- * @example
288
- * ```ts
289
- * ClaudeAuth.extractCode("abc123"); // "abc123"
290
- * ClaudeAuth.extractCode("https://platform.claude.com/oauth/code/callback?code=abc123&state=xyz"); // "abc123"
291
- * ```
292
- */
293
- static extractCode(input) {
294
- const trimmed = input.trim();
295
- try {
296
- const url = new URL(trimmed);
297
- const code = url.searchParams.get("code");
298
- if (code) return code;
299
- } catch {
300
- }
301
- const hashIdx = trimmed.indexOf("#");
302
- if (hashIdx > 0) {
303
- return trimmed.substring(0, hashIdx);
304
- }
305
- return trimmed;
306
- }
307
- /**
308
- * Refresh an expired Claude token.
309
- *
310
- * @param refreshToken - The refresh token from a previous authentication
311
- * @returns New auth token with refreshed access token
312
- * @throws {TokenExchangeError} If the refresh request fails
313
- *
314
- * @example
315
- * ```ts
316
- * const auth = new ClaudeAuth();
317
- * const newToken = await auth.refreshToken(oldToken.refreshToken);
318
- * ```
319
- */
320
- async refreshToken(refreshToken) {
321
- const response = await this.fetchFn(TOKEN_URL, {
322
- method: "POST",
323
- headers: { "Content-Type": "application/json" },
324
- body: JSON.stringify({
325
- grant_type: "refresh_token",
326
- refresh_token: refreshToken,
327
- client_id: CLIENT_ID2
328
- })
329
- });
330
- if (!response.ok) {
331
- const text = await response.text();
332
- throw new TokenExchangeError(
333
- `Token refresh failed: ${response.status} ${text}`
334
- );
335
- }
336
- const data = await response.json();
337
- return this.mapTokenResponse(data);
338
- }
339
- generateCodeVerifier() {
340
- const bytes = this.randomBytes(96);
341
- return base64Encode(bytes).replace(/\+/g, "~").replace(/=/g, "_").replace(/\//g, "-");
342
- }
343
- generateState() {
344
- const bytes = this.randomBytes(16);
345
- return hexEncode(bytes);
346
- }
347
- buildAuthorizeUrl(redirectUri, scopes, codeVerifier, state) {
348
- const codeChallenge = this.generateCodeChallengeSync(codeVerifier);
349
- const url = new URL(AUTHORIZE_URL);
350
- url.searchParams.set("code", "true");
351
- url.searchParams.set("client_id", CLIENT_ID2);
352
- url.searchParams.set("response_type", "code");
353
- url.searchParams.set("redirect_uri", redirectUri);
354
- url.searchParams.set("scope", scopes);
355
- url.searchParams.set("code_challenge", codeChallenge);
356
- url.searchParams.set("code_challenge_method", "S256");
357
- url.searchParams.set("state", state);
358
- return url.toString();
359
- }
360
- generateCodeChallengeSync(verifier) {
361
- const hash = crypto$1.createHash("sha256").update(verifier).digest("base64");
362
- return hash.split("=")[0].replace(/\+/g, "-").replace(/\//g, "_");
363
- }
364
- async exchangeCode(code, codeVerifier, state, redirectUri) {
365
- const response = await this.fetchFn(TOKEN_URL, {
366
- method: "POST",
367
- headers: { "Content-Type": "application/json" },
368
- body: JSON.stringify({
369
- grant_type: "authorization_code",
370
- code,
371
- redirect_uri: redirectUri,
372
- client_id: CLIENT_ID2,
373
- code_verifier: codeVerifier,
374
- state
375
- })
376
- });
377
- if (!response.ok) {
378
- const text = await response.text();
379
- throw new TokenExchangeError(
380
- `Token exchange failed: ${response.status} ${text}`
381
- );
382
- }
383
- const data = await response.json();
384
- return this.mapTokenResponse(data);
385
- }
386
- mapTokenResponse(data) {
387
- return {
388
- accessToken: data.access_token,
389
- tokenType: data.token_type ?? "bearer",
390
- expiresIn: data.expires_in,
391
- obtainedAt: Date.now(),
392
- refreshToken: data.refresh_token,
393
- scopes: data.scope?.split(" ") ?? []
394
- };
395
- }
396
- };
397
- }
398
- });
399
-
400
- // src/auth/refresh-manager.ts
401
- var TokenRefreshManager;
402
- var init_refresh_manager = __esm({
403
- "src/auth/refresh-manager.ts"() {
404
- TokenRefreshManager = class {
405
- currentToken;
406
- refreshFn;
407
- threshold;
408
- maxRetries;
409
- retryDelayMs;
410
- minDelayMs;
411
- timerId = null;
412
- running = false;
413
- disposed = false;
414
- listeners = {
415
- refreshed: /* @__PURE__ */ new Set(),
416
- error: /* @__PURE__ */ new Set(),
417
- expired: /* @__PURE__ */ new Set(),
418
- disposed: /* @__PURE__ */ new Set()
419
- };
420
- constructor(options) {
421
- this.currentToken = { ...options.token };
422
- this.refreshFn = options.refresh;
423
- this.threshold = options.refreshThreshold ?? 0.8;
424
- this.maxRetries = options.maxRetries ?? 3;
425
- this.retryDelayMs = options.retryDelayMs ?? 1e3;
426
- this.minDelayMs = options.minDelayMs ?? 1e3;
427
- }
428
- /** Register an event listener */
429
- on(event, listener) {
430
- this.listeners[event].add(listener);
431
- return this;
432
- }
433
- /** Remove an event listener */
434
- off(event, listener) {
435
- this.listeners[event].delete(listener);
436
- return this;
437
- }
438
- /** Current token managed by this instance */
439
- get token() {
440
- return { ...this.currentToken };
441
- }
442
- /** Whether the manager is currently running */
443
- get isRunning() {
444
- return this.running;
445
- }
446
- /** Whether the manager has been disposed */
447
- get isDisposed() {
448
- return this.disposed;
449
- }
450
- /**
451
- * Start automatic refresh scheduling.
452
- * If the token is already expired, emits "expired" immediately.
453
- * If the token has no expiresIn, does nothing (long-lived token).
454
- */
455
- start() {
456
- if (this.disposed) return;
457
- if (this.running) return;
458
- this.running = true;
459
- this.schedule();
460
- }
461
- /** Stop automatic refresh (can be restarted with start()) */
462
- stop() {
463
- this.running = false;
464
- this.clearTimer();
465
- }
466
- /**
467
- * Update the managed token (e.g. after manual refresh).
468
- * Reschedules automatic refresh if running.
469
- */
470
- updateToken(token) {
471
- if (this.disposed) return;
472
- this.currentToken = { ...token };
473
- if (this.running) {
474
- this.clearTimer();
475
- this.schedule();
476
- }
477
- }
478
- /** Stop and clean up all resources */
479
- dispose() {
480
- if (this.disposed) return;
481
- this.stop();
482
- this.disposed = true;
483
- this.emit("disposed");
484
- for (const set of Object.values(this.listeners)) {
485
- set.clear();
486
- }
487
- }
488
- // ─── Private ──────────────────────────────────────────────────
489
- schedule() {
490
- if (!this.running || this.disposed) return;
491
- const delayMs = this.computeRefreshDelay();
492
- if (delayMs === null) {
493
- return;
494
- }
495
- if (delayMs <= 0) {
496
- this.timerId = setTimeout(() => {
497
- this.timerId = null;
498
- if (!this.running || this.disposed) return;
499
- void this.performRefresh();
500
- }, 0);
501
- return;
502
- }
503
- this.timerId = setTimeout(() => {
504
- this.timerId = null;
505
- if (!this.running || this.disposed) return;
506
- void this.performRefresh();
507
- }, Math.max(delayMs, this.minDelayMs));
508
- }
509
- async performRefresh(attempt = 1) {
510
- if (!this.running || this.disposed) return;
511
- try {
512
- const newToken = await this.refreshFn(this.currentToken);
513
- if (!this.running || this.disposed) return;
514
- this.currentToken = { ...newToken };
515
- this.emit("refreshed", newToken);
516
- this.schedule();
517
- } catch (err) {
518
- if (!this.running || this.disposed) return;
519
- const error = err instanceof Error ? err : new Error(String(err));
520
- this.emit("error", error, attempt);
521
- if (attempt < this.maxRetries) {
522
- const delay = this.retryDelayMs * Math.pow(2, attempt - 1);
523
- this.timerId = setTimeout(() => {
524
- this.timerId = null;
525
- if (!this.running || this.disposed) return;
526
- void this.performRefresh(attempt + 1);
527
- }, delay);
528
- } else {
529
- if (this.isTokenExpired()) {
530
- this.running = false;
531
- this.emit("expired");
532
- } else {
533
- const expiresIn = this.currentToken.expiresIn;
534
- if (expiresIn == null) return;
535
- const expiresAt = this.currentToken.obtainedAt + expiresIn * 1e3;
536
- const waitMs = Math.max(expiresAt - Date.now(), this.minDelayMs);
537
- this.timerId = setTimeout(() => {
538
- this.timerId = null;
539
- if (!this.running || this.disposed) return;
540
- void this.performRefresh();
541
- }, waitMs);
542
- }
543
- }
544
- }
545
- }
546
- computeRefreshDelay() {
547
- if (this.currentToken.expiresIn == null) return null;
548
- const lifetimeMs = this.currentToken.expiresIn * 1e3;
549
- const refreshAtMs = this.currentToken.obtainedAt + lifetimeMs * this.threshold;
550
- const now = Date.now();
551
- const delay = refreshAtMs - now;
552
- return delay;
553
- }
554
- isTokenExpired() {
555
- if (this.currentToken.expiresIn == null) return false;
556
- const expiresAt = this.currentToken.obtainedAt + this.currentToken.expiresIn * 1e3;
557
- return Date.now() >= expiresAt;
558
- }
559
- clearTimer() {
560
- if (this.timerId !== null) {
561
- clearTimeout(this.timerId);
562
- this.timerId = null;
563
- }
564
- }
565
- emit(event, ...args) {
566
- for (const listener of this.listeners[event]) {
567
- try {
568
- listener(...args);
569
- } catch {
570
- }
571
- }
572
- }
573
- };
574
- }
575
- });
576
-
577
- // src/auth/index.ts
578
- var auth_exports = {};
579
- __export(auth_exports, {
580
- AccessDeniedError: () => AccessDeniedError,
581
- AuthError: () => AuthError,
582
- ClaudeAuth: () => ClaudeAuth,
583
- CopilotAuth: () => CopilotAuth,
584
- DeviceCodeExpiredError: () => DeviceCodeExpiredError,
585
- TokenExchangeError: () => TokenExchangeError,
586
- TokenRefreshManager: () => TokenRefreshManager
587
- });
588
- var init_auth = __esm({
589
- "src/auth/index.ts"() {
590
- init_types();
591
- init_copilot_auth();
592
- init_claude_auth();
593
- init_refresh_manager();
594
- }
595
- });
5
+ // src/chat/react/ChatProvider.ts
596
6
  var ChatRuntimeContext = react.createContext(null);
597
7
  function ChatProvider({ runtime, children }) {
598
8
  return react.createElement(ChatRuntimeContext.Provider, { value: runtime }, children);
@@ -605,10 +15,15 @@ function useChatRuntime() {
605
15
  return runtime;
606
16
  }
607
17
 
608
- // src/chat/core.ts
18
+ // src/chat/types.ts
609
19
  function createChatId() {
610
20
  return crypto.randomUUID();
611
21
  }
22
+ function isObservableSession(session) {
23
+ return "subscribe" in session && typeof session.subscribe === "function" && "getSnapshot" in session && typeof session.getSnapshot === "function";
24
+ }
25
+
26
+ // src/chat/bridge.ts
612
27
  function chatEventToAgentEvent(event) {
613
28
  switch (event.type) {
614
29
  case "message:delta":
@@ -634,7 +49,7 @@ function chatEventToAgentEvent(event) {
634
49
  result: event.result
635
50
  };
636
51
  case "error":
637
- return { type: "error", error: event.error, recoverable: event.recoverable };
52
+ return { type: "error", error: event.error, recoverable: event.recoverable, code: event.code };
638
53
  default:
639
54
  return null;
640
55
  }
@@ -787,9 +202,26 @@ function useChat(options = {}) {
787
202
  const [isGenerating, setIsGenerating] = react.useState(false);
788
203
  const [status, setStatus] = react.useState("idle");
789
204
  const [error, setError] = react.useState(null);
205
+ const [usage, setUsage] = react.useState(null);
790
206
  const generatingRef = react.useRef(false);
207
+ const lastUserMessageRef = react.useRef(null);
791
208
  const onErrorRef = react.useRef(options.onError);
792
209
  onErrorRef.current = options.onError;
210
+ const dismissTimerRef = react.useRef(null);
211
+ const autoDismissMs = options.autoDismissMs ?? 0;
212
+ react.useEffect(() => {
213
+ if (!error || autoDismissMs <= 0) return;
214
+ dismissTimerRef.current = setTimeout(() => {
215
+ setError(null);
216
+ dismissTimerRef.current = null;
217
+ }, autoDismissMs);
218
+ return () => {
219
+ if (dismissTimerRef.current) {
220
+ clearTimeout(dismissTimerRef.current);
221
+ dismissTimerRef.current = null;
222
+ }
223
+ };
224
+ }, [error, autoDismissMs]);
793
225
  react.useEffect(() => {
794
226
  if (!sessionId) {
795
227
  setMessages([]);
@@ -801,6 +233,15 @@ function useChat(options = {}) {
801
233
  }
802
234
  });
803
235
  }, [sessionId, runtime]);
236
+ react.useEffect(() => {
237
+ return runtime.onSessionChange(() => {
238
+ const activeId = runtime.activeSessionId;
239
+ if (activeId && activeId !== sessionId) {
240
+ setSessionId(activeId);
241
+ setError(null);
242
+ }
243
+ });
244
+ }, [runtime, sessionId]);
804
245
  const ensureSession = react.useCallback(async () => {
805
246
  if (sessionId) return sessionId;
806
247
  const session = await runtime.createSession({
@@ -812,6 +253,7 @@ function useChat(options = {}) {
812
253
  const sendMessage = react.useCallback(
813
254
  async (content) => {
814
255
  if (generatingRef.current) return;
256
+ lastUserMessageRef.current = content;
815
257
  setError(null);
816
258
  generatingRef.current = true;
817
259
  setIsGenerating(true);
@@ -831,8 +273,19 @@ function useChat(options = {}) {
831
273
  };
832
274
  setMessages((prev) => [...prev, userMsg]);
833
275
  for await (const event of runtime.send(sid, content)) {
276
+ if (event.type === "usage") {
277
+ setUsage({
278
+ promptTokens: event.promptTokens,
279
+ completionTokens: event.completionTokens,
280
+ totalTokens: event.promptTokens + event.completionTokens,
281
+ model: event.model
282
+ });
283
+ }
834
284
  const agentEvent = chatEventToAgentEvent(event);
835
285
  if (agentEvent) {
286
+ if (agentEvent.type === "error" && !agentEvent.recoverable) {
287
+ throw new Error(agentEvent.error || "Stream error");
288
+ }
836
289
  accumulator.apply(agentEvent);
837
290
  hasEvents = true;
838
291
  const snapshot = accumulator.snapshot();
@@ -887,6 +340,10 @@ function useChat(options = {}) {
887
340
  const clearError = react.useCallback(() => {
888
341
  setError(null);
889
342
  }, []);
343
+ const retryLastMessage = react.useCallback(async () => {
344
+ if (!lastUserMessageRef.current || generatingRef.current) return;
345
+ await sendMessage(lastUserMessageRef.current);
346
+ }, [sendMessage]);
890
347
  const newSession = react.useCallback(async () => {
891
348
  const session = await runtime.createSession({
892
349
  config: { model: "", backend: "" }
@@ -894,6 +351,7 @@ function useChat(options = {}) {
894
351
  setSessionId(session.id);
895
352
  setMessages([]);
896
353
  setError(null);
354
+ lastUserMessageRef.current = null;
897
355
  return session.id;
898
356
  }, [runtime]);
899
357
  return {
@@ -905,7 +363,9 @@ function useChat(options = {}) {
905
363
  status,
906
364
  error,
907
365
  clearError,
908
- newSession
366
+ retryLastMessage,
367
+ newSession,
368
+ usage
909
369
  };
910
370
  }
911
371
  var EMPTY_MESSAGES = [];
@@ -953,7 +413,7 @@ function useMessages(options) {
953
413
  messagesRef.current = session.messages;
954
414
  isLoadedRef.current = true;
955
415
  emitChange();
956
- if (session.subscribe && session.getSnapshot) {
416
+ if (isObservableSession(session)) {
957
417
  unsubscribe = session.subscribe(() => {
958
418
  const snapshot = session.getSnapshot();
959
419
  messagesRef.current = snapshot.messages;
@@ -1025,124 +485,6 @@ function useSessions() {
1025
485
  }, [fetchSessions]);
1026
486
  return { sessions, loading, error, refresh };
1027
487
  }
1028
- function defaultRenderText(part, index) {
1029
- return react.createElement("span", { key: index, "data-part": "text" }, part.text);
1030
- }
1031
- function defaultRenderReasoning(part, index) {
1032
- return react.createElement("span", { key: index, "data-part": "reasoning" }, part.text);
1033
- }
1034
- function defaultRenderToolCall(part, index) {
1035
- return react.createElement("span", { key: index, "data-part": "tool_call", "data-tool-name": part.name }, part.name);
1036
- }
1037
- function defaultRenderSource(part, index) {
1038
- return react.createElement("a", { key: index, href: part.url, "data-part": "source" }, part.title ?? part.url);
1039
- }
1040
- function defaultRenderFile(part, index) {
1041
- return react.createElement("span", { key: index, "data-part": "file" }, part.name);
1042
- }
1043
- function renderPart(props, part, index) {
1044
- switch (part.type) {
1045
- case "text":
1046
- return (props.renderText ?? defaultRenderText)(part, index);
1047
- case "reasoning":
1048
- return (props.renderReasoning ?? defaultRenderReasoning)(part, index);
1049
- case "tool_call":
1050
- return (props.renderToolCall ?? defaultRenderToolCall)(part, index);
1051
- case "source":
1052
- return (props.renderSource ?? defaultRenderSource)(part, index);
1053
- case "file":
1054
- return (props.renderFile ?? defaultRenderFile)(part, index);
1055
- }
1056
- }
1057
- function Message(props) {
1058
- const { message } = props;
1059
- const children = message.parts.map((part, i) => renderPart(props, part, i));
1060
- return react.createElement(
1061
- "div",
1062
- {
1063
- "data-role": message.role,
1064
- "data-status": message.status
1065
- },
1066
- ...children
1067
- );
1068
- }
1069
- function ThinkingBlock({ text, isStreaming, defaultOpen }) {
1070
- const attrs = {
1071
- "data-thinking": "true"
1072
- };
1073
- if (isStreaming) {
1074
- attrs["data-streaming"] = "true";
1075
- }
1076
- if (defaultOpen) {
1077
- attrs.open = true;
1078
- }
1079
- return react.createElement(
1080
- "details",
1081
- attrs,
1082
- react.createElement("summary", null, isStreaming ? "Thinking..." : "Reasoning"),
1083
- react.createElement("div", null, text)
1084
- );
1085
- }
1086
- function ToolCallView({ part, onApprove, onDeny, renderArgs, renderResult }) {
1087
- const children = [
1088
- react.createElement("span", { key: "name", "data-tool-label": "name" }, part.name),
1089
- react.createElement("span", { key: "status", "data-tool-label": "status" }, part.status)
1090
- ];
1091
- if (part.args !== void 0) {
1092
- children.push(
1093
- renderArgs ? renderArgs(part.args) : react.createElement("pre", { key: "args", "data-tool-label": "args" }, JSON.stringify(part.args, null, 2))
1094
- );
1095
- }
1096
- if (part.result !== void 0) {
1097
- children.push(
1098
- renderResult ? renderResult(part.result) : react.createElement("pre", { key: "result", "data-tool-label": "result" }, JSON.stringify(part.result, null, 2))
1099
- );
1100
- }
1101
- if (part.error) {
1102
- children.push(
1103
- react.createElement("span", { key: "error", "data-tool-label": "error", role: "alert" }, part.error)
1104
- );
1105
- }
1106
- if (part.status === "requires_approval") {
1107
- children.push(
1108
- react.createElement("button", { key: "approve", onClick: onApprove, "data-action": "approve" }, "Approve"),
1109
- react.createElement("button", { key: "deny", onClick: onDeny, "data-action": "deny" }, "Deny")
1110
- );
1111
- }
1112
- return react.createElement(
1113
- "div",
1114
- {
1115
- "data-tool-status": part.status,
1116
- "data-tool-name": part.name
1117
- },
1118
- ...children
1119
- );
1120
- }
1121
- function useToolApproval(messages, onApprove, onDeny) {
1122
- const pendingRequests = react.useMemo(() => {
1123
- const requests = [];
1124
- for (const msg of messages) {
1125
- for (const part of msg.parts) {
1126
- if (part.type === "tool_call" && part.status === "requires_approval") {
1127
- requests.push({
1128
- toolCallId: part.toolCallId,
1129
- toolName: part.name,
1130
- toolArgs: part.args ?? {},
1131
- messageId: msg.id
1132
- });
1133
- }
1134
- }
1135
- }
1136
- return requests;
1137
- }, [messages]);
1138
- const approve = react.useCallback((toolCallId) => {
1139
- onApprove?.(toolCallId);
1140
- }, [onApprove]);
1141
- const deny = react.useCallback((toolCallId) => {
1142
- onDeny?.(toolCallId);
1143
- }, [onDeny]);
1144
- return { pendingRequests, approve, deny };
1145
- }
1146
488
  function parseBlocks(text) {
1147
489
  const tokens = [];
1148
490
  const lines = text.split("\n");
@@ -1291,68 +633,370 @@ function MarkdownRenderer(props) {
1291
633
  const children = tokens.map((token, i) => renderBlock(token, i, props));
1292
634
  return react.createElement("div", { "data-md-root": true }, ...children);
1293
635
  }
1294
- var ThreadSlotsContext = react.createContext(null);
1295
- function ThreadProvider({
1296
- children,
1297
- renderMessage,
1298
- renderToolCall,
1299
- renderThinkingBlock
1300
- }) {
1301
- const value = { renderMessage, renderToolCall, renderThinkingBlock };
1302
- return react.createElement(ThreadSlotsContext.Provider, { value }, children);
1303
- }
1304
- function useThreadSlots() {
1305
- const ctx = react.useContext(ThreadSlotsContext);
1306
- if (!ctx) {
1307
- throw new Error("useThreadSlots must be used within a ThreadProvider");
636
+ function ThinkingBlock({ text, isStreaming, defaultOpen }) {
637
+ const attrs = {
638
+ "data-thinking": "true"
639
+ };
640
+ if (isStreaming) {
641
+ attrs["data-streaming"] = "true";
1308
642
  }
1309
- return ctx;
643
+ if (defaultOpen) {
644
+ attrs.open = true;
645
+ }
646
+ return react.createElement(
647
+ "details",
648
+ attrs,
649
+ react.createElement("summary", null, isStreaming ? "Thinking..." : "Reasoning"),
650
+ react.createElement("div", null, text)
651
+ );
652
+ }
653
+ function ToolCallView({ part, onApprove, onDeny, renderArgs, renderResult }) {
654
+ const children = [];
655
+ children.push(
656
+ react.createElement(
657
+ "div",
658
+ { key: "header", "data-tool-header": "true" },
659
+ react.createElement("span", { "data-tool-label": "name" }, part.name),
660
+ react.createElement("span", { "data-tool-label": "status" }, part.status)
661
+ )
662
+ );
663
+ if (part.args !== void 0) {
664
+ children.push(
665
+ renderArgs ? renderArgs(part.args) : react.createElement(
666
+ "details",
667
+ { key: "args", "data-tool-details": "args" },
668
+ react.createElement("summary", null, "Arguments"),
669
+ react.createElement("pre", { "data-tool-label": "args" }, JSON.stringify(part.args, null, 2))
670
+ )
671
+ );
672
+ }
673
+ if (part.result !== void 0) {
674
+ children.push(
675
+ renderResult ? renderResult(part.result) : react.createElement(
676
+ "details",
677
+ { key: "result", "data-tool-details": "result", open: true },
678
+ react.createElement("summary", null, "Result"),
679
+ react.createElement("pre", { "data-tool-label": "result" }, JSON.stringify(part.result, null, 2))
680
+ )
681
+ );
682
+ }
683
+ if (part.error) {
684
+ children.push(
685
+ react.createElement("span", { key: "error", "data-tool-label": "error", role: "alert" }, part.error)
686
+ );
687
+ }
688
+ if (part.status === "requires_approval") {
689
+ children.push(
690
+ react.createElement(
691
+ "div",
692
+ { key: "actions", "data-tool-actions": "true" },
693
+ react.createElement("button", { key: "approve", onClick: onApprove, "data-action": "approve" }, "Approve"),
694
+ react.createElement("button", { key: "deny", onClick: onDeny, "data-action": "deny" }, "Deny")
695
+ )
696
+ );
697
+ }
698
+ return react.createElement(
699
+ "div",
700
+ {
701
+ "data-tool-status": part.status,
702
+ "data-tool-name": part.name
703
+ },
704
+ ...children
705
+ );
706
+ }
707
+
708
+ // src/chat/react/Message.ts
709
+ function defaultRenderText(part, index) {
710
+ return react.createElement(
711
+ "div",
712
+ { key: index, "data-part": "text" },
713
+ react.createElement(MarkdownRenderer, { content: part.text })
714
+ );
715
+ }
716
+ function defaultRenderReasoning(part, index) {
717
+ return react.createElement(ThinkingBlock, {
718
+ key: index,
719
+ text: part.text,
720
+ isStreaming: part.status === "streaming"
721
+ });
722
+ }
723
+ function defaultRenderToolCall(part, index) {
724
+ return react.createElement(
725
+ "div",
726
+ { key: index, "data-part": "tool_call" },
727
+ react.createElement(ToolCallView, { part })
728
+ );
729
+ }
730
+ function defaultRenderSource(part, index) {
731
+ return react.createElement("a", { key: index, href: part.url, "data-part": "source" }, part.title ?? part.url);
732
+ }
733
+ function defaultRenderFile(part, index) {
734
+ return react.createElement("span", { key: index, "data-part": "file" }, part.name);
735
+ }
736
+ function renderPart(props, part, index) {
737
+ switch (part.type) {
738
+ case "text":
739
+ return (props.renderText ?? defaultRenderText)(part, index);
740
+ case "reasoning":
741
+ return (props.renderReasoning ?? defaultRenderReasoning)(part, index);
742
+ case "tool_call":
743
+ return (props.renderToolCall ?? defaultRenderToolCall)(part, index);
744
+ case "source":
745
+ return (props.renderSource ?? defaultRenderSource)(part, index);
746
+ case "file":
747
+ return (props.renderFile ?? defaultRenderFile)(part, index);
748
+ }
749
+ }
750
+ function Message(props) {
751
+ const { message } = props;
752
+ const children = message.parts.map((part, i) => renderPart(props, part, i));
753
+ return react.createElement(
754
+ "div",
755
+ {
756
+ "data-role": message.role,
757
+ "data-status": message.status
758
+ },
759
+ ...children
760
+ );
761
+ }
762
+ function useToolApproval(messages, onApprove, onDeny) {
763
+ const pendingRequests = react.useMemo(() => {
764
+ const requests = [];
765
+ for (const msg of messages) {
766
+ for (const part of msg.parts) {
767
+ if (part.type === "tool_call" && part.status === "requires_approval") {
768
+ requests.push({
769
+ toolCallId: part.toolCallId,
770
+ toolName: part.name,
771
+ toolArgs: part.args ?? {},
772
+ messageId: msg.id
773
+ });
774
+ }
775
+ }
776
+ }
777
+ return requests;
778
+ }, [messages]);
779
+ const approve = react.useCallback((toolCallId) => {
780
+ onApprove?.(toolCallId);
781
+ }, [onApprove]);
782
+ const deny = react.useCallback((toolCallId) => {
783
+ onDeny?.(toolCallId);
784
+ }, [onDeny]);
785
+ return { pendingRequests, approve, deny };
786
+ }
787
+ var ThreadSlotsContext = react.createContext(null);
788
+ function ThreadProvider({
789
+ children,
790
+ renderMessage,
791
+ renderToolCall,
792
+ renderThinkingBlock
793
+ }) {
794
+ const value = { renderMessage, renderToolCall, renderThinkingBlock };
795
+ return react.createElement(ThreadSlotsContext.Provider, { value }, children);
796
+ }
797
+ function useThreadSlots() {
798
+ const ctx = react.useContext(ThreadSlotsContext);
799
+ if (!ctx) {
800
+ throw new Error("useThreadSlots must be used within a ThreadProvider");
801
+ }
802
+ return ctx;
1310
803
  }
1311
804
  function useOptionalThreadSlots() {
1312
805
  return react.useContext(ThreadSlotsContext);
1313
806
  }
807
+ function useVirtualMessages(items, options = {}) {
808
+ const { estimatedItemHeight = 80, overscan = 3 } = options;
809
+ const [scrollTop, setScrollTop] = react.useState(0);
810
+ const [containerHeight, setContainerHeight] = react.useState(0);
811
+ const containerElRef = react.useRef(null);
812
+ const containerRef = react.useCallback((el) => {
813
+ containerElRef.current = el;
814
+ if (el) {
815
+ setContainerHeight(el.clientHeight);
816
+ }
817
+ }, []);
818
+ react.useEffect(() => {
819
+ const el = containerElRef.current;
820
+ if (!el || typeof ResizeObserver === "undefined") return;
821
+ const ro = new ResizeObserver(() => {
822
+ setContainerHeight(el.clientHeight);
823
+ });
824
+ ro.observe(el);
825
+ return () => ro.disconnect();
826
+ }, []);
827
+ const onScroll = react.useCallback(
828
+ (event) => {
829
+ setScrollTop(event.currentTarget.scrollTop);
830
+ setContainerHeight(event.currentTarget.clientHeight);
831
+ },
832
+ []
833
+ );
834
+ const result = react.useMemo(() => {
835
+ const totalCount = items.length;
836
+ const totalHeight = totalCount * estimatedItemHeight;
837
+ if (totalCount === 0 || containerHeight === 0) {
838
+ return {
839
+ visibleItems: items.slice(),
840
+ startIndex: 0,
841
+ endIndex: totalCount,
842
+ topSpacerHeight: 0,
843
+ bottomSpacerHeight: 0,
844
+ totalHeight
845
+ };
846
+ }
847
+ const rawStart = Math.floor(scrollTop / estimatedItemHeight) - overscan;
848
+ const startIndex = Math.max(0, rawStart);
849
+ const visibleCount = Math.ceil(containerHeight / estimatedItemHeight);
850
+ const rawEnd = Math.floor(scrollTop / estimatedItemHeight) + visibleCount + overscan;
851
+ const endIndex = Math.min(totalCount, rawEnd);
852
+ return {
853
+ visibleItems: items.slice(startIndex, endIndex),
854
+ startIndex,
855
+ endIndex,
856
+ topSpacerHeight: startIndex * estimatedItemHeight,
857
+ bottomSpacerHeight: (totalCount - endIndex) * estimatedItemHeight,
858
+ totalHeight
859
+ };
860
+ }, [items, scrollTop, containerHeight, estimatedItemHeight, overscan]);
861
+ return {
862
+ ...result,
863
+ onScroll,
864
+ containerRef
865
+ };
866
+ }
1314
867
 
1315
868
  // src/chat/react/Thread.ts
1316
869
  function Thread({
1317
870
  messages,
1318
871
  isGenerating,
1319
872
  autoScroll = true,
1320
- className
873
+ className,
874
+ virtualize
1321
875
  }) {
1322
876
  const sentinelRef = react.useRef(null);
1323
877
  const containerRef = react.useRef(null);
1324
878
  const [userScrolledUp, setUserScrolledUp] = react.useState(false);
1325
- const handleScroll = react.useCallback(() => {
1326
- const el = containerRef.current;
1327
- if (!el) return;
1328
- const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 1;
1329
- setUserScrolledUp(!atBottom);
879
+ const isScrollingProgrammatically = react.useRef(false);
880
+ const isVirtualized = virtualize != null && virtualize !== false;
881
+ const virtualizeOpts = virtualize === true ? {} : !isVirtualized ? false : virtualize;
882
+ const virtual = useVirtualMessages(
883
+ messages,
884
+ virtualizeOpts || void 0
885
+ );
886
+ const handleScroll = react.useCallback(
887
+ (e) => {
888
+ if (isVirtualized) {
889
+ virtual.onScroll(e);
890
+ }
891
+ if (isScrollingProgrammatically.current) return;
892
+ const el = containerRef.current;
893
+ if (!el) return;
894
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 1;
895
+ setUserScrolledUp(!atBottom);
896
+ },
897
+ [isVirtualized, virtual.onScroll]
898
+ );
899
+ const scrollToBottom = react.useCallback(() => {
900
+ isScrollingProgrammatically.current = true;
901
+ sentinelRef.current?.scrollIntoView({ behavior: "smooth" });
902
+ setUserScrolledUp(false);
903
+ setTimeout(() => {
904
+ isScrollingProgrammatically.current = false;
905
+ }, 500);
1330
906
  }, []);
1331
907
  react.useEffect(() => {
1332
908
  if (!autoScroll || userScrolledUp) return;
1333
909
  sentinelRef.current?.scrollIntoView({ behavior: "smooth" });
1334
910
  }, [messages, autoScroll, userScrolledUp]);
1335
911
  const slots = useOptionalThreadSlots();
912
+ const mergedRef = react.useCallback(
913
+ (el) => {
914
+ containerRef.current = el;
915
+ if (isVirtualized) {
916
+ virtual.containerRef(el);
917
+ }
918
+ },
919
+ [isVirtualized, virtual.containerRef]
920
+ );
1336
921
  const attrs = { "data-thread": "true", className };
1337
922
  if (isGenerating) {
1338
923
  attrs["data-thread-loading"] = "true";
1339
924
  }
1340
- attrs.ref = containerRef;
925
+ if (virtualizeOpts) {
926
+ attrs["data-thread-virtualized"] = "true";
927
+ }
928
+ attrs.ref = mergedRef;
1341
929
  attrs.onScroll = handleScroll;
1342
- const children = messages.map((msg, i) => {
1343
- const content = slots?.renderMessage ? slots.renderMessage(msg, i) : react.createElement(Message, {
930
+ const children = [];
931
+ if (messages.length === 0 && !isGenerating) {
932
+ children.push(
933
+ react.createElement(
934
+ "div",
935
+ { key: "__empty", "data-thread-empty": "true" },
936
+ "Start a conversation"
937
+ )
938
+ );
939
+ }
940
+ const renderMessages = virtualizeOpts ? virtual.visibleItems : messages;
941
+ const startOffset = virtualizeOpts ? virtual.startIndex : 0;
942
+ if (virtualizeOpts && virtual.topSpacerHeight > 0) {
943
+ children.push(
944
+ react.createElement("div", {
945
+ key: "__virtual-top",
946
+ "data-virtual-spacer": "top",
947
+ style: { height: virtual.topSpacerHeight }
948
+ })
949
+ );
950
+ }
951
+ for (let i = 0; i < renderMessages.length; i++) {
952
+ const msg = renderMessages[i];
953
+ const originalIndex = startOffset + i;
954
+ const content = slots?.renderMessage ? slots.renderMessage(msg, originalIndex) : react.createElement(Message, {
1344
955
  key: msg.id,
1345
956
  message: msg,
1346
957
  renderToolCall: slots?.renderToolCall,
1347
958
  renderReasoning: slots?.renderThinkingBlock ? (part, idx) => slots.renderThinkingBlock(part, idx) : void 0
1348
959
  });
1349
- return react.createElement(
1350
- "div",
1351
- { key: msg.id, "data-thread-message": "true", "data-role": msg.role },
1352
- content
960
+ children.push(
961
+ react.createElement(
962
+ "div",
963
+ { key: msg.id, "data-thread-message": "true", "data-role": msg.role },
964
+ content
965
+ )
1353
966
  );
1354
- });
967
+ }
968
+ if (virtualizeOpts && virtual.bottomSpacerHeight > 0) {
969
+ children.push(
970
+ react.createElement("div", {
971
+ key: "__virtual-bottom",
972
+ "data-virtual-spacer": "bottom",
973
+ style: { height: virtual.bottomSpacerHeight }
974
+ })
975
+ );
976
+ }
977
+ if (isGenerating) {
978
+ children.push(
979
+ react.createElement(
980
+ "div",
981
+ { key: "__loading", "data-thread-loading-indicator": "true" },
982
+ react.createElement("span", null),
983
+ react.createElement("span", null),
984
+ react.createElement("span", null)
985
+ )
986
+ );
987
+ }
1355
988
  children.push(react.createElement("div", { key: "__sentinel", ref: sentinelRef }));
989
+ if (userScrolledUp) {
990
+ children.push(
991
+ react.createElement("button", {
992
+ key: "__scroll-to-bottom",
993
+ "data-action": "scroll-to-bottom",
994
+ type: "button",
995
+ onClick: scrollToBottom,
996
+ "aria-label": "Scroll to bottom"
997
+ })
998
+ );
999
+ }
1356
1000
  return react.createElement("div", attrs, ...children);
1357
1001
  }
1358
1002
  function Composer({
@@ -1399,21 +1043,20 @@ function Composer({
1399
1043
  []
1400
1044
  );
1401
1045
  const children = [
1402
- react.createElement("textarea", {
1403
- key: "textarea",
1404
- ref: textareaRef,
1405
- value,
1406
- onChange: handleChange,
1407
- onKeyDown: handleKeyDown,
1408
- placeholder,
1409
- disabled: disabled || false,
1410
- "aria-label": "Message input",
1411
- rows: 1
1412
- })
1413
- ];
1414
- if (isGenerating) {
1415
- children.push(
1416
- react.createElement(
1046
+ react.createElement(
1047
+ "div",
1048
+ { key: "input-wrapper", "data-input": "" },
1049
+ react.createElement("textarea", {
1050
+ ref: textareaRef,
1051
+ value,
1052
+ onChange: handleChange,
1053
+ onKeyDown: handleKeyDown,
1054
+ placeholder,
1055
+ disabled: disabled || false,
1056
+ "aria-label": "Message input",
1057
+ rows: 1
1058
+ }),
1059
+ isGenerating ? react.createElement(
1417
1060
  "button",
1418
1061
  {
1419
1062
  key: "stop",
@@ -1422,11 +1065,7 @@ function Composer({
1422
1065
  type: "button"
1423
1066
  },
1424
1067
  "Stop"
1425
- )
1426
- );
1427
- } else {
1428
- children.push(
1429
- react.createElement(
1068
+ ) : react.createElement(
1430
1069
  "button",
1431
1070
  {
1432
1071
  key: "send",
@@ -1437,8 +1076,8 @@ function Composer({
1437
1076
  },
1438
1077
  "Send"
1439
1078
  )
1440
- );
1441
- }
1079
+ )
1080
+ ];
1442
1081
  return react.createElement(
1443
1082
  "div",
1444
1083
  { "data-composer": "true", className },
@@ -1448,6 +1087,20 @@ function Composer({
1448
1087
  function isFullSession(item) {
1449
1088
  return "messages" in item && Array.isArray(item.messages);
1450
1089
  }
1090
+ function formatRelativeTime(date) {
1091
+ const now = Date.now();
1092
+ const diff = now - date.getTime();
1093
+ const seconds = Math.floor(diff / 1e3);
1094
+ if (seconds < 60) return "now";
1095
+ const minutes = Math.floor(seconds / 60);
1096
+ if (minutes < 60) return `${minutes}m`;
1097
+ const hours = Math.floor(minutes / 60);
1098
+ if (hours < 24) return `${hours}h`;
1099
+ const days = Math.floor(hours / 24);
1100
+ if (days < 30) return `${days}d`;
1101
+ const months = Math.floor(days / 30);
1102
+ return `${months}mo`;
1103
+ }
1451
1104
  function normalizeSession(item) {
1452
1105
  if (isFullSession(item)) {
1453
1106
  return {
@@ -1485,32 +1138,38 @@ function ThreadList({
1485
1138
  return normalized.filter((s) => (s.title ?? "").toLowerCase().includes(q));
1486
1139
  }, [normalized, searchQuery]);
1487
1140
  const children = [];
1488
- children.push(
1489
- react.createElement("input", {
1490
- key: "search",
1491
- "data-thread-list-search": "true",
1492
- value: searchQuery ?? "",
1493
- onChange: handleSearchChange,
1494
- placeholder: "Search sessions..."
1495
- })
1496
- );
1497
1141
  children.push(
1498
1142
  react.createElement(
1499
- "button",
1500
- {
1501
- key: "create",
1502
- "data-action": "create-session",
1503
- onClick: onCreate,
1504
- type: "button"
1505
- },
1506
- "New"
1143
+ "div",
1144
+ { key: "header", "data-thread-list-header": "true" },
1145
+ react.createElement("input", {
1146
+ "data-thread-list-search": "true",
1147
+ value: searchQuery ?? "",
1148
+ onChange: handleSearchChange,
1149
+ placeholder: "Search..."
1150
+ }),
1151
+ react.createElement(
1152
+ "button",
1153
+ {
1154
+ "data-action": "create-session",
1155
+ onClick: onCreate,
1156
+ type: "button"
1157
+ },
1158
+ "+"
1159
+ )
1507
1160
  )
1508
1161
  );
1509
1162
  const items = filtered.map((session) => {
1510
1163
  const isActive = session.id === activeSessionId;
1511
1164
  const itemChildren = [
1512
- react.createElement("span", { key: "title" }, session.title ?? "Untitled")
1165
+ react.createElement("span", { key: "title", "data-session-title": "true" }, session.title ?? "Untitled")
1513
1166
  ];
1167
+ if (session.updatedAt) {
1168
+ const timeStr = formatRelativeTime(new Date(session.updatedAt));
1169
+ itemChildren.push(
1170
+ react.createElement("span", { key: "time", "data-session-time": "true" }, timeStr)
1171
+ );
1172
+ }
1514
1173
  if (onDelete) {
1515
1174
  itemChildren.push(
1516
1175
  react.createElement(
@@ -1524,7 +1183,7 @@ function ThreadList({
1524
1183
  },
1525
1184
  type: "button"
1526
1185
  },
1527
- "Delete"
1186
+ "\xD7"
1528
1187
  )
1529
1188
  );
1530
1189
  }
@@ -1534,6 +1193,7 @@ function ThreadList({
1534
1193
  key: session.id,
1535
1194
  "data-session-item": "true",
1536
1195
  "data-session-active": isActive ? "true" : "false",
1196
+ "data-session-status": session.status ?? "active",
1537
1197
  onClick: () => onSelect(session.id)
1538
1198
  },
1539
1199
  ...itemChildren
@@ -1584,13 +1244,19 @@ function useSSE(url, options = {}) {
1584
1244
  setStatus("connecting");
1585
1245
  (async () => {
1586
1246
  try {
1587
- const response = await fetch(url, {
1247
+ const fetchInit = {
1248
+ method: optionsRef.current.method ?? "GET",
1588
1249
  headers: {
1589
1250
  Accept: "text/event-stream",
1590
1251
  ...optionsRef.current.headers
1591
1252
  },
1592
1253
  signal: controller.signal
1593
- });
1254
+ };
1255
+ if (fetchInit.method === "POST" && optionsRef.current.body !== void 0) {
1256
+ fetchInit.headers["Content-Type"] = "application/json";
1257
+ fetchInit.body = JSON.stringify(optionsRef.current.body);
1258
+ }
1259
+ const response = await fetch(url, fetchInit);
1594
1260
  if (!response.ok) {
1595
1261
  throw new Error(`SSE request failed: ${response.status}`);
1596
1262
  }
@@ -1679,7 +1345,8 @@ function useModels() {
1679
1345
  const mapped = result.map((m) => ({
1680
1346
  id: m.id,
1681
1347
  name: m.name ?? m.id,
1682
- tier: m.provider
1348
+ tier: m.provider,
1349
+ provider: m.provider
1683
1350
  }));
1684
1351
  setModels(mapped);
1685
1352
  } catch (err) {
@@ -1712,11 +1379,13 @@ function ModelSelector({
1712
1379
  selectedModel,
1713
1380
  onSelect,
1714
1381
  placeholder = "Select model",
1715
- className
1382
+ className,
1383
+ allowFreeText = true
1716
1384
  }) {
1717
1385
  const [open, setOpen] = react.useState(false);
1718
1386
  const [search, setSearch] = react.useState("");
1719
1387
  const [highlightIndex, setHighlightIndex] = react.useState(0);
1388
+ const [freeText, setFreeText] = react.useState(selectedModel ?? "");
1720
1389
  const containerRef = react.useRef(null);
1721
1390
  const filtered = react.useMemo(() => {
1722
1391
  if (!search) return models;
@@ -1770,6 +1439,42 @@ function ModelSelector({
1770
1439
  },
1771
1440
  [filtered, highlightIndex, handleSelect]
1772
1441
  );
1442
+ if (models.length === 0 && allowFreeText) {
1443
+ return react.createElement(
1444
+ "div",
1445
+ {
1446
+ "data-model-selector": "true",
1447
+ "data-model-selector-freetext": "true",
1448
+ className,
1449
+ ref: containerRef
1450
+ },
1451
+ react.createElement("input", {
1452
+ "data-model-input": "true",
1453
+ value: freeText,
1454
+ placeholder: "Enter model name...",
1455
+ onChange: (e) => setFreeText(e.target.value),
1456
+ onKeyDown: (e) => {
1457
+ if (e.key === "Enter") {
1458
+ e.preventDefault();
1459
+ const val = freeText.trim();
1460
+ if (val) onSelect(val);
1461
+ }
1462
+ }
1463
+ }),
1464
+ react.createElement(
1465
+ "button",
1466
+ {
1467
+ type: "button",
1468
+ "data-action": "apply-model",
1469
+ onClick: () => {
1470
+ const val = freeText.trim();
1471
+ if (val) onSelect(val);
1472
+ }
1473
+ },
1474
+ "Apply"
1475
+ )
1476
+ );
1477
+ }
1773
1478
  const children = [];
1774
1479
  children.push(
1775
1480
  react.createElement(
@@ -1796,6 +1501,7 @@ function ModelSelector({
1796
1501
  autoFocus: true
1797
1502
  })
1798
1503
  );
1504
+ const hasMultipleProviders = new Set(filtered.map((m) => m.provider).filter(Boolean)).size > 1;
1799
1505
  filtered.forEach((model, idx) => {
1800
1506
  const isSelected = model.id === selectedModel;
1801
1507
  const isHighlighted = idx === highlightIndex;
@@ -1807,13 +1513,17 @@ function ModelSelector({
1807
1513
  if (model.tier) {
1808
1514
  attrs["data-tier"] = model.tier;
1809
1515
  }
1516
+ if (model.provider && hasMultipleProviders) {
1517
+ attrs["data-model-provider"] = model.provider;
1518
+ }
1810
1519
  if (isSelected) {
1811
1520
  attrs["data-model-selected"] = "true";
1812
1521
  }
1813
1522
  if (isHighlighted) {
1814
1523
  attrs["data-model-highlighted"] = "true";
1815
1524
  }
1816
- dropdownChildren.push(react.createElement("div", attrs, model.name));
1525
+ const label = model.provider && hasMultipleProviders ? `${model.name} (${model.provider})` : model.name;
1526
+ dropdownChildren.push(react.createElement("div", attrs, label));
1817
1527
  });
1818
1528
  children.push(
1819
1529
  react.createElement(
@@ -1833,124 +1543,203 @@ function ModelSelector({
1833
1543
  ...children
1834
1544
  );
1835
1545
  }
1836
- var _authLoaders = {
1837
- async loadCopilotAuth() {
1838
- const { CopilotAuth: CopilotAuth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
1839
- return CopilotAuth2;
1840
- },
1841
- async loadClaudeAuth() {
1842
- const { ClaudeAuth: ClaudeAuth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
1843
- return ClaudeAuth2;
1844
- }
1845
- };
1846
- function useAuth(options) {
1847
- const { backend, onAuthenticated } = options;
1546
+ function useCopilotAuth(options) {
1547
+ const { baseUrl, headers } = options;
1548
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
1848
1549
  const [status, setStatus] = react.useState("idle");
1849
1550
  const [error, setError] = react.useState(null);
1850
1551
  const [token, setToken] = react.useState(null);
1851
1552
  const [deviceCode, setDeviceCode] = react.useState(null);
1852
1553
  const [verificationUrl, setVerificationUrl] = react.useState(null);
1853
- const [authorizeUrl, setAuthorizeUrl] = react.useState(null);
1854
- const onAuthenticatedRef = react.useRef(onAuthenticated);
1855
- onAuthenticatedRef.current = onAuthenticated;
1856
- const completeAuthRef = react.useRef(null);
1857
- const startDeviceFlow = react.useCallback(async () => {
1858
- if (backend !== "copilot") return;
1554
+ const onAuthenticatedRef = react.useRef(options.onAuthenticated);
1555
+ onAuthenticatedRef.current = options.onAuthenticated;
1556
+ const onErrorRef = react.useRef(options.onError);
1557
+ onErrorRef.current = options.onError;
1558
+ const post = react.useCallback(
1559
+ async (path, body) => {
1560
+ const res = await fetchFn(`${baseUrl}${path}`, {
1561
+ method: "POST",
1562
+ headers: { "Content-Type": "application/json", ...headers },
1563
+ body: body ? JSON.stringify(body) : void 0
1564
+ });
1565
+ const data = await res.json();
1566
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1567
+ return data;
1568
+ },
1569
+ [baseUrl, fetchFn, headers]
1570
+ );
1571
+ const start = react.useCallback(async () => {
1859
1572
  setStatus("pending");
1860
1573
  setError(null);
1861
1574
  try {
1862
- const CopilotAuth2 = await _authLoaders.loadCopilotAuth();
1863
- const auth = new CopilotAuth2();
1864
- const result = await auth.startDeviceFlow();
1575
+ const result = await post("/auth/start", { provider: "copilot" });
1865
1576
  setDeviceCode(result.userCode);
1866
1577
  setVerificationUrl(result.verificationUrl);
1867
- const authToken = await result.waitForToken();
1578
+ await post("/auth/copilot/poll");
1579
+ const authToken = {
1580
+ accessToken: "server-managed",
1581
+ tokenType: "bearer",
1582
+ obtainedAt: Date.now()
1583
+ };
1868
1584
  setToken(authToken);
1869
1585
  setStatus("authenticated");
1870
1586
  onAuthenticatedRef.current?.(authToken);
1871
1587
  } catch (err) {
1872
- setError(err instanceof Error ? err : new Error(String(err)));
1588
+ const e = err instanceof Error ? err : new Error(String(err));
1589
+ setError(e);
1873
1590
  setStatus("error");
1591
+ onErrorRef.current?.(e);
1874
1592
  }
1875
- }, [backend]);
1876
- const startOAuthFlow = react.useCallback(async () => {
1877
- if (backend !== "claude") return;
1593
+ }, [post]);
1594
+ const reset = react.useCallback(() => {
1595
+ setStatus("idle");
1596
+ setError(null);
1597
+ setToken(null);
1598
+ setDeviceCode(null);
1599
+ setVerificationUrl(null);
1600
+ }, []);
1601
+ return { status, error, token, deviceCode, verificationUrl, start, reset };
1602
+ }
1603
+ function useClaudeAuth(options) {
1604
+ const { baseUrl, headers } = options;
1605
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
1606
+ const [status, setStatus] = react.useState("idle");
1607
+ const [error, setError] = react.useState(null);
1608
+ const [token, setToken] = react.useState(null);
1609
+ const [authorizeUrl, setAuthorizeUrl] = react.useState(null);
1610
+ const onAuthenticatedRef = react.useRef(options.onAuthenticated);
1611
+ onAuthenticatedRef.current = options.onAuthenticated;
1612
+ const onErrorRef = react.useRef(options.onError);
1613
+ onErrorRef.current = options.onError;
1614
+ const post = react.useCallback(
1615
+ async (path, body) => {
1616
+ const res = await fetchFn(`${baseUrl}${path}`, {
1617
+ method: "POST",
1618
+ headers: { "Content-Type": "application/json", ...headers },
1619
+ body: body ? JSON.stringify(body) : void 0
1620
+ });
1621
+ const data = await res.json();
1622
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1623
+ return data;
1624
+ },
1625
+ [baseUrl, fetchFn, headers]
1626
+ );
1627
+ const start = react.useCallback(async () => {
1878
1628
  setStatus("pending");
1879
1629
  setError(null);
1880
1630
  try {
1881
- const ClaudeAuth2 = await _authLoaders.loadClaudeAuth();
1882
- const auth = new ClaudeAuth2();
1883
- const result = auth.startOAuthFlow();
1631
+ const result = await post("/auth/start", { provider: "claude" });
1884
1632
  setAuthorizeUrl(result.authorizeUrl);
1885
- completeAuthRef.current = result.completeAuth;
1886
1633
  } catch (err) {
1887
- setError(err instanceof Error ? err : new Error(String(err)));
1634
+ const e = err instanceof Error ? err : new Error(String(err));
1635
+ setError(e);
1888
1636
  setStatus("error");
1637
+ onErrorRef.current?.(e);
1889
1638
  }
1890
- }, [backend]);
1891
- const completeOAuth = react.useCallback(async (codeOrUrl) => {
1892
- if (!completeAuthRef.current) return;
1639
+ }, [post]);
1640
+ const complete = react.useCallback(async (codeOrUrl) => {
1893
1641
  try {
1894
- const authToken = await completeAuthRef.current(codeOrUrl);
1642
+ await post("/auth/claude/complete", { code: codeOrUrl });
1643
+ const authToken = {
1644
+ accessToken: "server-managed",
1645
+ tokenType: "bearer",
1646
+ obtainedAt: Date.now()
1647
+ };
1895
1648
  setToken(authToken);
1896
1649
  setStatus("authenticated");
1897
1650
  onAuthenticatedRef.current?.(authToken);
1898
1651
  } catch (err) {
1899
- setError(err instanceof Error ? err : new Error(String(err)));
1652
+ const e = err instanceof Error ? err : new Error(String(err));
1653
+ setError(e);
1900
1654
  setStatus("error");
1655
+ onErrorRef.current?.(e);
1901
1656
  }
1657
+ }, [post]);
1658
+ const reset = react.useCallback(() => {
1659
+ setStatus("idle");
1660
+ setError(null);
1661
+ setToken(null);
1662
+ setAuthorizeUrl(null);
1902
1663
  }, []);
1903
- const submitApiKey = react.useCallback((key) => {
1904
- if (backend !== "api-key") return;
1664
+ return { status, error, token, authorizeUrl, start, complete, reset };
1665
+ }
1666
+ function useApiKeyAuth(options) {
1667
+ const { baseUrl, headers } = options;
1668
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
1669
+ const [status, setStatus] = react.useState("idle");
1670
+ const [error, setError] = react.useState(null);
1671
+ const [token, setToken] = react.useState(null);
1672
+ const onAuthenticatedRef = react.useRef(options.onAuthenticated);
1673
+ onAuthenticatedRef.current = options.onAuthenticated;
1674
+ const onErrorRef = react.useRef(options.onError);
1675
+ onErrorRef.current = options.onError;
1676
+ const submit = react.useCallback(async (key, apiBaseUrl) => {
1905
1677
  if (!key || !key.trim()) {
1906
- setError(new Error("API key cannot be empty"));
1678
+ const e = new Error("API key cannot be empty");
1679
+ setError(e);
1907
1680
  setStatus("error");
1681
+ onErrorRef.current?.(e);
1908
1682
  return;
1909
1683
  }
1910
- const authToken = {
1911
- accessToken: key.trim(),
1912
- tokenType: "bearer",
1913
- obtainedAt: Date.now()
1914
- };
1915
- setToken(authToken);
1916
- setStatus("authenticated");
1917
- onAuthenticatedRef.current?.(authToken);
1918
- }, [backend]);
1684
+ setStatus("pending");
1685
+ setError(null);
1686
+ try {
1687
+ const res = await fetchFn(`${baseUrl}/auth/vercel/complete`, {
1688
+ method: "POST",
1689
+ headers: { "Content-Type": "application/json", ...headers },
1690
+ body: JSON.stringify({
1691
+ apiKey: key.trim(),
1692
+ ...apiBaseUrl ? { baseUrl: apiBaseUrl } : {}
1693
+ })
1694
+ });
1695
+ const data = await res.json();
1696
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1697
+ const authToken = {
1698
+ accessToken: "server-managed",
1699
+ tokenType: "bearer",
1700
+ obtainedAt: Date.now()
1701
+ };
1702
+ setToken(authToken);
1703
+ setStatus("authenticated");
1704
+ onAuthenticatedRef.current?.(authToken);
1705
+ } catch (err) {
1706
+ const e = err instanceof Error ? err : new Error(String(err));
1707
+ setError(e);
1708
+ setStatus("error");
1709
+ onErrorRef.current?.(e);
1710
+ }
1711
+ }, [baseUrl, fetchFn, headers]);
1919
1712
  const reset = react.useCallback(() => {
1920
1713
  setStatus("idle");
1921
1714
  setError(null);
1922
1715
  setToken(null);
1923
- setDeviceCode(null);
1924
- setVerificationUrl(null);
1925
- setAuthorizeUrl(null);
1926
- completeAuthRef.current = null;
1927
1716
  }, []);
1928
- return {
1929
- status,
1930
- error,
1931
- startDeviceFlow,
1932
- deviceCode,
1933
- verificationUrl,
1934
- startOAuthFlow,
1935
- authorizeUrl,
1936
- completeOAuth,
1937
- submitApiKey,
1938
- token,
1939
- reset
1940
- };
1717
+ return { status, error, token, submit, reset };
1941
1718
  }
1719
+
1720
+ // src/chat/react/useRemoteAuth.ts
1942
1721
  function useRemoteAuth(options) {
1943
1722
  const { backend, baseUrl, onAuthenticated, headers } = options;
1944
1723
  const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
1945
1724
  const [status, setStatus] = react.useState("idle");
1946
1725
  const [error, setError] = react.useState(null);
1947
1726
  const [token, setToken] = react.useState(null);
1948
- const [deviceCode, setDeviceCode] = react.useState(null);
1949
- const [verificationUrl, setVerificationUrl] = react.useState(null);
1950
- const [authorizeUrl, setAuthorizeUrl] = react.useState(null);
1951
1727
  const [savedProviders, setSavedProviders] = react.useState([]);
1952
1728
  const onAuthenticatedRef = react.useRef(onAuthenticated);
1953
1729
  onAuthenticatedRef.current = onAuthenticated;
1730
+ const handleSuccess = react.useCallback((authToken) => {
1731
+ setToken(authToken);
1732
+ setStatus("authenticated");
1733
+ onAuthenticatedRef.current?.(authToken);
1734
+ }, []);
1735
+ const handleError = react.useCallback((err) => {
1736
+ setError(err);
1737
+ setStatus("error");
1738
+ }, []);
1739
+ const hookOpts = { baseUrl, headers, fetch: fetchFn, onAuthenticated: handleSuccess, onError: handleError };
1740
+ const copilot = useCopilotAuth(hookOpts);
1741
+ const claude = useClaudeAuth(hookOpts);
1742
+ const apiKey = useApiKeyAuth(hookOpts);
1954
1743
  const post = react.useCallback(
1955
1744
  async (path, body) => {
1956
1745
  const res = await fetchFn(`${baseUrl}${path}`, {
@@ -1980,82 +1769,26 @@ function useRemoteAuth(options) {
1980
1769
  if (backend !== "copilot") return;
1981
1770
  setStatus("pending");
1982
1771
  setError(null);
1983
- try {
1984
- const result = await post("/auth/start", { provider: "copilot" });
1985
- setDeviceCode(result.userCode);
1986
- setVerificationUrl(result.verificationUrl);
1987
- await post("/auth/copilot/poll");
1988
- const authToken = {
1989
- accessToken: "server-managed",
1990
- tokenType: "bearer",
1991
- obtainedAt: Date.now()
1992
- };
1993
- setToken(authToken);
1994
- setStatus("authenticated");
1995
- onAuthenticatedRef.current?.(authToken);
1996
- } catch (err) {
1997
- setError(err instanceof Error ? err : new Error(String(err)));
1998
- setStatus("error");
1999
- }
2000
- }, [backend, post]);
1772
+ await copilot.start();
1773
+ }, [backend, copilot]);
2001
1774
  const startOAuthFlow = react.useCallback(async () => {
2002
1775
  if (backend !== "claude") return;
2003
1776
  setStatus("pending");
2004
1777
  setError(null);
2005
- try {
2006
- const result = await post("/auth/start", { provider: "claude" });
2007
- setAuthorizeUrl(result.authorizeUrl);
2008
- } catch (err) {
2009
- setError(err instanceof Error ? err : new Error(String(err)));
2010
- setStatus("error");
2011
- }
2012
- }, [backend, post]);
1778
+ await claude.start();
1779
+ }, [backend, claude]);
2013
1780
  const completeOAuth = react.useCallback(
2014
1781
  async (codeOrUrl) => {
2015
- try {
2016
- await post("/auth/claude/complete", { code: codeOrUrl });
2017
- const authToken = {
2018
- accessToken: "server-managed",
2019
- tokenType: "bearer",
2020
- obtainedAt: Date.now()
2021
- };
2022
- setToken(authToken);
2023
- setStatus("authenticated");
2024
- onAuthenticatedRef.current?.(authToken);
2025
- } catch (err) {
2026
- setError(err instanceof Error ? err : new Error(String(err)));
2027
- setStatus("error");
2028
- }
1782
+ await claude.complete(codeOrUrl);
2029
1783
  },
2030
- [post]
1784
+ [claude]
2031
1785
  );
2032
1786
  const submitApiKey = react.useCallback(
2033
1787
  async (key, apiBaseUrl) => {
2034
1788
  if (backend !== "vercel-ai") return;
2035
- if (!key || !key.trim()) {
2036
- setError(new Error("API key cannot be empty"));
2037
- setStatus("error");
2038
- return;
2039
- }
2040
- try {
2041
- await post("/auth/vercel/complete", {
2042
- apiKey: key.trim(),
2043
- ...apiBaseUrl ? { baseUrl: apiBaseUrl } : {}
2044
- });
2045
- const authToken = {
2046
- accessToken: "server-managed",
2047
- tokenType: "bearer",
2048
- obtainedAt: Date.now()
2049
- };
2050
- setToken(authToken);
2051
- setStatus("authenticated");
2052
- onAuthenticatedRef.current?.(authToken);
2053
- } catch (err) {
2054
- setError(err instanceof Error ? err : new Error(String(err)));
2055
- setStatus("error");
2056
- }
1789
+ await apiKey.submit(key, apiBaseUrl);
2057
1790
  },
2058
- [backend, post]
1791
+ [backend, apiKey]
2059
1792
  );
2060
1793
  const loadSavedTokens = react.useCallback(async () => {
2061
1794
  try {
@@ -2094,55 +1827,44 @@ function useRemoteAuth(options) {
2094
1827
  setStatus("idle");
2095
1828
  setError(null);
2096
1829
  setToken(null);
2097
- setDeviceCode(null);
2098
- setVerificationUrl(null);
2099
- setAuthorizeUrl(null);
1830
+ copilot.reset();
1831
+ claude.reset();
1832
+ apiKey.reset();
2100
1833
  setSavedProviders([]);
2101
- }, []);
1834
+ }, [copilot, claude, apiKey]);
2102
1835
  const start = react.useCallback(async (provider) => {
2103
1836
  const target = provider ?? backend;
2104
1837
  setStatus("pending");
2105
1838
  setError(null);
2106
- try {
2107
- switch (target) {
2108
- case "copilot": {
2109
- const result = await post("/auth/start", { provider: "copilot" });
2110
- setDeviceCode(result.userCode);
2111
- setVerificationUrl(result.verificationUrl);
2112
- await post("/auth/copilot/poll");
2113
- const authToken = {
2114
- accessToken: "server-managed",
2115
- tokenType: "bearer",
2116
- obtainedAt: Date.now()
2117
- };
2118
- setToken(authToken);
2119
- setStatus("authenticated");
2120
- onAuthenticatedRef.current?.(authToken);
2121
- break;
2122
- }
2123
- case "claude": {
2124
- const result = await post("/auth/start", { provider: "claude" });
2125
- setAuthorizeUrl(result.authorizeUrl);
2126
- break;
2127
- }
2128
- case "vercel-ai":
2129
- throw new Error("vercel-ai requires submitApiKey(key, baseUrl) \u2014 cannot auto-start");
2130
- default:
2131
- throw new Error(`Unknown auth provider: ${target}`);
1839
+ switch (target) {
1840
+ case "copilot":
1841
+ await copilot.start();
1842
+ break;
1843
+ case "claude":
1844
+ await claude.start();
1845
+ break;
1846
+ case "vercel-ai": {
1847
+ const e = new Error("vercel-ai requires submitApiKey(key, baseUrl) \u2014 cannot auto-start");
1848
+ setError(e);
1849
+ setStatus("error");
1850
+ return;
1851
+ }
1852
+ default: {
1853
+ const e = new Error(`Unknown auth provider: ${target}`);
1854
+ setError(e);
1855
+ setStatus("error");
1856
+ return;
2132
1857
  }
2133
- } catch (err) {
2134
- setError(err instanceof Error ? err : new Error(String(err)));
2135
- setStatus("error");
2136
1858
  }
2137
- }, [backend, post]);
1859
+ }, [backend, copilot, claude]);
2138
1860
  return {
2139
1861
  status,
2140
1862
  error,
2141
1863
  startDeviceFlow,
2142
- deviceCode,
2143
- verificationUrl,
1864
+ deviceCode: copilot.deviceCode,
1865
+ verificationUrl: copilot.verificationUrl,
2144
1866
  startOAuthFlow,
2145
- authorizeUrl,
1867
+ authorizeUrl: claude.authorizeUrl,
2146
1868
  completeOAuth,
2147
1869
  submitApiKey,
2148
1870
  start,
@@ -2155,18 +1877,45 @@ function useRemoteAuth(options) {
2155
1877
  };
2156
1878
  }
2157
1879
 
2158
- // src/chat/react/RemoteChatRuntime.ts
2159
- var RemoteChatRuntime = class {
1880
+ // src/chat/listener-set.ts
1881
+ var ListenerSet = class {
1882
+ _listeners = /* @__PURE__ */ new Set();
1883
+ /** Add a listener. Returns an unsubscribe function. */
1884
+ add(callback) {
1885
+ this._listeners.add(callback);
1886
+ return () => {
1887
+ this._listeners.delete(callback);
1888
+ };
1889
+ }
1890
+ /** Notify all listeners with the given arguments. Errors are isolated per listener. */
1891
+ notify(...args) {
1892
+ for (const cb of this._listeners) {
1893
+ try {
1894
+ cb(...args);
1895
+ } catch {
1896
+ }
1897
+ }
1898
+ }
1899
+ /** Remove all listeners. */
1900
+ clear() {
1901
+ this._listeners.clear();
1902
+ }
1903
+ /** Current number of listeners. */
1904
+ get size() {
1905
+ return this._listeners.size;
1906
+ }
1907
+ };
1908
+
1909
+ // src/chat/react/RemoteChatClient.ts
1910
+ var RemoteChatClient = class {
2160
1911
  _status = "idle";
2161
1912
  _activeSessionId = null;
2162
- _currentBackend = "default";
2163
- _currentModel;
1913
+ _selectedProviderId = null;
2164
1914
  _abortController = null;
2165
- _tools = /* @__PURE__ */ new Map();
2166
- _middlewares = [];
2167
1915
  baseUrl;
2168
1916
  headers;
2169
1917
  _fetch;
1918
+ _selectionListeners = new ListenerSet();
2170
1919
  constructor(options) {
2171
1920
  this.baseUrl = options.baseUrl.replace(/\/$/, "");
2172
1921
  this.headers = options.headers ?? {};
@@ -2185,6 +1934,21 @@ var RemoteChatRuntime = class {
2185
1934
  throw new Error("Runtime is disposed");
2186
1935
  }
2187
1936
  }
1937
+ // ─── Provider Selection ────────────────────────────────────
1938
+ get selectedProviderId() {
1939
+ return this._selectedProviderId;
1940
+ }
1941
+ selectProvider(providerId) {
1942
+ this.assertNotDisposed();
1943
+ this._selectedProviderId = providerId;
1944
+ this._notifySelectionChange(providerId);
1945
+ }
1946
+ onSelectionChange(callback) {
1947
+ return this._selectionListeners.add(callback);
1948
+ }
1949
+ _notifySelectionChange(providerId) {
1950
+ this._selectionListeners.notify(providerId);
1951
+ }
2188
1952
  // ─── Sessions ───────────────────────────────────────────────
2189
1953
  get activeSessionId() {
2190
1954
  return this._activeSessionId;
@@ -2216,16 +1980,22 @@ var RemoteChatRuntime = class {
2216
1980
  }
2217
1981
  this._notifySessionChange();
2218
1982
  }
2219
- async archiveSession(id) {
1983
+ /**
1984
+ * Fetch context window stats from server.
1985
+ * Returns null if stats not available (e.g. no messages sent yet).
1986
+ */
1987
+ async getContextStats(sessionId) {
2220
1988
  this.assertNotDisposed();
2221
- await this._post(`/sessions/${id}/archive`, {});
2222
- this._notifySessionChange();
1989
+ const res = await this._get(`/sessions/${sessionId}/context-stats`);
1990
+ const data = await res.json();
1991
+ return data;
2223
1992
  }
2224
1993
  async switchSession(id) {
2225
1994
  this.assertNotDisposed();
2226
1995
  const session = await this.getSession(id);
2227
1996
  if (!session) throw new Error(`Session not found: ${id}`);
2228
1997
  this._activeSessionId = session.id;
1998
+ this._notifySessionChange();
2229
1999
  return session;
2230
2000
  }
2231
2001
  // ─── Messaging ──────────────────────────────────────────────
@@ -2244,7 +2014,8 @@ var RemoteChatRuntime = class {
2244
2014
  body: JSON.stringify({
2245
2015
  sessionId,
2246
2016
  message,
2247
- model: options?.model
2017
+ model: options?.model,
2018
+ providerId: this._selectedProviderId ?? void 0
2248
2019
  }),
2249
2020
  signal: this._abortController.signal
2250
2021
  });
@@ -2279,64 +2050,43 @@ var RemoteChatRuntime = class {
2279
2050
  this._post("/abort", {}).catch(() => {
2280
2051
  });
2281
2052
  }
2282
- // ─── Backend / Model ────────────────────────────────────────
2283
- get currentBackend() {
2284
- return this._currentBackend;
2285
- }
2286
- get currentModel() {
2287
- return this._currentModel;
2288
- }
2289
- async switchBackend(name) {
2290
- this.assertNotDisposed();
2291
- await this._post("/backend/switch", { backend: name });
2292
- this._currentBackend = name;
2293
- }
2294
- switchModel(model) {
2295
- this._currentModel = model;
2296
- this._post("/model/switch", { model }).catch(() => {
2297
- });
2298
- }
2053
+ // ─── Discovery ─────────────────────────────────────────────
2299
2054
  async listModels() {
2300
2055
  this.assertNotDisposed();
2301
2056
  const res = await this._get("/models");
2302
2057
  return await res.json();
2303
2058
  }
2304
- // ─── Tools (client-side registry) ───────────────────────────
2305
- get registeredTools() {
2306
- return this._tools;
2307
- }
2308
- registerTool(tool) {
2309
- this._tools.set(tool.name, tool);
2059
+ async listBackends() {
2060
+ this.assertNotDisposed();
2061
+ const res = await this._get("/backends");
2062
+ return await res.json();
2310
2063
  }
2311
- removeTool(name) {
2312
- this._tools.delete(name);
2064
+ // ─── Providers ──────────────────────────────────────────────
2065
+ async listProviders() {
2066
+ this.assertNotDisposed();
2067
+ const res = await this._get("/providers");
2068
+ return await res.json();
2313
2069
  }
2314
- // ─── Middleware (client-side) ───────────────────────────────
2315
- use(middleware) {
2316
- this._middlewares.push(middleware);
2070
+ async createProvider(config) {
2071
+ this.assertNotDisposed();
2072
+ const res = await this._post("/providers", config);
2073
+ return await res.json();
2317
2074
  }
2318
- removeMiddleware(middleware) {
2319
- const idx = this._middlewares.indexOf(middleware);
2320
- if (idx !== -1) this._middlewares.splice(idx, 1);
2075
+ async updateProvider(id, changes) {
2076
+ this.assertNotDisposed();
2077
+ await this._put(`/providers/${id}`, changes);
2321
2078
  }
2322
- // ─── Context ────────────────────────────────────────────────
2323
- getContextStats(_sessionId) {
2324
- return null;
2079
+ async deleteProvider(id) {
2080
+ this.assertNotDisposed();
2081
+ await this._delete(`/providers/${id}`);
2325
2082
  }
2326
- _sessionListeners = /* @__PURE__ */ new Set();
2083
+ // ─── Session Change Notifications ────────────────────────────
2084
+ _sessionListeners = new ListenerSet();
2327
2085
  onSessionChange(callback) {
2328
- this._sessionListeners.add(callback);
2329
- return () => {
2330
- this._sessionListeners.delete(callback);
2331
- };
2086
+ return this._sessionListeners.add(callback);
2332
2087
  }
2333
2088
  _notifySessionChange() {
2334
- for (const cb of this._sessionListeners) {
2335
- try {
2336
- cb();
2337
- } catch {
2338
- }
2339
- }
2089
+ this._sessionListeners.notify();
2340
2090
  }
2341
2091
  // ─── Internal HTTP helpers ──────────────────────────────────
2342
2092
  async _get(path) {
@@ -2373,14 +2123,28 @@ var RemoteChatRuntime = class {
2373
2123
  }
2374
2124
  return res;
2375
2125
  }
2376
- // ─── SSE Parser ─────────────────────────────────────────────
2377
- async *_parseSSEStream(body, signal) {
2378
- const reader = body.getReader();
2379
- const decoder = new TextDecoder();
2380
- let buffer = "";
2381
- const abortPromise = new Promise((_, reject) => {
2382
- if (signal.aborted) {
2383
- reject(new DOMException("Aborted", "AbortError"));
2126
+ async _put(path, body) {
2127
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2128
+ method: "PUT",
2129
+ headers: {
2130
+ "Content-Type": "application/json",
2131
+ ...this.headers
2132
+ },
2133
+ body: JSON.stringify(body)
2134
+ });
2135
+ if (!res.ok) {
2136
+ throw new Error(`PUT ${path} failed: ${res.status} ${res.statusText}`);
2137
+ }
2138
+ return res;
2139
+ }
2140
+ // ─── SSE Parser ─────────────────────────────────────────────
2141
+ async *_parseSSEStream(body, signal) {
2142
+ const reader = body.getReader();
2143
+ const decoder = new TextDecoder();
2144
+ let buffer = "";
2145
+ const abortPromise = new Promise((_, reject) => {
2146
+ if (signal.aborted) {
2147
+ reject(new DOMException("Aborted", "AbortError"));
2384
2148
  return;
2385
2149
  }
2386
2150
  signal.addEventListener("abort", () => {
@@ -2427,8 +2191,7 @@ function useRemoteChat(options) {
2427
2191
  authBaseUrl,
2428
2192
  backend,
2429
2193
  onReady,
2430
- headers,
2431
- autoRestore = true
2194
+ headers
2432
2195
  } = options;
2433
2196
  const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
2434
2197
  const [phase, setPhase] = react.useState("initializing");
@@ -2452,7 +2215,7 @@ function useRemoteChat(options) {
2452
2215
  const restoredRef = react.useRef(false);
2453
2216
  const [tokensLoaded, setTokensLoaded] = react.useState(false);
2454
2217
  react.useEffect(() => {
2455
- if (!autoRestore || restoredRef.current) return;
2218
+ if (restoredRef.current) return;
2456
2219
  restoredRef.current = true;
2457
2220
  (async () => {
2458
2221
  try {
@@ -2461,7 +2224,7 @@ function useRemoteChat(options) {
2461
2224
  }
2462
2225
  if (mountedRef.current) setTokensLoaded(true);
2463
2226
  })();
2464
- }, [autoRestore]);
2227
+ }, []);
2465
2228
  react.useEffect(() => {
2466
2229
  if (!tokensLoaded) return;
2467
2230
  if (auth.status === "idle" && auth.savedProviders.includes(backend) && phase === "initializing") {
@@ -2488,7 +2251,7 @@ function useRemoteChat(options) {
2488
2251
  setError(null);
2489
2252
  (async () => {
2490
2253
  try {
2491
- const rt = new RemoteChatRuntime({
2254
+ const rt = new RemoteChatClient({
2492
2255
  baseUrl: chatBaseUrl,
2493
2256
  headers,
2494
2257
  fetch: fetchFn
@@ -2543,197 +2306,1488 @@ function useRemoteChat(options) {
2543
2306
  logout
2544
2307
  };
2545
2308
  }
2546
- function AuthDialog({
2309
+ function CopilotAuthForm({ auth, onAuthComplete }) {
2310
+ const children = [];
2311
+ if (auth.status === "idle") {
2312
+ children.push(
2313
+ react.createElement("button", {
2314
+ key: "start",
2315
+ type: "button",
2316
+ "data-action": "start-auth",
2317
+ onClick: () => {
2318
+ auth.startDeviceFlow().then(() => onAuthComplete());
2319
+ }
2320
+ }, "Authenticate with GitHub")
2321
+ );
2322
+ }
2323
+ if (auth.deviceCode) {
2324
+ children.push(
2325
+ react.createElement("div", { key: "code", "data-device-code": "true" }, auth.deviceCode)
2326
+ );
2327
+ if (auth.verificationUrl) {
2328
+ children.push(
2329
+ react.createElement("a", {
2330
+ key: "url",
2331
+ href: auth.verificationUrl,
2332
+ target: "_blank",
2333
+ rel: "noreferrer"
2334
+ }, "Open GitHub \u2192")
2335
+ );
2336
+ }
2337
+ }
2338
+ if (auth.status === "pending") {
2339
+ children.push(
2340
+ react.createElement("span", { key: "wait", "data-auth-loading": "true" }, "Waiting...")
2341
+ );
2342
+ }
2343
+ if (auth.status === "authenticated") {
2344
+ children.push(
2345
+ react.createElement(
2346
+ "div",
2347
+ { key: "done", "data-auth-success": "true" },
2348
+ "\u2713 Authenticated",
2349
+ react.createElement("button", {
2350
+ type: "button",
2351
+ "data-action": "continue",
2352
+ onClick: onAuthComplete
2353
+ }, "Continue \u2192")
2354
+ )
2355
+ );
2356
+ }
2357
+ if (auth.error) {
2358
+ children.push(
2359
+ react.createElement("div", { key: "error", "data-auth-error-display": "true" }, auth.error.message)
2360
+ );
2361
+ }
2362
+ return react.createElement("div", { "data-auth-flow": "copilot" }, ...children);
2363
+ }
2364
+ function ClaudeAuthForm({ auth, onAuthComplete }) {
2365
+ const codeRef = react.useRef(null);
2366
+ const children = [];
2367
+ if (auth.status === "idle") {
2368
+ children.push(
2369
+ react.createElement("button", {
2370
+ key: "start",
2371
+ type: "button",
2372
+ "data-action": "start-auth",
2373
+ onClick: () => auth.startOAuthFlow()
2374
+ }, "Authenticate with Claude")
2375
+ );
2376
+ }
2377
+ if (auth.authorizeUrl) {
2378
+ children.push(
2379
+ react.createElement("a", {
2380
+ key: "url",
2381
+ href: auth.authorizeUrl,
2382
+ target: "_blank",
2383
+ rel: "noreferrer"
2384
+ }, "Open authorization page \u2192")
2385
+ );
2386
+ children.push(
2387
+ react.createElement(
2388
+ "div",
2389
+ { key: "complete", "data-auth-complete": "true" },
2390
+ react.createElement("input", {
2391
+ ref: codeRef,
2392
+ placeholder: "Paste code or redirect URL..."
2393
+ }),
2394
+ react.createElement("button", {
2395
+ type: "button",
2396
+ "data-action": "complete-auth",
2397
+ onClick: () => {
2398
+ const v = codeRef.current?.value?.trim();
2399
+ if (v) auth.completeOAuth(v).then(() => onAuthComplete());
2400
+ }
2401
+ }, "Submit")
2402
+ )
2403
+ );
2404
+ }
2405
+ if (auth.status === "authenticated") {
2406
+ children.push(
2407
+ react.createElement(
2408
+ "div",
2409
+ { key: "done", "data-auth-success": "true" },
2410
+ "\u2713 Authenticated",
2411
+ react.createElement("button", {
2412
+ type: "button",
2413
+ "data-action": "continue",
2414
+ onClick: onAuthComplete
2415
+ }, "Continue \u2192")
2416
+ )
2417
+ );
2418
+ }
2419
+ if (auth.error) {
2420
+ children.push(
2421
+ react.createElement("div", { key: "error", "data-auth-error-display": "true" }, auth.error.message)
2422
+ );
2423
+ }
2424
+ return react.createElement("div", { "data-auth-flow": "claude" }, ...children);
2425
+ }
2426
+ function VercelAIAuthForm({ auth, onAuthComplete }) {
2427
+ const apiKeyRef = react.useRef(null);
2428
+ const baseUrlRef = react.useRef(null);
2429
+ const children = [];
2430
+ if (auth.status !== "authenticated") {
2431
+ children.push(
2432
+ react.createElement(
2433
+ "div",
2434
+ { key: "apikey", "data-auth-apikey": "true" },
2435
+ react.createElement("input", {
2436
+ ref: baseUrlRef,
2437
+ placeholder: "Base URL (default: openai.com/v1)"
2438
+ }),
2439
+ react.createElement("input", {
2440
+ ref: apiKeyRef,
2441
+ type: "password",
2442
+ placeholder: "API Key (sk-...)"
2443
+ }),
2444
+ react.createElement("button", {
2445
+ type: "button",
2446
+ "data-action": "submit-apikey",
2447
+ onClick: () => {
2448
+ const k = apiKeyRef.current?.value?.trim();
2449
+ if (k) {
2450
+ auth.submitApiKey(k, baseUrlRef.current?.value?.trim() || void 0).then(() => onAuthComplete());
2451
+ }
2452
+ }
2453
+ }, "Connect")
2454
+ )
2455
+ );
2456
+ }
2457
+ if (auth.status === "authenticated") {
2458
+ children.push(
2459
+ react.createElement(
2460
+ "div",
2461
+ { key: "done", "data-auth-success": "true" },
2462
+ "\u2713 Connected",
2463
+ react.createElement("button", {
2464
+ type: "button",
2465
+ "data-action": "continue",
2466
+ onClick: onAuthComplete
2467
+ }, "Continue \u2192")
2468
+ )
2469
+ );
2470
+ }
2471
+ if (auth.error) {
2472
+ children.push(
2473
+ react.createElement("div", { key: "error", "data-auth-error-display": "true" }, auth.error.message)
2474
+ );
2475
+ }
2476
+ return react.createElement("div", { "data-auth-flow": "vercel-ai" }, ...children);
2477
+ }
2478
+ function BackendSelector({
2547
2479
  backends,
2548
- selectedBackend: controlledBackend,
2549
- onBackendChange,
2550
- onAuthenticated,
2551
- renderCopilotFlow,
2552
- renderClaudeFlow,
2553
- renderApiKeyFlow,
2480
+ onSelect,
2554
2481
  className
2555
2482
  }) {
2556
- const [internalBackend, setInternalBackend] = react.useState(
2557
- controlledBackend ?? backends[0] ?? "copilot"
2558
- );
2559
- const activeBackend = controlledBackend ?? internalBackend;
2560
- const handleBackendChange = react.useCallback(
2561
- (backend) => {
2562
- setInternalBackend(backend);
2563
- onBackendChange?.(backend);
2564
- },
2565
- [onBackendChange]
2483
+ const handleClick = react.useCallback(
2484
+ (name) => () => onSelect(name),
2485
+ [onSelect]
2566
2486
  );
2567
- const auth = useAuth({ backend: activeBackend, onAuthenticated });
2568
- const children = [];
2569
- const selectorButtons = backends.map(
2487
+ const items = backends.map(
2570
2488
  (backend) => react.createElement(
2571
2489
  "button",
2572
2490
  {
2573
- key: backend,
2491
+ key: backend.name,
2574
2492
  type: "button",
2575
- "data-auth-backend": backend,
2576
- "data-auth-selected": String(backend === activeBackend),
2577
- onClick: () => handleBackendChange(backend)
2493
+ "data-backend-item": "true",
2494
+ "data-backend-name": backend.name,
2495
+ onClick: handleClick(backend.name)
2578
2496
  },
2579
- backend
2497
+ backend.name
2580
2498
  )
2581
2499
  );
2582
- children.push(
2583
- react.createElement(
2584
- "div",
2585
- { key: "selector", "data-auth-selector": "true" },
2586
- ...selectorButtons
2587
- )
2500
+ return react.createElement(
2501
+ "div",
2502
+ { "data-backend-selector": "true", className },
2503
+ ...items
2588
2504
  );
2589
- const contentAttrs = {
2590
- key: "content",
2591
- "data-auth-content": "true",
2592
- "data-auth-status": auth.status
2593
- };
2594
- if (auth.error) {
2595
- contentAttrs["data-auth-error"] = auth.error.message;
2596
- }
2597
- let flowContent = null;
2598
- if (activeBackend === "copilot") {
2599
- if (renderCopilotFlow && auth.deviceCode && auth.verificationUrl) {
2600
- flowContent = renderCopilotFlow({
2601
- deviceCode: auth.deviceCode,
2602
- verificationUrl: auth.verificationUrl,
2603
- status: auth.status
2604
- });
2605
- } else {
2606
- const copilotChildren = [];
2607
- if (auth.status === "idle") {
2608
- copilotChildren.push(
2609
- react.createElement(
2610
- "button",
2611
- {
2612
- key: "start",
2613
- type: "button",
2614
- "data-action": "start-device-flow",
2615
- onClick: auth.startDeviceFlow
2616
- },
2617
- "Start Device Flow"
2618
- )
2619
- );
2620
- }
2621
- if (auth.deviceCode && auth.verificationUrl) {
2622
- copilotChildren.push(
2623
- react.createElement("span", { key: "code", "data-device-code": "true" }, auth.deviceCode)
2624
- );
2625
- copilotChildren.push(
2626
- react.createElement("a", { key: "url", "data-verification-url": "true", href: auth.verificationUrl }, auth.verificationUrl)
2627
- );
2505
+ }
2506
+ function useBackends() {
2507
+ const runtime = useChatRuntime();
2508
+ const [backends, setBackends] = react.useState([]);
2509
+ const [isLoading, setIsLoading] = react.useState(true);
2510
+ const [error, setError] = react.useState(null);
2511
+ const mountedRef = react.useRef(true);
2512
+ const fetchBackends = react.useCallback(() => {
2513
+ setIsLoading(true);
2514
+ setError(null);
2515
+ try {
2516
+ const result = runtime.listBackends();
2517
+ if (result instanceof Promise) {
2518
+ result.then((data) => {
2519
+ if (mountedRef.current) {
2520
+ setBackends(data);
2521
+ setIsLoading(false);
2522
+ }
2523
+ }).catch((err) => {
2524
+ if (mountedRef.current) {
2525
+ setError(err instanceof Error ? err : new Error(String(err)));
2526
+ setIsLoading(false);
2527
+ }
2528
+ });
2529
+ } else {
2530
+ if (mountedRef.current) {
2531
+ setBackends(result);
2532
+ setIsLoading(false);
2533
+ }
2628
2534
  }
2629
- if (auth.status === "pending") {
2630
- copilotChildren.push(
2631
- react.createElement("span", { key: "loading", "data-auth-loading": "true" }, "Waiting...")
2632
- );
2535
+ } catch (err) {
2536
+ if (mountedRef.current) {
2537
+ setError(err instanceof Error ? err : new Error(String(err)));
2538
+ setIsLoading(false);
2633
2539
  }
2634
- flowContent = react.createElement(
2635
- "div",
2636
- { "data-auth-flow": "copilot" },
2637
- ...copilotChildren
2638
- );
2639
2540
  }
2640
- } else if (activeBackend === "claude") {
2641
- if (renderClaudeFlow) {
2642
- flowContent = renderClaudeFlow({
2643
- authorizeUrl: auth.authorizeUrl,
2644
- status: auth.status,
2645
- completeOAuth: auth.completeOAuth
2646
- });
2647
- } else {
2648
- const claudeChildren = [];
2649
- if (auth.status === "idle") {
2650
- claudeChildren.push(
2651
- react.createElement(
2652
- "button",
2653
- {
2654
- key: "start",
2655
- type: "button",
2656
- "data-action": "start-oauth-flow",
2657
- onClick: auth.startOAuthFlow
2658
- },
2659
- "Start OAuth Flow"
2660
- )
2661
- );
2541
+ }, [runtime]);
2542
+ react.useEffect(() => {
2543
+ mountedRef.current = true;
2544
+ fetchBackends();
2545
+ return () => {
2546
+ mountedRef.current = false;
2547
+ };
2548
+ }, [fetchBackends]);
2549
+ return { backends, isLoading, error, refresh: fetchBackends };
2550
+ }
2551
+ function isProviderCapable(runtime) {
2552
+ return typeof runtime === "object" && runtime !== null && typeof runtime.listProviders === "function";
2553
+ }
2554
+ function useProviders() {
2555
+ const runtime = useChatRuntime();
2556
+ const [providers, setProviders] = react.useState([]);
2557
+ const [isLoading, setIsLoading] = react.useState(true);
2558
+ const [error, setError] = react.useState(null);
2559
+ const mountedRef = react.useRef(true);
2560
+ const fetchProviders = react.useCallback(() => {
2561
+ setIsLoading(true);
2562
+ setError(null);
2563
+ if (!isProviderCapable(runtime)) {
2564
+ setIsLoading(false);
2565
+ return;
2566
+ }
2567
+ runtime.listProviders().then((data) => {
2568
+ if (mountedRef.current) {
2569
+ setProviders(data);
2570
+ setIsLoading(false);
2662
2571
  }
2663
- if (auth.authorizeUrl) {
2664
- claudeChildren.push(
2665
- react.createElement("a", { key: "url", "data-authorize-url": "true", href: auth.authorizeUrl }, auth.authorizeUrl)
2666
- );
2572
+ }).catch((err) => {
2573
+ if (mountedRef.current) {
2574
+ setError(err instanceof Error ? err : new Error(String(err)));
2575
+ setIsLoading(false);
2667
2576
  }
2668
- flowContent = react.createElement(
2669
- "div",
2670
- { "data-auth-flow": "claude" },
2671
- ...claudeChildren
2577
+ });
2578
+ }, [runtime]);
2579
+ react.useEffect(() => {
2580
+ mountedRef.current = true;
2581
+ fetchProviders();
2582
+ return () => {
2583
+ mountedRef.current = false;
2584
+ };
2585
+ }, [fetchProviders]);
2586
+ const createProvider = react.useCallback(
2587
+ async (config) => {
2588
+ if (!isProviderCapable(runtime)) return;
2589
+ try {
2590
+ await runtime.createProvider(config);
2591
+ fetchProviders();
2592
+ } catch (err) {
2593
+ setError(err instanceof Error ? err : new Error(String(err)));
2594
+ }
2595
+ },
2596
+ [runtime, fetchProviders]
2597
+ );
2598
+ const updateProvider = react.useCallback(
2599
+ async (id, changes) => {
2600
+ if (!isProviderCapable(runtime)) return;
2601
+ try {
2602
+ await runtime.updateProvider(id, changes);
2603
+ fetchProviders();
2604
+ } catch (err) {
2605
+ setError(err instanceof Error ? err : new Error(String(err)));
2606
+ }
2607
+ },
2608
+ [runtime, fetchProviders]
2609
+ );
2610
+ const deleteProvider = react.useCallback(
2611
+ async (id) => {
2612
+ if (!isProviderCapable(runtime)) return;
2613
+ try {
2614
+ await runtime.deleteProvider(id);
2615
+ fetchProviders();
2616
+ } catch (err) {
2617
+ setError(err instanceof Error ? err : new Error(String(err)));
2618
+ }
2619
+ },
2620
+ [runtime, fetchProviders]
2621
+ );
2622
+ const selectProvider = react.useCallback(
2623
+ (id) => {
2624
+ if (!isProviderCapable(runtime)) return;
2625
+ runtime.selectProvider(id);
2626
+ },
2627
+ [runtime]
2628
+ );
2629
+ return { providers, isLoading, error, refresh: fetchProviders, createProvider, updateProvider, deleteProvider, selectProvider };
2630
+ }
2631
+ function ProviderSelector({
2632
+ providers,
2633
+ activeProviderId,
2634
+ onSelect,
2635
+ onSettingsClick,
2636
+ className
2637
+ }) {
2638
+ const [open, setOpen] = react.useState(false);
2639
+ const [highlightIndex, setHighlightIndex] = react.useState(0);
2640
+ const containerRef = react.useRef(null);
2641
+ const activeProvider = react.useMemo(
2642
+ () => providers.find((p) => p.id === activeProviderId),
2643
+ [providers, activeProviderId]
2644
+ );
2645
+ react.useEffect(() => {
2646
+ if (!open) return;
2647
+ const handler = (e) => {
2648
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
2649
+ setOpen(false);
2650
+ }
2651
+ };
2652
+ document.addEventListener("mousedown", handler);
2653
+ return () => document.removeEventListener("mousedown", handler);
2654
+ }, [open]);
2655
+ react.useEffect(() => {
2656
+ setHighlightIndex(0);
2657
+ }, [providers.length]);
2658
+ const handleToggle = react.useCallback(() => {
2659
+ setOpen((prev) => {
2660
+ if (!prev) setHighlightIndex(0);
2661
+ return !prev;
2662
+ });
2663
+ }, []);
2664
+ const handleSelect = react.useCallback(
2665
+ (id) => {
2666
+ onSelect(id);
2667
+ setOpen(false);
2668
+ },
2669
+ [onSelect]
2670
+ );
2671
+ const handleKeyDown = react.useCallback(
2672
+ (e) => {
2673
+ if (e.key === "ArrowDown") {
2674
+ e.preventDefault();
2675
+ setHighlightIndex((prev) => Math.min(prev + 1, providers.length - 1));
2676
+ } else if (e.key === "ArrowUp") {
2677
+ e.preventDefault();
2678
+ setHighlightIndex((prev) => Math.max(prev - 1, 0));
2679
+ } else if (e.key === "Enter") {
2680
+ e.preventDefault();
2681
+ if (providers[highlightIndex]) {
2682
+ handleSelect(providers[highlightIndex].id);
2683
+ }
2684
+ } else if (e.key === "Escape") {
2685
+ e.preventDefault();
2686
+ setOpen(false);
2687
+ }
2688
+ },
2689
+ [providers, highlightIndex, handleSelect]
2690
+ );
2691
+ const children = [];
2692
+ children.push(
2693
+ react.createElement(
2694
+ "button",
2695
+ {
2696
+ key: "trigger",
2697
+ type: "button",
2698
+ "data-provider-trigger": "true",
2699
+ onClick: handleToggle,
2700
+ onKeyDown: handleKeyDown
2701
+ },
2702
+ activeProvider ? activeProvider.label : "Select provider"
2703
+ )
2704
+ );
2705
+ if (open) {
2706
+ const dropdownChildren = [];
2707
+ providers.forEach((provider, idx) => {
2708
+ const isActive = provider.id === activeProviderId;
2709
+ const isHighlighted = idx === highlightIndex;
2710
+ const attrs = {
2711
+ key: provider.id,
2712
+ "data-provider-item": "true",
2713
+ onClick: () => handleSelect(provider.id)
2714
+ };
2715
+ if (isActive) attrs["data-provider-active"] = "true";
2716
+ if (isHighlighted) attrs["data-provider-highlighted"] = "true";
2717
+ dropdownChildren.push(
2718
+ react.createElement(
2719
+ "div",
2720
+ attrs,
2721
+ react.createElement("span", { "data-provider-label": "true" }, provider.label),
2722
+ react.createElement("span", { "data-provider-model": "true" }, provider.model)
2723
+ )
2672
2724
  );
2673
- }
2674
- } else if (activeBackend === "api-key") {
2675
- if (renderApiKeyFlow) {
2676
- flowContent = renderApiKeyFlow({
2677
- submitApiKey: auth.submitApiKey,
2678
- status: auth.status
2679
- });
2680
- } else {
2681
- flowContent = react.createElement(
2682
- "div",
2683
- { "data-auth-flow": "api-key" },
2725
+ });
2726
+ if (onSettingsClick) {
2727
+ dropdownChildren.push(
2684
2728
  react.createElement(
2685
2729
  "button",
2686
2730
  {
2687
- key: "submit",
2731
+ key: "settings",
2688
2732
  type: "button",
2689
- "data-action": "submit-api-key",
2690
- onClick: () => auth.submitApiKey("")
2691
- },
2692
- "Submit API Key"
2733
+ "data-provider-settings-btn": "true",
2734
+ onClick: (e) => {
2735
+ e.stopPropagation();
2736
+ setOpen(false);
2737
+ onSettingsClick();
2738
+ }
2739
+ },
2740
+ "\u2699 Settings"
2693
2741
  )
2694
2742
  );
2695
2743
  }
2744
+ children.push(
2745
+ react.createElement(
2746
+ "div",
2747
+ { key: "dropdown", "data-provider-dropdown": "true", onKeyDown: handleKeyDown },
2748
+ ...dropdownChildren
2749
+ )
2750
+ );
2696
2751
  }
2697
- children.push(react.createElement("div", contentAttrs, flowContent));
2698
- if (auth.error) {
2752
+ return react.createElement(
2753
+ "div",
2754
+ {
2755
+ "data-provider-selector": "true",
2756
+ className,
2757
+ ref: containerRef
2758
+ },
2759
+ ...children
2760
+ );
2761
+ }
2762
+ function ProviderModelSelector({
2763
+ providers = [],
2764
+ models = [],
2765
+ activeProviderId,
2766
+ selectedModel,
2767
+ onSelectProvider,
2768
+ onSelectModel,
2769
+ onSettingsClick,
2770
+ placeholder = "Select model",
2771
+ className
2772
+ }) {
2773
+ const [open, setOpen] = react.useState(false);
2774
+ const [search, setSearch] = react.useState("");
2775
+ const [highlightIndex, setHighlightIndex] = react.useState(0);
2776
+ const containerRef = react.useRef(null);
2777
+ const isProviderMode = providers.length > 0;
2778
+ const items = react.useMemo(() => {
2779
+ if (isProviderMode) {
2780
+ return providers.map((p) => ({
2781
+ id: p.id,
2782
+ label: p.label,
2783
+ sublabel: p.model,
2784
+ type: "provider"
2785
+ }));
2786
+ }
2787
+ return models.map((m) => ({
2788
+ id: m.id,
2789
+ label: m.name,
2790
+ sublabel: m.provider,
2791
+ tier: m.tier,
2792
+ type: "model"
2793
+ }));
2794
+ }, [isProviderMode, providers, models]);
2795
+ const filtered = react.useMemo(() => {
2796
+ if (!search) return items;
2797
+ const q = search.toLowerCase();
2798
+ return items.filter(
2799
+ (item) => item.label.toLowerCase().includes(q) || item.sublabel && item.sublabel.toLowerCase().includes(q)
2800
+ );
2801
+ }, [items, search]);
2802
+ react.useEffect(() => {
2803
+ setHighlightIndex(0);
2804
+ }, [filtered.length]);
2805
+ react.useEffect(() => {
2806
+ if (!open) return;
2807
+ const handler = (e) => {
2808
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
2809
+ setOpen(false);
2810
+ }
2811
+ };
2812
+ document.addEventListener("mousedown", handler);
2813
+ return () => document.removeEventListener("mousedown", handler);
2814
+ }, [open]);
2815
+ const triggerLabel = react.useMemo(() => {
2816
+ if (isProviderMode && activeProviderId) {
2817
+ const p = providers.find((prov) => prov.id === activeProviderId);
2818
+ return p ? p.label : placeholder;
2819
+ }
2820
+ if (!isProviderMode && selectedModel) {
2821
+ const m = models.find((mod) => mod.id === selectedModel);
2822
+ return m ? m.name : selectedModel;
2823
+ }
2824
+ return placeholder;
2825
+ }, [isProviderMode, activeProviderId, providers, selectedModel, models, placeholder]);
2826
+ const handleToggle = react.useCallback(() => {
2827
+ setOpen((prev) => {
2828
+ if (!prev) {
2829
+ setSearch("");
2830
+ setHighlightIndex(0);
2831
+ }
2832
+ return !prev;
2833
+ });
2834
+ }, []);
2835
+ const handleSelect = react.useCallback(
2836
+ (item) => {
2837
+ if (item.type === "provider" && onSelectProvider) {
2838
+ onSelectProvider(item.id);
2839
+ } else if (item.type === "model" && onSelectModel) {
2840
+ onSelectModel(item.id);
2841
+ }
2842
+ setOpen(false);
2843
+ setSearch("");
2844
+ },
2845
+ [onSelectProvider, onSelectModel]
2846
+ );
2847
+ const handleSearchChange = react.useCallback((e) => {
2848
+ setSearch(e.target.value);
2849
+ }, []);
2850
+ const handleKeyDown = react.useCallback(
2851
+ (e) => {
2852
+ if (e.key === "ArrowDown") {
2853
+ e.preventDefault();
2854
+ setHighlightIndex((prev) => Math.min(prev + 1, filtered.length - 1));
2855
+ } else if (e.key === "ArrowUp") {
2856
+ e.preventDefault();
2857
+ setHighlightIndex((prev) => Math.max(prev - 1, 0));
2858
+ } else if (e.key === "Enter") {
2859
+ e.preventDefault();
2860
+ if (filtered[highlightIndex]) {
2861
+ handleSelect(filtered[highlightIndex]);
2862
+ }
2863
+ } else if (e.key === "Escape") {
2864
+ e.preventDefault();
2865
+ setOpen(false);
2866
+ }
2867
+ },
2868
+ [filtered, highlightIndex, handleSelect]
2869
+ );
2870
+ const children = [];
2871
+ children.push(
2872
+ react.createElement(
2873
+ "button",
2874
+ {
2875
+ key: "trigger",
2876
+ type: "button",
2877
+ "data-pms-trigger": "true",
2878
+ onClick: handleToggle
2879
+ },
2880
+ triggerLabel
2881
+ )
2882
+ );
2883
+ if (open) {
2884
+ const dropdownChildren = [];
2885
+ if (items.length > 3) {
2886
+ dropdownChildren.push(
2887
+ react.createElement("input", {
2888
+ key: "search",
2889
+ "data-pms-search": "true",
2890
+ value: search,
2891
+ onChange: handleSearchChange,
2892
+ onKeyDown: handleKeyDown,
2893
+ placeholder: isProviderMode ? "Search providers..." : "Search models...",
2894
+ autoFocus: true
2895
+ })
2896
+ );
2897
+ }
2898
+ filtered.forEach((item, idx) => {
2899
+ const isActive = isProviderMode ? item.id === activeProviderId : item.id === selectedModel;
2900
+ const isHighlighted = idx === highlightIndex;
2901
+ const attrs = {
2902
+ key: item.id,
2903
+ "data-pms-item": "true",
2904
+ "data-pms-type": item.type,
2905
+ onClick: () => handleSelect(item)
2906
+ };
2907
+ if (item.tier) attrs["data-tier"] = item.tier;
2908
+ if (isActive) attrs["data-pms-active"] = "true";
2909
+ if (isHighlighted) attrs["data-pms-highlighted"] = "true";
2910
+ const itemChildren = [
2911
+ react.createElement("span", { key: "label", "data-pms-label": "true" }, item.label)
2912
+ ];
2913
+ if (item.sublabel) {
2914
+ itemChildren.push(
2915
+ react.createElement("span", { key: "sub", "data-pms-sublabel": "true" }, item.sublabel)
2916
+ );
2917
+ }
2918
+ dropdownChildren.push(react.createElement("div", attrs, ...itemChildren));
2919
+ });
2920
+ if (onSettingsClick) {
2921
+ dropdownChildren.push(
2922
+ react.createElement(
2923
+ "button",
2924
+ {
2925
+ key: "settings",
2926
+ type: "button",
2927
+ "data-pms-settings": "true",
2928
+ onClick: (e) => {
2929
+ e.stopPropagation();
2930
+ setOpen(false);
2931
+ onSettingsClick();
2932
+ }
2933
+ },
2934
+ "\u2699 Settings"
2935
+ )
2936
+ );
2937
+ }
2699
2938
  children.push(
2700
2939
  react.createElement(
2701
2940
  "div",
2702
- { key: "error", "data-auth-error-display": "true" },
2703
- auth.error.message
2941
+ { key: "dropdown", "data-pms-dropdown": "true", onKeyDown: handleKeyDown },
2942
+ ...dropdownChildren
2704
2943
  )
2705
2944
  );
2706
2945
  }
2707
2946
  return react.createElement(
2708
2947
  "div",
2709
- { "data-auth-dialog": "true", className },
2948
+ {
2949
+ "data-provider-model-selector": "true",
2950
+ "data-pms-mode": isProviderMode ? "provider" : "model",
2951
+ className,
2952
+ ref: containerRef
2953
+ },
2710
2954
  ...children
2711
2955
  );
2712
2956
  }
2957
+ var BACKENDS = [
2958
+ { id: "copilot", label: "GitHub Copilot" },
2959
+ { id: "claude", label: "Claude" },
2960
+ { id: "vercel-ai", label: "Vercel AI" }
2961
+ ];
2962
+ var AUTH_FORMS = {
2963
+ copilot: CopilotAuthForm,
2964
+ claude: ClaudeAuthForm,
2965
+ "vercel-ai": VercelAIAuthForm
2966
+ };
2967
+ function ProviderSettings({
2968
+ providers,
2969
+ onClose,
2970
+ onProviderCreated,
2971
+ onProviderDeleted,
2972
+ onProviderUpdated,
2973
+ onAuthCompleted,
2974
+ authBaseUrl = "/api/auth",
2975
+ className
2976
+ }) {
2977
+ const [view, setView] = react.useState("list");
2978
+ const [addStep, setAddStep] = react.useState("select-backend");
2979
+ const [selectedBackend, setSelectedBackend] = react.useState("copilot");
2980
+ const [editingId, setEditingId] = react.useState(null);
2981
+ const [availableModels, setAvailableModels] = react.useState([]);
2982
+ const modelRef = react.useRef(null);
2983
+ const labelRef = react.useRef(null);
2984
+ const editModelRef = react.useRef(null);
2985
+ const editLabelRef = react.useRef(null);
2986
+ const runtime = useChatRuntime();
2987
+ const auth = useRemoteAuth({
2988
+ backend: selectedBackend,
2989
+ baseUrl: authBaseUrl
2990
+ });
2991
+ const handleBackendSelect = react.useCallback((backend) => {
2992
+ setSelectedBackend(backend);
2993
+ auth.reset();
2994
+ setAddStep("auth");
2995
+ }, [auth]);
2996
+ const handleAuthComplete = react.useCallback(() => {
2997
+ onAuthCompleted?.(selectedBackend);
2998
+ setAddStep("configure");
2999
+ }, [selectedBackend, onAuthCompleted]);
3000
+ react.useEffect(() => {
3001
+ if (addStep !== "configure" && view !== "edit") return;
3002
+ const load = async () => {
3003
+ try {
3004
+ const models = await runtime.listModels();
3005
+ setAvailableModels(models);
3006
+ } catch {
3007
+ }
3008
+ };
3009
+ load();
3010
+ }, [addStep, view, runtime]);
3011
+ const handleCreate = react.useCallback(() => {
3012
+ const modelEl = modelRef.current;
3013
+ const model = modelEl?.value?.trim();
3014
+ const label = labelRef.current?.value?.trim() || model;
3015
+ if (!model) return;
3016
+ const existing = providers.find((p) => p.backend === selectedBackend);
3017
+ if (existing) {
3018
+ onProviderUpdated?.(existing.id, { model, label });
3019
+ } else {
3020
+ onProviderCreated?.({ backend: selectedBackend, model, label });
3021
+ }
3022
+ setView("list");
3023
+ setAddStep("select-backend");
3024
+ setAvailableModels([]);
3025
+ auth.reset();
3026
+ }, [selectedBackend, providers, onProviderCreated, onProviderUpdated, auth]);
3027
+ const handleEdit = react.useCallback((id) => {
3028
+ setEditingId(id);
3029
+ setView("edit");
3030
+ }, []);
3031
+ const handleUpdate = react.useCallback(() => {
3032
+ if (!editingId) return;
3033
+ const modelEl = editModelRef.current;
3034
+ const model = modelEl?.value?.trim();
3035
+ const label = editLabelRef.current?.value?.trim();
3036
+ if (!model && !label) return;
3037
+ const changes = {};
3038
+ if (model) changes.model = model;
3039
+ if (label) changes.label = label;
3040
+ onProviderUpdated?.(editingId, changes);
3041
+ setView("list");
3042
+ setEditingId(null);
3043
+ }, [editingId, onProviderUpdated]);
3044
+ const handleDelete = react.useCallback((id) => {
3045
+ onProviderDeleted?.(id);
3046
+ }, [onProviderDeleted]);
3047
+ const handleStartAdd = react.useCallback(() => {
3048
+ setView("add");
3049
+ setAddStep("select-backend");
3050
+ auth.reset();
3051
+ }, [auth]);
3052
+ if (view === "list") {
3053
+ const items = providers.map(
3054
+ (p) => react.createElement(
3055
+ "div",
3056
+ { key: p.id, "data-provider-settings-item": "true" },
3057
+ react.createElement("span", { "data-provider-settings-label": "true" }, p.label),
3058
+ react.createElement("span", { "data-provider-settings-model": "true" }, `${p.backend} / ${p.model}`),
3059
+ react.createElement(
3060
+ "div",
3061
+ { "data-provider-settings-actions": "true" },
3062
+ react.createElement("button", {
3063
+ type: "button",
3064
+ "data-action": "edit-provider",
3065
+ onClick: () => handleEdit(p.id)
3066
+ }, "Edit"),
3067
+ react.createElement("button", {
3068
+ type: "button",
3069
+ "data-action": "delete-provider",
3070
+ onClick: () => handleDelete(p.id)
3071
+ }, "Delete")
3072
+ )
3073
+ )
3074
+ );
3075
+ return react.createElement(
3076
+ "div",
3077
+ { "data-provider-settings": "true", className },
3078
+ react.createElement(
3079
+ "div",
3080
+ { "data-provider-settings-header": "true" },
3081
+ react.createElement("span", null, "Providers"),
3082
+ onClose ? react.createElement("button", {
3083
+ type: "button",
3084
+ "data-provider-settings-close": "true",
3085
+ onClick: onClose
3086
+ }, "\u2715") : null
3087
+ ),
3088
+ react.createElement(
3089
+ "div",
3090
+ { "data-provider-settings-list": "true" },
3091
+ ...items,
3092
+ items.length === 0 ? react.createElement("div", { "data-provider-settings-empty": "true" }, "No providers configured") : null
3093
+ ),
3094
+ react.createElement("button", {
3095
+ type: "button",
3096
+ "data-action": "add-provider",
3097
+ onClick: handleStartAdd
3098
+ }, "+ Add Provider")
3099
+ );
3100
+ }
3101
+ if (view === "add") {
3102
+ const formChildren = [];
3103
+ formChildren.push(
3104
+ react.createElement(
3105
+ "div",
3106
+ { "data-provider-settings-header": "true", key: "header" },
3107
+ react.createElement("span", null, "Add Provider"),
3108
+ react.createElement("button", {
3109
+ type: "button",
3110
+ "data-provider-settings-close": "true",
3111
+ onClick: () => {
3112
+ setView("list");
3113
+ setAddStep("select-backend");
3114
+ }
3115
+ }, "\u2190 Back")
3116
+ )
3117
+ );
3118
+ if (addStep === "select-backend") {
3119
+ formChildren.push(
3120
+ react.createElement(
3121
+ "div",
3122
+ { key: "backends", "data-provider-settings-backends": "true" },
3123
+ ...BACKENDS.map(
3124
+ (b) => react.createElement("button", {
3125
+ key: b.id,
3126
+ type: "button",
3127
+ "data-provider-backend-option": b.id,
3128
+ onClick: () => handleBackendSelect(b.id)
3129
+ }, b.label)
3130
+ )
3131
+ )
3132
+ );
3133
+ } else if (addStep === "auth") {
3134
+ const FormComponent = selectedBackend ? AUTH_FORMS[selectedBackend] : null;
3135
+ formChildren.push(
3136
+ react.createElement(
3137
+ "div",
3138
+ { key: "auth", "data-provider-settings-auth": "true" },
3139
+ FormComponent ? react.createElement(FormComponent, { auth, onAuthComplete: handleAuthComplete }) : null
3140
+ )
3141
+ );
3142
+ } else if (addStep === "configure") {
3143
+ const modelInput = availableModels.length > 0 ? react.createElement(
3144
+ "select",
3145
+ {
3146
+ ref: modelRef,
3147
+ "data-input": "model",
3148
+ defaultValue: ""
3149
+ },
3150
+ react.createElement("option", { value: "", disabled: true }, "Select a model\u2026"),
3151
+ ...availableModels.map(
3152
+ (m) => react.createElement("option", { key: m.id, value: m.id }, m.name || m.id)
3153
+ )
3154
+ ) : react.createElement("input", {
3155
+ ref: modelRef,
3156
+ placeholder: "Model name (e.g. gpt-5-mini)",
3157
+ "data-input": "model"
3158
+ });
3159
+ formChildren.push(
3160
+ react.createElement(
3161
+ "div",
3162
+ { key: "config", "data-provider-settings-form": "true" },
3163
+ modelInput,
3164
+ react.createElement("input", {
3165
+ ref: labelRef,
3166
+ placeholder: "Display label (e.g. GPT-5 Mini)",
3167
+ "data-input": "label"
3168
+ }),
3169
+ react.createElement("button", {
3170
+ type: "button",
3171
+ "data-action": "save-provider",
3172
+ onClick: handleCreate
3173
+ }, "Save Provider")
3174
+ )
3175
+ );
3176
+ }
3177
+ return react.createElement(
3178
+ "div",
3179
+ { "data-provider-settings": "true", className },
3180
+ ...formChildren
3181
+ );
3182
+ }
3183
+ const editingProvider = providers.find((p) => p.id === editingId);
3184
+ return react.createElement(
3185
+ "div",
3186
+ { "data-provider-settings": "true", className },
3187
+ react.createElement(
3188
+ "div",
3189
+ { "data-provider-settings-header": "true" },
3190
+ react.createElement("span", null, "Edit Provider"),
3191
+ react.createElement("button", {
3192
+ type: "button",
3193
+ "data-provider-settings-close": "true",
3194
+ onClick: () => {
3195
+ setView("list");
3196
+ setEditingId(null);
3197
+ }
3198
+ }, "\u2190 Back")
3199
+ ),
3200
+ react.createElement(
3201
+ "div",
3202
+ { "data-provider-settings-form": "true" },
3203
+ availableModels.length > 0 ? react.createElement(
3204
+ "select",
3205
+ {
3206
+ ref: editModelRef,
3207
+ defaultValue: editingProvider?.model ?? "",
3208
+ "data-input": "model"
3209
+ },
3210
+ react.createElement("option", { value: "", disabled: true }, "Select a model\u2026"),
3211
+ ...availableModels.map(
3212
+ (m) => react.createElement("option", { key: m.id, value: m.id }, m.name || m.id)
3213
+ )
3214
+ ) : react.createElement("input", {
3215
+ ref: editModelRef,
3216
+ defaultValue: editingProvider?.model ?? "",
3217
+ placeholder: "Model name",
3218
+ "data-input": "model"
3219
+ }),
3220
+ react.createElement("input", {
3221
+ ref: editLabelRef,
3222
+ defaultValue: editingProvider?.label ?? "",
3223
+ placeholder: "Display label",
3224
+ "data-input": "label"
3225
+ }),
3226
+ react.createElement("button", {
3227
+ type: "button",
3228
+ "data-action": "update-provider",
3229
+ onClick: handleUpdate
3230
+ }, "Update")
3231
+ )
3232
+ );
3233
+ }
3234
+ function formatTokens(n) {
3235
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
3236
+ return String(n);
3237
+ }
3238
+ function ContextStatsDisplay({ stats, className }) {
3239
+ if (!stats) return null;
3240
+ const hasRealData = stats.realPromptTokens != null && stats.modelContextWindow != null;
3241
+ if (!hasRealData) return null;
3242
+ const promptTokens = stats.realPromptTokens;
3243
+ const contextWindow = stats.modelContextWindow;
3244
+ const availableBudget = Math.max(0, contextWindow - promptTokens);
3245
+ const usagePercent = contextWindow > 0 ? Math.round(promptTokens / contextWindow * 100) : 0;
3246
+ return react.createElement(
3247
+ "div",
3248
+ {
3249
+ "data-context-stats": "",
3250
+ "data-context-truncated": stats.wasTruncated ? "true" : "false",
3251
+ className
3252
+ },
3253
+ react.createElement(
3254
+ "span",
3255
+ { "data-context-tokens": "" },
3256
+ `${formatTokens(promptTokens)} tokens`
3257
+ ),
3258
+ react.createElement(
3259
+ "span",
3260
+ { "data-context-budget": "" },
3261
+ `${formatTokens(availableBudget)} available`
3262
+ ),
3263
+ react.createElement(
3264
+ "span",
3265
+ { "data-context-usage": "", "data-usage-percent": String(usagePercent) },
3266
+ `${usagePercent}%`
3267
+ ),
3268
+ stats.removedCount > 0 ? react.createElement(
3269
+ "span",
3270
+ { "data-context-removed": "" },
3271
+ `${stats.removedCount} trimmed`
3272
+ ) : null
3273
+ );
3274
+ }
3275
+ function ChatLayout({ children, sidebar, overlay, className }) {
3276
+ const overlayNodes = Array.isArray(overlay) ? overlay : [overlay ?? null];
3277
+ return react.createElement(
3278
+ "div",
3279
+ { "data-chat-ui": "", className },
3280
+ ...overlayNodes,
3281
+ sidebar ?? null,
3282
+ children
3283
+ );
3284
+ }
3285
+ function ChatHeader({
3286
+ showBackendSelector = false,
3287
+ showModelSelector = true,
3288
+ hasProviders = false,
3289
+ backends = [],
3290
+ models = [],
3291
+ selectedModel,
3292
+ onBackendSelect,
3293
+ onModelSelect,
3294
+ BackendSelectorComponent: BSC = BackendSelector,
3295
+ ModelSelectorComponent: MSC = ModelSelector
3296
+ }) {
3297
+ const children = [];
3298
+ if (showBackendSelector) {
3299
+ children.push(
3300
+ react.createElement(BSC, {
3301
+ key: "backend-selector",
3302
+ backends,
3303
+ onSelect: onBackendSelect ?? (() => {
3304
+ })
3305
+ })
3306
+ );
3307
+ }
3308
+ if (showModelSelector && !hasProviders && models.length > 0) {
3309
+ children.push(
3310
+ react.createElement(MSC, {
3311
+ key: "model-selector",
3312
+ models,
3313
+ selectedModel,
3314
+ onSelect: onModelSelect ?? (() => {
3315
+ })
3316
+ })
3317
+ );
3318
+ }
3319
+ if (children.length === 0) return null;
3320
+ return react.createElement("div", { "data-chat-header": "" }, ...children);
3321
+ }
3322
+ function UsageBadge({ usage, className }) {
3323
+ if (!usage) return null;
3324
+ return react.createElement(
3325
+ "span",
3326
+ { "data-usage-badge": "true", className },
3327
+ react.createElement("span", { "data-usage-tokens": "prompt" }, `\u2191${usage.promptTokens}`),
3328
+ react.createElement("span", { "data-usage-tokens": "completion" }, `\u2193${usage.completionTokens}`),
3329
+ react.createElement("span", { "data-usage-tokens": "total" }, `\u03A3${usage.totalTokens}`)
3330
+ );
3331
+ }
3332
+
3333
+ // src/chat/react/ChatInputArea.ts
3334
+ function ChatInputArea({
3335
+ onSend,
3336
+ onStop,
3337
+ isGenerating,
3338
+ placeholder,
3339
+ providers = [],
3340
+ models = [],
3341
+ activeProviderId,
3342
+ selectedModel,
3343
+ onSelectProvider,
3344
+ onSelectModel,
3345
+ onSettingsClick,
3346
+ ComposerComponent: CC = Composer,
3347
+ ProviderModelSelectorComponent: PMSC = ProviderModelSelector,
3348
+ usage
3349
+ }) {
3350
+ const selectorRow = react.createElement(
3351
+ "div",
3352
+ { "data-chat-input-controls": "" },
3353
+ react.createElement(PMSC, {
3354
+ providers,
3355
+ models,
3356
+ activeProviderId,
3357
+ selectedModel,
3358
+ onSelectProvider,
3359
+ onSelectModel,
3360
+ onSettingsClick
3361
+ }),
3362
+ usage ? react.createElement(UsageBadge, { usage }) : null
3363
+ );
3364
+ return react.createElement(
3365
+ "div",
3366
+ { "data-chat-input-area": "" },
3367
+ react.createElement(
3368
+ "div",
3369
+ { "data-chat-input-container": "" },
3370
+ selectorRow,
3371
+ react.createElement(CC, {
3372
+ onSend,
3373
+ onStop,
3374
+ isGenerating,
3375
+ placeholder
3376
+ })
3377
+ )
3378
+ );
3379
+ }
3380
+ var FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
3381
+ var CLOSE_ANIMATION_MS = 150;
3382
+ function ChatSettingsOverlay({
3383
+ open,
3384
+ onClose,
3385
+ providers = [],
3386
+ authBaseUrl,
3387
+ onProviderCreated,
3388
+ onProviderDeleted,
3389
+ onProviderUpdated,
3390
+ onAuthCompleted,
3391
+ ProviderSettingsComponent: PSC = ProviderSettings
3392
+ }) {
3393
+ const [isClosing, setIsClosing] = react.useState(false);
3394
+ const contentRef = react.useRef(null);
3395
+ const previousFocusRef = react.useRef(null);
3396
+ const handleClose = react.useCallback(() => {
3397
+ setIsClosing(true);
3398
+ setTimeout(() => {
3399
+ setIsClosing(false);
3400
+ onClose();
3401
+ }, CLOSE_ANIMATION_MS);
3402
+ }, [onClose]);
3403
+ const handleKeyDown = react.useCallback((e) => {
3404
+ if (e.key === "Escape") {
3405
+ e.stopPropagation();
3406
+ handleClose();
3407
+ return;
3408
+ }
3409
+ if (e.key === "Tab" && contentRef.current) {
3410
+ const focusable = Array.from(contentRef.current.querySelectorAll(FOCUSABLE));
3411
+ if (focusable.length === 0) return;
3412
+ const first = focusable[0];
3413
+ const last = focusable[focusable.length - 1];
3414
+ if (e.shiftKey && document.activeElement === first) {
3415
+ e.preventDefault();
3416
+ last.focus();
3417
+ } else if (!e.shiftKey && document.activeElement === last) {
3418
+ e.preventDefault();
3419
+ first.focus();
3420
+ }
3421
+ }
3422
+ }, [handleClose]);
3423
+ const handleBackdropClick = react.useCallback((e) => {
3424
+ if (e.target === e.currentTarget) handleClose();
3425
+ }, [handleClose]);
3426
+ react.useEffect(() => {
3427
+ if (!open) return;
3428
+ previousFocusRef.current = document.activeElement;
3429
+ document.addEventListener("keydown", handleKeyDown);
3430
+ const timer = setTimeout(() => {
3431
+ if (contentRef.current) {
3432
+ const first = contentRef.current.querySelector(FOCUSABLE);
3433
+ if (first) first.focus();
3434
+ }
3435
+ }, 50);
3436
+ return () => {
3437
+ document.removeEventListener("keydown", handleKeyDown);
3438
+ clearTimeout(timer);
3439
+ const prev = previousFocusRef.current;
3440
+ if (prev && typeof prev.focus === "function") prev.focus();
3441
+ };
3442
+ }, [open, handleKeyDown]);
3443
+ if (!open && !isClosing) return null;
3444
+ return react.createElement(
3445
+ "div",
3446
+ {
3447
+ "data-provider-settings-overlay": "true",
3448
+ "data-closing": isClosing ? "true" : void 0,
3449
+ onClick: handleBackdropClick,
3450
+ role: "dialog",
3451
+ "aria-modal": "true",
3452
+ "aria-label": "Provider settings"
3453
+ },
3454
+ react.createElement(
3455
+ "div",
3456
+ {
3457
+ ref: contentRef,
3458
+ "data-provider-settings-content": "true",
3459
+ "data-closing": isClosing ? "true" : void 0
3460
+ },
3461
+ react.createElement(PSC, {
3462
+ providers,
3463
+ authBaseUrl,
3464
+ onClose: handleClose,
3465
+ onProviderCreated: onProviderCreated ?? void 0,
3466
+ onProviderDeleted: onProviderDeleted ?? void 0,
3467
+ onProviderUpdated: onProviderUpdated ?? void 0,
3468
+ onAuthCompleted: onAuthCompleted ?? void 0
3469
+ })
3470
+ )
3471
+ );
3472
+ }
3473
+
3474
+ // src/chat/react/ChatUI.ts
3475
+ function ChatUIInner({
3476
+ slots,
3477
+ className,
3478
+ showSidebar = true,
3479
+ showModelSelector = true,
3480
+ showBackendSelector: showBackendSelectorProp,
3481
+ showProviderSelector: _showProviderSelectorProp,
3482
+ authBaseUrl,
3483
+ placeholder
3484
+ }) {
3485
+ const runtime = useChatRuntime();
3486
+ const { messages, sendMessage, stop, isGenerating, newSession, error, clearError, retryLastMessage, usage } = useChat();
3487
+ const { sessions } = useSessions();
3488
+ const { models, refresh: refreshModels } = useModels();
3489
+ const { backends } = useBackends();
3490
+ const { providers, createProvider, updateProvider, deleteProvider, selectProvider, refresh } = useProviders();
3491
+ const [settingsOpen, setSettingsOpen] = react.useState(false);
3492
+ const [activeProviderId, setActiveProviderId] = react.useState(void 0);
3493
+ const [selectedModelId, setSelectedModelId] = react.useState(void 0);
3494
+ const [searchQuery, setSearchQuery] = react.useState("");
3495
+ const hasProviders = providers.length > 0;
3496
+ react.useEffect(() => {
3497
+ if (providers.length > 0 && !activeProviderId) {
3498
+ setActiveProviderId(providers[0].id);
3499
+ selectProvider(providers[0].id);
3500
+ }
3501
+ }, [providers, activeProviderId, selectProvider]);
3502
+ const showBackendSelectorResolved = showBackendSelectorProp ?? false;
3503
+ const handleSelect = react.useCallback(async (id) => {
3504
+ try {
3505
+ await runtime.switchSession(id);
3506
+ } catch {
3507
+ }
3508
+ }, [runtime]);
3509
+ const handleCreate = react.useCallback(async () => {
3510
+ try {
3511
+ await newSession();
3512
+ } catch {
3513
+ }
3514
+ }, [newSession]);
3515
+ const handleDelete = react.useCallback(async (id) => {
3516
+ try {
3517
+ await runtime.deleteSession(id);
3518
+ } catch {
3519
+ }
3520
+ }, [runtime]);
3521
+ const handleModelSelect = react.useCallback((modelId) => {
3522
+ setSelectedModelId(modelId);
3523
+ }, []);
3524
+ const handleBackendSelect = react.useCallback((_name) => {
3525
+ refreshModels();
3526
+ }, [refreshModels]);
3527
+ const handleProviderSelect = react.useCallback((id) => {
3528
+ selectProvider(id);
3529
+ setActiveProviderId(id);
3530
+ }, [selectProvider]);
3531
+ const hasSlotOverrides = !!(slots?.renderToolCall || slots?.renderMessage || slots?.renderThinkingBlock);
3532
+ const ThreadComponent = slots?.thread ?? Thread;
3533
+ const ThreadListComponent = slots?.threadList ?? ThreadList;
3534
+ const ContextStatsComponent = slots?.contextStats ?? ContextStatsDisplay;
3535
+ const [contextStats, setContextStats] = react.useState(null);
3536
+ react.useEffect(() => {
3537
+ if (!runtime.activeSessionId) {
3538
+ setContextStats(null);
3539
+ return;
3540
+ }
3541
+ const result = runtime.getContextStats(runtime.activeSessionId);
3542
+ if (result instanceof Promise) {
3543
+ result.then(setContextStats, () => setContextStats(null));
3544
+ } else {
3545
+ setContextStats(result);
3546
+ }
3547
+ }, [runtime, runtime.activeSessionId, messages.length]);
3548
+ const showEmptyState = !hasProviders && messages.length === 0;
3549
+ const mainContent = react.createElement(
3550
+ "div",
3551
+ { "data-chat-main": "" },
3552
+ react.createElement(ChatHeader, {
3553
+ showBackendSelector: showBackendSelectorResolved,
3554
+ showModelSelector,
3555
+ hasProviders,
3556
+ backends,
3557
+ models,
3558
+ selectedModel: selectedModelId,
3559
+ onBackendSelect: handleBackendSelect,
3560
+ onModelSelect: handleModelSelect,
3561
+ BackendSelectorComponent: slots?.backendSelector,
3562
+ ModelSelectorComponent: slots?.modelSelector
3563
+ }),
3564
+ contextStats ? react.createElement(ContextStatsComponent, { stats: contextStats }) : null,
3565
+ error ? react.createElement(
3566
+ "div",
3567
+ { "data-chat-error": "" },
3568
+ react.createElement("span", { "data-chat-error-text": "" }, error.message),
3569
+ react.createElement(
3570
+ "div",
3571
+ { "data-chat-error-actions": "" },
3572
+ react.createElement("button", {
3573
+ "data-action": "retry",
3574
+ type: "button",
3575
+ onClick: retryLastMessage
3576
+ }, "Retry"),
3577
+ react.createElement("button", {
3578
+ "data-action": "dismiss-error",
3579
+ type: "button",
3580
+ onClick: clearError
3581
+ }, "\u2715")
3582
+ ),
3583
+ error.stack ? react.createElement(
3584
+ "details",
3585
+ { "data-chat-error-details": "" },
3586
+ react.createElement("summary", null, "Details"),
3587
+ react.createElement("pre", null, error.stack)
3588
+ ) : null
3589
+ ) : null,
3590
+ showEmptyState ? react.createElement(
3591
+ "div",
3592
+ { "data-chat-empty-state": "" },
3593
+ react.createElement("div", { "data-chat-empty-title": "" }, "Connect a provider to start chatting"),
3594
+ react.createElement("button", {
3595
+ "data-action": "open-settings",
3596
+ onClick: () => setSettingsOpen(true)
3597
+ }, "+ Connect Provider")
3598
+ ) : react.createElement(ThreadComponent, { messages, isGenerating, autoScroll: true }),
3599
+ react.createElement(ChatInputArea, {
3600
+ onSend: sendMessage,
3601
+ onStop: stop,
3602
+ isGenerating,
3603
+ placeholder,
3604
+ providers,
3605
+ models,
3606
+ activeProviderId,
3607
+ selectedModel: selectedModelId,
3608
+ onSelectProvider: handleProviderSelect,
3609
+ onSelectModel: handleModelSelect,
3610
+ onSettingsClick: () => setSettingsOpen(true),
3611
+ ComposerComponent: slots?.composer,
3612
+ ProviderModelSelectorComponent: slots?.providerModelSelector,
3613
+ usage
3614
+ })
3615
+ );
3616
+ const wrappedMain = hasSlotOverrides ? react.createElement(ThreadProvider, {
3617
+ renderToolCall: slots.renderToolCall,
3618
+ renderMessage: slots.renderMessage,
3619
+ renderThinkingBlock: slots.renderThinkingBlock,
3620
+ children: mainContent
3621
+ }) : mainContent;
3622
+ const sidebar = showSidebar ? react.createElement(ThreadListComponent, {
3623
+ sessions,
3624
+ activeSessionId: runtime.activeSessionId ?? void 0,
3625
+ onSelect: handleSelect,
3626
+ onCreate: handleCreate,
3627
+ onDelete: handleDelete,
3628
+ searchQuery,
3629
+ onSearchChange: setSearchQuery
3630
+ }) : void 0;
3631
+ const settingsOverlay = react.createElement(ChatSettingsOverlay, {
3632
+ open: settingsOpen,
3633
+ onClose: () => setSettingsOpen(false),
3634
+ providers,
3635
+ authBaseUrl,
3636
+ onProviderCreated: (p) => createProvider({ backend: p.backend, model: p.model, label: p.label ?? "" }),
3637
+ onProviderDeleted: (id) => deleteProvider(id),
3638
+ onProviderUpdated: (id, changes) => updateProvider(id, changes),
3639
+ onAuthCompleted: () => refresh(),
3640
+ ProviderSettingsComponent: slots?.providerSettings
3641
+ });
3642
+ return react.createElement(ChatLayout, {
3643
+ className,
3644
+ sidebar,
3645
+ overlay: [slots?.authDialog ?? null, settingsOverlay],
3646
+ children: wrappedMain
3647
+ });
3648
+ }
3649
+ function ChatUI({ runtime, ...rest }) {
3650
+ return react.createElement(ChatProvider, {
3651
+ runtime,
3652
+ children: react.createElement(ChatUIInner, rest)
3653
+ });
3654
+ }
3655
+ function PermissionDialog({
3656
+ requests,
3657
+ onApprove,
3658
+ onDeny,
3659
+ onApproveAll,
3660
+ onDenyAll,
3661
+ renderArgs,
3662
+ className
3663
+ }) {
3664
+ if (requests.length === 0) return null;
3665
+ const items = requests.map(
3666
+ (req) => react.createElement(
3667
+ "div",
3668
+ {
3669
+ key: req.toolCallId,
3670
+ "data-permission-request": "true",
3671
+ "data-tool-name": req.toolName
3672
+ },
3673
+ // Tool name
3674
+ react.createElement(
3675
+ "div",
3676
+ { "data-permission-tool-name": "true" },
3677
+ req.toolName
3678
+ ),
3679
+ // Arguments display
3680
+ react.createElement(
3681
+ "div",
3682
+ { "data-permission-tool-args": "true" },
3683
+ renderArgs ? renderArgs(req.toolArgs, req.toolName) : react.createElement("pre", null, JSON.stringify(req.toolArgs, null, 2))
3684
+ ),
3685
+ // Action buttons
3686
+ react.createElement(
3687
+ "div",
3688
+ { "data-permission-actions": "true" },
3689
+ react.createElement(
3690
+ "button",
3691
+ {
3692
+ type: "button",
3693
+ "data-action": "approve",
3694
+ onClick: () => onApprove(req.toolCallId),
3695
+ "aria-label": `Approve ${req.toolName}`
3696
+ },
3697
+ "Allow"
3698
+ ),
3699
+ react.createElement(
3700
+ "button",
3701
+ {
3702
+ type: "button",
3703
+ "data-action": "deny",
3704
+ onClick: () => onDeny(req.toolCallId),
3705
+ "aria-label": `Deny ${req.toolName}`
3706
+ },
3707
+ "Deny"
3708
+ )
3709
+ )
3710
+ )
3711
+ );
3712
+ const bulkActions = requests.length > 1 && (onApproveAll || onDenyAll) ? react.createElement(
3713
+ "div",
3714
+ { "data-permission-bulk-actions": "true" },
3715
+ onApproveAll ? react.createElement(
3716
+ "button",
3717
+ {
3718
+ type: "button",
3719
+ "data-action": "approve-all",
3720
+ onClick: onApproveAll,
3721
+ "aria-label": "Approve all tool calls"
3722
+ },
3723
+ "Allow All"
3724
+ ) : null,
3725
+ onDenyAll ? react.createElement(
3726
+ "button",
3727
+ {
3728
+ type: "button",
3729
+ "data-action": "deny-all",
3730
+ onClick: onDenyAll,
3731
+ "aria-label": "Deny all tool calls"
3732
+ },
3733
+ "Deny All"
3734
+ ) : null
3735
+ ) : null;
3736
+ return react.createElement(
3737
+ "div",
3738
+ {
3739
+ "data-permission-dialog": "true",
3740
+ role: "dialog",
3741
+ "aria-label": "Tool permission requests",
3742
+ className
3743
+ },
3744
+ ...items,
3745
+ bulkActions
3746
+ );
3747
+ }
2713
3748
 
2714
- exports.AuthDialog = AuthDialog;
3749
+ exports.BackendSelector = BackendSelector;
3750
+ exports.ChatHeader = ChatHeader;
3751
+ exports.ChatInputArea = ChatInputArea;
3752
+ exports.ChatLayout = ChatLayout;
2715
3753
  exports.ChatProvider = ChatProvider;
3754
+ exports.ChatSettingsOverlay = ChatSettingsOverlay;
3755
+ exports.ChatUI = ChatUI;
3756
+ exports.ClaudeAuthForm = ClaudeAuthForm;
2716
3757
  exports.Composer = Composer;
3758
+ exports.ContextStatsDisplay = ContextStatsDisplay;
3759
+ exports.CopilotAuthForm = CopilotAuthForm;
2717
3760
  exports.MarkdownRenderer = MarkdownRenderer;
2718
3761
  exports.Message = Message;
2719
3762
  exports.ModelSelector = ModelSelector;
2720
- exports.RemoteChatRuntime = RemoteChatRuntime;
3763
+ exports.PermissionDialog = PermissionDialog;
3764
+ exports.ProviderModelSelector = ProviderModelSelector;
3765
+ exports.ProviderSelector = ProviderSelector;
3766
+ exports.ProviderSettings = ProviderSettings;
3767
+ exports.RemoteChatClient = RemoteChatClient;
2721
3768
  exports.ThinkingBlock = ThinkingBlock;
2722
3769
  exports.Thread = Thread;
2723
3770
  exports.ThreadList = ThreadList;
2724
3771
  exports.ThreadProvider = ThreadProvider;
2725
3772
  exports.ToolCallView = ToolCallView;
2726
- exports.useAuth = useAuth;
3773
+ exports.UsageBadge = UsageBadge;
3774
+ exports.VercelAIAuthForm = VercelAIAuthForm;
3775
+ exports.useApiKeyAuth = useApiKeyAuth;
3776
+ exports.useBackends = useBackends;
2727
3777
  exports.useChat = useChat;
2728
3778
  exports.useChatRuntime = useChatRuntime;
3779
+ exports.useClaudeAuth = useClaudeAuth;
3780
+ exports.useCopilotAuth = useCopilotAuth;
2729
3781
  exports.useMessages = useMessages;
2730
3782
  exports.useModels = useModels;
2731
3783
  exports.useOptionalThreadSlots = useOptionalThreadSlots;
3784
+ exports.useProviders = useProviders;
2732
3785
  exports.useRemoteAuth = useRemoteAuth;
2733
3786
  exports.useRemoteChat = useRemoteChat;
2734
3787
  exports.useSSE = useSSE;
2735
3788
  exports.useSessions = useSessions;
2736
3789
  exports.useThreadSlots = useThreadSlots;
2737
3790
  exports.useToolApproval = useToolApproval;
3791
+ exports.useVirtualMessages = useVirtualMessages;
2738
3792
  //# sourceMappingURL=react.cjs.map
2739
3793
  //# sourceMappingURL=react.cjs.map