@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.
- package/README.md +433 -6
- package/dist/auth/index.cjs +188 -1
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +154 -138
- package/dist/auth/index.d.ts +154 -138
- package/dist/auth/index.js +188 -2
- package/dist/auth/index.js.map +1 -1
- package/dist/backends/claude.cjs +341 -22
- package/dist/backends/claude.cjs.map +1 -1
- package/dist/backends/claude.d.cts +2 -1
- package/dist/backends/claude.d.ts +2 -1
- package/dist/backends/claude.js +341 -22
- package/dist/backends/claude.js.map +1 -1
- package/dist/backends/copilot.cjs +133 -25
- package/dist/backends/copilot.cjs.map +1 -1
- package/dist/backends/copilot.d.cts +2 -1
- package/dist/backends/copilot.d.ts +2 -1
- package/dist/backends/copilot.js +133 -25
- package/dist/backends/copilot.js.map +1 -1
- package/dist/backends/vercel-ai.cjs +66 -19
- package/dist/backends/vercel-ai.cjs.map +1 -1
- package/dist/backends/vercel-ai.d.cts +1 -1
- package/dist/backends/vercel-ai.d.ts +1 -1
- package/dist/backends/vercel-ai.js +66 -19
- package/dist/backends/vercel-ai.js.map +1 -1
- package/dist/chat/accumulator.cjs +147 -0
- package/dist/chat/accumulator.cjs.map +1 -0
- package/dist/chat/accumulator.d.cts +61 -0
- package/dist/chat/accumulator.d.ts +61 -0
- package/dist/chat/accumulator.js +145 -0
- package/dist/chat/accumulator.js.map +1 -0
- package/dist/chat/backends.cjs +3534 -0
- package/dist/chat/backends.cjs.map +1 -0
- package/dist/chat/backends.d.cts +62 -0
- package/dist/chat/backends.d.ts +62 -0
- package/dist/chat/backends.js +3501 -0
- package/dist/chat/backends.js.map +1 -0
- package/dist/chat/context.cjs +230 -0
- package/dist/chat/context.cjs.map +1 -0
- package/dist/chat/context.d.cts +167 -0
- package/dist/chat/context.d.ts +167 -0
- package/dist/chat/context.js +227 -0
- package/dist/chat/context.js.map +1 -0
- package/dist/chat/core.cjs +282 -0
- package/dist/chat/core.cjs.map +1 -0
- package/dist/chat/core.d.cts +435 -0
- package/dist/chat/core.d.ts +435 -0
- package/dist/chat/core.js +261 -0
- package/dist/chat/core.js.map +1 -0
- package/dist/chat/errors.cjs +251 -0
- package/dist/chat/errors.cjs.map +1 -0
- package/dist/chat/errors.d.cts +122 -0
- package/dist/chat/errors.d.ts +122 -0
- package/dist/chat/errors.js +243 -0
- package/dist/chat/errors.js.map +1 -0
- package/dist/chat/events.cjs +203 -0
- package/dist/chat/events.cjs.map +1 -0
- package/dist/chat/events.d.cts +241 -0
- package/dist/chat/events.d.ts +241 -0
- package/dist/chat/events.js +196 -0
- package/dist/chat/events.js.map +1 -0
- package/dist/chat/index.cjs +5359 -0
- package/dist/chat/index.cjs.map +1 -0
- package/dist/chat/index.d.cts +52 -0
- package/dist/chat/index.d.ts +52 -0
- package/dist/chat/index.js +5296 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/react.cjs +2739 -0
- package/dist/chat/react.cjs.map +1 -0
- package/dist/chat/react.d.cts +619 -0
- package/dist/chat/react.d.ts +619 -0
- package/dist/chat/react.js +2714 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chat/runtime.cjs +1030 -0
- package/dist/chat/runtime.cjs.map +1 -0
- package/dist/chat/runtime.d.cts +118 -0
- package/dist/chat/runtime.d.ts +118 -0
- package/dist/chat/runtime.js +1028 -0
- package/dist/chat/runtime.js.map +1 -0
- package/dist/chat/server.cjs +643 -0
- package/dist/chat/server.cjs.map +1 -0
- package/dist/chat/server.d.cts +287 -0
- package/dist/chat/server.d.ts +287 -0
- package/dist/chat/server.js +617 -0
- package/dist/chat/server.js.map +1 -0
- package/dist/chat/sessions.cjs +398 -0
- package/dist/chat/sessions.cjs.map +1 -0
- package/dist/chat/sessions.d.cts +239 -0
- package/dist/chat/sessions.d.ts +239 -0
- package/dist/chat/sessions.js +394 -0
- package/dist/chat/sessions.js.map +1 -0
- package/dist/chat/state.cjs +177 -0
- package/dist/chat/state.cjs.map +1 -0
- package/dist/chat/state.d.cts +92 -0
- package/dist/chat/state.d.ts +92 -0
- package/dist/chat/state.js +167 -0
- package/dist/chat/state.js.map +1 -0
- package/dist/chat/storage.cjs +240 -0
- package/dist/chat/storage.cjs.map +1 -0
- package/dist/chat/storage.d.cts +191 -0
- package/dist/chat/storage.d.ts +191 -0
- package/dist/chat/storage.js +236 -0
- package/dist/chat/storage.js.map +1 -0
- package/dist/errors-BDLbNu9w.d.cts +13 -0
- package/dist/errors-BDLbNu9w.d.ts +13 -0
- package/dist/in-process-transport-C2oPTYs6.d.ts +223 -0
- package/dist/in-process-transport-DG-w5G6k.d.cts +223 -0
- package/dist/index.cjs +25 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -4
- package/dist/index.d.ts +32 -4
- package/dist/index.js +25 -13
- package/dist/index.js.map +1 -1
- package/dist/transport-D1OaUgRk.d.ts +67 -0
- package/dist/transport-DX1Nhm4N.d.cts +67 -0
- package/dist/types-Bh5AhqD-.d.ts +141 -0
- package/dist/types-CGF7AEX1.d.cts +141 -0
- package/dist/{types-BvwNzZCj.d.cts → types-CqvUAYxt.d.cts} +21 -3
- package/dist/{types-BvwNzZCj.d.ts → types-CqvUAYxt.d.ts} +21 -3
- package/dist/types-DLZzlJxt.d.ts +39 -0
- package/dist/types-tE0CXwBl.d.cts +39 -0
- 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
|