@dexto/tui 1.8.1 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/agent-backend.d.ts +2 -2
  2. package/dist/agent-backend.d.ts.map +1 -1
  3. package/dist/components/overlays/ConnectOverlay.cjs +423 -0
  4. package/dist/components/overlays/ConnectOverlay.d.ts +21 -0
  5. package/dist/components/overlays/ConnectOverlay.d.ts.map +1 -0
  6. package/dist/components/overlays/ConnectOverlay.js +412 -0
  7. package/dist/containers/OverlayContainer.cjs +58 -0
  8. package/dist/containers/OverlayContainer.d.ts.map +1 -1
  9. package/dist/containers/OverlayContainer.js +58 -0
  10. package/dist/hooks/useTokenCounter.cjs +9 -11
  11. package/dist/hooks/useTokenCounter.d.ts.map +1 -1
  12. package/dist/hooks/useTokenCounter.js +9 -11
  13. package/dist/index.d.cts +2 -2
  14. package/dist/interactive-commands/auth/index.cjs +9 -0
  15. package/dist/interactive-commands/auth/index.d.ts +1 -0
  16. package/dist/interactive-commands/auth/index.d.ts.map +1 -1
  17. package/dist/interactive-commands/auth/index.js +8 -0
  18. package/dist/interactive-commands/commands.cjs +3 -1
  19. package/dist/interactive-commands/commands.d.ts.map +1 -1
  20. package/dist/interactive-commands/commands.js +4 -2
  21. package/dist/services/processStream.cjs +29 -20
  22. package/dist/services/processStream.d.ts.map +1 -1
  23. package/dist/services/processStream.js +29 -20
  24. package/dist/services/processStream.test.cjs +58 -20
  25. package/dist/services/processStream.test.js +58 -20
  26. package/dist/state/types.d.ts +1 -1
  27. package/dist/state/types.d.ts.map +1 -1
  28. package/dist/utils/commandOverlays.cjs +1 -0
  29. package/dist/utils/commandOverlays.d.ts.map +1 -1
  30. package/dist/utils/commandOverlays.js +1 -0
  31. package/package.json +5 -5
@@ -0,0 +1,412 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ forwardRef,
4
+ useCallback,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useMemo,
8
+ useRef,
9
+ useState
10
+ } from "react";
11
+ import { Box, Text } from "ink";
12
+ import open from "open";
13
+ import { BaseSelector } from "../base/BaseSelector.js";
14
+ import {
15
+ deleteModelAuthProfile,
16
+ getDefaultModelAuthProfileIdForProvider,
17
+ getModelAuthProfileId,
18
+ getProviderAuthDefinitions,
19
+ listSavedModelAuthProfiles,
20
+ markModelAuthProviderConnected,
21
+ saveApiKeyModelAuthProfile,
22
+ saveProviderApiKey,
23
+ setDefaultModelAuthProfile,
24
+ startModelAuthBrowserLogin
25
+ } from "@dexto/agent-management";
26
+ import { LLM_PROVIDERS } from "@dexto/llm";
27
+ import { applyLayeredEnvironmentLoading, isValidApiKeyFormat } from "../../host/index.js";
28
+ function methodHint(method, profile, defaultProfileId) {
29
+ const parts = [
30
+ profile ? profile.id === defaultProfileId ? "Connected (default)" : "Connected" : void 0,
31
+ method.hint
32
+ ].filter((part) => Boolean(part));
33
+ return parts.length ? parts.join(" - ") : void 0;
34
+ }
35
+ function maskSecret(secret) {
36
+ if (secret.length <= 8) {
37
+ return "*".repeat(secret.length);
38
+ }
39
+ return `${secret.slice(0, 4)}${"*".repeat(Math.min(secret.length - 8, 24))}${secret.slice(-4)}`;
40
+ }
41
+ function toLlmProvider(providerId) {
42
+ const provider = LLM_PROVIDERS.find((candidate) => candidate === providerId);
43
+ if (!provider) {
44
+ throw new Error(`API-key auth is not implemented for provider: ${providerId}`);
45
+ }
46
+ return provider;
47
+ }
48
+ const ConnectOverlay = forwardRef(
49
+ function ConnectOverlay2({ isVisible, onDone }, ref) {
50
+ const selectorRef = useRef(null);
51
+ const loginCancelRef = useRef(null);
52
+ const [step, setStep] = useState("provider");
53
+ const [selectedIndex, setSelectedIndex] = useState(0);
54
+ const [provider, setProvider] = useState(null);
55
+ const [method, setMethod] = useState(null);
56
+ const [profiles, setProfiles] = useState([]);
57
+ const [defaultProfileId, setDefaultProfileId] = useState(null);
58
+ const [apiKey, setApiKey] = useState("");
59
+ const [status, setStatus] = useState("Choose a provider to connect.");
60
+ const [error, setError] = useState(null);
61
+ const currentProfileId = provider && method ? getModelAuthProfileId(provider.providerId, method.id) : null;
62
+ const existingProfile = currentProfileId ? profiles.find((profile) => profile.id === currentProfileId) ?? null : null;
63
+ const close = useCallback(
64
+ (outcome) => {
65
+ void loginCancelRef.current?.();
66
+ loginCancelRef.current = null;
67
+ onDone(outcome);
68
+ },
69
+ [onDone]
70
+ );
71
+ const handleActionError = useCallback((err) => {
72
+ setError(err instanceof Error ? err.message : String(err));
73
+ setStep("error");
74
+ }, []);
75
+ useEffect(() => {
76
+ if (!isVisible) {
77
+ return;
78
+ }
79
+ setStep("provider");
80
+ setSelectedIndex(0);
81
+ setProvider(null);
82
+ setMethod(null);
83
+ setProfiles([]);
84
+ setDefaultProfileId(null);
85
+ setApiKey("");
86
+ setStatus("Choose a provider to connect.");
87
+ setError(null);
88
+ }, [isVisible]);
89
+ const providerOptions = useMemo(
90
+ () => getProviderAuthDefinitions().map((item) => ({
91
+ value: item.providerId,
92
+ label: item.label,
93
+ hint: `${item.methods.length} method${item.methods.length === 1 ? "" : "s"}`
94
+ })),
95
+ []
96
+ );
97
+ const methodOptions = useMemo(() => {
98
+ if (!provider) {
99
+ return [];
100
+ }
101
+ return provider.methods.map((item) => {
102
+ const profile = profiles.find(
103
+ (candidate) => candidate.id === getModelAuthProfileId(provider.providerId, item.id)
104
+ );
105
+ return {
106
+ value: item.id,
107
+ label: item.label,
108
+ hint: methodHint(item, profile, defaultProfileId)
109
+ };
110
+ });
111
+ }, [defaultProfileId, profiles, provider]);
112
+ const existingActionOptions = useMemo(() => {
113
+ if (!existingProfile) {
114
+ return [];
115
+ }
116
+ return [
117
+ {
118
+ value: "use",
119
+ label: existingProfile.id === defaultProfileId ? "Keep as default" : "Use existing",
120
+ hint: existingProfile.id === defaultProfileId ? "No changes" : "Set this method as provider default"
121
+ },
122
+ { value: "replace", label: "Replace credentials", hint: "Reconnect this method" },
123
+ {
124
+ value: "delete",
125
+ label: "Delete credentials",
126
+ ...existingProfile.id === defaultProfileId ? { hint: "Also clears default" } : {}
127
+ }
128
+ ];
129
+ }, [defaultProfileId, existingProfile]);
130
+ const activeItems = step === "provider" ? providerOptions : step === "method" ? methodOptions : step === "existing-action" ? existingActionOptions : [];
131
+ const loadProvider = useCallback(async (nextProvider) => {
132
+ setProvider(nextProvider);
133
+ setMethod(null);
134
+ setStatus(`Loading saved ${nextProvider.label} profiles...`);
135
+ const [savedProfiles, savedDefaultProfileId] = await Promise.all([
136
+ listSavedModelAuthProfiles(nextProvider.providerId),
137
+ getDefaultModelAuthProfileIdForProvider(nextProvider.providerId)
138
+ ]);
139
+ setProfiles(savedProfiles);
140
+ setDefaultProfileId(savedDefaultProfileId);
141
+ setSelectedIndex(0);
142
+ setStatus(`Choose how to connect ${nextProvider.label}.`);
143
+ setStep("method");
144
+ }, []);
145
+ const saveApiKey = useCallback(async () => {
146
+ if (!provider || !method) {
147
+ close({ outcome: "cancelled" });
148
+ return;
149
+ }
150
+ const trimmed = apiKey.trim();
151
+ if (!trimmed) {
152
+ setError("API key is required");
153
+ return;
154
+ }
155
+ const llmProvider = toLlmProvider(provider.providerId);
156
+ if (!isValidApiKeyFormat(trimmed, llmProvider)) {
157
+ setError(`${provider.label} API key format is invalid`);
158
+ return;
159
+ }
160
+ setError(null);
161
+ try {
162
+ setStatus(`Saving ${provider.label} API key...`);
163
+ await saveProviderApiKey(llmProvider, trimmed, process.cwd());
164
+ await applyLayeredEnvironmentLoading();
165
+ await saveApiKeyModelAuthProfile(llmProvider);
166
+ await markModelAuthProviderConnected(provider.providerId);
167
+ close({
168
+ outcome: "success",
169
+ providerId: provider.providerId,
170
+ message: `Connected ${provider.label} (${method.label})`
171
+ });
172
+ } catch (err) {
173
+ setError(err instanceof Error ? err.message : String(err));
174
+ }
175
+ }, [apiKey, close, method, provider]);
176
+ const connectOAuth = useCallback(
177
+ async (nextProvider, nextMethod) => {
178
+ setStep("oauth-progress");
179
+ setStatus(`Starting ${nextMethod.label}...`);
180
+ let timeout = null;
181
+ try {
182
+ const login = await startModelAuthBrowserLogin({
183
+ providerId: nextProvider.providerId,
184
+ methodId: nextMethod.id
185
+ });
186
+ loginCancelRef.current = login.cancel;
187
+ setStatus(`Opening browser for ${nextMethod.label}...`);
188
+ await open(login.authUrl).catch(() => void 0);
189
+ setStatus("Waiting for browser authorization...");
190
+ await Promise.race([
191
+ login.waitForProfile(),
192
+ new Promise((_, reject) => {
193
+ timeout = setTimeout(
194
+ () => reject(new Error(`${nextMethod.label} timed out`)),
195
+ 5 * 60 * 1e3
196
+ );
197
+ })
198
+ ]);
199
+ await markModelAuthProviderConnected(nextProvider.providerId);
200
+ close({
201
+ outcome: "success",
202
+ providerId: nextProvider.providerId,
203
+ message: `Connected ${nextProvider.label} (${nextMethod.label})`
204
+ });
205
+ } catch (err) {
206
+ setError(err instanceof Error ? err.message : String(err));
207
+ setStep("error");
208
+ } finally {
209
+ if (timeout) {
210
+ clearTimeout(timeout);
211
+ }
212
+ await loginCancelRef.current?.();
213
+ loginCancelRef.current = null;
214
+ }
215
+ },
216
+ [close]
217
+ );
218
+ const startConnect = useCallback(async () => {
219
+ if (!provider || !method) {
220
+ close({ outcome: "cancelled" });
221
+ return;
222
+ }
223
+ if (method.kind === "api_key") {
224
+ setApiKey("");
225
+ setError(null);
226
+ setStatus(`Enter your ${provider.label} API key.`);
227
+ setStep("api-key");
228
+ return;
229
+ }
230
+ if (method.kind === "oauth") {
231
+ await connectOAuth(provider, method);
232
+ }
233
+ }, [close, connectOAuth, method, provider]);
234
+ const handleSelect = useCallback(
235
+ async (option) => {
236
+ if (step === "provider") {
237
+ const nextProvider = getProviderAuthDefinitions().find(
238
+ (candidate) => candidate.providerId === option.value
239
+ );
240
+ if (nextProvider) {
241
+ await loadProvider(nextProvider);
242
+ }
243
+ return;
244
+ }
245
+ if (step === "method" && provider) {
246
+ const nextMethod = provider.methods.find(
247
+ (candidate) => candidate.id === option.value
248
+ );
249
+ if (nextMethod) {
250
+ setMethod(nextMethod);
251
+ const profileId = getModelAuthProfileId(provider.providerId, nextMethod.id);
252
+ if (profiles.some((item) => item.id === profileId)) {
253
+ setStatus(`Manage ${provider.label} ${nextMethod.label}.`);
254
+ setStep("existing-action");
255
+ setSelectedIndex(0);
256
+ return;
257
+ }
258
+ if (nextMethod.kind === "api_key") {
259
+ setApiKey("");
260
+ setError(null);
261
+ setStatus(`Enter your ${provider.label} API key.`);
262
+ setStep("api-key");
263
+ return;
264
+ }
265
+ if (nextMethod.kind === "oauth") {
266
+ await connectOAuth(provider, nextMethod);
267
+ }
268
+ }
269
+ return;
270
+ }
271
+ if (step === "existing-action" && provider && method && currentProfileId) {
272
+ if (option.value === "use") {
273
+ await setDefaultModelAuthProfile({
274
+ providerId: provider.providerId,
275
+ profileId: currentProfileId
276
+ });
277
+ close({
278
+ outcome: "success",
279
+ providerId: provider.providerId,
280
+ message: `Using ${provider.label} ${method.label}`
281
+ });
282
+ return;
283
+ }
284
+ if (option.value === "replace") {
285
+ await startConnect();
286
+ return;
287
+ }
288
+ if (option.value === "delete") {
289
+ setStatus(`Press Enter to delete ${provider.label} ${method.label}.`);
290
+ setStep("delete-confirm");
291
+ }
292
+ }
293
+ },
294
+ [
295
+ close,
296
+ connectOAuth,
297
+ currentProfileId,
298
+ loadProvider,
299
+ method,
300
+ profiles,
301
+ provider,
302
+ startConnect,
303
+ step
304
+ ]
305
+ );
306
+ useImperativeHandle(
307
+ ref,
308
+ () => ({
309
+ handleInput: (input, key) => {
310
+ if (!isVisible) return false;
311
+ if (step === "api-key") {
312
+ if (key.escape) {
313
+ close({ outcome: "cancelled" });
314
+ return true;
315
+ }
316
+ if (key.return) {
317
+ void saveApiKey().catch(handleActionError);
318
+ return true;
319
+ }
320
+ if (key.backspace || key.delete) {
321
+ setApiKey((prev) => prev.slice(0, -1));
322
+ setError(null);
323
+ return true;
324
+ }
325
+ if (input && !key.ctrl && !key.meta) {
326
+ setApiKey((prev) => prev + input);
327
+ setError(null);
328
+ return true;
329
+ }
330
+ return true;
331
+ }
332
+ if (step === "delete-confirm") {
333
+ if (key.escape) {
334
+ close({ outcome: "cancelled" });
335
+ return true;
336
+ }
337
+ if (key.return && provider && method && currentProfileId) {
338
+ void deleteModelAuthProfile(currentProfileId).then(
339
+ () => close({
340
+ outcome: "success",
341
+ providerId: provider.providerId,
342
+ message: `Deleted ${provider.label} ${method.label}`
343
+ })
344
+ ).catch(handleActionError);
345
+ return true;
346
+ }
347
+ return true;
348
+ }
349
+ if (step === "oauth-progress") {
350
+ if (key.escape) {
351
+ close({ outcome: "cancelled" });
352
+ }
353
+ return true;
354
+ }
355
+ if (step === "error") {
356
+ if (key.escape || key.return) {
357
+ close({ outcome: "closed" });
358
+ }
359
+ return true;
360
+ }
361
+ return selectorRef.current?.handleInput(input, key) ?? false;
362
+ }
363
+ }),
364
+ [
365
+ close,
366
+ currentProfileId,
367
+ handleActionError,
368
+ isVisible,
369
+ method,
370
+ provider,
371
+ saveApiKey,
372
+ step
373
+ ]
374
+ );
375
+ if (!isVisible) return null;
376
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
377
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Connect Model Provider" }) }),
378
+ /* @__PURE__ */ jsx(Text, { color: error ? "red" : "gray", children: error ?? status }),
379
+ step === "api-key" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
380
+ /* @__PURE__ */ jsx(Text, { children: maskSecret(apiKey) }),
381
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter save \u2022 Esc cancel" })
382
+ ] }) : step === "delete-confirm" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
383
+ /* @__PURE__ */ jsx(Text, { color: "red", children: "This removes saved credentials for this method." }),
384
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter delete \u2022 Esc cancel" })
385
+ ] }) : step === "oauth-progress" ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Esc cancel" }) }) : step === "error" ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter close \u2022 Esc close" }) }) : /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
386
+ BaseSelector,
387
+ {
388
+ ref: selectorRef,
389
+ items: activeItems,
390
+ isVisible: true,
391
+ selectedIndex,
392
+ onSelectIndex: setSelectedIndex,
393
+ onSelect: (item) => void handleSelect(item).catch(handleActionError),
394
+ onClose: () => close({ outcome: "cancelled" }),
395
+ title: step === "provider" ? "Providers" : step === "method" ? `${provider?.label ?? "Provider"} methods` : "Existing profile",
396
+ formatItem: (item, selected) => /* @__PURE__ */ jsxs(Text, { ...selected ? { color: "cyan" } : {}, children: [
397
+ selected ? "\u203A " : " ",
398
+ item.label,
399
+ item.hint ? /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
400
+ " \u2014 ",
401
+ item.hint
402
+ ] }) : null
403
+ ] })
404
+ }
405
+ ) })
406
+ ] });
407
+ }
408
+ );
409
+ var ConnectOverlay_default = ConnectOverlay;
410
+ export {
411
+ ConnectOverlay_default as default
412
+ };
@@ -76,6 +76,7 @@ var import_MarketplaceBrowser = __toESM(require("../components/overlays/Marketpl
76
76
  var import_MarketplaceAddPrompt = __toESM(require("../components/overlays/MarketplaceAddPrompt.js"), 1);
77
77
  var import_LoginOverlay = __toESM(require("../components/overlays/LoginOverlay.js"), 1);
78
78
  var import_LogoutOverlay = __toESM(require("../components/overlays/LogoutOverlay.js"), 1);
79
+ var import_ConnectOverlay = __toESM(require("../components/overlays/ConnectOverlay.js"), 1);
79
80
  var import_llm = require("@dexto/llm");
80
81
  var import_core2 = require("@dexto/core");
81
82
  var import_messageFormatting = require("../utils/messageFormatting.js");
@@ -149,6 +150,7 @@ const OverlayContainer = (0, import_react.forwardRef)(
149
150
  const apiKeyInputRef = (0, import_react.useRef)(null);
150
151
  const loginOverlayRef = (0, import_react.useRef)(null);
151
152
  const logoutOverlayRef = (0, import_react.useRef)(null);
153
+ const connectOverlayRef = (0, import_react.useRef)(null);
152
154
  const searchOverlayRef = (0, import_react.useRef)(null);
153
155
  const promptListRef = (0, import_react.useRef)(null);
154
156
  const promptAddChoiceRef = (0, import_react.useRef)(null);
@@ -235,6 +237,8 @@ const OverlayContainer = (0, import_react.forwardRef)(
235
237
  return loginOverlayRef.current?.handleInput(inputStr, key) ?? false;
236
238
  case "logout":
237
239
  return logoutOverlayRef.current?.handleInput(inputStr, key) ?? false;
240
+ case "connect":
241
+ return connectOverlayRef.current?.handleInput(inputStr, key) ?? false;
238
242
  case "search":
239
243
  return searchOverlayRef.current?.handleInput(inputStr, key) ?? false;
240
244
  case "prompt-list":
@@ -1200,6 +1204,52 @@ ${result.context}`,
1200
1204
  },
1201
1205
  [handleClose, setMessages]
1202
1206
  );
1207
+ const handleConnectDone = (0, import_react.useCallback)(
1208
+ async (outcome) => {
1209
+ handleClose();
1210
+ if (outcome.outcome === "closed") {
1211
+ return;
1212
+ }
1213
+ setMessages((prev) => [
1214
+ ...prev,
1215
+ {
1216
+ id: (0, import_idGenerator.generateMessageId)("system"),
1217
+ role: "system",
1218
+ content: outcome.outcome === "success" ? `\u2705 ${outcome.message}` : "Model provider connection cancelled.",
1219
+ timestamp: /* @__PURE__ */ new Date()
1220
+ }
1221
+ ]);
1222
+ if (outcome.outcome !== "success") {
1223
+ return;
1224
+ }
1225
+ const currentConfig = agent.getCurrentLLMConfig(session.id || void 0);
1226
+ if (currentConfig.provider !== outcome.providerId) {
1227
+ return;
1228
+ }
1229
+ try {
1230
+ await agent.switchLLM(
1231
+ {
1232
+ provider: currentConfig.provider,
1233
+ model: currentConfig.model,
1234
+ ...currentConfig.baseURL ? { baseURL: currentConfig.baseURL } : {},
1235
+ ...currentConfig.reasoning ? { reasoning: currentConfig.reasoning } : {}
1236
+ },
1237
+ session.id || void 0
1238
+ );
1239
+ } catch (error) {
1240
+ setMessages((prev) => [
1241
+ ...prev,
1242
+ {
1243
+ id: (0, import_idGenerator.generateMessageId)("error"),
1244
+ role: "system",
1245
+ content: `Failed to apply model provider connection: ${error instanceof Error ? error.message : String(error)}`,
1246
+ timestamp: /* @__PURE__ */ new Date()
1247
+ }
1248
+ ]);
1249
+ }
1250
+ },
1251
+ [agent, handleClose, session.id, setMessages]
1252
+ );
1203
1253
  const handleLogLevelSelect = (0, import_react.useCallback)(
1204
1254
  (level) => {
1205
1255
  setUi((prev) => ({ ...prev, activeOverlay: "none", mcpWizardServerType: null }));
@@ -2452,6 +2502,14 @@ Use /${data.name} to run it.`,
2452
2502
  onDone: handleLogoutDone
2453
2503
  }
2454
2504
  ) }),
2505
+ ui.activeOverlay === "connect" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2506
+ import_ConnectOverlay.default,
2507
+ {
2508
+ ref: connectOverlayRef,
2509
+ isVisible: true,
2510
+ onDone: handleConnectDone
2511
+ }
2512
+ ) }),
2455
2513
  ui.activeOverlay === "api-key-input" && ui.pendingModelSwitch && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2456
2514
  import_ApiKeyInput.default,
2457
2515
  {
@@ -1 +1 @@
1
- {"version":3,"file":"OverlayContainer.d.ts","sourceRoot":"","sources":["../../src/containers/OverlayContainer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAyE,MAAM,OAAO,CAAC;AAI9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAE5D,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAGH,KAAK,eAAe,EACvB,MAAM,iCAAiC,CAAC;AAmGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAmC3D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAuC3D,MAAM,WAAW,sBAAsB;IACnC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,qBAAqB;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3D,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/D,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7D,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAC;IAC1E,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAC1E,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,mDAAmD;IACnD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,qBAAqB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,sGA06F5B,CAAC"}
1
+ {"version":3,"file":"OverlayContainer.d.ts","sourceRoot":"","sources":["../../src/containers/OverlayContainer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAyE,MAAM,OAAO,CAAC;AAI9F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAE5D,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAGH,KAAK,eAAe,EACvB,MAAM,iCAAiC,CAAC;AAmGzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAuC3D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAuC3D,MAAM,WAAW,sBAAsB;IACnC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,qBAAqB;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3D,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/D,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7D,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAC;IAC1E,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAC1E,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,mDAAmD;IACnD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,qBAAqB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,sGAi/F5B,CAAC"}
@@ -56,6 +56,7 @@ import MarketplaceBrowser from "../components/overlays/MarketplaceBrowser.js";
56
56
  import MarketplaceAddPrompt from "../components/overlays/MarketplaceAddPrompt.js";
57
57
  import LoginOverlay from "../components/overlays/LoginOverlay.js";
58
58
  import LogoutOverlay from "../components/overlays/LogoutOverlay.js";
59
+ import ConnectOverlay from "../components/overlays/ConnectOverlay.js";
59
60
  import { LLM_PROVIDERS, getModelDisplayName, getReasoningProfile } from "@dexto/llm";
60
61
  import { DextoValidationError, LLMErrorCode } from "@dexto/core";
61
62
  import { createUserMessage, convertHistoryToUIMessages } from "../utils/messageFormatting.js";
@@ -129,6 +130,7 @@ const OverlayContainer = forwardRef(
129
130
  const apiKeyInputRef = useRef(null);
130
131
  const loginOverlayRef = useRef(null);
131
132
  const logoutOverlayRef = useRef(null);
133
+ const connectOverlayRef = useRef(null);
132
134
  const searchOverlayRef = useRef(null);
133
135
  const promptListRef = useRef(null);
134
136
  const promptAddChoiceRef = useRef(null);
@@ -215,6 +217,8 @@ const OverlayContainer = forwardRef(
215
217
  return loginOverlayRef.current?.handleInput(inputStr, key) ?? false;
216
218
  case "logout":
217
219
  return logoutOverlayRef.current?.handleInput(inputStr, key) ?? false;
220
+ case "connect":
221
+ return connectOverlayRef.current?.handleInput(inputStr, key) ?? false;
218
222
  case "search":
219
223
  return searchOverlayRef.current?.handleInput(inputStr, key) ?? false;
220
224
  case "prompt-list":
@@ -1180,6 +1184,52 @@ ${result.context}`,
1180
1184
  },
1181
1185
  [handleClose, setMessages]
1182
1186
  );
1187
+ const handleConnectDone = useCallback(
1188
+ async (outcome) => {
1189
+ handleClose();
1190
+ if (outcome.outcome === "closed") {
1191
+ return;
1192
+ }
1193
+ setMessages((prev) => [
1194
+ ...prev,
1195
+ {
1196
+ id: generateMessageId("system"),
1197
+ role: "system",
1198
+ content: outcome.outcome === "success" ? `\u2705 ${outcome.message}` : "Model provider connection cancelled.",
1199
+ timestamp: /* @__PURE__ */ new Date()
1200
+ }
1201
+ ]);
1202
+ if (outcome.outcome !== "success") {
1203
+ return;
1204
+ }
1205
+ const currentConfig = agent.getCurrentLLMConfig(session.id || void 0);
1206
+ if (currentConfig.provider !== outcome.providerId) {
1207
+ return;
1208
+ }
1209
+ try {
1210
+ await agent.switchLLM(
1211
+ {
1212
+ provider: currentConfig.provider,
1213
+ model: currentConfig.model,
1214
+ ...currentConfig.baseURL ? { baseURL: currentConfig.baseURL } : {},
1215
+ ...currentConfig.reasoning ? { reasoning: currentConfig.reasoning } : {}
1216
+ },
1217
+ session.id || void 0
1218
+ );
1219
+ } catch (error) {
1220
+ setMessages((prev) => [
1221
+ ...prev,
1222
+ {
1223
+ id: generateMessageId("error"),
1224
+ role: "system",
1225
+ content: `Failed to apply model provider connection: ${error instanceof Error ? error.message : String(error)}`,
1226
+ timestamp: /* @__PURE__ */ new Date()
1227
+ }
1228
+ ]);
1229
+ }
1230
+ },
1231
+ [agent, handleClose, session.id, setMessages]
1232
+ );
1183
1233
  const handleLogLevelSelect = useCallback(
1184
1234
  (level) => {
1185
1235
  setUi((prev) => ({ ...prev, activeOverlay: "none", mcpWizardServerType: null }));
@@ -2432,6 +2482,14 @@ Use /${data.name} to run it.`,
2432
2482
  onDone: handleLogoutDone
2433
2483
  }
2434
2484
  ) }),
2485
+ ui.activeOverlay === "connect" && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
2486
+ ConnectOverlay,
2487
+ {
2488
+ ref: connectOverlayRef,
2489
+ isVisible: true,
2490
+ onDone: handleConnectDone
2491
+ }
2492
+ ) }),
2435
2493
  ui.activeOverlay === "api-key-input" && ui.pendingModelSwitch && /* @__PURE__ */ jsx(
2436
2494
  ApiKeyInput,
2437
2495
  {
@@ -67,17 +67,15 @@ function useTokenCounter({ agent, isActive }) {
67
67
  "llm:response",
68
68
  (payload) => {
69
69
  const usage = payload.tokenUsage;
70
- if (usage) {
71
- const rawInputTokens = usage.inputTokens ?? 0;
72
- const cacheWriteTokens = usage.cacheWriteTokens ?? 0;
73
- const inputTokens = Math.max(0, rawInputTokens - cacheWriteTokens);
74
- if (inputTokens > 0) {
75
- setLastInputTokens(inputTokens);
76
- }
77
- const outputTokens = usage.outputTokens ?? 0;
78
- if (outputTokens > 0) {
79
- setCumulativeOutputTokens((prev) => prev + outputTokens);
80
- }
70
+ const rawInputTokens = usage.inputTokens ?? 0;
71
+ const cacheWriteTokens = usage.cacheWriteTokens ?? 0;
72
+ const inputTokens = Math.max(0, rawInputTokens - cacheWriteTokens);
73
+ if (inputTokens > 0) {
74
+ setLastInputTokens(inputTokens);
75
+ }
76
+ const outputTokens = usage.outputTokens ?? 0;
77
+ if (outputTokens > 0) {
78
+ setCumulativeOutputTokens((prev) => prev + outputTokens);
81
79
  }
82
80
  currentCharCountRef.current = 0;
83
81
  setCurrentSegmentEstimate(0);
@@ -1 +1 @@
1
- {"version":3,"file":"useTokenCounter.d.ts","sourceRoot":"","sources":["../../src/hooks/useTokenCounter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,WAAW,mBAAmB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,eAAe,CAAC;IACvB,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IAC/B,yDAAyD;IACzD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;CACrB;AAsBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,mBAAmB,GAAG,kBAAkB,CA6F5F"}
1
+ {"version":3,"file":"useTokenCounter.d.ts","sourceRoot":"","sources":["../../src/hooks/useTokenCounter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,WAAW,mBAAmB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,eAAe,CAAC;IACvB,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IAC/B,yDAAyD;IACzD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,sBAAsB,EAAE,MAAM,CAAC;IAC/B,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;CACrB;AAsBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,mBAAmB,GAAG,kBAAkB,CA2F5F"}
@@ -44,17 +44,15 @@ function useTokenCounter({ agent, isActive }) {
44
44
  "llm:response",
45
45
  (payload) => {
46
46
  const usage = payload.tokenUsage;
47
- if (usage) {
48
- const rawInputTokens = usage.inputTokens ?? 0;
49
- const cacheWriteTokens = usage.cacheWriteTokens ?? 0;
50
- const inputTokens = Math.max(0, rawInputTokens - cacheWriteTokens);
51
- if (inputTokens > 0) {
52
- setLastInputTokens(inputTokens);
53
- }
54
- const outputTokens = usage.outputTokens ?? 0;
55
- if (outputTokens > 0) {
56
- setCumulativeOutputTokens((prev) => prev + outputTokens);
57
- }
47
+ const rawInputTokens = usage.inputTokens ?? 0;
48
+ const cacheWriteTokens = usage.cacheWriteTokens ?? 0;
49
+ const inputTokens = Math.max(0, rawInputTokens - cacheWriteTokens);
50
+ if (inputTokens > 0) {
51
+ setLastInputTokens(inputTokens);
52
+ }
53
+ const outputTokens = usage.outputTokens ?? 0;
54
+ if (outputTokens > 0) {
55
+ setCumulativeOutputTokens((prev) => prev + outputTokens);
58
56
  }
59
57
  currentCharCountRef.current = 0;
60
58
  setCurrentSegmentEstimate(0);