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