zidane 5.0.4 → 5.0.5
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/chat.d.ts +58 -1
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/tui.d.ts +13 -15
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +407 -126
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-BF3hMNgo.js → turn-operations-DZ3TrljX.js} +75 -2
- package/dist/turn-operations-DZ3TrljX.js.map +1 -0
- package/package.json +1 -1
- package/dist/turn-operations-BF3hMNgo.js.map +0 -1
package/dist/tui.js
CHANGED
|
@@ -2,7 +2,7 @@ import { d as createAgent } from "./tools-CLazLRb4.js";
|
|
|
2
2
|
import { n as formatTokenUsage } from "./stats-DZIsGqzu.js";
|
|
3
3
|
import { n as loadSession, t as createSession } from "./session-791hhrFa.js";
|
|
4
4
|
import { createTuiStore } from "./session/sqlite.js";
|
|
5
|
-
import { $
|
|
5
|
+
import { $ as SETTINGS_TOGGLES, C as useSafeModeQueue, D as isOnSafelist, E as getSafelist, Et as findGitRoot, F as supportsOAuth, G as ageString, I as buildModelCatalog, J as shortId, K as compactPath, L as filterModelCatalog, Lt as useCompletion, N as splitPromptSegments, Ot as createSkillsCompletionProvider, P as runOAuthLogin, Q as SETTINGS_CHOICES, Qt as modelSupportsReasoning, R as indexOfEntry, Rt as detectAuth, S as useSafeModeActions, St as stripSpawnTokensLine, T as addToSafelist, Tt as toolResultText, V as discoverProjectMcps, W as generateSessionTitle, Wt as setProviderCredential, X as useEnabledToggleSet, Xt as getContextWindow, Y as listProjectFiles, Z as DEFAULT_SETTINGS, _ as discoverProjectSkills, _t as lastContextSizeFromTurns, a as ThemeProvider, at as resolveTheme, b as writeSessionExport, c as useSurfaces, d as finalizeStreamingMarkdown, dt as ConfigProvider, et as SettingsProvider, f as finalizeStreamingMarkdownForOwner, ft as useConfig, g as defaultSkillScanPaths, gt as eventsFromTurns, h as buildSkillsConfig, ht as deriveSessionTitle, i as turnAsText, it as resolveChipColor, j as suggestSafelistEntry, jt as createFilesCompletionProvider, kt as uniqueSkillNamesFromReferences, m as useStreamBuffer, n as deleteTurnSafely, nn as piIdOf, o as useColors, p as turnContextSize, pt as resolveConfig, q as fmtTokens, r as truncateTurnsAt, s as useSelectStyle, tt as useSettings, u as useTheme, vt as listSessionMeta, wt as toolCallPreview, x as SafeModeProvider, xt as selectableTurnIds, z as buildMcpServers } from "./turn-operations-DZ3TrljX.js";
|
|
6
6
|
import { Buffer } from "node:buffer";
|
|
7
7
|
import { createContext, createElement, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { RGBA, SyntaxStyle, addDefaultParsers, createCliRenderer, defaultTextareaKeyBindings, getTreeSitterClient } from "@opentui/core";
|
|
@@ -108,7 +108,7 @@ function Modal({ title, bottomTitle, onClose, disableEscape = false, children, m
|
|
|
108
108
|
//#endregion
|
|
109
109
|
//#region src/tui/agent-picker.tsx
|
|
110
110
|
/** Cap the scroll window — a long custom registry shouldn't push the modal off-screen. */
|
|
111
|
-
const VISIBLE_ROW_CAP
|
|
111
|
+
const VISIBLE_ROW_CAP = 10;
|
|
112
112
|
/**
|
|
113
113
|
* Modal that lists the registered {@link AgentProfile}s and lets the user
|
|
114
114
|
* pick one. Rows show: `● selected · label description`.
|
|
@@ -130,8 +130,8 @@ function AgentPickerModal({ agents, currentAgentId, onPick }) {
|
|
|
130
130
|
description: p.description,
|
|
131
131
|
value: p.id
|
|
132
132
|
})), [profiles, currentAgentId]);
|
|
133
|
-
if (profiles.length === 0) return /* @__PURE__ */ jsx(EmptyState$
|
|
134
|
-
const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP
|
|
133
|
+
if (profiles.length === 0) return /* @__PURE__ */ jsx(EmptyState$1, {});
|
|
134
|
+
const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP);
|
|
135
135
|
const currentMissing = initialIndex < 0;
|
|
136
136
|
const safeIndex = currentMissing ? 0 : initialIndex;
|
|
137
137
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
@@ -176,7 +176,7 @@ function AgentPickerModal({ agents, currentAgentId, onPick }) {
|
|
|
176
176
|
]
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
|
-
function EmptyState$
|
|
179
|
+
function EmptyState$1() {
|
|
180
180
|
const COLOR = useColors();
|
|
181
181
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
182
182
|
title: "select agent",
|
|
@@ -733,7 +733,7 @@ function Transcript({ events, settings, selectedTurnId = null }) {
|
|
|
733
733
|
});
|
|
734
734
|
return () => cancelAnimationFrame(handle);
|
|
735
735
|
}, [selectedTurnId, anchors]);
|
|
736
|
-
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyState
|
|
736
|
+
if (items.length === 0) return /* @__PURE__ */ jsx(EmptyState, {});
|
|
737
737
|
return /* @__PURE__ */ jsx("scrollbox", {
|
|
738
738
|
ref: scrollboxRef,
|
|
739
739
|
focusable: false,
|
|
@@ -905,7 +905,7 @@ function SubagentBlock({ events, previous, selectedTurnId = null, anchorIds }) {
|
|
|
905
905
|
}, i))
|
|
906
906
|
});
|
|
907
907
|
}
|
|
908
|
-
function EmptyState
|
|
908
|
+
function EmptyState() {
|
|
909
909
|
return /* @__PURE__ */ jsx("box", {
|
|
910
910
|
style: {
|
|
911
911
|
flexGrow: 1,
|
|
@@ -1216,12 +1216,6 @@ function ToolResultBlock({ text, indent }) {
|
|
|
1216
1216
|
}
|
|
1217
1217
|
//#endregion
|
|
1218
1218
|
//#region src/tui/effort-picker.tsx
|
|
1219
|
-
/**
|
|
1220
|
-
* Reasoning effort options surfaced in the picker. Mirrors {@link ThinkingLevel}
|
|
1221
|
-
* minus `'adaptive'` (Anthropic-only — opt in via {@link EffortPickerModal}'s
|
|
1222
|
-
* `supportsAdaptive` flag rather than confusing OpenAI / OpenRouter users with
|
|
1223
|
-
* a level their model silently treats as `'off'`).
|
|
1224
|
-
*/
|
|
1225
1219
|
const BASE_LEVELS = [
|
|
1226
1220
|
{
|
|
1227
1221
|
id: "off",
|
|
@@ -1248,58 +1242,162 @@ const ADAPTIVE_LEVEL = {
|
|
|
1248
1242
|
id: "adaptive",
|
|
1249
1243
|
description: "model decides per-turn (Anthropic)"
|
|
1250
1244
|
};
|
|
1251
|
-
/**
|
|
1252
|
-
* Modal that lets the user pick a reasoning effort for the active model.
|
|
1253
|
-
* Only surfaced for models whose registry entry reports
|
|
1254
|
-
* `reasoning: true` — see `modelSupportsReasoning`.
|
|
1255
|
-
*
|
|
1256
|
-
* `'adaptive'` is Anthropic-only; pass `supportsAdaptive` to surface it.
|
|
1257
|
-
*/
|
|
1258
1245
|
function EffortPickerModal({ current, supportsAdaptive, onPick }) {
|
|
1259
1246
|
const COLOR = useColors();
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1247
|
+
const SURFACE = useSurfaces();
|
|
1248
|
+
const inputRef = useRef(null);
|
|
1249
|
+
const [query, setQuery] = useState("");
|
|
1250
|
+
const levels = useMemo(() => {
|
|
1251
|
+
return (supportsAdaptive ? [...BASE_LEVELS, ADAPTIVE_LEVEL] : BASE_LEVELS).map((l) => ({
|
|
1252
|
+
...l,
|
|
1253
|
+
searchCorpus: `${l.id} ${l.description}`.toLowerCase()
|
|
1254
|
+
}));
|
|
1255
|
+
}, [supportsAdaptive]);
|
|
1256
|
+
const filtered = useMemo(() => {
|
|
1257
|
+
const trimmed = query.trim().toLowerCase();
|
|
1258
|
+
if (!trimmed) return levels;
|
|
1259
|
+
const terms = trimmed.split(/\s+/);
|
|
1260
|
+
return levels.filter((l) => terms.every((t) => l.searchCorpus.includes(t)));
|
|
1261
|
+
}, [levels, query]);
|
|
1262
|
+
const [selectedIdx, setSelectedIdx] = useState(() => {
|
|
1268
1263
|
const idx = levels.findIndex((l) => l.id === current);
|
|
1269
|
-
|
|
1270
|
-
|
|
1264
|
+
if (idx >= 0) return idx;
|
|
1265
|
+
const fallback = levels.findIndex((l) => l.id === "medium");
|
|
1266
|
+
return fallback < 0 ? 0 : fallback;
|
|
1267
|
+
});
|
|
1268
|
+
const handleQueryChange = useCallback((next) => {
|
|
1269
|
+
setQuery(next);
|
|
1270
|
+
setSelectedIdx(0);
|
|
1271
|
+
}, []);
|
|
1272
|
+
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
1273
|
+
const commit = () => {
|
|
1274
|
+
const row = filtered[safeIndex];
|
|
1275
|
+
if (row) onPick(row.id);
|
|
1276
|
+
};
|
|
1277
|
+
useEffect(() => {
|
|
1278
|
+
inputRef.current?.focus();
|
|
1279
|
+
}, []);
|
|
1280
|
+
useKeyboard((key) => {
|
|
1281
|
+
if (key.name === "up") {
|
|
1282
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
if (key.name === "down") {
|
|
1286
|
+
setSelectedIdx((i) => Math.min(filtered.length - 1, i + 1));
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (key.name === "return") commit();
|
|
1290
|
+
});
|
|
1271
1291
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
1272
1292
|
title: "select reasoning effort",
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1293
|
+
maxWidth: 80,
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsx("box", {
|
|
1296
|
+
style: {
|
|
1297
|
+
border: true,
|
|
1298
|
+
borderColor: COLOR.borderActive,
|
|
1299
|
+
paddingLeft: 1,
|
|
1300
|
+
paddingRight: 1,
|
|
1301
|
+
height: 3
|
|
1302
|
+
},
|
|
1303
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1304
|
+
ref: inputRef,
|
|
1305
|
+
focused: true,
|
|
1306
|
+
placeholder: "search effort levels…",
|
|
1307
|
+
onInput: handleQueryChange,
|
|
1308
|
+
onSubmit: () => {},
|
|
1309
|
+
style: { flexGrow: 1 }
|
|
1310
|
+
})
|
|
1311
|
+
}),
|
|
1312
|
+
/* @__PURE__ */ jsx("box", {
|
|
1313
|
+
style: {
|
|
1314
|
+
flexDirection: "column",
|
|
1315
|
+
flexShrink: 0
|
|
1316
|
+
},
|
|
1317
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
|
|
1318
|
+
fg: COLOR.dim,
|
|
1319
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1320
|
+
fg: COLOR.mute,
|
|
1321
|
+
children: "no levels match "
|
|
1322
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1323
|
+
fg: COLOR.warn,
|
|
1324
|
+
children: query.trim()
|
|
1325
|
+
})]
|
|
1326
|
+
}) : filtered.map((level, i) => /* @__PURE__ */ jsx(EffortRow, {
|
|
1327
|
+
level,
|
|
1328
|
+
isCurrent: level.id === current,
|
|
1329
|
+
isFocused: i === safeIndex,
|
|
1330
|
+
highlightBg: SURFACE.selection
|
|
1331
|
+
}, level.id))
|
|
1332
|
+
}),
|
|
1333
|
+
/* @__PURE__ */ jsxs("text", {
|
|
1334
|
+
fg: COLOR.dim,
|
|
1335
|
+
children: [
|
|
1336
|
+
/* @__PURE__ */ jsx("span", {
|
|
1337
|
+
fg: COLOR.warn,
|
|
1338
|
+
children: "↑↓"
|
|
1339
|
+
}),
|
|
1340
|
+
" navigate · ",
|
|
1341
|
+
/* @__PURE__ */ jsx("span", {
|
|
1342
|
+
fg: COLOR.warn,
|
|
1343
|
+
children: "↵"
|
|
1344
|
+
}),
|
|
1345
|
+
" select · ",
|
|
1346
|
+
/* @__PURE__ */ jsx("span", {
|
|
1347
|
+
fg: COLOR.warn,
|
|
1348
|
+
children: "esc"
|
|
1349
|
+
}),
|
|
1350
|
+
" close · ",
|
|
1351
|
+
/* @__PURE__ */ jsx("span", {
|
|
1352
|
+
fg: COLOR.mute,
|
|
1353
|
+
children: `${filtered.length} / ${levels.length} level${levels.length === 1 ? "" : "s"}`
|
|
1354
|
+
})
|
|
1355
|
+
]
|
|
1356
|
+
})
|
|
1357
|
+
]
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Single row in the picker. Mirrors `ModelRow` in `model-picker.tsx`:
|
|
1362
|
+
* `●` marker for the current pick, single-space middle-dot separators,
|
|
1363
|
+
* focused row gets the `surfaces.selection` background lift.
|
|
1364
|
+
*/
|
|
1365
|
+
function EffortRow({ level, isCurrent, isFocused, highlightBg }) {
|
|
1366
|
+
const COLOR = useColors();
|
|
1367
|
+
const marker = isCurrent ? "●" : " ";
|
|
1368
|
+
return /* @__PURE__ */ jsx("box", {
|
|
1369
|
+
style: {
|
|
1370
|
+
height: 1,
|
|
1371
|
+
paddingLeft: 1,
|
|
1372
|
+
paddingRight: 1,
|
|
1373
|
+
flexShrink: 0,
|
|
1374
|
+
backgroundColor: isFocused ? highlightBg : void 0
|
|
1375
|
+
},
|
|
1376
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
1377
|
+
wrapMode: "none",
|
|
1285
1378
|
children: [
|
|
1286
1379
|
/* @__PURE__ */ jsx("span", {
|
|
1287
|
-
fg: COLOR.
|
|
1288
|
-
children:
|
|
1380
|
+
fg: isCurrent ? COLOR.brand : COLOR.mute,
|
|
1381
|
+
children: marker
|
|
1289
1382
|
}),
|
|
1290
|
-
" navigate · ",
|
|
1291
1383
|
/* @__PURE__ */ jsx("span", {
|
|
1292
|
-
fg: COLOR.
|
|
1293
|
-
children: "
|
|
1384
|
+
fg: COLOR.mute,
|
|
1385
|
+
children: " "
|
|
1294
1386
|
}),
|
|
1295
|
-
" select · ",
|
|
1296
1387
|
/* @__PURE__ */ jsx("span", {
|
|
1297
|
-
fg: COLOR.
|
|
1298
|
-
children:
|
|
1388
|
+
fg: isFocused ? COLOR.brand : COLOR.dim,
|
|
1389
|
+
children: level.id
|
|
1299
1390
|
}),
|
|
1300
|
-
"
|
|
1391
|
+
/* @__PURE__ */ jsx("span", {
|
|
1392
|
+
fg: COLOR.mute,
|
|
1393
|
+
children: " · "
|
|
1394
|
+
}),
|
|
1395
|
+
/* @__PURE__ */ jsx("span", {
|
|
1396
|
+
fg: COLOR.mute,
|
|
1397
|
+
children: level.description
|
|
1398
|
+
})
|
|
1301
1399
|
]
|
|
1302
|
-
})
|
|
1400
|
+
})
|
|
1303
1401
|
});
|
|
1304
1402
|
}
|
|
1305
1403
|
//#endregion
|
|
@@ -1465,50 +1563,134 @@ function McpsSettingsModal({ catalog }) {
|
|
|
1465
1563
|
}
|
|
1466
1564
|
//#endregion
|
|
1467
1565
|
//#region src/tui/model-picker.tsx
|
|
1468
|
-
/** Cap the visible scroll window so a 30-model list doesn't push the modal off-screen. */
|
|
1469
|
-
const VISIBLE_ROW_CAP = 12;
|
|
1470
1566
|
/**
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1567
|
+
* Cross-provider, searchable model picker.
|
|
1568
|
+
*
|
|
1569
|
+
* The picker unions every available provider's models into one flat
|
|
1570
|
+
* catalog, lets the user filter with a typed query, and returns both
|
|
1571
|
+
* the chosen provider and model id on commit. Pick a model from a
|
|
1572
|
+
* different provider than the current one and the caller is expected
|
|
1573
|
+
* to swap the active `ProviderAuth` + rebuild the agent — see
|
|
1574
|
+
* `app.tsx`'s `onPickModel` for the canonical wiring.
|
|
1475
1575
|
*
|
|
1476
|
-
*
|
|
1576
|
+
* Geometry:
|
|
1577
|
+
* - Top: single-row search input. The input owns focus so the user
|
|
1578
|
+
* can type immediately; ↑/↓/↵/⎋ are intercepted before the input's
|
|
1579
|
+
* text handler sees them so navigation works without losing focus.
|
|
1580
|
+
* - Middle: windowed list of catalog rows around the current
|
|
1581
|
+
* selection. Each row shows the model name (or id) in brand color,
|
|
1582
|
+
* the provider label in dim, and a compact capability suffix
|
|
1583
|
+
* (`ctx 200k · reasoning · vision`).
|
|
1584
|
+
* - Bottom: shortcut hint row.
|
|
1585
|
+
*
|
|
1586
|
+
* Empty states:
|
|
1587
|
+
* - No available providers → "no providers configured" notice.
|
|
1588
|
+
* - Query has no matches → "no matches" row, keep the search input
|
|
1589
|
+
* live so the user can backspace out.
|
|
1477
1590
|
*/
|
|
1478
|
-
|
|
1591
|
+
/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */
|
|
1592
|
+
const VISIBLE_ROWS = 10;
|
|
1593
|
+
function ModelPickerModal({ providers, modelsFor, current, onPick }) {
|
|
1479
1594
|
const COLOR = useColors();
|
|
1480
|
-
const
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1595
|
+
const SURFACE = useSurfaces();
|
|
1596
|
+
const inputRef = useRef(null);
|
|
1597
|
+
const [query, setQuery] = useState("");
|
|
1598
|
+
useEffect(() => {
|
|
1599
|
+
inputRef.current?.focus();
|
|
1600
|
+
}, []);
|
|
1601
|
+
const catalog = useMemo(() => buildModelCatalog({
|
|
1602
|
+
providers,
|
|
1603
|
+
modelsFor,
|
|
1604
|
+
current
|
|
1605
|
+
}), [
|
|
1606
|
+
providers,
|
|
1607
|
+
modelsFor,
|
|
1608
|
+
current
|
|
1609
|
+
]);
|
|
1610
|
+
const filtered = useMemo(() => filterModelCatalog(catalog, query), [catalog, query]);
|
|
1611
|
+
const [selectedIdx, setSelectedIdx] = useState(() => Math.max(0, indexOfEntry(catalog, current)));
|
|
1612
|
+
const handleQueryChange = useCallback((next) => {
|
|
1613
|
+
setQuery(next);
|
|
1614
|
+
setSelectedIdx(0);
|
|
1615
|
+
}, []);
|
|
1616
|
+
const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1);
|
|
1617
|
+
const commit = () => {
|
|
1618
|
+
const row = filtered[safeIndex];
|
|
1619
|
+
if (row) onPick({
|
|
1620
|
+
providerKey: row.providerKey,
|
|
1621
|
+
modelId: row.model.id
|
|
1622
|
+
});
|
|
1623
|
+
};
|
|
1624
|
+
const viewport = useMemo(() => {
|
|
1625
|
+
if (filtered.length <= VISIBLE_ROWS) return {
|
|
1626
|
+
start: 0,
|
|
1627
|
+
slice: filtered
|
|
1628
|
+
};
|
|
1629
|
+
const half = Math.floor(VISIBLE_ROWS / 2);
|
|
1630
|
+
let start = Math.max(0, safeIndex - half);
|
|
1631
|
+
if (start + VISIBLE_ROWS > filtered.length) start = filtered.length - VISIBLE_ROWS;
|
|
1632
|
+
return {
|
|
1633
|
+
start,
|
|
1634
|
+
slice: filtered.slice(start, start + VISIBLE_ROWS)
|
|
1635
|
+
};
|
|
1636
|
+
}, [filtered, safeIndex]);
|
|
1637
|
+
useKeyboard((key) => {
|
|
1638
|
+
if (key.name === "up") {
|
|
1639
|
+
setSelectedIdx((i) => Math.max(0, i - 1));
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (key.name === "down") {
|
|
1643
|
+
setSelectedIdx((i) => Math.min(filtered.length - 1, i + 1));
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (key.name === "return") commit();
|
|
1647
|
+
});
|
|
1648
|
+
if (providers.length === 0) return /* @__PURE__ */ jsx(EmptyProvidersState, {});
|
|
1491
1649
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
1492
1650
|
title: "select model",
|
|
1651
|
+
maxWidth: 100,
|
|
1493
1652
|
children: [
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1653
|
+
/* @__PURE__ */ jsx("box", {
|
|
1654
|
+
style: {
|
|
1655
|
+
border: true,
|
|
1656
|
+
borderColor: COLOR.borderActive,
|
|
1657
|
+
paddingLeft: 1,
|
|
1658
|
+
paddingRight: 1,
|
|
1659
|
+
height: 3
|
|
1660
|
+
},
|
|
1661
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
1662
|
+
ref: inputRef,
|
|
1663
|
+
focused: true,
|
|
1664
|
+
placeholder: "search models — provider, name, capability…",
|
|
1665
|
+
onInput: handleQueryChange,
|
|
1666
|
+
onSubmit: () => {},
|
|
1667
|
+
style: { flexGrow: 1 }
|
|
1668
|
+
})
|
|
1497
1669
|
}),
|
|
1498
|
-
/* @__PURE__ */ jsx("
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1670
|
+
/* @__PURE__ */ jsx("box", {
|
|
1671
|
+
style: {
|
|
1672
|
+
flexDirection: "column",
|
|
1673
|
+
height: VISIBLE_ROWS,
|
|
1674
|
+
flexShrink: 0
|
|
1675
|
+
},
|
|
1676
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsxs("text", {
|
|
1677
|
+
fg: COLOR.dim,
|
|
1678
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1679
|
+
fg: COLOR.mute,
|
|
1680
|
+
children: "no models match "
|
|
1681
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1682
|
+
fg: COLOR.warn,
|
|
1683
|
+
children: query.trim()
|
|
1684
|
+
})]
|
|
1685
|
+
}) : viewport.slice.map((entry, i) => /* @__PURE__ */ jsx(ModelRow, {
|
|
1686
|
+
entry,
|
|
1687
|
+
isCurrent: entry.providerKey === current.providerKey && entry.model.id === current.modelId,
|
|
1688
|
+
isFocused: viewport.start + i === safeIndex,
|
|
1689
|
+
highlightBg: SURFACE.selection
|
|
1690
|
+
}, `${entry.providerKey}:${entry.model.id}`))
|
|
1509
1691
|
}),
|
|
1510
1692
|
/* @__PURE__ */ jsxs("text", {
|
|
1511
|
-
fg: COLOR.
|
|
1693
|
+
fg: COLOR.dim,
|
|
1512
1694
|
children: [
|
|
1513
1695
|
/* @__PURE__ */ jsx("span", {
|
|
1514
1696
|
fg: COLOR.warn,
|
|
@@ -1524,38 +1706,94 @@ function ModelPickerModal({ models, currentModelId, onPick }) {
|
|
|
1524
1706
|
fg: COLOR.warn,
|
|
1525
1707
|
children: "esc"
|
|
1526
1708
|
}),
|
|
1527
|
-
" close"
|
|
1709
|
+
" close · ",
|
|
1710
|
+
/* @__PURE__ */ jsx("span", {
|
|
1711
|
+
fg: COLOR.mute,
|
|
1712
|
+
children: `${filtered.length} / ${catalog.length} model${catalog.length === 1 ? "" : "s"}`
|
|
1713
|
+
})
|
|
1528
1714
|
]
|
|
1529
1715
|
})
|
|
1530
1716
|
]
|
|
1531
1717
|
});
|
|
1532
1718
|
}
|
|
1533
|
-
|
|
1719
|
+
/**
|
|
1720
|
+
* Single row in the picker. Renders the model name in `brand`, the
|
|
1721
|
+
* provider tag in `dim`, and the capability suffix in `mute`. The
|
|
1722
|
+
* focused row gets a subtle selection background (same surface as
|
|
1723
|
+
* select-turn mode in the transcript) so it pops without a separate
|
|
1724
|
+
* marker glyph competing with the `●` "current" indicator.
|
|
1725
|
+
*/
|
|
1726
|
+
function ModelRow({ entry, isCurrent, isFocused, highlightBg }) {
|
|
1727
|
+
const COLOR = useColors();
|
|
1728
|
+
const marker = isCurrent ? "●" : " ";
|
|
1729
|
+
return /* @__PURE__ */ jsx("box", {
|
|
1730
|
+
style: {
|
|
1731
|
+
height: 1,
|
|
1732
|
+
paddingLeft: 1,
|
|
1733
|
+
paddingRight: 1,
|
|
1734
|
+
flexShrink: 0,
|
|
1735
|
+
backgroundColor: isFocused ? highlightBg : void 0
|
|
1736
|
+
},
|
|
1737
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
1738
|
+
wrapMode: "none",
|
|
1739
|
+
children: [
|
|
1740
|
+
/* @__PURE__ */ jsx("span", {
|
|
1741
|
+
fg: isCurrent ? COLOR.brand : COLOR.mute,
|
|
1742
|
+
children: marker
|
|
1743
|
+
}),
|
|
1744
|
+
/* @__PURE__ */ jsx("span", {
|
|
1745
|
+
fg: COLOR.mute,
|
|
1746
|
+
children: " "
|
|
1747
|
+
}),
|
|
1748
|
+
/* @__PURE__ */ jsx("span", {
|
|
1749
|
+
fg: isFocused ? COLOR.brand : COLOR.dim,
|
|
1750
|
+
children: entry.model.name ?? entry.model.id
|
|
1751
|
+
}),
|
|
1752
|
+
/* @__PURE__ */ jsx("span", {
|
|
1753
|
+
fg: COLOR.mute,
|
|
1754
|
+
children: " · "
|
|
1755
|
+
}),
|
|
1756
|
+
/* @__PURE__ */ jsx("span", {
|
|
1757
|
+
fg: COLOR.model,
|
|
1758
|
+
children: entry.providerLabel
|
|
1759
|
+
}),
|
|
1760
|
+
/* @__PURE__ */ jsx("span", {
|
|
1761
|
+
fg: COLOR.mute,
|
|
1762
|
+
children: " · "
|
|
1763
|
+
}),
|
|
1764
|
+
/* @__PURE__ */ jsx("span", {
|
|
1765
|
+
fg: COLOR.mute,
|
|
1766
|
+
children: describeModel(entry.model)
|
|
1767
|
+
})
|
|
1768
|
+
]
|
|
1769
|
+
})
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
function EmptyProvidersState() {
|
|
1534
1773
|
const COLOR = useColors();
|
|
1535
1774
|
return /* @__PURE__ */ jsxs(Modal, {
|
|
1536
1775
|
title: "select model",
|
|
1537
1776
|
children: [/* @__PURE__ */ jsx("text", {
|
|
1538
1777
|
fg: COLOR.dim,
|
|
1539
|
-
children: "No
|
|
1778
|
+
children: "No authed providers — configure one via"
|
|
1540
1779
|
}), /* @__PURE__ */ jsxs("text", {
|
|
1541
|
-
fg: COLOR.
|
|
1780
|
+
fg: COLOR.dim,
|
|
1542
1781
|
children: [
|
|
1543
|
-
"Set",
|
|
1544
1782
|
/* @__PURE__ */ jsx("span", {
|
|
1545
1783
|
fg: COLOR.model,
|
|
1546
|
-
children: "
|
|
1784
|
+
children: " settings → re-configure providers"
|
|
1547
1785
|
}),
|
|
1548
|
-
"
|
|
1786
|
+
" or ",
|
|
1549
1787
|
/* @__PURE__ */ jsx("span", {
|
|
1550
1788
|
fg: COLOR.model,
|
|
1551
|
-
children: "
|
|
1789
|
+
children: "esc → sessions → settings"
|
|
1552
1790
|
}),
|
|
1553
|
-
"
|
|
1791
|
+
" first."
|
|
1554
1792
|
]
|
|
1555
1793
|
})]
|
|
1556
1794
|
});
|
|
1557
1795
|
}
|
|
1558
|
-
/** "ctx 200k · reasoning · vision" — compact
|
|
1796
|
+
/** "ctx 200k · reasoning · vision" — compact capability blurb. */
|
|
1559
1797
|
function describeModel(m) {
|
|
1560
1798
|
const parts = [`ctx ${fmtTokens(m.contextWindow)}`];
|
|
1561
1799
|
if (m.reasoning) parts.push("reasoning");
|
|
@@ -4131,6 +4369,7 @@ function AppShell() {
|
|
|
4131
4369
|
const SURFACE = useSurfaces();
|
|
4132
4370
|
const queue = useSafeModeQueue();
|
|
4133
4371
|
const { requestApproval, resolveHead, denyAll } = useSafeModeActions();
|
|
4372
|
+
const pendingApproval = queue[0] ?? null;
|
|
4134
4373
|
const { providers: providerRegistry, agents: agentRegistry, initialAgentId, store, stateStore, modelsFor, resumeProvider, initialPicked, initialState } = config;
|
|
4135
4374
|
const lastResumedSessionId = initialState.lastSessionId;
|
|
4136
4375
|
const dataDir = config.paths.userDir;
|
|
@@ -4497,6 +4736,13 @@ function AppShell() {
|
|
|
4497
4736
|
settings.showAllProjects,
|
|
4498
4737
|
sessionProjectRoot
|
|
4499
4738
|
]);
|
|
4739
|
+
const [availableProviders, setAvailableProviders] = useState([]);
|
|
4740
|
+
const refreshAvailableProviders = useCallback(() => {
|
|
4741
|
+
setAvailableProviders(detectAuth(config.paths.userDir, providerRegistry).filter((p) => p.available));
|
|
4742
|
+
}, [config.paths.userDir, providerRegistry]);
|
|
4743
|
+
useEffect(() => {
|
|
4744
|
+
refreshAvailableProviders();
|
|
4745
|
+
}, [refreshAvailableProviders]);
|
|
4500
4746
|
const onPickProvider = useCallback(async (p) => {
|
|
4501
4747
|
const next = makePicked(p);
|
|
4502
4748
|
if (!next) return;
|
|
@@ -4505,13 +4751,15 @@ function AppShell() {
|
|
|
4505
4751
|
...stateStore.load(),
|
|
4506
4752
|
lastProvider: p.key
|
|
4507
4753
|
});
|
|
4754
|
+
refreshAvailableProviders();
|
|
4508
4755
|
if ((await refreshSessions()).length === 0) await activateSession(null, p.key);
|
|
4509
4756
|
else setScreen("sessions");
|
|
4510
4757
|
}, [
|
|
4511
4758
|
refreshSessions,
|
|
4512
4759
|
activateSession,
|
|
4513
4760
|
makePicked,
|
|
4514
|
-
stateStore
|
|
4761
|
+
stateStore,
|
|
4762
|
+
refreshAvailableProviders
|
|
4515
4763
|
]);
|
|
4516
4764
|
const onCreateSession = useCallback(async () => {
|
|
4517
4765
|
if (picked) await activateSession(null, picked.provider.key);
|
|
@@ -4532,33 +4780,63 @@ function AppShell() {
|
|
|
4532
4780
|
denyAll();
|
|
4533
4781
|
agentRef.current?.abort();
|
|
4534
4782
|
}, [denyAll]);
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4783
|
+
/**
|
|
4784
|
+
* Pick a `{ providerKey, modelId }` tuple from the cross-provider
|
|
4785
|
+
* model picker. Two branches:
|
|
4786
|
+
*
|
|
4787
|
+
* - **Same provider, different model** — update `picked.model` +
|
|
4788
|
+
* remember it in `lastModelByProvider`. The active agent keeps
|
|
4789
|
+
* running; `agent.run({ model })` accepts the model per-call so
|
|
4790
|
+
* no session rebuild is needed.
|
|
4791
|
+
* - **Different provider** — swap the active `ProviderAuth`,
|
|
4792
|
+
* persist BOTH `lastProvider` + `lastModelByProvider`, then
|
|
4793
|
+
* re-activate the current session against the new provider's
|
|
4794
|
+
* factory. The session id (and conversation history) is
|
|
4795
|
+
* preserved; only the bound agent + provider instance change.
|
|
4796
|
+
*
|
|
4797
|
+
* Either branch re-resolves the reasoning effort: a non-reasoning
|
|
4798
|
+
* model loses its `effort`, a reasoning model gets the remembered
|
|
4799
|
+
* per-model value (falling back to a sensible default).
|
|
4800
|
+
*/
|
|
4801
|
+
const onPickModel = useCallback(async (next) => {
|
|
4802
|
+
const nextProvider = availableProviders.find((p) => p.key === next.providerKey);
|
|
4803
|
+
if (!nextProvider) {
|
|
4804
|
+
debugLog("onPickModel: unknown provider key", next.providerKey);
|
|
4805
|
+
modal.close();
|
|
4806
|
+
return;
|
|
4807
|
+
}
|
|
4808
|
+
const descriptor = providerRegistry[nextProvider.key];
|
|
4809
|
+
const prior = stateStore.load();
|
|
4810
|
+
const providerChanged = picked?.provider.key !== nextProvider.key;
|
|
4811
|
+
stateStore.save({
|
|
4812
|
+
...prior,
|
|
4813
|
+
...providerChanged ? { lastProvider: nextProvider.key } : {},
|
|
4814
|
+
lastModelByProvider: {
|
|
4815
|
+
...prior.lastModelByProvider,
|
|
4816
|
+
[nextProvider.key]: next.modelId
|
|
4817
|
+
}
|
|
4818
|
+
});
|
|
4819
|
+
const nextEffort = descriptor ? effortForModel(descriptor, next.modelId, prior.lastEffortByModel) : void 0;
|
|
4820
|
+
setPicked(nextEffort ? {
|
|
4821
|
+
provider: nextProvider,
|
|
4822
|
+
model: next.modelId,
|
|
4823
|
+
effort: nextEffort
|
|
4824
|
+
} : {
|
|
4825
|
+
provider: nextProvider,
|
|
4826
|
+
model: next.modelId
|
|
4556
4827
|
});
|
|
4557
4828
|
modal.close();
|
|
4829
|
+
if (providerChanged && currentSession && !busy && !pendingApproval) await activateSession(currentSession.id, nextProvider.key);
|
|
4558
4830
|
}, [
|
|
4559
|
-
|
|
4831
|
+
availableProviders,
|
|
4832
|
+
providerRegistry,
|
|
4560
4833
|
stateStore,
|
|
4561
|
-
|
|
4834
|
+
modal,
|
|
4835
|
+
picked,
|
|
4836
|
+
currentSession,
|
|
4837
|
+
busy,
|
|
4838
|
+
pendingApproval,
|
|
4839
|
+
activateSession
|
|
4562
4840
|
]);
|
|
4563
4841
|
const onPickEffort = useCallback((effort) => {
|
|
4564
4842
|
setPicked((prev) => {
|
|
@@ -4661,7 +4939,6 @@ function AppShell() {
|
|
|
4661
4939
|
setBusy(false);
|
|
4662
4940
|
}
|
|
4663
4941
|
}, [picked, stream]);
|
|
4664
|
-
const pendingApproval = queue[0] ?? null;
|
|
4665
4942
|
const onReauth = useMemo(() => {
|
|
4666
4943
|
if (busy || pendingApproval) return void 0;
|
|
4667
4944
|
return () => {
|
|
@@ -4932,7 +5209,7 @@ function AppShell() {
|
|
|
4932
5209
|
}
|
|
4933
5210
|
return;
|
|
4934
5211
|
}
|
|
4935
|
-
if (key.ctrl && key.name === "
|
|
5212
|
+
if (key.ctrl && key.name === "o" && screen !== "auth") {
|
|
4936
5213
|
modal.open(/* @__PURE__ */ jsx(SettingsModal, { actions: {
|
|
4937
5214
|
onReauth,
|
|
4938
5215
|
onOpenSkills: onOpenSkillsSettings,
|
|
@@ -4952,8 +5229,12 @@ function AppShell() {
|
|
|
4952
5229
|
}
|
|
4953
5230
|
if (key.ctrl && key.name === "m" && screen === "chat" && picked && !busy) {
|
|
4954
5231
|
modal.open(/* @__PURE__ */ jsx(ModelPickerModal, {
|
|
4955
|
-
|
|
4956
|
-
|
|
5232
|
+
providers: availableProviders,
|
|
5233
|
+
modelsFor,
|
|
5234
|
+
current: {
|
|
5235
|
+
providerKey: picked.provider.key,
|
|
5236
|
+
modelId: picked.model
|
|
5237
|
+
},
|
|
4957
5238
|
onPick: onPickModel
|
|
4958
5239
|
}));
|
|
4959
5240
|
return;
|
|
@@ -5159,7 +5440,7 @@ function buildHints({ screen, busy, pending, currentSession, hasMultipleAgents,
|
|
|
5159
5440
|
label: "session"
|
|
5160
5441
|
},
|
|
5161
5442
|
{
|
|
5162
|
-
key: "ctrl
|
|
5443
|
+
key: "ctrl+o",
|
|
5163
5444
|
label: "settings"
|
|
5164
5445
|
},
|
|
5165
5446
|
{
|
|
@@ -5190,7 +5471,7 @@ function buildHints({ screen, busy, pending, currentSession, hasMultipleAgents,
|
|
|
5190
5471
|
label: "session"
|
|
5191
5472
|
}] : [],
|
|
5192
5473
|
{
|
|
5193
|
-
key: "ctrl
|
|
5474
|
+
key: "ctrl+o",
|
|
5194
5475
|
label: "settings"
|
|
5195
5476
|
},
|
|
5196
5477
|
{
|