@witqq/agent-sdk 0.7.0 → 0.9.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/dist/{types-CqvUAYxt.d.ts → agent-C6H2CgJA.d.cts} +139 -102
- package/dist/{types-CqvUAYxt.d.cts → agent-F7oB6eKp.d.ts} +139 -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 +337 -112
- 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 +337 -112
- package/dist/backends/copilot.js.map +1 -1
- package/dist/backends/mock-llm.cjs +719 -0
- package/dist/backends/mock-llm.cjs.map +1 -0
- package/dist/backends/mock-llm.d.cts +37 -0
- package/dist/backends/mock-llm.d.ts +37 -0
- package/dist/backends/mock-llm.js +717 -0
- package/dist/backends/mock-llm.js.map +1 -0
- package/dist/backends/vercel-ai.cjs +301 -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 +301 -61
- package/dist/backends/vercel-ai.js.map +1 -1
- package/dist/backends-Cno0gZjy.d.cts +114 -0
- package/dist/backends-Cno0gZjy.d.ts +114 -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 +1084 -821
- 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 +1082 -800
- 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 +60 -27
- package/dist/chat/core.cjs.map +1 -1
- package/dist/chat/core.d.cts +41 -382
- package/dist/chat/core.d.ts +41 -382
- package/dist/chat/core.js +58 -28
- 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 +1612 -1125
- 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 +1600 -1097
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react/theme.css +2517 -0
- package/dist/chat/react.cjs +2212 -1158
- package/dist/chat/react.cjs.map +1 -1
- package/dist/chat/react.d.cts +665 -122
- package/dist/chat/react.d.ts +665 -122
- package/dist/chat/react.js +2191 -1156
- package/dist/chat/react.js.map +1 -1
- package/dist/chat/runtime.cjs +405 -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 +405 -186
- package/dist/chat/runtime.js.map +1 -1
- package/dist/chat/server.cjs +2247 -212
- 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 +2234 -213
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/sessions.cjs +64 -66
- 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 +65 -67
- package/dist/chat/sessions.js.map +1 -1
- package/dist/chat/sqlite.cjs +536 -0
- package/dist/chat/sqlite.cjs.map +1 -0
- package/dist/chat/sqlite.d.cts +164 -0
- package/dist/chat/sqlite.d.ts +164 -0
- package/dist/chat/sqlite.js +527 -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 +58 -33
- package/dist/chat/storage.cjs.map +1 -1
- package/dist/chat/storage.d.cts +18 -8
- package/dist/chat/storage.d.ts +18 -8
- package/dist/chat/storage.js +59 -34
- 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-7EIit9Xk.d.ts} +72 -33
- package/dist/{in-process-transport-DG-w5G6k.d.cts → in-process-transport-Ct9YcX8I.d.cts} +72 -33
- package/dist/index.cjs +354 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -123
- package/dist/index.d.ts +294 -123
- package/dist/index.js +347 -60
- 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 +1107 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +144 -0
- package/dist/testing.d.ts +144 -0
- package/dist/testing.js +1101 -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-DLWCN18G.d.cts} +5 -4
- package/dist/{transport-D1OaUgRk.d.ts → transport-DsuS-GeM.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-DgtI1hzh.d.ts +364 -0
- package/dist/types-DkSXALKg.d.cts +364 -0
- package/package.json +41 -5
- package/LICENSE +0 -21
- package/README.md +0 -948
- 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,8 +273,19 @@ 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) {
|
|
286
|
+
if (agentEvent.type === "error" && !agentEvent.recoverable) {
|
|
287
|
+
throw new Error(agentEvent.error || "Stream error");
|
|
288
|
+
}
|
|
836
289
|
accumulator.apply(agentEvent);
|
|
837
290
|
hasEvents = true;
|
|
838
291
|
const snapshot = accumulator.snapshot();
|
|
@@ -887,6 +340,10 @@ function useChat(options = {}) {
|
|
|
887
340
|
const clearError = react.useCallback(() => {
|
|
888
341
|
setError(null);
|
|
889
342
|
}, []);
|
|
343
|
+
const retryLastMessage = react.useCallback(async () => {
|
|
344
|
+
if (!lastUserMessageRef.current || generatingRef.current) return;
|
|
345
|
+
await sendMessage(lastUserMessageRef.current);
|
|
346
|
+
}, [sendMessage]);
|
|
890
347
|
const newSession = react.useCallback(async () => {
|
|
891
348
|
const session = await runtime.createSession({
|
|
892
349
|
config: { model: "", backend: "" }
|
|
@@ -894,6 +351,7 @@ function useChat(options = {}) {
|
|
|
894
351
|
setSessionId(session.id);
|
|
895
352
|
setMessages([]);
|
|
896
353
|
setError(null);
|
|
354
|
+
lastUserMessageRef.current = null;
|
|
897
355
|
return session.id;
|
|
898
356
|
}, [runtime]);
|
|
899
357
|
return {
|
|
@@ -905,7 +363,9 @@ function useChat(options = {}) {
|
|
|
905
363
|
status,
|
|
906
364
|
error,
|
|
907
365
|
clearError,
|
|
908
|
-
|
|
366
|
+
retryLastMessage,
|
|
367
|
+
newSession,
|
|
368
|
+
usage
|
|
909
369
|
};
|
|
910
370
|
}
|
|
911
371
|
var EMPTY_MESSAGES = [];
|
|
@@ -953,7 +413,7 @@ function useMessages(options) {
|
|
|
953
413
|
messagesRef.current = session.messages;
|
|
954
414
|
isLoadedRef.current = true;
|
|
955
415
|
emitChange();
|
|
956
|
-
if (session
|
|
416
|
+
if (isObservableSession(session)) {
|
|
957
417
|
unsubscribe = session.subscribe(() => {
|
|
958
418
|
const snapshot = session.getSnapshot();
|
|
959
419
|
messagesRef.current = snapshot.messages;
|
|
@@ -1025,124 +485,6 @@ function useSessions() {
|
|
|
1025
485
|
}, [fetchSessions]);
|
|
1026
486
|
return { sessions, loading, error, refresh };
|
|
1027
487
|
}
|
|
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
488
|
function parseBlocks(text) {
|
|
1147
489
|
const tokens = [];
|
|
1148
490
|
const lines = text.split("\n");
|
|
@@ -1291,68 +633,370 @@ function MarkdownRenderer(props) {
|
|
|
1291
633
|
const children = tokens.map((token, i) => renderBlock(token, i, props));
|
|
1292
634
|
return react.createElement("div", { "data-md-root": true }, ...children);
|
|
1293
635
|
}
|
|
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");
|
|
636
|
+
function ThinkingBlock({ text, isStreaming, defaultOpen }) {
|
|
637
|
+
const attrs = {
|
|
638
|
+
"data-thinking": "true"
|
|
639
|
+
};
|
|
640
|
+
if (isStreaming) {
|
|
641
|
+
attrs["data-streaming"] = "true";
|
|
1308
642
|
}
|
|
1309
|
-
|
|
643
|
+
if (defaultOpen) {
|
|
644
|
+
attrs.open = true;
|
|
645
|
+
}
|
|
646
|
+
return react.createElement(
|
|
647
|
+
"details",
|
|
648
|
+
attrs,
|
|
649
|
+
react.createElement("summary", null, isStreaming ? "Thinking..." : "Reasoning"),
|
|
650
|
+
react.createElement("div", null, text)
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
function ToolCallView({ part, onApprove, onDeny, renderArgs, renderResult }) {
|
|
654
|
+
const children = [];
|
|
655
|
+
children.push(
|
|
656
|
+
react.createElement(
|
|
657
|
+
"div",
|
|
658
|
+
{ key: "header", "data-tool-header": "true" },
|
|
659
|
+
react.createElement("span", { "data-tool-label": "name" }, part.name),
|
|
660
|
+
react.createElement("span", { "data-tool-label": "status" }, part.status)
|
|
661
|
+
)
|
|
662
|
+
);
|
|
663
|
+
if (part.args !== void 0) {
|
|
664
|
+
children.push(
|
|
665
|
+
renderArgs ? renderArgs(part.args) : react.createElement(
|
|
666
|
+
"details",
|
|
667
|
+
{ key: "args", "data-tool-details": "args" },
|
|
668
|
+
react.createElement("summary", null, "Arguments"),
|
|
669
|
+
react.createElement("pre", { "data-tool-label": "args" }, JSON.stringify(part.args, null, 2))
|
|
670
|
+
)
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
if (part.result !== void 0) {
|
|
674
|
+
children.push(
|
|
675
|
+
renderResult ? renderResult(part.result) : react.createElement(
|
|
676
|
+
"details",
|
|
677
|
+
{ key: "result", "data-tool-details": "result", open: true },
|
|
678
|
+
react.createElement("summary", null, "Result"),
|
|
679
|
+
react.createElement("pre", { "data-tool-label": "result" }, JSON.stringify(part.result, null, 2))
|
|
680
|
+
)
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (part.error) {
|
|
684
|
+
children.push(
|
|
685
|
+
react.createElement("span", { key: "error", "data-tool-label": "error", role: "alert" }, part.error)
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (part.status === "requires_approval") {
|
|
689
|
+
children.push(
|
|
690
|
+
react.createElement(
|
|
691
|
+
"div",
|
|
692
|
+
{ key: "actions", "data-tool-actions": "true" },
|
|
693
|
+
react.createElement("button", { key: "approve", onClick: onApprove, "data-action": "approve" }, "Approve"),
|
|
694
|
+
react.createElement("button", { key: "deny", onClick: onDeny, "data-action": "deny" }, "Deny")
|
|
695
|
+
)
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return react.createElement(
|
|
699
|
+
"div",
|
|
700
|
+
{
|
|
701
|
+
"data-tool-status": part.status,
|
|
702
|
+
"data-tool-name": part.name
|
|
703
|
+
},
|
|
704
|
+
...children
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/chat/react/Message.ts
|
|
709
|
+
function defaultRenderText(part, index) {
|
|
710
|
+
return react.createElement(
|
|
711
|
+
"div",
|
|
712
|
+
{ key: index, "data-part": "text" },
|
|
713
|
+
react.createElement(MarkdownRenderer, { content: part.text })
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
function defaultRenderReasoning(part, index) {
|
|
717
|
+
return react.createElement(ThinkingBlock, {
|
|
718
|
+
key: index,
|
|
719
|
+
text: part.text,
|
|
720
|
+
isStreaming: part.status === "streaming"
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
function defaultRenderToolCall(part, index) {
|
|
724
|
+
return react.createElement(
|
|
725
|
+
"div",
|
|
726
|
+
{ key: index, "data-part": "tool_call" },
|
|
727
|
+
react.createElement(ToolCallView, { part })
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
function defaultRenderSource(part, index) {
|
|
731
|
+
return react.createElement("a", { key: index, href: part.url, "data-part": "source" }, part.title ?? part.url);
|
|
732
|
+
}
|
|
733
|
+
function defaultRenderFile(part, index) {
|
|
734
|
+
return react.createElement("span", { key: index, "data-part": "file" }, part.name);
|
|
735
|
+
}
|
|
736
|
+
function renderPart(props, part, index) {
|
|
737
|
+
switch (part.type) {
|
|
738
|
+
case "text":
|
|
739
|
+
return (props.renderText ?? defaultRenderText)(part, index);
|
|
740
|
+
case "reasoning":
|
|
741
|
+
return (props.renderReasoning ?? defaultRenderReasoning)(part, index);
|
|
742
|
+
case "tool_call":
|
|
743
|
+
return (props.renderToolCall ?? defaultRenderToolCall)(part, index);
|
|
744
|
+
case "source":
|
|
745
|
+
return (props.renderSource ?? defaultRenderSource)(part, index);
|
|
746
|
+
case "file":
|
|
747
|
+
return (props.renderFile ?? defaultRenderFile)(part, index);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function Message(props) {
|
|
751
|
+
const { message } = props;
|
|
752
|
+
const children = message.parts.map((part, i) => renderPart(props, part, i));
|
|
753
|
+
return react.createElement(
|
|
754
|
+
"div",
|
|
755
|
+
{
|
|
756
|
+
"data-role": message.role,
|
|
757
|
+
"data-status": message.status
|
|
758
|
+
},
|
|
759
|
+
...children
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
function useToolApproval(messages, onApprove, onDeny) {
|
|
763
|
+
const pendingRequests = react.useMemo(() => {
|
|
764
|
+
const requests = [];
|
|
765
|
+
for (const msg of messages) {
|
|
766
|
+
for (const part of msg.parts) {
|
|
767
|
+
if (part.type === "tool_call" && part.status === "requires_approval") {
|
|
768
|
+
requests.push({
|
|
769
|
+
toolCallId: part.toolCallId,
|
|
770
|
+
toolName: part.name,
|
|
771
|
+
toolArgs: part.args ?? {},
|
|
772
|
+
messageId: msg.id
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return requests;
|
|
778
|
+
}, [messages]);
|
|
779
|
+
const approve = react.useCallback((toolCallId) => {
|
|
780
|
+
onApprove?.(toolCallId);
|
|
781
|
+
}, [onApprove]);
|
|
782
|
+
const deny = react.useCallback((toolCallId) => {
|
|
783
|
+
onDeny?.(toolCallId);
|
|
784
|
+
}, [onDeny]);
|
|
785
|
+
return { pendingRequests, approve, deny };
|
|
786
|
+
}
|
|
787
|
+
var ThreadSlotsContext = react.createContext(null);
|
|
788
|
+
function ThreadProvider({
|
|
789
|
+
children,
|
|
790
|
+
renderMessage,
|
|
791
|
+
renderToolCall,
|
|
792
|
+
renderThinkingBlock
|
|
793
|
+
}) {
|
|
794
|
+
const value = { renderMessage, renderToolCall, renderThinkingBlock };
|
|
795
|
+
return react.createElement(ThreadSlotsContext.Provider, { value }, children);
|
|
796
|
+
}
|
|
797
|
+
function useThreadSlots() {
|
|
798
|
+
const ctx = react.useContext(ThreadSlotsContext);
|
|
799
|
+
if (!ctx) {
|
|
800
|
+
throw new Error("useThreadSlots must be used within a ThreadProvider");
|
|
801
|
+
}
|
|
802
|
+
return ctx;
|
|
1310
803
|
}
|
|
1311
804
|
function useOptionalThreadSlots() {
|
|
1312
805
|
return react.useContext(ThreadSlotsContext);
|
|
1313
806
|
}
|
|
807
|
+
function useVirtualMessages(items, options = {}) {
|
|
808
|
+
const { estimatedItemHeight = 80, overscan = 3 } = options;
|
|
809
|
+
const [scrollTop, setScrollTop] = react.useState(0);
|
|
810
|
+
const [containerHeight, setContainerHeight] = react.useState(0);
|
|
811
|
+
const containerElRef = react.useRef(null);
|
|
812
|
+
const containerRef = react.useCallback((el) => {
|
|
813
|
+
containerElRef.current = el;
|
|
814
|
+
if (el) {
|
|
815
|
+
setContainerHeight(el.clientHeight);
|
|
816
|
+
}
|
|
817
|
+
}, []);
|
|
818
|
+
react.useEffect(() => {
|
|
819
|
+
const el = containerElRef.current;
|
|
820
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
821
|
+
const ro = new ResizeObserver(() => {
|
|
822
|
+
setContainerHeight(el.clientHeight);
|
|
823
|
+
});
|
|
824
|
+
ro.observe(el);
|
|
825
|
+
return () => ro.disconnect();
|
|
826
|
+
}, []);
|
|
827
|
+
const onScroll = react.useCallback(
|
|
828
|
+
(event) => {
|
|
829
|
+
setScrollTop(event.currentTarget.scrollTop);
|
|
830
|
+
setContainerHeight(event.currentTarget.clientHeight);
|
|
831
|
+
},
|
|
832
|
+
[]
|
|
833
|
+
);
|
|
834
|
+
const result = react.useMemo(() => {
|
|
835
|
+
const totalCount = items.length;
|
|
836
|
+
const totalHeight = totalCount * estimatedItemHeight;
|
|
837
|
+
if (totalCount === 0 || containerHeight === 0) {
|
|
838
|
+
return {
|
|
839
|
+
visibleItems: items.slice(),
|
|
840
|
+
startIndex: 0,
|
|
841
|
+
endIndex: totalCount,
|
|
842
|
+
topSpacerHeight: 0,
|
|
843
|
+
bottomSpacerHeight: 0,
|
|
844
|
+
totalHeight
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
const rawStart = Math.floor(scrollTop / estimatedItemHeight) - overscan;
|
|
848
|
+
const startIndex = Math.max(0, rawStart);
|
|
849
|
+
const visibleCount = Math.ceil(containerHeight / estimatedItemHeight);
|
|
850
|
+
const rawEnd = Math.floor(scrollTop / estimatedItemHeight) + visibleCount + overscan;
|
|
851
|
+
const endIndex = Math.min(totalCount, rawEnd);
|
|
852
|
+
return {
|
|
853
|
+
visibleItems: items.slice(startIndex, endIndex),
|
|
854
|
+
startIndex,
|
|
855
|
+
endIndex,
|
|
856
|
+
topSpacerHeight: startIndex * estimatedItemHeight,
|
|
857
|
+
bottomSpacerHeight: (totalCount - endIndex) * estimatedItemHeight,
|
|
858
|
+
totalHeight
|
|
859
|
+
};
|
|
860
|
+
}, [items, scrollTop, containerHeight, estimatedItemHeight, overscan]);
|
|
861
|
+
return {
|
|
862
|
+
...result,
|
|
863
|
+
onScroll,
|
|
864
|
+
containerRef
|
|
865
|
+
};
|
|
866
|
+
}
|
|
1314
867
|
|
|
1315
868
|
// src/chat/react/Thread.ts
|
|
1316
869
|
function Thread({
|
|
1317
870
|
messages,
|
|
1318
871
|
isGenerating,
|
|
1319
872
|
autoScroll = true,
|
|
1320
|
-
className
|
|
873
|
+
className,
|
|
874
|
+
virtualize
|
|
1321
875
|
}) {
|
|
1322
876
|
const sentinelRef = react.useRef(null);
|
|
1323
877
|
const containerRef = react.useRef(null);
|
|
1324
878
|
const [userScrolledUp, setUserScrolledUp] = react.useState(false);
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
879
|
+
const isScrollingProgrammatically = react.useRef(false);
|
|
880
|
+
const isVirtualized = virtualize != null && virtualize !== false;
|
|
881
|
+
const virtualizeOpts = virtualize === true ? {} : !isVirtualized ? false : virtualize;
|
|
882
|
+
const virtual = useVirtualMessages(
|
|
883
|
+
messages,
|
|
884
|
+
virtualizeOpts || void 0
|
|
885
|
+
);
|
|
886
|
+
const handleScroll = react.useCallback(
|
|
887
|
+
(e) => {
|
|
888
|
+
if (isVirtualized) {
|
|
889
|
+
virtual.onScroll(e);
|
|
890
|
+
}
|
|
891
|
+
if (isScrollingProgrammatically.current) return;
|
|
892
|
+
const el = containerRef.current;
|
|
893
|
+
if (!el) return;
|
|
894
|
+
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 1;
|
|
895
|
+
setUserScrolledUp(!atBottom);
|
|
896
|
+
},
|
|
897
|
+
[isVirtualized, virtual.onScroll]
|
|
898
|
+
);
|
|
899
|
+
const scrollToBottom = react.useCallback(() => {
|
|
900
|
+
isScrollingProgrammatically.current = true;
|
|
901
|
+
sentinelRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
902
|
+
setUserScrolledUp(false);
|
|
903
|
+
setTimeout(() => {
|
|
904
|
+
isScrollingProgrammatically.current = false;
|
|
905
|
+
}, 500);
|
|
1330
906
|
}, []);
|
|
1331
907
|
react.useEffect(() => {
|
|
1332
908
|
if (!autoScroll || userScrolledUp) return;
|
|
1333
909
|
sentinelRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
1334
910
|
}, [messages, autoScroll, userScrolledUp]);
|
|
1335
911
|
const slots = useOptionalThreadSlots();
|
|
912
|
+
const mergedRef = react.useCallback(
|
|
913
|
+
(el) => {
|
|
914
|
+
containerRef.current = el;
|
|
915
|
+
if (isVirtualized) {
|
|
916
|
+
virtual.containerRef(el);
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
[isVirtualized, virtual.containerRef]
|
|
920
|
+
);
|
|
1336
921
|
const attrs = { "data-thread": "true", className };
|
|
1337
922
|
if (isGenerating) {
|
|
1338
923
|
attrs["data-thread-loading"] = "true";
|
|
1339
924
|
}
|
|
1340
|
-
|
|
925
|
+
if (virtualizeOpts) {
|
|
926
|
+
attrs["data-thread-virtualized"] = "true";
|
|
927
|
+
}
|
|
928
|
+
attrs.ref = mergedRef;
|
|
1341
929
|
attrs.onScroll = handleScroll;
|
|
1342
|
-
const children =
|
|
1343
|
-
|
|
930
|
+
const children = [];
|
|
931
|
+
if (messages.length === 0 && !isGenerating) {
|
|
932
|
+
children.push(
|
|
933
|
+
react.createElement(
|
|
934
|
+
"div",
|
|
935
|
+
{ key: "__empty", "data-thread-empty": "true" },
|
|
936
|
+
"Start a conversation"
|
|
937
|
+
)
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
const renderMessages = virtualizeOpts ? virtual.visibleItems : messages;
|
|
941
|
+
const startOffset = virtualizeOpts ? virtual.startIndex : 0;
|
|
942
|
+
if (virtualizeOpts && virtual.topSpacerHeight > 0) {
|
|
943
|
+
children.push(
|
|
944
|
+
react.createElement("div", {
|
|
945
|
+
key: "__virtual-top",
|
|
946
|
+
"data-virtual-spacer": "top",
|
|
947
|
+
style: { height: virtual.topSpacerHeight }
|
|
948
|
+
})
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
for (let i = 0; i < renderMessages.length; i++) {
|
|
952
|
+
const msg = renderMessages[i];
|
|
953
|
+
const originalIndex = startOffset + i;
|
|
954
|
+
const content = slots?.renderMessage ? slots.renderMessage(msg, originalIndex) : react.createElement(Message, {
|
|
1344
955
|
key: msg.id,
|
|
1345
956
|
message: msg,
|
|
1346
957
|
renderToolCall: slots?.renderToolCall,
|
|
1347
958
|
renderReasoning: slots?.renderThinkingBlock ? (part, idx) => slots.renderThinkingBlock(part, idx) : void 0
|
|
1348
959
|
});
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
960
|
+
children.push(
|
|
961
|
+
react.createElement(
|
|
962
|
+
"div",
|
|
963
|
+
{ key: msg.id, "data-thread-message": "true", "data-role": msg.role },
|
|
964
|
+
content
|
|
965
|
+
)
|
|
1353
966
|
);
|
|
1354
|
-
}
|
|
967
|
+
}
|
|
968
|
+
if (virtualizeOpts && virtual.bottomSpacerHeight > 0) {
|
|
969
|
+
children.push(
|
|
970
|
+
react.createElement("div", {
|
|
971
|
+
key: "__virtual-bottom",
|
|
972
|
+
"data-virtual-spacer": "bottom",
|
|
973
|
+
style: { height: virtual.bottomSpacerHeight }
|
|
974
|
+
})
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
if (isGenerating) {
|
|
978
|
+
children.push(
|
|
979
|
+
react.createElement(
|
|
980
|
+
"div",
|
|
981
|
+
{ key: "__loading", "data-thread-loading-indicator": "true" },
|
|
982
|
+
react.createElement("span", null),
|
|
983
|
+
react.createElement("span", null),
|
|
984
|
+
react.createElement("span", null)
|
|
985
|
+
)
|
|
986
|
+
);
|
|
987
|
+
}
|
|
1355
988
|
children.push(react.createElement("div", { key: "__sentinel", ref: sentinelRef }));
|
|
989
|
+
if (userScrolledUp) {
|
|
990
|
+
children.push(
|
|
991
|
+
react.createElement("button", {
|
|
992
|
+
key: "__scroll-to-bottom",
|
|
993
|
+
"data-action": "scroll-to-bottom",
|
|
994
|
+
type: "button",
|
|
995
|
+
onClick: scrollToBottom,
|
|
996
|
+
"aria-label": "Scroll to bottom"
|
|
997
|
+
})
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1356
1000
|
return react.createElement("div", attrs, ...children);
|
|
1357
1001
|
}
|
|
1358
1002
|
function Composer({
|
|
@@ -1399,21 +1043,20 @@ function Composer({
|
|
|
1399
1043
|
[]
|
|
1400
1044
|
);
|
|
1401
1045
|
const children = [
|
|
1402
|
-
react.createElement(
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
react.createElement(
|
|
1046
|
+
react.createElement(
|
|
1047
|
+
"div",
|
|
1048
|
+
{ key: "input-wrapper", "data-input": "" },
|
|
1049
|
+
react.createElement("textarea", {
|
|
1050
|
+
ref: textareaRef,
|
|
1051
|
+
value,
|
|
1052
|
+
onChange: handleChange,
|
|
1053
|
+
onKeyDown: handleKeyDown,
|
|
1054
|
+
placeholder,
|
|
1055
|
+
disabled: disabled || false,
|
|
1056
|
+
"aria-label": "Message input",
|
|
1057
|
+
rows: 1
|
|
1058
|
+
}),
|
|
1059
|
+
isGenerating ? react.createElement(
|
|
1417
1060
|
"button",
|
|
1418
1061
|
{
|
|
1419
1062
|
key: "stop",
|
|
@@ -1422,11 +1065,7 @@ function Composer({
|
|
|
1422
1065
|
type: "button"
|
|
1423
1066
|
},
|
|
1424
1067
|
"Stop"
|
|
1425
|
-
)
|
|
1426
|
-
);
|
|
1427
|
-
} else {
|
|
1428
|
-
children.push(
|
|
1429
|
-
react.createElement(
|
|
1068
|
+
) : react.createElement(
|
|
1430
1069
|
"button",
|
|
1431
1070
|
{
|
|
1432
1071
|
key: "send",
|
|
@@ -1437,8 +1076,8 @@ function Composer({
|
|
|
1437
1076
|
},
|
|
1438
1077
|
"Send"
|
|
1439
1078
|
)
|
|
1440
|
-
)
|
|
1441
|
-
|
|
1079
|
+
)
|
|
1080
|
+
];
|
|
1442
1081
|
return react.createElement(
|
|
1443
1082
|
"div",
|
|
1444
1083
|
{ "data-composer": "true", className },
|
|
@@ -1448,6 +1087,20 @@ function Composer({
|
|
|
1448
1087
|
function isFullSession(item) {
|
|
1449
1088
|
return "messages" in item && Array.isArray(item.messages);
|
|
1450
1089
|
}
|
|
1090
|
+
function formatRelativeTime(date) {
|
|
1091
|
+
const now = Date.now();
|
|
1092
|
+
const diff = now - date.getTime();
|
|
1093
|
+
const seconds = Math.floor(diff / 1e3);
|
|
1094
|
+
if (seconds < 60) return "now";
|
|
1095
|
+
const minutes = Math.floor(seconds / 60);
|
|
1096
|
+
if (minutes < 60) return `${minutes}m`;
|
|
1097
|
+
const hours = Math.floor(minutes / 60);
|
|
1098
|
+
if (hours < 24) return `${hours}h`;
|
|
1099
|
+
const days = Math.floor(hours / 24);
|
|
1100
|
+
if (days < 30) return `${days}d`;
|
|
1101
|
+
const months = Math.floor(days / 30);
|
|
1102
|
+
return `${months}mo`;
|
|
1103
|
+
}
|
|
1451
1104
|
function normalizeSession(item) {
|
|
1452
1105
|
if (isFullSession(item)) {
|
|
1453
1106
|
return {
|
|
@@ -1485,32 +1138,38 @@ function ThreadList({
|
|
|
1485
1138
|
return normalized.filter((s) => (s.title ?? "").toLowerCase().includes(q));
|
|
1486
1139
|
}, [normalized, searchQuery]);
|
|
1487
1140
|
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
1141
|
children.push(
|
|
1498
1142
|
react.createElement(
|
|
1499
|
-
"
|
|
1500
|
-
{
|
|
1501
|
-
|
|
1502
|
-
"data-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1143
|
+
"div",
|
|
1144
|
+
{ key: "header", "data-thread-list-header": "true" },
|
|
1145
|
+
react.createElement("input", {
|
|
1146
|
+
"data-thread-list-search": "true",
|
|
1147
|
+
value: searchQuery ?? "",
|
|
1148
|
+
onChange: handleSearchChange,
|
|
1149
|
+
placeholder: "Search..."
|
|
1150
|
+
}),
|
|
1151
|
+
react.createElement(
|
|
1152
|
+
"button",
|
|
1153
|
+
{
|
|
1154
|
+
"data-action": "create-session",
|
|
1155
|
+
onClick: onCreate,
|
|
1156
|
+
type: "button"
|
|
1157
|
+
},
|
|
1158
|
+
"+"
|
|
1159
|
+
)
|
|
1507
1160
|
)
|
|
1508
1161
|
);
|
|
1509
1162
|
const items = filtered.map((session) => {
|
|
1510
1163
|
const isActive = session.id === activeSessionId;
|
|
1511
1164
|
const itemChildren = [
|
|
1512
|
-
react.createElement("span", { key: "title" }, session.title ?? "Untitled")
|
|
1165
|
+
react.createElement("span", { key: "title", "data-session-title": "true" }, session.title ?? "Untitled")
|
|
1513
1166
|
];
|
|
1167
|
+
if (session.updatedAt) {
|
|
1168
|
+
const timeStr = formatRelativeTime(new Date(session.updatedAt));
|
|
1169
|
+
itemChildren.push(
|
|
1170
|
+
react.createElement("span", { key: "time", "data-session-time": "true" }, timeStr)
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1514
1173
|
if (onDelete) {
|
|
1515
1174
|
itemChildren.push(
|
|
1516
1175
|
react.createElement(
|
|
@@ -1524,7 +1183,7 @@ function ThreadList({
|
|
|
1524
1183
|
},
|
|
1525
1184
|
type: "button"
|
|
1526
1185
|
},
|
|
1527
|
-
"
|
|
1186
|
+
"\xD7"
|
|
1528
1187
|
)
|
|
1529
1188
|
);
|
|
1530
1189
|
}
|
|
@@ -1534,6 +1193,7 @@ function ThreadList({
|
|
|
1534
1193
|
key: session.id,
|
|
1535
1194
|
"data-session-item": "true",
|
|
1536
1195
|
"data-session-active": isActive ? "true" : "false",
|
|
1196
|
+
"data-session-status": session.status ?? "active",
|
|
1537
1197
|
onClick: () => onSelect(session.id)
|
|
1538
1198
|
},
|
|
1539
1199
|
...itemChildren
|
|
@@ -1584,13 +1244,19 @@ function useSSE(url, options = {}) {
|
|
|
1584
1244
|
setStatus("connecting");
|
|
1585
1245
|
(async () => {
|
|
1586
1246
|
try {
|
|
1587
|
-
const
|
|
1247
|
+
const fetchInit = {
|
|
1248
|
+
method: optionsRef.current.method ?? "GET",
|
|
1588
1249
|
headers: {
|
|
1589
1250
|
Accept: "text/event-stream",
|
|
1590
1251
|
...optionsRef.current.headers
|
|
1591
1252
|
},
|
|
1592
1253
|
signal: controller.signal
|
|
1593
|
-
}
|
|
1254
|
+
};
|
|
1255
|
+
if (fetchInit.method === "POST" && optionsRef.current.body !== void 0) {
|
|
1256
|
+
fetchInit.headers["Content-Type"] = "application/json";
|
|
1257
|
+
fetchInit.body = JSON.stringify(optionsRef.current.body);
|
|
1258
|
+
}
|
|
1259
|
+
const response = await fetch(url, fetchInit);
|
|
1594
1260
|
if (!response.ok) {
|
|
1595
1261
|
throw new Error(`SSE request failed: ${response.status}`);
|
|
1596
1262
|
}
|
|
@@ -1679,7 +1345,8 @@ function useModels() {
|
|
|
1679
1345
|
const mapped = result.map((m) => ({
|
|
1680
1346
|
id: m.id,
|
|
1681
1347
|
name: m.name ?? m.id,
|
|
1682
|
-
tier: m.provider
|
|
1348
|
+
tier: m.provider,
|
|
1349
|
+
provider: m.provider
|
|
1683
1350
|
}));
|
|
1684
1351
|
setModels(mapped);
|
|
1685
1352
|
} catch (err) {
|
|
@@ -1712,11 +1379,13 @@ function ModelSelector({
|
|
|
1712
1379
|
selectedModel,
|
|
1713
1380
|
onSelect,
|
|
1714
1381
|
placeholder = "Select model",
|
|
1715
|
-
className
|
|
1382
|
+
className,
|
|
1383
|
+
allowFreeText = true
|
|
1716
1384
|
}) {
|
|
1717
1385
|
const [open, setOpen] = react.useState(false);
|
|
1718
1386
|
const [search, setSearch] = react.useState("");
|
|
1719
1387
|
const [highlightIndex, setHighlightIndex] = react.useState(0);
|
|
1388
|
+
const [freeText, setFreeText] = react.useState(selectedModel ?? "");
|
|
1720
1389
|
const containerRef = react.useRef(null);
|
|
1721
1390
|
const filtered = react.useMemo(() => {
|
|
1722
1391
|
if (!search) return models;
|
|
@@ -1770,6 +1439,42 @@ function ModelSelector({
|
|
|
1770
1439
|
},
|
|
1771
1440
|
[filtered, highlightIndex, handleSelect]
|
|
1772
1441
|
);
|
|
1442
|
+
if (models.length === 0 && allowFreeText) {
|
|
1443
|
+
return react.createElement(
|
|
1444
|
+
"div",
|
|
1445
|
+
{
|
|
1446
|
+
"data-model-selector": "true",
|
|
1447
|
+
"data-model-selector-freetext": "true",
|
|
1448
|
+
className,
|
|
1449
|
+
ref: containerRef
|
|
1450
|
+
},
|
|
1451
|
+
react.createElement("input", {
|
|
1452
|
+
"data-model-input": "true",
|
|
1453
|
+
value: freeText,
|
|
1454
|
+
placeholder: "Enter model name...",
|
|
1455
|
+
onChange: (e) => setFreeText(e.target.value),
|
|
1456
|
+
onKeyDown: (e) => {
|
|
1457
|
+
if (e.key === "Enter") {
|
|
1458
|
+
e.preventDefault();
|
|
1459
|
+
const val = freeText.trim();
|
|
1460
|
+
if (val) onSelect(val);
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}),
|
|
1464
|
+
react.createElement(
|
|
1465
|
+
"button",
|
|
1466
|
+
{
|
|
1467
|
+
type: "button",
|
|
1468
|
+
"data-action": "apply-model",
|
|
1469
|
+
onClick: () => {
|
|
1470
|
+
const val = freeText.trim();
|
|
1471
|
+
if (val) onSelect(val);
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
"Apply"
|
|
1475
|
+
)
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1773
1478
|
const children = [];
|
|
1774
1479
|
children.push(
|
|
1775
1480
|
react.createElement(
|
|
@@ -1796,6 +1501,7 @@ function ModelSelector({
|
|
|
1796
1501
|
autoFocus: true
|
|
1797
1502
|
})
|
|
1798
1503
|
);
|
|
1504
|
+
const hasMultipleProviders = new Set(filtered.map((m) => m.provider).filter(Boolean)).size > 1;
|
|
1799
1505
|
filtered.forEach((model, idx) => {
|
|
1800
1506
|
const isSelected = model.id === selectedModel;
|
|
1801
1507
|
const isHighlighted = idx === highlightIndex;
|
|
@@ -1807,13 +1513,17 @@ function ModelSelector({
|
|
|
1807
1513
|
if (model.tier) {
|
|
1808
1514
|
attrs["data-tier"] = model.tier;
|
|
1809
1515
|
}
|
|
1516
|
+
if (model.provider && hasMultipleProviders) {
|
|
1517
|
+
attrs["data-model-provider"] = model.provider;
|
|
1518
|
+
}
|
|
1810
1519
|
if (isSelected) {
|
|
1811
1520
|
attrs["data-model-selected"] = "true";
|
|
1812
1521
|
}
|
|
1813
1522
|
if (isHighlighted) {
|
|
1814
1523
|
attrs["data-model-highlighted"] = "true";
|
|
1815
1524
|
}
|
|
1816
|
-
|
|
1525
|
+
const label = model.provider && hasMultipleProviders ? `${model.name} (${model.provider})` : model.name;
|
|
1526
|
+
dropdownChildren.push(react.createElement("div", attrs, label));
|
|
1817
1527
|
});
|
|
1818
1528
|
children.push(
|
|
1819
1529
|
react.createElement(
|
|
@@ -1833,124 +1543,203 @@ function ModelSelector({
|
|
|
1833
1543
|
...children
|
|
1834
1544
|
);
|
|
1835
1545
|
}
|
|
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;
|
|
1546
|
+
function useCopilotAuth(options) {
|
|
1547
|
+
const { baseUrl, headers } = options;
|
|
1548
|
+
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1848
1549
|
const [status, setStatus] = react.useState("idle");
|
|
1849
1550
|
const [error, setError] = react.useState(null);
|
|
1850
1551
|
const [token, setToken] = react.useState(null);
|
|
1851
1552
|
const [deviceCode, setDeviceCode] = react.useState(null);
|
|
1852
1553
|
const [verificationUrl, setVerificationUrl] = react.useState(null);
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1554
|
+
const onAuthenticatedRef = react.useRef(options.onAuthenticated);
|
|
1555
|
+
onAuthenticatedRef.current = options.onAuthenticated;
|
|
1556
|
+
const onErrorRef = react.useRef(options.onError);
|
|
1557
|
+
onErrorRef.current = options.onError;
|
|
1558
|
+
const post = react.useCallback(
|
|
1559
|
+
async (path, body) => {
|
|
1560
|
+
const res = await fetchFn(`${baseUrl}${path}`, {
|
|
1561
|
+
method: "POST",
|
|
1562
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
1563
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1564
|
+
});
|
|
1565
|
+
const data = await res.json();
|
|
1566
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
1567
|
+
return data;
|
|
1568
|
+
},
|
|
1569
|
+
[baseUrl, fetchFn, headers]
|
|
1570
|
+
);
|
|
1571
|
+
const start = react.useCallback(async () => {
|
|
1859
1572
|
setStatus("pending");
|
|
1860
1573
|
setError(null);
|
|
1861
1574
|
try {
|
|
1862
|
-
const
|
|
1863
|
-
const auth = new CopilotAuth2();
|
|
1864
|
-
const result = await auth.startDeviceFlow();
|
|
1575
|
+
const result = await post("/auth/start", { provider: "copilot" });
|
|
1865
1576
|
setDeviceCode(result.userCode);
|
|
1866
1577
|
setVerificationUrl(result.verificationUrl);
|
|
1867
|
-
|
|
1578
|
+
await post("/auth/copilot/poll");
|
|
1579
|
+
const authToken = {
|
|
1580
|
+
accessToken: "server-managed",
|
|
1581
|
+
tokenType: "bearer",
|
|
1582
|
+
obtainedAt: Date.now()
|
|
1583
|
+
};
|
|
1868
1584
|
setToken(authToken);
|
|
1869
1585
|
setStatus("authenticated");
|
|
1870
1586
|
onAuthenticatedRef.current?.(authToken);
|
|
1871
1587
|
} catch (err) {
|
|
1872
|
-
|
|
1588
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1589
|
+
setError(e);
|
|
1873
1590
|
setStatus("error");
|
|
1591
|
+
onErrorRef.current?.(e);
|
|
1874
1592
|
}
|
|
1875
|
-
}, [
|
|
1876
|
-
const
|
|
1877
|
-
|
|
1593
|
+
}, [post]);
|
|
1594
|
+
const reset = react.useCallback(() => {
|
|
1595
|
+
setStatus("idle");
|
|
1596
|
+
setError(null);
|
|
1597
|
+
setToken(null);
|
|
1598
|
+
setDeviceCode(null);
|
|
1599
|
+
setVerificationUrl(null);
|
|
1600
|
+
}, []);
|
|
1601
|
+
return { status, error, token, deviceCode, verificationUrl, start, reset };
|
|
1602
|
+
}
|
|
1603
|
+
function useClaudeAuth(options) {
|
|
1604
|
+
const { baseUrl, headers } = options;
|
|
1605
|
+
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1606
|
+
const [status, setStatus] = react.useState("idle");
|
|
1607
|
+
const [error, setError] = react.useState(null);
|
|
1608
|
+
const [token, setToken] = react.useState(null);
|
|
1609
|
+
const [authorizeUrl, setAuthorizeUrl] = react.useState(null);
|
|
1610
|
+
const onAuthenticatedRef = react.useRef(options.onAuthenticated);
|
|
1611
|
+
onAuthenticatedRef.current = options.onAuthenticated;
|
|
1612
|
+
const onErrorRef = react.useRef(options.onError);
|
|
1613
|
+
onErrorRef.current = options.onError;
|
|
1614
|
+
const post = react.useCallback(
|
|
1615
|
+
async (path, body) => {
|
|
1616
|
+
const res = await fetchFn(`${baseUrl}${path}`, {
|
|
1617
|
+
method: "POST",
|
|
1618
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
1619
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1620
|
+
});
|
|
1621
|
+
const data = await res.json();
|
|
1622
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
1623
|
+
return data;
|
|
1624
|
+
},
|
|
1625
|
+
[baseUrl, fetchFn, headers]
|
|
1626
|
+
);
|
|
1627
|
+
const start = react.useCallback(async () => {
|
|
1878
1628
|
setStatus("pending");
|
|
1879
1629
|
setError(null);
|
|
1880
1630
|
try {
|
|
1881
|
-
const
|
|
1882
|
-
const auth = new ClaudeAuth2();
|
|
1883
|
-
const result = auth.startOAuthFlow();
|
|
1631
|
+
const result = await post("/auth/start", { provider: "claude" });
|
|
1884
1632
|
setAuthorizeUrl(result.authorizeUrl);
|
|
1885
|
-
completeAuthRef.current = result.completeAuth;
|
|
1886
1633
|
} catch (err) {
|
|
1887
|
-
|
|
1634
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1635
|
+
setError(e);
|
|
1888
1636
|
setStatus("error");
|
|
1637
|
+
onErrorRef.current?.(e);
|
|
1889
1638
|
}
|
|
1890
|
-
}, [
|
|
1891
|
-
const
|
|
1892
|
-
if (!completeAuthRef.current) return;
|
|
1639
|
+
}, [post]);
|
|
1640
|
+
const complete = react.useCallback(async (codeOrUrl) => {
|
|
1893
1641
|
try {
|
|
1894
|
-
|
|
1642
|
+
await post("/auth/claude/complete", { code: codeOrUrl });
|
|
1643
|
+
const authToken = {
|
|
1644
|
+
accessToken: "server-managed",
|
|
1645
|
+
tokenType: "bearer",
|
|
1646
|
+
obtainedAt: Date.now()
|
|
1647
|
+
};
|
|
1895
1648
|
setToken(authToken);
|
|
1896
1649
|
setStatus("authenticated");
|
|
1897
1650
|
onAuthenticatedRef.current?.(authToken);
|
|
1898
1651
|
} catch (err) {
|
|
1899
|
-
|
|
1652
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1653
|
+
setError(e);
|
|
1900
1654
|
setStatus("error");
|
|
1655
|
+
onErrorRef.current?.(e);
|
|
1901
1656
|
}
|
|
1657
|
+
}, [post]);
|
|
1658
|
+
const reset = react.useCallback(() => {
|
|
1659
|
+
setStatus("idle");
|
|
1660
|
+
setError(null);
|
|
1661
|
+
setToken(null);
|
|
1662
|
+
setAuthorizeUrl(null);
|
|
1902
1663
|
}, []);
|
|
1903
|
-
|
|
1904
|
-
|
|
1664
|
+
return { status, error, token, authorizeUrl, start, complete, reset };
|
|
1665
|
+
}
|
|
1666
|
+
function useApiKeyAuth(options) {
|
|
1667
|
+
const { baseUrl, headers } = options;
|
|
1668
|
+
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1669
|
+
const [status, setStatus] = react.useState("idle");
|
|
1670
|
+
const [error, setError] = react.useState(null);
|
|
1671
|
+
const [token, setToken] = react.useState(null);
|
|
1672
|
+
const onAuthenticatedRef = react.useRef(options.onAuthenticated);
|
|
1673
|
+
onAuthenticatedRef.current = options.onAuthenticated;
|
|
1674
|
+
const onErrorRef = react.useRef(options.onError);
|
|
1675
|
+
onErrorRef.current = options.onError;
|
|
1676
|
+
const submit = react.useCallback(async (key, apiBaseUrl) => {
|
|
1905
1677
|
if (!key || !key.trim()) {
|
|
1906
|
-
|
|
1678
|
+
const e = new Error("API key cannot be empty");
|
|
1679
|
+
setError(e);
|
|
1907
1680
|
setStatus("error");
|
|
1681
|
+
onErrorRef.current?.(e);
|
|
1908
1682
|
return;
|
|
1909
1683
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1684
|
+
setStatus("pending");
|
|
1685
|
+
setError(null);
|
|
1686
|
+
try {
|
|
1687
|
+
const res = await fetchFn(`${baseUrl}/auth/vercel/complete`, {
|
|
1688
|
+
method: "POST",
|
|
1689
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
1690
|
+
body: JSON.stringify({
|
|
1691
|
+
apiKey: key.trim(),
|
|
1692
|
+
...apiBaseUrl ? { baseUrl: apiBaseUrl } : {}
|
|
1693
|
+
})
|
|
1694
|
+
});
|
|
1695
|
+
const data = await res.json();
|
|
1696
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
1697
|
+
const authToken = {
|
|
1698
|
+
accessToken: "server-managed",
|
|
1699
|
+
tokenType: "bearer",
|
|
1700
|
+
obtainedAt: Date.now()
|
|
1701
|
+
};
|
|
1702
|
+
setToken(authToken);
|
|
1703
|
+
setStatus("authenticated");
|
|
1704
|
+
onAuthenticatedRef.current?.(authToken);
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
1707
|
+
setError(e);
|
|
1708
|
+
setStatus("error");
|
|
1709
|
+
onErrorRef.current?.(e);
|
|
1710
|
+
}
|
|
1711
|
+
}, [baseUrl, fetchFn, headers]);
|
|
1919
1712
|
const reset = react.useCallback(() => {
|
|
1920
1713
|
setStatus("idle");
|
|
1921
1714
|
setError(null);
|
|
1922
1715
|
setToken(null);
|
|
1923
|
-
setDeviceCode(null);
|
|
1924
|
-
setVerificationUrl(null);
|
|
1925
|
-
setAuthorizeUrl(null);
|
|
1926
|
-
completeAuthRef.current = null;
|
|
1927
1716
|
}, []);
|
|
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
|
-
};
|
|
1717
|
+
return { status, error, token, submit, reset };
|
|
1941
1718
|
}
|
|
1719
|
+
|
|
1720
|
+
// src/chat/react/useRemoteAuth.ts
|
|
1942
1721
|
function useRemoteAuth(options) {
|
|
1943
1722
|
const { backend, baseUrl, onAuthenticated, headers } = options;
|
|
1944
1723
|
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1945
1724
|
const [status, setStatus] = react.useState("idle");
|
|
1946
1725
|
const [error, setError] = react.useState(null);
|
|
1947
1726
|
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
1727
|
const [savedProviders, setSavedProviders] = react.useState([]);
|
|
1952
1728
|
const onAuthenticatedRef = react.useRef(onAuthenticated);
|
|
1953
1729
|
onAuthenticatedRef.current = onAuthenticated;
|
|
1730
|
+
const handleSuccess = react.useCallback((authToken) => {
|
|
1731
|
+
setToken(authToken);
|
|
1732
|
+
setStatus("authenticated");
|
|
1733
|
+
onAuthenticatedRef.current?.(authToken);
|
|
1734
|
+
}, []);
|
|
1735
|
+
const handleError = react.useCallback((err) => {
|
|
1736
|
+
setError(err);
|
|
1737
|
+
setStatus("error");
|
|
1738
|
+
}, []);
|
|
1739
|
+
const hookOpts = { baseUrl, headers, fetch: fetchFn, onAuthenticated: handleSuccess, onError: handleError };
|
|
1740
|
+
const copilot = useCopilotAuth(hookOpts);
|
|
1741
|
+
const claude = useClaudeAuth(hookOpts);
|
|
1742
|
+
const apiKey = useApiKeyAuth(hookOpts);
|
|
1954
1743
|
const post = react.useCallback(
|
|
1955
1744
|
async (path, body) => {
|
|
1956
1745
|
const res = await fetchFn(`${baseUrl}${path}`, {
|
|
@@ -1980,82 +1769,26 @@ function useRemoteAuth(options) {
|
|
|
1980
1769
|
if (backend !== "copilot") return;
|
|
1981
1770
|
setStatus("pending");
|
|
1982
1771
|
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]);
|
|
1772
|
+
await copilot.start();
|
|
1773
|
+
}, [backend, copilot]);
|
|
2001
1774
|
const startOAuthFlow = react.useCallback(async () => {
|
|
2002
1775
|
if (backend !== "claude") return;
|
|
2003
1776
|
setStatus("pending");
|
|
2004
1777
|
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]);
|
|
1778
|
+
await claude.start();
|
|
1779
|
+
}, [backend, claude]);
|
|
2013
1780
|
const completeOAuth = react.useCallback(
|
|
2014
1781
|
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
|
-
}
|
|
1782
|
+
await claude.complete(codeOrUrl);
|
|
2029
1783
|
},
|
|
2030
|
-
[
|
|
1784
|
+
[claude]
|
|
2031
1785
|
);
|
|
2032
1786
|
const submitApiKey = react.useCallback(
|
|
2033
1787
|
async (key, apiBaseUrl) => {
|
|
2034
1788
|
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
|
-
}
|
|
1789
|
+
await apiKey.submit(key, apiBaseUrl);
|
|
2057
1790
|
},
|
|
2058
|
-
[backend,
|
|
1791
|
+
[backend, apiKey]
|
|
2059
1792
|
);
|
|
2060
1793
|
const loadSavedTokens = react.useCallback(async () => {
|
|
2061
1794
|
try {
|
|
@@ -2094,55 +1827,44 @@ function useRemoteAuth(options) {
|
|
|
2094
1827
|
setStatus("idle");
|
|
2095
1828
|
setError(null);
|
|
2096
1829
|
setToken(null);
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
1830
|
+
copilot.reset();
|
|
1831
|
+
claude.reset();
|
|
1832
|
+
apiKey.reset();
|
|
2100
1833
|
setSavedProviders([]);
|
|
2101
|
-
}, []);
|
|
1834
|
+
}, [copilot, claude, apiKey]);
|
|
2102
1835
|
const start = react.useCallback(async (provider) => {
|
|
2103
1836
|
const target = provider ?? backend;
|
|
2104
1837
|
setStatus("pending");
|
|
2105
1838
|
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}`);
|
|
1839
|
+
switch (target) {
|
|
1840
|
+
case "copilot":
|
|
1841
|
+
await copilot.start();
|
|
1842
|
+
break;
|
|
1843
|
+
case "claude":
|
|
1844
|
+
await claude.start();
|
|
1845
|
+
break;
|
|
1846
|
+
case "vercel-ai": {
|
|
1847
|
+
const e = new Error("vercel-ai requires submitApiKey(key, baseUrl) \u2014 cannot auto-start");
|
|
1848
|
+
setError(e);
|
|
1849
|
+
setStatus("error");
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
default: {
|
|
1853
|
+
const e = new Error(`Unknown auth provider: ${target}`);
|
|
1854
|
+
setError(e);
|
|
1855
|
+
setStatus("error");
|
|
1856
|
+
return;
|
|
2132
1857
|
}
|
|
2133
|
-
} catch (err) {
|
|
2134
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2135
|
-
setStatus("error");
|
|
2136
1858
|
}
|
|
2137
|
-
}, [backend,
|
|
1859
|
+
}, [backend, copilot, claude]);
|
|
2138
1860
|
return {
|
|
2139
1861
|
status,
|
|
2140
1862
|
error,
|
|
2141
1863
|
startDeviceFlow,
|
|
2142
|
-
deviceCode,
|
|
2143
|
-
verificationUrl,
|
|
1864
|
+
deviceCode: copilot.deviceCode,
|
|
1865
|
+
verificationUrl: copilot.verificationUrl,
|
|
2144
1866
|
startOAuthFlow,
|
|
2145
|
-
authorizeUrl,
|
|
1867
|
+
authorizeUrl: claude.authorizeUrl,
|
|
2146
1868
|
completeOAuth,
|
|
2147
1869
|
submitApiKey,
|
|
2148
1870
|
start,
|
|
@@ -2155,18 +1877,45 @@ function useRemoteAuth(options) {
|
|
|
2155
1877
|
};
|
|
2156
1878
|
}
|
|
2157
1879
|
|
|
2158
|
-
// src/chat/
|
|
2159
|
-
var
|
|
1880
|
+
// src/chat/listener-set.ts
|
|
1881
|
+
var ListenerSet = class {
|
|
1882
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
1883
|
+
/** Add a listener. Returns an unsubscribe function. */
|
|
1884
|
+
add(callback) {
|
|
1885
|
+
this._listeners.add(callback);
|
|
1886
|
+
return () => {
|
|
1887
|
+
this._listeners.delete(callback);
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
/** Notify all listeners with the given arguments. Errors are isolated per listener. */
|
|
1891
|
+
notify(...args) {
|
|
1892
|
+
for (const cb of this._listeners) {
|
|
1893
|
+
try {
|
|
1894
|
+
cb(...args);
|
|
1895
|
+
} catch {
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
/** Remove all listeners. */
|
|
1900
|
+
clear() {
|
|
1901
|
+
this._listeners.clear();
|
|
1902
|
+
}
|
|
1903
|
+
/** Current number of listeners. */
|
|
1904
|
+
get size() {
|
|
1905
|
+
return this._listeners.size;
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
// src/chat/react/RemoteChatClient.ts
|
|
1910
|
+
var RemoteChatClient = class {
|
|
2160
1911
|
_status = "idle";
|
|
2161
1912
|
_activeSessionId = null;
|
|
2162
|
-
|
|
2163
|
-
_currentModel;
|
|
1913
|
+
_selectedProviderId = null;
|
|
2164
1914
|
_abortController = null;
|
|
2165
|
-
_tools = /* @__PURE__ */ new Map();
|
|
2166
|
-
_middlewares = [];
|
|
2167
1915
|
baseUrl;
|
|
2168
1916
|
headers;
|
|
2169
1917
|
_fetch;
|
|
1918
|
+
_selectionListeners = new ListenerSet();
|
|
2170
1919
|
constructor(options) {
|
|
2171
1920
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
2172
1921
|
this.headers = options.headers ?? {};
|
|
@@ -2185,6 +1934,21 @@ var RemoteChatRuntime = class {
|
|
|
2185
1934
|
throw new Error("Runtime is disposed");
|
|
2186
1935
|
}
|
|
2187
1936
|
}
|
|
1937
|
+
// ─── Provider Selection ────────────────────────────────────
|
|
1938
|
+
get selectedProviderId() {
|
|
1939
|
+
return this._selectedProviderId;
|
|
1940
|
+
}
|
|
1941
|
+
selectProvider(providerId) {
|
|
1942
|
+
this.assertNotDisposed();
|
|
1943
|
+
this._selectedProviderId = providerId;
|
|
1944
|
+
this._notifySelectionChange(providerId);
|
|
1945
|
+
}
|
|
1946
|
+
onSelectionChange(callback) {
|
|
1947
|
+
return this._selectionListeners.add(callback);
|
|
1948
|
+
}
|
|
1949
|
+
_notifySelectionChange(providerId) {
|
|
1950
|
+
this._selectionListeners.notify(providerId);
|
|
1951
|
+
}
|
|
2188
1952
|
// ─── Sessions ───────────────────────────────────────────────
|
|
2189
1953
|
get activeSessionId() {
|
|
2190
1954
|
return this._activeSessionId;
|
|
@@ -2216,16 +1980,22 @@ var RemoteChatRuntime = class {
|
|
|
2216
1980
|
}
|
|
2217
1981
|
this._notifySessionChange();
|
|
2218
1982
|
}
|
|
2219
|
-
|
|
1983
|
+
/**
|
|
1984
|
+
* Fetch context window stats from server.
|
|
1985
|
+
* Returns null if stats not available (e.g. no messages sent yet).
|
|
1986
|
+
*/
|
|
1987
|
+
async getContextStats(sessionId) {
|
|
2220
1988
|
this.assertNotDisposed();
|
|
2221
|
-
await this.
|
|
2222
|
-
|
|
1989
|
+
const res = await this._get(`/sessions/${sessionId}/context-stats`);
|
|
1990
|
+
const data = await res.json();
|
|
1991
|
+
return data;
|
|
2223
1992
|
}
|
|
2224
1993
|
async switchSession(id) {
|
|
2225
1994
|
this.assertNotDisposed();
|
|
2226
1995
|
const session = await this.getSession(id);
|
|
2227
1996
|
if (!session) throw new Error(`Session not found: ${id}`);
|
|
2228
1997
|
this._activeSessionId = session.id;
|
|
1998
|
+
this._notifySessionChange();
|
|
2229
1999
|
return session;
|
|
2230
2000
|
}
|
|
2231
2001
|
// ─── Messaging ──────────────────────────────────────────────
|
|
@@ -2244,7 +2014,8 @@ var RemoteChatRuntime = class {
|
|
|
2244
2014
|
body: JSON.stringify({
|
|
2245
2015
|
sessionId,
|
|
2246
2016
|
message,
|
|
2247
|
-
model: options?.model
|
|
2017
|
+
model: options?.model,
|
|
2018
|
+
providerId: this._selectedProviderId ?? void 0
|
|
2248
2019
|
}),
|
|
2249
2020
|
signal: this._abortController.signal
|
|
2250
2021
|
});
|
|
@@ -2279,64 +2050,43 @@ var RemoteChatRuntime = class {
|
|
|
2279
2050
|
this._post("/abort", {}).catch(() => {
|
|
2280
2051
|
});
|
|
2281
2052
|
}
|
|
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
|
-
}
|
|
2053
|
+
// ─── Discovery ─────────────────────────────────────────────
|
|
2299
2054
|
async listModels() {
|
|
2300
2055
|
this.assertNotDisposed();
|
|
2301
2056
|
const res = await this._get("/models");
|
|
2302
2057
|
return await res.json();
|
|
2303
2058
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
registerTool(tool) {
|
|
2309
|
-
this._tools.set(tool.name, tool);
|
|
2059
|
+
async listBackends() {
|
|
2060
|
+
this.assertNotDisposed();
|
|
2061
|
+
const res = await this._get("/backends");
|
|
2062
|
+
return await res.json();
|
|
2310
2063
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2064
|
+
// ─── Providers ──────────────────────────────────────────────
|
|
2065
|
+
async listProviders() {
|
|
2066
|
+
this.assertNotDisposed();
|
|
2067
|
+
const res = await this._get("/providers");
|
|
2068
|
+
return await res.json();
|
|
2313
2069
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
this.
|
|
2070
|
+
async createProvider(config) {
|
|
2071
|
+
this.assertNotDisposed();
|
|
2072
|
+
const res = await this._post("/providers", config);
|
|
2073
|
+
return await res.json();
|
|
2317
2074
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2075
|
+
async updateProvider(id, changes) {
|
|
2076
|
+
this.assertNotDisposed();
|
|
2077
|
+
await this._put(`/providers/${id}`, changes);
|
|
2321
2078
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2079
|
+
async deleteProvider(id) {
|
|
2080
|
+
this.assertNotDisposed();
|
|
2081
|
+
await this._delete(`/providers/${id}`);
|
|
2325
2082
|
}
|
|
2326
|
-
|
|
2083
|
+
// ─── Session Change Notifications ────────────────────────────
|
|
2084
|
+
_sessionListeners = new ListenerSet();
|
|
2327
2085
|
onSessionChange(callback) {
|
|
2328
|
-
this._sessionListeners.add(callback);
|
|
2329
|
-
return () => {
|
|
2330
|
-
this._sessionListeners.delete(callback);
|
|
2331
|
-
};
|
|
2086
|
+
return this._sessionListeners.add(callback);
|
|
2332
2087
|
}
|
|
2333
2088
|
_notifySessionChange() {
|
|
2334
|
-
|
|
2335
|
-
try {
|
|
2336
|
-
cb();
|
|
2337
|
-
} catch {
|
|
2338
|
-
}
|
|
2339
|
-
}
|
|
2089
|
+
this._sessionListeners.notify();
|
|
2340
2090
|
}
|
|
2341
2091
|
// ─── Internal HTTP helpers ──────────────────────────────────
|
|
2342
2092
|
async _get(path) {
|
|
@@ -2373,14 +2123,28 @@ var RemoteChatRuntime = class {
|
|
|
2373
2123
|
}
|
|
2374
2124
|
return res;
|
|
2375
2125
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2126
|
+
async _put(path, body) {
|
|
2127
|
+
const res = await this._fetch(`${this.baseUrl}${path}`, {
|
|
2128
|
+
method: "PUT",
|
|
2129
|
+
headers: {
|
|
2130
|
+
"Content-Type": "application/json",
|
|
2131
|
+
...this.headers
|
|
2132
|
+
},
|
|
2133
|
+
body: JSON.stringify(body)
|
|
2134
|
+
});
|
|
2135
|
+
if (!res.ok) {
|
|
2136
|
+
throw new Error(`PUT ${path} failed: ${res.status} ${res.statusText}`);
|
|
2137
|
+
}
|
|
2138
|
+
return res;
|
|
2139
|
+
}
|
|
2140
|
+
// ─── SSE Parser ─────────────────────────────────────────────
|
|
2141
|
+
async *_parseSSEStream(body, signal) {
|
|
2142
|
+
const reader = body.getReader();
|
|
2143
|
+
const decoder = new TextDecoder();
|
|
2144
|
+
let buffer = "";
|
|
2145
|
+
const abortPromise = new Promise((_, reject) => {
|
|
2146
|
+
if (signal.aborted) {
|
|
2147
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2384
2148
|
return;
|
|
2385
2149
|
}
|
|
2386
2150
|
signal.addEventListener("abort", () => {
|
|
@@ -2427,8 +2191,7 @@ function useRemoteChat(options) {
|
|
|
2427
2191
|
authBaseUrl,
|
|
2428
2192
|
backend,
|
|
2429
2193
|
onReady,
|
|
2430
|
-
headers
|
|
2431
|
-
autoRestore = true
|
|
2194
|
+
headers
|
|
2432
2195
|
} = options;
|
|
2433
2196
|
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2434
2197
|
const [phase, setPhase] = react.useState("initializing");
|
|
@@ -2452,7 +2215,7 @@ function useRemoteChat(options) {
|
|
|
2452
2215
|
const restoredRef = react.useRef(false);
|
|
2453
2216
|
const [tokensLoaded, setTokensLoaded] = react.useState(false);
|
|
2454
2217
|
react.useEffect(() => {
|
|
2455
|
-
if (
|
|
2218
|
+
if (restoredRef.current) return;
|
|
2456
2219
|
restoredRef.current = true;
|
|
2457
2220
|
(async () => {
|
|
2458
2221
|
try {
|
|
@@ -2461,7 +2224,7 @@ function useRemoteChat(options) {
|
|
|
2461
2224
|
}
|
|
2462
2225
|
if (mountedRef.current) setTokensLoaded(true);
|
|
2463
2226
|
})();
|
|
2464
|
-
}, [
|
|
2227
|
+
}, []);
|
|
2465
2228
|
react.useEffect(() => {
|
|
2466
2229
|
if (!tokensLoaded) return;
|
|
2467
2230
|
if (auth.status === "idle" && auth.savedProviders.includes(backend) && phase === "initializing") {
|
|
@@ -2488,7 +2251,7 @@ function useRemoteChat(options) {
|
|
|
2488
2251
|
setError(null);
|
|
2489
2252
|
(async () => {
|
|
2490
2253
|
try {
|
|
2491
|
-
const rt = new
|
|
2254
|
+
const rt = new RemoteChatClient({
|
|
2492
2255
|
baseUrl: chatBaseUrl,
|
|
2493
2256
|
headers,
|
|
2494
2257
|
fetch: fetchFn
|
|
@@ -2543,197 +2306,1488 @@ function useRemoteChat(options) {
|
|
|
2543
2306
|
logout
|
|
2544
2307
|
};
|
|
2545
2308
|
}
|
|
2546
|
-
function
|
|
2309
|
+
function CopilotAuthForm({ auth, onAuthComplete }) {
|
|
2310
|
+
const children = [];
|
|
2311
|
+
if (auth.status === "idle") {
|
|
2312
|
+
children.push(
|
|
2313
|
+
react.createElement("button", {
|
|
2314
|
+
key: "start",
|
|
2315
|
+
type: "button",
|
|
2316
|
+
"data-action": "start-auth",
|
|
2317
|
+
onClick: () => {
|
|
2318
|
+
auth.startDeviceFlow().then(() => onAuthComplete());
|
|
2319
|
+
}
|
|
2320
|
+
}, "Authenticate with GitHub")
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
if (auth.deviceCode) {
|
|
2324
|
+
children.push(
|
|
2325
|
+
react.createElement("div", { key: "code", "data-device-code": "true" }, auth.deviceCode)
|
|
2326
|
+
);
|
|
2327
|
+
if (auth.verificationUrl) {
|
|
2328
|
+
children.push(
|
|
2329
|
+
react.createElement("a", {
|
|
2330
|
+
key: "url",
|
|
2331
|
+
href: auth.verificationUrl,
|
|
2332
|
+
target: "_blank",
|
|
2333
|
+
rel: "noreferrer"
|
|
2334
|
+
}, "Open GitHub \u2192")
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (auth.status === "pending") {
|
|
2339
|
+
children.push(
|
|
2340
|
+
react.createElement("span", { key: "wait", "data-auth-loading": "true" }, "Waiting...")
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
if (auth.status === "authenticated") {
|
|
2344
|
+
children.push(
|
|
2345
|
+
react.createElement(
|
|
2346
|
+
"div",
|
|
2347
|
+
{ key: "done", "data-auth-success": "true" },
|
|
2348
|
+
"\u2713 Authenticated",
|
|
2349
|
+
react.createElement("button", {
|
|
2350
|
+
type: "button",
|
|
2351
|
+
"data-action": "continue",
|
|
2352
|
+
onClick: onAuthComplete
|
|
2353
|
+
}, "Continue \u2192")
|
|
2354
|
+
)
|
|
2355
|
+
);
|
|
2356
|
+
}
|
|
2357
|
+
if (auth.error) {
|
|
2358
|
+
children.push(
|
|
2359
|
+
react.createElement("div", { key: "error", "data-auth-error-display": "true" }, auth.error.message)
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
return react.createElement("div", { "data-auth-flow": "copilot" }, ...children);
|
|
2363
|
+
}
|
|
2364
|
+
function ClaudeAuthForm({ auth, onAuthComplete }) {
|
|
2365
|
+
const codeRef = react.useRef(null);
|
|
2366
|
+
const children = [];
|
|
2367
|
+
if (auth.status === "idle") {
|
|
2368
|
+
children.push(
|
|
2369
|
+
react.createElement("button", {
|
|
2370
|
+
key: "start",
|
|
2371
|
+
type: "button",
|
|
2372
|
+
"data-action": "start-auth",
|
|
2373
|
+
onClick: () => auth.startOAuthFlow()
|
|
2374
|
+
}, "Authenticate with Claude")
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
if (auth.authorizeUrl) {
|
|
2378
|
+
children.push(
|
|
2379
|
+
react.createElement("a", {
|
|
2380
|
+
key: "url",
|
|
2381
|
+
href: auth.authorizeUrl,
|
|
2382
|
+
target: "_blank",
|
|
2383
|
+
rel: "noreferrer"
|
|
2384
|
+
}, "Open authorization page \u2192")
|
|
2385
|
+
);
|
|
2386
|
+
children.push(
|
|
2387
|
+
react.createElement(
|
|
2388
|
+
"div",
|
|
2389
|
+
{ key: "complete", "data-auth-complete": "true" },
|
|
2390
|
+
react.createElement("input", {
|
|
2391
|
+
ref: codeRef,
|
|
2392
|
+
placeholder: "Paste code or redirect URL..."
|
|
2393
|
+
}),
|
|
2394
|
+
react.createElement("button", {
|
|
2395
|
+
type: "button",
|
|
2396
|
+
"data-action": "complete-auth",
|
|
2397
|
+
onClick: () => {
|
|
2398
|
+
const v = codeRef.current?.value?.trim();
|
|
2399
|
+
if (v) auth.completeOAuth(v).then(() => onAuthComplete());
|
|
2400
|
+
}
|
|
2401
|
+
}, "Submit")
|
|
2402
|
+
)
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
if (auth.status === "authenticated") {
|
|
2406
|
+
children.push(
|
|
2407
|
+
react.createElement(
|
|
2408
|
+
"div",
|
|
2409
|
+
{ key: "done", "data-auth-success": "true" },
|
|
2410
|
+
"\u2713 Authenticated",
|
|
2411
|
+
react.createElement("button", {
|
|
2412
|
+
type: "button",
|
|
2413
|
+
"data-action": "continue",
|
|
2414
|
+
onClick: onAuthComplete
|
|
2415
|
+
}, "Continue \u2192")
|
|
2416
|
+
)
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
if (auth.error) {
|
|
2420
|
+
children.push(
|
|
2421
|
+
react.createElement("div", { key: "error", "data-auth-error-display": "true" }, auth.error.message)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
return react.createElement("div", { "data-auth-flow": "claude" }, ...children);
|
|
2425
|
+
}
|
|
2426
|
+
function VercelAIAuthForm({ auth, onAuthComplete }) {
|
|
2427
|
+
const apiKeyRef = react.useRef(null);
|
|
2428
|
+
const baseUrlRef = react.useRef(null);
|
|
2429
|
+
const children = [];
|
|
2430
|
+
if (auth.status !== "authenticated") {
|
|
2431
|
+
children.push(
|
|
2432
|
+
react.createElement(
|
|
2433
|
+
"div",
|
|
2434
|
+
{ key: "apikey", "data-auth-apikey": "true" },
|
|
2435
|
+
react.createElement("input", {
|
|
2436
|
+
ref: baseUrlRef,
|
|
2437
|
+
placeholder: "Base URL (default: openai.com/v1)"
|
|
2438
|
+
}),
|
|
2439
|
+
react.createElement("input", {
|
|
2440
|
+
ref: apiKeyRef,
|
|
2441
|
+
type: "password",
|
|
2442
|
+
placeholder: "API Key (sk-...)"
|
|
2443
|
+
}),
|
|
2444
|
+
react.createElement("button", {
|
|
2445
|
+
type: "button",
|
|
2446
|
+
"data-action": "submit-apikey",
|
|
2447
|
+
onClick: () => {
|
|
2448
|
+
const k = apiKeyRef.current?.value?.trim();
|
|
2449
|
+
if (k) {
|
|
2450
|
+
auth.submitApiKey(k, baseUrlRef.current?.value?.trim() || void 0).then(() => onAuthComplete());
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}, "Connect")
|
|
2454
|
+
)
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
if (auth.status === "authenticated") {
|
|
2458
|
+
children.push(
|
|
2459
|
+
react.createElement(
|
|
2460
|
+
"div",
|
|
2461
|
+
{ key: "done", "data-auth-success": "true" },
|
|
2462
|
+
"\u2713 Connected",
|
|
2463
|
+
react.createElement("button", {
|
|
2464
|
+
type: "button",
|
|
2465
|
+
"data-action": "continue",
|
|
2466
|
+
onClick: onAuthComplete
|
|
2467
|
+
}, "Continue \u2192")
|
|
2468
|
+
)
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
if (auth.error) {
|
|
2472
|
+
children.push(
|
|
2473
|
+
react.createElement("div", { key: "error", "data-auth-error-display": "true" }, auth.error.message)
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
return react.createElement("div", { "data-auth-flow": "vercel-ai" }, ...children);
|
|
2477
|
+
}
|
|
2478
|
+
function BackendSelector({
|
|
2547
2479
|
backends,
|
|
2548
|
-
|
|
2549
|
-
onBackendChange,
|
|
2550
|
-
onAuthenticated,
|
|
2551
|
-
renderCopilotFlow,
|
|
2552
|
-
renderClaudeFlow,
|
|
2553
|
-
renderApiKeyFlow,
|
|
2480
|
+
onSelect,
|
|
2554
2481
|
className
|
|
2555
2482
|
}) {
|
|
2556
|
-
const
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
const activeBackend = controlledBackend ?? internalBackend;
|
|
2560
|
-
const handleBackendChange = react.useCallback(
|
|
2561
|
-
(backend) => {
|
|
2562
|
-
setInternalBackend(backend);
|
|
2563
|
-
onBackendChange?.(backend);
|
|
2564
|
-
},
|
|
2565
|
-
[onBackendChange]
|
|
2483
|
+
const handleClick = react.useCallback(
|
|
2484
|
+
(name) => () => onSelect(name),
|
|
2485
|
+
[onSelect]
|
|
2566
2486
|
);
|
|
2567
|
-
const
|
|
2568
|
-
const children = [];
|
|
2569
|
-
const selectorButtons = backends.map(
|
|
2487
|
+
const items = backends.map(
|
|
2570
2488
|
(backend) => react.createElement(
|
|
2571
2489
|
"button",
|
|
2572
2490
|
{
|
|
2573
|
-
key: backend,
|
|
2491
|
+
key: backend.name,
|
|
2574
2492
|
type: "button",
|
|
2575
|
-
"data-
|
|
2576
|
-
"data-
|
|
2577
|
-
onClick: (
|
|
2493
|
+
"data-backend-item": "true",
|
|
2494
|
+
"data-backend-name": backend.name,
|
|
2495
|
+
onClick: handleClick(backend.name)
|
|
2578
2496
|
},
|
|
2579
|
-
backend
|
|
2497
|
+
backend.name
|
|
2580
2498
|
)
|
|
2581
2499
|
);
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
...selectorButtons
|
|
2587
|
-
)
|
|
2500
|
+
return react.createElement(
|
|
2501
|
+
"div",
|
|
2502
|
+
{ "data-backend-selector": "true", className },
|
|
2503
|
+
...items
|
|
2588
2504
|
);
|
|
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
|
-
);
|
|
2620
|
-
}
|
|
2621
|
-
if (auth.deviceCode && auth.verificationUrl) {
|
|
2622
|
-
copilotChildren.push(
|
|
2623
|
-
react.createElement("span", { key: "code", "data-device-code": "true" }, auth.deviceCode)
|
|
2624
|
-
);
|
|
2625
|
-
copilotChildren.push(
|
|
2626
|
-
react.createElement("a", { key: "url", "data-verification-url": "true", href: auth.verificationUrl }, auth.verificationUrl)
|
|
2627
|
-
);
|
|
2505
|
+
}
|
|
2506
|
+
function useBackends() {
|
|
2507
|
+
const runtime = useChatRuntime();
|
|
2508
|
+
const [backends, setBackends] = react.useState([]);
|
|
2509
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
2510
|
+
const [error, setError] = react.useState(null);
|
|
2511
|
+
const mountedRef = react.useRef(true);
|
|
2512
|
+
const fetchBackends = react.useCallback(() => {
|
|
2513
|
+
setIsLoading(true);
|
|
2514
|
+
setError(null);
|
|
2515
|
+
try {
|
|
2516
|
+
const result = runtime.listBackends();
|
|
2517
|
+
if (result instanceof Promise) {
|
|
2518
|
+
result.then((data) => {
|
|
2519
|
+
if (mountedRef.current) {
|
|
2520
|
+
setBackends(data);
|
|
2521
|
+
setIsLoading(false);
|
|
2522
|
+
}
|
|
2523
|
+
}).catch((err) => {
|
|
2524
|
+
if (mountedRef.current) {
|
|
2525
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2526
|
+
setIsLoading(false);
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
} else {
|
|
2530
|
+
if (mountedRef.current) {
|
|
2531
|
+
setBackends(result);
|
|
2532
|
+
setIsLoading(false);
|
|
2533
|
+
}
|
|
2628
2534
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
);
|
|
2535
|
+
} catch (err) {
|
|
2536
|
+
if (mountedRef.current) {
|
|
2537
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2538
|
+
setIsLoading(false);
|
|
2633
2539
|
}
|
|
2634
|
-
flowContent = react.createElement(
|
|
2635
|
-
"div",
|
|
2636
|
-
{ "data-auth-flow": "copilot" },
|
|
2637
|
-
...copilotChildren
|
|
2638
|
-
);
|
|
2639
2540
|
}
|
|
2640
|
-
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2541
|
+
}, [runtime]);
|
|
2542
|
+
react.useEffect(() => {
|
|
2543
|
+
mountedRef.current = true;
|
|
2544
|
+
fetchBackends();
|
|
2545
|
+
return () => {
|
|
2546
|
+
mountedRef.current = false;
|
|
2547
|
+
};
|
|
2548
|
+
}, [fetchBackends]);
|
|
2549
|
+
return { backends, isLoading, error, refresh: fetchBackends };
|
|
2550
|
+
}
|
|
2551
|
+
function isProviderCapable(runtime) {
|
|
2552
|
+
return typeof runtime === "object" && runtime !== null && typeof runtime.listProviders === "function";
|
|
2553
|
+
}
|
|
2554
|
+
function useProviders() {
|
|
2555
|
+
const runtime = useChatRuntime();
|
|
2556
|
+
const [providers, setProviders] = react.useState([]);
|
|
2557
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
2558
|
+
const [error, setError] = react.useState(null);
|
|
2559
|
+
const mountedRef = react.useRef(true);
|
|
2560
|
+
const fetchProviders = react.useCallback(() => {
|
|
2561
|
+
setIsLoading(true);
|
|
2562
|
+
setError(null);
|
|
2563
|
+
if (!isProviderCapable(runtime)) {
|
|
2564
|
+
setIsLoading(false);
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
runtime.listProviders().then((data) => {
|
|
2568
|
+
if (mountedRef.current) {
|
|
2569
|
+
setProviders(data);
|
|
2570
|
+
setIsLoading(false);
|
|
2662
2571
|
}
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
);
|
|
2572
|
+
}).catch((err) => {
|
|
2573
|
+
if (mountedRef.current) {
|
|
2574
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2575
|
+
setIsLoading(false);
|
|
2667
2576
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2577
|
+
});
|
|
2578
|
+
}, [runtime]);
|
|
2579
|
+
react.useEffect(() => {
|
|
2580
|
+
mountedRef.current = true;
|
|
2581
|
+
fetchProviders();
|
|
2582
|
+
return () => {
|
|
2583
|
+
mountedRef.current = false;
|
|
2584
|
+
};
|
|
2585
|
+
}, [fetchProviders]);
|
|
2586
|
+
const createProvider = react.useCallback(
|
|
2587
|
+
async (config) => {
|
|
2588
|
+
if (!isProviderCapable(runtime)) return;
|
|
2589
|
+
try {
|
|
2590
|
+
await runtime.createProvider(config);
|
|
2591
|
+
fetchProviders();
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2594
|
+
}
|
|
2595
|
+
},
|
|
2596
|
+
[runtime, fetchProviders]
|
|
2597
|
+
);
|
|
2598
|
+
const updateProvider = react.useCallback(
|
|
2599
|
+
async (id, changes) => {
|
|
2600
|
+
if (!isProviderCapable(runtime)) return;
|
|
2601
|
+
try {
|
|
2602
|
+
await runtime.updateProvider(id, changes);
|
|
2603
|
+
fetchProviders();
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2606
|
+
}
|
|
2607
|
+
},
|
|
2608
|
+
[runtime, fetchProviders]
|
|
2609
|
+
);
|
|
2610
|
+
const deleteProvider = react.useCallback(
|
|
2611
|
+
async (id) => {
|
|
2612
|
+
if (!isProviderCapable(runtime)) return;
|
|
2613
|
+
try {
|
|
2614
|
+
await runtime.deleteProvider(id);
|
|
2615
|
+
fetchProviders();
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
2618
|
+
}
|
|
2619
|
+
},
|
|
2620
|
+
[runtime, fetchProviders]
|
|
2621
|
+
);
|
|
2622
|
+
const selectProvider = react.useCallback(
|
|
2623
|
+
(id) => {
|
|
2624
|
+
if (!isProviderCapable(runtime)) return;
|
|
2625
|
+
runtime.selectProvider(id);
|
|
2626
|
+
},
|
|
2627
|
+
[runtime]
|
|
2628
|
+
);
|
|
2629
|
+
return { providers, isLoading, error, refresh: fetchProviders, createProvider, updateProvider, deleteProvider, selectProvider };
|
|
2630
|
+
}
|
|
2631
|
+
function ProviderSelector({
|
|
2632
|
+
providers,
|
|
2633
|
+
activeProviderId,
|
|
2634
|
+
onSelect,
|
|
2635
|
+
onSettingsClick,
|
|
2636
|
+
className
|
|
2637
|
+
}) {
|
|
2638
|
+
const [open, setOpen] = react.useState(false);
|
|
2639
|
+
const [highlightIndex, setHighlightIndex] = react.useState(0);
|
|
2640
|
+
const containerRef = react.useRef(null);
|
|
2641
|
+
const activeProvider = react.useMemo(
|
|
2642
|
+
() => providers.find((p) => p.id === activeProviderId),
|
|
2643
|
+
[providers, activeProviderId]
|
|
2644
|
+
);
|
|
2645
|
+
react.useEffect(() => {
|
|
2646
|
+
if (!open) return;
|
|
2647
|
+
const handler = (e) => {
|
|
2648
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
2649
|
+
setOpen(false);
|
|
2650
|
+
}
|
|
2651
|
+
};
|
|
2652
|
+
document.addEventListener("mousedown", handler);
|
|
2653
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
2654
|
+
}, [open]);
|
|
2655
|
+
react.useEffect(() => {
|
|
2656
|
+
setHighlightIndex(0);
|
|
2657
|
+
}, [providers.length]);
|
|
2658
|
+
const handleToggle = react.useCallback(() => {
|
|
2659
|
+
setOpen((prev) => {
|
|
2660
|
+
if (!prev) setHighlightIndex(0);
|
|
2661
|
+
return !prev;
|
|
2662
|
+
});
|
|
2663
|
+
}, []);
|
|
2664
|
+
const handleSelect = react.useCallback(
|
|
2665
|
+
(id) => {
|
|
2666
|
+
onSelect(id);
|
|
2667
|
+
setOpen(false);
|
|
2668
|
+
},
|
|
2669
|
+
[onSelect]
|
|
2670
|
+
);
|
|
2671
|
+
const handleKeyDown = react.useCallback(
|
|
2672
|
+
(e) => {
|
|
2673
|
+
if (e.key === "ArrowDown") {
|
|
2674
|
+
e.preventDefault();
|
|
2675
|
+
setHighlightIndex((prev) => Math.min(prev + 1, providers.length - 1));
|
|
2676
|
+
} else if (e.key === "ArrowUp") {
|
|
2677
|
+
e.preventDefault();
|
|
2678
|
+
setHighlightIndex((prev) => Math.max(prev - 1, 0));
|
|
2679
|
+
} else if (e.key === "Enter") {
|
|
2680
|
+
e.preventDefault();
|
|
2681
|
+
if (providers[highlightIndex]) {
|
|
2682
|
+
handleSelect(providers[highlightIndex].id);
|
|
2683
|
+
}
|
|
2684
|
+
} else if (e.key === "Escape") {
|
|
2685
|
+
e.preventDefault();
|
|
2686
|
+
setOpen(false);
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
[providers, highlightIndex, handleSelect]
|
|
2690
|
+
);
|
|
2691
|
+
const children = [];
|
|
2692
|
+
children.push(
|
|
2693
|
+
react.createElement(
|
|
2694
|
+
"button",
|
|
2695
|
+
{
|
|
2696
|
+
key: "trigger",
|
|
2697
|
+
type: "button",
|
|
2698
|
+
"data-provider-trigger": "true",
|
|
2699
|
+
onClick: handleToggle,
|
|
2700
|
+
onKeyDown: handleKeyDown
|
|
2701
|
+
},
|
|
2702
|
+
activeProvider ? activeProvider.label : "Select provider"
|
|
2703
|
+
)
|
|
2704
|
+
);
|
|
2705
|
+
if (open) {
|
|
2706
|
+
const dropdownChildren = [];
|
|
2707
|
+
providers.forEach((provider, idx) => {
|
|
2708
|
+
const isActive = provider.id === activeProviderId;
|
|
2709
|
+
const isHighlighted = idx === highlightIndex;
|
|
2710
|
+
const attrs = {
|
|
2711
|
+
key: provider.id,
|
|
2712
|
+
"data-provider-item": "true",
|
|
2713
|
+
onClick: () => handleSelect(provider.id)
|
|
2714
|
+
};
|
|
2715
|
+
if (isActive) attrs["data-provider-active"] = "true";
|
|
2716
|
+
if (isHighlighted) attrs["data-provider-highlighted"] = "true";
|
|
2717
|
+
dropdownChildren.push(
|
|
2718
|
+
react.createElement(
|
|
2719
|
+
"div",
|
|
2720
|
+
attrs,
|
|
2721
|
+
react.createElement("span", { "data-provider-label": "true" }, provider.label),
|
|
2722
|
+
react.createElement("span", { "data-provider-model": "true" }, provider.model)
|
|
2723
|
+
)
|
|
2672
2724
|
);
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
|
-
|
|
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" },
|
|
2725
|
+
});
|
|
2726
|
+
if (onSettingsClick) {
|
|
2727
|
+
dropdownChildren.push(
|
|
2684
2728
|
react.createElement(
|
|
2685
2729
|
"button",
|
|
2686
2730
|
{
|
|
2687
|
-
key: "
|
|
2731
|
+
key: "settings",
|
|
2688
2732
|
type: "button",
|
|
2689
|
-
"data-
|
|
2690
|
-
onClick: () =>
|
|
2691
|
-
|
|
2692
|
-
|
|
2733
|
+
"data-provider-settings-btn": "true",
|
|
2734
|
+
onClick: (e) => {
|
|
2735
|
+
e.stopPropagation();
|
|
2736
|
+
setOpen(false);
|
|
2737
|
+
onSettingsClick();
|
|
2738
|
+
}
|
|
2739
|
+
},
|
|
2740
|
+
"\u2699 Settings"
|
|
2693
2741
|
)
|
|
2694
2742
|
);
|
|
2695
2743
|
}
|
|
2744
|
+
children.push(
|
|
2745
|
+
react.createElement(
|
|
2746
|
+
"div",
|
|
2747
|
+
{ key: "dropdown", "data-provider-dropdown": "true", onKeyDown: handleKeyDown },
|
|
2748
|
+
...dropdownChildren
|
|
2749
|
+
)
|
|
2750
|
+
);
|
|
2696
2751
|
}
|
|
2697
|
-
|
|
2698
|
-
|
|
2752
|
+
return react.createElement(
|
|
2753
|
+
"div",
|
|
2754
|
+
{
|
|
2755
|
+
"data-provider-selector": "true",
|
|
2756
|
+
className,
|
|
2757
|
+
ref: containerRef
|
|
2758
|
+
},
|
|
2759
|
+
...children
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
function ProviderModelSelector({
|
|
2763
|
+
providers = [],
|
|
2764
|
+
models = [],
|
|
2765
|
+
activeProviderId,
|
|
2766
|
+
selectedModel,
|
|
2767
|
+
onSelectProvider,
|
|
2768
|
+
onSelectModel,
|
|
2769
|
+
onSettingsClick,
|
|
2770
|
+
placeholder = "Select model",
|
|
2771
|
+
className
|
|
2772
|
+
}) {
|
|
2773
|
+
const [open, setOpen] = react.useState(false);
|
|
2774
|
+
const [search, setSearch] = react.useState("");
|
|
2775
|
+
const [highlightIndex, setHighlightIndex] = react.useState(0);
|
|
2776
|
+
const containerRef = react.useRef(null);
|
|
2777
|
+
const isProviderMode = providers.length > 0;
|
|
2778
|
+
const items = react.useMemo(() => {
|
|
2779
|
+
if (isProviderMode) {
|
|
2780
|
+
return providers.map((p) => ({
|
|
2781
|
+
id: p.id,
|
|
2782
|
+
label: p.label,
|
|
2783
|
+
sublabel: p.model,
|
|
2784
|
+
type: "provider"
|
|
2785
|
+
}));
|
|
2786
|
+
}
|
|
2787
|
+
return models.map((m) => ({
|
|
2788
|
+
id: m.id,
|
|
2789
|
+
label: m.name,
|
|
2790
|
+
sublabel: m.provider,
|
|
2791
|
+
tier: m.tier,
|
|
2792
|
+
type: "model"
|
|
2793
|
+
}));
|
|
2794
|
+
}, [isProviderMode, providers, models]);
|
|
2795
|
+
const filtered = react.useMemo(() => {
|
|
2796
|
+
if (!search) return items;
|
|
2797
|
+
const q = search.toLowerCase();
|
|
2798
|
+
return items.filter(
|
|
2799
|
+
(item) => item.label.toLowerCase().includes(q) || item.sublabel && item.sublabel.toLowerCase().includes(q)
|
|
2800
|
+
);
|
|
2801
|
+
}, [items, search]);
|
|
2802
|
+
react.useEffect(() => {
|
|
2803
|
+
setHighlightIndex(0);
|
|
2804
|
+
}, [filtered.length]);
|
|
2805
|
+
react.useEffect(() => {
|
|
2806
|
+
if (!open) return;
|
|
2807
|
+
const handler = (e) => {
|
|
2808
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
2809
|
+
setOpen(false);
|
|
2810
|
+
}
|
|
2811
|
+
};
|
|
2812
|
+
document.addEventListener("mousedown", handler);
|
|
2813
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
2814
|
+
}, [open]);
|
|
2815
|
+
const triggerLabel = react.useMemo(() => {
|
|
2816
|
+
if (isProviderMode && activeProviderId) {
|
|
2817
|
+
const p = providers.find((prov) => prov.id === activeProviderId);
|
|
2818
|
+
return p ? p.label : placeholder;
|
|
2819
|
+
}
|
|
2820
|
+
if (!isProviderMode && selectedModel) {
|
|
2821
|
+
const m = models.find((mod) => mod.id === selectedModel);
|
|
2822
|
+
return m ? m.name : selectedModel;
|
|
2823
|
+
}
|
|
2824
|
+
return placeholder;
|
|
2825
|
+
}, [isProviderMode, activeProviderId, providers, selectedModel, models, placeholder]);
|
|
2826
|
+
const handleToggle = react.useCallback(() => {
|
|
2827
|
+
setOpen((prev) => {
|
|
2828
|
+
if (!prev) {
|
|
2829
|
+
setSearch("");
|
|
2830
|
+
setHighlightIndex(0);
|
|
2831
|
+
}
|
|
2832
|
+
return !prev;
|
|
2833
|
+
});
|
|
2834
|
+
}, []);
|
|
2835
|
+
const handleSelect = react.useCallback(
|
|
2836
|
+
(item) => {
|
|
2837
|
+
if (item.type === "provider" && onSelectProvider) {
|
|
2838
|
+
onSelectProvider(item.id);
|
|
2839
|
+
} else if (item.type === "model" && onSelectModel) {
|
|
2840
|
+
onSelectModel(item.id);
|
|
2841
|
+
}
|
|
2842
|
+
setOpen(false);
|
|
2843
|
+
setSearch("");
|
|
2844
|
+
},
|
|
2845
|
+
[onSelectProvider, onSelectModel]
|
|
2846
|
+
);
|
|
2847
|
+
const handleSearchChange = react.useCallback((e) => {
|
|
2848
|
+
setSearch(e.target.value);
|
|
2849
|
+
}, []);
|
|
2850
|
+
const handleKeyDown = react.useCallback(
|
|
2851
|
+
(e) => {
|
|
2852
|
+
if (e.key === "ArrowDown") {
|
|
2853
|
+
e.preventDefault();
|
|
2854
|
+
setHighlightIndex((prev) => Math.min(prev + 1, filtered.length - 1));
|
|
2855
|
+
} else if (e.key === "ArrowUp") {
|
|
2856
|
+
e.preventDefault();
|
|
2857
|
+
setHighlightIndex((prev) => Math.max(prev - 1, 0));
|
|
2858
|
+
} else if (e.key === "Enter") {
|
|
2859
|
+
e.preventDefault();
|
|
2860
|
+
if (filtered[highlightIndex]) {
|
|
2861
|
+
handleSelect(filtered[highlightIndex]);
|
|
2862
|
+
}
|
|
2863
|
+
} else if (e.key === "Escape") {
|
|
2864
|
+
e.preventDefault();
|
|
2865
|
+
setOpen(false);
|
|
2866
|
+
}
|
|
2867
|
+
},
|
|
2868
|
+
[filtered, highlightIndex, handleSelect]
|
|
2869
|
+
);
|
|
2870
|
+
const children = [];
|
|
2871
|
+
children.push(
|
|
2872
|
+
react.createElement(
|
|
2873
|
+
"button",
|
|
2874
|
+
{
|
|
2875
|
+
key: "trigger",
|
|
2876
|
+
type: "button",
|
|
2877
|
+
"data-pms-trigger": "true",
|
|
2878
|
+
onClick: handleToggle
|
|
2879
|
+
},
|
|
2880
|
+
triggerLabel
|
|
2881
|
+
)
|
|
2882
|
+
);
|
|
2883
|
+
if (open) {
|
|
2884
|
+
const dropdownChildren = [];
|
|
2885
|
+
if (items.length > 3) {
|
|
2886
|
+
dropdownChildren.push(
|
|
2887
|
+
react.createElement("input", {
|
|
2888
|
+
key: "search",
|
|
2889
|
+
"data-pms-search": "true",
|
|
2890
|
+
value: search,
|
|
2891
|
+
onChange: handleSearchChange,
|
|
2892
|
+
onKeyDown: handleKeyDown,
|
|
2893
|
+
placeholder: isProviderMode ? "Search providers..." : "Search models...",
|
|
2894
|
+
autoFocus: true
|
|
2895
|
+
})
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
filtered.forEach((item, idx) => {
|
|
2899
|
+
const isActive = isProviderMode ? item.id === activeProviderId : item.id === selectedModel;
|
|
2900
|
+
const isHighlighted = idx === highlightIndex;
|
|
2901
|
+
const attrs = {
|
|
2902
|
+
key: item.id,
|
|
2903
|
+
"data-pms-item": "true",
|
|
2904
|
+
"data-pms-type": item.type,
|
|
2905
|
+
onClick: () => handleSelect(item)
|
|
2906
|
+
};
|
|
2907
|
+
if (item.tier) attrs["data-tier"] = item.tier;
|
|
2908
|
+
if (isActive) attrs["data-pms-active"] = "true";
|
|
2909
|
+
if (isHighlighted) attrs["data-pms-highlighted"] = "true";
|
|
2910
|
+
const itemChildren = [
|
|
2911
|
+
react.createElement("span", { key: "label", "data-pms-label": "true" }, item.label)
|
|
2912
|
+
];
|
|
2913
|
+
if (item.sublabel) {
|
|
2914
|
+
itemChildren.push(
|
|
2915
|
+
react.createElement("span", { key: "sub", "data-pms-sublabel": "true" }, item.sublabel)
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2918
|
+
dropdownChildren.push(react.createElement("div", attrs, ...itemChildren));
|
|
2919
|
+
});
|
|
2920
|
+
if (onSettingsClick) {
|
|
2921
|
+
dropdownChildren.push(
|
|
2922
|
+
react.createElement(
|
|
2923
|
+
"button",
|
|
2924
|
+
{
|
|
2925
|
+
key: "settings",
|
|
2926
|
+
type: "button",
|
|
2927
|
+
"data-pms-settings": "true",
|
|
2928
|
+
onClick: (e) => {
|
|
2929
|
+
e.stopPropagation();
|
|
2930
|
+
setOpen(false);
|
|
2931
|
+
onSettingsClick();
|
|
2932
|
+
}
|
|
2933
|
+
},
|
|
2934
|
+
"\u2699 Settings"
|
|
2935
|
+
)
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2699
2938
|
children.push(
|
|
2700
2939
|
react.createElement(
|
|
2701
2940
|
"div",
|
|
2702
|
-
{ key: "
|
|
2703
|
-
|
|
2941
|
+
{ key: "dropdown", "data-pms-dropdown": "true", onKeyDown: handleKeyDown },
|
|
2942
|
+
...dropdownChildren
|
|
2704
2943
|
)
|
|
2705
2944
|
);
|
|
2706
2945
|
}
|
|
2707
2946
|
return react.createElement(
|
|
2708
2947
|
"div",
|
|
2709
|
-
{
|
|
2948
|
+
{
|
|
2949
|
+
"data-provider-model-selector": "true",
|
|
2950
|
+
"data-pms-mode": isProviderMode ? "provider" : "model",
|
|
2951
|
+
className,
|
|
2952
|
+
ref: containerRef
|
|
2953
|
+
},
|
|
2710
2954
|
...children
|
|
2711
2955
|
);
|
|
2712
2956
|
}
|
|
2957
|
+
var BACKENDS = [
|
|
2958
|
+
{ id: "copilot", label: "GitHub Copilot" },
|
|
2959
|
+
{ id: "claude", label: "Claude" },
|
|
2960
|
+
{ id: "vercel-ai", label: "Vercel AI" }
|
|
2961
|
+
];
|
|
2962
|
+
var AUTH_FORMS = {
|
|
2963
|
+
copilot: CopilotAuthForm,
|
|
2964
|
+
claude: ClaudeAuthForm,
|
|
2965
|
+
"vercel-ai": VercelAIAuthForm
|
|
2966
|
+
};
|
|
2967
|
+
function ProviderSettings({
|
|
2968
|
+
providers,
|
|
2969
|
+
onClose,
|
|
2970
|
+
onProviderCreated,
|
|
2971
|
+
onProviderDeleted,
|
|
2972
|
+
onProviderUpdated,
|
|
2973
|
+
onAuthCompleted,
|
|
2974
|
+
authBaseUrl = "/api/auth",
|
|
2975
|
+
className
|
|
2976
|
+
}) {
|
|
2977
|
+
const [view, setView] = react.useState("list");
|
|
2978
|
+
const [addStep, setAddStep] = react.useState("select-backend");
|
|
2979
|
+
const [selectedBackend, setSelectedBackend] = react.useState("copilot");
|
|
2980
|
+
const [editingId, setEditingId] = react.useState(null);
|
|
2981
|
+
const [availableModels, setAvailableModels] = react.useState([]);
|
|
2982
|
+
const modelRef = react.useRef(null);
|
|
2983
|
+
const labelRef = react.useRef(null);
|
|
2984
|
+
const editModelRef = react.useRef(null);
|
|
2985
|
+
const editLabelRef = react.useRef(null);
|
|
2986
|
+
const runtime = useChatRuntime();
|
|
2987
|
+
const auth = useRemoteAuth({
|
|
2988
|
+
backend: selectedBackend,
|
|
2989
|
+
baseUrl: authBaseUrl
|
|
2990
|
+
});
|
|
2991
|
+
const handleBackendSelect = react.useCallback((backend) => {
|
|
2992
|
+
setSelectedBackend(backend);
|
|
2993
|
+
auth.reset();
|
|
2994
|
+
setAddStep("auth");
|
|
2995
|
+
}, [auth]);
|
|
2996
|
+
const handleAuthComplete = react.useCallback(() => {
|
|
2997
|
+
onAuthCompleted?.(selectedBackend);
|
|
2998
|
+
setAddStep("configure");
|
|
2999
|
+
}, [selectedBackend, onAuthCompleted]);
|
|
3000
|
+
react.useEffect(() => {
|
|
3001
|
+
if (addStep !== "configure" && view !== "edit") return;
|
|
3002
|
+
const load = async () => {
|
|
3003
|
+
try {
|
|
3004
|
+
const models = await runtime.listModels();
|
|
3005
|
+
setAvailableModels(models);
|
|
3006
|
+
} catch {
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
load();
|
|
3010
|
+
}, [addStep, view, runtime]);
|
|
3011
|
+
const handleCreate = react.useCallback(() => {
|
|
3012
|
+
const modelEl = modelRef.current;
|
|
3013
|
+
const model = modelEl?.value?.trim();
|
|
3014
|
+
const label = labelRef.current?.value?.trim() || model;
|
|
3015
|
+
if (!model) return;
|
|
3016
|
+
const existing = providers.find((p) => p.backend === selectedBackend);
|
|
3017
|
+
if (existing) {
|
|
3018
|
+
onProviderUpdated?.(existing.id, { model, label });
|
|
3019
|
+
} else {
|
|
3020
|
+
onProviderCreated?.({ backend: selectedBackend, model, label });
|
|
3021
|
+
}
|
|
3022
|
+
setView("list");
|
|
3023
|
+
setAddStep("select-backend");
|
|
3024
|
+
setAvailableModels([]);
|
|
3025
|
+
auth.reset();
|
|
3026
|
+
}, [selectedBackend, providers, onProviderCreated, onProviderUpdated, auth]);
|
|
3027
|
+
const handleEdit = react.useCallback((id) => {
|
|
3028
|
+
setEditingId(id);
|
|
3029
|
+
setView("edit");
|
|
3030
|
+
}, []);
|
|
3031
|
+
const handleUpdate = react.useCallback(() => {
|
|
3032
|
+
if (!editingId) return;
|
|
3033
|
+
const modelEl = editModelRef.current;
|
|
3034
|
+
const model = modelEl?.value?.trim();
|
|
3035
|
+
const label = editLabelRef.current?.value?.trim();
|
|
3036
|
+
if (!model && !label) return;
|
|
3037
|
+
const changes = {};
|
|
3038
|
+
if (model) changes.model = model;
|
|
3039
|
+
if (label) changes.label = label;
|
|
3040
|
+
onProviderUpdated?.(editingId, changes);
|
|
3041
|
+
setView("list");
|
|
3042
|
+
setEditingId(null);
|
|
3043
|
+
}, [editingId, onProviderUpdated]);
|
|
3044
|
+
const handleDelete = react.useCallback((id) => {
|
|
3045
|
+
onProviderDeleted?.(id);
|
|
3046
|
+
}, [onProviderDeleted]);
|
|
3047
|
+
const handleStartAdd = react.useCallback(() => {
|
|
3048
|
+
setView("add");
|
|
3049
|
+
setAddStep("select-backend");
|
|
3050
|
+
auth.reset();
|
|
3051
|
+
}, [auth]);
|
|
3052
|
+
if (view === "list") {
|
|
3053
|
+
const items = providers.map(
|
|
3054
|
+
(p) => react.createElement(
|
|
3055
|
+
"div",
|
|
3056
|
+
{ key: p.id, "data-provider-settings-item": "true" },
|
|
3057
|
+
react.createElement("span", { "data-provider-settings-label": "true" }, p.label),
|
|
3058
|
+
react.createElement("span", { "data-provider-settings-model": "true" }, `${p.backend} / ${p.model}`),
|
|
3059
|
+
react.createElement(
|
|
3060
|
+
"div",
|
|
3061
|
+
{ "data-provider-settings-actions": "true" },
|
|
3062
|
+
react.createElement("button", {
|
|
3063
|
+
type: "button",
|
|
3064
|
+
"data-action": "edit-provider",
|
|
3065
|
+
onClick: () => handleEdit(p.id)
|
|
3066
|
+
}, "Edit"),
|
|
3067
|
+
react.createElement("button", {
|
|
3068
|
+
type: "button",
|
|
3069
|
+
"data-action": "delete-provider",
|
|
3070
|
+
onClick: () => handleDelete(p.id)
|
|
3071
|
+
}, "Delete")
|
|
3072
|
+
)
|
|
3073
|
+
)
|
|
3074
|
+
);
|
|
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, "Providers"),
|
|
3082
|
+
onClose ? react.createElement("button", {
|
|
3083
|
+
type: "button",
|
|
3084
|
+
"data-provider-settings-close": "true",
|
|
3085
|
+
onClick: onClose
|
|
3086
|
+
}, "\u2715") : null
|
|
3087
|
+
),
|
|
3088
|
+
react.createElement(
|
|
3089
|
+
"div",
|
|
3090
|
+
{ "data-provider-settings-list": "true" },
|
|
3091
|
+
...items,
|
|
3092
|
+
items.length === 0 ? react.createElement("div", { "data-provider-settings-empty": "true" }, "No providers configured") : null
|
|
3093
|
+
),
|
|
3094
|
+
react.createElement("button", {
|
|
3095
|
+
type: "button",
|
|
3096
|
+
"data-action": "add-provider",
|
|
3097
|
+
onClick: handleStartAdd
|
|
3098
|
+
}, "+ Add Provider")
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
3101
|
+
if (view === "add") {
|
|
3102
|
+
const formChildren = [];
|
|
3103
|
+
formChildren.push(
|
|
3104
|
+
react.createElement(
|
|
3105
|
+
"div",
|
|
3106
|
+
{ "data-provider-settings-header": "true", key: "header" },
|
|
3107
|
+
react.createElement("span", null, "Add Provider"),
|
|
3108
|
+
react.createElement("button", {
|
|
3109
|
+
type: "button",
|
|
3110
|
+
"data-provider-settings-close": "true",
|
|
3111
|
+
onClick: () => {
|
|
3112
|
+
setView("list");
|
|
3113
|
+
setAddStep("select-backend");
|
|
3114
|
+
}
|
|
3115
|
+
}, "\u2190 Back")
|
|
3116
|
+
)
|
|
3117
|
+
);
|
|
3118
|
+
if (addStep === "select-backend") {
|
|
3119
|
+
formChildren.push(
|
|
3120
|
+
react.createElement(
|
|
3121
|
+
"div",
|
|
3122
|
+
{ key: "backends", "data-provider-settings-backends": "true" },
|
|
3123
|
+
...BACKENDS.map(
|
|
3124
|
+
(b) => react.createElement("button", {
|
|
3125
|
+
key: b.id,
|
|
3126
|
+
type: "button",
|
|
3127
|
+
"data-provider-backend-option": b.id,
|
|
3128
|
+
onClick: () => handleBackendSelect(b.id)
|
|
3129
|
+
}, b.label)
|
|
3130
|
+
)
|
|
3131
|
+
)
|
|
3132
|
+
);
|
|
3133
|
+
} else if (addStep === "auth") {
|
|
3134
|
+
const FormComponent = selectedBackend ? AUTH_FORMS[selectedBackend] : null;
|
|
3135
|
+
formChildren.push(
|
|
3136
|
+
react.createElement(
|
|
3137
|
+
"div",
|
|
3138
|
+
{ key: "auth", "data-provider-settings-auth": "true" },
|
|
3139
|
+
FormComponent ? react.createElement(FormComponent, { auth, onAuthComplete: handleAuthComplete }) : null
|
|
3140
|
+
)
|
|
3141
|
+
);
|
|
3142
|
+
} else if (addStep === "configure") {
|
|
3143
|
+
const modelInput = availableModels.length > 0 ? react.createElement(
|
|
3144
|
+
"select",
|
|
3145
|
+
{
|
|
3146
|
+
ref: modelRef,
|
|
3147
|
+
"data-input": "model",
|
|
3148
|
+
defaultValue: ""
|
|
3149
|
+
},
|
|
3150
|
+
react.createElement("option", { value: "", disabled: true }, "Select a model\u2026"),
|
|
3151
|
+
...availableModels.map(
|
|
3152
|
+
(m) => react.createElement("option", { key: m.id, value: m.id }, m.name || m.id)
|
|
3153
|
+
)
|
|
3154
|
+
) : react.createElement("input", {
|
|
3155
|
+
ref: modelRef,
|
|
3156
|
+
placeholder: "Model name (e.g. gpt-5-mini)",
|
|
3157
|
+
"data-input": "model"
|
|
3158
|
+
});
|
|
3159
|
+
formChildren.push(
|
|
3160
|
+
react.createElement(
|
|
3161
|
+
"div",
|
|
3162
|
+
{ key: "config", "data-provider-settings-form": "true" },
|
|
3163
|
+
modelInput,
|
|
3164
|
+
react.createElement("input", {
|
|
3165
|
+
ref: labelRef,
|
|
3166
|
+
placeholder: "Display label (e.g. GPT-5 Mini)",
|
|
3167
|
+
"data-input": "label"
|
|
3168
|
+
}),
|
|
3169
|
+
react.createElement("button", {
|
|
3170
|
+
type: "button",
|
|
3171
|
+
"data-action": "save-provider",
|
|
3172
|
+
onClick: handleCreate
|
|
3173
|
+
}, "Save Provider")
|
|
3174
|
+
)
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
return react.createElement(
|
|
3178
|
+
"div",
|
|
3179
|
+
{ "data-provider-settings": "true", className },
|
|
3180
|
+
...formChildren
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
const editingProvider = providers.find((p) => p.id === editingId);
|
|
3184
|
+
return react.createElement(
|
|
3185
|
+
"div",
|
|
3186
|
+
{ "data-provider-settings": "true", className },
|
|
3187
|
+
react.createElement(
|
|
3188
|
+
"div",
|
|
3189
|
+
{ "data-provider-settings-header": "true" },
|
|
3190
|
+
react.createElement("span", null, "Edit Provider"),
|
|
3191
|
+
react.createElement("button", {
|
|
3192
|
+
type: "button",
|
|
3193
|
+
"data-provider-settings-close": "true",
|
|
3194
|
+
onClick: () => {
|
|
3195
|
+
setView("list");
|
|
3196
|
+
setEditingId(null);
|
|
3197
|
+
}
|
|
3198
|
+
}, "\u2190 Back")
|
|
3199
|
+
),
|
|
3200
|
+
react.createElement(
|
|
3201
|
+
"div",
|
|
3202
|
+
{ "data-provider-settings-form": "true" },
|
|
3203
|
+
availableModels.length > 0 ? react.createElement(
|
|
3204
|
+
"select",
|
|
3205
|
+
{
|
|
3206
|
+
ref: editModelRef,
|
|
3207
|
+
defaultValue: editingProvider?.model ?? "",
|
|
3208
|
+
"data-input": "model"
|
|
3209
|
+
},
|
|
3210
|
+
react.createElement("option", { value: "", disabled: true }, "Select a model\u2026"),
|
|
3211
|
+
...availableModels.map(
|
|
3212
|
+
(m) => react.createElement("option", { key: m.id, value: m.id }, m.name || m.id)
|
|
3213
|
+
)
|
|
3214
|
+
) : react.createElement("input", {
|
|
3215
|
+
ref: editModelRef,
|
|
3216
|
+
defaultValue: editingProvider?.model ?? "",
|
|
3217
|
+
placeholder: "Model name",
|
|
3218
|
+
"data-input": "model"
|
|
3219
|
+
}),
|
|
3220
|
+
react.createElement("input", {
|
|
3221
|
+
ref: editLabelRef,
|
|
3222
|
+
defaultValue: editingProvider?.label ?? "",
|
|
3223
|
+
placeholder: "Display label",
|
|
3224
|
+
"data-input": "label"
|
|
3225
|
+
}),
|
|
3226
|
+
react.createElement("button", {
|
|
3227
|
+
type: "button",
|
|
3228
|
+
"data-action": "update-provider",
|
|
3229
|
+
onClick: handleUpdate
|
|
3230
|
+
}, "Update")
|
|
3231
|
+
)
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
function formatTokens(n) {
|
|
3235
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
3236
|
+
return String(n);
|
|
3237
|
+
}
|
|
3238
|
+
function ContextStatsDisplay({ stats, className }) {
|
|
3239
|
+
if (!stats) return null;
|
|
3240
|
+
const hasRealData = stats.realPromptTokens != null && stats.modelContextWindow != null;
|
|
3241
|
+
if (!hasRealData) return null;
|
|
3242
|
+
const promptTokens = stats.realPromptTokens;
|
|
3243
|
+
const contextWindow = stats.modelContextWindow;
|
|
3244
|
+
const availableBudget = Math.max(0, contextWindow - promptTokens);
|
|
3245
|
+
const usagePercent = contextWindow > 0 ? Math.round(promptTokens / contextWindow * 100) : 0;
|
|
3246
|
+
return react.createElement(
|
|
3247
|
+
"div",
|
|
3248
|
+
{
|
|
3249
|
+
"data-context-stats": "",
|
|
3250
|
+
"data-context-truncated": stats.wasTruncated ? "true" : "false",
|
|
3251
|
+
className
|
|
3252
|
+
},
|
|
3253
|
+
react.createElement(
|
|
3254
|
+
"span",
|
|
3255
|
+
{ "data-context-tokens": "" },
|
|
3256
|
+
`${formatTokens(promptTokens)} tokens`
|
|
3257
|
+
),
|
|
3258
|
+
react.createElement(
|
|
3259
|
+
"span",
|
|
3260
|
+
{ "data-context-budget": "" },
|
|
3261
|
+
`${formatTokens(availableBudget)} available`
|
|
3262
|
+
),
|
|
3263
|
+
react.createElement(
|
|
3264
|
+
"span",
|
|
3265
|
+
{ "data-context-usage": "", "data-usage-percent": String(usagePercent) },
|
|
3266
|
+
`${usagePercent}%`
|
|
3267
|
+
),
|
|
3268
|
+
stats.removedCount > 0 ? react.createElement(
|
|
3269
|
+
"span",
|
|
3270
|
+
{ "data-context-removed": "" },
|
|
3271
|
+
`${stats.removedCount} trimmed`
|
|
3272
|
+
) : null
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3275
|
+
function ChatLayout({ children, sidebar, overlay, className }) {
|
|
3276
|
+
const overlayNodes = Array.isArray(overlay) ? overlay : [overlay ?? null];
|
|
3277
|
+
return react.createElement(
|
|
3278
|
+
"div",
|
|
3279
|
+
{ "data-chat-ui": "", className },
|
|
3280
|
+
...overlayNodes,
|
|
3281
|
+
sidebar ?? null,
|
|
3282
|
+
children
|
|
3283
|
+
);
|
|
3284
|
+
}
|
|
3285
|
+
function ChatHeader({
|
|
3286
|
+
showBackendSelector = false,
|
|
3287
|
+
showModelSelector = true,
|
|
3288
|
+
hasProviders = false,
|
|
3289
|
+
backends = [],
|
|
3290
|
+
models = [],
|
|
3291
|
+
selectedModel,
|
|
3292
|
+
onBackendSelect,
|
|
3293
|
+
onModelSelect,
|
|
3294
|
+
BackendSelectorComponent: BSC = BackendSelector,
|
|
3295
|
+
ModelSelectorComponent: MSC = ModelSelector
|
|
3296
|
+
}) {
|
|
3297
|
+
const children = [];
|
|
3298
|
+
if (showBackendSelector) {
|
|
3299
|
+
children.push(
|
|
3300
|
+
react.createElement(BSC, {
|
|
3301
|
+
key: "backend-selector",
|
|
3302
|
+
backends,
|
|
3303
|
+
onSelect: onBackendSelect ?? (() => {
|
|
3304
|
+
})
|
|
3305
|
+
})
|
|
3306
|
+
);
|
|
3307
|
+
}
|
|
3308
|
+
if (showModelSelector && !hasProviders && models.length > 0) {
|
|
3309
|
+
children.push(
|
|
3310
|
+
react.createElement(MSC, {
|
|
3311
|
+
key: "model-selector",
|
|
3312
|
+
models,
|
|
3313
|
+
selectedModel,
|
|
3314
|
+
onSelect: onModelSelect ?? (() => {
|
|
3315
|
+
})
|
|
3316
|
+
})
|
|
3317
|
+
);
|
|
3318
|
+
}
|
|
3319
|
+
if (children.length === 0) return null;
|
|
3320
|
+
return react.createElement("div", { "data-chat-header": "" }, ...children);
|
|
3321
|
+
}
|
|
3322
|
+
function UsageBadge({ usage, className }) {
|
|
3323
|
+
if (!usage) return null;
|
|
3324
|
+
return react.createElement(
|
|
3325
|
+
"span",
|
|
3326
|
+
{ "data-usage-badge": "true", className },
|
|
3327
|
+
react.createElement("span", { "data-usage-tokens": "prompt" }, `\u2191${usage.promptTokens}`),
|
|
3328
|
+
react.createElement("span", { "data-usage-tokens": "completion" }, `\u2193${usage.completionTokens}`),
|
|
3329
|
+
react.createElement("span", { "data-usage-tokens": "total" }, `\u03A3${usage.totalTokens}`)
|
|
3330
|
+
);
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
// src/chat/react/ChatInputArea.ts
|
|
3334
|
+
function ChatInputArea({
|
|
3335
|
+
onSend,
|
|
3336
|
+
onStop,
|
|
3337
|
+
isGenerating,
|
|
3338
|
+
placeholder,
|
|
3339
|
+
providers = [],
|
|
3340
|
+
models = [],
|
|
3341
|
+
activeProviderId,
|
|
3342
|
+
selectedModel,
|
|
3343
|
+
onSelectProvider,
|
|
3344
|
+
onSelectModel,
|
|
3345
|
+
onSettingsClick,
|
|
3346
|
+
ComposerComponent: CC = Composer,
|
|
3347
|
+
ProviderModelSelectorComponent: PMSC = ProviderModelSelector,
|
|
3348
|
+
usage
|
|
3349
|
+
}) {
|
|
3350
|
+
const selectorRow = react.createElement(
|
|
3351
|
+
"div",
|
|
3352
|
+
{ "data-chat-input-controls": "" },
|
|
3353
|
+
react.createElement(PMSC, {
|
|
3354
|
+
providers,
|
|
3355
|
+
models,
|
|
3356
|
+
activeProviderId,
|
|
3357
|
+
selectedModel,
|
|
3358
|
+
onSelectProvider,
|
|
3359
|
+
onSelectModel,
|
|
3360
|
+
onSettingsClick
|
|
3361
|
+
}),
|
|
3362
|
+
usage ? react.createElement(UsageBadge, { usage }) : null
|
|
3363
|
+
);
|
|
3364
|
+
return react.createElement(
|
|
3365
|
+
"div",
|
|
3366
|
+
{ "data-chat-input-area": "" },
|
|
3367
|
+
react.createElement(
|
|
3368
|
+
"div",
|
|
3369
|
+
{ "data-chat-input-container": "" },
|
|
3370
|
+
selectorRow,
|
|
3371
|
+
react.createElement(CC, {
|
|
3372
|
+
onSend,
|
|
3373
|
+
onStop,
|
|
3374
|
+
isGenerating,
|
|
3375
|
+
placeholder
|
|
3376
|
+
})
|
|
3377
|
+
)
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
var FOCUSABLE = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
3381
|
+
var CLOSE_ANIMATION_MS = 150;
|
|
3382
|
+
function ChatSettingsOverlay({
|
|
3383
|
+
open,
|
|
3384
|
+
onClose,
|
|
3385
|
+
providers = [],
|
|
3386
|
+
authBaseUrl,
|
|
3387
|
+
onProviderCreated,
|
|
3388
|
+
onProviderDeleted,
|
|
3389
|
+
onProviderUpdated,
|
|
3390
|
+
onAuthCompleted,
|
|
3391
|
+
ProviderSettingsComponent: PSC = ProviderSettings
|
|
3392
|
+
}) {
|
|
3393
|
+
const [isClosing, setIsClosing] = react.useState(false);
|
|
3394
|
+
const contentRef = react.useRef(null);
|
|
3395
|
+
const previousFocusRef = react.useRef(null);
|
|
3396
|
+
const handleClose = react.useCallback(() => {
|
|
3397
|
+
setIsClosing(true);
|
|
3398
|
+
setTimeout(() => {
|
|
3399
|
+
setIsClosing(false);
|
|
3400
|
+
onClose();
|
|
3401
|
+
}, CLOSE_ANIMATION_MS);
|
|
3402
|
+
}, [onClose]);
|
|
3403
|
+
const handleKeyDown = react.useCallback((e) => {
|
|
3404
|
+
if (e.key === "Escape") {
|
|
3405
|
+
e.stopPropagation();
|
|
3406
|
+
handleClose();
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
if (e.key === "Tab" && contentRef.current) {
|
|
3410
|
+
const focusable = Array.from(contentRef.current.querySelectorAll(FOCUSABLE));
|
|
3411
|
+
if (focusable.length === 0) return;
|
|
3412
|
+
const first = focusable[0];
|
|
3413
|
+
const last = focusable[focusable.length - 1];
|
|
3414
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
3415
|
+
e.preventDefault();
|
|
3416
|
+
last.focus();
|
|
3417
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
3418
|
+
e.preventDefault();
|
|
3419
|
+
first.focus();
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
}, [handleClose]);
|
|
3423
|
+
const handleBackdropClick = react.useCallback((e) => {
|
|
3424
|
+
if (e.target === e.currentTarget) handleClose();
|
|
3425
|
+
}, [handleClose]);
|
|
3426
|
+
react.useEffect(() => {
|
|
3427
|
+
if (!open) return;
|
|
3428
|
+
previousFocusRef.current = document.activeElement;
|
|
3429
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3430
|
+
const timer = setTimeout(() => {
|
|
3431
|
+
if (contentRef.current) {
|
|
3432
|
+
const first = contentRef.current.querySelector(FOCUSABLE);
|
|
3433
|
+
if (first) first.focus();
|
|
3434
|
+
}
|
|
3435
|
+
}, 50);
|
|
3436
|
+
return () => {
|
|
3437
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
3438
|
+
clearTimeout(timer);
|
|
3439
|
+
const prev = previousFocusRef.current;
|
|
3440
|
+
if (prev && typeof prev.focus === "function") prev.focus();
|
|
3441
|
+
};
|
|
3442
|
+
}, [open, handleKeyDown]);
|
|
3443
|
+
if (!open && !isClosing) return null;
|
|
3444
|
+
return react.createElement(
|
|
3445
|
+
"div",
|
|
3446
|
+
{
|
|
3447
|
+
"data-provider-settings-overlay": "true",
|
|
3448
|
+
"data-closing": isClosing ? "true" : void 0,
|
|
3449
|
+
onClick: handleBackdropClick,
|
|
3450
|
+
role: "dialog",
|
|
3451
|
+
"aria-modal": "true",
|
|
3452
|
+
"aria-label": "Provider settings"
|
|
3453
|
+
},
|
|
3454
|
+
react.createElement(
|
|
3455
|
+
"div",
|
|
3456
|
+
{
|
|
3457
|
+
ref: contentRef,
|
|
3458
|
+
"data-provider-settings-content": "true",
|
|
3459
|
+
"data-closing": isClosing ? "true" : void 0
|
|
3460
|
+
},
|
|
3461
|
+
react.createElement(PSC, {
|
|
3462
|
+
providers,
|
|
3463
|
+
authBaseUrl,
|
|
3464
|
+
onClose: handleClose,
|
|
3465
|
+
onProviderCreated: onProviderCreated ?? void 0,
|
|
3466
|
+
onProviderDeleted: onProviderDeleted ?? void 0,
|
|
3467
|
+
onProviderUpdated: onProviderUpdated ?? void 0,
|
|
3468
|
+
onAuthCompleted: onAuthCompleted ?? void 0
|
|
3469
|
+
})
|
|
3470
|
+
)
|
|
3471
|
+
);
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
// src/chat/react/ChatUI.ts
|
|
3475
|
+
function ChatUIInner({
|
|
3476
|
+
slots,
|
|
3477
|
+
className,
|
|
3478
|
+
showSidebar = true,
|
|
3479
|
+
showModelSelector = true,
|
|
3480
|
+
showBackendSelector: showBackendSelectorProp,
|
|
3481
|
+
showProviderSelector: _showProviderSelectorProp,
|
|
3482
|
+
authBaseUrl,
|
|
3483
|
+
placeholder
|
|
3484
|
+
}) {
|
|
3485
|
+
const runtime = useChatRuntime();
|
|
3486
|
+
const { messages, sendMessage, stop, isGenerating, newSession, error, clearError, retryLastMessage, usage } = useChat();
|
|
3487
|
+
const { sessions } = useSessions();
|
|
3488
|
+
const { models, refresh: refreshModels } = useModels();
|
|
3489
|
+
const { backends } = useBackends();
|
|
3490
|
+
const { providers, createProvider, updateProvider, deleteProvider, selectProvider, refresh } = useProviders();
|
|
3491
|
+
const [settingsOpen, setSettingsOpen] = react.useState(false);
|
|
3492
|
+
const [activeProviderId, setActiveProviderId] = react.useState(void 0);
|
|
3493
|
+
const [selectedModelId, setSelectedModelId] = react.useState(void 0);
|
|
3494
|
+
const [searchQuery, setSearchQuery] = react.useState("");
|
|
3495
|
+
const hasProviders = providers.length > 0;
|
|
3496
|
+
react.useEffect(() => {
|
|
3497
|
+
if (providers.length > 0 && !activeProviderId) {
|
|
3498
|
+
setActiveProviderId(providers[0].id);
|
|
3499
|
+
selectProvider(providers[0].id);
|
|
3500
|
+
}
|
|
3501
|
+
}, [providers, activeProviderId, selectProvider]);
|
|
3502
|
+
const showBackendSelectorResolved = showBackendSelectorProp ?? false;
|
|
3503
|
+
const handleSelect = react.useCallback(async (id) => {
|
|
3504
|
+
try {
|
|
3505
|
+
await runtime.switchSession(id);
|
|
3506
|
+
} catch {
|
|
3507
|
+
}
|
|
3508
|
+
}, [runtime]);
|
|
3509
|
+
const handleCreate = react.useCallback(async () => {
|
|
3510
|
+
try {
|
|
3511
|
+
await newSession();
|
|
3512
|
+
} catch {
|
|
3513
|
+
}
|
|
3514
|
+
}, [newSession]);
|
|
3515
|
+
const handleDelete = react.useCallback(async (id) => {
|
|
3516
|
+
try {
|
|
3517
|
+
await runtime.deleteSession(id);
|
|
3518
|
+
} catch {
|
|
3519
|
+
}
|
|
3520
|
+
}, [runtime]);
|
|
3521
|
+
const handleModelSelect = react.useCallback((modelId) => {
|
|
3522
|
+
setSelectedModelId(modelId);
|
|
3523
|
+
}, []);
|
|
3524
|
+
const handleBackendSelect = react.useCallback((_name) => {
|
|
3525
|
+
refreshModels();
|
|
3526
|
+
}, [refreshModels]);
|
|
3527
|
+
const handleProviderSelect = react.useCallback((id) => {
|
|
3528
|
+
selectProvider(id);
|
|
3529
|
+
setActiveProviderId(id);
|
|
3530
|
+
}, [selectProvider]);
|
|
3531
|
+
const hasSlotOverrides = !!(slots?.renderToolCall || slots?.renderMessage || slots?.renderThinkingBlock);
|
|
3532
|
+
const ThreadComponent = slots?.thread ?? Thread;
|
|
3533
|
+
const ThreadListComponent = slots?.threadList ?? ThreadList;
|
|
3534
|
+
const ContextStatsComponent = slots?.contextStats ?? ContextStatsDisplay;
|
|
3535
|
+
const [contextStats, setContextStats] = react.useState(null);
|
|
3536
|
+
react.useEffect(() => {
|
|
3537
|
+
if (!runtime.activeSessionId) {
|
|
3538
|
+
setContextStats(null);
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
const result = runtime.getContextStats(runtime.activeSessionId);
|
|
3542
|
+
if (result instanceof Promise) {
|
|
3543
|
+
result.then(setContextStats, () => setContextStats(null));
|
|
3544
|
+
} else {
|
|
3545
|
+
setContextStats(result);
|
|
3546
|
+
}
|
|
3547
|
+
}, [runtime, runtime.activeSessionId, messages.length]);
|
|
3548
|
+
const showEmptyState = !hasProviders && messages.length === 0;
|
|
3549
|
+
const mainContent = react.createElement(
|
|
3550
|
+
"div",
|
|
3551
|
+
{ "data-chat-main": "" },
|
|
3552
|
+
react.createElement(ChatHeader, {
|
|
3553
|
+
showBackendSelector: showBackendSelectorResolved,
|
|
3554
|
+
showModelSelector,
|
|
3555
|
+
hasProviders,
|
|
3556
|
+
backends,
|
|
3557
|
+
models,
|
|
3558
|
+
selectedModel: selectedModelId,
|
|
3559
|
+
onBackendSelect: handleBackendSelect,
|
|
3560
|
+
onModelSelect: handleModelSelect,
|
|
3561
|
+
BackendSelectorComponent: slots?.backendSelector,
|
|
3562
|
+
ModelSelectorComponent: slots?.modelSelector
|
|
3563
|
+
}),
|
|
3564
|
+
contextStats ? react.createElement(ContextStatsComponent, { stats: contextStats }) : null,
|
|
3565
|
+
error ? react.createElement(
|
|
3566
|
+
"div",
|
|
3567
|
+
{ "data-chat-error": "" },
|
|
3568
|
+
react.createElement("span", { "data-chat-error-text": "" }, error.message),
|
|
3569
|
+
react.createElement(
|
|
3570
|
+
"div",
|
|
3571
|
+
{ "data-chat-error-actions": "" },
|
|
3572
|
+
react.createElement("button", {
|
|
3573
|
+
"data-action": "retry",
|
|
3574
|
+
type: "button",
|
|
3575
|
+
onClick: retryLastMessage
|
|
3576
|
+
}, "Retry"),
|
|
3577
|
+
react.createElement("button", {
|
|
3578
|
+
"data-action": "dismiss-error",
|
|
3579
|
+
type: "button",
|
|
3580
|
+
onClick: clearError
|
|
3581
|
+
}, "\u2715")
|
|
3582
|
+
),
|
|
3583
|
+
error.stack ? react.createElement(
|
|
3584
|
+
"details",
|
|
3585
|
+
{ "data-chat-error-details": "" },
|
|
3586
|
+
react.createElement("summary", null, "Details"),
|
|
3587
|
+
react.createElement("pre", null, error.stack)
|
|
3588
|
+
) : null
|
|
3589
|
+
) : null,
|
|
3590
|
+
showEmptyState ? react.createElement(
|
|
3591
|
+
"div",
|
|
3592
|
+
{ "data-chat-empty-state": "" },
|
|
3593
|
+
react.createElement("div", { "data-chat-empty-title": "" }, "Connect a provider to start chatting"),
|
|
3594
|
+
react.createElement("button", {
|
|
3595
|
+
"data-action": "open-settings",
|
|
3596
|
+
onClick: () => setSettingsOpen(true)
|
|
3597
|
+
}, "+ Connect Provider")
|
|
3598
|
+
) : react.createElement(ThreadComponent, { messages, isGenerating, autoScroll: true }),
|
|
3599
|
+
react.createElement(ChatInputArea, {
|
|
3600
|
+
onSend: sendMessage,
|
|
3601
|
+
onStop: stop,
|
|
3602
|
+
isGenerating,
|
|
3603
|
+
placeholder,
|
|
3604
|
+
providers,
|
|
3605
|
+
models,
|
|
3606
|
+
activeProviderId,
|
|
3607
|
+
selectedModel: selectedModelId,
|
|
3608
|
+
onSelectProvider: handleProviderSelect,
|
|
3609
|
+
onSelectModel: handleModelSelect,
|
|
3610
|
+
onSettingsClick: () => setSettingsOpen(true),
|
|
3611
|
+
ComposerComponent: slots?.composer,
|
|
3612
|
+
ProviderModelSelectorComponent: slots?.providerModelSelector,
|
|
3613
|
+
usage
|
|
3614
|
+
})
|
|
3615
|
+
);
|
|
3616
|
+
const wrappedMain = hasSlotOverrides ? react.createElement(ThreadProvider, {
|
|
3617
|
+
renderToolCall: slots.renderToolCall,
|
|
3618
|
+
renderMessage: slots.renderMessage,
|
|
3619
|
+
renderThinkingBlock: slots.renderThinkingBlock,
|
|
3620
|
+
children: mainContent
|
|
3621
|
+
}) : mainContent;
|
|
3622
|
+
const sidebar = showSidebar ? react.createElement(ThreadListComponent, {
|
|
3623
|
+
sessions,
|
|
3624
|
+
activeSessionId: runtime.activeSessionId ?? void 0,
|
|
3625
|
+
onSelect: handleSelect,
|
|
3626
|
+
onCreate: handleCreate,
|
|
3627
|
+
onDelete: handleDelete,
|
|
3628
|
+
searchQuery,
|
|
3629
|
+
onSearchChange: setSearchQuery
|
|
3630
|
+
}) : void 0;
|
|
3631
|
+
const settingsOverlay = react.createElement(ChatSettingsOverlay, {
|
|
3632
|
+
open: settingsOpen,
|
|
3633
|
+
onClose: () => setSettingsOpen(false),
|
|
3634
|
+
providers,
|
|
3635
|
+
authBaseUrl,
|
|
3636
|
+
onProviderCreated: (p) => createProvider({ backend: p.backend, model: p.model, label: p.label ?? "" }),
|
|
3637
|
+
onProviderDeleted: (id) => deleteProvider(id),
|
|
3638
|
+
onProviderUpdated: (id, changes) => updateProvider(id, changes),
|
|
3639
|
+
onAuthCompleted: () => refresh(),
|
|
3640
|
+
ProviderSettingsComponent: slots?.providerSettings
|
|
3641
|
+
});
|
|
3642
|
+
return react.createElement(ChatLayout, {
|
|
3643
|
+
className,
|
|
3644
|
+
sidebar,
|
|
3645
|
+
overlay: [slots?.authDialog ?? null, settingsOverlay],
|
|
3646
|
+
children: wrappedMain
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
function ChatUI({ runtime, ...rest }) {
|
|
3650
|
+
return react.createElement(ChatProvider, {
|
|
3651
|
+
runtime,
|
|
3652
|
+
children: react.createElement(ChatUIInner, rest)
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3655
|
+
function PermissionDialog({
|
|
3656
|
+
requests,
|
|
3657
|
+
onApprove,
|
|
3658
|
+
onDeny,
|
|
3659
|
+
onApproveAll,
|
|
3660
|
+
onDenyAll,
|
|
3661
|
+
renderArgs,
|
|
3662
|
+
className
|
|
3663
|
+
}) {
|
|
3664
|
+
if (requests.length === 0) return null;
|
|
3665
|
+
const items = requests.map(
|
|
3666
|
+
(req) => react.createElement(
|
|
3667
|
+
"div",
|
|
3668
|
+
{
|
|
3669
|
+
key: req.toolCallId,
|
|
3670
|
+
"data-permission-request": "true",
|
|
3671
|
+
"data-tool-name": req.toolName
|
|
3672
|
+
},
|
|
3673
|
+
// Tool name
|
|
3674
|
+
react.createElement(
|
|
3675
|
+
"div",
|
|
3676
|
+
{ "data-permission-tool-name": "true" },
|
|
3677
|
+
req.toolName
|
|
3678
|
+
),
|
|
3679
|
+
// Arguments display
|
|
3680
|
+
react.createElement(
|
|
3681
|
+
"div",
|
|
3682
|
+
{ "data-permission-tool-args": "true" },
|
|
3683
|
+
renderArgs ? renderArgs(req.toolArgs, req.toolName) : react.createElement("pre", null, JSON.stringify(req.toolArgs, null, 2))
|
|
3684
|
+
),
|
|
3685
|
+
// Action buttons
|
|
3686
|
+
react.createElement(
|
|
3687
|
+
"div",
|
|
3688
|
+
{ "data-permission-actions": "true" },
|
|
3689
|
+
react.createElement(
|
|
3690
|
+
"button",
|
|
3691
|
+
{
|
|
3692
|
+
type: "button",
|
|
3693
|
+
"data-action": "approve",
|
|
3694
|
+
onClick: () => onApprove(req.toolCallId),
|
|
3695
|
+
"aria-label": `Approve ${req.toolName}`
|
|
3696
|
+
},
|
|
3697
|
+
"Allow"
|
|
3698
|
+
),
|
|
3699
|
+
react.createElement(
|
|
3700
|
+
"button",
|
|
3701
|
+
{
|
|
3702
|
+
type: "button",
|
|
3703
|
+
"data-action": "deny",
|
|
3704
|
+
onClick: () => onDeny(req.toolCallId),
|
|
3705
|
+
"aria-label": `Deny ${req.toolName}`
|
|
3706
|
+
},
|
|
3707
|
+
"Deny"
|
|
3708
|
+
)
|
|
3709
|
+
)
|
|
3710
|
+
)
|
|
3711
|
+
);
|
|
3712
|
+
const bulkActions = requests.length > 1 && (onApproveAll || onDenyAll) ? react.createElement(
|
|
3713
|
+
"div",
|
|
3714
|
+
{ "data-permission-bulk-actions": "true" },
|
|
3715
|
+
onApproveAll ? react.createElement(
|
|
3716
|
+
"button",
|
|
3717
|
+
{
|
|
3718
|
+
type: "button",
|
|
3719
|
+
"data-action": "approve-all",
|
|
3720
|
+
onClick: onApproveAll,
|
|
3721
|
+
"aria-label": "Approve all tool calls"
|
|
3722
|
+
},
|
|
3723
|
+
"Allow All"
|
|
3724
|
+
) : null,
|
|
3725
|
+
onDenyAll ? react.createElement(
|
|
3726
|
+
"button",
|
|
3727
|
+
{
|
|
3728
|
+
type: "button",
|
|
3729
|
+
"data-action": "deny-all",
|
|
3730
|
+
onClick: onDenyAll,
|
|
3731
|
+
"aria-label": "Deny all tool calls"
|
|
3732
|
+
},
|
|
3733
|
+
"Deny All"
|
|
3734
|
+
) : null
|
|
3735
|
+
) : null;
|
|
3736
|
+
return react.createElement(
|
|
3737
|
+
"div",
|
|
3738
|
+
{
|
|
3739
|
+
"data-permission-dialog": "true",
|
|
3740
|
+
role: "dialog",
|
|
3741
|
+
"aria-label": "Tool permission requests",
|
|
3742
|
+
className
|
|
3743
|
+
},
|
|
3744
|
+
...items,
|
|
3745
|
+
bulkActions
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
2713
3748
|
|
|
2714
|
-
exports.
|
|
3749
|
+
exports.BackendSelector = BackendSelector;
|
|
3750
|
+
exports.ChatHeader = ChatHeader;
|
|
3751
|
+
exports.ChatInputArea = ChatInputArea;
|
|
3752
|
+
exports.ChatLayout = ChatLayout;
|
|
2715
3753
|
exports.ChatProvider = ChatProvider;
|
|
3754
|
+
exports.ChatSettingsOverlay = ChatSettingsOverlay;
|
|
3755
|
+
exports.ChatUI = ChatUI;
|
|
3756
|
+
exports.ClaudeAuthForm = ClaudeAuthForm;
|
|
2716
3757
|
exports.Composer = Composer;
|
|
3758
|
+
exports.ContextStatsDisplay = ContextStatsDisplay;
|
|
3759
|
+
exports.CopilotAuthForm = CopilotAuthForm;
|
|
2717
3760
|
exports.MarkdownRenderer = MarkdownRenderer;
|
|
2718
3761
|
exports.Message = Message;
|
|
2719
3762
|
exports.ModelSelector = ModelSelector;
|
|
2720
|
-
exports.
|
|
3763
|
+
exports.PermissionDialog = PermissionDialog;
|
|
3764
|
+
exports.ProviderModelSelector = ProviderModelSelector;
|
|
3765
|
+
exports.ProviderSelector = ProviderSelector;
|
|
3766
|
+
exports.ProviderSettings = ProviderSettings;
|
|
3767
|
+
exports.RemoteChatClient = RemoteChatClient;
|
|
2721
3768
|
exports.ThinkingBlock = ThinkingBlock;
|
|
2722
3769
|
exports.Thread = Thread;
|
|
2723
3770
|
exports.ThreadList = ThreadList;
|
|
2724
3771
|
exports.ThreadProvider = ThreadProvider;
|
|
2725
3772
|
exports.ToolCallView = ToolCallView;
|
|
2726
|
-
exports.
|
|
3773
|
+
exports.UsageBadge = UsageBadge;
|
|
3774
|
+
exports.VercelAIAuthForm = VercelAIAuthForm;
|
|
3775
|
+
exports.useApiKeyAuth = useApiKeyAuth;
|
|
3776
|
+
exports.useBackends = useBackends;
|
|
2727
3777
|
exports.useChat = useChat;
|
|
2728
3778
|
exports.useChatRuntime = useChatRuntime;
|
|
3779
|
+
exports.useClaudeAuth = useClaudeAuth;
|
|
3780
|
+
exports.useCopilotAuth = useCopilotAuth;
|
|
2729
3781
|
exports.useMessages = useMessages;
|
|
2730
3782
|
exports.useModels = useModels;
|
|
2731
3783
|
exports.useOptionalThreadSlots = useOptionalThreadSlots;
|
|
3784
|
+
exports.useProviders = useProviders;
|
|
2732
3785
|
exports.useRemoteAuth = useRemoteAuth;
|
|
2733
3786
|
exports.useRemoteChat = useRemoteChat;
|
|
2734
3787
|
exports.useSSE = useSSE;
|
|
2735
3788
|
exports.useSessions = useSessions;
|
|
2736
3789
|
exports.useThreadSlots = useThreadSlots;
|
|
2737
3790
|
exports.useToolApproval = useToolApproval;
|
|
3791
|
+
exports.useVirtualMessages = useVirtualMessages;
|
|
2738
3792
|
//# sourceMappingURL=react.cjs.map
|
|
2739
3793
|
//# sourceMappingURL=react.cjs.map
|