@lazyingart/agintiflow 0.20.4 → 0.20.6
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 +7 -4
- package/package.json +1 -1
- package/references/model-routing-provider-design.md +2 -2
- package/references/venice-model-reference.md +5 -2
- package/scripts/smoke-model-roles.js +17 -1
- package/src/agent-runner.js +30 -1
- package/src/interactive-cli.js +154 -24
- package/src/model-client.js +155 -19
- package/src/model-routing.js +9 -2
package/docs/model-selection.md
CHANGED
|
@@ -48,14 +48,17 @@ Interactive commands:
|
|
|
48
48
|
|
|
49
49
|
In the interactive CLI, `/provider`, `/route`, `/model`, `/spare`, and `/auxiliary model` without arguments open selectors. 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.
|
|
50
50
|
|
|
51
|
-
`/venice`
|
|
51
|
+
`/venice` opens a two-step selector for the Venice route and main models. The current text choices are:
|
|
52
52
|
|
|
53
53
|
```text
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
venice/venice-uncensored-1-2
|
|
55
|
+
venice/venice-uncensored
|
|
56
|
+
venice/gemma-4-uncensored
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
For scripts or non-interactive terminals, `/venice` uses Venice 1.2 for both roles. You can also set both roles directly with `/venice 1.2`, `/venice 1.1`, or `/venice gemma`. Use two values to set route and main separately, for example `/venice 1.2 gemma`.
|
|
60
|
+
|
|
61
|
+
Use `/venice off` to switch back to the DeepSeek defaults:
|
|
59
62
|
|
|
60
63
|
```text
|
|
61
64
|
/route deepseek/deepseek-v4-flash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.6",
|
|
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",
|
|
@@ -53,7 +53,7 @@ Interactive equivalents:
|
|
|
53
53
|
/auxiliary model grsai/nano-banana-2
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
`/venice`
|
|
56
|
+
`/venice` opens a route/main selector for Venice text models. The selector includes `venice/venice-uncensored-1-2` (Venice 1.2), `venice/venice-uncensored` (Venice 1.1), and `venice/gemma-4-uncensored` (Gemma 4). In non-interactive shells, `/venice` keeps script compatibility by selecting `venice/venice-uncensored-1-2` for both roles. `/venice 1.2 gemma` sets route to Venice 1.2 and main to Gemma 4; `/venice off` restores `deepseek/deepseek-v4-flash` for route and `deepseek/deepseek-v4-pro` for main.
|
|
57
57
|
|
|
58
58
|
The web UI should expose model names as dropdowns, not free-text fields. The left panel should stay focused on common daily controls, while model-role editing and less-used switches live in an Advanced settings modal. The terminal-like capability panels belong after the runtime log so the left control panel remains short.
|
|
59
59
|
|
|
@@ -74,7 +74,7 @@ Codex wrapper defaults stay separate from native OpenAI API settings: GPT-5.5 me
|
|
|
74
74
|
|
|
75
75
|
| UI bucket | Concrete default | Notes |
|
|
76
76
|
|---|---|---|
|
|
77
|
-
| `venice-uncensored` | `venice-uncensored-1-2` | Venice-native text |
|
|
77
|
+
| `venice-uncensored` | `venice-uncensored-1-2` | Venice-native text; `/venice` also exposes `venice-uncensored` as Venice 1.1 |
|
|
78
78
|
| `venice-qwen` | `qwen3-6-27b` | Qwen-family text/code |
|
|
79
79
|
| `venice-gpt` | `openai-gpt-55` | OpenAI-family through Venice |
|
|
80
80
|
| `venice-claude` | `claude-sonnet-4-6` | Claude-family through Venice |
|
|
@@ -19,7 +19,7 @@ Store `VENICE_API_KEY` only in ignored local files such as `.aginti/.env` or a s
|
|
|
19
19
|
|
|
20
20
|
| Bucket | Primary model | Other useful models | Use |
|
|
21
21
|
|---|---|---|---|
|
|
22
|
-
| `venice-uncensored` | `venice-uncensored-1-2` | `venice-uncensored`, `venice-uncensored-role-play` | Private/uncensored Venice-native text route |
|
|
22
|
+
| `venice-uncensored` | `venice-uncensored-1-2` | `e2ee-venice-uncensored-24b-p`, `venice-uncensored-role-play`, `venice-uncensored` legacy | Private/uncensored Venice-native text route |
|
|
23
23
|
| `venice-gemma` | `gemma-4-uncensored` | `google-gemma-4-31b-it`, `google-gemma-4-26b-a4b-it` | Low-cost open-model writing and experimentation |
|
|
24
24
|
| `venice-qwen` | `qwen3-6-27b` | `qwen-3-6-plus`, `qwen3-coder-480b-a35b-instruct-turbo`, `qwen3-vl-235b-a22b` | Chinese/open coding and general tasks |
|
|
25
25
|
| `venice-gpt` | `openai-gpt-55` | `openai-gpt-54`, `openai-gpt-54-mini`, `openai-gpt-53-codex`, `openai-gpt-53-codex-spark` | OpenAI-family routes through Venice |
|
|
@@ -44,12 +44,15 @@ Store `VENICE_API_KEY` only in ignored local files such as `.aginti/.env` or a s
|
|
|
44
44
|
| `google-gemma-4-26b-a4b-it` | 256K | Google Gemma 4 26B A4B Instruct |
|
|
45
45
|
| `venice-uncensored-1-2` | 128K | Venice Uncensored 1.2 |
|
|
46
46
|
| `openai-gpt-54-mini` | 400K | GPT-5.4 Mini |
|
|
47
|
+
| `e2ee-venice-uncensored-24b-p` | 32K | Venice Uncensored 1.1 E2EE |
|
|
47
48
|
| `venice-uncensored-role-play` | 128K | Venice Role Play Uncensored |
|
|
48
49
|
| `gemini-3-1-pro-preview` | 1.0M | Gemini 3.1 Pro Preview |
|
|
49
50
|
| `claude-sonnet-4-6` | 1.0M | Claude Sonnet 4.6 |
|
|
50
51
|
| `openai-gpt-53-codex` | 400K | GPT-5.3 Codex |
|
|
51
52
|
| `openai-gpt-52` | 256K | GPT-5.2 |
|
|
52
|
-
| `venice-uncensored` | 32K | Venice Uncensored legacy |
|
|
53
|
+
| `venice-uncensored` | 32K | Venice Uncensored legacy / deprecated |
|
|
54
|
+
|
|
55
|
+
AgInTiFlow exposes the primary Venice text choices through `/venice`: Venice 1.2 (`venice-uncensored-1-2`), Venice 1.1 (`venice-uncensored`), and Gemma 4 (`gemma-4-uncensored`). The command can select route and main independently, so a fast Venice route can be paired with a larger Gemma main model when useful. The E2EE 1.1 ID (`e2ee-venice-uncensored-24b-p`) is documented for reference, but the selector uses the working non-E2EE 1.1 route because the E2EE route currently returns an upstream provider error in live tests.
|
|
53
56
|
|
|
54
57
|
## Image And Edit Models
|
|
55
58
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
modelsForProviderGroup,
|
|
10
10
|
selectModelRoute,
|
|
11
11
|
} from "../src/model-routing.js";
|
|
12
|
-
import { parseTextToolCalls } from "../src/model-client.js";
|
|
12
|
+
import { parseTextToolCalls, usesTextToolProtocol } from "../src/model-client.js";
|
|
13
13
|
|
|
14
14
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
15
|
|
|
@@ -109,11 +109,24 @@ assert(fastRoute.model === "deepseek-v4-flash", "fast route did not use route mo
|
|
|
109
109
|
|
|
110
110
|
assert(MODEL_PROVIDER_GROUPS["venice-gpt"].provider === "venice", "venice-gpt group missing");
|
|
111
111
|
assert(modelsForProviderGroup("venice-gemma").some((item) => item.id === "gemma-4-uncensored"), "venice-gemma bucket missing Gemma");
|
|
112
|
+
assert(modelsForProviderGroup("venice-uncensored").some((item) => item.id === "e2ee-venice-uncensored-24b-p"), "venice-uncensored bucket missing Venice 1.1");
|
|
112
113
|
assert(AUXILIARY_MODEL_CATALOG["venice-image"].some((item) => item.id === "gpt-image-2"), "Venice image catalog missing GPT Image 2");
|
|
113
114
|
const parsedTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]call_123[ARGS]{"path":".","maxDepth":1}');
|
|
114
115
|
assert(parsedTextToolCalls.length === 1, "Venice text tool-call parser did not detect encoded tool call");
|
|
115
116
|
assert(parsedTextToolCalls[0].function.name === "list_files", "Venice text tool-call parser returned wrong tool name");
|
|
116
117
|
assert(parsedTextToolCalls[0].function.arguments.includes('"maxDepth":1'), "Venice text tool-call parser returned wrong arguments");
|
|
118
|
+
const looseTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]{"path":"."}[TOOL_CALLS]inspect_project[ARGS]{"path":"."}');
|
|
119
|
+
assert(looseTextToolCalls.length === 2, "Venice loose text tool-call parser did not detect multiple calls");
|
|
120
|
+
assert(looseTextToolCalls[1].function.name === "inspect_project", "Venice loose text tool-call parser returned wrong second tool");
|
|
121
|
+
const nativeMarkerText = parseTextToolCalls('Done. <|tool_call>call:finish{result:<|"|>Done<|"|>}');
|
|
122
|
+
assert(nativeMarkerText.length === 0, "native marker text should not be treated as JSON text tool call");
|
|
123
|
+
const jsonBlockToolCalls = parseTextToolCalls('TOOL_CALLS:\n```json\n[{"name":"list_files","arguments":{"path":"/workspace"}}]\n```');
|
|
124
|
+
assert(jsonBlockToolCalls.length === 1, "Venice JSON text tool-call parser did not detect JSON block calls");
|
|
125
|
+
assert(jsonBlockToolCalls[0].function.arguments.includes("/workspace"), "Venice JSON text tool-call parser returned wrong arguments");
|
|
126
|
+
assert(usesTextToolProtocol({ provider: "venice", model: "gemma-4-uncensored" }), "Venice Gemma should use text tool protocol");
|
|
127
|
+
assert(usesTextToolProtocol({ provider: "venice", model: "e2ee-venice-uncensored-24b-p" }), "Venice 1.1 should use text tool protocol");
|
|
128
|
+
assert(usesTextToolProtocol({ provider: "venice", model: "venice-uncensored" }), "Venice legacy 1.1 should use text tool protocol");
|
|
129
|
+
assert(!usesTextToolProtocol({ provider: "venice", model: "venice-uncensored-1-2" }), "Venice 1.2 should keep native tool calls first");
|
|
117
130
|
|
|
118
131
|
const output = await runCli(["models"]);
|
|
119
132
|
assert(output.includes("/route") && output.includes("/spare") && output.includes("venice-gpt"), "aginti models output missing role details");
|
|
@@ -122,6 +135,9 @@ const interactiveOutput = await runInteractive("/venice\n");
|
|
|
122
135
|
assert(interactiveOutput.includes("venice=on"), "/venice did not enable Venice roles");
|
|
123
136
|
assert(interactiveOutput.includes("route=venice/venice-uncensored-1-2"), "/venice did not set Venice route role");
|
|
124
137
|
assert(interactiveOutput.includes("main=venice/venice-uncensored-1-2"), "/venice did not set Venice main role");
|
|
138
|
+
const interactiveGemmaOutput = await runInteractive("/venice 1.1 gemma\n");
|
|
139
|
+
assert(interactiveGemmaOutput.includes("route=venice/venice-uncensored"), "/venice 1.1 did not set Venice 1.1 route role");
|
|
140
|
+
assert(interactiveGemmaOutput.includes("main=venice/gemma-4-uncensored"), "/venice gemma did not set Gemma 4 main role");
|
|
125
141
|
const interactiveOffOutput = await runInteractive("/venice off\n");
|
|
126
142
|
assert(interactiveOffOutput.includes("venice=off"), "/venice off did not restore DeepSeek roles");
|
|
127
143
|
assert(interactiveOffOutput.includes("route=deepseek/deepseek-v4-flash"), "/venice off did not restore DeepSeek route role");
|
package/src/agent-runner.js
CHANGED
|
@@ -739,7 +739,7 @@ async function buildSnapshot(browserState, store, step, config) {
|
|
|
739
739
|
|
|
740
740
|
async function injectQueuedUserMessages(store, state, observers) {
|
|
741
741
|
const inbox = await store.drainInbox();
|
|
742
|
-
if (inbox.length === 0) return;
|
|
742
|
+
if (inbox.length === 0) return 0;
|
|
743
743
|
|
|
744
744
|
for (const item of inbox) {
|
|
745
745
|
const content = String(item.content || "").trim();
|
|
@@ -762,6 +762,7 @@ async function injectQueuedUserMessages(store, state, observers) {
|
|
|
762
762
|
priority: item.priority || "normal",
|
|
763
763
|
});
|
|
764
764
|
}
|
|
765
|
+
return inbox.length;
|
|
765
766
|
}
|
|
766
767
|
|
|
767
768
|
async function executeTool(browserState, toolCall, snapshot, config, store, observers, state) {
|
|
@@ -1374,6 +1375,16 @@ export async function runAgent(config) {
|
|
|
1374
1375
|
});
|
|
1375
1376
|
|
|
1376
1377
|
throwIfAborted(config);
|
|
1378
|
+
await store.appendEvent("model.requested", {
|
|
1379
|
+
step,
|
|
1380
|
+
provider: config.provider,
|
|
1381
|
+
model: config.model,
|
|
1382
|
+
});
|
|
1383
|
+
observers.event("model.requested", {
|
|
1384
|
+
step,
|
|
1385
|
+
provider: config.provider,
|
|
1386
|
+
model: config.model,
|
|
1387
|
+
});
|
|
1377
1388
|
const response = await requestNextStep(client, config, state.messages);
|
|
1378
1389
|
const assistantMessage = response.choices[0]?.message;
|
|
1379
1390
|
if (!assistantMessage) {
|
|
@@ -1399,6 +1410,13 @@ export async function runAgent(config) {
|
|
|
1399
1410
|
const toolCalls = assistantMessage.tool_calls || [];
|
|
1400
1411
|
|
|
1401
1412
|
if (toolCalls.length === 0) {
|
|
1413
|
+
const queuedCount = await injectQueuedUserMessages(store, state, observers);
|
|
1414
|
+
if (queuedCount > 0) {
|
|
1415
|
+
state.stepsCompleted = step;
|
|
1416
|
+
state.updatedAt = new Date().toISOString();
|
|
1417
|
+
await store.saveState(state);
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1402
1420
|
const fallback = assistantMessage.content?.trim() || "No tool call returned.";
|
|
1403
1421
|
appendChatEntry(state, "assistant", fallback);
|
|
1404
1422
|
await store.appendEvent("session.finished", {
|
|
@@ -1419,6 +1437,7 @@ export async function runAgent(config) {
|
|
|
1419
1437
|
};
|
|
1420
1438
|
}
|
|
1421
1439
|
|
|
1440
|
+
let continueForQueuedInput = false;
|
|
1422
1441
|
for (const toolCall of toolCalls) {
|
|
1423
1442
|
throwIfAborted(config);
|
|
1424
1443
|
const toolResult = await executeTool(browserState, toolCall, snapshot, config, store, observers, state);
|
|
@@ -1462,6 +1481,14 @@ export async function runAgent(config) {
|
|
|
1462
1481
|
}
|
|
1463
1482
|
|
|
1464
1483
|
if (toolResult.done) {
|
|
1484
|
+
const queuedCount = await injectQueuedUserMessages(store, state, observers);
|
|
1485
|
+
if (queuedCount > 0) {
|
|
1486
|
+
state.stepsCompleted = step;
|
|
1487
|
+
state.updatedAt = new Date().toISOString();
|
|
1488
|
+
await store.saveState(state);
|
|
1489
|
+
continueForQueuedInput = true;
|
|
1490
|
+
break;
|
|
1491
|
+
}
|
|
1465
1492
|
state.stepsCompleted = step;
|
|
1466
1493
|
state.updatedAt = new Date().toISOString();
|
|
1467
1494
|
state.meta.lastUrl = browserState.page?.url() || state.meta.lastUrl;
|
|
@@ -1487,6 +1514,8 @@ export async function runAgent(config) {
|
|
|
1487
1514
|
}
|
|
1488
1515
|
}
|
|
1489
1516
|
|
|
1517
|
+
if (continueForQueuedInput) continue;
|
|
1518
|
+
|
|
1490
1519
|
await injectQueuedUserMessages(store, state, observers);
|
|
1491
1520
|
|
|
1492
1521
|
state.stepsCompleted = step;
|
package/src/interactive-cli.js
CHANGED
|
@@ -593,7 +593,7 @@ function printHelp() {
|
|
|
593
593
|
" /instructions Show AGINTI.md project instructions status.",
|
|
594
594
|
" /memory Alias for /instructions.",
|
|
595
595
|
" /models Show route/main/spare/wrapper/auxiliary model roles.",
|
|
596
|
-
" /venice [
|
|
596
|
+
" /venice [off|model] Pick Venice route/main models, or restore DeepSeek defaults.",
|
|
597
597
|
" /route [mode|provider/model]",
|
|
598
598
|
" Open route selector, or set routing/fast route model.",
|
|
599
599
|
" /model [provider/model] Open main-model selector, or set the active/main model.",
|
|
@@ -1640,23 +1640,61 @@ function useDeepSeekDefaults(state) {
|
|
|
1640
1640
|
state.mainModel = "deepseek-v4-pro";
|
|
1641
1641
|
}
|
|
1642
1642
|
|
|
1643
|
-
function
|
|
1643
|
+
function veniceTextModelChoices() {
|
|
1644
|
+
return [
|
|
1645
|
+
{
|
|
1646
|
+
provider: "venice",
|
|
1647
|
+
model: "venice-uncensored-1-2",
|
|
1648
|
+
label: "Venice 1.2",
|
|
1649
|
+
description: "default Venice text model; 128K context",
|
|
1650
|
+
},
|
|
1651
|
+
{
|
|
1652
|
+
provider: "venice",
|
|
1653
|
+
model: "venice-uncensored",
|
|
1654
|
+
label: "Venice 1.1",
|
|
1655
|
+
description: "legacy Venice text model; 32K context",
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
provider: "venice",
|
|
1659
|
+
model: "gemma-4-uncensored",
|
|
1660
|
+
label: "Gemma 4",
|
|
1661
|
+
description: "Gemma-family Venice text model; 256K context",
|
|
1662
|
+
},
|
|
1663
|
+
];
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function resolveVeniceTextModel(value = "") {
|
|
1667
|
+
const normalized = String(value).trim().toLowerCase();
|
|
1668
|
+
if (!normalized || normalized === "on" || normalized === "venice" || normalized === "1.2" || normalized === "venice-1.2") {
|
|
1669
|
+
return "venice-uncensored-1-2";
|
|
1670
|
+
}
|
|
1671
|
+
if (
|
|
1672
|
+
normalized === "1.1" ||
|
|
1673
|
+
normalized === "venice-1.1"
|
|
1674
|
+
) {
|
|
1675
|
+
return "venice-uncensored";
|
|
1676
|
+
}
|
|
1677
|
+
if (normalized === "legacy" || normalized === "venice-uncensored") {
|
|
1678
|
+
return "venice-uncensored";
|
|
1679
|
+
}
|
|
1680
|
+
if (normalized === "e2ee" || normalized === "e2ee-venice-uncensored-24b-p") {
|
|
1681
|
+
return "e2ee-venice-uncensored-24b-p";
|
|
1682
|
+
}
|
|
1683
|
+
if (normalized === "gemma" || normalized === "gemma4" || normalized === "gemma-4" || normalized === "gemma-4-uncensored") {
|
|
1684
|
+
return "gemma-4-uncensored";
|
|
1685
|
+
}
|
|
1686
|
+
const exact = veniceTextModelChoices().find((item) => item.model.toLowerCase() === normalized);
|
|
1687
|
+
return exact?.model || "";
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
function useVeniceModels(state, routeModel = "venice-uncensored-1-2", mainModel = routeModel) {
|
|
1644
1691
|
state.routingMode = "smart";
|
|
1645
1692
|
state.provider = "deepseek";
|
|
1646
1693
|
state.model = "";
|
|
1647
1694
|
state.routeProvider = "venice";
|
|
1648
|
-
state.routeModel =
|
|
1695
|
+
state.routeModel = routeModel;
|
|
1649
1696
|
state.mainProvider = "venice";
|
|
1650
|
-
state.mainModel =
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
function isVeniceDefaultActive(state) {
|
|
1654
|
-
return (
|
|
1655
|
-
state.routeProvider === "venice" &&
|
|
1656
|
-
state.routeModel === "venice-uncensored-1-2" &&
|
|
1657
|
-
state.mainProvider === "venice" &&
|
|
1658
|
-
state.mainModel === "venice-uncensored-1-2"
|
|
1659
|
-
);
|
|
1697
|
+
state.mainModel = mainModel;
|
|
1660
1698
|
}
|
|
1661
1699
|
|
|
1662
1700
|
function printModelRoles(state) {
|
|
@@ -1737,12 +1775,30 @@ function modelRoleChoices(role = "main") {
|
|
|
1737
1775
|
{
|
|
1738
1776
|
provider: "venice",
|
|
1739
1777
|
model: "venice-uncensored-1-2",
|
|
1740
|
-
label: "Venice
|
|
1778
|
+
label: "Venice 1.2",
|
|
1741
1779
|
description: "Venice text route; use /auth venice if missing",
|
|
1742
1780
|
route: true,
|
|
1743
1781
|
main: true,
|
|
1744
1782
|
spare: true,
|
|
1745
1783
|
},
|
|
1784
|
+
{
|
|
1785
|
+
provider: "venice",
|
|
1786
|
+
model: "venice-uncensored",
|
|
1787
|
+
label: "Venice 1.1",
|
|
1788
|
+
description: "legacy Venice text route; use /auth venice if missing",
|
|
1789
|
+
route: true,
|
|
1790
|
+
main: true,
|
|
1791
|
+
spare: true,
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
provider: "venice",
|
|
1795
|
+
model: "gemma-4-uncensored",
|
|
1796
|
+
label: "Gemma 4",
|
|
1797
|
+
description: "Gemma-family Venice route; use /auth venice if missing",
|
|
1798
|
+
route: true,
|
|
1799
|
+
main: true,
|
|
1800
|
+
spare: true,
|
|
1801
|
+
},
|
|
1746
1802
|
{
|
|
1747
1803
|
provider: "openai",
|
|
1748
1804
|
model: "gpt-5.5",
|
|
@@ -1849,7 +1905,7 @@ function providerChoices() {
|
|
|
1849
1905
|
provider: "venice",
|
|
1850
1906
|
model: "venice-uncensored-1-2",
|
|
1851
1907
|
label: "Venice",
|
|
1852
|
-
description: "manual Venice route; /venice
|
|
1908
|
+
description: "manual Venice route; /venice picks route+main roles",
|
|
1853
1909
|
},
|
|
1854
1910
|
{
|
|
1855
1911
|
provider: "qwen",
|
|
@@ -1876,14 +1932,19 @@ function clearSelector(lineCount) {
|
|
|
1876
1932
|
}
|
|
1877
1933
|
}
|
|
1878
1934
|
|
|
1935
|
+
function clearSelectorSequence(lineCount) {
|
|
1936
|
+
return Array.from({ length: Math.max(lineCount, 0) }, () => "\x1b[1A\r\x1b[2K").join("");
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1879
1939
|
function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0 }) {
|
|
1880
|
-
if (lineCount > 0) clearSelector(lineCount);
|
|
1881
1940
|
const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
|
|
1882
1941
|
const bodyWidth = width - 4;
|
|
1942
|
+
const safeTitle = compactLine(title, bodyWidth);
|
|
1943
|
+
const safeSubtitle = compactLine(subtitle, bodyWidth);
|
|
1883
1944
|
const rows = [
|
|
1884
1945
|
`╭${"─".repeat(width - 2)}╮`,
|
|
1885
|
-
`│ ${padVisible(
|
|
1886
|
-
`│ ${padVisible(
|
|
1946
|
+
`│ ${padVisible(safeTitle, bodyWidth)} │`,
|
|
1947
|
+
`│ ${padVisible(safeSubtitle, bodyWidth)} │`,
|
|
1887
1948
|
`├${"─".repeat(width - 2)}┤`,
|
|
1888
1949
|
...options.map((option, index) => {
|
|
1889
1950
|
const marker = index === selectedIndex ? ">" : " ";
|
|
@@ -1892,7 +1953,7 @@ function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0
|
|
|
1892
1953
|
}),
|
|
1893
1954
|
`╰${"─".repeat(width - 2)}╯`,
|
|
1894
1955
|
];
|
|
1895
|
-
output.write(`${rows.join("\n")}\n`);
|
|
1956
|
+
output.write(`${lineCount > 0 ? clearSelectorSequence(lineCount) : ""}${rows.join("\n")}\n`);
|
|
1896
1957
|
return rows.length;
|
|
1897
1958
|
}
|
|
1898
1959
|
|
|
@@ -2012,6 +2073,38 @@ async function pickModelRole(role, state) {
|
|
|
2012
2073
|
return true;
|
|
2013
2074
|
}
|
|
2014
2075
|
|
|
2076
|
+
async function pickVeniceRouteAndMain(state) {
|
|
2077
|
+
const options = veniceTextModelChoices();
|
|
2078
|
+
const routeIndex = Math.max(
|
|
2079
|
+
options.findIndex((item) => item.provider === state.routeProvider && item.model === state.routeModel),
|
|
2080
|
+
0
|
|
2081
|
+
);
|
|
2082
|
+
const route = await selectModelChoice({
|
|
2083
|
+
title: "Select Venice route model",
|
|
2084
|
+
subtitle: "Route handles planning and short turns. Up/Down selects, Enter confirms, Esc cancels.",
|
|
2085
|
+
options,
|
|
2086
|
+
initialIndex: routeIndex,
|
|
2087
|
+
});
|
|
2088
|
+
if (!route) return false;
|
|
2089
|
+
|
|
2090
|
+
const mainIndex = Math.max(
|
|
2091
|
+
options.findIndex((item) => item.provider === state.mainProvider && item.model === state.mainModel),
|
|
2092
|
+
options.findIndex((item) => item.model === route.model),
|
|
2093
|
+
0
|
|
2094
|
+
);
|
|
2095
|
+
const main = await selectModelChoice({
|
|
2096
|
+
title: "Select Venice main model",
|
|
2097
|
+
subtitle: "Main handles complex work. Up/Down selects, Enter confirms, Esc cancels.",
|
|
2098
|
+
options,
|
|
2099
|
+
initialIndex: mainIndex,
|
|
2100
|
+
});
|
|
2101
|
+
if (!main) return false;
|
|
2102
|
+
|
|
2103
|
+
useVeniceModels(state, route.model, main.model);
|
|
2104
|
+
printSystemLine(`venice=on routing=smart route=venice/${state.routeModel} main=venice/${state.mainModel}`);
|
|
2105
|
+
return true;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2015
2108
|
async function pickProvider(state) {
|
|
2016
2109
|
const options = providerChoices();
|
|
2017
2110
|
const currentProvider = state.provider || "deepseek";
|
|
@@ -2266,19 +2359,45 @@ async function handleCommand(line, state, packageDir) {
|
|
|
2266
2359
|
return true;
|
|
2267
2360
|
}
|
|
2268
2361
|
if (command === "venice") {
|
|
2269
|
-
const
|
|
2362
|
+
const canUseSelector = input.isTTY && output.isTTY && typeof input.setRawMode === "function";
|
|
2363
|
+
const action = value || (canUseSelector ? "select" : "on");
|
|
2270
2364
|
if (action === "off" || action === "deepseek" || action === "default") {
|
|
2271
2365
|
useDeepSeekDefaults(state);
|
|
2272
2366
|
printSystemLine("venice=off routing=smart route=deepseek/deepseek-v4-flash main=deepseek/deepseek-v4-pro");
|
|
2273
2367
|
return true;
|
|
2274
2368
|
}
|
|
2275
|
-
if (action
|
|
2276
|
-
|
|
2369
|
+
if (action === "select" && canUseSelector) {
|
|
2370
|
+
const changed = await pickVeniceRouteAndMain(state);
|
|
2371
|
+
if (!changed) {
|
|
2372
|
+
printSystemLine(`route=${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"} main=${state.mainProvider || "deepseek"}/${state.mainModel || "deepseek-v4-pro"}`);
|
|
2373
|
+
return true;
|
|
2374
|
+
}
|
|
2375
|
+
const keys = providerKeyStatus(process.cwd());
|
|
2376
|
+
if (!keys.venice) {
|
|
2377
|
+
printAgentMessage("Venice model roles are selected, but no Venice key is configured. Run `/auth venice` to save one.");
|
|
2378
|
+
}
|
|
2277
2379
|
return true;
|
|
2278
2380
|
}
|
|
2279
|
-
|
|
2381
|
+
|
|
2382
|
+
const parts = action.split(/\s+/).filter(Boolean);
|
|
2383
|
+
const routeModel = resolveVeniceTextModel(parts[0] || "on");
|
|
2384
|
+
const mainModel = resolveVeniceTextModel(parts.slice(1).join(" ") || parts[0] || "on");
|
|
2385
|
+
if (!routeModel || !mainModel) {
|
|
2386
|
+
printAgentMessage(
|
|
2387
|
+
[
|
|
2388
|
+
"Usage: /venice [off|1.2|1.1|gemma] [main-model]",
|
|
2389
|
+
"Examples:",
|
|
2390
|
+
" /venice",
|
|
2391
|
+
" /venice gemma",
|
|
2392
|
+
" /venice 1.2 gemma",
|
|
2393
|
+
" /venice off",
|
|
2394
|
+
].join("\n")
|
|
2395
|
+
);
|
|
2396
|
+
return true;
|
|
2397
|
+
}
|
|
2398
|
+
useVeniceModels(state, routeModel, mainModel);
|
|
2280
2399
|
const keys = providerKeyStatus(process.cwd());
|
|
2281
|
-
printSystemLine(
|
|
2400
|
+
printSystemLine(`venice=on routing=smart route=venice/${state.routeModel} main=venice/${state.mainModel}`);
|
|
2282
2401
|
if (!keys.venice) {
|
|
2283
2402
|
printAgentMessage("Venice model roles are selected, but no Venice key is configured. Run `/auth venice` to save one.");
|
|
2284
2403
|
}
|
|
@@ -2525,6 +2644,7 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
2525
2644
|
printSystemLine(`status=running workingOn=${state.activeGoal}`);
|
|
2526
2645
|
}
|
|
2527
2646
|
let result;
|
|
2647
|
+
let runError = null;
|
|
2528
2648
|
let queuedAfterFinish = [];
|
|
2529
2649
|
try {
|
|
2530
2650
|
result = await runAgent({
|
|
@@ -2552,6 +2672,8 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
2552
2672
|
onEvent: (type, data = {}) => {
|
|
2553
2673
|
if (type === "plan.created") {
|
|
2554
2674
|
printStatusEvent(state, "planned");
|
|
2675
|
+
} else if (type === "model.requested") {
|
|
2676
|
+
printStatusEvent(state, "model_wait", `${data.provider || "model"}/${data.model || ""}`);
|
|
2555
2677
|
} else if (type === "tool.started") {
|
|
2556
2678
|
printStatusEvent(state, "tool", data.toolName || "unknown");
|
|
2557
2679
|
} else if (type === "tool.completed") {
|
|
@@ -2574,10 +2696,18 @@ async function runPrompt(prompt, state, packageDir) {
|
|
|
2574
2696
|
}
|
|
2575
2697
|
},
|
|
2576
2698
|
});
|
|
2699
|
+
} catch (error) {
|
|
2700
|
+
runError = error;
|
|
2577
2701
|
} finally {
|
|
2578
2702
|
detachInterrupts();
|
|
2579
2703
|
queuedAfterFinish = await liveInput.stop();
|
|
2580
2704
|
}
|
|
2705
|
+
if (runError) {
|
|
2706
|
+
state.status = isAbortError(runError) ? "stopped" : "failed";
|
|
2707
|
+
state.activeGoal = "";
|
|
2708
|
+
printSystemLine(`status=${state.status} session=${state.sessionId}`);
|
|
2709
|
+
throw runError;
|
|
2710
|
+
}
|
|
2581
2711
|
state.sessionId = result.sessionId || state.sessionId;
|
|
2582
2712
|
state.status = result.stopped ? "stopped" : "idle";
|
|
2583
2713
|
state.activeGoal = "";
|
package/src/model-client.js
CHANGED
|
@@ -73,16 +73,110 @@ function toolChoiceForProvider(config, messages = []) {
|
|
|
73
73
|
return messages.some((message) => message.role === "tool") ? "auto" : "required";
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export function usesTextToolProtocol(config = {}) {
|
|
77
|
+
if (config.provider !== "venice") return false;
|
|
78
|
+
const model = String(config.model || "").toLowerCase();
|
|
79
|
+
return model === "gemma-4-uncensored" || model === "e2ee-venice-uncensored-24b-p" || model === "venice-uncensored";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function shouldRetryWithTextToolProtocol(error, config = {}) {
|
|
83
|
+
if (config.provider !== "venice") return false;
|
|
84
|
+
const message = [
|
|
85
|
+
error?.message,
|
|
86
|
+
error?.error?.message,
|
|
87
|
+
error?.response?.data?.error?.message,
|
|
88
|
+
error?.response?.data?.message,
|
|
89
|
+
]
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.join(" ");
|
|
92
|
+
return /invalid request parameters|tool_choice|parallel_tool_calls|tools/i.test(message);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function textToolProtocolPrompt(tools = []) {
|
|
96
|
+
const toolLines = tools.map((tool) => {
|
|
97
|
+
const fn = tool.function || {};
|
|
98
|
+
const properties = fn.parameters?.properties ? Object.keys(fn.parameters.properties).slice(0, 8) : [];
|
|
99
|
+
const required = Array.isArray(fn.parameters?.required) ? fn.parameters.required : [];
|
|
100
|
+
const args = properties.length > 0 ? ` args=${properties.join(",")}${required.length ? ` required=${required.join(",")}` : ""}` : "";
|
|
101
|
+
return `- ${fn.name}: ${String(fn.description || "").slice(0, 180)}${args}`;
|
|
102
|
+
});
|
|
103
|
+
return [
|
|
104
|
+
"This provider/model may not accept native OpenAI function-call parameters.",
|
|
105
|
+
"Use this text tool protocol when you need a tool:",
|
|
106
|
+
'[TOOL_CALLS]tool_name[ARGS]{"arg":"value"}',
|
|
107
|
+
'A strict id form is also accepted: [TOOL_CALLS]tool_name[ARGS]call_short_id[ARGS]{"arg":"value"}',
|
|
108
|
+
'A JSON block form is accepted too: TOOL_CALLS: ```json [{"name":"tool_name","arguments":{"arg":"value"}}] ```',
|
|
109
|
+
"Return only one or more TOOL_CALLS blocks when calling tools; do not wrap them in markdown.",
|
|
110
|
+
"If no tool is needed, answer normally.",
|
|
111
|
+
"Available text tools:",
|
|
112
|
+
...toolLines,
|
|
113
|
+
].join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function messagesWithTextToolProtocol(config, messages, tools) {
|
|
117
|
+
const prepared = prepareMessages(config, messages).map((message) => {
|
|
118
|
+
if (message.role === "tool") {
|
|
119
|
+
return {
|
|
120
|
+
role: "user",
|
|
121
|
+
content: `Tool result for ${message.tool_call_id || "previous tool"}:\n${message.content || ""}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
|
|
125
|
+
return {
|
|
126
|
+
role: "assistant",
|
|
127
|
+
content:
|
|
128
|
+
message.content ||
|
|
129
|
+
`Requested tools: ${message.tool_calls
|
|
130
|
+
.map((call) => `${call.function?.name || "tool"}(${call.function?.arguments || "{}"})`)
|
|
131
|
+
.join("; ")}`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return message;
|
|
135
|
+
});
|
|
136
|
+
const protocol = { role: "system", content: textToolProtocolPrompt(tools) };
|
|
137
|
+
if (prepared[0]?.role === "system") return [prepared[0], protocol, ...prepared.slice(1)];
|
|
138
|
+
return [protocol, ...prepared];
|
|
139
|
+
}
|
|
140
|
+
|
|
76
141
|
export function parseTextToolCalls(content = "") {
|
|
77
142
|
const text = String(content || "");
|
|
78
|
-
if (!text.includes("[TOOL_CALLS]")) return [];
|
|
143
|
+
if (!text.includes("[TOOL_CALLS]") && !/TOOL_CALLS\s*:/i.test(text)) return [];
|
|
79
144
|
|
|
80
145
|
const calls = [];
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
146
|
+
const jsonBlock = text.match(/TOOL_CALLS\s*:\s*```(?:json)?\s*([\s\S]*?)```/i);
|
|
147
|
+
if (jsonBlock?.[1]) {
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(jsonBlock[1].trim());
|
|
150
|
+
if (Array.isArray(parsed)) {
|
|
151
|
+
for (const item of parsed) {
|
|
152
|
+
const name = String(item?.name || item?.tool || "").trim();
|
|
153
|
+
if (!name) continue;
|
|
154
|
+
const args = item?.arguments && typeof item.arguments === "object" ? item.arguments : {};
|
|
155
|
+
calls.push({
|
|
156
|
+
id: String(item?.id || `text-tool-${calls.length + 1}`),
|
|
157
|
+
type: "function",
|
|
158
|
+
function: {
|
|
159
|
+
name,
|
|
160
|
+
arguments: JSON.stringify(args),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Fall through to bracket parser below.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const chunk of text.split("[TOOL_CALLS]").slice(1)) {
|
|
171
|
+
const match = chunk.match(/^([A-Za-z0-9_-]+)\[ARGS\]([\s\S]*?)$/);
|
|
172
|
+
const name = match?.[1]?.trim();
|
|
173
|
+
let rawArgs = match?.[2]?.trim() || "{}";
|
|
174
|
+
let id = `text-tool-${calls.length + 1}`;
|
|
175
|
+
const strictParts = rawArgs.split("[ARGS]");
|
|
176
|
+
if (strictParts.length >= 2 && !rawArgs.startsWith("{") && !rawArgs.startsWith("[")) {
|
|
177
|
+
id = strictParts.shift()?.trim() || id;
|
|
178
|
+
rawArgs = strictParts.join("[ARGS]").trim() || "{}";
|
|
179
|
+
}
|
|
86
180
|
if (!name) continue;
|
|
87
181
|
try {
|
|
88
182
|
JSON.parse(rawArgs);
|
|
@@ -101,14 +195,39 @@ export function parseTextToolCalls(content = "") {
|
|
|
101
195
|
return calls;
|
|
102
196
|
}
|
|
103
197
|
|
|
198
|
+
function textBeforeToolCallMarker(content = "") {
|
|
199
|
+
return String(content || "")
|
|
200
|
+
.split("[TOOL_CALLS]")[0]
|
|
201
|
+
.split("TOOL_CALLS:")[0]
|
|
202
|
+
.split("<|tool_call>")[0]
|
|
203
|
+
.trim();
|
|
204
|
+
}
|
|
205
|
+
|
|
104
206
|
function normalizeTextToolCallResponse(response) {
|
|
105
207
|
const message = response?.choices?.[0]?.message;
|
|
106
208
|
if (!message || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) return response;
|
|
107
209
|
|
|
108
210
|
const calls = parseTextToolCalls(message.content || "");
|
|
109
|
-
if (calls.length === 0)
|
|
211
|
+
if (calls.length === 0) {
|
|
212
|
+
const cleanedContent = textBeforeToolCallMarker(message.content || "");
|
|
213
|
+
if (!cleanedContent || cleanedContent === message.content) return response;
|
|
214
|
+
return {
|
|
215
|
+
...response,
|
|
216
|
+
choices: response.choices.map((choice, index) =>
|
|
217
|
+
index === 0
|
|
218
|
+
? {
|
|
219
|
+
...choice,
|
|
220
|
+
message: {
|
|
221
|
+
...message,
|
|
222
|
+
content: cleanedContent,
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
: choice
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
110
229
|
|
|
111
|
-
const content =
|
|
230
|
+
const content = textBeforeToolCallMarker(message.content || "");
|
|
112
231
|
return {
|
|
113
232
|
...response,
|
|
114
233
|
choices: response.choices.map((choice, index) =>
|
|
@@ -919,16 +1038,33 @@ export async function requestNextStep(client, config, messages) {
|
|
|
919
1038
|
]);
|
|
920
1039
|
}
|
|
921
1040
|
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1041
|
+
const textToolProtocol = usesTextToolProtocol(config);
|
|
1042
|
+
const nativePayload = {
|
|
1043
|
+
model: config.model,
|
|
1044
|
+
temperature: 0,
|
|
1045
|
+
tool_choice: toolChoiceForProvider(config, messages),
|
|
1046
|
+
parallel_tool_calls: false,
|
|
1047
|
+
messages: prepareMessages(config, messages),
|
|
1048
|
+
tools,
|
|
1049
|
+
};
|
|
1050
|
+
const textPayload = {
|
|
1051
|
+
model: config.model,
|
|
1052
|
+
temperature: 0,
|
|
1053
|
+
messages: messagesWithTextToolProtocol(config, messages, tools),
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
let response;
|
|
1057
|
+
try {
|
|
1058
|
+
response = await client.chat.completions.create(
|
|
1059
|
+
textToolProtocol ? textPayload : nativePayload,
|
|
1060
|
+
requestOptions(config)
|
|
1061
|
+
);
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
if (!textToolProtocol && shouldRetryWithTextToolProtocol(error, config)) {
|
|
1064
|
+
response = await client.chat.completions.create(textPayload, requestOptions(config));
|
|
1065
|
+
} else {
|
|
1066
|
+
throw error;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
933
1069
|
return normalizeTextToolCallResponse(response);
|
|
934
1070
|
}
|
package/src/model-routing.js
CHANGED
|
@@ -215,12 +215,19 @@ export const PROVIDER_MODEL_CATALOG = {
|
|
|
215
215
|
context: "128K",
|
|
216
216
|
description: "Current Venice uncensored text route.",
|
|
217
217
|
},
|
|
218
|
+
{
|
|
219
|
+
id: "e2ee-venice-uncensored-24b-p",
|
|
220
|
+
label: "Venice Uncensored 1.1",
|
|
221
|
+
bucket: "venice-uncensored",
|
|
222
|
+
context: "32K",
|
|
223
|
+
description: "E2EE Venice uncensored 1.1 text route.",
|
|
224
|
+
},
|
|
218
225
|
{
|
|
219
226
|
id: "venice-uncensored",
|
|
220
|
-
label: "Venice Uncensored",
|
|
227
|
+
label: "Venice Uncensored Legacy",
|
|
221
228
|
bucket: "venice-uncensored",
|
|
222
229
|
context: "32K",
|
|
223
|
-
description: "
|
|
230
|
+
description: "Deprecated legacy Venice uncensored text route.",
|
|
224
231
|
},
|
|
225
232
|
{
|
|
226
233
|
id: "venice-uncensored-role-play",
|