@witqq/agent-sdk 0.6.0 → 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 +341 -22
  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 +341 -22
  13. package/dist/backends/claude.js.map +1 -1
  14. package/dist/backends/copilot.cjs +133 -25
  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 +133 -25
  19. package/dist/backends/copilot.js.map +1 -1
  20. package/dist/backends/vercel-ai.cjs +66 -19
  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 +66 -19
  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,2739 @@
1
+ 'use strict';
2
+
3
+ var crypto$1 = require('crypto');
4
+ var react = require('react');
5
+
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
+ });
596
+ var ChatRuntimeContext = react.createContext(null);
597
+ function ChatProvider({ runtime, children }) {
598
+ return react.createElement(ChatRuntimeContext.Provider, { value: runtime }, children);
599
+ }
600
+ function useChatRuntime() {
601
+ const runtime = react.useContext(ChatRuntimeContext);
602
+ if (!runtime) {
603
+ throw new Error("useChatRuntime must be used within a ChatProvider");
604
+ }
605
+ return runtime;
606
+ }
607
+
608
+ // src/chat/core.ts
609
+ function createChatId() {
610
+ return crypto.randomUUID();
611
+ }
612
+ function chatEventToAgentEvent(event) {
613
+ switch (event.type) {
614
+ case "message:delta":
615
+ return { type: "text_delta", text: event.text };
616
+ case "thinking:start":
617
+ return { type: "thinking_start" };
618
+ case "thinking:delta":
619
+ return { type: "thinking_delta", text: event.text };
620
+ case "thinking:end":
621
+ return { type: "thinking_end" };
622
+ case "tool:start":
623
+ return {
624
+ type: "tool_call_start",
625
+ toolCallId: event.toolCallId,
626
+ toolName: event.toolName,
627
+ args: event.args
628
+ };
629
+ case "tool:complete":
630
+ return {
631
+ type: "tool_call_end",
632
+ toolCallId: event.toolCallId,
633
+ toolName: event.toolName,
634
+ result: event.result
635
+ };
636
+ case "error":
637
+ return { type: "error", error: event.error, recoverable: event.recoverable };
638
+ default:
639
+ return null;
640
+ }
641
+ }
642
+
643
+ // src/chat/accumulator.ts
644
+ var MessageAccumulator = class {
645
+ messageId;
646
+ parts = [];
647
+ status = "pending";
648
+ currentTextPart = null;
649
+ currentReasoningPart = null;
650
+ toolCallParts = /* @__PURE__ */ new Map();
651
+ _finalized = false;
652
+ constructor(messageId) {
653
+ this.messageId = messageId ?? createChatId();
654
+ }
655
+ /** Get current message ID */
656
+ get id() {
657
+ return this.messageId;
658
+ }
659
+ /**
660
+ * Apply an AgentEvent to accumulate into the message
661
+ * @param event - AgentEvent to process
662
+ * @throws Error if accumulator is already finalized
663
+ */
664
+ apply(event) {
665
+ if (this._finalized) throw new Error("Cannot apply events to finalized accumulator");
666
+ if (this.status === "pending") {
667
+ this.status = "streaming";
668
+ }
669
+ switch (event.type) {
670
+ case "text_delta":
671
+ this.handleTextDelta(event.text);
672
+ break;
673
+ case "thinking_start":
674
+ this.finalizeCurrentText();
675
+ this.currentReasoningPart = { type: "reasoning", text: "", status: "streaming" };
676
+ this.parts.push(this.currentReasoningPart);
677
+ break;
678
+ case "thinking_delta":
679
+ if (this.currentReasoningPart) {
680
+ this.currentReasoningPart.text += event.text;
681
+ }
682
+ break;
683
+ case "thinking_end":
684
+ if (this.currentReasoningPart) {
685
+ this.currentReasoningPart.status = "complete";
686
+ this.currentReasoningPart = null;
687
+ }
688
+ break;
689
+ case "tool_call_start": {
690
+ this.finalizeCurrentText();
691
+ const toolPart = {
692
+ type: "tool_call",
693
+ toolCallId: event.toolCallId,
694
+ name: event.toolName,
695
+ args: event.args,
696
+ status: "running"
697
+ };
698
+ this.toolCallParts.set(event.toolCallId, toolPart);
699
+ this.parts.push(toolPart);
700
+ break;
701
+ }
702
+ case "tool_call_end": {
703
+ const existing = this.toolCallParts.get(event.toolCallId);
704
+ if (existing) {
705
+ existing.result = event.result;
706
+ existing.status = "complete";
707
+ }
708
+ break;
709
+ }
710
+ case "error":
711
+ this.status = "error";
712
+ break;
713
+ }
714
+ }
715
+ handleTextDelta(text) {
716
+ if (!this.currentTextPart) {
717
+ this.currentTextPart = { type: "text", text: "", status: "streaming" };
718
+ this.parts.push(this.currentTextPart);
719
+ }
720
+ this.currentTextPart.text += text;
721
+ }
722
+ finalizeCurrentText() {
723
+ if (this.currentTextPart) {
724
+ this.currentTextPart.status = "complete";
725
+ this.currentTextPart = null;
726
+ }
727
+ }
728
+ /**
729
+ * Get a snapshot of the current accumulated message (for streaming UI)
730
+ * @returns ChatMessage with current parts and "streaming" status
731
+ */
732
+ snapshot() {
733
+ const now = (/* @__PURE__ */ new Date()).toISOString();
734
+ return {
735
+ id: this.messageId,
736
+ role: "assistant",
737
+ parts: this.parts.map((p) => ({ ...p })),
738
+ status: this.status === "pending" ? "pending" : "streaming",
739
+ createdAt: now,
740
+ updatedAt: now
741
+ };
742
+ }
743
+ /**
744
+ * Finalize the accumulator and return the complete ChatMessage
745
+ * @returns Completed ChatMessage with all parts finalized
746
+ * @throws Error if accumulator is already finalized
747
+ */
748
+ finalize() {
749
+ if (this._finalized) throw new Error("Accumulator already finalized");
750
+ this._finalized = true;
751
+ this.finalizeCurrentText();
752
+ if (this.currentReasoningPart) {
753
+ this.currentReasoningPart.status = "complete";
754
+ this.currentReasoningPart = null;
755
+ }
756
+ for (const [, toolPart] of this.toolCallParts) {
757
+ if (toolPart.status === "running" || toolPart.status === "pending") {
758
+ toolPart.status = "error";
759
+ }
760
+ }
761
+ if (this.status !== "error" && this.status !== "cancelled") {
762
+ this.status = "complete";
763
+ }
764
+ const now = (/* @__PURE__ */ new Date()).toISOString();
765
+ return {
766
+ id: this.messageId,
767
+ role: "assistant",
768
+ parts: this.parts,
769
+ status: this.status,
770
+ createdAt: now,
771
+ updatedAt: now
772
+ };
773
+ }
774
+ /** Check if the accumulator has been finalized */
775
+ get finalized() {
776
+ return this._finalized;
777
+ }
778
+ };
779
+
780
+ // src/chat/react/useChat.ts
781
+ function useChat(options = {}) {
782
+ const runtime = useChatRuntime();
783
+ const [sessionId, setSessionId] = react.useState(
784
+ options.sessionId ?? null
785
+ );
786
+ const [messages, setMessages] = react.useState([]);
787
+ const [isGenerating, setIsGenerating] = react.useState(false);
788
+ const [status, setStatus] = react.useState("idle");
789
+ const [error, setError] = react.useState(null);
790
+ const generatingRef = react.useRef(false);
791
+ const onErrorRef = react.useRef(options.onError);
792
+ onErrorRef.current = options.onError;
793
+ react.useEffect(() => {
794
+ if (!sessionId) {
795
+ setMessages([]);
796
+ return;
797
+ }
798
+ runtime.getSession(sessionId).then((session) => {
799
+ if (session) {
800
+ setMessages([...session.messages]);
801
+ }
802
+ });
803
+ }, [sessionId, runtime]);
804
+ const ensureSession = react.useCallback(async () => {
805
+ if (sessionId) return sessionId;
806
+ const session = await runtime.createSession({
807
+ config: { model: "", backend: "" }
808
+ });
809
+ setSessionId(session.id);
810
+ return session.id;
811
+ }, [sessionId, runtime]);
812
+ const sendMessage = react.useCallback(
813
+ async (content) => {
814
+ if (generatingRef.current) return;
815
+ setError(null);
816
+ generatingRef.current = true;
817
+ setIsGenerating(true);
818
+ setStatus("streaming");
819
+ const accumulator = new MessageAccumulator();
820
+ let hasEvents = false;
821
+ try {
822
+ const sid = await ensureSession();
823
+ const now = (/* @__PURE__ */ new Date()).toISOString();
824
+ const userMsg = {
825
+ id: crypto.randomUUID(),
826
+ role: "user",
827
+ parts: [{ type: "text", text: content, status: "complete" }],
828
+ status: "complete",
829
+ createdAt: now,
830
+ updatedAt: now
831
+ };
832
+ setMessages((prev) => [...prev, userMsg]);
833
+ for await (const event of runtime.send(sid, content)) {
834
+ const agentEvent = chatEventToAgentEvent(event);
835
+ if (agentEvent) {
836
+ accumulator.apply(agentEvent);
837
+ hasEvents = true;
838
+ const snapshot = accumulator.snapshot();
839
+ setMessages((prev) => {
840
+ const last = prev[prev.length - 1];
841
+ if (last && last.id === snapshot.id) {
842
+ return [...prev.slice(0, -1), snapshot];
843
+ }
844
+ return [...prev, snapshot];
845
+ });
846
+ }
847
+ }
848
+ const session = await runtime.getSession(sid);
849
+ if (session) {
850
+ setMessages([...session.messages]);
851
+ } else if (hasEvents) {
852
+ const final = accumulator.finalize();
853
+ setMessages((prev) => {
854
+ const last = prev[prev.length - 1];
855
+ if (last && last.id === final.id) {
856
+ return [...prev.slice(0, -1), final];
857
+ }
858
+ return [...prev, final];
859
+ });
860
+ }
861
+ } catch (err) {
862
+ const e = err instanceof Error ? err : new Error(String(err));
863
+ setError(e);
864
+ onErrorRef.current?.(e);
865
+ if (hasEvents && !accumulator.finalized) {
866
+ accumulator.apply({ type: "error", error: e.message, recoverable: false });
867
+ const errorSnapshot = accumulator.finalize();
868
+ setMessages((prev) => {
869
+ const last = prev[prev.length - 1];
870
+ if (last && last.id === errorSnapshot.id) {
871
+ return [...prev.slice(0, -1), errorSnapshot];
872
+ }
873
+ return prev;
874
+ });
875
+ }
876
+ } finally {
877
+ generatingRef.current = false;
878
+ setIsGenerating(false);
879
+ setStatus(runtime.status);
880
+ }
881
+ },
882
+ [ensureSession, runtime]
883
+ );
884
+ const stop = react.useCallback(() => {
885
+ runtime.abort();
886
+ }, [runtime]);
887
+ const clearError = react.useCallback(() => {
888
+ setError(null);
889
+ }, []);
890
+ const newSession = react.useCallback(async () => {
891
+ const session = await runtime.createSession({
892
+ config: { model: "", backend: "" }
893
+ });
894
+ setSessionId(session.id);
895
+ setMessages([]);
896
+ setError(null);
897
+ return session.id;
898
+ }, [runtime]);
899
+ return {
900
+ sessionId,
901
+ messages,
902
+ sendMessage,
903
+ stop,
904
+ isGenerating,
905
+ status,
906
+ error,
907
+ clearError,
908
+ newSession
909
+ };
910
+ }
911
+ var EMPTY_MESSAGES = [];
912
+ function useMessages(options) {
913
+ const runtime = useChatRuntime();
914
+ const { sessionId } = options;
915
+ const sessionRef = react.useRef(null);
916
+ const messagesRef = react.useRef(EMPTY_MESSAGES);
917
+ const isLoadedRef = react.useRef(false);
918
+ const versionRef = react.useRef(0);
919
+ const listenersRef = react.useRef(/* @__PURE__ */ new Set());
920
+ const emitChange = react.useCallback(() => {
921
+ versionRef.current++;
922
+ for (const listener of listenersRef.current) {
923
+ listener();
924
+ }
925
+ }, []);
926
+ const subscribe = react.useCallback(
927
+ (callback) => {
928
+ listenersRef.current.add(callback);
929
+ return () => {
930
+ listenersRef.current.delete(callback);
931
+ };
932
+ },
933
+ []
934
+ );
935
+ const getSnapshot = react.useCallback(() => {
936
+ return messagesRef.current;
937
+ }, []);
938
+ react.useEffect(() => {
939
+ let cancelled = false;
940
+ let unsubscribe;
941
+ let pollInterval;
942
+ async function load() {
943
+ const session = await runtime.getSession(sessionId);
944
+ if (cancelled) return;
945
+ if (!session) {
946
+ sessionRef.current = null;
947
+ messagesRef.current = EMPTY_MESSAGES;
948
+ isLoadedRef.current = false;
949
+ emitChange();
950
+ return;
951
+ }
952
+ sessionRef.current = session;
953
+ messagesRef.current = session.messages;
954
+ isLoadedRef.current = true;
955
+ emitChange();
956
+ if (session.subscribe && session.getSnapshot) {
957
+ unsubscribe = session.subscribe(() => {
958
+ const snapshot = session.getSnapshot();
959
+ messagesRef.current = snapshot.messages;
960
+ emitChange();
961
+ });
962
+ } else {
963
+ pollInterval = setInterval(async () => {
964
+ if (cancelled) return;
965
+ const updated = await runtime.getSession(sessionId);
966
+ if (cancelled || !updated) return;
967
+ if (updated.messages.length !== messagesRef.current.length) {
968
+ messagesRef.current = updated.messages;
969
+ emitChange();
970
+ }
971
+ }, 500);
972
+ }
973
+ }
974
+ load();
975
+ return () => {
976
+ cancelled = true;
977
+ unsubscribe?.();
978
+ if (pollInterval) clearInterval(pollInterval);
979
+ };
980
+ }, [sessionId, runtime, emitChange]);
981
+ const messages = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
982
+ return {
983
+ messages,
984
+ isLoaded: isLoadedRef.current
985
+ };
986
+ }
987
+ function toSessionInfo(s) {
988
+ return {
989
+ id: s.id,
990
+ title: s.title,
991
+ status: s.status,
992
+ messageCount: s.metadata.messageCount,
993
+ lastMessage: s.messages[s.messages.length - 1],
994
+ createdAt: s.createdAt,
995
+ updatedAt: s.updatedAt
996
+ };
997
+ }
998
+ function useSessions() {
999
+ const runtime = useChatRuntime();
1000
+ const [sessions, setSessions] = react.useState([]);
1001
+ const [loading, setLoading] = react.useState(true);
1002
+ const [error, setError] = react.useState(null);
1003
+ const fetchSessions = react.useCallback(async () => {
1004
+ try {
1005
+ const list = await runtime.listSessions();
1006
+ setSessions(list.map(toSessionInfo));
1007
+ setError(null);
1008
+ } catch (err) {
1009
+ setError(err instanceof Error ? err : new Error(String(err)));
1010
+ } finally {
1011
+ setLoading(false);
1012
+ }
1013
+ }, [runtime]);
1014
+ react.useEffect(() => {
1015
+ fetchSessions();
1016
+ }, [fetchSessions]);
1017
+ react.useEffect(() => {
1018
+ return runtime.onSessionChange(() => {
1019
+ fetchSessions();
1020
+ });
1021
+ }, [runtime, fetchSessions]);
1022
+ const refresh = react.useCallback(() => {
1023
+ setLoading(true);
1024
+ fetchSessions();
1025
+ }, [fetchSessions]);
1026
+ return { sessions, loading, error, refresh };
1027
+ }
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
+ function parseBlocks(text) {
1147
+ const tokens = [];
1148
+ const lines = text.split("\n");
1149
+ let i = 0;
1150
+ while (i < lines.length) {
1151
+ const line = lines[i];
1152
+ const fenceMatch = line.match(/^```(\w*)/);
1153
+ if (fenceMatch) {
1154
+ const language = fenceMatch[1] || void 0;
1155
+ const codeLines = [];
1156
+ i++;
1157
+ while (i < lines.length && !lines[i].startsWith("```")) {
1158
+ codeLines.push(lines[i]);
1159
+ i++;
1160
+ }
1161
+ i++;
1162
+ tokens.push({ type: "code_block", code: codeLines.join("\n"), language });
1163
+ continue;
1164
+ }
1165
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
1166
+ if (headingMatch) {
1167
+ tokens.push({ type: "heading", level: headingMatch[1].length, content: headingMatch[2] });
1168
+ i++;
1169
+ continue;
1170
+ }
1171
+ if (line.startsWith("> ")) {
1172
+ const quoteLines = [];
1173
+ while (i < lines.length && lines[i].startsWith("> ")) {
1174
+ quoteLines.push(lines[i].slice(2));
1175
+ i++;
1176
+ }
1177
+ tokens.push({ type: "blockquote", content: quoteLines.join("\n") });
1178
+ continue;
1179
+ }
1180
+ if (/^[-*]\s+/.test(line)) {
1181
+ const items = [];
1182
+ while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
1183
+ items.push(lines[i].replace(/^[-*]\s+/, ""));
1184
+ i++;
1185
+ }
1186
+ tokens.push({ type: "list", ordered: false, items });
1187
+ continue;
1188
+ }
1189
+ if (/^\d+\.\s+/.test(line)) {
1190
+ const items = [];
1191
+ while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
1192
+ items.push(lines[i].replace(/^\d+\.\s+/, ""));
1193
+ i++;
1194
+ }
1195
+ tokens.push({ type: "list", ordered: true, items });
1196
+ continue;
1197
+ }
1198
+ if (line.trim() === "") {
1199
+ i++;
1200
+ continue;
1201
+ }
1202
+ const paraLines = [];
1203
+ while (i < lines.length && lines[i].trim() !== "" && !lines[i].match(/^(#{1,6}\s|```|>\s|[-*]\s|\d+\.\s)/)) {
1204
+ paraLines.push(lines[i]);
1205
+ i++;
1206
+ }
1207
+ if (paraLines.length > 0) {
1208
+ tokens.push({ type: "paragraph", content: paraLines.join("\n") });
1209
+ }
1210
+ }
1211
+ return tokens;
1212
+ }
1213
+ function parseInline(text, props) {
1214
+ const nodes = [];
1215
+ const regex = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*]+\*)|(_[^_]+_)|(\[[^\]]+\]\([^)]+\))/g;
1216
+ let lastIndex = 0;
1217
+ let match;
1218
+ while ((match = regex.exec(text)) !== null) {
1219
+ if (match.index > lastIndex) {
1220
+ nodes.push(text.slice(lastIndex, match.index));
1221
+ }
1222
+ const fragment = match[0];
1223
+ if (fragment.startsWith("`")) {
1224
+ const code = fragment.slice(1, -1);
1225
+ nodes.push(react.createElement("code", { key: `ic-${match.index}`, "data-md-inline-code": true }, code));
1226
+ } else if (fragment.startsWith("**")) {
1227
+ const content = fragment.slice(2, -2);
1228
+ nodes.push(react.createElement("strong", { key: `b-${match.index}` }, content));
1229
+ } else if (fragment.startsWith("*") || fragment.startsWith("_")) {
1230
+ const content = fragment.slice(1, -1);
1231
+ nodes.push(react.createElement("em", { key: `i-${match.index}` }, content));
1232
+ } else if (fragment.startsWith("[")) {
1233
+ const linkMatch = fragment.match(/\[([^\]]+)\]\(([^)]+)\)/);
1234
+ if (linkMatch) {
1235
+ nodes.push(
1236
+ props?.renderLink ? props.renderLink(linkMatch[2], linkMatch[1]) : react.createElement("a", { key: `a-${match.index}`, href: linkMatch[2] }, linkMatch[1])
1237
+ );
1238
+ }
1239
+ }
1240
+ lastIndex = match.index + fragment.length;
1241
+ }
1242
+ if (lastIndex < text.length) {
1243
+ nodes.push(text.slice(lastIndex));
1244
+ }
1245
+ return nodes;
1246
+ }
1247
+ function renderBlock(token, index, props) {
1248
+ switch (token.type) {
1249
+ case "heading":
1250
+ return react.createElement(
1251
+ `h${token.level}`,
1252
+ { key: index, "data-md-heading": true },
1253
+ ...parseInline(token.content, props)
1254
+ );
1255
+ case "paragraph":
1256
+ return react.createElement(
1257
+ "p",
1258
+ { key: index, "data-md-paragraph": true },
1259
+ ...parseInline(token.content, props)
1260
+ );
1261
+ case "code_block":
1262
+ if (props.renderCode) {
1263
+ return props.renderCode(token.code, token.language);
1264
+ }
1265
+ return react.createElement(
1266
+ "pre",
1267
+ { key: index, "data-md-code-block": true },
1268
+ react.createElement(
1269
+ "code",
1270
+ { className: token.language ? `language-${token.language}` : void 0 },
1271
+ token.code
1272
+ )
1273
+ );
1274
+ case "blockquote":
1275
+ return react.createElement(
1276
+ "blockquote",
1277
+ { key: index, "data-md-blockquote": true },
1278
+ ...parseInline(token.content, props)
1279
+ );
1280
+ case "list": {
1281
+ const tag = token.ordered ? "ol" : "ul";
1282
+ const items = token.items.map(
1283
+ (item, i) => react.createElement("li", { key: i }, ...parseInline(item, props))
1284
+ );
1285
+ return react.createElement(tag, { key: index, "data-md-list": true }, ...items);
1286
+ }
1287
+ }
1288
+ }
1289
+ function MarkdownRenderer(props) {
1290
+ const tokens = parseBlocks(props.content);
1291
+ const children = tokens.map((token, i) => renderBlock(token, i, props));
1292
+ return react.createElement("div", { "data-md-root": true }, ...children);
1293
+ }
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");
1308
+ }
1309
+ return ctx;
1310
+ }
1311
+ function useOptionalThreadSlots() {
1312
+ return react.useContext(ThreadSlotsContext);
1313
+ }
1314
+
1315
+ // src/chat/react/Thread.ts
1316
+ function Thread({
1317
+ messages,
1318
+ isGenerating,
1319
+ autoScroll = true,
1320
+ className
1321
+ }) {
1322
+ const sentinelRef = react.useRef(null);
1323
+ const containerRef = react.useRef(null);
1324
+ const [userScrolledUp, setUserScrolledUp] = react.useState(false);
1325
+ const handleScroll = react.useCallback(() => {
1326
+ const el = containerRef.current;
1327
+ if (!el) return;
1328
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 1;
1329
+ setUserScrolledUp(!atBottom);
1330
+ }, []);
1331
+ react.useEffect(() => {
1332
+ if (!autoScroll || userScrolledUp) return;
1333
+ sentinelRef.current?.scrollIntoView({ behavior: "smooth" });
1334
+ }, [messages, autoScroll, userScrolledUp]);
1335
+ const slots = useOptionalThreadSlots();
1336
+ const attrs = { "data-thread": "true", className };
1337
+ if (isGenerating) {
1338
+ attrs["data-thread-loading"] = "true";
1339
+ }
1340
+ attrs.ref = containerRef;
1341
+ attrs.onScroll = handleScroll;
1342
+ const children = messages.map((msg, i) => {
1343
+ const content = slots?.renderMessage ? slots.renderMessage(msg, i) : react.createElement(Message, {
1344
+ key: msg.id,
1345
+ message: msg,
1346
+ renderToolCall: slots?.renderToolCall,
1347
+ renderReasoning: slots?.renderThinkingBlock ? (part, idx) => slots.renderThinkingBlock(part, idx) : void 0
1348
+ });
1349
+ return react.createElement(
1350
+ "div",
1351
+ { key: msg.id, "data-thread-message": "true", "data-role": msg.role },
1352
+ content
1353
+ );
1354
+ });
1355
+ children.push(react.createElement("div", { key: "__sentinel", ref: sentinelRef }));
1356
+ return react.createElement("div", attrs, ...children);
1357
+ }
1358
+ function Composer({
1359
+ onSend,
1360
+ onStop,
1361
+ isGenerating,
1362
+ disabled,
1363
+ placeholder = "Type a message...",
1364
+ maxRows = 5,
1365
+ className
1366
+ }) {
1367
+ const textareaRef = react.useRef(null);
1368
+ const [value, setValue] = react.useState("");
1369
+ const resize = react.useCallback(() => {
1370
+ const el = textareaRef.current;
1371
+ if (!el) return;
1372
+ el.style.height = "auto";
1373
+ const lineHeight = parseInt(getComputedStyle(el).lineHeight) || 20;
1374
+ const maxHeight = lineHeight * maxRows;
1375
+ el.style.height = `${Math.min(el.scrollHeight, maxHeight)}px`;
1376
+ }, [maxRows]);
1377
+ react.useEffect(() => {
1378
+ resize();
1379
+ }, [value, resize]);
1380
+ const handleSend = react.useCallback(() => {
1381
+ const trimmed = value.trim();
1382
+ if (!trimmed || isGenerating) return;
1383
+ onSend(trimmed);
1384
+ setValue("");
1385
+ }, [value, isGenerating, onSend]);
1386
+ const handleKeyDown = react.useCallback(
1387
+ (e) => {
1388
+ if (e.key === "Enter" && !e.shiftKey) {
1389
+ e.preventDefault();
1390
+ handleSend();
1391
+ }
1392
+ },
1393
+ [handleSend]
1394
+ );
1395
+ const handleChange = react.useCallback(
1396
+ (e) => {
1397
+ setValue(e.target.value);
1398
+ },
1399
+ []
1400
+ );
1401
+ 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(
1417
+ "button",
1418
+ {
1419
+ key: "stop",
1420
+ "data-action": "stop",
1421
+ onClick: onStop,
1422
+ type: "button"
1423
+ },
1424
+ "Stop"
1425
+ )
1426
+ );
1427
+ } else {
1428
+ children.push(
1429
+ react.createElement(
1430
+ "button",
1431
+ {
1432
+ key: "send",
1433
+ "data-action": "send",
1434
+ onClick: handleSend,
1435
+ disabled: !value.trim() || isGenerating,
1436
+ type: "button"
1437
+ },
1438
+ "Send"
1439
+ )
1440
+ );
1441
+ }
1442
+ return react.createElement(
1443
+ "div",
1444
+ { "data-composer": "true", className },
1445
+ ...children
1446
+ );
1447
+ }
1448
+ function isFullSession(item) {
1449
+ return "messages" in item && Array.isArray(item.messages);
1450
+ }
1451
+ function normalizeSession(item) {
1452
+ if (isFullSession(item)) {
1453
+ return {
1454
+ id: item.id,
1455
+ title: item.title,
1456
+ status: item.status,
1457
+ messageCount: item.metadata?.messageCount ?? item.messages.length,
1458
+ lastMessage: item.messages[item.messages.length - 1],
1459
+ createdAt: item.createdAt,
1460
+ updatedAt: item.updatedAt
1461
+ };
1462
+ }
1463
+ return item;
1464
+ }
1465
+ function ThreadList({
1466
+ sessions,
1467
+ activeSessionId,
1468
+ onSelect,
1469
+ onCreate,
1470
+ onDelete,
1471
+ searchQuery,
1472
+ onSearchChange,
1473
+ className
1474
+ }) {
1475
+ const handleSearchChange = react.useCallback(
1476
+ (e) => {
1477
+ onSearchChange?.(e.target.value);
1478
+ },
1479
+ [onSearchChange]
1480
+ );
1481
+ const normalized = react.useMemo(() => sessions.map(normalizeSession), [sessions]);
1482
+ const filtered = react.useMemo(() => {
1483
+ if (!searchQuery) return normalized;
1484
+ const q = searchQuery.toLowerCase();
1485
+ return normalized.filter((s) => (s.title ?? "").toLowerCase().includes(q));
1486
+ }, [normalized, searchQuery]);
1487
+ 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
+ children.push(
1498
+ react.createElement(
1499
+ "button",
1500
+ {
1501
+ key: "create",
1502
+ "data-action": "create-session",
1503
+ onClick: onCreate,
1504
+ type: "button"
1505
+ },
1506
+ "New"
1507
+ )
1508
+ );
1509
+ const items = filtered.map((session) => {
1510
+ const isActive = session.id === activeSessionId;
1511
+ const itemChildren = [
1512
+ react.createElement("span", { key: "title" }, session.title ?? "Untitled")
1513
+ ];
1514
+ if (onDelete) {
1515
+ itemChildren.push(
1516
+ react.createElement(
1517
+ "button",
1518
+ {
1519
+ key: "delete",
1520
+ "data-action": "delete-session",
1521
+ onClick: (e) => {
1522
+ e.stopPropagation();
1523
+ onDelete(session.id);
1524
+ },
1525
+ type: "button"
1526
+ },
1527
+ "Delete"
1528
+ )
1529
+ );
1530
+ }
1531
+ return react.createElement(
1532
+ "div",
1533
+ {
1534
+ key: session.id,
1535
+ "data-session-item": "true",
1536
+ "data-session-active": isActive ? "true" : "false",
1537
+ onClick: () => onSelect(session.id)
1538
+ },
1539
+ ...itemChildren
1540
+ );
1541
+ });
1542
+ children.push(
1543
+ react.createElement(
1544
+ "div",
1545
+ { key: "items", "data-thread-list-items": "true" },
1546
+ ...items
1547
+ )
1548
+ );
1549
+ return react.createElement(
1550
+ "div",
1551
+ { "data-thread-list": "true", className },
1552
+ ...children
1553
+ );
1554
+ }
1555
+ function useSSE(url, options = {}) {
1556
+ const [status, setStatus] = react.useState(url === null ? "idle" : "idle");
1557
+ const [lastEvent, setLastEvent] = react.useState(null);
1558
+ const abortRef = react.useRef(null);
1559
+ const reconnectTimerRef = react.useRef(null);
1560
+ const optionsRef = react.useRef(options);
1561
+ optionsRef.current = options;
1562
+ const disconnect = react.useCallback(() => {
1563
+ if (reconnectTimerRef.current !== null) {
1564
+ clearTimeout(reconnectTimerRef.current);
1565
+ reconnectTimerRef.current = null;
1566
+ }
1567
+ if (abortRef.current) {
1568
+ abortRef.current.abort();
1569
+ abortRef.current = null;
1570
+ }
1571
+ setStatus("closed");
1572
+ }, []);
1573
+ const connect = react.useCallback(() => {
1574
+ if (!url) return;
1575
+ if (reconnectTimerRef.current !== null) {
1576
+ clearTimeout(reconnectTimerRef.current);
1577
+ reconnectTimerRef.current = null;
1578
+ }
1579
+ if (abortRef.current) {
1580
+ abortRef.current.abort();
1581
+ }
1582
+ const controller = new AbortController();
1583
+ abortRef.current = controller;
1584
+ setStatus("connecting");
1585
+ (async () => {
1586
+ try {
1587
+ const response = await fetch(url, {
1588
+ headers: {
1589
+ Accept: "text/event-stream",
1590
+ ...optionsRef.current.headers
1591
+ },
1592
+ signal: controller.signal
1593
+ });
1594
+ if (!response.ok) {
1595
+ throw new Error(`SSE request failed: ${response.status}`);
1596
+ }
1597
+ if (!response.body) {
1598
+ throw new Error("SSE response has no body");
1599
+ }
1600
+ setStatus("open");
1601
+ const reader = response.body.getReader();
1602
+ const decoder = new TextDecoder();
1603
+ let buffer = "";
1604
+ let dataLines = [];
1605
+ const dispatchEvent = () => {
1606
+ if (dataLines.length === 0) return;
1607
+ const data = dataLines.join("\n");
1608
+ dataLines = [];
1609
+ try {
1610
+ const parsed = JSON.parse(data);
1611
+ setLastEvent(parsed);
1612
+ optionsRef.current.onEvent?.(parsed);
1613
+ } catch {
1614
+ }
1615
+ };
1616
+ while (true) {
1617
+ const { done, value } = await reader.read();
1618
+ if (done) break;
1619
+ buffer += decoder.decode(value, { stream: true });
1620
+ const lines = buffer.split("\n");
1621
+ buffer = lines.pop();
1622
+ for (const line of lines) {
1623
+ if (line === "") {
1624
+ dispatchEvent();
1625
+ } else if (line.startsWith("data:")) {
1626
+ dataLines.push(line.slice(5).trimStart());
1627
+ } else if (line.startsWith("event:")) {
1628
+ }
1629
+ }
1630
+ }
1631
+ if (dataLines.length > 0) {
1632
+ dispatchEvent();
1633
+ }
1634
+ if (!controller.signal.aborted) {
1635
+ setStatus("closed");
1636
+ }
1637
+ } catch (err) {
1638
+ if (controller.signal.aborted) return;
1639
+ const error = err instanceof Error ? err : new Error(String(err));
1640
+ setStatus("error");
1641
+ optionsRef.current.onError?.(error);
1642
+ if (optionsRef.current.reconnect && !controller.signal.aborted) {
1643
+ const delay = optionsRef.current.reconnectInterval ?? 3e3;
1644
+ reconnectTimerRef.current = setTimeout(() => {
1645
+ if (abortRef.current && !abortRef.current.signal.aborted) {
1646
+ connect();
1647
+ }
1648
+ }, delay);
1649
+ }
1650
+ }
1651
+ })();
1652
+ }, [url]);
1653
+ react.useEffect(() => {
1654
+ return () => {
1655
+ if (reconnectTimerRef.current !== null) {
1656
+ clearTimeout(reconnectTimerRef.current);
1657
+ reconnectTimerRef.current = null;
1658
+ }
1659
+ if (abortRef.current) {
1660
+ abortRef.current.abort();
1661
+ abortRef.current = null;
1662
+ }
1663
+ };
1664
+ }, []);
1665
+ return { status, connect, disconnect, lastEvent };
1666
+ }
1667
+ function useModels() {
1668
+ const runtime = useChatRuntime();
1669
+ const [models, setModels] = react.useState([]);
1670
+ const [isLoading, setIsLoading] = react.useState(true);
1671
+ const [error, setError] = react.useState(null);
1672
+ const mountedRef = react.useRef(true);
1673
+ const fetchModels = react.useCallback(async () => {
1674
+ setIsLoading(true);
1675
+ setError(null);
1676
+ try {
1677
+ const result = await runtime.listModels();
1678
+ if (!mountedRef.current) return;
1679
+ const mapped = result.map((m) => ({
1680
+ id: m.id,
1681
+ name: m.name ?? m.id,
1682
+ tier: m.provider
1683
+ }));
1684
+ setModels(mapped);
1685
+ } catch (err) {
1686
+ if (!mountedRef.current) return;
1687
+ setError(err instanceof Error ? err : new Error(String(err)));
1688
+ } finally {
1689
+ if (mountedRef.current) {
1690
+ setIsLoading(false);
1691
+ }
1692
+ }
1693
+ }, [runtime]);
1694
+ react.useEffect(() => {
1695
+ mountedRef.current = true;
1696
+ fetchModels();
1697
+ return () => {
1698
+ mountedRef.current = false;
1699
+ };
1700
+ }, [fetchModels]);
1701
+ const search = react.useCallback(
1702
+ (query) => {
1703
+ const q = query.toLowerCase();
1704
+ return models.filter((m) => m.name.toLowerCase().includes(q));
1705
+ },
1706
+ [models]
1707
+ );
1708
+ return { models, isLoading, error, refresh: fetchModels, search };
1709
+ }
1710
+ function ModelSelector({
1711
+ models,
1712
+ selectedModel,
1713
+ onSelect,
1714
+ placeholder = "Select model",
1715
+ className
1716
+ }) {
1717
+ const [open, setOpen] = react.useState(false);
1718
+ const [search, setSearch] = react.useState("");
1719
+ const [highlightIndex, setHighlightIndex] = react.useState(0);
1720
+ const containerRef = react.useRef(null);
1721
+ const filtered = react.useMemo(() => {
1722
+ if (!search) return models;
1723
+ const q = search.toLowerCase();
1724
+ return models.filter((m) => m.name.toLowerCase().includes(q));
1725
+ }, [models, search]);
1726
+ react.useEffect(() => {
1727
+ setHighlightIndex(0);
1728
+ }, [filtered.length]);
1729
+ const selectedInfo = react.useMemo(
1730
+ () => models.find((m) => m.id === selectedModel),
1731
+ [models, selectedModel]
1732
+ );
1733
+ const handleToggle = react.useCallback(() => {
1734
+ setOpen((prev) => {
1735
+ if (!prev) {
1736
+ setSearch("");
1737
+ setHighlightIndex(0);
1738
+ }
1739
+ return !prev;
1740
+ });
1741
+ }, []);
1742
+ const handleSelect = react.useCallback(
1743
+ (modelId) => {
1744
+ onSelect(modelId);
1745
+ setOpen(false);
1746
+ setSearch("");
1747
+ },
1748
+ [onSelect]
1749
+ );
1750
+ const handleSearchChange = react.useCallback((e) => {
1751
+ setSearch(e.target.value);
1752
+ }, []);
1753
+ const handleKeyDown = react.useCallback(
1754
+ (e) => {
1755
+ if (e.key === "ArrowDown") {
1756
+ e.preventDefault();
1757
+ setHighlightIndex((prev) => Math.min(prev + 1, filtered.length - 1));
1758
+ } else if (e.key === "ArrowUp") {
1759
+ e.preventDefault();
1760
+ setHighlightIndex((prev) => Math.max(prev - 1, 0));
1761
+ } else if (e.key === "Enter") {
1762
+ e.preventDefault();
1763
+ if (filtered[highlightIndex]) {
1764
+ handleSelect(filtered[highlightIndex].id);
1765
+ }
1766
+ } else if (e.key === "Escape") {
1767
+ e.preventDefault();
1768
+ setOpen(false);
1769
+ }
1770
+ },
1771
+ [filtered, highlightIndex, handleSelect]
1772
+ );
1773
+ const children = [];
1774
+ children.push(
1775
+ react.createElement(
1776
+ "button",
1777
+ {
1778
+ key: "trigger",
1779
+ "data-model-selector-trigger": "true",
1780
+ onClick: handleToggle,
1781
+ type: "button"
1782
+ },
1783
+ selectedInfo ? selectedInfo.name : placeholder
1784
+ )
1785
+ );
1786
+ if (open) {
1787
+ const dropdownChildren = [];
1788
+ dropdownChildren.push(
1789
+ react.createElement("input", {
1790
+ key: "search",
1791
+ "data-model-selector-search": "true",
1792
+ value: search,
1793
+ onChange: handleSearchChange,
1794
+ onKeyDown: handleKeyDown,
1795
+ placeholder: "Search models...",
1796
+ autoFocus: true
1797
+ })
1798
+ );
1799
+ filtered.forEach((model, idx) => {
1800
+ const isSelected = model.id === selectedModel;
1801
+ const isHighlighted = idx === highlightIndex;
1802
+ const attrs = {
1803
+ key: model.id,
1804
+ "data-model-option": "true",
1805
+ onClick: () => handleSelect(model.id)
1806
+ };
1807
+ if (model.tier) {
1808
+ attrs["data-tier"] = model.tier;
1809
+ }
1810
+ if (isSelected) {
1811
+ attrs["data-model-selected"] = "true";
1812
+ }
1813
+ if (isHighlighted) {
1814
+ attrs["data-model-highlighted"] = "true";
1815
+ }
1816
+ dropdownChildren.push(react.createElement("div", attrs, model.name));
1817
+ });
1818
+ children.push(
1819
+ react.createElement(
1820
+ "div",
1821
+ { key: "dropdown", "data-model-selector-dropdown": "true" },
1822
+ ...dropdownChildren
1823
+ )
1824
+ );
1825
+ }
1826
+ return react.createElement(
1827
+ "div",
1828
+ {
1829
+ "data-model-selector": "true",
1830
+ className,
1831
+ ref: containerRef
1832
+ },
1833
+ ...children
1834
+ );
1835
+ }
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;
1848
+ const [status, setStatus] = react.useState("idle");
1849
+ const [error, setError] = react.useState(null);
1850
+ const [token, setToken] = react.useState(null);
1851
+ const [deviceCode, setDeviceCode] = react.useState(null);
1852
+ 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;
1859
+ setStatus("pending");
1860
+ setError(null);
1861
+ try {
1862
+ const CopilotAuth2 = await _authLoaders.loadCopilotAuth();
1863
+ const auth = new CopilotAuth2();
1864
+ const result = await auth.startDeviceFlow();
1865
+ setDeviceCode(result.userCode);
1866
+ setVerificationUrl(result.verificationUrl);
1867
+ const authToken = await result.waitForToken();
1868
+ setToken(authToken);
1869
+ setStatus("authenticated");
1870
+ onAuthenticatedRef.current?.(authToken);
1871
+ } catch (err) {
1872
+ setError(err instanceof Error ? err : new Error(String(err)));
1873
+ setStatus("error");
1874
+ }
1875
+ }, [backend]);
1876
+ const startOAuthFlow = react.useCallback(async () => {
1877
+ if (backend !== "claude") return;
1878
+ setStatus("pending");
1879
+ setError(null);
1880
+ try {
1881
+ const ClaudeAuth2 = await _authLoaders.loadClaudeAuth();
1882
+ const auth = new ClaudeAuth2();
1883
+ const result = auth.startOAuthFlow();
1884
+ setAuthorizeUrl(result.authorizeUrl);
1885
+ completeAuthRef.current = result.completeAuth;
1886
+ } catch (err) {
1887
+ setError(err instanceof Error ? err : new Error(String(err)));
1888
+ setStatus("error");
1889
+ }
1890
+ }, [backend]);
1891
+ const completeOAuth = react.useCallback(async (codeOrUrl) => {
1892
+ if (!completeAuthRef.current) return;
1893
+ try {
1894
+ const authToken = await completeAuthRef.current(codeOrUrl);
1895
+ setToken(authToken);
1896
+ setStatus("authenticated");
1897
+ onAuthenticatedRef.current?.(authToken);
1898
+ } catch (err) {
1899
+ setError(err instanceof Error ? err : new Error(String(err)));
1900
+ setStatus("error");
1901
+ }
1902
+ }, []);
1903
+ const submitApiKey = react.useCallback((key) => {
1904
+ if (backend !== "api-key") return;
1905
+ if (!key || !key.trim()) {
1906
+ setError(new Error("API key cannot be empty"));
1907
+ setStatus("error");
1908
+ return;
1909
+ }
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]);
1919
+ const reset = react.useCallback(() => {
1920
+ setStatus("idle");
1921
+ setError(null);
1922
+ setToken(null);
1923
+ setDeviceCode(null);
1924
+ setVerificationUrl(null);
1925
+ setAuthorizeUrl(null);
1926
+ completeAuthRef.current = null;
1927
+ }, []);
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
+ };
1941
+ }
1942
+ function useRemoteAuth(options) {
1943
+ const { backend, baseUrl, onAuthenticated, headers } = options;
1944
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
1945
+ const [status, setStatus] = react.useState("idle");
1946
+ const [error, setError] = react.useState(null);
1947
+ 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
+ const [savedProviders, setSavedProviders] = react.useState([]);
1952
+ const onAuthenticatedRef = react.useRef(onAuthenticated);
1953
+ onAuthenticatedRef.current = onAuthenticated;
1954
+ const post = react.useCallback(
1955
+ async (path, body) => {
1956
+ const res = await fetchFn(`${baseUrl}${path}`, {
1957
+ method: "POST",
1958
+ headers: { "Content-Type": "application/json", ...headers },
1959
+ body: body ? JSON.stringify(body) : void 0
1960
+ });
1961
+ const data = await res.json();
1962
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1963
+ return data;
1964
+ },
1965
+ [baseUrl, fetchFn, headers]
1966
+ );
1967
+ const get = react.useCallback(
1968
+ async (path) => {
1969
+ const res = await fetchFn(`${baseUrl}${path}`, {
1970
+ method: "GET",
1971
+ headers: { ...headers }
1972
+ });
1973
+ const data = await res.json();
1974
+ if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
1975
+ return data;
1976
+ },
1977
+ [baseUrl, fetchFn, headers]
1978
+ );
1979
+ const startDeviceFlow = react.useCallback(async () => {
1980
+ if (backend !== "copilot") return;
1981
+ setStatus("pending");
1982
+ 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]);
2001
+ const startOAuthFlow = react.useCallback(async () => {
2002
+ if (backend !== "claude") return;
2003
+ setStatus("pending");
2004
+ 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]);
2013
+ const completeOAuth = react.useCallback(
2014
+ 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
+ }
2029
+ },
2030
+ [post]
2031
+ );
2032
+ const submitApiKey = react.useCallback(
2033
+ async (key, apiBaseUrl) => {
2034
+ 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
+ }
2057
+ },
2058
+ [backend, post]
2059
+ );
2060
+ const loadSavedTokens = react.useCallback(async () => {
2061
+ try {
2062
+ const data = await get("/tokens/saved");
2063
+ setSavedProviders(data.saved || []);
2064
+ } catch {
2065
+ }
2066
+ }, [get]);
2067
+ const useSavedToken = react.useCallback(
2068
+ async (provider) => {
2069
+ try {
2070
+ await post("/tokens/use", { provider });
2071
+ const authToken = {
2072
+ accessToken: "server-managed",
2073
+ tokenType: "bearer",
2074
+ obtainedAt: Date.now()
2075
+ };
2076
+ setToken(authToken);
2077
+ setStatus("authenticated");
2078
+ onAuthenticatedRef.current?.(authToken);
2079
+ } catch (err) {
2080
+ setError(err instanceof Error ? err : new Error(String(err)));
2081
+ setStatus("error");
2082
+ }
2083
+ },
2084
+ [post]
2085
+ );
2086
+ const clearTokens = react.useCallback(async () => {
2087
+ try {
2088
+ await post("/tokens/clear");
2089
+ setSavedProviders([]);
2090
+ } catch {
2091
+ }
2092
+ }, [post]);
2093
+ const reset = react.useCallback(() => {
2094
+ setStatus("idle");
2095
+ setError(null);
2096
+ setToken(null);
2097
+ setDeviceCode(null);
2098
+ setVerificationUrl(null);
2099
+ setAuthorizeUrl(null);
2100
+ setSavedProviders([]);
2101
+ }, []);
2102
+ const start = react.useCallback(async (provider) => {
2103
+ const target = provider ?? backend;
2104
+ setStatus("pending");
2105
+ 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}`);
2132
+ }
2133
+ } catch (err) {
2134
+ setError(err instanceof Error ? err : new Error(String(err)));
2135
+ setStatus("error");
2136
+ }
2137
+ }, [backend, post]);
2138
+ return {
2139
+ status,
2140
+ error,
2141
+ startDeviceFlow,
2142
+ deviceCode,
2143
+ verificationUrl,
2144
+ startOAuthFlow,
2145
+ authorizeUrl,
2146
+ completeOAuth,
2147
+ submitApiKey,
2148
+ start,
2149
+ token,
2150
+ reset,
2151
+ savedProviders,
2152
+ loadSavedTokens,
2153
+ useSavedToken,
2154
+ clearTokens
2155
+ };
2156
+ }
2157
+
2158
+ // src/chat/react/RemoteChatRuntime.ts
2159
+ var RemoteChatRuntime = class {
2160
+ _status = "idle";
2161
+ _activeSessionId = null;
2162
+ _currentBackend = "default";
2163
+ _currentModel;
2164
+ _abortController = null;
2165
+ _tools = /* @__PURE__ */ new Map();
2166
+ _middlewares = [];
2167
+ baseUrl;
2168
+ headers;
2169
+ _fetch;
2170
+ constructor(options) {
2171
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
2172
+ this.headers = options.headers ?? {};
2173
+ this._fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
2174
+ }
2175
+ // ─── Lifecycle ──────────────────────────────────────────────
2176
+ get status() {
2177
+ return this._status;
2178
+ }
2179
+ async dispose() {
2180
+ this.abort();
2181
+ this._status = "disposed";
2182
+ }
2183
+ assertNotDisposed() {
2184
+ if (this._status === "disposed") {
2185
+ throw new Error("Runtime is disposed");
2186
+ }
2187
+ }
2188
+ // ─── Sessions ───────────────────────────────────────────────
2189
+ get activeSessionId() {
2190
+ return this._activeSessionId;
2191
+ }
2192
+ async createSession(options) {
2193
+ this.assertNotDisposed();
2194
+ const res = await this._post("/sessions/create", options);
2195
+ const session = await res.json();
2196
+ this._activeSessionId = session.id;
2197
+ this._notifySessionChange();
2198
+ return session;
2199
+ }
2200
+ async getSession(id) {
2201
+ this.assertNotDisposed();
2202
+ const res = await this._get(`/sessions/${id}`);
2203
+ if (res.status === 404) return null;
2204
+ return await res.json();
2205
+ }
2206
+ async listSessions(_options) {
2207
+ this.assertNotDisposed();
2208
+ const res = await this._get("/sessions");
2209
+ return await res.json();
2210
+ }
2211
+ async deleteSession(id) {
2212
+ this.assertNotDisposed();
2213
+ await this._delete(`/sessions/${id}`);
2214
+ if (this._activeSessionId === id) {
2215
+ this._activeSessionId = null;
2216
+ }
2217
+ this._notifySessionChange();
2218
+ }
2219
+ async archiveSession(id) {
2220
+ this.assertNotDisposed();
2221
+ await this._post(`/sessions/${id}/archive`, {});
2222
+ this._notifySessionChange();
2223
+ }
2224
+ async switchSession(id) {
2225
+ this.assertNotDisposed();
2226
+ const session = await this.getSession(id);
2227
+ if (!session) throw new Error(`Session not found: ${id}`);
2228
+ this._activeSessionId = session.id;
2229
+ return session;
2230
+ }
2231
+ // ─── Messaging ──────────────────────────────────────────────
2232
+ async *send(sessionId, message, options) {
2233
+ this.assertNotDisposed();
2234
+ this._status = "streaming";
2235
+ this._abortController = new AbortController();
2236
+ try {
2237
+ const res = await this._fetch(`${this.baseUrl}/send`, {
2238
+ method: "POST",
2239
+ headers: {
2240
+ "Content-Type": "application/json",
2241
+ Accept: "text/event-stream",
2242
+ ...this.headers
2243
+ },
2244
+ body: JSON.stringify({
2245
+ sessionId,
2246
+ message,
2247
+ model: options?.model
2248
+ }),
2249
+ signal: this._abortController.signal
2250
+ });
2251
+ if (!res.ok) {
2252
+ throw new Error(`Send failed: ${res.status} ${res.statusText}`);
2253
+ }
2254
+ if (!res.body) {
2255
+ throw new Error("No response body for SSE stream");
2256
+ }
2257
+ yield* this._parseSSEStream(res.body, this._abortController.signal);
2258
+ } catch (err) {
2259
+ if (err instanceof DOMException && err.name === "AbortError") ; else {
2260
+ this._status = "error";
2261
+ throw err;
2262
+ }
2263
+ } finally {
2264
+ this._abortController = null;
2265
+ if (this._status === "streaming") {
2266
+ this._status = "idle";
2267
+ }
2268
+ this._notifySessionChange();
2269
+ }
2270
+ }
2271
+ abort() {
2272
+ if (this._abortController) {
2273
+ this._abortController.abort();
2274
+ this._abortController = null;
2275
+ }
2276
+ if (this._status === "streaming") {
2277
+ this._status = "idle";
2278
+ }
2279
+ this._post("/abort", {}).catch(() => {
2280
+ });
2281
+ }
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
+ }
2299
+ async listModels() {
2300
+ this.assertNotDisposed();
2301
+ const res = await this._get("/models");
2302
+ return await res.json();
2303
+ }
2304
+ // ─── Tools (client-side registry) ───────────────────────────
2305
+ get registeredTools() {
2306
+ return this._tools;
2307
+ }
2308
+ registerTool(tool) {
2309
+ this._tools.set(tool.name, tool);
2310
+ }
2311
+ removeTool(name) {
2312
+ this._tools.delete(name);
2313
+ }
2314
+ // ─── Middleware (client-side) ───────────────────────────────
2315
+ use(middleware) {
2316
+ this._middlewares.push(middleware);
2317
+ }
2318
+ removeMiddleware(middleware) {
2319
+ const idx = this._middlewares.indexOf(middleware);
2320
+ if (idx !== -1) this._middlewares.splice(idx, 1);
2321
+ }
2322
+ // ─── Context ────────────────────────────────────────────────
2323
+ getContextStats(_sessionId) {
2324
+ return null;
2325
+ }
2326
+ _sessionListeners = /* @__PURE__ */ new Set();
2327
+ onSessionChange(callback) {
2328
+ this._sessionListeners.add(callback);
2329
+ return () => {
2330
+ this._sessionListeners.delete(callback);
2331
+ };
2332
+ }
2333
+ _notifySessionChange() {
2334
+ for (const cb of this._sessionListeners) {
2335
+ try {
2336
+ cb();
2337
+ } catch {
2338
+ }
2339
+ }
2340
+ }
2341
+ // ─── Internal HTTP helpers ──────────────────────────────────
2342
+ async _get(path) {
2343
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2344
+ method: "GET",
2345
+ headers: { ...this.headers }
2346
+ });
2347
+ if (!res.ok && res.status !== 404) {
2348
+ throw new Error(`GET ${path} failed: ${res.status} ${res.statusText}`);
2349
+ }
2350
+ return res;
2351
+ }
2352
+ async _post(path, body) {
2353
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2354
+ method: "POST",
2355
+ headers: {
2356
+ "Content-Type": "application/json",
2357
+ ...this.headers
2358
+ },
2359
+ body: JSON.stringify(body)
2360
+ });
2361
+ if (!res.ok) {
2362
+ throw new Error(`POST ${path} failed: ${res.status} ${res.statusText}`);
2363
+ }
2364
+ return res;
2365
+ }
2366
+ async _delete(path) {
2367
+ const res = await this._fetch(`${this.baseUrl}${path}`, {
2368
+ method: "DELETE",
2369
+ headers: { ...this.headers }
2370
+ });
2371
+ if (!res.ok) {
2372
+ throw new Error(`DELETE ${path} failed: ${res.status} ${res.statusText}`);
2373
+ }
2374
+ return res;
2375
+ }
2376
+ // ─── SSE Parser ─────────────────────────────────────────────
2377
+ async *_parseSSEStream(body, signal) {
2378
+ const reader = body.getReader();
2379
+ const decoder = new TextDecoder();
2380
+ let buffer = "";
2381
+ const abortPromise = new Promise((_, reject) => {
2382
+ if (signal.aborted) {
2383
+ reject(new DOMException("Aborted", "AbortError"));
2384
+ return;
2385
+ }
2386
+ signal.addEventListener("abort", () => {
2387
+ reject(new DOMException("Aborted", "AbortError"));
2388
+ }, { once: true });
2389
+ });
2390
+ try {
2391
+ while (true) {
2392
+ if (signal.aborted) break;
2393
+ const { done, value } = await Promise.race([
2394
+ reader.read(),
2395
+ abortPromise
2396
+ ]);
2397
+ if (done) break;
2398
+ buffer += decoder.decode(value, { stream: true });
2399
+ const lines = buffer.split("\n");
2400
+ buffer = lines.pop();
2401
+ for (const line of lines) {
2402
+ if (signal.aborted) return;
2403
+ if (line.startsWith("data: ")) {
2404
+ const data = line.slice(6).trim();
2405
+ if (data === "[DONE]") return;
2406
+ try {
2407
+ yield JSON.parse(data);
2408
+ } catch {
2409
+ }
2410
+ }
2411
+ }
2412
+ }
2413
+ } catch (err) {
2414
+ if (err instanceof DOMException && err.name === "AbortError") ; else {
2415
+ throw err;
2416
+ }
2417
+ } finally {
2418
+ reader.releaseLock();
2419
+ }
2420
+ }
2421
+ };
2422
+
2423
+ // src/chat/react/useRemoteChat.ts
2424
+ function useRemoteChat(options) {
2425
+ const {
2426
+ chatBaseUrl,
2427
+ authBaseUrl,
2428
+ backend,
2429
+ onReady,
2430
+ headers,
2431
+ autoRestore = true
2432
+ } = options;
2433
+ const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
2434
+ const [phase, setPhase] = react.useState("initializing");
2435
+ const [runtime, setRuntime] = react.useState(null);
2436
+ const [sessionId, setSessionId] = react.useState(null);
2437
+ const [error, setError] = react.useState(null);
2438
+ const onReadyRef = react.useRef(onReady);
2439
+ onReadyRef.current = onReady;
2440
+ const mountedRef = react.useRef(true);
2441
+ react.useEffect(() => {
2442
+ return () => {
2443
+ mountedRef.current = false;
2444
+ };
2445
+ }, []);
2446
+ const auth = useRemoteAuth({
2447
+ backend,
2448
+ baseUrl: authBaseUrl,
2449
+ fetch: fetchFn,
2450
+ headers
2451
+ });
2452
+ const restoredRef = react.useRef(false);
2453
+ const [tokensLoaded, setTokensLoaded] = react.useState(false);
2454
+ react.useEffect(() => {
2455
+ if (!autoRestore || restoredRef.current) return;
2456
+ restoredRef.current = true;
2457
+ (async () => {
2458
+ try {
2459
+ await auth.loadSavedTokens();
2460
+ } catch {
2461
+ }
2462
+ if (mountedRef.current) setTokensLoaded(true);
2463
+ })();
2464
+ }, [autoRestore]);
2465
+ react.useEffect(() => {
2466
+ if (!tokensLoaded) return;
2467
+ if (auth.status === "idle" && auth.savedProviders.includes(backend) && phase === "initializing") {
2468
+ auth.useSavedToken(backend).catch(() => {
2469
+ if (mountedRef.current) setPhase("unauthenticated");
2470
+ });
2471
+ } else if (auth.status === "idle" && !auth.savedProviders.includes(backend) && phase === "initializing") {
2472
+ setPhase("unauthenticated");
2473
+ }
2474
+ }, [tokensLoaded, auth.status, auth.savedProviders, backend, phase]);
2475
+ react.useEffect(() => {
2476
+ if (auth.status === "pending") {
2477
+ setPhase("authenticating");
2478
+ } else if (auth.status === "error" && auth.error) {
2479
+ setError(auth.error);
2480
+ setPhase("error");
2481
+ }
2482
+ }, [auth.status, auth.error]);
2483
+ const creatingRef = react.useRef(false);
2484
+ react.useEffect(() => {
2485
+ if (auth.status !== "authenticated" || creatingRef.current || runtime !== null) return;
2486
+ creatingRef.current = true;
2487
+ setPhase("creating");
2488
+ setError(null);
2489
+ (async () => {
2490
+ try {
2491
+ const rt = new RemoteChatRuntime({
2492
+ baseUrl: chatBaseUrl,
2493
+ headers,
2494
+ fetch: fetchFn
2495
+ });
2496
+ const session = await rt.createSession({});
2497
+ if (!mountedRef.current) return;
2498
+ setRuntime(rt);
2499
+ setSessionId(session.id);
2500
+ setPhase("ready");
2501
+ onReadyRef.current?.();
2502
+ } catch (err) {
2503
+ if (!mountedRef.current) return;
2504
+ creatingRef.current = false;
2505
+ setError(err instanceof Error ? err : new Error(String(err)));
2506
+ setPhase("error");
2507
+ }
2508
+ })();
2509
+ }, [auth.status, chatBaseUrl]);
2510
+ const newSession = react.useCallback(async () => {
2511
+ if (!runtime) throw new Error("Runtime not ready");
2512
+ const session = await runtime.createSession({});
2513
+ setSessionId(session.id);
2514
+ return session.id;
2515
+ }, [runtime]);
2516
+ const logout = react.useCallback(async () => {
2517
+ try {
2518
+ await auth.clearTokens();
2519
+ } catch {
2520
+ }
2521
+ if (runtime) {
2522
+ try {
2523
+ runtime.dispose();
2524
+ } catch {
2525
+ }
2526
+ }
2527
+ auth.reset();
2528
+ setRuntime(null);
2529
+ setSessionId(null);
2530
+ setError(null);
2531
+ setTokensLoaded(false);
2532
+ creatingRef.current = false;
2533
+ restoredRef.current = false;
2534
+ setPhase("unauthenticated");
2535
+ }, [auth.clearTokens, auth.reset, runtime]);
2536
+ return {
2537
+ phase,
2538
+ runtime,
2539
+ sessionId,
2540
+ auth,
2541
+ error,
2542
+ newSession,
2543
+ logout
2544
+ };
2545
+ }
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 });
2568
+ const children = [];
2569
+ const selectorButtons = backends.map(
2570
+ (backend) => react.createElement(
2571
+ "button",
2572
+ {
2573
+ key: backend,
2574
+ 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
+ );
2620
+ }
2621
+ if (auth.deviceCode && auth.verificationUrl) {
2622
+ copilotChildren.push(
2623
+ react.createElement("span", { key: "code", "data-device-code": "true" }, auth.deviceCode)
2624
+ );
2625
+ copilotChildren.push(
2626
+ react.createElement("a", { key: "url", "data-verification-url": "true", href: auth.verificationUrl }, auth.verificationUrl)
2627
+ );
2628
+ }
2629
+ if (auth.status === "pending") {
2630
+ copilotChildren.push(
2631
+ react.createElement("span", { key: "loading", "data-auth-loading": "true" }, "Waiting...")
2632
+ );
2633
+ }
2634
+ flowContent = react.createElement(
2635
+ "div",
2636
+ { "data-auth-flow": "copilot" },
2637
+ ...copilotChildren
2638
+ );
2639
+ }
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)
2666
+ );
2667
+ }
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" },
2684
+ react.createElement(
2685
+ "button",
2686
+ {
2687
+ key: "submit",
2688
+ type: "button",
2689
+ "data-action": "submit-api-key",
2690
+ onClick: () => auth.submitApiKey("")
2691
+ },
2692
+ "Submit API Key"
2693
+ )
2694
+ );
2695
+ }
2696
+ }
2697
+ children.push(react.createElement("div", contentAttrs, flowContent));
2698
+ if (auth.error) {
2699
+ children.push(
2700
+ react.createElement(
2701
+ "div",
2702
+ { key: "error", "data-auth-error-display": "true" },
2703
+ auth.error.message
2704
+ )
2705
+ );
2706
+ }
2707
+ return react.createElement(
2708
+ "div",
2709
+ { "data-auth-dialog": "true", className },
2710
+ ...children
2711
+ );
2712
+ }
2713
+
2714
+ exports.AuthDialog = AuthDialog;
2715
+ exports.ChatProvider = ChatProvider;
2716
+ exports.Composer = Composer;
2717
+ exports.MarkdownRenderer = MarkdownRenderer;
2718
+ exports.Message = Message;
2719
+ exports.ModelSelector = ModelSelector;
2720
+ exports.RemoteChatRuntime = RemoteChatRuntime;
2721
+ exports.ThinkingBlock = ThinkingBlock;
2722
+ exports.Thread = Thread;
2723
+ exports.ThreadList = ThreadList;
2724
+ exports.ThreadProvider = ThreadProvider;
2725
+ exports.ToolCallView = ToolCallView;
2726
+ exports.useAuth = useAuth;
2727
+ exports.useChat = useChat;
2728
+ exports.useChatRuntime = useChatRuntime;
2729
+ exports.useMessages = useMessages;
2730
+ exports.useModels = useModels;
2731
+ exports.useOptionalThreadSlots = useOptionalThreadSlots;
2732
+ exports.useRemoteAuth = useRemoteAuth;
2733
+ exports.useRemoteChat = useRemoteChat;
2734
+ exports.useSSE = useSSE;
2735
+ exports.useSessions = useSessions;
2736
+ exports.useThreadSlots = useThreadSlots;
2737
+ exports.useToolApproval = useToolApproval;
2738
+ //# sourceMappingURL=react.cjs.map
2739
+ //# sourceMappingURL=react.cjs.map