@lazyingart/agintiflow 0.17.6 → 0.19.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 CHANGED
@@ -46,15 +46,17 @@ aginti --list-profiles
46
46
  aginti --sandbox-status
47
47
  ```
48
48
 
49
- On first interactive use, if no main model key is detected, `aginti` opens an auth wizard. Use Up/Down to choose DeepSeek, OpenAI, or Qwen, paste the key, and press Enter to save it to the project-local ignored file `.aginti/.env` with `0600` permissions. The wizard points to DeepSeek keys at `https://platform.deepseek.com/api_keys` and OpenAI keys at `https://platform.openai.com/api-keys`. It then offers the optional auxiliary image key; press Esc to skip. You can rerun it even when keys already exist:
49
+ On first interactive use, if no main model key is detected, `aginti` opens an auth wizard. Use Up/Down to choose DeepSeek, OpenAI, Qwen, or Venice, paste the key, and press Enter to save it to the project-local ignored file `.aginti/.env` with `0600` permissions. The wizard points to DeepSeek keys at `https://platform.deepseek.com/api_keys`, OpenAI keys at `https://platform.openai.com/api-keys`, and Venice at `https://venice.ai`. It then offers the optional auxiliary image key; press Esc to skip. You can rerun it even when keys already exist:
50
50
 
51
51
  ```bash
52
52
  aginti auth
53
53
  aginti auth openai
54
+ aginti auth venice
54
55
  # inside chat, use /login or /auth for the same wizard
55
56
  # or non-interactively:
56
57
  printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
57
58
  printf '%s' "$QWEN_API_KEY" | aginti keys set qwen --stdin
59
+ printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
58
60
 
59
61
  # optional image-generation auxiliary skill:
60
62
  aginti login grsai
@@ -75,6 +77,8 @@ Inside chat, type normal requests such as `write a small Python CLI app with tes
75
77
 
76
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).
77
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 `/auxilliary` defaults to GRS AI/Nano Banana for image tools. See [docs/model-selection.md](docs/model-selection.md) and run `aginti models`.
81
+
78
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).
79
83
 
80
84
  AgInTiFlow can also spend cheap DeepSeek calls on parallel scout notes before the main executor starts a complicated task. It first writes a bounded project map to `.aginti/codebase-map.json`, then runs scouts for architecture, implementation, review, research, context mapping, tests, git workflow, integration, symbol tracing, and dependency risks. A coordinator Swarm Board is injected for the main agent and saved as `artifacts/scout-blackboard.json` in the session. The executor still does the real file/shell/browser work itself. Disable with `--no-parallel-scouts` or set `--scout-count 1..10`.
@@ -83,7 +87,7 @@ The next productive-agent roadmap is tracked in [docs/productive-agent-roadmap.m
83
87
 
84
88
  For current docs, install errors, package/toolchain setup, and source discovery, the agent has a guarded `web_search` tool. It returns compact search results without browser search-engine loops and respects configured domain allowlists. Disable with `--no-web-search`.
85
89
 
86
- For raster image work, AgInTiFlow has an optional `image_generation` skill backed by the `generate_image` tool and a local `GRSAI` key. The skill tells DeepSeek when image generation is appropriate; the tool calls GRS AI Nano Banana, saves manifests/images under `artifacts/images`, and sends the result to the canvas. See [docs/auxiliary-image-generation.md](docs/auxiliary-image-generation.md).
90
+ For raster image work, AgInTiFlow has optional `image_generation` and `venice_image_generation` skills backed by the `generate_image` tool and local `GRSAI` or `VENICE_API_KEY` credentials. The skill tells the model when image generation is appropriate; the tool saves manifests/images under `artifacts/images` and sends the result to the canvas. See [docs/auxiliary-image-generation.md](docs/auxiliary-image-generation.md) and [references/venice-model-reference.md](references/venice-model-reference.md).
87
91
 
88
92
  For long-running shell work, AgInTiFlow exposes host-side tmux tools when the shell tool is enabled: `tmux_list_sessions`, `tmux_capture_pane`, `tmux_send_keys`, and `tmux_start_session`. Use normal prompts such as `start this test server in tmux and monitor it` or `check my tmux session`. The agent captures panes before interacting, redacts outputs, blocks secret-like sends, and avoids sending sudo passwords or destructive commands. Tmux is intentionally host-side: Docker `run_command` containers are short-lived, so tmux servers started there cannot persist.
89
93
 
@@ -273,7 +277,7 @@ Defaults:
273
277
  | `smart` | DeepSeek | Fast for normal tasks, pro for complex tasks | `AGENT_ROUTING_MODE=smart` |
274
278
  | `fast` | DeepSeek | `deepseek-v4-flash` | `DEEPSEEK_FAST_MODEL` |
275
279
  | `complex` | DeepSeek | `deepseek-v4-pro` | `DEEPSEEK_PRO_MODEL` |
276
- | `manual` | DeepSeek/OpenAI/Qwen | user supplied | `AGENT_PROVIDER`, `LLM_MODEL` |
280
+ | `manual` | DeepSeek/OpenAI/Qwen/Venice | user supplied | `AGENT_PROVIDER`, `LLM_MODEL` |
277
281
 
278
282
  Provider credentials:
279
283
 
@@ -282,6 +286,7 @@ Provider credentials:
282
286
  | OpenAI | `OPENAI_API_KEY` | `https://api.openai.com/v1` |
283
287
  | DeepSeek | `DEEPSEEK_API_KEY` | `https://api.deepseek.com/v1` |
284
288
  | Qwen | `QWEN_API_KEY` | `QWEN_BASE_URL` or DashScope compatible mode |
289
+ | Venice | `VENICE_API_KEY` | `VENICE_API_BASE` or `https://api.venice.ai/api/v1` |
285
290
 
286
291
  Project-local credentials can be stored without committing secrets:
287
292
 
@@ -5,16 +5,18 @@ AgInTiFlow separates **skills** from **tools**:
5
5
  - A skill is instruction and routing context that teaches the agent when a capability is useful.
6
6
  - A tool is the deterministic callable function that performs the action and writes auditable artifacts.
7
7
 
8
- The optional image-generation skill is `image_generation`. Its tool is `generate_image`.
8
+ The optional image-generation skills are `image_generation` and `venice_image_generation`. Both use the deterministic `generate_image` tool.
9
9
 
10
10
  ## Setup
11
11
 
12
- Store the GRS AI key project-locally:
12
+ Store a GRS AI or Venice key project-locally:
13
13
 
14
14
  ```bash
15
15
  aginti login grsai
16
+ aginti login venice
16
17
  # or
17
18
  printf '%s' "$GRSAI" | aginti keys set grsai --stdin
19
+ printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
18
20
  ```
19
21
 
20
22
  Inside interactive chat, use either spelling:
@@ -24,7 +26,7 @@ Inside interactive chat, use either spelling:
24
26
  /auxiliary grsai
25
27
  ```
26
28
 
27
- The key is saved in `.aginti/.env` as `GRSAI` with `0600` permissions. The CLI and web app only report whether the key exists; they never return the raw value.
29
+ Keys are saved in `.aginti/.env` as `GRSAI` or `VENICE_API_KEY` with `0600` permissions. The CLI and web app only report whether a key exists; they never return raw values.
28
30
 
29
31
  ## Runtime Flow
30
32
 
@@ -33,6 +35,8 @@ For image, cover, poster, illustration, photo, or logo-concept requests, the mod
33
35
  ```json
34
36
  {
35
37
  "prompt": "A polished cyan robot painting a circuit-board river, high-end product illustration",
38
+ "provider": "venice",
39
+ "model": "nano-banana-2",
36
40
  "outputDir": "artifacts/images/robot-cover",
37
41
  "outputStem": "robot-cover",
38
42
  "aspectRatio": "1:1",
@@ -40,18 +44,25 @@ For image, cover, poster, illustration, photo, or logo-concept requests, the mod
40
44
  }
41
45
  ```
42
46
 
43
- The tool uses the GRS AI Nano Banana API:
47
+ With GRS AI, the tool uses the Nano Banana API:
44
48
 
45
49
  - `POST https://grsaiapi.com/v1/draw/nano-banana`
46
50
  - `POST https://grsaiapi.com/v1/draw/result`
47
51
  - `Authorization: Bearer <GRSAI>`
48
52
 
53
+ With Venice, the tool uses the image-generation API:
54
+
55
+ - `POST https://api.venice.ai/api/v1/image/generate`
56
+ - `Authorization: Bearer <VENICE_API_KEY>`
57
+ - image models such as `nano-banana-2`, `gpt-image-2`, `qwen-image-2`, `wan-2-7-text-to-image`, `bria-bg-remover`, and `venice-sd35`
58
+
49
59
  Saved workspace artifacts:
50
60
 
51
61
  - `prompt.txt`
52
62
  - `request_payload.redacted.json`
53
63
  - `submit_response.json`
54
64
  - `result_response.json`
65
+ - `venice_result_response.json` for Venice image calls
55
66
  - `task_manifest.json`
56
67
  - generated image files, for example `image.png`
57
68
 
@@ -0,0 +1,70 @@
1
+ # Model Selection
2
+
3
+ AgInTiFlow treats model choice as a role-based control plane. A provider supplies models; a role decides how the agent uses one.
4
+
5
+ ## Default Roles
6
+
7
+ | Role | CLI command | Default | Purpose |
8
+ | --- | --- | --- | --- |
9
+ | Route | `/route` | `deepseek/deepseek-v4-flash` | Fast planner, triage, short tasks, and routing decisions. |
10
+ | Main | `/model` or `/main` | `deepseek/deepseek-v4-pro` | Complex executor for coding, debugging, writing, and long tasks. |
11
+ | Spare | `/spare` | `openai/gpt-5.4` with `medium` reasoning | Optional fallback or cross-check model. |
12
+ | Wrapper | `/wrapper` | `codex gpt-5.5 medium` | External coding assistant when wrapper tools are enabled. |
13
+ | Auxiliary | `/auxilliary` | `grsai/nano-banana-2` | Image/media tools; Venice image models are optional. |
14
+
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
+
17
+ ## CLI Commands
18
+
19
+ ```bash
20
+ aginti models
21
+ aginti --list-models
22
+ aginti --list-routes
23
+
24
+ # one-shot overrides
25
+ aginti --route-model deepseek-v4-flash --main-model deepseek-v4-pro "fix this project"
26
+ aginti --provider venice --model venice-uncensored-1-2 --routing manual "draft a note"
27
+ aginti --spare-provider openai --spare-model gpt-5.4 --spare-reasoning medium "review this design"
28
+ aginti --allow-wrappers --wrapper codex --wrapper-model gpt-5.5 "patch this bug"
29
+ aginti --image --aux-provider venice --aux-model gpt-image-2 "generate a logo"
30
+ ```
31
+
32
+ Interactive commands:
33
+
34
+ ```text
35
+ /models
36
+ /route deepseek/deepseek-v4-flash
37
+ /model deepseek/deepseek-v4-pro
38
+ /spare openai/gpt-5.4 medium
39
+ /wrapper codex gpt-5.5 medium
40
+ /auxilliary model grsai/nano-banana-2
41
+ ```
42
+
43
+ ## Provider Buckets
44
+
45
+ | Bucket | Provider | Typical use |
46
+ | --- | --- | --- |
47
+ | `deepseek` | DeepSeek | Default route/main because V4 Flash and V4 Pro are cheap and strong. |
48
+ | `openai` | OpenAI | Spare/frontier checks, Codex-family work, and explicit manual routes. |
49
+ | `qwen` | Qwen | Chinese and general-purpose OpenAI-compatible tasks. |
50
+ | `venice-uncensored` | Venice | Venice-native uncensored text models. |
51
+ | `venice-gpt` | Venice | GPT-family models through Venice. |
52
+ | `venice-claude` | Venice | Claude-family models through Venice. |
53
+ | `venice-gemma` | Venice | Gemma-family models through Venice. |
54
+ | `venice-qwen` | Venice | Qwen-family models through Venice. |
55
+ | `venice-image` | Venice | Image generation/editing such as Nano Banana, GPT Image, Wan, Qwen Image. |
56
+ | `grsai` | GRS AI | Auxiliary image generation only. |
57
+
58
+ ## Keys
59
+
60
+ Keys are set per project and stored only in ignored `.aginti/.env`:
61
+
62
+ ```bash
63
+ aginti auth
64
+ aginti auth venice
65
+ aginti keys status
66
+ printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
67
+ ```
68
+
69
+ The web UI mirrors the same roles in its **Model roles** panel. Use the top provider/model fields for manual runs; use the role fields to change how smart routing chooses route, main, spare, wrapper, and auxiliary models.
70
+
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.17.6",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
- "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
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",
7
7
  "homepage": "https://flow.lazying.art",
8
8
  "repository": {
@@ -18,6 +18,9 @@
18
18
  "browser",
19
19
  "playwright",
20
20
  "deepseek",
21
+ "venice",
22
+ "openai",
23
+ "qwen",
21
24
  "sandbox",
22
25
  "cli"
23
26
  ],
@@ -38,6 +41,8 @@
38
41
  "logos/banner-opaque.png",
39
42
  "logos/logo.png",
40
43
  "public/",
44
+ "references/*.md",
45
+ "references/run_design_scan.sh",
41
46
  "scripts/install-docker-ubuntu.sh",
42
47
  "scripts/setup-agent-toolchain-docker.sh",
43
48
  "scripts/real-deepseek-capabilities.js",
@@ -47,6 +52,7 @@
47
52
  "scripts/smoke-coding-tools.js",
48
53
  "scripts/smoke-capabilities.js",
49
54
  "scripts/smoke-inbox.js",
55
+ "scripts/smoke-model-roles.js",
50
56
  "scripts/smoke-platform.js",
51
57
  "scripts/smoke-skills.js",
52
58
  "scripts/smoke-tmux-tools.js",
@@ -73,11 +79,12 @@
73
79
  "smoke:skills": "node scripts/smoke-skills.js",
74
80
  "smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
75
81
  "smoke:inbox": "node scripts/smoke-inbox.js",
82
+ "smoke:model-roles": "node scripts/smoke-model-roles.js",
76
83
  "smoke:platform": "node scripts/smoke-platform.js",
77
84
  "smoke:tmux-tools": "node scripts/smoke-tmux-tools.js",
78
85
  "smoke:web-api": "node scripts/smoke-web-api.js",
79
86
  "real:deepseek": "node scripts/real-deepseek-capabilities.js",
80
- "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:auth && npm run smoke:capabilities && npm run smoke:platform && npm run smoke:skills && npm run smoke:tmux-tools && npm run smoke:cli-chat && npm run smoke:inbox",
87
+ "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:auth && npm run smoke:capabilities && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:skills && npm run smoke:tmux-tools && npm run smoke:cli-chat && npm run smoke:inbox",
81
88
  "pack:dry-run": "npm pack --dry-run",
82
89
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
83
90
  },
package/public/app.js CHANGED
@@ -22,9 +22,9 @@ const translations = {
22
22
  projectStatusTitle: "Project folder",
23
23
  setupTitle: "Provider setup",
24
24
  setupHelp:
25
- "DeepSeek/OpenAI/Qwen keys are missing. Use mock mode, export an env var, or save a project-local model key.",
25
+ "DeepSeek/OpenAI/Qwen/Venice keys are missing. Use mock mode, export an env var, or save a project-local model key.",
26
26
  setupEnvHelp:
27
- "Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, QWEN_API_KEY, LLM_API_KEY, and optional GRSAI for image generation. Mock mode remains available for local tests.",
27
+ "Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, QWEN_API_KEY, VENICE_API_KEY, LLM_API_KEY, and optional GRSAI for image generation. Mock mode remains available for local tests.",
28
28
  setupKeyLinksLabel: "Get keys:",
29
29
  deepseekKeyLink: "DeepSeek API keys",
30
30
  openaiKeyLink: "OpenAI API keys",
@@ -649,6 +649,21 @@ const languageField = document.querySelector("#language");
649
649
  const routingModeField = document.querySelector("#routingMode");
650
650
  const providerField = document.querySelector("#provider");
651
651
  const modelField = document.querySelector("#model");
652
+ const modelOptionsEl = document.querySelector("#model-options");
653
+ const modelCatalogEl = document.querySelector("#model-catalog");
654
+ const modelRoleGridEl = document.querySelector("#model-role-grid");
655
+ const modelRoutePillEl = document.querySelector("#model-route-pill");
656
+ const routeProviderField = document.querySelector("#routeProvider");
657
+ const routeModelField = document.querySelector("#routeModel");
658
+ const mainProviderField = document.querySelector("#mainProvider");
659
+ const mainModelField = document.querySelector("#mainModel");
660
+ const spareProviderField = document.querySelector("#spareProvider");
661
+ const spareModelField = document.querySelector("#spareModel");
662
+ const spareReasoningField = document.querySelector("#spareReasoning");
663
+ const wrapperModelField = document.querySelector("#wrapperModel");
664
+ const wrapperReasoningField = document.querySelector("#wrapperReasoning");
665
+ const auxiliaryProviderField = document.querySelector("#auxiliaryProvider");
666
+ const auxiliaryModelField = document.querySelector("#auxiliaryModel");
652
667
  const routingHintEl = document.querySelector("#routing-hint");
653
668
  const modelRouteStatusEl = document.querySelector("#model-route-status");
654
669
  const projectStatusEl = document.querySelector("#project-status");
@@ -718,11 +733,16 @@ const defaults = {
718
733
  openai: "gpt-5.4-mini",
719
734
  deepseek: "deepseek-v4-flash",
720
735
  qwen: "qwen-plus",
736
+ venice: "venice-uncensored-1-2",
721
737
  mock: "mock-agent",
722
738
  };
723
739
 
724
740
  let currentLanguage = "en";
725
741
  let routingPresets = {};
742
+ let modelCatalog = {};
743
+ let modelRoles = {};
744
+ let modelGroups = {};
745
+ let auxiliaryModelCatalog = {};
726
746
  let taskProfiles = [];
727
747
  let projectInfo = null;
728
748
  let currentSessionId = "";
@@ -789,12 +809,14 @@ function renderKeyStatus(status = lastKeyStatus) {
789
809
  status.openai ? t("availableLabel") : t("missingLabel")
790
810
  } · DeepSeek ${status.deepseek ? t("availableLabel") : t("missingLabel")} · Qwen ${
791
811
  status.qwen ? t("availableLabel") : t("missingLabel")
812
+ } · Venice ${
813
+ status.venice ? t("availableLabel") : t("missingLabel")
792
814
  } · GRS AI ${
793
815
  status.grsai ? t("availableLabel") : t("missingLabel")
794
816
  } · ${t("mockLabel")} ${
795
817
  status.mock ? t("availableLabel") : t("missingLabel")
796
818
  }`;
797
- if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.deepseek || status.qwen);
819
+ if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.deepseek || status.qwen || status.venice);
798
820
  }
799
821
 
800
822
  function renderProjectStatus(info = projectInfo) {
@@ -959,6 +981,90 @@ function renderSandboxLogs(logs) {
959
981
  .join("\n\n");
960
982
  }
961
983
 
984
+ function providerModelOptions(provider = providerField.value) {
985
+ return modelCatalog[provider] || [];
986
+ }
987
+
988
+ function renderModelRoles() {
989
+ if (!modelRoleGridEl) return;
990
+ const roles = {
991
+ route: {
992
+ ...(modelRoles.route || {}),
993
+ provider: routeProviderField?.value || modelRoles.route?.provider || "deepseek",
994
+ model: routeModelField?.value || modelRoles.route?.model || "deepseek-v4-flash",
995
+ },
996
+ main: {
997
+ ...(modelRoles.main || {}),
998
+ provider: mainProviderField?.value || modelRoles.main?.provider || "deepseek",
999
+ model: mainModelField?.value || modelRoles.main?.model || "deepseek-v4-pro",
1000
+ },
1001
+ spare: {
1002
+ ...(modelRoles.spare || {}),
1003
+ provider: spareProviderField?.value || modelRoles.spare?.provider || "openai",
1004
+ model: spareModelField?.value || modelRoles.spare?.model || "gpt-5.4",
1005
+ reasoning: spareReasoningField?.value || modelRoles.spare?.reasoning || "medium",
1006
+ },
1007
+ wrapper: {
1008
+ ...(modelRoles.wrapper || {}),
1009
+ provider: preferredWrapperField?.value || modelRoles.wrapper?.provider || "codex",
1010
+ model: wrapperModelField?.value || modelRoles.wrapper?.model || "gpt-5.5",
1011
+ reasoning: wrapperReasoningField?.value || modelRoles.wrapper?.reasoning || "medium",
1012
+ },
1013
+ auxiliary: {
1014
+ ...(modelRoles.auxiliary || {}),
1015
+ provider: auxiliaryProviderField?.value || modelRoles.auxiliary?.provider || "grsai",
1016
+ model: auxiliaryModelField?.value || modelRoles.auxiliary?.model || "nano-banana-2",
1017
+ },
1018
+ };
1019
+ modelRoleGridEl.innerHTML = Object.values(roles)
1020
+ .map((role) => {
1021
+ const reasoning = role.reasoning ? ` · ${role.reasoning}` : "";
1022
+ return `
1023
+ <div class="model-role-card">
1024
+ <strong>${escapeHtml(role.label || role.id)}</strong>
1025
+ <span>${escapeHtml(`${role.provider}/${role.model}${reasoning}`)}</span>
1026
+ </div>
1027
+ `;
1028
+ })
1029
+ .join("");
1030
+ }
1031
+
1032
+ function renderModelOptions() {
1033
+ const provider = providerField.value || "deepseek";
1034
+ const options = providerModelOptions(provider);
1035
+ if (modelOptionsEl) {
1036
+ modelOptionsEl.innerHTML = options
1037
+ .map((item) => `<option value="${escapeHtml(item.id)}">${escapeHtml(item.label || item.id)}</option>`)
1038
+ .join("");
1039
+ }
1040
+ if (modelRoutePillEl) {
1041
+ const mode = routingModeField.value || "smart";
1042
+ const primary = modelField.value.trim() || defaults[provider] || "";
1043
+ const routeLabel = `${routeProviderField?.value || "deepseek"}/${routeModelField?.value || "deepseek-v4-flash"}`;
1044
+ const mainLabel = `${mainProviderField?.value || "deepseek"}/${mainModelField?.value || "deepseek-v4-pro"}`;
1045
+ modelRoutePillEl.textContent =
1046
+ mode === "manual"
1047
+ ? `${provider} · ${primary}`
1048
+ : `${mode} · route ${routeLabel} · main ${mainLabel}`;
1049
+ }
1050
+ if (!modelCatalogEl) return;
1051
+ if (options.length === 0) {
1052
+ modelCatalogEl.innerHTML = "";
1053
+ return;
1054
+ }
1055
+ modelCatalogEl.innerHTML = options
1056
+ .map(
1057
+ (item) => `
1058
+ <button class="model-chip" type="button" data-model-id="${escapeHtml(item.id)}" data-active="${item.id === modelField.value.trim()}">
1059
+ <strong>${escapeHtml(item.label || item.id)}</strong>
1060
+ <span>${escapeHtml([item.bucket || item.role, item.context, item.reasoning?.length ? `reasoning ${item.reasoning.join("/")}` : ""].filter(Boolean).join(" · "))}</span>
1061
+ </button>
1062
+ `
1063
+ )
1064
+ .join("");
1065
+ renderModelRoles();
1066
+ }
1067
+
962
1068
  function updateRoutingHint() {
963
1069
  const mode = routingModeField.value || "smart";
964
1070
  const hintKey = {
@@ -969,8 +1075,17 @@ function updateRoutingHint() {
969
1075
  }[mode];
970
1076
  routingHintEl.textContent = t(hintKey);
971
1077
 
972
- if (mode !== "manual") {
973
- const preset = mode === "complex" ? routingPresets.complex : routingPresets.fast;
1078
+ if (mode !== "manual" && providerField.value === "deepseek") {
1079
+ const preset =
1080
+ mode === "complex"
1081
+ ? {
1082
+ provider: mainProviderField?.value || routingPresets.complex?.provider,
1083
+ model: mainModelField?.value || routingPresets.complex?.model,
1084
+ }
1085
+ : {
1086
+ provider: routeProviderField?.value || routingPresets.fast?.provider,
1087
+ model: routeModelField?.value || routingPresets.fast?.model,
1088
+ };
974
1089
  if (preset) {
975
1090
  providerField.value = preset.provider === "deepseek" ? "deepseek" : providerField.value;
976
1091
  modelField.value = preset.model || modelField.value;
@@ -980,6 +1095,7 @@ function updateRoutingHint() {
980
1095
  modelRouteStatusEl.textContent = `${t("modelRouteStatus")}: ${providerField.value} / ${
981
1096
  modelField.value.trim() || defaults[providerField.value] || ""
982
1097
  }`;
1098
+ renderModelOptions();
983
1099
  }
984
1100
 
985
1101
  function updatePackageWarning() {
@@ -1037,6 +1153,17 @@ function formPayload() {
1037
1153
  routingMode: routingModeField.value,
1038
1154
  provider: providerField.value,
1039
1155
  model: modelField.value.trim(),
1156
+ routeProvider: routeProviderField?.value || "deepseek",
1157
+ routeModel: routeModelField?.value.trim() || "deepseek-v4-flash",
1158
+ mainProvider: mainProviderField?.value || "deepseek",
1159
+ mainModel: mainModelField?.value.trim() || "deepseek-v4-pro",
1160
+ spareProvider: spareProviderField?.value || "openai",
1161
+ spareModel: spareModelField?.value.trim() || "gpt-5.4",
1162
+ spareReasoning: spareReasoningField?.value || "medium",
1163
+ wrapperModel: wrapperModelField?.value.trim() || "gpt-5.5",
1164
+ wrapperReasoning: wrapperReasoningField?.value || "medium",
1165
+ auxiliaryProvider: auxiliaryProviderField?.value || "grsai",
1166
+ auxiliaryModel: auxiliaryModelField?.value.trim() || "nano-banana-2",
1040
1167
  startUrl: document.querySelector("#startUrl").value.trim(),
1041
1168
  allowedDomains: document.querySelector("#allowedDomains").value.trim(),
1042
1169
  commandCwd: document.querySelector("#commandCwd").value.trim(),
@@ -2293,7 +2420,7 @@ deleteSessionButton.addEventListener("click", () => {
2293
2420
  });
2294
2421
 
2295
2422
  providerField.addEventListener("change", () => {
2296
- if (providerField.value === "mock") {
2423
+ if (providerField.value === "mock" || providerField.value === "venice" || providerField.value === "openai" || providerField.value === "qwen") {
2297
2424
  routingModeField.value = "manual";
2298
2425
  }
2299
2426
 
@@ -2302,6 +2429,7 @@ providerField.addEventListener("change", () => {
2302
2429
  modelField.value === defaults.openai ||
2303
2430
  modelField.value === defaults.deepseek ||
2304
2431
  modelField.value === defaults.qwen ||
2432
+ modelField.value === defaults.venice ||
2305
2433
  modelField.value === defaults.mock
2306
2434
  ) {
2307
2435
  modelField.value = defaults[providerField.value] || "";
@@ -2310,7 +2438,27 @@ providerField.addEventListener("change", () => {
2310
2438
  schedulePreferenceSave();
2311
2439
  });
2312
2440
 
2441
+ modelCatalogEl?.addEventListener("click", (event) => {
2442
+ const button = event.target.closest("[data-model-id]");
2443
+ if (!button) return;
2444
+ modelField.value = button.dataset.modelId || modelField.value;
2445
+ updateRoutingHint();
2446
+ schedulePreferenceSave();
2447
+ });
2448
+
2313
2449
  modelField.addEventListener("input", updateRoutingHint);
2450
+ [routeProviderField, routeModelField, mainProviderField, mainModelField, spareProviderField, spareModelField, spareReasoningField, wrapperModelField, wrapperReasoningField, auxiliaryProviderField, auxiliaryModelField]
2451
+ .filter(Boolean)
2452
+ .forEach((field) => {
2453
+ field.addEventListener("input", () => {
2454
+ renderModelRoles();
2455
+ schedulePreferenceSave();
2456
+ });
2457
+ field.addEventListener("change", () => {
2458
+ renderModelRoles();
2459
+ schedulePreferenceSave();
2460
+ });
2461
+ });
2314
2462
  sandboxModeField.addEventListener("change", updatePackageWarning);
2315
2463
  packageInstallPolicyField.addEventListener("change", updatePackageWarning);
2316
2464
  allowWrapperToolsField.addEventListener("change", () => renderWrapperStatus());
@@ -2534,10 +2682,15 @@ async function loadConfig() {
2534
2682
  const data = await response.json();
2535
2683
  const prefs = data.preferences || {};
2536
2684
  routingPresets = data.routing?.presets || {};
2685
+ modelCatalog = data.modelCatalog || {};
2686
+ modelRoles = data.modelRoles || {};
2687
+ modelGroups = data.modelGroups || {};
2688
+ auxiliaryModelCatalog = data.auxiliaryModelCatalog || {};
2537
2689
  taskProfiles = data.taskProfiles || [];
2538
2690
  projectInfo = data.project || null;
2539
2691
  defaults.openai = data.defaults?.openai?.model || defaults.openai;
2540
2692
  defaults.qwen = data.defaults?.qwen?.model || defaults.qwen;
2693
+ defaults.venice = data.defaults?.venice?.model || defaults.venice;
2541
2694
  defaults.deepseek = routingPresets.fast?.model || data.defaults?.deepseek?.model || defaults.deepseek;
2542
2695
  defaults.mock = data.defaults?.mock?.model || defaults.mock;
2543
2696
 
@@ -2546,6 +2699,17 @@ async function loadConfig() {
2546
2699
  routingModeField.value = prefs.routingMode || "smart";
2547
2700
  providerField.value = prefs.provider || "deepseek";
2548
2701
  modelField.value = prefs.model || defaults[providerField.value] || "deepseek-v4-flash";
2702
+ if (routeProviderField) routeProviderField.value = prefs.routeProvider || modelRoles.route?.provider || "deepseek";
2703
+ if (routeModelField) routeModelField.value = prefs.routeModel || modelRoles.route?.model || "deepseek-v4-flash";
2704
+ if (mainProviderField) mainProviderField.value = prefs.mainProvider || modelRoles.main?.provider || "deepseek";
2705
+ if (mainModelField) mainModelField.value = prefs.mainModel || modelRoles.main?.model || "deepseek-v4-pro";
2706
+ if (spareProviderField) spareProviderField.value = prefs.spareProvider || modelRoles.spare?.provider || "openai";
2707
+ if (spareModelField) spareModelField.value = prefs.spareModel || modelRoles.spare?.model || "gpt-5.4";
2708
+ if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning || modelRoles.spare?.reasoning || "medium";
2709
+ if (wrapperModelField) wrapperModelField.value = prefs.wrapperModel || modelRoles.wrapper?.model || "gpt-5.5";
2710
+ if (wrapperReasoningField) wrapperReasoningField.value = prefs.wrapperReasoning || modelRoles.wrapper?.reasoning || "medium";
2711
+ if (auxiliaryProviderField) auxiliaryProviderField.value = prefs.auxiliaryProvider || modelRoles.auxiliary?.provider || "grsai";
2712
+ if (auxiliaryModelField) auxiliaryModelField.value = prefs.auxiliaryModel || modelRoles.auxiliary?.model || "nano-banana-2";
2549
2713
  renderTaskProfiles(prefs.taskProfile || "auto");
2550
2714
  document.querySelector("#startUrl").value = prefs.startUrl || "";
2551
2715
  document.querySelector("#allowedDomains").value = prefs.allowedDomains || "";
package/public/index.html CHANGED
@@ -45,16 +45,18 @@
45
45
  <div>
46
46
  <strong data-i18n="setupTitle">Provider setup</strong>
47
47
  <p class="subtle" data-i18n="setupHelp">
48
- DeepSeek/OpenAI keys are missing. Use mock mode, export an env var, or save a project-local DeepSeek key.
48
+ DeepSeek/OpenAI/Qwen/Venice keys are missing. Use mock mode, export an env var, or save a project-local key.
49
49
  </p>
50
50
  <p class="subtle" data-i18n="setupEnvHelp">
51
- Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, QWEN_API_KEY, or LLM_API_KEY. Mock mode remains available for local tests.
51
+ Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, QWEN_API_KEY, VENICE_API_KEY, or LLM_API_KEY. Mock mode remains available for local tests.
52
52
  </p>
53
53
  <p class="subtle key-links">
54
54
  <span data-i18n="setupKeyLinksLabel">Get keys:</span>
55
55
  <a href="https://platform.deepseek.com/api_keys" target="_blank" rel="noreferrer" data-i18n="deepseekKeyLink">DeepSeek API keys</a>
56
56
  <span aria-hidden="true">·</span>
57
57
  <a href="https://platform.openai.com/api-keys" target="_blank" rel="noreferrer" data-i18n="openaiKeyLink">OpenAI API keys</a>
58
+ <span aria-hidden="true">·</span>
59
+ <a href="https://venice.ai" target="_blank" rel="noreferrer">Venice API</a>
58
60
  </p>
59
61
  </div>
60
62
  <div class="grid">
@@ -64,6 +66,7 @@
64
66
  <option value="deepseek">DeepSeek</option>
65
67
  <option value="openai">OpenAI</option>
66
68
  <option value="qwen">Qwen</option>
69
+ <option value="venice">Venice</option>
67
70
  <option value="grsai">GRS AI image</option>
68
71
  </select>
69
72
  </label>
@@ -96,18 +99,92 @@
96
99
  <option value="deepseek">DeepSeek</option>
97
100
  <option value="openai">OpenAI</option>
98
101
  <option value="qwen">Qwen</option>
102
+ <option value="venice">Venice</option>
99
103
  <option value="mock" data-i18n="mockProviderOption">Mock local</option>
100
104
  </select>
101
105
  </label>
102
106
 
103
107
  <label>
104
108
  <span data-i18n="modelLabel">Model</span>
105
- <input id="model" name="model" type="text" value="deepseek-v4-flash" />
109
+ <input id="model" name="model" type="text" list="model-options" value="deepseek-v4-flash" />
110
+ <datalist id="model-options"></datalist>
106
111
  </label>
107
112
  </div>
108
113
 
109
114
  <p id="routing-hint" class="subtle route-hint"></p>
110
115
  <p id="model-route-status" class="subtle route-hint"></p>
116
+ <section class="model-catalog-panel" aria-label="Model catalog">
117
+ <div class="model-catalog-head">
118
+ <span>Route</span>
119
+ <strong id="model-route-pill">DeepSeek flash/pro</strong>
120
+ </div>
121
+ <div id="model-role-grid" class="model-role-grid"></div>
122
+ <div id="model-catalog" class="model-catalog"></div>
123
+ </section>
124
+
125
+ <section class="model-role-editor" aria-label="Model role settings">
126
+ <div class="model-role-editor-head">
127
+ <strong>Model roles</strong>
128
+ <span>Route, main, spare, wrapper, auxiliary</span>
129
+ </div>
130
+ <div class="role-editor-grid">
131
+ <label>
132
+ <span>Route</span>
133
+ <select id="routeProvider" name="routeProvider">
134
+ <option value="deepseek">DeepSeek</option>
135
+ <option value="openai">OpenAI</option>
136
+ <option value="qwen">Qwen</option>
137
+ <option value="venice">Venice</option>
138
+ <option value="mock">Mock</option>
139
+ </select>
140
+ <input id="routeModel" name="routeModel" type="text" placeholder="deepseek-v4-flash" />
141
+ </label>
142
+ <label>
143
+ <span>Main</span>
144
+ <select id="mainProvider" name="mainProvider">
145
+ <option value="deepseek">DeepSeek</option>
146
+ <option value="openai">OpenAI</option>
147
+ <option value="qwen">Qwen</option>
148
+ <option value="venice">Venice</option>
149
+ </select>
150
+ <input id="mainModel" name="mainModel" type="text" placeholder="deepseek-v4-pro" />
151
+ </label>
152
+ <label>
153
+ <span>Spare</span>
154
+ <select id="spareProvider" name="spareProvider">
155
+ <option value="openai">OpenAI</option>
156
+ <option value="deepseek">DeepSeek</option>
157
+ <option value="qwen">Qwen</option>
158
+ <option value="venice">Venice</option>
159
+ </select>
160
+ <input id="spareModel" name="spareModel" type="text" placeholder="gpt-5.4" />
161
+ <select id="spareReasoning" name="spareReasoning">
162
+ <option value="low">low</option>
163
+ <option value="medium">medium</option>
164
+ <option value="high">high</option>
165
+ <option value="xhigh">xhigh</option>
166
+ </select>
167
+ </label>
168
+ <label>
169
+ <span>Wrapper</span>
170
+ <input id="wrapperModel" name="wrapperModel" type="text" placeholder="gpt-5.5" />
171
+ <select id="wrapperReasoning" name="wrapperReasoning">
172
+ <option value="low">low</option>
173
+ <option value="medium">medium</option>
174
+ <option value="high">high</option>
175
+ <option value="xhigh">xhigh</option>
176
+ </select>
177
+ </label>
178
+ <label>
179
+ <span>Auxiliary</span>
180
+ <select id="auxiliaryProvider" name="auxiliaryProvider">
181
+ <option value="grsai">GRS AI</option>
182
+ <option value="venice">Venice image</option>
183
+ </select>
184
+ <input id="auxiliaryModel" name="auxiliaryModel" type="text" placeholder="nano-banana-2" />
185
+ </label>
186
+ </div>
187
+ </section>
111
188
 
112
189
  <label>
113
190
  <span data-i18n="taskProfileLabel">Task profile</span>