@witqq/agent-sdk 0.7.0 → 0.8.0

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