@lazyingart/agintiflow 0.19.1 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/docs/auxiliary-image-generation.md +0 -1
- package/docs/model-selection.md +6 -2
- package/package.json +1 -1
- package/references/model-routing-provider-design.md +2 -2
- package/scripts/smoke-auth.js +1 -1
- package/scripts/smoke-cli-chat.js +26 -1
- package/src/agent-runner.js +1 -1
- package/src/auth-onboarding.js +0 -1
- package/src/auxiliary-tools.js +1 -1
- package/src/capabilities.js +1 -1
- package/src/cli.js +1 -1
- package/src/interactive-cli.js +275 -20
- package/src/model-client.js +2 -2
- package/src/model-routing.js +1 -1
- package/src/project.js +0 -2
- package/src/task-profiles.js +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
|
|
|
60
60
|
|
|
61
61
|
# optional image-generation auxiliary skill:
|
|
62
62
|
aginti login grsai
|
|
63
|
-
# inside chat, use /
|
|
63
|
+
# inside chat, use /auxiliary grsai
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Start an interactive Codex-style CLI chat from any project folder:
|
|
@@ -77,7 +77,7 @@ Inside chat, type normal requests such as `write a small Python CLI app with tes
|
|
|
77
77
|
|
|
78
78
|
For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
|
|
79
79
|
|
|
80
|
-
Model choice is role-based: `/route` defaults to DeepSeek V4 Flash, `/model` or `/main` defaults to DeepSeek V4 Pro, `/spare` defaults to OpenAI GPT-5.4 medium, `/wrapper` defaults to Codex GPT-5.5 medium when enabled, and `/
|
|
80
|
+
Model choice is role-based: `/route` defaults to DeepSeek V4 Flash, `/model` or `/main` defaults to DeepSeek V4 Pro, `/spare` defaults to OpenAI GPT-5.4 medium, `/wrapper` defaults to Codex GPT-5.5 medium when enabled, and `/auxiliary` defaults to GRS AI/Nano Banana for image tools. See [docs/model-selection.md](docs/model-selection.md) and run `aginti models`.
|
|
81
81
|
|
|
82
82
|
For larger repositories, use `--profile large-codebase` or choose **Large codebase engineering** in the web UI. The web default stays **Auto**, and Auto now escalates codebase/system/debugging prompts to the same engineering loop when needed. Complex work routes to DeepSeek v4 pro, starts with `inspect_project`, then uses search/read/patch/check loops inspired by Codex, Copilot SDK, Claude Code, Gemini CLI, Qwen, and Claw Code. See [docs/large-codebase-engineering.md](docs/large-codebase-engineering.md).
|
|
83
83
|
|
package/docs/model-selection.md
CHANGED
|
@@ -10,7 +10,7 @@ AgInTiFlow treats model choice as a role-based control plane. A provider supplie
|
|
|
10
10
|
| Main | `/model` or `/main` | `deepseek/deepseek-v4-pro` | Complex executor for coding, debugging, writing, and long tasks. |
|
|
11
11
|
| Spare | `/spare` | `openai/gpt-5.4` with `medium` reasoning | Optional fallback or cross-check model. |
|
|
12
12
|
| Wrapper | `/wrapper` | `codex gpt-5.5 medium` | External coding assistant when wrapper tools are enabled. |
|
|
13
|
-
| Auxiliary | `/
|
|
13
|
+
| Auxiliary | `/auxiliary` | `grsai/nano-banana-2` | Image/media tools; Venice image models are optional. |
|
|
14
14
|
|
|
15
15
|
Smart routing still works as before: normal work goes to the route model, and complex work goes to the main model. Manual provider/model selection remains available for one-off runs.
|
|
16
16
|
|
|
@@ -34,13 +34,17 @@ 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
|
|
40
42
|
/wrapper codex gpt-5.5 medium
|
|
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.0",
|
|
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",
|
|
@@ -22,7 +22,7 @@ The UI should be role-first rather than provider-first:
|
|
|
22
22
|
- **Main model (`/model` or `/main`):** complex executor. Default `deepseek/deepseek-v4-pro`.
|
|
23
23
|
- **Spare model (`/spare`):** fallback/cross-check model. Default `openai/gpt-5.4` with `medium` reasoning.
|
|
24
24
|
- **Wrapper (`/wrapper`):** external coding assistant. Default Codex with `gpt-5.5` medium reasoning, disabled unless wrapper tools are enabled.
|
|
25
|
-
- **Auxiliary (`/
|
|
25
|
+
- **Auxiliary (`/auxiliary`):** media/image tools. Default `grsai/nano-banana-2`; Venice image models are optional.
|
|
26
26
|
|
|
27
27
|
This keeps the mental model stable: providers are supply, roles are policy.
|
|
28
28
|
|
|
@@ -47,7 +47,7 @@ Interactive equivalents:
|
|
|
47
47
|
/model deepseek/deepseek-v4-pro
|
|
48
48
|
/spare openai/gpt-5.4 medium
|
|
49
49
|
/wrapper codex gpt-5.5 medium
|
|
50
|
-
/
|
|
50
|
+
/auxiliary model grsai/nano-banana-2
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
`/venice` keeps smart routing enabled and points both route and main roles at `venice/venice-uncensored-1-2`.
|
package/scripts/smoke-auth.js
CHANGED
|
@@ -54,7 +54,7 @@ async function runCli(args, stdin = "") {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
try {
|
|
57
|
-
assert(normalizeAuthProvider("
|
|
57
|
+
assert(normalizeAuthProvider("auxiliary") === "grsai", "auxiliary alias did not normalize to grsai");
|
|
58
58
|
assert(normalizeAuthProvider("qwen") === "qwen", "qwen provider did not normalize");
|
|
59
59
|
assert(normalizeAuthProvider("venice") === "venice", "venice provider did not normalize");
|
|
60
60
|
assert(authProviderKeyUrl("deepseek") === "https://platform.deepseek.com/api_keys", "DeepSeek key URL is missing");
|
|
@@ -137,9 +137,28 @@ try {
|
|
|
137
137
|
const queuedText = queuedPromptLayout.renderedRows
|
|
138
138
|
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
139
139
|
.join("\n");
|
|
140
|
-
if (
|
|
140
|
+
if (
|
|
141
|
+
!queuedText.includes("run running · tool: apply_patch") ||
|
|
142
|
+
!queuedText.includes("→ apply this") ||
|
|
143
|
+
!queuedText.includes("↳ run this") ||
|
|
144
|
+
!queuedText.includes("cwd /tmp/aginti-project")
|
|
145
|
+
) {
|
|
141
146
|
throw new Error("terminal prompt layout did not render live input queue and cwd footer");
|
|
142
147
|
}
|
|
148
|
+
const hintPromptLayout = buildPromptLayout("/mo", 3, 90, 24, { suggestions: ["/models", "/model"], suggestionIndex: 1 });
|
|
149
|
+
const hintText = hintPromptLayout.renderedRows
|
|
150
|
+
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
151
|
+
.join("\n");
|
|
152
|
+
if (!hintText.includes("user> /mo") || !hintText.includes("hint /models >/model")) {
|
|
153
|
+
throw new Error("terminal prompt layout did not align user and hint text columns");
|
|
154
|
+
}
|
|
155
|
+
const exactHintLayout = buildPromptLayout("/model", 6, 90, 24);
|
|
156
|
+
const exactHintText = exactHintLayout.renderedRows
|
|
157
|
+
.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, ""))
|
|
158
|
+
.join("\n");
|
|
159
|
+
if (!exactHintText.includes("hint >/model") || exactHintText.includes("/models")) {
|
|
160
|
+
throw new Error("exact slash commands should not show broader prefix matches");
|
|
161
|
+
}
|
|
143
162
|
if (classifyEscapeAction({ active: false }) !== "noop") {
|
|
144
163
|
throw new Error("idle Esc should not redraw or clear the prompt");
|
|
145
164
|
}
|
|
@@ -159,6 +178,11 @@ try {
|
|
|
159
178
|
if (!instructionsResult.stdout.includes("AGINTI.md") || !instructionsResult.stdout.includes("Project instructions")) {
|
|
160
179
|
throw new Error("interactive /instructions did not show AGINTI.md");
|
|
161
180
|
}
|
|
181
|
+
const helpResult = await runChat("/help\n/exit\n");
|
|
182
|
+
const misspelledAuxiliary = "/auxil" + "liary";
|
|
183
|
+
if (!helpResult.stdout.includes("/auxiliary") || helpResult.stdout.includes(misspelledAuxiliary)) {
|
|
184
|
+
throw new Error("interactive help did not expose only the correctly spelled /auxiliary command");
|
|
185
|
+
}
|
|
162
186
|
const skillsResult = await runChat("/skills website\n/exit\n");
|
|
163
187
|
if (!skillsResult.stdout.includes("website-app") || !skillsResult.stdout.includes("Website And App Builder")) {
|
|
164
188
|
throw new Error("interactive /skills did not show matching built-in skills");
|
|
@@ -217,6 +241,7 @@ try {
|
|
|
217
241
|
"agent-response-gutter",
|
|
218
242
|
"aginti-md",
|
|
219
243
|
"instructions-command",
|
|
244
|
+
"auxiliary-command-spelling",
|
|
220
245
|
"skills-command",
|
|
221
246
|
"slash-prefix-autoselect",
|
|
222
247
|
"instructions-chat-edit",
|
package/src/agent-runner.js
CHANGED
|
@@ -319,7 +319,7 @@ async function createInitialState(config, sessionId) {
|
|
|
319
319
|
config.allowAuxiliaryTools
|
|
320
320
|
? `Auxiliary skills are available: ${listAuxiliarySkills()
|
|
321
321
|
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
322
|
-
.join(", ")}. Use generate_image for real raster image/photo/illustration/cover/poster/logo requests when appropriate; if image keys are missing, ask the user to run /
|
|
322
|
+
.join(", ")}. Use generate_image for real raster image/photo/illustration/cover/poster/logo requests when appropriate; if image keys are missing, ask the user to run /auxiliary grsai, aginti login grsai, or aginti login venice.`
|
|
323
323
|
: "Auxiliary skills are disabled for this run.",
|
|
324
324
|
config.allowWebSearch
|
|
325
325
|
? "web_search is available for current information, docs, package/toolchain errors, and source discovery. Prefer web_search over browser search-engine navigation."
|
package/src/auth-onboarding.js
CHANGED
package/src/auxiliary-tools.js
CHANGED
|
@@ -424,7 +424,7 @@ export async function generateImage(args = {}, config = {}) {
|
|
|
424
424
|
|
|
425
425
|
const apiKey = grsaiKey();
|
|
426
426
|
if (!apiKey) {
|
|
427
|
-
throw new Error("Missing GRSAI key. Run `aginti login grsai`, `aginti keys set grsai --stdin`, or `/
|
|
427
|
+
throw new Error("Missing GRSAI key. Run `aginti login grsai`, `aginti keys set grsai --stdin`, or `/auxiliary grsai`.");
|
|
428
428
|
}
|
|
429
429
|
|
|
430
430
|
const submitUrl = `${host}/v1/draw/nano-banana`;
|
package/src/capabilities.js
CHANGED
|
@@ -165,7 +165,7 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
|
|
|
165
165
|
capability("venice-key", keyStatus.venice, { envVars: keyStatus.envVars.venice }),
|
|
166
166
|
capability("grsai-key", keyStatus.grsai, {
|
|
167
167
|
envVars: keyStatus.envVars.grsai,
|
|
168
|
-
setup: "Optional for image generation. Run `aginti login grsai` or use `/
|
|
168
|
+
setup: "Optional for image generation. Run `aginti login grsai` or use `/auxiliary grsai` in chat.",
|
|
169
169
|
}),
|
|
170
170
|
capability("file-tools", Boolean(config.allowFileTools), { workspace: config.commandCwd }),
|
|
171
171
|
capability("shell-tool", Boolean(config.allowShellTool), {
|
package/src/cli.js
CHANGED
|
@@ -354,7 +354,7 @@ function providerLabel(provider) {
|
|
|
354
354
|
if (normalized === "openai") return "OpenAI";
|
|
355
355
|
if (normalized === "qwen") return "Qwen";
|
|
356
356
|
if (normalized === "venice") return "Venice";
|
|
357
|
-
if (normalized === "grsai" || normalized === "auxiliary"
|
|
357
|
+
if (normalized === "grsai" || normalized === "auxiliary") return "GRSAI";
|
|
358
358
|
return "DeepSeek";
|
|
359
359
|
}
|
|
360
360
|
|
package/src/interactive-cli.js
CHANGED
|
@@ -56,7 +56,6 @@ const SLASH_COMMANDS = [
|
|
|
56
56
|
"/auth",
|
|
57
57
|
"/instructions",
|
|
58
58
|
"/memory",
|
|
59
|
-
"/auxilliary",
|
|
60
59
|
"/auxiliary",
|
|
61
60
|
"/new",
|
|
62
61
|
"/resume",
|
|
@@ -172,9 +171,20 @@ function promptGutter() {
|
|
|
172
171
|
function commandSuggestions(line = "") {
|
|
173
172
|
const trimmed = String(line || "");
|
|
174
173
|
if (!trimmed.startsWith("/") || /\s/.test(trimmed)) return [];
|
|
174
|
+
if (SLASH_COMMANDS.includes(trimmed)) return [trimmed];
|
|
175
175
|
return SLASH_COMMANDS.filter((command) => command.startsWith(trimmed)).slice(0, 8);
|
|
176
176
|
}
|
|
177
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
|
+
|
|
178
188
|
function resolveSlashCommand(command = "") {
|
|
179
189
|
const raw = String(command || "").trim();
|
|
180
190
|
if (!raw) return raw;
|
|
@@ -575,14 +585,14 @@ function printHelp() {
|
|
|
575
585
|
" /memory Alias for /instructions.",
|
|
576
586
|
" /models Show route/main/spare/wrapper/auxiliary model roles.",
|
|
577
587
|
" /venice Shortcut: use Venice Uncensored 1.2 for route and main roles.",
|
|
578
|
-
" /route
|
|
579
|
-
"
|
|
580
|
-
" /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.",
|
|
581
591
|
" /spare <provider/model> [reasoning]",
|
|
582
592
|
" Set spare model, e.g. /spare openai/gpt-5.4 medium.",
|
|
583
593
|
" /wrapper [on|off|codex model reasoning]",
|
|
584
594
|
" Configure optional external wrapper.",
|
|
585
|
-
" /
|
|
595
|
+
" /auxiliary [status|grsai|venice|model grsai/nano-banana-2|on|off|image]",
|
|
586
596
|
" Manage optional auxiliary skills, including image generation.",
|
|
587
597
|
" /new Start a fresh session on the next message.",
|
|
588
598
|
" /resume <session-id> Continue a saved session.",
|
|
@@ -731,14 +741,17 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
731
741
|
}
|
|
732
742
|
}
|
|
733
743
|
|
|
734
|
-
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));
|
|
735
748
|
const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
|
|
736
749
|
const visible = promptVisibleWindow(rows, cursorRow, height);
|
|
737
750
|
const renderedRows = [];
|
|
738
751
|
let renderedCursorRow = cursorRow - visible.start;
|
|
739
752
|
|
|
740
753
|
if (options.statusLine) {
|
|
741
|
-
renderedRows.push(panelLine(
|
|
754
|
+
renderedRows.push(panelLine(`${labelText("run", { prompt: true })}${compactLine(options.statusLine, lineWidth - 10)}`, ansi.statusBg, lineWidth));
|
|
742
755
|
renderedCursorRow += 1;
|
|
743
756
|
}
|
|
744
757
|
|
|
@@ -768,10 +781,10 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
|
|
|
768
781
|
}
|
|
769
782
|
|
|
770
783
|
if (suggestions.length > 0) {
|
|
771
|
-
renderedRows.push(panelLine(
|
|
784
|
+
renderedRows.push(panelLine(`${labelText("hint", { prompt: true })}${renderSuggestionList(suggestions, suggestionIndex)}`, ansi.systemBg, lineWidth));
|
|
772
785
|
}
|
|
773
786
|
if (options.commandCwd) {
|
|
774
|
-
renderedRows.push(panelLine(
|
|
787
|
+
renderedRows.push(panelLine(`${labelText("cwd", { prompt: true })}${options.commandCwd}`, ansi.systemBg, lineWidth));
|
|
775
788
|
}
|
|
776
789
|
|
|
777
790
|
return {
|
|
@@ -885,6 +898,8 @@ function readTtyPrompt(options = {}) {
|
|
|
885
898
|
let historyIndex = promptHistory.length;
|
|
886
899
|
let draft = "";
|
|
887
900
|
let redrawHandle = null;
|
|
901
|
+
let suggestionAnchor = "";
|
|
902
|
+
let suggestionIndex = 0;
|
|
888
903
|
|
|
889
904
|
const cleanup = () => {
|
|
890
905
|
if (redrawHandle) {
|
|
@@ -902,14 +917,19 @@ function readTtyPrompt(options = {}) {
|
|
|
902
917
|
clearImmediate(redrawHandle);
|
|
903
918
|
redrawHandle = null;
|
|
904
919
|
}
|
|
905
|
-
|
|
920
|
+
const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
|
|
921
|
+
rendered = renderPromptBuffer(buffer, cursor, rendered, {
|
|
922
|
+
...options,
|
|
923
|
+
suggestions,
|
|
924
|
+
suggestionIndex,
|
|
925
|
+
});
|
|
906
926
|
};
|
|
907
927
|
|
|
908
928
|
const redraw = () => {
|
|
909
929
|
if (redrawHandle) return;
|
|
910
930
|
redrawHandle = setImmediate(() => {
|
|
911
931
|
redrawHandle = null;
|
|
912
|
-
|
|
932
|
+
renderNow();
|
|
913
933
|
});
|
|
914
934
|
};
|
|
915
935
|
|
|
@@ -923,13 +943,33 @@ function readTtyPrompt(options = {}) {
|
|
|
923
943
|
resolve(buffer);
|
|
924
944
|
};
|
|
925
945
|
|
|
926
|
-
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length) => {
|
|
946
|
+
const setBuffer = (nextBuffer, nextCursor = nextBuffer.length, { keepSuggestionAnchor = false } = {}) => {
|
|
927
947
|
buffer = nextBuffer;
|
|
928
948
|
cursor = clamp(nextCursor, 0, buffer.length);
|
|
929
949
|
preferredColumn = null;
|
|
950
|
+
if (!keepSuggestionAnchor) {
|
|
951
|
+
suggestionAnchor = "";
|
|
952
|
+
suggestionIndex = 0;
|
|
953
|
+
}
|
|
930
954
|
redraw();
|
|
931
955
|
};
|
|
932
956
|
|
|
957
|
+
const cycleSuggestion = (delta) => {
|
|
958
|
+
const firstLine = buffer.split("\n")[0] || "";
|
|
959
|
+
if (!suggestionAnchor) suggestionAnchor = firstLine;
|
|
960
|
+
const suggestions = commandSuggestions(suggestionAnchor);
|
|
961
|
+
if (suggestions.length === 0) {
|
|
962
|
+
suggestionAnchor = "";
|
|
963
|
+
suggestionIndex = 0;
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
suggestionIndex = (suggestionIndex + delta + suggestions.length) % suggestions.length;
|
|
967
|
+
setBuffer(suggestions[suggestionIndex], suggestions[suggestionIndex].length, { keepSuggestionAnchor: true });
|
|
968
|
+
return true;
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
const suggestionModeActive = () => commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "").length > 1;
|
|
972
|
+
|
|
933
973
|
const moveVertical = (direction) => {
|
|
934
974
|
const layout = buildPromptLayout(buffer, cursor);
|
|
935
975
|
const location = cursorLocation(layout, cursor);
|
|
@@ -980,32 +1020,40 @@ function readTtyPrompt(options = {}) {
|
|
|
980
1020
|
if (key.name === "backspace") {
|
|
981
1021
|
({ buffer, cursor } = removeBefore(buffer, cursor));
|
|
982
1022
|
preferredColumn = null;
|
|
1023
|
+
suggestionAnchor = "";
|
|
1024
|
+
suggestionIndex = 0;
|
|
983
1025
|
redraw();
|
|
984
1026
|
return;
|
|
985
1027
|
}
|
|
986
1028
|
if (key.name === "delete") {
|
|
987
1029
|
({ buffer, cursor } = removeAt(buffer, cursor));
|
|
988
1030
|
preferredColumn = null;
|
|
1031
|
+
suggestionAnchor = "";
|
|
1032
|
+
suggestionIndex = 0;
|
|
989
1033
|
redraw();
|
|
990
1034
|
return;
|
|
991
1035
|
}
|
|
992
1036
|
if (key.name === "left") {
|
|
1037
|
+
if (suggestionModeActive() && cycleSuggestion(-1)) return;
|
|
993
1038
|
cursor = Math.max(cursor - 1, 0);
|
|
994
1039
|
preferredColumn = null;
|
|
995
1040
|
redraw();
|
|
996
1041
|
return;
|
|
997
1042
|
}
|
|
998
1043
|
if (key.name === "right") {
|
|
1044
|
+
if (suggestionModeActive() && cycleSuggestion(1)) return;
|
|
999
1045
|
cursor = Math.min(cursor + 1, buffer.length);
|
|
1000
1046
|
preferredColumn = null;
|
|
1001
1047
|
redraw();
|
|
1002
1048
|
return;
|
|
1003
1049
|
}
|
|
1004
1050
|
if (key.name === "up") {
|
|
1051
|
+
if (suggestionModeActive() && cycleSuggestion(-1)) return;
|
|
1005
1052
|
moveVertical(-1);
|
|
1006
1053
|
return;
|
|
1007
1054
|
}
|
|
1008
1055
|
if (key.name === "down") {
|
|
1056
|
+
if (suggestionModeActive() && cycleSuggestion(1)) return;
|
|
1009
1057
|
moveVertical(1);
|
|
1010
1058
|
return;
|
|
1011
1059
|
}
|
|
@@ -1037,10 +1085,12 @@ function readTtyPrompt(options = {}) {
|
|
|
1037
1085
|
return;
|
|
1038
1086
|
}
|
|
1039
1087
|
if (key.name === "tab") {
|
|
1040
|
-
const suggestions = commandSuggestions(buffer.split("\n")[0] || "");
|
|
1041
|
-
if (suggestions.length
|
|
1042
|
-
buffer = suggestions[0];
|
|
1088
|
+
const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
|
|
1089
|
+
if (suggestions.length > 0) {
|
|
1090
|
+
buffer = suggestions[clamp(suggestionIndex, 0, suggestions.length - 1)];
|
|
1043
1091
|
cursor = buffer.length;
|
|
1092
|
+
suggestionAnchor = "";
|
|
1093
|
+
suggestionIndex = 0;
|
|
1044
1094
|
}
|
|
1045
1095
|
preferredColumn = null;
|
|
1046
1096
|
redraw();
|
|
@@ -1055,6 +1105,8 @@ function readTtyPrompt(options = {}) {
|
|
|
1055
1105
|
const text = str.replace(/\r/g, "");
|
|
1056
1106
|
({ buffer, cursor } = insertAt(buffer, cursor, text));
|
|
1057
1107
|
preferredColumn = null;
|
|
1108
|
+
suggestionAnchor = "";
|
|
1109
|
+
suggestionIndex = 0;
|
|
1058
1110
|
redraw();
|
|
1059
1111
|
}
|
|
1060
1112
|
};
|
|
@@ -1588,7 +1640,7 @@ function printModelRoles(state) {
|
|
|
1588
1640
|
"/model deepseek/deepseek-v4-pro",
|
|
1589
1641
|
"/spare openai/gpt-5.4 medium",
|
|
1590
1642
|
"/wrapper codex gpt-5.5 medium",
|
|
1591
|
-
"/
|
|
1643
|
+
"/auxiliary model grsai/nano-banana-2",
|
|
1592
1644
|
"",
|
|
1593
1645
|
"Provider groups",
|
|
1594
1646
|
...groups,
|
|
@@ -1602,6 +1654,193 @@ function printModelRoles(state) {
|
|
|
1602
1654
|
);
|
|
1603
1655
|
}
|
|
1604
1656
|
|
|
1657
|
+
function modelRoleChoices(role = "main") {
|
|
1658
|
+
const common = [
|
|
1659
|
+
{
|
|
1660
|
+
provider: "deepseek",
|
|
1661
|
+
model: "deepseek-v4-flash",
|
|
1662
|
+
label: "DeepSeek V4 Flash",
|
|
1663
|
+
description: "fast planner, short shell/browser/code work",
|
|
1664
|
+
route: true,
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
provider: "deepseek",
|
|
1668
|
+
model: "deepseek-v4-pro",
|
|
1669
|
+
label: "DeepSeek V4 Pro",
|
|
1670
|
+
description: "complex coding, debugging, writing, and long tasks",
|
|
1671
|
+
main: true,
|
|
1672
|
+
},
|
|
1673
|
+
{
|
|
1674
|
+
provider: "venice",
|
|
1675
|
+
model: "venice-uncensored-1-2",
|
|
1676
|
+
label: "Venice Uncensored 1.2",
|
|
1677
|
+
description: "Venice text route; use /auth venice if missing",
|
|
1678
|
+
route: true,
|
|
1679
|
+
main: true,
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
provider: "openai",
|
|
1683
|
+
model: "gpt-5.5",
|
|
1684
|
+
label: "OpenAI GPT-5.5",
|
|
1685
|
+
description: "frontier spare/manual model",
|
|
1686
|
+
main: true,
|
|
1687
|
+
},
|
|
1688
|
+
{
|
|
1689
|
+
provider: "openai",
|
|
1690
|
+
model: "gpt-5.4",
|
|
1691
|
+
label: "OpenAI GPT-5.4",
|
|
1692
|
+
description: "strong everyday coding spare",
|
|
1693
|
+
main: true,
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
provider: "openai",
|
|
1697
|
+
model: "gpt-5.4-mini",
|
|
1698
|
+
label: "OpenAI GPT-5.4 Mini",
|
|
1699
|
+
description: "small fast spare route",
|
|
1700
|
+
route: true,
|
|
1701
|
+
},
|
|
1702
|
+
{
|
|
1703
|
+
provider: "qwen",
|
|
1704
|
+
model: "qwen-plus",
|
|
1705
|
+
label: "Qwen Plus",
|
|
1706
|
+
description: "general Qwen route",
|
|
1707
|
+
route: true,
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
provider: "qwen",
|
|
1711
|
+
model: "qwen-max",
|
|
1712
|
+
label: "Qwen Max",
|
|
1713
|
+
description: "larger Qwen route",
|
|
1714
|
+
main: true,
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
provider: "mock",
|
|
1718
|
+
model: "mock-agent",
|
|
1719
|
+
label: "Mock Agent",
|
|
1720
|
+
description: "local deterministic test route",
|
|
1721
|
+
route: true,
|
|
1722
|
+
main: true,
|
|
1723
|
+
},
|
|
1724
|
+
];
|
|
1725
|
+
const filtered = common.filter((item) => (role === "route" ? item.route : item.main));
|
|
1726
|
+
return role === "route"
|
|
1727
|
+
? filtered.sort((a, b) => Number(b.model === "deepseek-v4-flash") - Number(a.model === "deepseek-v4-flash"))
|
|
1728
|
+
: filtered.sort((a, b) => Number(b.model === "deepseek-v4-pro") - Number(a.model === "deepseek-v4-pro"));
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function modelChoiceLine(option) {
|
|
1732
|
+
return `${option.label} ${option.provider}/${option.model} ${option.description}`;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function clearSelector(lineCount) {
|
|
1736
|
+
for (let index = 0; index < lineCount; index += 1) {
|
|
1737
|
+
output.write("\x1b[1A\r\x1b[2K");
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0 }) {
|
|
1742
|
+
if (lineCount > 0) clearSelector(lineCount);
|
|
1743
|
+
const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
|
|
1744
|
+
const bodyWidth = width - 4;
|
|
1745
|
+
const rows = [
|
|
1746
|
+
`╭${"─".repeat(width - 2)}╮`,
|
|
1747
|
+
`│ ${padVisible(title, bodyWidth)} │`,
|
|
1748
|
+
`│ ${padVisible(subtitle, bodyWidth)} │`,
|
|
1749
|
+
`├${"─".repeat(width - 2)}┤`,
|
|
1750
|
+
...options.map((option, index) => {
|
|
1751
|
+
const marker = index === selectedIndex ? ">" : " ";
|
|
1752
|
+
const rendered = `${marker} ${compactLine(modelChoiceLine(option), bodyWidth - 2)}`;
|
|
1753
|
+
return `│ ${padVisible(index === selectedIndex ? color(rendered, ansi.userBg, ansi.bold) : rendered, bodyWidth)} │`;
|
|
1754
|
+
}),
|
|
1755
|
+
`╰${"─".repeat(width - 2)}╯`,
|
|
1756
|
+
];
|
|
1757
|
+
output.write(`${rows.join("\n")}\n`);
|
|
1758
|
+
return rows.length;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function selectModelChoice({ title, subtitle, options, initialIndex = 0 }) {
|
|
1762
|
+
if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") return Promise.resolve(null);
|
|
1763
|
+
return new Promise((resolve) => {
|
|
1764
|
+
emitKeypressEvents(input);
|
|
1765
|
+
const wasRaw = Boolean(input.isRaw);
|
|
1766
|
+
let selectedIndex = clamp(initialIndex, 0, Math.max(options.length - 1, 0));
|
|
1767
|
+
let lineCount = 0;
|
|
1768
|
+
const cleanup = () => {
|
|
1769
|
+
input.off("keypress", handler);
|
|
1770
|
+
if (typeof input.setRawMode === "function") input.setRawMode(wasRaw);
|
|
1771
|
+
input.pause();
|
|
1772
|
+
output.write(ansi.cursorShow);
|
|
1773
|
+
};
|
|
1774
|
+
const redraw = () => {
|
|
1775
|
+
lineCount = renderSelector({ title, subtitle, options, selectedIndex, lineCount });
|
|
1776
|
+
};
|
|
1777
|
+
const finish = (value) => {
|
|
1778
|
+
if (lineCount > 0) clearSelector(lineCount);
|
|
1779
|
+
cleanup();
|
|
1780
|
+
resolve(value);
|
|
1781
|
+
};
|
|
1782
|
+
const handler = (_str = "", key = {}) => {
|
|
1783
|
+
if (key.ctrl && key.name === "c") {
|
|
1784
|
+
finish(null);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
if (key.name === "escape") {
|
|
1788
|
+
finish(null);
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
if (key.name === "up" || key.name === "left") {
|
|
1792
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
1793
|
+
redraw();
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (key.name === "down" || key.name === "right" || key.name === "tab") {
|
|
1797
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
1798
|
+
redraw();
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (key.name === "return" || key.name === "enter" || key.sequence === "\r") {
|
|
1802
|
+
finish(options[selectedIndex]);
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
input.resume();
|
|
1806
|
+
input.setRawMode(true);
|
|
1807
|
+
input.on("keypress", handler);
|
|
1808
|
+
output.write(ansi.cursorHide);
|
|
1809
|
+
redraw();
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
async function pickModelRole(role, state) {
|
|
1814
|
+
const options = modelRoleChoices(role);
|
|
1815
|
+
const currentProvider = role === "route" ? state.routeProvider || "deepseek" : state.mainProvider || state.provider || "deepseek";
|
|
1816
|
+
const currentModel = role === "route" ? state.routeModel || "deepseek-v4-flash" : state.mainModel || "deepseek-v4-pro";
|
|
1817
|
+
const initialIndex = Math.max(
|
|
1818
|
+
options.findIndex((item) => item.provider === currentProvider && item.model === currentModel),
|
|
1819
|
+
0
|
|
1820
|
+
);
|
|
1821
|
+
const selected = await selectModelChoice({
|
|
1822
|
+
title: role === "route" ? "Select route model" : "Select main model",
|
|
1823
|
+
subtitle: "Up/Down/Left/Right selects, Enter confirms, Esc cancels.",
|
|
1824
|
+
options,
|
|
1825
|
+
initialIndex,
|
|
1826
|
+
});
|
|
1827
|
+
if (!selected) return false;
|
|
1828
|
+
|
|
1829
|
+
state.routingMode = "smart";
|
|
1830
|
+
state.provider = "deepseek";
|
|
1831
|
+
state.model = "";
|
|
1832
|
+
if (role === "route") {
|
|
1833
|
+
state.routeProvider = selected.provider;
|
|
1834
|
+
state.routeModel = selected.model;
|
|
1835
|
+
printSystemLine(`route=${state.routeProvider}/${state.routeModel}`);
|
|
1836
|
+
} else {
|
|
1837
|
+
state.mainProvider = selected.provider;
|
|
1838
|
+
state.mainModel = selected.model;
|
|
1839
|
+
printSystemLine(`main=${state.mainProvider}/${state.mainModel}`);
|
|
1840
|
+
}
|
|
1841
|
+
return true;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1605
1844
|
async function maybeOnboardDeepSeekKey(state) {
|
|
1606
1845
|
if (!shouldPromptForDeepSeek(state, process.cwd())) return;
|
|
1607
1846
|
|
|
@@ -1695,7 +1934,7 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1695
1934
|
);
|
|
1696
1935
|
return true;
|
|
1697
1936
|
}
|
|
1698
|
-
if (command === "
|
|
1937
|
+
if (command === "auxiliary") {
|
|
1699
1938
|
const action = value || "status";
|
|
1700
1939
|
if (action === "status") {
|
|
1701
1940
|
const keys = providerKeyStatus(process.cwd());
|
|
@@ -1705,7 +1944,7 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1705
1944
|
`Image generation: ${keys.grsai ? "GRSAI key available" : "missing GRSAI key"}`,
|
|
1706
1945
|
`Venice image: ${keys.venice ? "Venice key available" : "missing Venice key"}`,
|
|
1707
1946
|
`Selected auxiliary: ${state.auxiliaryProvider || "grsai"}/${state.auxiliaryModel || "nano-banana-2"}`,
|
|
1708
|
-
"Use `/
|
|
1947
|
+
"Use `/auxiliary grsai` or `/auxiliary venice` to paste a key, `/auxiliary model grsai/nano-banana-2`, `/auxiliary image`, or `/auxiliary off`.",
|
|
1709
1948
|
].join("\n")
|
|
1710
1949
|
);
|
|
1711
1950
|
return true;
|
|
@@ -1737,7 +1976,7 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1737
1976
|
await promptAndSaveProviderKey(action === "venice" ? "venice" : "grsai", state);
|
|
1738
1977
|
return true;
|
|
1739
1978
|
}
|
|
1740
|
-
printAgentMessage("Usage: /
|
|
1979
|
+
printAgentMessage("Usage: /auxiliary [status|grsai|venice|model grsai/nano-banana-2|on|off|image]");
|
|
1741
1980
|
return true;
|
|
1742
1981
|
}
|
|
1743
1982
|
if (command === "new") {
|
|
@@ -1844,6 +2083,11 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1844
2083
|
}
|
|
1845
2084
|
if (command === "route") {
|
|
1846
2085
|
if (!value) {
|
|
2086
|
+
if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
|
|
2087
|
+
const changed = await pickModelRole("route", state);
|
|
2088
|
+
if (!changed) printSystemLine(`route=${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"}`);
|
|
2089
|
+
return true;
|
|
2090
|
+
}
|
|
1847
2091
|
printAgentMessage(
|
|
1848
2092
|
`Route model: ${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"}\nUse /route deepseek/deepseek-v4-flash or /route fast|smart|complex.`
|
|
1849
2093
|
);
|
|
@@ -1861,7 +2105,18 @@ async function handleCommand(line, state, packageDir) {
|
|
|
1861
2105
|
return true;
|
|
1862
2106
|
}
|
|
1863
2107
|
if (command === "main" || command === "model") {
|
|
1864
|
-
if (!value
|
|
2108
|
+
if (!value) {
|
|
2109
|
+
if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
|
|
2110
|
+
const changed = await pickModelRole("main", state);
|
|
2111
|
+
if (!changed) printSystemLine(`main=${state.mainProvider || state.provider || "deepseek"}/${state.mainModel || state.model || "deepseek-v4-pro"}`);
|
|
2112
|
+
return true;
|
|
2113
|
+
}
|
|
2114
|
+
printAgentMessage(
|
|
2115
|
+
`Main model: ${state.mainProvider || state.provider || "deepseek"}/${state.mainModel || state.model || "deepseek-v4-pro"}\nUse /model deepseek/deepseek-v4-pro or /model auto.`
|
|
2116
|
+
);
|
|
2117
|
+
return true;
|
|
2118
|
+
}
|
|
2119
|
+
if (value === "auto") {
|
|
1865
2120
|
if (command === "main") {
|
|
1866
2121
|
state.mainProvider = "";
|
|
1867
2122
|
state.mainModel = "";
|
package/src/model-client.js
CHANGED
|
@@ -241,7 +241,7 @@ export async function createPlan(client, config, state) {
|
|
|
241
241
|
config.allowAuxiliaryTools
|
|
242
242
|
? `Auxiliary skills are enabled: ${listAuxiliarySkills()
|
|
243
243
|
.map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
|
|
244
|
-
.join(", ")}. For raster image generation requests, plan to use generate_image when a GRSAI or Venice image key is available; otherwise ask the user to run /
|
|
244
|
+
.join(", ")}. For raster image generation requests, plan to use generate_image when a GRSAI or Venice image key is available; otherwise ask the user to run /auxiliary grsai, aginti login grsai, or aginti login venice.`
|
|
245
245
|
: "Auxiliary skills are disabled for this run.",
|
|
246
246
|
config.allowWebSearch
|
|
247
247
|
? "web_search is available for current information, docs, install errors, package/toolchain questions, and source discovery. Prefer web_search over opening a search engine in the browser."
|
|
@@ -703,7 +703,7 @@ export async function requestNextStep(client, config, messages) {
|
|
|
703
703
|
function: {
|
|
704
704
|
name: "generate_image",
|
|
705
705
|
description:
|
|
706
|
-
"Generate a raster image artifact through optional GRS AI Nano Banana or Venice image-generation skills. Use for user requests that explicitly need a real image/photo/illustration/cover/poster/logo concept rather than SVG/code-native graphics. Saves prompt, redacted payload, manifest, and downloaded images in the workspace. If keys are missing, ask the user to run /
|
|
706
|
+
"Generate a raster image artifact through optional GRS AI Nano Banana or Venice image-generation skills. Use for user requests that explicitly need a real image/photo/illustration/cover/poster/logo concept rather than SVG/code-native graphics. Saves prompt, redacted payload, manifest, and downloaded images in the workspace. If keys are missing, ask the user to run /auxiliary grsai, aginti login grsai, or aginti login venice.",
|
|
707
707
|
parameters: {
|
|
708
708
|
type: "object",
|
|
709
709
|
properties: {
|
package/src/model-routing.js
CHANGED
|
@@ -519,7 +519,7 @@ export function getModelRoleDefaults(overrides = {}) {
|
|
|
519
519
|
auxiliary: {
|
|
520
520
|
id: "auxiliary",
|
|
521
521
|
label: "Auxiliary",
|
|
522
|
-
command: "/
|
|
522
|
+
command: "/auxiliary",
|
|
523
523
|
provider: auxiliaryProvider,
|
|
524
524
|
model: auxiliaryModel,
|
|
525
525
|
description: "Image/media tools. Default: GRS AI/Nano Banana; Venice image is optional.",
|
package/src/project.js
CHANGED
|
@@ -306,7 +306,6 @@ export function providerKeyPreview(projectRoot = process.cwd(), provider = "") {
|
|
|
306
306
|
const normalized = String(provider || "").trim().toLowerCase();
|
|
307
307
|
const aliases = {
|
|
308
308
|
auxiliary: "grsai",
|
|
309
|
-
auxilliary: "grsai",
|
|
310
309
|
image: "grsai",
|
|
311
310
|
imagegen: "grsai",
|
|
312
311
|
v: "venice",
|
|
@@ -339,7 +338,6 @@ export async function setProviderKey(projectRoot, provider, value) {
|
|
|
339
338
|
const normalizedProvider = String(provider || "").toLowerCase();
|
|
340
339
|
const aliases = {
|
|
341
340
|
auxiliary: "grsai",
|
|
342
|
-
auxilliary: "grsai",
|
|
343
341
|
image: "grsai",
|
|
344
342
|
imagegen: "grsai",
|
|
345
343
|
v: "venice",
|
package/src/task-profiles.js
CHANGED
|
@@ -66,7 +66,7 @@ export const TASK_PROFILES = {
|
|
|
66
66
|
id: "image",
|
|
67
67
|
label: "Image generation",
|
|
68
68
|
prompt:
|
|
69
|
-
"For raster image, cover, poster, illustration, photo, and logo-concept tasks, write a clear visual prompt, use generate_image when the optional GRS AI or Venice image key is available, save outputs under artifacts/images, and send the selected image to the canvas. If keys are missing, ask the user to run /
|
|
69
|
+
"For raster image, cover, poster, illustration, photo, and logo-concept tasks, write a clear visual prompt, use generate_image when the optional GRS AI or Venice image key is available, save outputs under artifacts/images, and send the selected image to the canvas. If keys are missing, ask the user to run /auxiliary grsai, aginti login grsai, or aginti login venice.",
|
|
70
70
|
tools: ["auxiliary:image_generation", "files", "canvas"],
|
|
71
71
|
},
|
|
72
72
|
aaps: {
|