@lazyingart/agintiflow 0.20.3 → 0.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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` is a toggle. When off, it enables:
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
- /route venice/venice-uncensored-1-2
55
- /main venice/venice-uncensored-1-2
54
+ venice/venice-uncensored-1-2
55
+ venice/e2ee-venice-uncensored-24b-p
56
+ venice/gemma-4-uncensored
56
57
  ```
57
58
 
58
- When already on, `/venice` switches back to the DeepSeek defaults:
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",
3
+ "version": "0.20.5",
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` is a toggle: it keeps smart routing enabled and points both route and main roles at `venice/venice-uncensored-1-2`; running it again or using `/venice off` restores `deepseek/deepseek-v4-flash` for route and `deepseek/deepseek-v4-pro` for main.
56
+ `/venice` opens a route/main selector for Venice text models. The selector includes `venice/venice-uncensored-1-2` (Venice 1.2), `venice/e2ee-venice-uncensored-24b-p` (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 `e2ee-venice-uncensored-24b-p` 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 (`e2ee-venice-uncensored-24b-p`), 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 older `venice-uncensored` ID is retained only as a legacy alias.
53
56
 
54
57
  ## Image And Edit Models
55
58
 
@@ -4,7 +4,14 @@ import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { buildLaunchHeaderLines, buildPromptLayout, classifyEscapeAction, formatWorkspaceChange, stripMarkdown } from "../src/interactive-cli.js";
7
+ import {
8
+ buildLaunchHeaderLines,
9
+ buildPromptLayout,
10
+ canonicalSlashPromptBuffer,
11
+ classifyEscapeAction,
12
+ formatWorkspaceChange,
13
+ stripMarkdown,
14
+ } from "../src/interactive-cli.js";
8
15
 
9
16
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
17
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
@@ -179,6 +186,14 @@ try {
179
186
  if (classifyEscapeAction({ active: true, pendingAsap: [] }) !== "abort") {
180
187
  throw new Error("active Esc should abort when no ASAP pipe messages are pending");
181
188
  }
189
+ if (
190
+ canonicalSlashPromptBuffer("/ve") !== "/venice" ||
191
+ canonicalSlashPromptBuffer("/v") !== "/venice" ||
192
+ canonicalSlashPromptBuffer("/not-a-command") !== "/not-a-command" ||
193
+ canonicalSlashPromptBuffer("/venice off") !== "/venice off"
194
+ ) {
195
+ throw new Error("slash command prompt canonicalization did not preserve final submitted command correctly");
196
+ }
182
197
 
183
198
  await runCli(["init"], "");
184
199
  const instructions = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
@@ -265,6 +280,7 @@ try {
265
280
  "auxiliary-command-spelling",
266
281
  "skills-command",
267
282
  "slash-prefix-autoselect",
283
+ "slash-prefix-canonical-history",
268
284
  "instructions-chat-edit",
269
285
  "interactive-chat",
270
286
  "mock-file-write",
@@ -9,6 +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
13
 
13
14
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
14
15
 
@@ -108,7 +109,12 @@ assert(fastRoute.model === "deepseek-v4-flash", "fast route did not use route mo
108
109
 
109
110
  assert(MODEL_PROVIDER_GROUPS["venice-gpt"].provider === "venice", "venice-gpt group missing");
110
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");
111
113
  assert(AUXILIARY_MODEL_CATALOG["venice-image"].some((item) => item.id === "gpt-image-2"), "Venice image catalog missing GPT Image 2");
114
+ const parsedTextToolCalls = parseTextToolCalls('[TOOL_CALLS]list_files[ARGS]call_123[ARGS]{"path":".","maxDepth":1}');
115
+ assert(parsedTextToolCalls.length === 1, "Venice text tool-call parser did not detect encoded tool call");
116
+ assert(parsedTextToolCalls[0].function.name === "list_files", "Venice text tool-call parser returned wrong tool name");
117
+ assert(parsedTextToolCalls[0].function.arguments.includes('"maxDepth":1'), "Venice text tool-call parser returned wrong arguments");
112
118
 
113
119
  const output = await runCli(["models"]);
114
120
  assert(output.includes("/route") && output.includes("/spare") && output.includes("venice-gpt"), "aginti models output missing role details");
@@ -117,6 +123,9 @@ const interactiveOutput = await runInteractive("/venice\n");
117
123
  assert(interactiveOutput.includes("venice=on"), "/venice did not enable Venice roles");
118
124
  assert(interactiveOutput.includes("route=venice/venice-uncensored-1-2"), "/venice did not set Venice route role");
119
125
  assert(interactiveOutput.includes("main=venice/venice-uncensored-1-2"), "/venice did not set Venice main role");
126
+ const interactiveGemmaOutput = await runInteractive("/venice 1.1 gemma\n");
127
+ assert(interactiveGemmaOutput.includes("route=venice/e2ee-venice-uncensored-24b-p"), "/venice 1.1 did not set Venice 1.1 route role");
128
+ assert(interactiveGemmaOutput.includes("main=venice/gemma-4-uncensored"), "/venice gemma did not set Gemma 4 main role");
120
129
  const interactiveOffOutput = await runInteractive("/venice off\n");
121
130
  assert(interactiveOffOutput.includes("venice=off"), "/venice off did not restore DeepSeek roles");
122
131
  assert(interactiveOffOutput.includes("route=deepseek/deepseek-v4-flash"), "/venice off did not restore DeepSeek route role");
@@ -125,7 +134,15 @@ console.log(
125
134
  JSON.stringify(
126
135
  {
127
136
  ok: true,
128
- checks: ["role-defaults", "route-overrides", "provider-groups", "auxiliary-catalog", "cli-models-command", "venice-shortcut"],
137
+ checks: [
138
+ "role-defaults",
139
+ "route-overrides",
140
+ "provider-groups",
141
+ "auxiliary-catalog",
142
+ "venice-text-tool-parser",
143
+ "cli-models-command",
144
+ "venice-shortcut",
145
+ ],
129
146
  },
130
147
  null,
131
148
  2
@@ -194,6 +194,15 @@ function resolveSlashCommand(command = "") {
194
194
  return suggestion ? suggestion.slice(1) : raw;
195
195
  }
196
196
 
197
+ export function canonicalSlashPromptBuffer(value = "") {
198
+ const text = String(value || "");
199
+ const trimmed = text.trim();
200
+ if (!trimmed.startsWith("/") || /\s/.test(trimmed)) return text;
201
+ const raw = trimmed.slice(1);
202
+ const resolved = resolveSlashCommand(raw);
203
+ return resolved === raw ? text : `/${resolved}`;
204
+ }
205
+
197
206
  function clamp(value, min, max) {
198
207
  return Math.min(Math.max(value, min), max);
199
208
  }
@@ -584,7 +593,7 @@ function printHelp() {
584
593
  " /instructions Show AGINTI.md project instructions status.",
585
594
  " /memory Alias for /instructions.",
586
595
  " /models Show route/main/spare/wrapper/auxiliary model roles.",
587
- " /venice [on|off] Toggle Venice Uncensored 1.2 for route and main roles.",
596
+ " /venice [off|model] Pick Venice route/main models, or restore DeepSeek defaults.",
588
597
  " /route [mode|provider/model]",
589
598
  " Open route selector, or set routing/fast route model.",
590
599
  " /model [provider/model] Open main-model selector, or set the active/main model.",
@@ -944,6 +953,11 @@ function readTtyPrompt(options = {}) {
944
953
  };
945
954
 
946
955
  const submit = () => {
956
+ const canonical = canonicalSlashPromptBuffer(buffer);
957
+ if (canonical !== buffer) {
958
+ buffer = canonical;
959
+ cursor = buffer.length;
960
+ }
947
961
  renderNow();
948
962
  moveToPromptBottom(rendered);
949
963
  cleanup();
@@ -1626,23 +1640,59 @@ function useDeepSeekDefaults(state) {
1626
1640
  state.mainModel = "deepseek-v4-pro";
1627
1641
  }
1628
1642
 
1629
- function useVeniceDefaults(state) {
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: "e2ee-venice-uncensored-24b-p",
1654
+ label: "Venice 1.1",
1655
+ description: "E2EE 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
+ normalized === "e2ee-venice-uncensored-24b-p"
1675
+ ) {
1676
+ return "e2ee-venice-uncensored-24b-p";
1677
+ }
1678
+ if (normalized === "legacy" || normalized === "venice-uncensored") {
1679
+ return "venice-uncensored";
1680
+ }
1681
+ if (normalized === "gemma" || normalized === "gemma4" || normalized === "gemma-4" || normalized === "gemma-4-uncensored") {
1682
+ return "gemma-4-uncensored";
1683
+ }
1684
+ const exact = veniceTextModelChoices().find((item) => item.model.toLowerCase() === normalized);
1685
+ return exact?.model || "";
1686
+ }
1687
+
1688
+ function useVeniceModels(state, routeModel = "venice-uncensored-1-2", mainModel = routeModel) {
1630
1689
  state.routingMode = "smart";
1631
1690
  state.provider = "deepseek";
1632
1691
  state.model = "";
1633
1692
  state.routeProvider = "venice";
1634
- state.routeModel = "venice-uncensored-1-2";
1693
+ state.routeModel = routeModel;
1635
1694
  state.mainProvider = "venice";
1636
- state.mainModel = "venice-uncensored-1-2";
1637
- }
1638
-
1639
- function isVeniceDefaultActive(state) {
1640
- return (
1641
- state.routeProvider === "venice" &&
1642
- state.routeModel === "venice-uncensored-1-2" &&
1643
- state.mainProvider === "venice" &&
1644
- state.mainModel === "venice-uncensored-1-2"
1645
- );
1695
+ state.mainModel = mainModel;
1646
1696
  }
1647
1697
 
1648
1698
  function printModelRoles(state) {
@@ -1723,12 +1773,30 @@ function modelRoleChoices(role = "main") {
1723
1773
  {
1724
1774
  provider: "venice",
1725
1775
  model: "venice-uncensored-1-2",
1726
- label: "Venice Uncensored 1.2",
1776
+ label: "Venice 1.2",
1727
1777
  description: "Venice text route; use /auth venice if missing",
1728
1778
  route: true,
1729
1779
  main: true,
1730
1780
  spare: true,
1731
1781
  },
1782
+ {
1783
+ provider: "venice",
1784
+ model: "e2ee-venice-uncensored-24b-p",
1785
+ label: "Venice 1.1",
1786
+ description: "E2EE Venice text route; use /auth venice if missing",
1787
+ route: true,
1788
+ main: true,
1789
+ spare: true,
1790
+ },
1791
+ {
1792
+ provider: "venice",
1793
+ model: "gemma-4-uncensored",
1794
+ label: "Gemma 4",
1795
+ description: "Gemma-family Venice route; use /auth venice if missing",
1796
+ route: true,
1797
+ main: true,
1798
+ spare: true,
1799
+ },
1732
1800
  {
1733
1801
  provider: "openai",
1734
1802
  model: "gpt-5.5",
@@ -1835,7 +1903,7 @@ function providerChoices() {
1835
1903
  provider: "venice",
1836
1904
  model: "venice-uncensored-1-2",
1837
1905
  label: "Venice",
1838
- description: "manual Venice route; /venice toggles route+main roles",
1906
+ description: "manual Venice route; /venice picks route+main roles",
1839
1907
  },
1840
1908
  {
1841
1909
  provider: "qwen",
@@ -1866,10 +1934,12 @@ function renderSelector({ title, subtitle, options, selectedIndex, lineCount = 0
1866
1934
  if (lineCount > 0) clearSelector(lineCount);
1867
1935
  const width = Math.min(Math.max(terminalWidth() - 2, 60), 110);
1868
1936
  const bodyWidth = width - 4;
1937
+ const safeTitle = compactLine(title, bodyWidth);
1938
+ const safeSubtitle = compactLine(subtitle, bodyWidth);
1869
1939
  const rows = [
1870
1940
  `╭${"─".repeat(width - 2)}╮`,
1871
- `│ ${padVisible(title, bodyWidth)} │`,
1872
- `│ ${padVisible(subtitle, bodyWidth)} │`,
1941
+ `│ ${padVisible(safeTitle, bodyWidth)} │`,
1942
+ `│ ${padVisible(safeSubtitle, bodyWidth)} │`,
1873
1943
  `├${"─".repeat(width - 2)}┤`,
1874
1944
  ...options.map((option, index) => {
1875
1945
  const marker = index === selectedIndex ? ">" : " ";
@@ -1998,6 +2068,38 @@ async function pickModelRole(role, state) {
1998
2068
  return true;
1999
2069
  }
2000
2070
 
2071
+ async function pickVeniceRouteAndMain(state) {
2072
+ const options = veniceTextModelChoices();
2073
+ const routeIndex = Math.max(
2074
+ options.findIndex((item) => item.provider === state.routeProvider && item.model === state.routeModel),
2075
+ 0
2076
+ );
2077
+ const route = await selectModelChoice({
2078
+ title: "Select Venice route model",
2079
+ subtitle: "Route handles planning and short turns. Up/Down selects, Enter confirms, Esc cancels.",
2080
+ options,
2081
+ initialIndex: routeIndex,
2082
+ });
2083
+ if (!route) return false;
2084
+
2085
+ const mainIndex = Math.max(
2086
+ options.findIndex((item) => item.provider === state.mainProvider && item.model === state.mainModel),
2087
+ options.findIndex((item) => item.model === route.model),
2088
+ 0
2089
+ );
2090
+ const main = await selectModelChoice({
2091
+ title: "Select Venice main model",
2092
+ subtitle: "Main handles complex work. Up/Down selects, Enter confirms, Esc cancels.",
2093
+ options,
2094
+ initialIndex: mainIndex,
2095
+ });
2096
+ if (!main) return false;
2097
+
2098
+ useVeniceModels(state, route.model, main.model);
2099
+ printSystemLine(`venice=on routing=smart route=venice/${state.routeModel} main=venice/${state.mainModel}`);
2100
+ return true;
2101
+ }
2102
+
2001
2103
  async function pickProvider(state) {
2002
2104
  const options = providerChoices();
2003
2105
  const currentProvider = state.provider || "deepseek";
@@ -2252,19 +2354,45 @@ async function handleCommand(line, state, packageDir) {
2252
2354
  return true;
2253
2355
  }
2254
2356
  if (command === "venice") {
2255
- const action = value || (isVeniceDefaultActive(state) ? "off" : "on");
2357
+ const canUseSelector = input.isTTY && output.isTTY && typeof input.setRawMode === "function";
2358
+ const action = value || (canUseSelector ? "select" : "on");
2256
2359
  if (action === "off" || action === "deepseek" || action === "default") {
2257
2360
  useDeepSeekDefaults(state);
2258
2361
  printSystemLine("venice=off routing=smart route=deepseek/deepseek-v4-flash main=deepseek/deepseek-v4-pro");
2259
2362
  return true;
2260
2363
  }
2261
- if (action !== "on" && action !== "venice") {
2262
- printAgentMessage("Usage: /venice [on|off]. No argument toggles between Venice and DeepSeek defaults.");
2364
+ if (action === "select" && canUseSelector) {
2365
+ const changed = await pickVeniceRouteAndMain(state);
2366
+ if (!changed) {
2367
+ printSystemLine(`route=${state.routeProvider || "deepseek"}/${state.routeModel || "deepseek-v4-flash"} main=${state.mainProvider || "deepseek"}/${state.mainModel || "deepseek-v4-pro"}`);
2368
+ return true;
2369
+ }
2370
+ const keys = providerKeyStatus(process.cwd());
2371
+ if (!keys.venice) {
2372
+ printAgentMessage("Venice model roles are selected, but no Venice key is configured. Run `/auth venice` to save one.");
2373
+ }
2374
+ return true;
2375
+ }
2376
+
2377
+ const parts = action.split(/\s+/).filter(Boolean);
2378
+ const routeModel = resolveVeniceTextModel(parts[0] || "on");
2379
+ const mainModel = resolveVeniceTextModel(parts.slice(1).join(" ") || parts[0] || "on");
2380
+ if (!routeModel || !mainModel) {
2381
+ printAgentMessage(
2382
+ [
2383
+ "Usage: /venice [off|1.2|1.1|gemma] [main-model]",
2384
+ "Examples:",
2385
+ " /venice",
2386
+ " /venice gemma",
2387
+ " /venice 1.2 gemma",
2388
+ " /venice off",
2389
+ ].join("\n")
2390
+ );
2263
2391
  return true;
2264
2392
  }
2265
- useVeniceDefaults(state);
2393
+ useVeniceModels(state, routeModel, mainModel);
2266
2394
  const keys = providerKeyStatus(process.cwd());
2267
- printSystemLine("venice=on routing=smart route=venice/venice-uncensored-1-2 main=venice/venice-uncensored-1-2");
2395
+ printSystemLine(`venice=on routing=smart route=venice/${state.routeModel} main=venice/${state.mainModel}`);
2268
2396
  if (!keys.venice) {
2269
2397
  printAgentMessage("Venice model roles are selected, but no Venice key is configured. Run `/auth venice` to save one.");
2270
2398
  }
@@ -2607,7 +2735,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
2607
2735
  }
2608
2736
  throw error;
2609
2737
  }
2610
- const line = answer.trim();
2738
+ const line = canonicalSlashPromptBuffer(answer).trim();
2611
2739
  if (!line) continue;
2612
2740
  if (line.startsWith("/")) {
2613
2741
  const keepGoing = await handleCommand(line, state, packageDir);
@@ -2619,7 +2747,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
2619
2747
  const pendingPrompts = [{ content: line }];
2620
2748
  while (pendingPrompts.length > 0) {
2621
2749
  const nextPrompt = pendingPrompts.shift();
2622
- const content = String(nextPrompt.content || "").trim();
2750
+ const content = canonicalSlashPromptBuffer(String(nextPrompt.content || "")).trim();
2623
2751
  if (!content) continue;
2624
2752
  if (content.startsWith("/")) {
2625
2753
  const keepGoing = await handleCommand(content, state, packageDir);
@@ -73,6 +73,59 @@ function toolChoiceForProvider(config, messages = []) {
73
73
  return messages.some((message) => message.role === "tool") ? "auto" : "required";
74
74
  }
75
75
 
76
+ export function parseTextToolCalls(content = "") {
77
+ const text = String(content || "");
78
+ if (!text.includes("[TOOL_CALLS]")) return [];
79
+
80
+ const calls = [];
81
+ const pattern = /\[TOOL_CALLS\]([A-Za-z0-9_-]+)\[ARGS\]([A-Za-z0-9_.:-]+)\[ARGS\]([\s\S]*?)(?=\[TOOL_CALLS\]|$)/g;
82
+ for (const match of text.matchAll(pattern)) {
83
+ const name = match[1]?.trim();
84
+ const id = match[2]?.trim() || `text-tool-${calls.length + 1}`;
85
+ const rawArgs = match[3]?.trim() || "{}";
86
+ if (!name) continue;
87
+ try {
88
+ JSON.parse(rawArgs);
89
+ } catch {
90
+ continue;
91
+ }
92
+ calls.push({
93
+ id,
94
+ type: "function",
95
+ function: {
96
+ name,
97
+ arguments: rawArgs,
98
+ },
99
+ });
100
+ }
101
+ return calls;
102
+ }
103
+
104
+ function normalizeTextToolCallResponse(response) {
105
+ const message = response?.choices?.[0]?.message;
106
+ if (!message || Array.isArray(message.tool_calls) && message.tool_calls.length > 0) return response;
107
+
108
+ const calls = parseTextToolCalls(message.content || "");
109
+ if (calls.length === 0) return response;
110
+
111
+ const content = String(message.content || "").split("[TOOL_CALLS]")[0].trim();
112
+ return {
113
+ ...response,
114
+ choices: response.choices.map((choice, index) =>
115
+ index === 0
116
+ ? {
117
+ ...choice,
118
+ message: {
119
+ ...message,
120
+ content,
121
+ tool_calls: calls,
122
+ },
123
+ }
124
+ : choice
125
+ ),
126
+ };
127
+ }
128
+
76
129
  function mockCommandForGoal(goal = "") {
77
130
  const text = String(goal).toLowerCase();
78
131
  if (/\blist\b|folder contents|directory contents|files?/.test(text)) return "ls -la";
@@ -866,7 +919,7 @@ export async function requestNextStep(client, config, messages) {
866
919
  ]);
867
920
  }
868
921
 
869
- return client.chat.completions.create(
922
+ const response = await client.chat.completions.create(
870
923
  {
871
924
  model: config.model,
872
925
  temperature: 0,
@@ -877,4 +930,5 @@ export async function requestNextStep(client, config, messages) {
877
930
  },
878
931
  requestOptions(config)
879
932
  );
933
+ return normalizeTextToolCallResponse(response);
880
934
  }
@@ -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: "Legacy Venice uncensored text route.",
230
+ description: "Deprecated legacy Venice uncensored text route.",
224
231
  },
225
232
  {
226
233
  id: "venice-uncensored-role-play",