@lazyingart/agintiflow 0.18.0 → 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 +2 -0
- package/docs/model-selection.md +70 -0
- package/package.json +4 -2
- package/public/app.js +110 -3
- package/public/index.html +65 -0
- package/public/styles.css +76 -0
- package/references/model-routing-provider-design.md +32 -5
- package/scripts/smoke-model-roles.js +88 -0
- package/scripts/smoke-web-api.js +12 -0
- package/src/auxiliary-tools.js +14 -5
- package/src/cli.js +117 -2
- package/src/config.js +30 -1
- package/src/interactive-cli.js +220 -12
- package/src/model-routing.js +171 -9
- package/src/tool-wrappers.js +4 -1
- package/src/web-db.js +28 -2
- package/web.js +68 -1
package/README.md
CHANGED
|
@@ -77,6 +77,8 @@ Inside chat, type normal requests such as `write a small Python CLI app with tes
|
|
|
77
77
|
|
|
78
78
|
For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
|
|
79
79
|
|
|
80
|
+
Model choice is role-based: `/route` defaults to DeepSeek V4 Flash, `/model` or `/main` defaults to DeepSeek V4 Pro, `/spare` defaults to OpenAI GPT-5.4 medium, `/wrapper` defaults to Codex GPT-5.5 medium when enabled, and `/auxilliary` defaults to GRS AI/Nano Banana for image tools. See [docs/model-selection.md](docs/model-selection.md) and run `aginti models`.
|
|
81
|
+
|
|
80
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).
|
|
81
83
|
|
|
82
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`.
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"scripts/smoke-coding-tools.js",
|
|
53
53
|
"scripts/smoke-capabilities.js",
|
|
54
54
|
"scripts/smoke-inbox.js",
|
|
55
|
+
"scripts/smoke-model-roles.js",
|
|
55
56
|
"scripts/smoke-platform.js",
|
|
56
57
|
"scripts/smoke-skills.js",
|
|
57
58
|
"scripts/smoke-tmux-tools.js",
|
|
@@ -78,11 +79,12 @@
|
|
|
78
79
|
"smoke:skills": "node scripts/smoke-skills.js",
|
|
79
80
|
"smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
|
|
80
81
|
"smoke:inbox": "node scripts/smoke-inbox.js",
|
|
82
|
+
"smoke:model-roles": "node scripts/smoke-model-roles.js",
|
|
81
83
|
"smoke:platform": "node scripts/smoke-platform.js",
|
|
82
84
|
"smoke:tmux-tools": "node scripts/smoke-tmux-tools.js",
|
|
83
85
|
"smoke:web-api": "node scripts/smoke-web-api.js",
|
|
84
86
|
"real:deepseek": "node scripts/real-deepseek-capabilities.js",
|
|
85
|
-
"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",
|
|
86
88
|
"pack:dry-run": "npm pack --dry-run",
|
|
87
89
|
"smoke:capabilities": "node scripts/smoke-capabilities.js"
|
|
88
90
|
},
|
package/public/app.js
CHANGED
|
@@ -651,7 +651,19 @@ const providerField = document.querySelector("#provider");
|
|
|
651
651
|
const modelField = document.querySelector("#model");
|
|
652
652
|
const modelOptionsEl = document.querySelector("#model-options");
|
|
653
653
|
const modelCatalogEl = document.querySelector("#model-catalog");
|
|
654
|
+
const modelRoleGridEl = document.querySelector("#model-role-grid");
|
|
654
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");
|
|
655
667
|
const routingHintEl = document.querySelector("#routing-hint");
|
|
656
668
|
const modelRouteStatusEl = document.querySelector("#model-route-status");
|
|
657
669
|
const projectStatusEl = document.querySelector("#project-status");
|
|
@@ -728,6 +740,9 @@ const defaults = {
|
|
|
728
740
|
let currentLanguage = "en";
|
|
729
741
|
let routingPresets = {};
|
|
730
742
|
let modelCatalog = {};
|
|
743
|
+
let modelRoles = {};
|
|
744
|
+
let modelGroups = {};
|
|
745
|
+
let auxiliaryModelCatalog = {};
|
|
731
746
|
let taskProfiles = [];
|
|
732
747
|
let projectInfo = null;
|
|
733
748
|
let currentSessionId = "";
|
|
@@ -970,6 +985,50 @@ function providerModelOptions(provider = providerField.value) {
|
|
|
970
985
|
return modelCatalog[provider] || [];
|
|
971
986
|
}
|
|
972
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
|
+
|
|
973
1032
|
function renderModelOptions() {
|
|
974
1033
|
const provider = providerField.value || "deepseek";
|
|
975
1034
|
const options = providerModelOptions(provider);
|
|
@@ -981,11 +1040,12 @@ function renderModelOptions() {
|
|
|
981
1040
|
if (modelRoutePillEl) {
|
|
982
1041
|
const mode = routingModeField.value || "smart";
|
|
983
1042
|
const primary = modelField.value.trim() || defaults[provider] || "";
|
|
984
|
-
const
|
|
1043
|
+
const routeLabel = `${routeProviderField?.value || "deepseek"}/${routeModelField?.value || "deepseek-v4-flash"}`;
|
|
1044
|
+
const mainLabel = `${mainProviderField?.value || "deepseek"}/${mainModelField?.value || "deepseek-v4-pro"}`;
|
|
985
1045
|
modelRoutePillEl.textContent =
|
|
986
1046
|
mode === "manual"
|
|
987
1047
|
? `${provider} · ${primary}`
|
|
988
|
-
:
|
|
1048
|
+
: `${mode} · route ${routeLabel} · main ${mainLabel}`;
|
|
989
1049
|
}
|
|
990
1050
|
if (!modelCatalogEl) return;
|
|
991
1051
|
if (options.length === 0) {
|
|
@@ -1002,6 +1062,7 @@ function renderModelOptions() {
|
|
|
1002
1062
|
`
|
|
1003
1063
|
)
|
|
1004
1064
|
.join("");
|
|
1065
|
+
renderModelRoles();
|
|
1005
1066
|
}
|
|
1006
1067
|
|
|
1007
1068
|
function updateRoutingHint() {
|
|
@@ -1015,7 +1076,16 @@ function updateRoutingHint() {
|
|
|
1015
1076
|
routingHintEl.textContent = t(hintKey);
|
|
1016
1077
|
|
|
1017
1078
|
if (mode !== "manual" && providerField.value === "deepseek") {
|
|
1018
|
-
const preset =
|
|
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
|
+
};
|
|
1019
1089
|
if (preset) {
|
|
1020
1090
|
providerField.value = preset.provider === "deepseek" ? "deepseek" : providerField.value;
|
|
1021
1091
|
modelField.value = preset.model || modelField.value;
|
|
@@ -1083,6 +1153,17 @@ function formPayload() {
|
|
|
1083
1153
|
routingMode: routingModeField.value,
|
|
1084
1154
|
provider: providerField.value,
|
|
1085
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",
|
|
1086
1167
|
startUrl: document.querySelector("#startUrl").value.trim(),
|
|
1087
1168
|
allowedDomains: document.querySelector("#allowedDomains").value.trim(),
|
|
1088
1169
|
commandCwd: document.querySelector("#commandCwd").value.trim(),
|
|
@@ -2366,6 +2447,18 @@ modelCatalogEl?.addEventListener("click", (event) => {
|
|
|
2366
2447
|
});
|
|
2367
2448
|
|
|
2368
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
|
+
});
|
|
2369
2462
|
sandboxModeField.addEventListener("change", updatePackageWarning);
|
|
2370
2463
|
packageInstallPolicyField.addEventListener("change", updatePackageWarning);
|
|
2371
2464
|
allowWrapperToolsField.addEventListener("change", () => renderWrapperStatus());
|
|
@@ -2590,6 +2683,9 @@ async function loadConfig() {
|
|
|
2590
2683
|
const prefs = data.preferences || {};
|
|
2591
2684
|
routingPresets = data.routing?.presets || {};
|
|
2592
2685
|
modelCatalog = data.modelCatalog || {};
|
|
2686
|
+
modelRoles = data.modelRoles || {};
|
|
2687
|
+
modelGroups = data.modelGroups || {};
|
|
2688
|
+
auxiliaryModelCatalog = data.auxiliaryModelCatalog || {};
|
|
2593
2689
|
taskProfiles = data.taskProfiles || [];
|
|
2594
2690
|
projectInfo = data.project || null;
|
|
2595
2691
|
defaults.openai = data.defaults?.openai?.model || defaults.openai;
|
|
@@ -2603,6 +2699,17 @@ async function loadConfig() {
|
|
|
2603
2699
|
routingModeField.value = prefs.routingMode || "smart";
|
|
2604
2700
|
providerField.value = prefs.provider || "deepseek";
|
|
2605
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";
|
|
2606
2713
|
renderTaskProfiles(prefs.taskProfile || "auto");
|
|
2607
2714
|
document.querySelector("#startUrl").value = prefs.startUrl || "";
|
|
2608
2715
|
document.querySelector("#allowedDomains").value = prefs.allowedDomains || "";
|
package/public/index.html
CHANGED
|
@@ -118,9 +118,74 @@
|
|
|
118
118
|
<span>Route</span>
|
|
119
119
|
<strong id="model-route-pill">DeepSeek flash/pro</strong>
|
|
120
120
|
</div>
|
|
121
|
+
<div id="model-role-grid" class="model-role-grid"></div>
|
|
121
122
|
<div id="model-catalog" class="model-catalog"></div>
|
|
122
123
|
</section>
|
|
123
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>
|
|
188
|
+
|
|
124
189
|
<label>
|
|
125
190
|
<span data-i18n="taskProfileLabel">Task profile</span>
|
|
126
191
|
<select id="taskProfile" name="taskProfile">
|
package/public/styles.css
CHANGED
|
@@ -157,6 +157,82 @@ h1 {
|
|
|
157
157
|
gap: 8px;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
.model-role-grid {
|
|
161
|
+
display: grid;
|
|
162
|
+
gap: 8px;
|
|
163
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.model-role-card {
|
|
167
|
+
min-width: 0;
|
|
168
|
+
padding: 10px;
|
|
169
|
+
border: 1px solid rgba(15, 118, 110, 0.18);
|
|
170
|
+
border-radius: 14px;
|
|
171
|
+
background: rgba(255, 255, 255, 0.72);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.model-role-card strong,
|
|
175
|
+
.model-role-card span {
|
|
176
|
+
display: block;
|
|
177
|
+
overflow: hidden;
|
|
178
|
+
text-overflow: ellipsis;
|
|
179
|
+
white-space: nowrap;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.model-role-card strong {
|
|
183
|
+
color: #0f766e;
|
|
184
|
+
font-size: 0.84rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.model-role-card span {
|
|
188
|
+
color: var(--muted);
|
|
189
|
+
font-size: 0.76rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.model-role-editor {
|
|
193
|
+
display: grid;
|
|
194
|
+
gap: 10px;
|
|
195
|
+
margin-top: -2px;
|
|
196
|
+
padding: 12px;
|
|
197
|
+
border: 1px solid rgba(29, 78, 216, 0.14);
|
|
198
|
+
border-radius: 16px;
|
|
199
|
+
background:
|
|
200
|
+
linear-gradient(135deg, rgba(219, 234, 254, 0.62), rgba(255, 251, 235, 0.82)),
|
|
201
|
+
rgba(255, 255, 255, 0.82);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.model-role-editor-head {
|
|
205
|
+
display: flex;
|
|
206
|
+
justify-content: space-between;
|
|
207
|
+
gap: 10px;
|
|
208
|
+
color: var(--muted);
|
|
209
|
+
font-size: 0.78rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.model-role-editor-head strong {
|
|
213
|
+
color: #1d4ed8;
|
|
214
|
+
text-transform: uppercase;
|
|
215
|
+
letter-spacing: 0.08em;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.role-editor-grid {
|
|
219
|
+
display: grid;
|
|
220
|
+
gap: 10px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.role-editor-grid label {
|
|
224
|
+
display: grid;
|
|
225
|
+
gap: 6px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.role-editor-grid label > span {
|
|
229
|
+
color: var(--muted);
|
|
230
|
+
font-size: 0.74rem;
|
|
231
|
+
font-weight: 800;
|
|
232
|
+
letter-spacing: 0.07em;
|
|
233
|
+
text-transform: uppercase;
|
|
234
|
+
}
|
|
235
|
+
|
|
160
236
|
.model-chip {
|
|
161
237
|
display: grid;
|
|
162
238
|
gap: 2px;
|
|
@@ -14,13 +14,40 @@ DeepSeek remains the default smart-route backend:
|
|
|
14
14
|
|
|
15
15
|
If the user selects OpenAI, Qwen, or Venice as the provider, AgInTiFlow treats that as the primary manual provider even when the UI still shows a smart policy. This makes provider selection predictable while preserving DeepSeek as the default.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Role-Based Model Sections
|
|
18
18
|
|
|
19
|
-
The
|
|
19
|
+
The UI should be role-first rather than provider-first:
|
|
20
20
|
|
|
21
|
-
- **Route:**
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
21
|
+
- **Route model (`/route`):** fast planner and triage model. Default `deepseek/deepseek-v4-flash`.
|
|
22
|
+
- **Main model (`/model` or `/main`):** complex executor. Default `deepseek/deepseek-v4-pro`.
|
|
23
|
+
- **Spare model (`/spare`):** fallback/cross-check model. Default `openai/gpt-5.4` with `medium` reasoning.
|
|
24
|
+
- **Wrapper (`/wrapper`):** external coding assistant. Default Codex with `gpt-5.5` medium reasoning, disabled unless wrapper tools are enabled.
|
|
25
|
+
- **Auxiliary (`/auxilliary`):** media/image tools. Default `grsai/nano-banana-2`; Venice image models are optional.
|
|
26
|
+
|
|
27
|
+
This keeps the mental model stable: providers are supply, roles are policy.
|
|
28
|
+
|
|
29
|
+
## Current Commands
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
aginti models
|
|
33
|
+
aginti --list-models
|
|
34
|
+
aginti --list-routes
|
|
35
|
+
aginti --route-model deepseek-v4-flash --main-model deepseek-v4-pro "fix this project"
|
|
36
|
+
aginti --spare-provider openai --spare-model gpt-5.4 --spare-reasoning medium "review this patch"
|
|
37
|
+
aginti --allow-wrappers --wrapper codex --wrapper-model gpt-5.5 "use Codex as a helper"
|
|
38
|
+
aginti --image --aux-provider venice --aux-model gpt-image-2 "generate an image"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Interactive equivalents:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
/models
|
|
45
|
+
/route deepseek/deepseek-v4-flash
|
|
46
|
+
/model deepseek/deepseek-v4-pro
|
|
47
|
+
/spare openai/gpt-5.4 medium
|
|
48
|
+
/wrapper codex gpt-5.5 medium
|
|
49
|
+
/auxilliary model grsai/nano-banana-2
|
|
50
|
+
```
|
|
24
51
|
|
|
25
52
|
## OpenAI Model Reference
|
|
26
53
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
AUXILIARY_MODEL_CATALOG,
|
|
7
|
+
MODEL_PROVIDER_GROUPS,
|
|
8
|
+
getModelRoleDefaults,
|
|
9
|
+
modelsForProviderGroup,
|
|
10
|
+
selectModelRoute,
|
|
11
|
+
} from "../src/model-routing.js";
|
|
12
|
+
|
|
13
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
+
|
|
15
|
+
function assert(condition, message) {
|
|
16
|
+
if (!condition) throw new Error(message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runCli(args) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const child = spawn(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), ...args], {
|
|
22
|
+
cwd: repoRoot,
|
|
23
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
24
|
+
env: { ...process.env },
|
|
25
|
+
});
|
|
26
|
+
let stdout = "";
|
|
27
|
+
let stderr = "";
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
child.kill("SIGTERM");
|
|
30
|
+
reject(new Error("model role CLI smoke timed out"));
|
|
31
|
+
}, 12000);
|
|
32
|
+
child.stdout.on("data", (chunk) => {
|
|
33
|
+
stdout += String(chunk);
|
|
34
|
+
});
|
|
35
|
+
child.stderr.on("data", (chunk) => {
|
|
36
|
+
stderr += String(chunk);
|
|
37
|
+
});
|
|
38
|
+
child.on("error", (error) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
reject(error);
|
|
41
|
+
});
|
|
42
|
+
child.on("close", (code) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
if (code === 0) resolve(stdout);
|
|
45
|
+
else reject(new Error(`model role CLI smoke failed ${code}\n${stdout}\n${stderr}`));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const roles = getModelRoleDefaults();
|
|
51
|
+
assert(roles.route.provider === "deepseek", "route provider default should be deepseek");
|
|
52
|
+
assert(roles.route.model === "deepseek-v4-flash", "route model default should be deepseek-v4-flash");
|
|
53
|
+
assert(roles.main.model === "deepseek-v4-pro", "main model default should be deepseek-v4-pro");
|
|
54
|
+
assert(roles.spare.provider === "openai" && roles.spare.model === "gpt-5.4", "spare model default should be OpenAI GPT-5.4");
|
|
55
|
+
assert(roles.wrapper.provider === "codex" && roles.wrapper.model === "gpt-5.5", "wrapper default should be Codex GPT-5.5");
|
|
56
|
+
assert(roles.auxiliary.provider === "grsai" && roles.auxiliary.model === "nano-banana-2", "auxiliary default should be GRS AI Nano Banana");
|
|
57
|
+
|
|
58
|
+
const complexRoute = selectModelRoute({
|
|
59
|
+
routingMode: "complex",
|
|
60
|
+
provider: "deepseek",
|
|
61
|
+
mainModel: "deepseek-v4-pro",
|
|
62
|
+
});
|
|
63
|
+
assert(complexRoute.model === "deepseek-v4-pro", "complex route did not use main model override");
|
|
64
|
+
|
|
65
|
+
const fastRoute = selectModelRoute({
|
|
66
|
+
routingMode: "fast",
|
|
67
|
+
provider: "deepseek",
|
|
68
|
+
routeModel: "deepseek-v4-flash",
|
|
69
|
+
});
|
|
70
|
+
assert(fastRoute.model === "deepseek-v4-flash", "fast route did not use route model override");
|
|
71
|
+
|
|
72
|
+
assert(MODEL_PROVIDER_GROUPS["venice-gpt"].provider === "venice", "venice-gpt group missing");
|
|
73
|
+
assert(modelsForProviderGroup("venice-gemma").some((item) => item.id === "gemma-4-uncensored"), "venice-gemma bucket missing Gemma");
|
|
74
|
+
assert(AUXILIARY_MODEL_CATALOG["venice-image"].some((item) => item.id === "gpt-image-2"), "Venice image catalog missing GPT Image 2");
|
|
75
|
+
|
|
76
|
+
const output = await runCli(["models"]);
|
|
77
|
+
assert(output.includes("/route") && output.includes("/spare") && output.includes("venice-gpt"), "aginti models output missing role details");
|
|
78
|
+
|
|
79
|
+
console.log(
|
|
80
|
+
JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
ok: true,
|
|
83
|
+
checks: ["role-defaults", "route-overrides", "provider-groups", "auxiliary-catalog", "cli-models-command"],
|
|
84
|
+
},
|
|
85
|
+
null,
|
|
86
|
+
2
|
|
87
|
+
)
|
|
88
|
+
);
|
package/scripts/smoke-web-api.js
CHANGED
|
@@ -87,6 +87,12 @@ try {
|
|
|
87
87
|
if (!config.modelCatalog?.venice?.some((model) => model.id === "venice-uncensored-1-2")) {
|
|
88
88
|
throw new Error("venice model catalog is not advertised by /api/config");
|
|
89
89
|
}
|
|
90
|
+
if (config.modelRoles?.route?.model !== "deepseek-v4-flash" || config.modelRoles?.main?.model !== "deepseek-v4-pro") {
|
|
91
|
+
throw new Error("model role defaults are not advertised by /api/config");
|
|
92
|
+
}
|
|
93
|
+
if (!config.modelGroups?.["venice-gpt"] || !config.auxiliaryModelCatalog?.["venice-image"]) {
|
|
94
|
+
throw new Error("model provider groups are not advertised by /api/config");
|
|
95
|
+
}
|
|
90
96
|
|
|
91
97
|
const keyStatus = await fetchJson("/api/keys/status");
|
|
92
98
|
if (typeof keyStatus.keyStatus?.deepseek !== "boolean") throw new Error("key status endpoint is invalid");
|
|
@@ -149,6 +155,12 @@ try {
|
|
|
149
155
|
provider: "mock",
|
|
150
156
|
routingMode: "manual",
|
|
151
157
|
model: "mock-agent",
|
|
158
|
+
routeModel: "deepseek-v4-flash",
|
|
159
|
+
mainModel: "deepseek-v4-pro",
|
|
160
|
+
spareProvider: "openai",
|
|
161
|
+
spareModel: "gpt-5.4",
|
|
162
|
+
auxiliaryProvider: "grsai",
|
|
163
|
+
auxiliaryModel: "nano-banana-2",
|
|
152
164
|
goal: "Report the current working directory with a safe command.",
|
|
153
165
|
commandCwd: runtimeDir,
|
|
154
166
|
sandboxMode: "host",
|
package/src/auxiliary-tools.js
CHANGED
|
@@ -252,10 +252,16 @@ function resultUrls(payload) {
|
|
|
252
252
|
|
|
253
253
|
function normalizeImageProvider(value = "") {
|
|
254
254
|
const normalized = String(value || "").trim().toLowerCase();
|
|
255
|
-
if (["venice", "venice-ai", "veniceai"].includes(normalized)) return "venice";
|
|
255
|
+
if (["venice", "venice-image", "venice-ai", "veniceai"].includes(normalized)) return "venice";
|
|
256
256
|
return "grsai";
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
function defaultImageProvider(config = {}) {
|
|
260
|
+
return normalizeImageProvider(
|
|
261
|
+
config.auxiliaryProvider || process.env.AGINTI_AUX_PROVIDER || process.env.VENICE_IMAGE_PROVIDER || process.env.GRSAI_IMAGE_PROVIDER || ""
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
259
265
|
function veniceBaseUrl(value = "") {
|
|
260
266
|
return String(value || process.env.VENICE_API_BASE || process.env.VENICE_BASE_URL || DEFAULT_VENICE_BASE)
|
|
261
267
|
.trim()
|
|
@@ -268,7 +274,9 @@ async function generateVeniceImages({ prompt, args, target, outputStem, manifest
|
|
|
268
274
|
throw new Error("Missing VENICE_API_KEY. Run `aginti login venice` or `aginti keys set venice --stdin`.");
|
|
269
275
|
}
|
|
270
276
|
|
|
271
|
-
const model =
|
|
277
|
+
const model =
|
|
278
|
+
String(args.model || process.env.AGINTI_AUX_MODEL || process.env.VENICE_IMAGE_MODEL || DEFAULT_VENICE_IMAGE_MODEL).trim() ||
|
|
279
|
+
DEFAULT_VENICE_IMAGE_MODEL;
|
|
272
280
|
const format = String(args.format || "png").trim().toLowerCase() === "webp" ? "webp" : "png";
|
|
273
281
|
const sizing = veniceSizingForModel(model, args);
|
|
274
282
|
const payload = {
|
|
@@ -356,12 +364,13 @@ export async function generateImage(args = {}, config = {}) {
|
|
|
356
364
|
if (converted.url) references.push(converted);
|
|
357
365
|
}
|
|
358
366
|
|
|
359
|
-
const provider = normalizeImageProvider(args.provider
|
|
367
|
+
const provider = args.provider ? normalizeImageProvider(args.provider) : defaultImageProvider(config);
|
|
360
368
|
const host = provider === "venice" ? veniceBaseUrl(args.host) : String(args.host || DEFAULT_GRS_HOST).trim().replace(/\/+$/, "") || DEFAULT_GRS_HOST;
|
|
361
369
|
const model =
|
|
362
370
|
provider === "venice"
|
|
363
|
-
? String(args.model || process.env.VENICE_IMAGE_MODEL || DEFAULT_VENICE_IMAGE_MODEL).trim() ||
|
|
364
|
-
|
|
371
|
+
? String(args.model || config.auxiliaryModel || process.env.AGINTI_AUX_MODEL || process.env.VENICE_IMAGE_MODEL || DEFAULT_VENICE_IMAGE_MODEL).trim() ||
|
|
372
|
+
DEFAULT_VENICE_IMAGE_MODEL
|
|
373
|
+
: String(args.model || config.auxiliaryModel || process.env.AGINTI_AUX_MODEL || DEFAULT_IMAGE_MODEL).trim() || DEFAULT_IMAGE_MODEL;
|
|
365
374
|
const outputStem = safeStem(args.outputStem || "image");
|
|
366
375
|
const payload = {
|
|
367
376
|
model,
|