@lazyingart/agintiflow 0.19.2 → 0.20.1
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/docs/model-selection.md +4 -0
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +31 -1
- package/src/interactive-cli.js +280 -14
package/docs/model-selection.md
CHANGED
|
@@ -34,6 +34,8 @@ Interactive commands:
|
|
|
34
34
|
```text
|
|
35
35
|
/models
|
|
36
36
|
/venice
|
|
37
|
+
/route
|
|
38
|
+
/model
|
|
37
39
|
/route deepseek/deepseek-v4-flash
|
|
38
40
|
/model deepseek/deepseek-v4-pro
|
|
39
41
|
/spare openai/gpt-5.4 medium
|
|
@@ -41,6 +43,8 @@ Interactive commands:
|
|
|
41
43
|
/auxiliary model grsai/nano-banana-2
|
|
42
44
|
```
|
|
43
45
|
|
|
46
|
+
In the interactive CLI, `/route` and `/model` without arguments open a selector. Use Up/Down/Left/Right to move through choices, Enter to confirm, and Esc to cancel. Slash-command hints use the same arrow selection behavior: type a prefix such as `/mo`, use arrows to choose `/model` or `/models`, then press Enter or Tab.
|
|
47
|
+
|
|
44
48
|
`/venice` is a shortcut for:
|
|
45
49
|
|
|
46
50
|
```text
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -124,6 +124,17 @@ try {
|
|
|
124
124
|
if (promptLayout.cursorRow < 0 || promptLayout.cursorColumn < 0) {
|
|
125
125
|
throw new Error("terminal prompt layout returned an invalid cursor location");
|
|
126
126
|
}
|
|
127
|
+
const paddedPromptLayout = buildPromptLayout("hello", 5, 80, 24, { commandCwd: "/tmp/aginti-project" });
|
|
128
|
+
const paddedRows = paddedPromptLayout.renderedRows.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""));
|
|
129
|
+
if (
|
|
130
|
+
paddedPromptLayout.cursorRow !== 1 ||
|
|
131
|
+
paddedRows[0].trim() !== "" ||
|
|
132
|
+
!paddedRows[1].includes("user> hello") ||
|
|
133
|
+
paddedRows[2].trim() !== "" ||
|
|
134
|
+
!paddedRows[3].includes("cwd /tmp/aginti-project")
|
|
135
|
+
) {
|
|
136
|
+
throw new Error("terminal prompt layout did not render visual-only input padding safely");
|
|
137
|
+
}
|
|
127
138
|
const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
|
|
128
139
|
if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
|
|
129
140
|
throw new Error("terminal prompt layout did not bound redraw size for large prompts");
|
|
@@ -137,9 +148,28 @@ try {
|
|
|
137
148
|
const queuedText = queuedPromptLayout.renderedRows
|
|
138
149
|
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
139
150
|
.join("\n");
|
|
140
|
-
if (
|
|
151
|
+
if (
|
|
152
|
+
!queuedText.includes("run running · tool: apply_patch") ||
|
|
153
|
+
!queuedText.includes("→ apply this") ||
|
|
154
|
+
!queuedText.includes("↳ run this") ||
|
|
155
|
+
!queuedText.includes("cwd /tmp/aginti-project")
|
|
156
|
+
) {
|
|
141
157
|
throw new Error("terminal prompt layout did not render live input queue and cwd footer");
|
|
142
158
|
}
|
|
159
|
+
const hintPromptLayout = buildPromptLayout("/mo", 3, 90, 24, { suggestions: ["/models", "/model"], suggestionIndex: 1 });
|
|
160
|
+
const hintText = hintPromptLayout.renderedRows
|
|
161
|
+
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
162
|
+
.join("\n");
|
|
163
|
+
if (!hintText.includes("user> /mo") || !hintText.includes("hint /models >/model")) {
|
|
164
|
+
throw new Error("terminal prompt layout did not align user and hint text columns");
|
|
165
|
+
}
|
|
166
|
+
const exactHintLayout = buildPromptLayout("/model", 6, 90, 24);
|
|
167
|
+
const exactHintText = exactHintLayout.renderedRows
|
|
168
|
+
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
169
|
+
.join("\n");
|
|
170
|
+
if (!exactHintText.includes("hint >/model") || exactHintText.includes("/models")) {
|
|
171
|
+
throw new Error("exact slash commands should not show broader prefix matches");
|
|
172
|
+
}
|
|
143
173
|
if (classifyEscapeAction({ active: false }) !== "noop") {
|
|
144
174
|
throw new Error("idle Esc should not redraw or clear the prompt");
|
|
145
175
|
}
|
package/src/interactive-cli.js
CHANGED
|
@@ -171,9 +171,20 @@ function promptGutter() {
|
|
|
171
171
|
function commandSuggestions(line = "") {
|
|
172
172
|
const trimmed = String(line || "");
|
|
173
173
|
if (!trimmed.startsWith("/") || /\s/.test(trimmed)) return [];
|
|
174
|
+
if (SLASH_COMMANDS.includes(trimmed)) return [trimmed];
|
|
174
175
|
return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
function renderSuggestionList(suggestions = [], selectedIndex = 0) {
|
|
179
|
+
return suggestions
|
|
180
|
+
.map((suggestion, index) => {
|
|
181
|
+
const active = index === selectedIndex;
|
|
182
|
+
if (!active) return suggestion;
|
|
183
|
+
return useColor ? color(suggestion, ansi.userBg, ansi.bold) : `>${suggestion}`;
|
|
184
|
+
})
|
|
185
|
+
.join(" ");
|
|
186
|
+
}
|
|
187
|
+
|
|
177
188
|
function resolveSlashCommand(command = "") {
|
|
178
189
|
const raw = String(command || "").trim();
|
|
179
190
|
if (!raw) return raw;
|
|
@@ -574,9 +585,9 @@ function printHelp() {
|
|
|
574
585
|
" /memory Alias for /instructions.",
|
|
575
586
|
" /models Show route/main/spare/wrapper/auxiliary model roles.",
|
|
576
587
|
" /venice Shortcut: use Venice Uncensored 1.2 for route and main roles.",
|
|
577
|
-
" /route
|
|
578
|
-
"
|
|
579
|
-
" /model
|
|
588
|
+
" /route [mode|provider/model]",
|
|
589
|
+
" Open route selector, or set routing/fast route model.",
|
|
590
|
+
" /model [provider/model] Open main-model selector, or set the active/main model.",
|
|
580
591
|
" /spare <provider/model> [reasoning]",
|
|
581
592
|
" Set spare model, e.g. /spare openai/gpt-5.4 medium.",
|
|
582
593
|
" /wrapper [on|off|codex model reasoning]",
|
|
@@ -730,14 +741,18 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
730
741
|
}
|
|
731
742
|
}
|
|
732
743
|
|
|
733
|
-
const suggestions =
|
|
744
|
+
const suggestions = Array.isArray(options.suggestions)
|
|
745
|
+
? options.suggestions
|
|
746
|
+
: commandSuggestions(safeBuffer.split("\n")[0] || "");
|
|
747
|
+
const suggestionIndex = clamp(Number(options.suggestionIndex) || 0, 0, Math.max(suggestions.length - 1, 0));
|
|
734
748
|
const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
|
|
735
749
|
const visible = promptVisibleWindow(rows, cursorRow, height);
|
|
736
750
|
const renderedRows = [];
|
|
737
751
|
let renderedCursorRow = cursorRow - visible.start;
|
|
752
|
+
const useInputPadding = options.inputPadding !== false && height >= 10;
|
|
738
753
|
|
|
739
754
|
if (options.statusLine) {
|
|
740
|
-
renderedRows.push(panelLine(
|
|
755
|
+
renderedRows.push(panelLine(`${labelText("run", { prompt: true })}${compactLine(options.statusLine, lineWidth - 10)}`, ansi.statusBg, lineWidth));
|
|
741
756
|
renderedCursorRow += 1;
|
|
742
757
|
}
|
|
743
758
|
|
|
@@ -757,20 +772,29 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
757
772
|
renderedCursorRow += 1;
|
|
758
773
|
}
|
|
759
774
|
|
|
775
|
+
if (useInputPadding) {
|
|
776
|
+
renderedRows.push(panelLine("", ansi.userBg, lineWidth));
|
|
777
|
+
renderedCursorRow += 1;
|
|
778
|
+
}
|
|
779
|
+
|
|
760
780
|
for (const row of rows.slice(visible.start, visible.end)) {
|
|
761
781
|
const content = safeBuffer ? `${row.prefix}${row.text}` : `${row.prefix}${emptyHint}`;
|
|
762
782
|
renderedRows.push(panelLine(content, ansi.userBg, lineWidth));
|
|
763
783
|
}
|
|
764
784
|
|
|
785
|
+
if (useInputPadding) {
|
|
786
|
+
renderedRows.push(panelLine("", ansi.userBg, lineWidth));
|
|
787
|
+
}
|
|
788
|
+
|
|
765
789
|
if (visible.bottomHidden > 0) {
|
|
766
790
|
renderedRows.push(panelLine(` ... ${visible.bottomHidden} later input row${visible.bottomHidden === 1 ? "" : "s"}`, ansi.systemBg, lineWidth));
|
|
767
791
|
}
|
|
768
792
|
|
|
769
793
|
if (suggestions.length > 0) {
|
|
770
|
-
renderedRows.push(panelLine(
|
|
794
|
+
renderedRows.push(panelLine(`${labelText("hint", { prompt: true })}${renderSuggestionList(suggestions, suggestionIndex)}`, ansi.systemBg, lineWidth));
|
|
771
795
|
}
|
|
772
796
|
if (options.commandCwd) {
|
|
773
|
-
renderedRows.push(panelLine(
|
|
797
|
+
renderedRows.push(panelLine(`${labelText("cwd", { prompt: true })}${options.commandCwd}`, ansi.systemBg, lineWidth));
|
|
774
798
|
}
|
|
775
799
|
|
|
776
800
|
return {
|
|
@@ -884,6 +908,8 @@ function readTtyPrompt(options = {}) {
|
|
|
884
908
|
let historyIndex = promptHistory.length;
|
|
885
909
|
let draft = "";
|
|
886
910
|
let redrawHandle = null;
|
|
911
|
+
let suggestionAnchor = "";
|
|
912
|
+
let suggestionIndex = 0;
|
|
887
913
|
|
|
888
914
|
const cleanup = () => {
|
|
889
915
|
if (redrawHandle) {
|
|
@@ -901,14 +927,19 @@ function readTtyPrompt(options = {}) {
|
|
|
901
927
|
clearImmediate(redrawHandle);
|
|
902
928
|
redrawHandle = null;
|
|
903
929
|
}
|
|
904
|
-
|
|
930
|
+
const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
|
|
931
|
+
rendered = renderPromptBuffer(buffer, cursor, rendered, {
|
|
932
|
+
...options,
|
|
933
|
+
suggestions,
|
|
934
|
+
suggestionIndex,
|
|
935
|
+
});
|
|
905
936
|
};
|
|
906
937
|
|
|
907
938
|
const redraw = () => {
|
|
908
939
|
if (redrawHandle) return;
|
|
909
940
|
redrawHandle = setImmediate(() => {
|
|
910
941
|
redrawHandle = null;
|
|
911
|
-
|
|
942
|
+
renderNow();
|
|
912
943
|
});
|
|
913
944
|
};
|
|
914
945
|
|
|
@@ -922,13 +953,33 @@ function readTtyPrompt(options = {}) {
|
|
|
922
953
|
resolve(buffer);
|
|
923
954
|
};
|
|
924
955
|
|
|
925
|
-
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length) => {
|
|
956
|
+
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length, { keepSuggestionAnchor = false } = {}) => {
|
|
926
957
|
buffer = nextBuffer;
|
|
927
958
|
cursor = clamp(nextCursor, 0, buffer.length);
|
|
928
959
|
preferredColumn = null;
|
|
960
|
+
if (!keepSuggestionAnchor) {
|
|
961
|
+
suggestionAnchor = "";
|
|
962
|
+
suggestionIndex = 0;
|
|
963
|
+
}
|
|
929
964
|
redraw();
|
|
930
965
|
};
|
|
931
966
|
|
|
967
|
+
const cycleSuggestion = (delta) => {
|
|
968
|
+
const firstLine = buffer.split("\n")[0] || "";
|
|
969
|
+
if (!suggestionAnchor) suggestionAnchor = firstLine;
|
|
970
|
+
const suggestions = commandSuggestions(suggestionAnchor);
|
|
971
|
+
if (suggestions.length === 0) {
|
|
972
|
+
suggestionAnchor = "";
|
|
973
|
+
suggestionIndex = 0;
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
suggestionIndex = (suggestionIndex + delta + suggestions.length) % suggestions.length;
|
|
977
|
+
setBuffer(suggestions[suggestionIndex], suggestions[suggestionIndex].length, { keepSuggestionAnchor: true });
|
|
978
|
+
return true;
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
const suggestionModeActive = () => commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "").length > 1;
|
|
982
|
+
|
|
932
983
|
const moveVertical = (direction) => {
|
|
933
984
|
const layout = buildPromptLayout(buffer, cursor);
|
|
934
985
|
const location = cursorLocation(layout, cursor);
|
|
@@ -979,32 +1030,40 @@ function readTtyPrompt(options = {}) {
|
|
|
979
1030
|
if (key.name === "backspace") {
|
|
980
1031
|
({ buffer, cursor } = removeBefore(buffer, cursor));
|
|
981
1032
|
preferredColumn = null;
|
|
1033
|
+
suggestionAnchor = "";
|
|
1034
|
+
suggestionIndex = 0;
|
|
982
1035
|
redraw();
|
|
983
1036
|
return;
|
|
984
1037
|
}
|
|
985
1038
|
if (key.name === "delete") {
|
|
986
1039
|
({ buffer, cursor } = removeAt(buffer, cursor));
|
|
987
1040
|
preferredColumn = null;
|
|
1041
|
+
suggestionAnchor = "";
|
|
1042
|
+
suggestionIndex = 0;
|
|
988
1043
|
redraw();
|
|
989
1044
|
return;
|
|
990
1045
|
}
|
|
991
1046
|
if (key.name === "left") {
|
|
1047
|
+
if (suggestionModeActive() && cycleSuggestion(-1)) return;
|
|
992
1048
|
cursor = Math.max(cursor - 1, 0);
|
|
993
1049
|
preferredColumn = null;
|
|
994
1050
|
redraw();
|
|
995
1051
|
return;
|
|
996
1052
|
}
|
|
997
1053
|
if (key.name === "right") {
|
|
1054
|
+
if (suggestionModeActive() && cycleSuggestion(1)) return;
|
|
998
1055
|
cursor = Math.min(cursor + 1, buffer.length);
|
|
999
1056
|
preferredColumn = null;
|
|
1000
1057
|
redraw();
|
|
1001
1058
|
return;
|
|
1002
1059
|
}
|
|
1003
1060
|
if (key.name === "up") {
|
|
1061
|
+
if (suggestionModeActive() && cycleSuggestion(-1)) return;
|
|
1004
1062
|
moveVertical(-1);
|
|
1005
1063
|
return;
|
|
1006
1064
|
}
|
|
1007
1065
|
if (key.name === "down") {
|
|
1066
|
+
if (suggestionModeActive() && cycleSuggestion(1)) return;
|
|
1008
1067
|
moveVertical(1);
|
|
1009
1068
|
return;
|
|
1010
1069
|
}
|
|
@@ -1036,10 +1095,12 @@ function readTtyPrompt(options = {}) {
|
|
|
1036
1095
|
return;
|
|
1037
1096
|
}
|
|
1038
1097
|
if (key.name === "tab") {
|
|
1039
|
-
const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
|
|
1040
|
-
if (suggestions.length
|
|
1041
|
-
buffer = suggestions[0];
|
|
1098
|
+
const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
|
|
1099
|
+
if (suggestions.length > 0) {
|
|
1100
|
+
buffer = suggestions[clamp(suggestionIndex, 0, suggestions.length - 1)];
|
|
1042
1101
|
cursor = buffer.length;
|
|
1102
|
+
suggestionAnchor = "";
|
|
1103
|
+
suggestionIndex = 0;
|
|
1043
1104
|
}
|
|
1044
1105
|
preferredColumn = null;
|
|
1045
1106
|
redraw();
|
|
@@ -1054,6 +1115,8 @@ function readTtyPrompt(options = {}) {
|
|
|
1054
1115
|
const text = str.replace(/\r/g, "");
|
|
1055
1116
|
({ buffer, cursor } = insertAt(buffer, cursor, text));
|
|
1056
1117
|
preferredColumn = null;
|
|
1118
|
+
suggestionAnchor = "";
|
|
1119
|
+
suggestionIndex = 0;
|
|
1057
1120
|
redraw();
|
|
1058
1121
|
}
|
|
1059
1122
|
};
|
|
@@ -1601,6 +1664,193 @@ function printModelRoles(state) {
|
|
|
1601
1664
|
);
|
|
1602
1665
|
}
|
|
1603
1666
|
|
|
1667
|
+
function modelRoleChoices(role = "main") {
|
|
1668
|
+
const common = [
|
|
1669
|
+
{
|
|
1670
|
+
provider: "deepseek",
|
|
1671
|
+
model: "deepseek-v4-flash",
|
|
1672
|
+
label: "DeepSeek V4 Flash",
|
|
1673
|
+
description: "fast planner, short shell/browser/code work",
|
|
1674
|
+
route: true,
|
|
1675
|
+
},
|
|
1676
|
+
{
|
|
1677
|
+
provider: "deepseek",
|
|
1678
|
+
model: "deepseek-v4-pro",
|
|
1679
|
+
label: "DeepSeek V4 Pro",
|
|
1680
|
+
description: "complex coding, debugging, writing, and long tasks",
|
|
1681
|
+
main: true,
|
|
1682
|
+
},
|
|
1683
|
+
{
|
|
1684
|
+
provider: "venice",
|
|
1685
|
+
model: "venice-uncensored-1-2",
|
|
1686
|
+
label: "Venice Uncensored 1.2",
|
|
1687
|
+
description: "Venice text route; use /auth venice if missing",
|
|
1688
|
+
route: true,
|
|
1689
|
+
main: true,
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
provider: "openai",
|
|
1693
|
+
model: "gpt-5.5",
|
|
1694
|
+
label: "OpenAI GPT-5.5",
|
|
1695
|
+
description: "frontier spare/manual model",
|
|
1696
|
+
main: true,
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
provider: "openai",
|
|
1700
|
+
model: "gpt-5.4",
|
|
1701
|
+
label: "OpenAI GPT-5.4",
|
|
1702
|
+
description: "strong everyday coding spare",
|
|
1703
|
+
main: true,
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
provider: "openai",
|
|
1707
|
+
model: "gpt-5.4-mini",
|
|
1708
|
+
label: "OpenAI GPT-5.4 Mini",
|
|
1709
|
+
description: "small fast spare route",
|
|
1710
|
+
route: true,
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
provider: "qwen",
|
|
1714
|
+
model: "qwen-plus",
|
|
1715
|
+
label: "Qwen Plus",
|
|
1716
|
+
description: "general Qwen route",
|
|
1717
|
+
route: true,
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
provider: "qwen",
|
|
1721
|
+
model: "qwen-max",
|
|
1722
|
+
label: "Qwen Max",
|
|
1723
|
+
description: "larger Qwen route",
|
|
1724
|
+
main: true,
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
provider: "mock",
|
|
1728
|
+
model: "mock-agent",
|
|
1729
|
+
label: "Mock Agent",
|
|
1730
|
+
description: "local deterministic test route",
|
|
1731
|
+
route: true,
|
|
1732
|
+
main: true,
|
|
1733
|
+
},
|
|
1734
|
+
];
|
|
1735
|
+
const filtered = common.filter((item) => (role === "route" ? item.route : item.main));
|
|
1736
|
+
return role === "route"
|
|
1737
|
+
? filtered.sort((a, b) => Number(b.model === "deepseek-v4-flash") - Number(a.model === "deepseek-v4-flash"))
|
|
1738
|
+
: filtered.sort((a, b) => Number(b.model === "deepseek-v4-pro") - Number(a.model === "deepseek-v4-pro"));
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function modelChoiceLine(option) {
|
|
1742
|
+
return `${option.label} ${option.provider}/${option.model} ${option.description}`;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function clearSelector(lineCount) {
|
|
1746
|
+
for (let index = 0; index < lineCount; index += 1) {
|
|
1747
|
+
output.write("\x1b[1A\r\x1b[2K");
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0 }) {
|
|
1752
|
+
if (lineCount > 0) clearSelector(lineCount);
|
|
1753
|
+
const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
|
|
1754
|
+
const bodyWidth = width - 4;
|
|
1755
|
+
const rows = [
|
|
1756
|
+
`╭${"─".repeat(width - 2)}╮`,
|
|
1757
|
+
`│ ${padVisible(title, bodyWidth)} │`,
|
|
1758
|
+
`│ ${padVisible(subtitle, bodyWidth)} │`,
|
|
1759
|
+
`├${"─".repeat(width - 2)}┤`,
|
|
1760
|
+
...options.map((option, index) => {
|
|
1761
|
+
const marker = index === selectedIndex ? ">" : " ";
|
|
1762
|
+
const rendered = `${marker} ${compactLine(modelChoiceLine(option), bodyWidth - 2)}`;
|
|
1763
|
+
return `│ ${padVisible(index === selectedIndex ? color(rendered, ansi.userBg, ansi.bold) : rendered, bodyWidth)} │`;
|
|
1764
|
+
}),
|
|
1765
|
+
`╰${"─".repeat(width - 2)}╯`,
|
|
1766
|
+
];
|
|
1767
|
+
output.write(`${rows.join("\n")}\n`);
|
|
1768
|
+
return rows.length;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function selectModelChoice({ title, subtitle, options, initialIndex = 0 }) {
|
|
1772
|
+
if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") return Promise.resolve(null);
|
|
1773
|
+
return new Promise((resolve) => {
|
|
1774
|
+
emitKeypressEvents(input);
|
|
1775
|
+
const wasRaw = Boolean(input.isRaw);
|
|
1776
|
+
let selectedIndex = clamp(initialIndex, 0, Math.max(options.length - 1, 0));
|
|
1777
|
+
let lineCount = 0;
|
|
1778
|
+
const cleanup = () => {
|
|
1779
|
+
input.off("keypress", handler);
|
|
1780
|
+
if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
|
|
1781
|
+
input.pause();
|
|
1782
|
+
output.write(ansi.cursorShow);
|
|
1783
|
+
};
|
|
1784
|
+
const redraw = () => {
|
|
1785
|
+
lineCount = renderSelector({ title, subtitle, options, selectedIndex, lineCount });
|
|
1786
|
+
};
|
|
1787
|
+
const finish = (value) => {
|
|
1788
|
+
if (lineCount > 0) clearSelector(lineCount);
|
|
1789
|
+
cleanup();
|
|
1790
|
+
resolve(value);
|
|
1791
|
+
};
|
|
1792
|
+
const handler = (_str = "", key = {}) => {
|
|
1793
|
+
if (key.ctrl && key.name === "c") {
|
|
1794
|
+
finish(null);
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
if (key.name === "escape") {
|
|
1798
|
+
finish(null);
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (key.name === "up" || key.name === "left") {
|
|
1802
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
1803
|
+
redraw();
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
if (key.name === "down" || key.name === "right" || key.name === "tab") {
|
|
1807
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
1808
|
+
redraw();
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
if (key.name === "return" || key.name === "enter" || key.sequence === "\r") {
|
|
1812
|
+
finish(options[selectedIndex]);
|
|
1813
|
+
}
|
|
1814
|
+
};
|
|
1815
|
+
input.resume();
|
|
1816
|
+
input.setRawMode(true);
|
|
1817
|
+
input.on("keypress", handler);
|
|
1818
|
+
output.write(ansi.cursorHide);
|
|
1819
|
+
redraw();
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
async function pickModelRole(role, state) {
|
|
1824
|
+
const options = modelRoleChoices(role);
|
|
1825
|
+
const currentProvider = role === "route" ? state.routeProvider || "deepseek" : state.mainProvider || state.provider || "deepseek";
|
|
1826
|
+
const currentModel = role === "route" ? state.routeModel || "deepseek-v4-flash" : state.mainModel || "deepseek-v4-pro";
|
|
1827
|
+
const initialIndex = Math.max(
|
|
1828
|
+
options.findIndex((item) => item.provider === currentProvider && item.model === currentModel),
|
|
1829
|
+
0
|
|
1830
|
+
);
|
|
1831
|
+
const selected = await selectModelChoice({
|
|
1832
|
+
title: role === "route" ? "Select route model" : "Select main model",
|
|
1833
|
+
subtitle: "Up/Down/Left/Right selects, Enter confirms, Esc cancels.",
|
|
1834
|
+
options,
|
|
1835
|
+
initialIndex,
|
|
1836
|
+
});
|
|
1837
|
+
if (!selected) return false;
|
|
1838
|
+
|
|
1839
|
+
state.routingMode = "smart";
|
|
1840
|
+
state.provider = "deepseek";
|
|
1841
|
+
state.model = "";
|
|
1842
|
+
if (role === "route") {
|
|
1843
|
+
state.routeProvider = selected.provider;
|
|
1844
|
+
state.routeModel = selected.model;
|
|
1845
|
+
printSystemLine(`route=${state.routeProvider}/${state.routeModel}`);
|
|
1846
|
+
} else {
|
|
1847
|
+
state.mainProvider = selected.provider;
|
|
1848
|
+
state.mainModel = selected.model;
|
|
1849
|
+
printSystemLine(`main=${state.mainProvider}/${state.mainModel}`);
|
|
1850
|
+
}
|
|
1851
|
+
return true;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1604
1854
|
async function maybeOnboardDeepSeekKey(state) {
|
|
1605
1855
|
if (!shouldPromptForDeepSeek(state, process.cwd())) return;
|
|
1606
1856
|
|
|
@@ -1843,6 +2093,11 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1843
2093
|
}
|
|
1844
2094
|
if (command === "route") {
|
|
1845
2095
|
if (!value) {
|
|
2096
|
+
if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
|
|
2097
|
+
const changed = await pickModelRole("route", state);
|
|
2098
|
+
if (!changed) printSystemLine(`route=${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"}`);
|
|
2099
|
+
return true;
|
|
2100
|
+
}
|
|
1846
2101
|
printAgentMessage(
|
|
1847
2102
|
`Route model: ${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"}\nUse /route deepseek/deepseek-v4-flash or /route fast|smart|complex.`
|
|
1848
2103
|
);
|
|
@@ -1860,7 +2115,18 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1860
2115
|
return true;
|
|
1861
2116
|
}
|
|
1862
2117
|
if (command === "main" || command === "model") {
|
|
1863
|
-
if (!value
|
|
2118
|
+
if (!value) {
|
|
2119
|
+
if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
|
|
2120
|
+
const changed = await pickModelRole("main", state);
|
|
2121
|
+
if (!changed) printSystemLine(`main=${state.mainProvider || state.provider || "deepseek"}/${state.mainModel || state.model || "deepseek-v4-pro"}`);
|
|
2122
|
+
return true;
|
|
2123
|
+
}
|
|
2124
|
+
printAgentMessage(
|
|
2125
|
+
`Main model: ${state.mainProvider || state.provider || "deepseek"}/${state.mainModel || state.model || "deepseek-v4-pro"}\nUse /model deepseek/deepseek-v4-pro or /model auto.`
|
|
2126
|
+
);
|
|
2127
|
+
return true;
|
|
2128
|
+
}
|
|
2129
|
+
if (value === "auto") {
|
|
1864
2130
|
if (command === "main") {
|
|
1865
2131
|
state.mainProvider = "";
|
|
1866
2132
|
state.mainModel = "";
|