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