@witqq/agent-sdk 0.6.1 → 0.7.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 (122) hide show
  1. package/README.md +433 -6
  2. package/dist/auth/index.cjs +188 -1
  3. package/dist/auth/index.cjs.map +1 -1
  4. package/dist/auth/index.d.cts +154 -138
  5. package/dist/auth/index.d.ts +154 -138
  6. package/dist/auth/index.js +188 -2
  7. package/dist/auth/index.js.map +1 -1
  8. package/dist/backends/claude.cjs +315 -21
  9. package/dist/backends/claude.cjs.map +1 -1
  10. package/dist/backends/claude.d.cts +2 -1
  11. package/dist/backends/claude.d.ts +2 -1
  12. package/dist/backends/claude.js +315 -21
  13. package/dist/backends/claude.js.map +1 -1
  14. package/dist/backends/copilot.cjs +132 -24
  15. package/dist/backends/copilot.cjs.map +1 -1
  16. package/dist/backends/copilot.d.cts +2 -1
  17. package/dist/backends/copilot.d.ts +2 -1
  18. package/dist/backends/copilot.js +132 -24
  19. package/dist/backends/copilot.js.map +1 -1
  20. package/dist/backends/vercel-ai.cjs +56 -17
  21. package/dist/backends/vercel-ai.cjs.map +1 -1
  22. package/dist/backends/vercel-ai.d.cts +1 -1
  23. package/dist/backends/vercel-ai.d.ts +1 -1
  24. package/dist/backends/vercel-ai.js +56 -17
  25. package/dist/backends/vercel-ai.js.map +1 -1
  26. package/dist/chat/accumulator.cjs +147 -0
  27. package/dist/chat/accumulator.cjs.map +1 -0
  28. package/dist/chat/accumulator.d.cts +61 -0
  29. package/dist/chat/accumulator.d.ts +61 -0
  30. package/dist/chat/accumulator.js +145 -0
  31. package/dist/chat/accumulator.js.map +1 -0
  32. package/dist/chat/backends.cjs +3534 -0
  33. package/dist/chat/backends.cjs.map +1 -0
  34. package/dist/chat/backends.d.cts +62 -0
  35. package/dist/chat/backends.d.ts +62 -0
  36. package/dist/chat/backends.js +3501 -0
  37. package/dist/chat/backends.js.map +1 -0
  38. package/dist/chat/context.cjs +230 -0
  39. package/dist/chat/context.cjs.map +1 -0
  40. package/dist/chat/context.d.cts +167 -0
  41. package/dist/chat/context.d.ts +167 -0
  42. package/dist/chat/context.js +227 -0
  43. package/dist/chat/context.js.map +1 -0
  44. package/dist/chat/core.cjs +282 -0
  45. package/dist/chat/core.cjs.map +1 -0
  46. package/dist/chat/core.d.cts +435 -0
  47. package/dist/chat/core.d.ts +435 -0
  48. package/dist/chat/core.js +261 -0
  49. package/dist/chat/core.js.map +1 -0
  50. package/dist/chat/errors.cjs +251 -0
  51. package/dist/chat/errors.cjs.map +1 -0
  52. package/dist/chat/errors.d.cts +122 -0
  53. package/dist/chat/errors.d.ts +122 -0
  54. package/dist/chat/errors.js +243 -0
  55. package/dist/chat/errors.js.map +1 -0
  56. package/dist/chat/events.cjs +203 -0
  57. package/dist/chat/events.cjs.map +1 -0
  58. package/dist/chat/events.d.cts +241 -0
  59. package/dist/chat/events.d.ts +241 -0
  60. package/dist/chat/events.js +196 -0
  61. package/dist/chat/events.js.map +1 -0
  62. package/dist/chat/index.cjs +5359 -0
  63. package/dist/chat/index.cjs.map +1 -0
  64. package/dist/chat/index.d.cts +52 -0
  65. package/dist/chat/index.d.ts +52 -0
  66. package/dist/chat/index.js +5296 -0
  67. package/dist/chat/index.js.map +1 -0
  68. package/dist/chat/react.cjs +2739 -0
  69. package/dist/chat/react.cjs.map +1 -0
  70. package/dist/chat/react.d.cts +619 -0
  71. package/dist/chat/react.d.ts +619 -0
  72. package/dist/chat/react.js +2714 -0
  73. package/dist/chat/react.js.map +1 -0
  74. package/dist/chat/runtime.cjs +1030 -0
  75. package/dist/chat/runtime.cjs.map +1 -0
  76. package/dist/chat/runtime.d.cts +118 -0
  77. package/dist/chat/runtime.d.ts +118 -0
  78. package/dist/chat/runtime.js +1028 -0
  79. package/dist/chat/runtime.js.map +1 -0
  80. package/dist/chat/server.cjs +643 -0
  81. package/dist/chat/server.cjs.map +1 -0
  82. package/dist/chat/server.d.cts +287 -0
  83. package/dist/chat/server.d.ts +287 -0
  84. package/dist/chat/server.js +617 -0
  85. package/dist/chat/server.js.map +1 -0
  86. package/dist/chat/sessions.cjs +398 -0
  87. package/dist/chat/sessions.cjs.map +1 -0
  88. package/dist/chat/sessions.d.cts +239 -0
  89. package/dist/chat/sessions.d.ts +239 -0
  90. package/dist/chat/sessions.js +394 -0
  91. package/dist/chat/sessions.js.map +1 -0
  92. package/dist/chat/state.cjs +177 -0
  93. package/dist/chat/state.cjs.map +1 -0
  94. package/dist/chat/state.d.cts +92 -0
  95. package/dist/chat/state.d.ts +92 -0
  96. package/dist/chat/state.js +167 -0
  97. package/dist/chat/state.js.map +1 -0
  98. package/dist/chat/storage.cjs +240 -0
  99. package/dist/chat/storage.cjs.map +1 -0
  100. package/dist/chat/storage.d.cts +191 -0
  101. package/dist/chat/storage.d.ts +191 -0
  102. package/dist/chat/storage.js +236 -0
  103. package/dist/chat/storage.js.map +1 -0
  104. package/dist/errors-BDLbNu9w.d.cts +13 -0
  105. package/dist/errors-BDLbNu9w.d.ts +13 -0
  106. package/dist/in-process-transport-C2oPTYs6.d.ts +223 -0
  107. package/dist/in-process-transport-DG-w5G6k.d.cts +223 -0
  108. package/dist/index.cjs +25 -13
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +32 -4
  111. package/dist/index.d.ts +32 -4
  112. package/dist/index.js +25 -13
  113. package/dist/index.js.map +1 -1
  114. package/dist/transport-D1OaUgRk.d.ts +67 -0
  115. package/dist/transport-DX1Nhm4N.d.cts +67 -0
  116. package/dist/types-Bh5AhqD-.d.ts +141 -0
  117. package/dist/types-CGF7AEX1.d.cts +141 -0
  118. package/dist/{types-BvwNzZCj.d.cts → types-CqvUAYxt.d.cts} +21 -3
  119. package/dist/{types-BvwNzZCj.d.ts → types-CqvUAYxt.d.ts} +21 -3
  120. package/dist/types-DLZzlJxt.d.ts +39 -0
  121. package/dist/types-tE0CXwBl.d.cts +39 -0
  122. package/package.json +149 -2
@@ -0,0 +1,2714 @@
1
+ import { createHash, randomBytes } from 'crypto';
2
+ import { createContext, createElement, useContext, useState, useRef, useEffect, useCallback, useSyncExternalStore, useMemo } from 'react';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+
14
+ // src/errors.ts
15
+ var AgentSDKError;
16
+ var init_errors = __esm({
17
+ "src/errors.ts"() {
18
+ AgentSDKError = class extends Error {
19
+ /** @internal Marker for cross-bundle identity checks */
20
+ _agentSDKError = true;
21
+ constructor(message, options) {
22
+ super(message, options);
23
+ this.name = "AgentSDKError";
24
+ }
25
+ /** Check if an error is an AgentSDKError (works across bundled copies) */
26
+ static is(error) {
27
+ return error instanceof Error && "_agentSDKError" in error && error._agentSDKError === true;
28
+ }
29
+ };
30
+ }
31
+ });
32
+
33
+ // src/auth/types.ts
34
+ var AuthError, DeviceCodeExpiredError, AccessDeniedError, TokenExchangeError;
35
+ var init_types = __esm({
36
+ "src/auth/types.ts"() {
37
+ init_errors();
38
+ AuthError = class extends AgentSDKError {
39
+ constructor(message, options) {
40
+ super(message, options);
41
+ this.name = "AuthError";
42
+ }
43
+ };
44
+ DeviceCodeExpiredError = class extends AuthError {
45
+ constructor() {
46
+ super("Device code expired. Please restart the auth flow.");
47
+ this.name = "DeviceCodeExpiredError";
48
+ }
49
+ };
50
+ AccessDeniedError = class extends AuthError {
51
+ constructor() {
52
+ super("Access was denied by the user.");
53
+ this.name = "AccessDeniedError";
54
+ }
55
+ };
56
+ TokenExchangeError = class extends AuthError {
57
+ constructor(message, options) {
58
+ super(message, options);
59
+ this.name = "TokenExchangeError";
60
+ }
61
+ };
62
+ }
63
+ });
64
+
65
+ // src/auth/copilot-auth.ts
66
+ var CLIENT_ID, DEVICE_CODE_URL, ACCESS_TOKEN_URL, USER_API_URL, DEFAULT_SCOPES, GRANT_TYPE, CopilotAuth;
67
+ var init_copilot_auth = __esm({
68
+ "src/auth/copilot-auth.ts"() {
69
+ init_types();
70
+ CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
71
+ DEVICE_CODE_URL = "https://github.com/login/device/code";
72
+ ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
73
+ USER_API_URL = "https://api.github.com/user";
74
+ DEFAULT_SCOPES = "read:user,read:org,repo,gist";
75
+ GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
76
+ CopilotAuth = class {
77
+ fetchFn;
78
+ /** @param options - Optional configuration with custom fetch for testing */
79
+ constructor(options) {
80
+ this.fetchFn = options?.fetch ?? globalThis.fetch;
81
+ }
82
+ /**
83
+ * Start the GitHub Device Flow.
84
+ * Returns a device code result with user code, verification URL,
85
+ * and a `waitForToken()` function that polls until the user authorizes.
86
+ *
87
+ * @param options - Optional scopes and abort signal
88
+ * @returns Device flow result with user code, verification URL, and waitForToken poller
89
+ * @throws {AuthError} If the device code request fails
90
+ * @throws {DeviceCodeExpiredError} If the device code expires before user authorizes
91
+ * @throws {AccessDeniedError} If the user denies access
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const auth = new CopilotAuth();
96
+ * const { userCode, verificationUrl, waitForToken } = await auth.startDeviceFlow();
97
+ * console.log(`Open ${verificationUrl} and enter code: ${userCode}`);
98
+ * const token = await waitForToken();
99
+ * ```
100
+ */
101
+ async startDeviceFlow(options) {
102
+ const scopes = options?.scopes ?? DEFAULT_SCOPES;
103
+ const response = await this.fetchFn(DEVICE_CODE_URL, {
104
+ method: "POST",
105
+ headers: {
106
+ "Content-Type": "application/x-www-form-urlencoded",
107
+ Accept: "application/json"
108
+ },
109
+ body: new URLSearchParams({
110
+ client_id: CLIENT_ID,
111
+ scope: scopes
112
+ }),
113
+ signal: options?.signal
114
+ });
115
+ if (!response.ok) {
116
+ throw new AuthError(
117
+ `Failed to request device code: ${response.status} ${response.statusText}`
118
+ );
119
+ }
120
+ const data = await response.json();
121
+ return {
122
+ userCode: data.user_code,
123
+ verificationUrl: data.verification_uri,
124
+ waitForToken: (signal) => this.pollForToken(data.device_code, data.interval, signal)
125
+ };
126
+ }
127
+ async pollForToken(deviceCode, interval, signal) {
128
+ let pollIntervalMs = interval * 1e3;
129
+ while (true) {
130
+ if (signal?.aborted) {
131
+ throw new AuthError("Authentication was aborted.");
132
+ }
133
+ await this.delay(pollIntervalMs, signal);
134
+ const response = await this.fetchFn(ACCESS_TOKEN_URL, {
135
+ method: "POST",
136
+ headers: {
137
+ "Content-Type": "application/x-www-form-urlencoded",
138
+ Accept: "application/json"
139
+ },
140
+ body: new URLSearchParams({
141
+ client_id: CLIENT_ID,
142
+ device_code: deviceCode,
143
+ grant_type: GRANT_TYPE
144
+ }),
145
+ signal
146
+ });
147
+ if (!response.ok) {
148
+ throw new AuthError(
149
+ `Token poll failed: ${response.status} ${response.statusText}`
150
+ );
151
+ }
152
+ const data = await response.json();
153
+ if (data.access_token) {
154
+ const token = {
155
+ accessToken: data.access_token,
156
+ tokenType: data.token_type ?? "bearer",
157
+ obtainedAt: Date.now()
158
+ };
159
+ try {
160
+ const login = await this.fetchUserLogin(data.access_token, signal);
161
+ if (login) token.login = login;
162
+ } catch {
163
+ }
164
+ return token;
165
+ }
166
+ if (data.error === "authorization_pending") {
167
+ continue;
168
+ }
169
+ if (data.error === "slow_down") {
170
+ pollIntervalMs = (data.interval ?? interval + 5) * 1e3;
171
+ continue;
172
+ }
173
+ if (data.error === "expired_token") {
174
+ throw new DeviceCodeExpiredError();
175
+ }
176
+ if (data.error === "access_denied") {
177
+ throw new AccessDeniedError();
178
+ }
179
+ throw new AuthError(
180
+ data.error_description ?? `Unexpected error: ${data.error}`
181
+ );
182
+ }
183
+ }
184
+ async fetchUserLogin(token, signal) {
185
+ const response = await this.fetchFn(USER_API_URL, {
186
+ headers: {
187
+ Authorization: `Bearer ${token}`,
188
+ Accept: "application/json"
189
+ },
190
+ signal
191
+ });
192
+ if (!response.ok) return void 0;
193
+ const user = await response.json();
194
+ return user.login;
195
+ }
196
+ delay(ms, signal) {
197
+ return new Promise((resolve, reject) => {
198
+ const timer = setTimeout(resolve, ms);
199
+ signal?.addEventListener(
200
+ "abort",
201
+ () => {
202
+ clearTimeout(timer);
203
+ reject(new AuthError("Authentication was aborted."));
204
+ },
205
+ { once: true }
206
+ );
207
+ });
208
+ }
209
+ };
210
+ }
211
+ });
212
+ function defaultRandomBytes(size) {
213
+ return new Uint8Array(randomBytes(size));
214
+ }
215
+ function base64Encode(bytes) {
216
+ return Buffer.from(bytes).toString("base64");
217
+ }
218
+ function hexEncode(bytes) {
219
+ return Buffer.from(bytes).toString("hex");
220
+ }
221
+ var CLIENT_ID2, AUTHORIZE_URL, TOKEN_URL, DEFAULT_REDIRECT_URI, DEFAULT_SCOPES2, ClaudeAuth;
222
+ var init_claude_auth = __esm({
223
+ "src/auth/claude-auth.ts"() {
224
+ init_types();
225
+ CLIENT_ID2 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
226
+ AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
227
+ TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
228
+ DEFAULT_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
229
+ DEFAULT_SCOPES2 = "user:profile user:inference user:sessions:claude_code user:mcp_servers";
230
+ ClaudeAuth = class _ClaudeAuth {
231
+ fetchFn;
232
+ randomBytes;
233
+ /**
234
+ * @param options - Optional configuration with custom fetch and random bytes for testing
235
+ */
236
+ constructor(options) {
237
+ this.fetchFn = options?.fetch ?? globalThis.fetch;
238
+ this.randomBytes = options?.randomBytes ?? defaultRandomBytes;
239
+ }
240
+ /**
241
+ * Start the Claude OAuth+PKCE flow.
242
+ * Generates PKCE code verifier/challenge and returns an authorize URL
243
+ * plus a `completeAuth(code)` function for token exchange.
244
+ *
245
+ * @param options - Redirect URI and optional scopes
246
+ * @returns OAuth flow result with authorize URL and completeAuth function
247
+ * @throws {AuthError} If PKCE generation fails
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const auth = new ClaudeAuth();
252
+ * const { authorizeUrl, completeAuth } = auth.startOAuthFlow({
253
+ * redirectUri: "https://platform.claude.com/oauth/code/callback",
254
+ * });
255
+ * console.log(`Open: ${authorizeUrl}`);
256
+ * const token = await completeAuth(authorizationCode);
257
+ * ```
258
+ */
259
+ startOAuthFlow(options) {
260
+ const redirectUri = options?.redirectUri ?? DEFAULT_REDIRECT_URI;
261
+ const scopes = options?.scopes ?? DEFAULT_SCOPES2;
262
+ const codeVerifier = this.generateCodeVerifier();
263
+ const state = this.generateState();
264
+ const authorizeUrl = this.buildAuthorizeUrl(
265
+ redirectUri,
266
+ scopes,
267
+ codeVerifier,
268
+ state
269
+ );
270
+ return {
271
+ authorizeUrl,
272
+ completeAuth: (codeOrUrl) => {
273
+ const code = _ClaudeAuth.extractCode(codeOrUrl);
274
+ return this.exchangeCode(code, codeVerifier, state, redirectUri);
275
+ }
276
+ };
277
+ }
278
+ /**
279
+ * Extract an authorization code from user input.
280
+ * Accepts a raw code string or a full redirect URL containing a `code` query parameter.
281
+ *
282
+ * @param input - Raw authorization code or redirect URL
283
+ * @returns The extracted authorization code
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * ClaudeAuth.extractCode("abc123"); // "abc123"
288
+ * ClaudeAuth.extractCode("https://platform.claude.com/oauth/code/callback?code=abc123&state=xyz"); // "abc123"
289
+ * ```
290
+ */
291
+ static extractCode(input) {
292
+ const trimmed = input.trim();
293
+ try {
294
+ const url = new URL(trimmed);
295
+ const code = url.searchParams.get("code");
296
+ if (code) return code;
297
+ } catch {
298
+ }
299
+ const hashIdx = trimmed.indexOf("#");
300
+ if (hashIdx > 0) {
301
+ return trimmed.substring(0, hashIdx);
302
+ }
303
+ return trimmed;
304
+ }
305
+ /**
306
+ * Refresh an expired Claude token.
307
+ *
308
+ * @param refreshToken - The refresh token from a previous authentication
309
+ * @returns New auth token with refreshed access token
310
+ * @throws {TokenExchangeError} If the refresh request fails
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * const auth = new ClaudeAuth();
315
+ * const newToken = await auth.refreshToken(oldToken.refreshToken);
316
+ * ```
317
+ */
318
+ async refreshToken(refreshToken) {
319
+ const response = await this.fetchFn(TOKEN_URL, {
320
+ method: "POST",
321
+ headers: { "Content-Type": "application/json" },
322
+ body: JSON.stringify({
323
+ grant_type: "refresh_token",
324
+ refresh_token: refreshToken,
325
+ client_id: CLIENT_ID2
326
+ })
327
+ });
328
+ if (!response.ok) {
329
+ const text = await response.text();
330
+ throw new TokenExchangeError(
331
+ `Token refresh failed: ${response.status} ${text}`
332
+ );
333
+ }
334
+ const data = await response.json();
335
+ return this.mapTokenResponse(data);
336
+ }
337
+ generateCodeVerifier() {
338
+ const bytes = this.randomBytes(96);
339
+ return base64Encode(bytes).replace(/\+/g, "~").replace(/=/g, "_").replace(/\//g, "-");
340
+ }
341
+ generateState() {
342
+ const bytes = this.randomBytes(16);
343
+ return hexEncode(bytes);
344
+ }
345
+ buildAuthorizeUrl(redirectUri, scopes, codeVerifier, state) {
346
+ const codeChallenge = this.generateCodeChallengeSync(codeVerifier);
347
+ const url = new URL(AUTHORIZE_URL);
348
+ url.searchParams.set("code", "true");
349
+ url.searchParams.set("client_id", CLIENT_ID2);
350
+ url.searchParams.set("response_type", "code");
351
+ url.searchParams.set("redirect_uri", redirectUri);
352
+ url.searchParams.set("scope", scopes);
353
+ url.searchParams.set("code_challenge", codeChallenge);
354
+ url.searchParams.set("code_challenge_method", "S256");
355
+ url.searchParams.set("state", state);
356
+ return url.toString();
357
+ }
358
+ generateCodeChallengeSync(verifier) {
359
+ const hash = createHash("sha256").update(verifier).digest("base64");
360
+ return hash.split("=")[0].replace(/\+/g, "-").replace(/\//g, "_");
361
+ }
362
+ async exchangeCode(code, codeVerifier, state, redirectUri) {
363
+ const response = await this.fetchFn(TOKEN_URL, {
364
+ method: "POST",
365
+ headers: { "Content-Type": "application/json" },
366
+ body: JSON.stringify({
367
+ grant_type: "authorization_code",
368
+ code,
369
+ redirect_uri: redirectUri,
370
+ client_id: CLIENT_ID2,
371
+ code_verifier: codeVerifier,
372
+ state
373
+ })
374
+ });
375
+ if (!response.ok) {
376
+ const text = await response.text();
377
+ throw new TokenExchangeError(
378
+ `Token exchange failed: ${response.status} ${text}`
379
+ );
380
+ }
381
+ const data = await response.json();
382
+ return this.mapTokenResponse(data);
383
+ }
384
+ mapTokenResponse(data) {
385
+ return {
386
+ accessToken: data.access_token,
387
+ tokenType: data.token_type ?? "bearer",
388
+ expiresIn: data.expires_in,
389
+ obtainedAt: Date.now(),
390
+ refreshToken: data.refresh_token,
391
+ scopes: data.scope?.split(" ") ?? []
392
+ };
393
+ }
394
+ };
395
+ }
396
+ });
397
+
398
+ // src/auth/refresh-manager.ts
399
+ var TokenRefreshManager;
400
+ var init_refresh_manager = __esm({
401
+ "src/auth/refresh-manager.ts"() {
402
+ TokenRefreshManager = class {
403
+ currentToken;
404
+ refreshFn;
405
+ threshold;
406
+ maxRetries;
407
+ retryDelayMs;
408
+ minDelayMs;
409
+ timerId = null;
410
+ running = false;
411
+ disposed = false;
412
+ listeners = {
413
+ refreshed: /* @__PURE__ */ new Set(),
414
+ error: /* @__PURE__ */ new Set(),
415
+ expired: /* @__PURE__ */ new Set(),
416
+ disposed: /* @__PURE__ */ new Set()
417
+ };
418
+ constructor(options) {
419
+ this.currentToken = { ...options.token };
420
+ this.refreshFn = options.refresh;
421
+ this.threshold = options.refreshThreshold ?? 0.8;
422
+ this.maxRetries = options.maxRetries ?? 3;
423
+ this.retryDelayMs = options.retryDelayMs ?? 1e3;
424
+ this.minDelayMs = options.minDelayMs ?? 1e3;
425
+ }
426
+ /** Register an event listener */
427
+ on(event, listener) {
428
+ this.listeners[event].add(listener);
429
+ return this;
430
+ }
431
+ /** Remove an event listener */
432
+ off(event, listener) {
433
+ this.listeners[event].delete(listener);
434
+ return this;
435
+ }
436
+ /** Current token managed by this instance */
437
+ get token() {
438
+ return { ...this.currentToken };
439
+ }
440
+ /** Whether the manager is currently running */
441
+ get isRunning() {
442
+ return this.running;
443
+ }
444
+ /** Whether the manager has been disposed */
445
+ get isDisposed() {
446
+ return this.disposed;
447
+ }
448
+ /**
449
+ * Start automatic refresh scheduling.
450
+ * If the token is already expired, emits "expired" immediately.
451
+ * If the token has no expiresIn, does nothing (long-lived token).
452
+ */
453
+ start() {
454
+ if (this.disposed) return;
455
+ if (this.running) return;
456
+ this.running = true;
457
+ this.schedule();
458
+ }
459
+ /** Stop automatic refresh (can be restarted with start()) */
460
+ stop() {
461
+ this.running = false;
462
+ this.clearTimer();
463
+ }
464
+ /**
465
+ * Update the managed token (e.g. after manual refresh).
466
+ * Reschedules automatic refresh if running.
467
+ */
468
+ updateToken(token) {
469
+ if (this.disposed) return;
470
+ this.currentToken = { ...token };
471
+ if (this.running) {
472
+ this.clearTimer();
473
+ this.schedule();
474
+ }
475
+ }
476
+ /** Stop and clean up all resources */
477
+ dispose() {
478
+ if (this.disposed) return;
479
+ this.stop();
480
+ this.disposed = true;
481
+ this.emit("disposed");
482
+ for (const set of Object.values(this.listeners)) {
483
+ set.clear();
484
+ }
485
+ }
486
+ // ─── Private ──────────────────────────────────────────────────
487
+ schedule() {
488
+ if (!this.running || this.disposed) return;
489
+ const delayMs = this.computeRefreshDelay();
490
+ if (delayMs === null) {
491
+ return;
492
+ }
493
+ if (delayMs <= 0) {
494
+ this.timerId = setTimeout(() => {
495
+ this.timerId = null;
496
+ if (!this.running || this.disposed) return;
497
+ void this.performRefresh();
498
+ }, 0);
499
+ return;
500
+ }
501
+ this.timerId = setTimeout(() => {
502
+ this.timerId = null;
503
+ if (!this.running || this.disposed) return;
504
+ void this.performRefresh();
505
+ }, Math.max(delayMs, this.minDelayMs));
506
+ }
507
+ async performRefresh(attempt = 1) {
508
+ if (!this.running || this.disposed) return;
509
+ try {
510
+ const newToken = await this.refreshFn(this.currentToken);
511
+ if (!this.running || this.disposed) return;
512
+ this.currentToken = { ...newToken };
513
+ this.emit("refreshed", newToken);
514
+ this.schedule();
515
+ } catch (err) {
516
+ if (!this.running || this.disposed) return;
517
+ const error = err instanceof Error ? err : new Error(String(err));
518
+ this.emit("error", error, attempt);
519
+ if (attempt < this.maxRetries) {
520
+ const delay = this.retryDelayMs * Math.pow(2, attempt - 1);
521
+ this.timerId = setTimeout(() => {
522
+ this.timerId = null;
523
+ if (!this.running || this.disposed) return;
524
+ void this.performRefresh(attempt + 1);
525
+ }, delay);
526
+ } else {
527
+ if (this.isTokenExpired()) {
528
+ this.running = false;
529
+ this.emit("expired");
530
+ } else {
531
+ const expiresIn = this.currentToken.expiresIn;
532
+ if (expiresIn == null) return;
533
+ const expiresAt = this.currentToken.obtainedAt + expiresIn * 1e3;
534
+ const waitMs = Math.max(expiresAt - Date.now(), this.minDelayMs);
535
+ this.timerId = setTimeout(() => {
536
+ this.timerId = null;
537
+ if (!this.running || this.disposed) return;
538
+ void this.performRefresh();
539
+ }, waitMs);
540
+ }
541
+ }
542
+ }
543
+ }
544
+ computeRefreshDelay() {
545
+ if (this.currentToken.expiresIn == null) return null;
546
+ const lifetimeMs = this.currentToken.expiresIn * 1e3;
547
+ const refreshAtMs = this.currentToken.obtainedAt + lifetimeMs * this.threshold;
548
+ const now = Date.now();
549
+ const delay = refreshAtMs - now;
550
+ return delay;
551
+ }
552
+ isTokenExpired() {
553
+ if (this.currentToken.expiresIn == null) return false;
554
+ const expiresAt = this.currentToken.obtainedAt + this.currentToken.expiresIn * 1e3;
555
+ return Date.now() >= expiresAt;
556
+ }
557
+ clearTimer() {
558
+ if (this.timerId !== null) {
559
+ clearTimeout(this.timerId);
560
+ this.timerId = null;
561
+ }
562
+ }
563
+ emit(event, ...args) {
564
+ for (const listener of this.listeners[event]) {
565
+ try {
566
+ listener(...args);
567
+ } catch {
568
+ }
569
+ }
570
+ }
571
+ };
572
+ }
573
+ });
574
+
575
+ // src/auth/index.ts
576
+ var auth_exports = {};
577
+ __export(auth_exports, {
578
+ AccessDeniedError: () => AccessDeniedError,
579
+ AuthError: () => AuthError,
580
+ ClaudeAuth: () => ClaudeAuth,
581
+ CopilotAuth: () => CopilotAuth,
582
+ DeviceCodeExpiredError: () => DeviceCodeExpiredError,
583
+ TokenExchangeError: () => TokenExchangeError,
584
+ TokenRefreshManager: () => TokenRefreshManager
585
+ });
586
+ var init_auth = __esm({
587
+ "src/auth/index.ts"() {
588
+ init_types();
589
+ init_copilot_auth();
590
+ init_claude_auth();
591
+ init_refresh_manager();
592
+ }
593
+ });
594
+ var ChatRuntimeContext = createContext(null);
595
+ function ChatProvider({ runtime, children }) {
596
+ return createElement(ChatRuntimeContext.Provider, { value: runtime }, children);
597
+ }
598
+ function useChatRuntime() {
599
+ const runtime = useContext(ChatRuntimeContext);
600
+ if (!runtime) {
601
+ throw new Error("useChatRuntime must be used within a ChatProvider");
602
+ }
603
+ return runtime;
604
+ }
605
+
606
+ // src/chat/core.ts
607
+ function createChatId() {
608
+ return crypto.randomUUID();
609
+ }
610
+ function chatEventToAgentEvent(event) {
611
+ switch (event.type) {
612
+ case "message:delta":
613
+ return { type: "text_delta", text: event.text };
614
+ case "thinking:start":
615
+ return { type: "thinking_start" };
616
+ case "thinking:delta":
617
+ return { type: "thinking_delta", text: event.text };
618
+ case "thinking:end":
619
+ return { type: "thinking_end" };
620
+ case "tool:start":
621
+ return {
622
+ type: "tool_call_start",
623
+ toolCallId: event.toolCallId,
624
+ toolName: event.toolName,
625
+ args: event.args
626
+ };
627
+ case "tool:complete":
628
+ return {
629
+ type: "tool_call_end",
630
+ toolCallId: event.toolCallId,
631
+ toolName: event.toolName,
632
+ result: event.result
633
+ };
634
+ case "error":
635
+ return { type: "error", error: event.error, recoverable: event.recoverable };
636
+ default:
637
+ return null;
638
+ }
639
+ }
640
+
641
+ // src/chat/accumulator.ts
642
+ var MessageAccumulator = class {
643
+ messageId;
644
+ parts = [];
645
+ status = "pending";
646
+ currentTextPart = null;
647
+ currentReasoningPart = null;
648
+ toolCallParts = /* @__PURE__ */ new Map();
649
+ _finalized = false;
650
+ constructor(messageId) {
651
+ this.messageId = messageId ?? createChatId();
652
+ }
653
+ /** Get current message ID */
654
+ get id() {
655
+ return this.messageId;
656
+ }
657
+ /**
658
+ * Apply an AgentEvent to accumulate into the message
659
+ * @param event - AgentEvent to process
660
+ * @throws Error if accumulator is already finalized
661
+ */
662
+ apply(event) {
663
+ if (this._finalized) throw new Error("Cannot apply events to finalized accumulator");
664
+ if (this.status === "pending") {
665
+ this.status = "streaming";
666
+ }
667
+ switch (event.type) {
668
+ case "text_delta":
669
+ this.handleTextDelta(event.text);
670
+ break;
671
+ case "thinking_start":
672
+ this.finalizeCurrentText();
673
+ this.currentReasoningPart = { type: "reasoning", text: "", status: "streaming" };
674
+ this.parts.push(this.currentReasoningPart);
675
+ break;
676
+ case "thinking_delta":
677
+ if (this.currentReasoningPart) {
678
+ this.currentReasoningPart.text += event.text;
679
+ }
680
+ break;
681
+ case "thinking_end":
682
+ if (this.currentReasoningPart) {
683
+ this.currentReasoningPart.status = "complete";
684
+ this.currentReasoningPart = null;
685
+ }
686
+ break;
687
+ case "tool_call_start": {
688
+ this.finalizeCurrentText();
689
+ const toolPart = {
690
+ type: "tool_call",
691
+ toolCallId: event.toolCallId,
692
+ name: event.toolName,
693
+ args: event.args,
694
+ status: "running"
695
+ };
696
+ this.toolCallParts.set(event.toolCallId, toolPart);
697
+ this.parts.push(toolPart);
698
+ break;
699
+ }
700
+ case "tool_call_end": {
701
+ const existing = this.toolCallParts.get(event.toolCallId);
702
+ if (existing) {
703
+ existing.result = event.result;
704
+ existing.status = "complete";
705
+ }
706
+ break;
707
+ }
708
+ case "error":
709
+ this.status = "error";
710
+ break;
711
+ }
712
+ }
713
+ handleTextDelta(text) {
714
+ if (!this.currentTextPart) {
715
+ this.currentTextPart = { type: "text", text: "", status: "streaming" };
716
+ this.parts.push(this.currentTextPart);
717
+ }
718
+ this.currentTextPart.text += text;
719
+ }
720
+ finalizeCurrentText() {
721
+ if (this.currentTextPart) {
722
+ this.currentTextPart.status = "complete";
723
+ this.currentTextPart = null;
724
+ }
725
+ }
726
+ /**
727
+ * Get a snapshot of the current accumulated message (for streaming UI)
728
+ * @returns ChatMessage with current parts and "streaming" status
729
+ */
730
+ snapshot() {
731
+ const now = (/* @__PURE__ */ new Date()).toISOString();
732
+ return {
733
+ id: this.messageId,
734
+ role: "assistant",
735
+ parts: this.parts.map((p) => ({ ...p })),
736
+ status: this.status === "pending" ? "pending" : "streaming",
737
+ createdAt: now,
738
+ updatedAt: now
739
+ };
740
+ }
741
+ /**
742
+ * Finalize the accumulator and return the complete ChatMessage
743
+ * @returns Completed ChatMessage with all parts finalized
744
+ * @throws Error if accumulator is already finalized
745
+ */
746
+ finalize() {
747
+ if (this._finalized) throw new Error("Accumulator already finalized");
748
+ this._finalized = true;
749
+ this.finalizeCurrentText();
750
+ if (this.currentReasoningPart) {
751
+ this.currentReasoningPart.status = "complete";
752
+ this.currentReasoningPart = null;
753
+ }
754
+ for (const [, toolPart] of this.toolCallParts) {
755
+ if (toolPart.status === "running" || toolPart.status === "pending") {
756
+ toolPart.status = "error";
757
+ }
758
+ }
759
+ if (this.status !== "error" && this.status !== "cancelled") {
760
+ this.status = "complete";
761
+ }
762
+ const now = (/* @__PURE__ */ new Date()).toISOString();
763
+ return {
764
+ id: this.messageId,
765
+ role: "assistant",
766
+ parts: this.parts,
767
+ status: this.status,
768
+ createdAt: now,
769
+ updatedAt: now
770
+ };
771
+ }
772
+ /** Check if the accumulator has been finalized */
773
+ get finalized() {
774
+ return this._finalized;
775
+ }
776
+ };
777
+
778
+ // src/chat/react/useChat.ts
779
+ function useChat(options = {}) {
780
+ const runtime = useChatRuntime();
781
+ const [sessionId, setSessionId] = useState(
782
+ options.sessionId ?? null
783
+ );
784
+ const [messages, setMessages] = useState([]);
785
+ const [isGenerating, setIsGenerating] = useState(false);
786
+ const [status, setStatus] = useState("idle");
787
+ const [error, setError] = useState(null);
788
+ const generatingRef = useRef(false);
789
+ const onErrorRef = useRef(options.onError);
790
+ onErrorRef.current = options.onError;
791
+ useEffect(() => {
792
+ if (!sessionId) {
793
+ setMessages([]);
794
+ return;
795
+ }
796
+ runtime.getSession(sessionId).then((session) => {
797
+ if (session) {
798
+ setMessages([...session.messages]);
799
+ }
800
+ });
801
+ }, [sessionId, runtime]);
802
+ const ensureSession = useCallback(async () => {
803
+ if (sessionId) return sessionId;
804
+ const session = await runtime.createSession({
805
+ config: { model: "", backend: "" }
806
+ });
807
+ setSessionId(session.id);
808
+ return session.id;
809
+ }, [sessionId, runtime]);
810
+ const sendMessage = useCallback(
811
+ async (content) => {
812
+ if (generatingRef.current) return;
813
+ setError(null);
814
+ generatingRef.current = true;
815
+ setIsGenerating(true);
816
+ setStatus("streaming");
817
+ const accumulator = new MessageAccumulator();
818
+ let hasEvents = false;
819
+ try {
820
+ const sid = await ensureSession();
821
+ const now = (/* @__PURE__ */ new Date()).toISOString();
822
+ const userMsg = {
823
+ id: crypto.randomUUID(),
824
+ role: "user",
825
+ parts: [{ type: "text", text: content, status: "complete" }],
826
+ status: "complete",
827
+ createdAt: now,
828
+ updatedAt: now
829
+ };
830
+ setMessages((prev) => [...prev, userMsg]);
831
+ for await (const event of runtime.send(sid, content)) {
832
+ const agentEvent = chatEventToAgentEvent(event);
833
+ if (agentEvent) {
834
+ accumulator.apply(agentEvent);
835
+ hasEvents = true;
836
+ const snapshot = accumulator.snapshot();
837
+ setMessages((prev) => {
838
+ const last = prev[prev.length - 1];
839
+ if (last && last.id === snapshot.id) {
840
+ return [...prev.slice(0, -1), snapshot];
841
+ }
842
+ return [...prev, snapshot];
843
+ });
844
+ }
845
+ }
846
+ const session = await runtime.getSession(sid);
847
+ if (session) {
848
+ setMessages([...session.messages]);
849
+ } else if (hasEvents) {
850
+ const final = accumulator.finalize();
851
+ setMessages((prev) => {
852
+ const last = prev[prev.length - 1];
853
+ if (last && last.id === final.id) {
854
+ return [...prev.slice(0, -1), final];
855
+ }
856
+ return [...prev, final];
857
+ });
858
+ }
859
+ } catch (err) {
860
+ const e = err instanceof Error ? err : new Error(String(err));
861
+ setError(e);
862
+ onErrorRef.current?.(e);
863
+ if (hasEvents && !accumulator.finalized) {
864
+ accumulator.apply({ type: "error", error: e.message, recoverable: false });
865
+ const errorSnapshot = accumulator.finalize();
866
+ setMessages((prev) => {
867
+ const last = prev[prev.length - 1];
868
+ if (last && last.id === errorSnapshot.id) {
869
+ return [...prev.slice(0, -1), errorSnapshot];
870
+ }
871
+ return prev;
872
+ });
873
+ }
874
+ } finally {
875
+ generatingRef.current = false;
876
+ setIsGenerating(false);
877
+ setStatus(runtime.status);
878
+ }
879
+ },
880
+ [ensureSession, runtime]
881
+ );
882
+ const stop = useCallback(() => {
883
+ runtime.abort();
884
+ }, [runtime]);
885
+ const clearError = useCallback(() => {
886
+ setError(null);
887
+ }, []);
888
+ const newSession = useCallback(async () => {
889
+ const session = await runtime.createSession({
890
+ config: { model: "", backend: "" }
891
+ });
892
+ setSessionId(session.id);
893
+ setMessages([]);
894
+ setError(null);
895
+ return session.id;
896
+ }, [runtime]);
897
+ return {
898
+ sessionId,
899
+ messages,
900
+ sendMessage,
901
+ stop,
902
+ isGenerating,
903
+ status,
904
+ error,
905
+ clearError,
906
+ newSession
907
+ };
908
+ }
909
+ var EMPTY_MESSAGES = [];
910
+ function useMessages(options) {
911
+ const runtime = useChatRuntime();
912
+ const { sessionId } = options;
913
+ const sessionRef = useRef(null);
914
+ const messagesRef = useRef(EMPTY_MESSAGES);
915
+ const isLoadedRef = useRef(false);
916
+ const versionRef = useRef(0);
917
+ const listenersRef = useRef(/* @__PURE__ */ new Set());
918
+ const emitChange = useCallback(() => {
919
+ versionRef.current++;
920
+ for (const listener of listenersRef.current) {
921
+ listener();
922
+ }
923
+ }, []);
924
+ const subscribe = useCallback(
925
+ (callback) => {
926
+ listenersRef.current.add(callback);
927
+ return () => {
928
+ listenersRef.current.delete(callback);
929
+ };
930
+ },
931
+ []
932
+ );
933
+ const getSnapshot = useCallback(() => {
934
+ return messagesRef.current;
935
+ }, []);
936
+ useEffect(() => {
937
+ let cancelled = false;
938
+ let unsubscribe;
939
+ let pollInterval;
940
+ async function load() {
941
+ const session = await runtime.getSession(sessionId);
942
+ if (cancelled) return;
943
+ if (!session) {
944
+ sessionRef.current = null;
945
+ messagesRef.current = EMPTY_MESSAGES;
946
+ isLoadedRef.current = false;
947
+ emitChange();
948
+ return;
949
+ }
950
+ sessionRef.current = session;
951
+ messagesRef.current = session.messages;
952
+ isLoadedRef.current = true;
953
+ emitChange();
954
+ if (session.subscribe && session.getSnapshot) {
955
+ unsubscribe = session.subscribe(() => {
956
+ const snapshot = session.getSnapshot();
957
+ messagesRef.current = snapshot.messages;
958
+ emitChange();
959
+ });
960
+ } else {
961
+ pollInterval = setInterval(async () => {
962
+ if (cancelled) return;
963
+ const updated = await runtime.getSession(sessionId);
964
+ if (cancelled || !updated) return;
965
+ if (updated.messages.length !== messagesRef.current.length) {
966
+ messagesRef.current = updated.messages;
967
+ emitChange();
968
+ }
969
+ }, 500);
970
+ }
971
+ }
972
+ load();
973
+ return () => {
974
+ cancelled = true;
975
+ unsubscribe?.();
976
+ if (pollInterval) clearInterval(pollInterval);
977
+ };
978
+ }, [sessionId, runtime, emitChange]);
979
+ const messages = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
980
+ return {
981
+ messages,
982
+ isLoaded: isLoadedRef.current
983
+ };
984
+ }
985
+ function toSessionInfo(s) {
986
+ return {
987
+ id: s.id,
988
+ title: s.title,
989
+ status: s.status,
990
+ messageCount: s.metadata.messageCount,
991
+ lastMessage: s.messages[s.messages.length - 1],
992
+ createdAt: s.createdAt,
993
+ updatedAt: s.updatedAt
994
+ };
995
+ }
996
+ function useSessions() {
997
+ const runtime = useChatRuntime();
998
+ const [sessions, setSessions] = useState([]);
999
+ const [loading, setLoading] = useState(true);
1000
+ const [error, setError] = useState(null);
1001
+ const fetchSessions = useCallback(async () => {
1002
+ try {
1003
+ const list = await runtime.listSessions();
1004
+ setSessions(list.map(toSessionInfo));
1005
+ setError(null);
1006
+ } catch (err) {
1007
+ setError(err instanceof Error ? err : new Error(String(err)));
1008
+ } finally {
1009
+ setLoading(false);
1010
+ }
1011
+ }, [runtime]);
1012
+ useEffect(() => {
1013
+ fetchSessions();
1014
+ }, [fetchSessions]);
1015
+ useEffect(() => {
1016
+ return runtime.onSessionChange(() => {
1017
+ fetchSessions();
1018
+ });
1019
+ }, [runtime, fetchSessions]);
1020
+ const refresh = useCallback(() => {
1021
+ setLoading(true);
1022
+ fetchSessions();
1023
+ }, [fetchSessions]);
1024
+ return { sessions, loading, error, refresh };
1025
+ }
1026
+ function defaultRenderText(part, index) {
1027
+ return createElement("span", { key: index, "data-part": "text" }, part.text);
1028
+ }
1029
+ function defaultRenderReasoning(part, index) {
1030
+ return createElement("span", { key: index, "data-part": "reasoning" }, part.text);
1031
+ }
1032
+ function defaultRenderToolCall(part, index) {
1033
+ return createElement("span", { key: index, "data-part": "tool_call", "data-tool-name": part.name }, part.name);
1034
+ }
1035
+ function defaultRenderSource(part, index) {
1036
+ return createElement("a", { key: index, href: part.url, "data-part": "source" }, part.title ?? part.url);
1037
+ }
1038
+ function defaultRenderFile(part, index) {
1039
+ return createElement("span", { key: index, "data-part": "file" }, part.name);
1040
+ }
1041
+ function renderPart(props, part, index) {
1042
+ switch (part.type) {
1043
+ case "text":
1044
+ return (props.renderText ?? defaultRenderText)(part, index);
1045
+ case "reasoning":
1046
+ return (props.renderReasoning ?? defaultRenderReasoning)(part, index);
1047
+ case "tool_call":
1048
+ return (props.renderToolCall ?? defaultRenderToolCall)(part, index);
1049
+ case "source":
1050
+ return (props.renderSource ?? defaultRenderSource)(part, index);
1051
+ case "file":
1052
+ return (props.renderFile ?? defaultRenderFile)(part, index);
1053
+ }
1054
+ }
1055
+ function Message(props) {
1056
+ const { message } = props;
1057
+ const children = message.parts.map((part, i) => renderPart(props, part, i));
1058
+ return createElement(
1059
+ "div",
1060
+ {
1061
+ "data-role": message.role,
1062
+ "data-status": message.status
1063
+ },
1064
+ ...children
1065
+ );
1066
+ }
1067
+ function ThinkingBlock({ text, isStreaming, defaultOpen }) {
1068
+ const attrs = {
1069
+ "data-thinking": "true"
1070
+ };
1071
+ if (isStreaming) {
1072
+ attrs["data-streaming"] = "true";
1073
+ }
1074
+ if (defaultOpen) {
1075
+ attrs.open = true;
1076
+ }
1077
+ return createElement(
1078
+ "details",
1079
+ attrs,
1080
+ createElement("summary", null, isStreaming ? "Thinking..." : "Reasoning"),
1081
+ createElement("div", null, text)
1082
+ );
1083
+ }
1084
+ function ToolCallView({ part, onApprove, onDeny, renderArgs, renderResult }) {
1085
+ const children = [
1086
+ createElement("span", { key: "name", "data-tool-label": "name" }, part.name),
1087
+ createElement("span", { key: "status", "data-tool-label": "status" }, part.status)
1088
+ ];
1089
+ if (part.args !== void 0) {
1090
+ children.push(
1091
+ renderArgs ? renderArgs(part.args) : createElement("pre", { key: "args", "data-tool-label": "args" }, JSON.stringify(part.args, null, 2))
1092
+ );
1093
+ }
1094
+ if (part.result !== void 0) {
1095
+ children.push(
1096
+ renderResult ? renderResult(part.result) : createElement("pre", { key: "result", "data-tool-label": "result" }, JSON.stringify(part.result, null, 2))
1097
+ );
1098
+ }
1099
+ if (part.error) {
1100
+ children.push(
1101
+ createElement("span", { key: "error", "data-tool-label": "error", role: "alert" }, part.error)
1102
+ );
1103
+ }
1104
+ if (part.status === "requires_approval") {
1105
+ children.push(
1106
+ createElement("button", { key: "approve", onClick: onApprove, "data-action": "approve" }, "Approve"),
1107
+ createElement("button", { key: "deny", onClick: onDeny, "data-action": "deny" }, "Deny")
1108
+ );
1109
+ }
1110
+ return createElement(
1111
+ "div",
1112
+ {
1113
+ "data-tool-status": part.status,
1114
+ "data-tool-name": part.name
1115
+ },
1116
+ ...children
1117
+ );
1118
+ }
1119
+ function useToolApproval(messages, onApprove, onDeny) {
1120
+ const pendingRequests = useMemo(() => {
1121
+ const requests = [];
1122
+ for (const msg of messages) {
1123
+ for (const part of msg.parts) {
1124
+ if (part.type === "tool_call" && part.status === "requires_approval") {
1125
+ requests.push({
1126
+ toolCallId: part.toolCallId,
1127
+ toolName: part.name,
1128
+ toolArgs: part.args ?? {},
1129
+ messageId: msg.id
1130
+ });
1131
+ }
1132
+ }
1133
+ }
1134
+ return requests;
1135
+ }, [messages]);
1136
+ const approve = useCallback((toolCallId) => {
1137
+ onApprove?.(toolCallId);
1138
+ }, [onApprove]);
1139
+ const deny = useCallback((toolCallId) => {
1140
+ onDeny?.(toolCallId);
1141
+ }, [onDeny]);
1142
+ return { pendingRequests, approve, deny };
1143
+ }
1144
+ function parseBlocks(text) {
1145
+ const tokens = [];
1146
+ const lines = text.split("\n");
1147
+ let i = 0;
1148
+ while (i < lines.length) {
1149
+ const line = lines[i];
1150
+ const fenceMatch = line.match(/^```(\w*)/);
1151
+ if (fenceMatch) {
1152
+ const language = fenceMatch[1] || void 0;
1153
+ const codeLines = [];
1154
+ i++;
1155
+ while (i < lines.length && !lines[i].startsWith("```")) {
1156
+ codeLines.push(lines[i]);
1157
+ i++;
1158
+ }
1159
+ i++;
1160
+ tokens.push({ type: "code_block", code: codeLines.join("\n"), language });
1161
+ continue;
1162
+ }
1163
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
1164
+ if (headingMatch) {
1165
+ tokens.push({ type: "heading", level: headingMatch[1].length, content: headingMatch[2] });
1166
+ i++;
1167
+ continue;
1168
+ }
1169
+ if (line.startsWith("> ")) {
1170
+ const quoteLines = [];
1171
+ while (i < lines.length && lines[i].startsWith("> ")) {
1172
+ quoteLines.push(lines[i].slice(2));
1173
+ i++;
1174
+ }
1175
+ tokens.push({ type: "blockquote", content: quoteLines.join("\n") });
1176
+ continue;
1177
+ }
1178
+ if (/^[-*]\s+/.test(line)) {
1179
+ const items = [];
1180
+ while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
1181
+ items.push(lines[i].replace(/^[-*]\s+/, ""));
1182
+ i++;
1183
+ }
1184
+ tokens.push({ type: "list", ordered: false, items });
1185
+ continue;
1186
+ }
1187
+ if (/^\d+\.\s+/.test(line)) {
1188
+ const items = [];
1189
+ while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
1190
+ items.push(lines[i].replace(/^\d+\.\s+/, ""));
1191
+ i++;
1192
+ }
1193
+ tokens.push({ type: "list", ordered: true, items });
1194
+ continue;
1195
+ }
1196
+ if (line.trim() === "") {
1197
+ i++;
1198
+ continue;
1199
+ }
1200
+ const paraLines = [];
1201
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^(#{1,6}\s|```|>\s|[-*]\s|\d+\.\s)/)) {
1202
+ paraLines.push(lines[i]);
1203
+ i++;
1204
+ }
1205
+ if (paraLines.length > 0) {
1206
+ tokens.push({ type: "paragraph", content: paraLines.join("\n") });
1207
+ }
1208
+ }
1209
+ return tokens;
1210
+ }
1211
+ function parseInline(text, props) {
1212
+ const nodes = [];
1213
+ const regex = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*]+\*)|(_[^_]+_)|(\[[^\]]+\]\([^)]+\))/g;
1214
+ let lastIndex = 0;
1215
+ let match;
1216
+ while ((match = regex.exec(text)) !== null) {
1217
+ if (match.index > lastIndex) {
1218
+ nodes.push(text.slice(lastIndex, match.index));
1219
+ }
1220
+ const fragment = match[0];
1221
+ if (fragment.startsWith("`")) {
1222
+ const code = fragment.slice(1, -1);
1223
+ nodes.push(createElement("code", { key: `ic-${match.index}`, "data-md-inline-code": true }, code));
1224
+ } else if (fragment.startsWith("**")) {
1225
+ const content = fragment.slice(2, -2);
1226
+ nodes.push(createElement("strong", { key: `b-${match.index}` }, content));
1227
+ } else if (fragment.startsWith("*") || fragment.startsWith("_")) {
1228
+ const content = fragment.slice(1, -1);
1229
+ nodes.push(createElement("em", { key: `i-${match.index}` }, content));
1230
+ } else if (fragment.startsWith("[")) {
1231
+ const linkMatch = fragment.match(/\[([^\]]+)\]\(([^)]+)\)/);
1232
+ if (linkMatch) {
1233
+ nodes.push(
1234
+ props?.renderLink ? props.renderLink(linkMatch[2], linkMatch[1]) : createElement("a", { key: `a-${match.index}`, href: linkMatch[2] }, linkMatch[1])
1235
+ );
1236
+ }
1237
+ }
1238
+ lastIndex = match.index + fragment.length;
1239
+ }
1240
+ if (lastIndex < text.length) {
1241
+ nodes.push(text.slice(lastIndex));
1242
+ }
1243
+ return nodes;
1244
+ }
1245
+ function renderBlock(token, index, props) {
1246
+ switch (token.type) {
1247
+ case "heading":
1248
+ return createElement(
1249
+ `h${token.level}`,
1250
+ { key: index, "data-md-heading": true },
1251
+ ...parseInline(token.content, props)
1252
+ );
1253
+ case "paragraph":
1254
+ return createElement(
1255
+ "p",
1256
+ { key: index, "data-md-paragraph": true },
1257
+ ...parseInline(token.content, props)
1258
+ );
1259
+ case "code_block":
1260
+ if (props.renderCode) {
1261
+ return props.renderCode(token.code, token.language);
1262
+ }
1263
+ return createElement(
1264
+ "pre",
1265
+ { key: index, "data-md-code-block": true },
1266
+ createElement(
1267
+ "code",
1268
+ { className: token.language ? `language-${token.language}` : void 0 },
1269
+ token.code
1270
+ )
1271
+ );
1272
+ case "blockquote":
1273
+ return createElement(
1274
+ "blockquote",
1275
+ { key: index, "data-md-blockquote": true },
1276
+ ...parseInline(token.content, props)
1277
+ );
1278
+ case "list": {
1279
+ const tag = token.ordered ? "ol" : "ul";
1280
+ const items = token.items.map(
1281
+ (item, i) => createElement("li", { key: i }, ...parseInline(item, props))
1282
+ );
1283
+ return createElement(tag, { key: index, "data-md-list": true }, ...items);
1284
+ }
1285
+ }
1286
+ }
1287
+ function MarkdownRenderer(props) {
1288
+ const tokens = parseBlocks(props.content);
1289
+ const children = tokens.map((token, i) => renderBlock(token, i, props));
1290
+ return createElement("div", { "data-md-root": true }, ...children);
1291
+ }
1292
+ var ThreadSlotsContext = createContext(null);
1293
+ function ThreadProvider({
1294
+ children,
1295
+ renderMessage,
1296
+ renderToolCall,
1297
+ renderThinkingBlock
1298
+ }) {
1299
+ const value = { renderMessage, renderToolCall, renderThinkingBlock };
1300
+ return createElement(ThreadSlotsContext.Provider, { value }, children);
1301
+ }
1302
+ function useThreadSlots() {
1303
+ const ctx = useContext(ThreadSlotsContext);
1304
+ if (!ctx) {
1305
+ throw new Error("useThreadSlots must be used within a ThreadProvider");
1306
+ }
1307
+ return ctx;
1308
+ }
1309
+ function useOptionalThreadSlots() {
1310
+ return useContext(ThreadSlotsContext);
1311
+ }
1312
+
1313
+ // src/chat/react/Thread.ts
1314
+ function Thread({
1315
+ messages,
1316
+ isGenerating,
1317
+ autoScroll = true,
1318
+ className
1319
+ }) {
1320
+ const sentinelRef = useRef(null);
1321
+ const containerRef = useRef(null);
1322
+ const [userScrolledUp, setUserScrolledUp] = useState(false);
1323
+ const handleScroll = useCallback(() => {
1324
+ const el = containerRef.current;
1325
+ if (!el) return;
1326
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 1;
1327
+ setUserScrolledUp(!atBottom);
1328
+ }, []);
1329
+ useEffect(() => {
1330
+ if (!autoScroll || userScrolledUp) return;
1331
+ sentinelRef.current?.scrollIntoView({ behavior: "smooth" });
1332
+ }, [messages, autoScroll, userScrolledUp]);
1333
+ const slots = useOptionalThreadSlots();
1334
+ const attrs = { "data-thread": "true", className };
1335
+ if (isGenerating) {
1336
+ attrs["data-thread-loading"] = "true";
1337
+ }
1338
+ attrs.ref = containerRef;
1339
+ attrs.onScroll = handleScroll;
1340
+ const children = messages.map((msg, i) => {
1341
+ const content = slots?.renderMessage ? slots.renderMessage(msg, i) : createElement(Message, {
1342
+ key: msg.id,
1343
+ message: msg,
1344
+ renderToolCall: slots?.renderToolCall,
1345
+ renderReasoning: slots?.renderThinkingBlock ? (part, idx) => slots.renderThinkingBlock(part, idx) : void 0
1346
+ });
1347
+ return createElement(
1348
+ "div",
1349
+ { key: msg.id, "data-thread-message": "true", "data-role": msg.role },
1350
+ content
1351
+ );
1352
+ });
1353
+ children.push(createElement("div", { key: "__sentinel", ref: sentinelRef }));
1354
+ return createElement("div", attrs, ...children);
1355
+ }
1356
+ function Composer({
1357
+ onSend,
1358
+ onStop,
1359
+ isGenerating,
1360
+ disabled,
1361
+ placeholder = "Type a message...",
1362
+ maxRows = 5,
1363
+ className
1364
+ }) {
1365
+ const textareaRef = useRef(null);
1366
+ const [value, setValue] = useState("");
1367
+ const resize = useCallback(() => {
1368
+ const el = textareaRef.current;
1369
+ if (!el) return;
1370
+ el.style.height = "auto";
1371
+ const lineHeight = parseInt(getComputedStyle(el).lineHeight) || 20;
1372
+ const maxHeight = lineHeight * maxRows;
1373
+ el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
1374
+ }, [maxRows]);
1375
+ useEffect(() => {
1376
+ resize();
1377
+ }, [value, resize]);
1378
+ const handleSend = useCallback(() => {
1379
+ const trimmed = value.trim();
1380
+ if (!trimmed || isGenerating) return;
1381
+ onSend(trimmed);
1382
+ setValue("");
1383
+ }, [value, isGenerating, onSend]);
1384
+ const handleKeyDown = useCallback(
1385
+ (e) => {
1386
+ if (e.key === "Enter" && !e.shiftKey) {
1387
+ e.preventDefault();
1388
+ handleSend();
1389
+ }
1390
+ },
1391
+ [handleSend]
1392
+ );
1393
+ const handleChange = useCallback(
1394
+ (e) => {
1395
+ setValue(e.target.value);
1396
+ },
1397
+ []
1398
+ );
1399
+ const children = [
1400
+ createElement("textarea", {
1401
+ key: "textarea",
1402
+ ref: textareaRef,
1403
+ value,
1404
+ onChange: handleChange,
1405
+ onKeyDown: handleKeyDown,
1406
+ placeholder,
1407
+ disabled: disabled || false,
1408
+ "aria-label": "Message input",
1409
+ rows: 1
1410
+ })
1411
+ ];
1412
+ if (isGenerating) {
1413
+ children.push(
1414
+ createElement(
1415
+ "button",
1416
+ {
1417
+ key: "stop",
1418
+ "data-action": "stop",
1419
+ onClick: onStop,
1420
+ type: "button"
1421
+ },
1422
+ "Stop"
1423
+ )
1424
+ );
1425
+ } else {
1426
+ children.push(
1427
+ createElement(
1428
+ "button",
1429
+ {
1430
+ key: "send",
1431
+ "data-action": "send",
1432
+ onClick: handleSend,
1433
+ disabled: !value.trim() || isGenerating,
1434
+ type: "button"
1435
+ },
1436
+ "Send"
1437
+ )
1438
+ );
1439
+ }
1440
+ return createElement(
1441
+ "div",
1442
+ { "data-composer": "true", className },
1443
+ ...children
1444
+ );
1445
+ }
1446
+ function isFullSession(item) {
1447
+ return "messages" in item && Array.isArray(item.messages);
1448
+ }
1449
+ function normalizeSession(item) {
1450
+ if (isFullSession(item)) {
1451
+ return {
1452
+ id: item.id,
1453
+ title: item.title,
1454
+ status: item.status,
1455
+ messageCount: item.metadata?.messageCount ?? item.messages.length,
1456
+ lastMessage: item.messages[item.messages.length - 1],
1457
+ createdAt: item.createdAt,
1458
+ updatedAt: item.updatedAt
1459
+ };
1460
+ }
1461
+ return item;
1462
+ }
1463
+ function ThreadList({
1464
+ sessions,
1465
+ activeSessionId,
1466
+ onSelect,
1467
+ onCreate,
1468
+ onDelete,
1469
+ searchQuery,
1470
+ onSearchChange,
1471
+ className
1472
+ }) {
1473
+ const handleSearchChange = useCallback(
1474
+ (e) => {
1475
+ onSearchChange?.(e.target.value);
1476
+ },
1477
+ [onSearchChange]
1478
+ );
1479
+ const normalized = useMemo(() => sessions.map(normalizeSession), [sessions]);
1480
+ const filtered = useMemo(() => {
1481
+ if (!searchQuery) return normalized;
1482
+ const q = searchQuery.toLowerCase();
1483
+ return normalized.filter((s) => (s.title ?? "").toLowerCase().includes(q));
1484
+ }, [normalized, searchQuery]);
1485
+ const children = [];
1486
+ children.push(
1487
+ createElement("input", {
1488
+ key: "search",
1489
+ "data-thread-list-search": "true",
1490
+ value: searchQuery ?? "",
1491
+ onChange: handleSearchChange,
1492
+ placeholder: "Search sessions..."
1493
+ })
1494
+ );
1495
+ children.push(
1496
+ createElement(
1497
+ "button",
1498
+ {
1499
+ key: "create",
1500
+ "data-action": "create-session",
1501
+ onClick: onCreate,
1502
+ type: "button"
1503
+ },
1504
+ "New"
1505
+ )
1506
+ );
1507
+ const items = filtered.map((session) => {
1508
+ const isActive = session.id === activeSessionId;
1509
+ const itemChildren = [
1510
+ createElement("span", { key: "title" }, session.title ?? "Untitled")
1511
+ ];
1512
+ if (onDelete) {
1513
+ itemChildren.push(
1514
+ createElement(
1515
+ "button",
1516
+ {
1517
+ key: "delete",
1518
+ "data-action": "delete-session",
1519
+ onClick: (e) => {
1520
+ e.stopPropagation();
1521
+ onDelete(session.id);
1522
+ },
1523
+ type: "button"
1524
+ },
1525
+ "Delete"
1526
+ )
1527
+ );
1528
+ }
1529
+ return createElement(
1530
+ "div",
1531
+ {
1532
+ key: session.id,
1533
+ "data-session-item": "true",
1534
+ "data-session-active": isActive ? "true" : "false",
1535
+ onClick: () => onSelect(session.id)
1536
+ },
1537
+ ...itemChildren
1538
+ );
1539
+ });
1540
+ children.push(
1541
+ createElement(
1542
+ "div",
1543
+ { key: "items", "data-thread-list-items": "true" },
1544
+ ...items
1545
+ )
1546
+ );
1547
+ return createElement(
1548
+ "div",
1549
+ { "data-thread-list": "true", className },
1550
+ ...children
1551
+ );
1552
+ }
1553
+ function useSSE(url, options = {}) {
1554
+ const [status, setStatus] = useState(url === null ? "idle" : "idle");
1555
+ const [lastEvent, setLastEvent] = useState(null);
1556
+ const abortRef = useRef(null);
1557
+ const reconnectTimerRef = useRef(null);
1558
+ const optionsRef = useRef(options);
1559
+ optionsRef.current = options;
1560
+ const disconnect = useCallback(() => {
1561
+ if (reconnectTimerRef.current !== null) {
1562
+ clearTimeout(reconnectTimerRef.current);
1563
+ reconnectTimerRef.current = null;
1564
+ }
1565
+ if (abortRef.current) {
1566
+ abortRef.current.abort();
1567
+ abortRef.current = null;
1568
+ }
1569
+ setStatus("closed");
1570
+ }, []);
1571
+ const connect = useCallback(() => {
1572
+ if (!url) return;
1573
+ if (reconnectTimerRef.current !== null) {
1574
+ clearTimeout(reconnectTimerRef.current);
1575
+ reconnectTimerRef.current = null;
1576
+ }
1577
+ if (abortRef.current) {
1578
+ abortRef.current.abort();
1579
+ }
1580
+ const controller = new AbortController();
1581
+ abortRef.current = controller;
1582
+ setStatus("connecting");
1583
+ (async () => {
1584
+ try {
1585
+ const response = await fetch(url, {
1586
+ headers: {
1587
+ Accept: "text/event-stream",
1588
+ ...optionsRef.current.headers
1589
+ },
1590
+ signal: controller.signal
1591
+ });
1592
+ if (!response.ok) {
1593
+ throw new Error(`SSE request failed: ${response.status}`);
1594
+ }
1595
+ if (!response.body) {
1596
+ throw new Error("SSE response has no body");
1597
+ }
1598
+ setStatus("open");
1599
+ const reader = response.body.getReader();
1600
+ const decoder = new TextDecoder();
1601
+ let buffer = "";
1602
+ let dataLines = [];
1603
+ const dispatchEvent = () => {
1604
+ if (dataLines.length === 0) return;
1605
+ const data = dataLines.join("\n");
1606
+ dataLines = [];
1607
+ try {
1608
+ const parsed = JSON.parse(data);
1609
+ setLastEvent(parsed);
1610
+ optionsRef.current.onEvent?.(parsed);
1611
+ } catch {
1612
+ }
1613
+ };
1614
+ while (true) {
1615
+ const { done, value } = await reader.read();
1616
+ if (done) break;
1617
+ buffer += decoder.decode(value, { stream: true });
1618
+ const lines = buffer.split("\n");
1619
+ buffer = lines.pop();
1620
+ for (const line of lines) {
1621
+ if (line === "") {
1622
+ dispatchEvent();
1623
+ } else if (line.startsWith("data:")) {
1624
+ dataLines.push(line.slice(5).trimStart());
1625
+ } else if (line.startsWith("event:")) {
1626
+ }
1627
+ }
1628
+ }
1629
+ if (dataLines.length > 0) {
1630
+ dispatchEvent();
1631
+ }
1632
+ if (!controller.signal.aborted) {
1633
+ setStatus("closed");
1634
+ }
1635
+ } catch (err) {
1636
+ if (controller.signal.aborted) return;
1637
+ const error = err instanceof Error ? err : new Error(String(err));
1638
+ setStatus("error");
1639
+ optionsRef.current.onError?.(error);
1640
+ if (optionsRef.current.reconnect && !controller.signal.aborted) {
1641
+ const delay = optionsRef.current.reconnectInterval ?? 3e3;
1642
+ reconnectTimerRef.current = setTimeout(() => {
1643
+ if (abortRef.current && !abortRef.current.signal.aborted) {
1644
+ connect();
1645
+ }
1646
+ }, delay);
1647
+ }
1648
+ }
1649
+ })();
1650
+ }, [url]);
1651
+ useEffect(() => {
1652
+ return () => {
1653
+ if (reconnectTimerRef.current !== null) {
1654
+ clearTimeout(reconnectTimerRef.current);
1655
+ reconnectTimerRef.current = null;
1656
+ }
1657
+ if (abortRef.current) {
1658
+ abortRef.current.abort();
1659
+ abortRef.current = null;
1660
+ }
1661
+ };
1662
+ }, []);
1663
+ return { status, connect, disconnect, lastEvent };
1664
+ }
1665
+ function useModels() {
1666
+ const runtime = useChatRuntime();
1667
+ const [models, setModels] = useState([]);
1668
+ const [isLoading, setIsLoading] = useState(true);
1669
+ const [error, setError] = useState(null);
1670
+ const mountedRef = useRef(true);
1671
+ const fetchModels = useCallback(async () => {
1672
+ setIsLoading(true);
1673
+ setError(null);
1674
+ try {
1675
+ const result = await runtime.listModels();
1676
+ if (!mountedRef.current) return;
1677
+ const mapped = result.map((m) => ({
1678
+ id: m.id,
1679
+ name: m.name ?? m.id,
1680
+ tier: m.provider
1681
+ }));
1682
+ setModels(mapped);
1683
+ } catch (err) {
1684
+ if (!mountedRef.current) return;
1685
+ setError(err instanceof Error ? err : new Error(String(err)));
1686
+ } finally {
1687
+ if (mountedRef.current) {
1688
+ setIsLoading(false);
1689
+ }
1690
+ }
1691
+ }, [runtime]);
1692
+ useEffect(() => {
1693
+ mountedRef.current = true;
1694
+ fetchModels();
1695
+ return () => {
1696
+ mountedRef.current = false;
1697
+ };
1698
+ }, [fetchModels]);
1699
+ const search = useCallback(
1700
+ (query) => {
1701
+ const q = query.toLowerCase();
1702
+ return models.filter((m) => m.name.toLowerCase().includes(q));
1703
+ },
1704
+ [models]
1705
+ );
1706
+ return { models, isLoading, error, refresh: fetchModels, search };
1707
+ }
1708
+ function ModelSelector({
1709
+ models,
1710
+ selectedModel,
1711
+ onSelect,
1712
+ placeholder = "Select model",
1713
+ className
1714
+ }) {
1715
+ const [open, setOpen] = useState(false);
1716
+ const [search, setSearch] = useState("");
1717
+ const [highlightIndex, setHighlightIndex] = useState(0);
1718
+ const containerRef = useRef(null);
1719
+ const filtered = useMemo(() => {
1720
+ if (!search) return models;
1721
+ const q = search.toLowerCase();
1722
+ return models.filter((m) => m.name.toLowerCase().includes(q));
1723
+ }, [models, search]);
1724
+ useEffect(() => {
1725
+ setHighlightIndex(0);
1726
+ }, [filtered.length]);
1727
+ const selectedInfo = useMemo(
1728
+ () => models.find((m) => m.id === selectedModel),
1729
+ [models, selectedModel]
1730
+ );
1731
+ const handleToggle = useCallback(() => {
1732
+ setOpen((prev) => {
1733
+ if (!prev) {
1734
+ setSearch("");
1735
+ setHighlightIndex(0);
1736
+ }
1737
+ return !prev;
1738
+ });
1739
+ }, []);
1740
+ const handleSelect = useCallback(
1741
+ (modelId) => {
1742
+ onSelect(modelId);
1743
+ setOpen(false);
1744
+ setSearch("");
1745
+ },
1746
+ [onSelect]
1747
+ );
1748
+ const handleSearchChange = useCallback((e) => {
1749
+ setSearch(e.target.value);
1750
+ }, []);
1751
+ const handleKeyDown = useCallback(
1752
+ (e) => {
1753
+ if (e.key === "ArrowDown") {
1754
+ e.preventDefault();
1755
+ setHighlightIndex((prev) => Math.min(prev + 1, filtered.length - 1));
1756
+ } else if (e.key === "ArrowUp") {
1757
+ e.preventDefault();
1758
+ setHighlightIndex((prev) => Math.max(prev - 1, 0));
1759
+ } else if (e.key === "Enter") {
1760
+ e.preventDefault();
1761
+ if (filtered[highlightIndex]) {
1762
+ handleSelect(filtered[highlightIndex].id);
1763
+ }
1764
+ } else if (e.key === "Escape") {
1765
+ e.preventDefault();
1766
+ setOpen(false);
1767
+ }
1768
+ },
1769
+ [filtered, highlightIndex, handleSelect]
1770
+ );
1771
+ const children = [];
1772
+ children.push(
1773
+ createElement(
1774
+ "button",
1775
+ {
1776
+ key: "trigger",
1777
+ "data-model-selector-trigger": "true",
1778
+ onClick: handleToggle,
1779
+ type: "button"
1780
+ },
1781
+ selectedInfo ? selectedInfo.name : placeholder
1782
+ )
1783
+ );
1784
+ if (open) {
1785
+ const dropdownChildren = [];
1786
+ dropdownChildren.push(
1787
+ createElement("input", {
1788
+ key: "search",
1789
+ "data-model-selector-search": "true",
1790
+ value: search,
1791
+ onChange: handleSearchChange,
1792
+ onKeyDown: handleKeyDown,
1793
+ placeholder: "Search models...",
1794
+ autoFocus: true
1795
+ })
1796
+ );
1797
+ filtered.forEach((model, idx) => {
1798
+ const isSelected = model.id === selectedModel;
1799
+ const isHighlighted = idx === highlightIndex;
1800
+ const attrs = {
1801
+ key: model.id,
1802
+ "data-model-option": "true",
1803
+ onClick: () => handleSelect(model.id)
1804
+ };
1805
+ if (model.tier) {
1806
+ attrs["data-tier"] = model.tier;
1807
+ }
1808
+ if (isSelected) {
1809
+ attrs["data-model-selected"] = "true";
1810
+ }
1811
+ if (isHighlighted) {
1812
+ attrs["data-model-highlighted"] = "true";
1813
+ }
1814
+ dropdownChildren.push(createElement("div", attrs, model.name));
1815
+ });
1816
+ children.push(
1817
+ createElement(
1818
+ "div",
1819
+ { key: "dropdown", "data-model-selector-dropdown": "true" },
1820
+ ...dropdownChildren
1821
+ )
1822
+ );
1823
+ }
1824
+ return createElement(
1825
+ "div",
1826
+ {
1827
+ "data-model-selector": "true",
1828
+ className,
1829
+ ref: containerRef
1830
+ },
1831
+ ...children
1832
+ );
1833
+ }
1834
+ var _authLoaders = {
1835
+ async loadCopilotAuth() {
1836
+ const { CopilotAuth: CopilotAuth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
1837
+ return CopilotAuth2;
1838
+ },
1839
+ async loadClaudeAuth() {
1840
+ const { ClaudeAuth: ClaudeAuth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
1841
+ return ClaudeAuth2;
1842
+ }
1843
+ };
1844
+ function useAuth(options) {
1845
+ const { backend, onAuthenticated } = options;
1846
+ const [status, setStatus] = useState("idle");
1847
+ const [error, setError] = useState(null);
1848
+ const [token, setToken] = useState(null);
1849
+ const [deviceCode, setDeviceCode] = useState(null);
1850
+ const [verificationUrl, setVerificationUrl] = useState(null);
1851
+ const [authorizeUrl, setAuthorizeUrl] = useState(null);
1852
+ const onAuthenticatedRef = useRef(onAuthenticated);
1853
+ onAuthenticatedRef.current = onAuthenticated;
1854
+ const completeAuthRef = useRef(null);
1855
+ const startDeviceFlow = useCallback(async () => {
1856
+ if (backend !== "copilot") return;
1857
+ setStatus("pending");
1858
+ setError(null);
1859
+ try {
1860
+ const CopilotAuth2 = await _authLoaders.loadCopilotAuth();
1861
+ const auth = new CopilotAuth2();
1862
+ const result = await auth.startDeviceFlow();
1863
+ setDeviceCode(result.userCode);
1864
+ setVerificationUrl(result.verificationUrl);
1865
+ const authToken = await result.waitForToken();
1866
+ setToken(authToken);
1867
+ setStatus("authenticated");
1868
+ onAuthenticatedRef.current?.(authToken);
1869
+ } catch (err) {
1870
+ setError(err instanceof Error ? err : new Error(String(err)));
1871
+ setStatus("error");
1872
+ }
1873
+ }, [backend]);
1874
+ const startOAuthFlow = useCallback(async () => {
1875
+ if (backend !== "claude") return;
1876
+ setStatus("pending");
1877
+ setError(null);
1878
+ try {
1879
+ const ClaudeAuth2 = await _authLoaders.loadClaudeAuth();
1880
+ const auth = new ClaudeAuth2();
1881
+ const result = auth.startOAuthFlow();
1882
+ setAuthorizeUrl(result.authorizeUrl);
1883
+ completeAuthRef.current = result.completeAuth;
1884
+ } catch (err) {
1885
+ setError(err instanceof Error ? err : new Error(String(err)));
1886
+ setStatus("error");
1887
+ }
1888
+ }, [backend]);
1889
+ const completeOAuth = useCallback(async (codeOrUrl) => {
1890
+ if (!completeAuthRef.current) return;
1891
+ try {
1892
+ const authToken = await completeAuthRef.current(codeOrUrl);
1893
+ setToken(authToken);
1894
+ setStatus("authenticated");
1895
+ onAuthenticatedRef.current?.(authToken);
1896
+ } catch (err) {
1897
+ setError(err instanceof Error ? err : new Error(String(err)));
1898
+ setStatus("error");
1899
+ }
1900
+ }, []);
1901
+ const submitApiKey = useCallback((key) => {
1902
+ if (backend !== "api-key") return;
1903
+ if (!key || !key.trim()) {
1904
+ setError(new Error("API key cannot be empty"));
1905
+ setStatus("error");
1906
+ return;
1907
+ }
1908
+ const authToken = {
1909
+ accessToken: key.trim(),
1910
+ tokenType: "bearer",
1911
+ obtainedAt: Date.now()
1912
+ };
1913
+ setToken(authToken);
1914
+ setStatus("authenticated");
1915
+ onAuthenticatedRef.current?.(authToken);
1916
+ }, [backend]);
1917
+ const reset = useCallback(() => {
1918
+ setStatus("idle");
1919
+ setError(null);
1920
+ setToken(null);
1921
+ setDeviceCode(null);
1922
+ setVerificationUrl(null);
1923
+ setAuthorizeUrl(null);
1924
+ completeAuthRef.current = null;
1925
+ }, []);
1926
+ return {
1927
+ status,
1928
+ error,
1929
+ startDeviceFlow,
1930
+ deviceCode,
1931
+ verificationUrl,
1932
+ startOAuthFlow,
1933
+ authorizeUrl,
1934
+ completeOAuth,
1935
+ submitApiKey,
1936
+ token,
1937
+ reset
1938
+ };
1939
+ }
1940
+ function useRemoteAuth(options) {
1941
+ const { backend, baseUrl, onAuthenticated, headers } = options;
1942
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
1943
+ const [status, setStatus] = useState("idle");
1944
+ const [error, setError] = useState(null);
1945
+ const [token, setToken] = useState(null);
1946
+ const [deviceCode, setDeviceCode] = useState(null);
1947
+ const [verificationUrl, setVerificationUrl] = useState(null);
1948
+ const [authorizeUrl, setAuthorizeUrl] = useState(null);
1949
+ const [savedProviders, setSavedProviders] = useState([]);
1950
+ const onAuthenticatedRef = useRef(onAuthenticated);
1951
+ onAuthenticatedRef.current = onAuthenticated;
1952
+ const post = useCallback(
1953
+ async (path, body) => {
1954
+ const res = await fetchFn(`${baseUrl}${path}`, {
1955
+ method: "POST",
1956
+ headers: { "Content-Type": "application/json", ...headers },
1957
+ body: body ? JSON.stringify(body) : void 0
1958
+ });
1959
+ const data = await res.json();
1960
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1961
+ return data;
1962
+ },
1963
+ [baseUrl, fetchFn, headers]
1964
+ );
1965
+ const get = useCallback(
1966
+ async (path) => {
1967
+ const res = await fetchFn(`${baseUrl}${path}`, {
1968
+ method: "GET",
1969
+ headers: { ...headers }
1970
+ });
1971
+ const data = await res.json();
1972
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1973
+ return data;
1974
+ },
1975
+ [baseUrl, fetchFn, headers]
1976
+ );
1977
+ const startDeviceFlow = useCallback(async () => {
1978
+ if (backend !== "copilot") return;
1979
+ setStatus("pending");
1980
+ setError(null);
1981
+ try {
1982
+ const result = await post("/auth/start", { provider: "copilot" });
1983
+ setDeviceCode(result.userCode);
1984
+ setVerificationUrl(result.verificationUrl);
1985
+ await post("/auth/copilot/poll");
1986
+ const authToken = {
1987
+ accessToken: "server-managed",
1988
+ tokenType: "bearer",
1989
+ obtainedAt: Date.now()
1990
+ };
1991
+ setToken(authToken);
1992
+ setStatus("authenticated");
1993
+ onAuthenticatedRef.current?.(authToken);
1994
+ } catch (err) {
1995
+ setError(err instanceof Error ? err : new Error(String(err)));
1996
+ setStatus("error");
1997
+ }
1998
+ }, [backend, post]);
1999
+ const startOAuthFlow = useCallback(async () => {
2000
+ if (backend !== "claude") return;
2001
+ setStatus("pending");
2002
+ setError(null);
2003
+ try {
2004
+ const result = await post("/auth/start", { provider: "claude" });
2005
+ setAuthorizeUrl(result.authorizeUrl);
2006
+ } catch (err) {
2007
+ setError(err instanceof Error ? err : new Error(String(err)));
2008
+ setStatus("error");
2009
+ }
2010
+ }, [backend, post]);
2011
+ const completeOAuth = useCallback(
2012
+ async (codeOrUrl) => {
2013
+ try {
2014
+ await post("/auth/claude/complete", { code: codeOrUrl });
2015
+ const authToken = {
2016
+ accessToken: "server-managed",
2017
+ tokenType: "bearer",
2018
+ obtainedAt: Date.now()
2019
+ };
2020
+ setToken(authToken);
2021
+ setStatus("authenticated");
2022
+ onAuthenticatedRef.current?.(authToken);
2023
+ } catch (err) {
2024
+ setError(err instanceof Error ? err : new Error(String(err)));
2025
+ setStatus("error");
2026
+ }
2027
+ },
2028
+ [post]
2029
+ );
2030
+ const submitApiKey = useCallback(
2031
+ async (key, apiBaseUrl) => {
2032
+ if (backend !== "vercel-ai") return;
2033
+ if (!key || !key.trim()) {
2034
+ setError(new Error("API key cannot be empty"));
2035
+ setStatus("error");
2036
+ return;
2037
+ }
2038
+ try {
2039
+ await post("/auth/vercel/complete", {
2040
+ apiKey: key.trim(),
2041
+ ...apiBaseUrl ? { baseUrl: apiBaseUrl } : {}
2042
+ });
2043
+ const authToken = {
2044
+ accessToken: "server-managed",
2045
+ tokenType: "bearer",
2046
+ obtainedAt: Date.now()
2047
+ };
2048
+ setToken(authToken);
2049
+ setStatus("authenticated");
2050
+ onAuthenticatedRef.current?.(authToken);
2051
+ } catch (err) {
2052
+ setError(err instanceof Error ? err : new Error(String(err)));
2053
+ setStatus("error");
2054
+ }
2055
+ },
2056
+ [backend, post]
2057
+ );
2058
+ const loadSavedTokens = useCallback(async () => {
2059
+ try {
2060
+ const data = await get("/tokens/saved");
2061
+ setSavedProviders(data.saved || []);
2062
+ } catch {
2063
+ }
2064
+ }, [get]);
2065
+ const useSavedToken = useCallback(
2066
+ async (provider) => {
2067
+ try {
2068
+ await post("/tokens/use", { provider });
2069
+ const authToken = {
2070
+ accessToken: "server-managed",
2071
+ tokenType: "bearer",
2072
+ obtainedAt: Date.now()
2073
+ };
2074
+ setToken(authToken);
2075
+ setStatus("authenticated");
2076
+ onAuthenticatedRef.current?.(authToken);
2077
+ } catch (err) {
2078
+ setError(err instanceof Error ? err : new Error(String(err)));
2079
+ setStatus("error");
2080
+ }
2081
+ },
2082
+ [post]
2083
+ );
2084
+ const clearTokens = useCallback(async () => {
2085
+ try {
2086
+ await post("/tokens/clear");
2087
+ setSavedProviders([]);
2088
+ } catch {
2089
+ }
2090
+ }, [post]);
2091
+ const reset = useCallback(() => {
2092
+ setStatus("idle");
2093
+ setError(null);
2094
+ setToken(null);
2095
+ setDeviceCode(null);
2096
+ setVerificationUrl(null);
2097
+ setAuthorizeUrl(null);
2098
+ setSavedProviders([]);
2099
+ }, []);
2100
+ const start = useCallback(async (provider) => {
2101
+ const target = provider ?? backend;
2102
+ setStatus("pending");
2103
+ setError(null);
2104
+ try {
2105
+ switch (target) {
2106
+ case "copilot": {
2107
+ const result = await post("/auth/start", { provider: "copilot" });
2108
+ setDeviceCode(result.userCode);
2109
+ setVerificationUrl(result.verificationUrl);
2110
+ await post("/auth/copilot/poll");
2111
+ const authToken = {
2112
+ accessToken: "server-managed",
2113
+ tokenType: "bearer",
2114
+ obtainedAt: Date.now()
2115
+ };
2116
+ setToken(authToken);
2117
+ setStatus("authenticated");
2118
+ onAuthenticatedRef.current?.(authToken);
2119
+ break;
2120
+ }
2121
+ case "claude": {
2122
+ const result = await post("/auth/start", { provider: "claude" });
2123
+ setAuthorizeUrl(result.authorizeUrl);
2124
+ break;
2125
+ }
2126
+ case "vercel-ai":
2127
+ throw new Error("vercel-ai requires submitApiKey(key, baseUrl) \u2014 cannot auto-start");
2128
+ default:
2129
+ throw new Error(`Unknown auth provider: ${target}`);
2130
+ }
2131
+ } catch (err) {
2132
+ setError(err instanceof Error ? err : new Error(String(err)));
2133
+ setStatus("error");
2134
+ }
2135
+ }, [backend, post]);
2136
+ return {
2137
+ status,
2138
+ error,
2139
+ startDeviceFlow,
2140
+ deviceCode,
2141
+ verificationUrl,
2142
+ startOAuthFlow,
2143
+ authorizeUrl,
2144
+ completeOAuth,
2145
+ submitApiKey,
2146
+ start,
2147
+ token,
2148
+ reset,
2149
+ savedProviders,
2150
+ loadSavedTokens,
2151
+ useSavedToken,
2152
+ clearTokens
2153
+ };
2154
+ }
2155
+
2156
+ // src/chat/react/RemoteChatRuntime.ts
2157
+ var RemoteChatRuntime = class {
2158
+ _status = "idle";
2159
+ _activeSessionId = null;
2160
+ _currentBackend = "default";
2161
+ _currentModel;
2162
+ _abortController = null;
2163
+ _tools = /* @__PURE__ */ new Map();
2164
+ _middlewares = [];
2165
+ baseUrl;
2166
+ headers;
2167
+ _fetch;
2168
+ constructor(options) {
2169
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
2170
+ this.headers = options.headers ?? {};
2171
+ this._fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
2172
+ }
2173
+ // ─── Lifecycle ──────────────────────────────────────────────
2174
+ get status() {
2175
+ return this._status;
2176
+ }
2177
+ async dispose() {
2178
+ this.abort();
2179
+ this._status = "disposed";
2180
+ }
2181
+ assertNotDisposed() {
2182
+ if (this._status === "disposed") {
2183
+ throw new Error("Runtime is disposed");
2184
+ }
2185
+ }
2186
+ // ─── Sessions ───────────────────────────────────────────────
2187
+ get activeSessionId() {
2188
+ return this._activeSessionId;
2189
+ }
2190
+ async createSession(options) {
2191
+ this.assertNotDisposed();
2192
+ const res = await this._post("/sessions/create", options);
2193
+ const session = await res.json();
2194
+ this._activeSessionId = session.id;
2195
+ this._notifySessionChange();
2196
+ return session;
2197
+ }
2198
+ async getSession(id) {
2199
+ this.assertNotDisposed();
2200
+ const res = await this._get(`/sessions/${id}`);
2201
+ if (res.status === 404) return null;
2202
+ return await res.json();
2203
+ }
2204
+ async listSessions(_options) {
2205
+ this.assertNotDisposed();
2206
+ const res = await this._get("/sessions");
2207
+ return await res.json();
2208
+ }
2209
+ async deleteSession(id) {
2210
+ this.assertNotDisposed();
2211
+ await this._delete(`/sessions/${id}`);
2212
+ if (this._activeSessionId === id) {
2213
+ this._activeSessionId = null;
2214
+ }
2215
+ this._notifySessionChange();
2216
+ }
2217
+ async archiveSession(id) {
2218
+ this.assertNotDisposed();
2219
+ await this._post(`/sessions/${id}/archive`, {});
2220
+ this._notifySessionChange();
2221
+ }
2222
+ async switchSession(id) {
2223
+ this.assertNotDisposed();
2224
+ const session = await this.getSession(id);
2225
+ if (!session) throw new Error(`Session not found: ${id}`);
2226
+ this._activeSessionId = session.id;
2227
+ return session;
2228
+ }
2229
+ // ─── Messaging ──────────────────────────────────────────────
2230
+ async *send(sessionId, message, options) {
2231
+ this.assertNotDisposed();
2232
+ this._status = "streaming";
2233
+ this._abortController = new AbortController();
2234
+ try {
2235
+ const res = await this._fetch(`${this.baseUrl}/send`, {
2236
+ method: "POST",
2237
+ headers: {
2238
+ "Content-Type": "application/json",
2239
+ Accept: "text/event-stream",
2240
+ ...this.headers
2241
+ },
2242
+ body: JSON.stringify({
2243
+ sessionId,
2244
+ message,
2245
+ model: options?.model
2246
+ }),
2247
+ signal: this._abortController.signal
2248
+ });
2249
+ if (!res.ok) {
2250
+ throw new Error(`Send failed: ${res.status} ${res.statusText}`);
2251
+ }
2252
+ if (!res.body) {
2253
+ throw new Error("No response body for SSE stream");
2254
+ }
2255
+ yield* this._parseSSEStream(res.body, this._abortController.signal);
2256
+ } catch (err) {
2257
+ if (err instanceof DOMException && err.name === "AbortError") ; else {
2258
+ this._status = "error";
2259
+ throw err;
2260
+ }
2261
+ } finally {
2262
+ this._abortController = null;
2263
+ if (this._status === "streaming") {
2264
+ this._status = "idle";
2265
+ }
2266
+ this._notifySessionChange();
2267
+ }
2268
+ }
2269
+ abort() {
2270
+ if (this._abortController) {
2271
+ this._abortController.abort();
2272
+ this._abortController = null;
2273
+ }
2274
+ if (this._status === "streaming") {
2275
+ this._status = "idle";
2276
+ }
2277
+ this._post("/abort", {}).catch(() => {
2278
+ });
2279
+ }
2280
+ // ─── Backend / Model ────────────────────────────────────────
2281
+ get currentBackend() {
2282
+ return this._currentBackend;
2283
+ }
2284
+ get currentModel() {
2285
+ return this._currentModel;
2286
+ }
2287
+ async switchBackend(name) {
2288
+ this.assertNotDisposed();
2289
+ await this._post("/backend/switch", { backend: name });
2290
+ this._currentBackend = name;
2291
+ }
2292
+ switchModel(model) {
2293
+ this._currentModel = model;
2294
+ this._post("/model/switch", { model }).catch(() => {
2295
+ });
2296
+ }
2297
+ async listModels() {
2298
+ this.assertNotDisposed();
2299
+ const res = await this._get("/models");
2300
+ return await res.json();
2301
+ }
2302
+ // ─── Tools (client-side registry) ───────────────────────────
2303
+ get registeredTools() {
2304
+ return this._tools;
2305
+ }
2306
+ registerTool(tool) {
2307
+ this._tools.set(tool.name, tool);
2308
+ }
2309
+ removeTool(name) {
2310
+ this._tools.delete(name);
2311
+ }
2312
+ // ─── Middleware (client-side) ───────────────────────────────
2313
+ use(middleware) {
2314
+ this._middlewares.push(middleware);
2315
+ }
2316
+ removeMiddleware(middleware) {
2317
+ const idx = this._middlewares.indexOf(middleware);
2318
+ if (idx !== -1) this._middlewares.splice(idx, 1);
2319
+ }
2320
+ // ─── Context ────────────────────────────────────────────────
2321
+ getContextStats(_sessionId) {
2322
+ return null;
2323
+ }
2324
+ _sessionListeners = /* @__PURE__ */ new Set();
2325
+ onSessionChange(callback) {
2326
+ this._sessionListeners.add(callback);
2327
+ return () => {
2328
+ this._sessionListeners.delete(callback);
2329
+ };
2330
+ }
2331
+ _notifySessionChange() {
2332
+ for (const cb of this._sessionListeners) {
2333
+ try {
2334
+ cb();
2335
+ } catch {
2336
+ }
2337
+ }
2338
+ }
2339
+ // ─── Internal HTTP helpers ──────────────────────────────────
2340
+ async _get(path) {
2341
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2342
+ method: "GET",
2343
+ headers: { ...this.headers }
2344
+ });
2345
+ if (!res.ok && res.status !== 404) {
2346
+ throw new Error(`GET ${path} failed: ${res.status} ${res.statusText}`);
2347
+ }
2348
+ return res;
2349
+ }
2350
+ async _post(path, body) {
2351
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2352
+ method: "POST",
2353
+ headers: {
2354
+ "Content-Type": "application/json",
2355
+ ...this.headers
2356
+ },
2357
+ body: JSON.stringify(body)
2358
+ });
2359
+ if (!res.ok) {
2360
+ throw new Error(`POST ${path} failed: ${res.status} ${res.statusText}`);
2361
+ }
2362
+ return res;
2363
+ }
2364
+ async _delete(path) {
2365
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2366
+ method: "DELETE",
2367
+ headers: { ...this.headers }
2368
+ });
2369
+ if (!res.ok) {
2370
+ throw new Error(`DELETE ${path} failed: ${res.status} ${res.statusText}`);
2371
+ }
2372
+ return res;
2373
+ }
2374
+ // ─── SSE Parser ─────────────────────────────────────────────
2375
+ async *_parseSSEStream(body, signal) {
2376
+ const reader = body.getReader();
2377
+ const decoder = new TextDecoder();
2378
+ let buffer = "";
2379
+ const abortPromise = new Promise((_, reject) => {
2380
+ if (signal.aborted) {
2381
+ reject(new DOMException("Aborted", "AbortError"));
2382
+ return;
2383
+ }
2384
+ signal.addEventListener("abort", () => {
2385
+ reject(new DOMException("Aborted", "AbortError"));
2386
+ }, { once: true });
2387
+ });
2388
+ try {
2389
+ while (true) {
2390
+ if (signal.aborted) break;
2391
+ const { done, value } = await Promise.race([
2392
+ reader.read(),
2393
+ abortPromise
2394
+ ]);
2395
+ if (done) break;
2396
+ buffer += decoder.decode(value, { stream: true });
2397
+ const lines = buffer.split("\n");
2398
+ buffer = lines.pop();
2399
+ for (const line of lines) {
2400
+ if (signal.aborted) return;
2401
+ if (line.startsWith("data: ")) {
2402
+ const data = line.slice(6).trim();
2403
+ if (data === "[DONE]") return;
2404
+ try {
2405
+ yield JSON.parse(data);
2406
+ } catch {
2407
+ }
2408
+ }
2409
+ }
2410
+ }
2411
+ } catch (err) {
2412
+ if (err instanceof DOMException && err.name === "AbortError") ; else {
2413
+ throw err;
2414
+ }
2415
+ } finally {
2416
+ reader.releaseLock();
2417
+ }
2418
+ }
2419
+ };
2420
+
2421
+ // src/chat/react/useRemoteChat.ts
2422
+ function useRemoteChat(options) {
2423
+ const {
2424
+ chatBaseUrl,
2425
+ authBaseUrl,
2426
+ backend,
2427
+ onReady,
2428
+ headers,
2429
+ autoRestore = true
2430
+ } = options;
2431
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
2432
+ const [phase, setPhase] = useState("initializing");
2433
+ const [runtime, setRuntime] = useState(null);
2434
+ const [sessionId, setSessionId] = useState(null);
2435
+ const [error, setError] = useState(null);
2436
+ const onReadyRef = useRef(onReady);
2437
+ onReadyRef.current = onReady;
2438
+ const mountedRef = useRef(true);
2439
+ useEffect(() => {
2440
+ return () => {
2441
+ mountedRef.current = false;
2442
+ };
2443
+ }, []);
2444
+ const auth = useRemoteAuth({
2445
+ backend,
2446
+ baseUrl: authBaseUrl,
2447
+ fetch: fetchFn,
2448
+ headers
2449
+ });
2450
+ const restoredRef = useRef(false);
2451
+ const [tokensLoaded, setTokensLoaded] = useState(false);
2452
+ useEffect(() => {
2453
+ if (!autoRestore || restoredRef.current) return;
2454
+ restoredRef.current = true;
2455
+ (async () => {
2456
+ try {
2457
+ await auth.loadSavedTokens();
2458
+ } catch {
2459
+ }
2460
+ if (mountedRef.current) setTokensLoaded(true);
2461
+ })();
2462
+ }, [autoRestore]);
2463
+ useEffect(() => {
2464
+ if (!tokensLoaded) return;
2465
+ if (auth.status === "idle" && auth.savedProviders.includes(backend) && phase === "initializing") {
2466
+ auth.useSavedToken(backend).catch(() => {
2467
+ if (mountedRef.current) setPhase("unauthenticated");
2468
+ });
2469
+ } else if (auth.status === "idle" && !auth.savedProviders.includes(backend) && phase === "initializing") {
2470
+ setPhase("unauthenticated");
2471
+ }
2472
+ }, [tokensLoaded, auth.status, auth.savedProviders, backend, phase]);
2473
+ useEffect(() => {
2474
+ if (auth.status === "pending") {
2475
+ setPhase("authenticating");
2476
+ } else if (auth.status === "error" && auth.error) {
2477
+ setError(auth.error);
2478
+ setPhase("error");
2479
+ }
2480
+ }, [auth.status, auth.error]);
2481
+ const creatingRef = useRef(false);
2482
+ useEffect(() => {
2483
+ if (auth.status !== "authenticated" || creatingRef.current || runtime !== null) return;
2484
+ creatingRef.current = true;
2485
+ setPhase("creating");
2486
+ setError(null);
2487
+ (async () => {
2488
+ try {
2489
+ const rt = new RemoteChatRuntime({
2490
+ baseUrl: chatBaseUrl,
2491
+ headers,
2492
+ fetch: fetchFn
2493
+ });
2494
+ const session = await rt.createSession({});
2495
+ if (!mountedRef.current) return;
2496
+ setRuntime(rt);
2497
+ setSessionId(session.id);
2498
+ setPhase("ready");
2499
+ onReadyRef.current?.();
2500
+ } catch (err) {
2501
+ if (!mountedRef.current) return;
2502
+ creatingRef.current = false;
2503
+ setError(err instanceof Error ? err : new Error(String(err)));
2504
+ setPhase("error");
2505
+ }
2506
+ })();
2507
+ }, [auth.status, chatBaseUrl]);
2508
+ const newSession = useCallback(async () => {
2509
+ if (!runtime) throw new Error("Runtime not ready");
2510
+ const session = await runtime.createSession({});
2511
+ setSessionId(session.id);
2512
+ return session.id;
2513
+ }, [runtime]);
2514
+ const logout = useCallback(async () => {
2515
+ try {
2516
+ await auth.clearTokens();
2517
+ } catch {
2518
+ }
2519
+ if (runtime) {
2520
+ try {
2521
+ runtime.dispose();
2522
+ } catch {
2523
+ }
2524
+ }
2525
+ auth.reset();
2526
+ setRuntime(null);
2527
+ setSessionId(null);
2528
+ setError(null);
2529
+ setTokensLoaded(false);
2530
+ creatingRef.current = false;
2531
+ restoredRef.current = false;
2532
+ setPhase("unauthenticated");
2533
+ }, [auth.clearTokens, auth.reset, runtime]);
2534
+ return {
2535
+ phase,
2536
+ runtime,
2537
+ sessionId,
2538
+ auth,
2539
+ error,
2540
+ newSession,
2541
+ logout
2542
+ };
2543
+ }
2544
+ function AuthDialog({
2545
+ backends,
2546
+ selectedBackend: controlledBackend,
2547
+ onBackendChange,
2548
+ onAuthenticated,
2549
+ renderCopilotFlow,
2550
+ renderClaudeFlow,
2551
+ renderApiKeyFlow,
2552
+ className
2553
+ }) {
2554
+ const [internalBackend, setInternalBackend] = useState(
2555
+ controlledBackend ?? backends[0] ?? "copilot"
2556
+ );
2557
+ const activeBackend = controlledBackend ?? internalBackend;
2558
+ const handleBackendChange = useCallback(
2559
+ (backend) => {
2560
+ setInternalBackend(backend);
2561
+ onBackendChange?.(backend);
2562
+ },
2563
+ [onBackendChange]
2564
+ );
2565
+ const auth = useAuth({ backend: activeBackend, onAuthenticated });
2566
+ const children = [];
2567
+ const selectorButtons = backends.map(
2568
+ (backend) => createElement(
2569
+ "button",
2570
+ {
2571
+ key: backend,
2572
+ type: "button",
2573
+ "data-auth-backend": backend,
2574
+ "data-auth-selected": String(backend === activeBackend),
2575
+ onClick: () => handleBackendChange(backend)
2576
+ },
2577
+ backend
2578
+ )
2579
+ );
2580
+ children.push(
2581
+ createElement(
2582
+ "div",
2583
+ { key: "selector", "data-auth-selector": "true" },
2584
+ ...selectorButtons
2585
+ )
2586
+ );
2587
+ const contentAttrs = {
2588
+ key: "content",
2589
+ "data-auth-content": "true",
2590
+ "data-auth-status": auth.status
2591
+ };
2592
+ if (auth.error) {
2593
+ contentAttrs["data-auth-error"] = auth.error.message;
2594
+ }
2595
+ let flowContent = null;
2596
+ if (activeBackend === "copilot") {
2597
+ if (renderCopilotFlow && auth.deviceCode && auth.verificationUrl) {
2598
+ flowContent = renderCopilotFlow({
2599
+ deviceCode: auth.deviceCode,
2600
+ verificationUrl: auth.verificationUrl,
2601
+ status: auth.status
2602
+ });
2603
+ } else {
2604
+ const copilotChildren = [];
2605
+ if (auth.status === "idle") {
2606
+ copilotChildren.push(
2607
+ createElement(
2608
+ "button",
2609
+ {
2610
+ key: "start",
2611
+ type: "button",
2612
+ "data-action": "start-device-flow",
2613
+ onClick: auth.startDeviceFlow
2614
+ },
2615
+ "Start Device Flow"
2616
+ )
2617
+ );
2618
+ }
2619
+ if (auth.deviceCode && auth.verificationUrl) {
2620
+ copilotChildren.push(
2621
+ createElement("span", { key: "code", "data-device-code": "true" }, auth.deviceCode)
2622
+ );
2623
+ copilotChildren.push(
2624
+ createElement("a", { key: "url", "data-verification-url": "true", href: auth.verificationUrl }, auth.verificationUrl)
2625
+ );
2626
+ }
2627
+ if (auth.status === "pending") {
2628
+ copilotChildren.push(
2629
+ createElement("span", { key: "loading", "data-auth-loading": "true" }, "Waiting...")
2630
+ );
2631
+ }
2632
+ flowContent = createElement(
2633
+ "div",
2634
+ { "data-auth-flow": "copilot" },
2635
+ ...copilotChildren
2636
+ );
2637
+ }
2638
+ } else if (activeBackend === "claude") {
2639
+ if (renderClaudeFlow) {
2640
+ flowContent = renderClaudeFlow({
2641
+ authorizeUrl: auth.authorizeUrl,
2642
+ status: auth.status,
2643
+ completeOAuth: auth.completeOAuth
2644
+ });
2645
+ } else {
2646
+ const claudeChildren = [];
2647
+ if (auth.status === "idle") {
2648
+ claudeChildren.push(
2649
+ createElement(
2650
+ "button",
2651
+ {
2652
+ key: "start",
2653
+ type: "button",
2654
+ "data-action": "start-oauth-flow",
2655
+ onClick: auth.startOAuthFlow
2656
+ },
2657
+ "Start OAuth Flow"
2658
+ )
2659
+ );
2660
+ }
2661
+ if (auth.authorizeUrl) {
2662
+ claudeChildren.push(
2663
+ createElement("a", { key: "url", "data-authorize-url": "true", href: auth.authorizeUrl }, auth.authorizeUrl)
2664
+ );
2665
+ }
2666
+ flowContent = createElement(
2667
+ "div",
2668
+ { "data-auth-flow": "claude" },
2669
+ ...claudeChildren
2670
+ );
2671
+ }
2672
+ } else if (activeBackend === "api-key") {
2673
+ if (renderApiKeyFlow) {
2674
+ flowContent = renderApiKeyFlow({
2675
+ submitApiKey: auth.submitApiKey,
2676
+ status: auth.status
2677
+ });
2678
+ } else {
2679
+ flowContent = createElement(
2680
+ "div",
2681
+ { "data-auth-flow": "api-key" },
2682
+ createElement(
2683
+ "button",
2684
+ {
2685
+ key: "submit",
2686
+ type: "button",
2687
+ "data-action": "submit-api-key",
2688
+ onClick: () => auth.submitApiKey("")
2689
+ },
2690
+ "Submit API Key"
2691
+ )
2692
+ );
2693
+ }
2694
+ }
2695
+ children.push(createElement("div", contentAttrs, flowContent));
2696
+ if (auth.error) {
2697
+ children.push(
2698
+ createElement(
2699
+ "div",
2700
+ { key: "error", "data-auth-error-display": "true" },
2701
+ auth.error.message
2702
+ )
2703
+ );
2704
+ }
2705
+ return createElement(
2706
+ "div",
2707
+ { "data-auth-dialog": "true", className },
2708
+ ...children
2709
+ );
2710
+ }
2711
+
2712
+ export { AuthDialog, ChatProvider, Composer, MarkdownRenderer, Message, ModelSelector, RemoteChatRuntime, ThinkingBlock, Thread, ThreadList, ThreadProvider, ToolCallView, useAuth, useChat, useChatRuntime, useMessages, useModels, useOptionalThreadSlots, useRemoteAuth, useRemoteChat, useSSE, useSessions, useThreadSlots, useToolApproval };
2713
+ //# sourceMappingURL=react.js.map
2714
+ //# sourceMappingURL=react.js.map