@lazyingart/agintiflow 0.20.187 → 0.20.189
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 +8 -1
- package/docs/model-selection.md +21 -0
- package/package.json +1 -1
- package/public/app.js +27 -9
- package/public/index.html +27 -2
- package/references/agintiflow-npm-publication-memory.md +22 -0
- package/references/lazyingrouter-account-login-design.md +259 -0
- package/references/openai-compatible-base-url-and-reasoning-policy.md +183 -0
- package/scripts/smoke-auth.js +44 -5
- package/scripts/smoke-model-roles.js +101 -1
- package/scripts/smoke-web-api.js +12 -0
- package/scripts/smoke-web-ui.js +1 -1
- package/src/auth-onboarding.js +15 -4
- package/src/capabilities.js +3 -1
- package/src/cli.js +26 -7
- package/src/config.js +53 -11
- package/src/guardrails.js +3 -3
- package/src/i18n.js +11 -11
- package/src/interactive-cli.js +129 -34
- package/src/json-specialist.js +2 -2
- package/src/model-client.js +53 -5
- package/src/model-routing.js +344 -10
- package/src/perception-tools.js +6 -2
- package/src/project.js +60 -7
- package/src/redaction.js +2 -1
- package/src/web-db.js +10 -1
- package/web.js +14 -5
package/README.md
CHANGED
|
@@ -85,11 +85,12 @@ aginti init --template aaps
|
|
|
85
85
|
aginti init --template supervision
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
On first interactive use, AgInTiFlow opens an auth wizard if no main model key is found. Pick DeepSeek, OpenAI, Qwen, or Venice, paste the key, and it saves to the ignored project-local `.aginti/.env` file with restricted permissions. You can rerun setup any time:
|
|
88
|
+
On first interactive use, AgInTiFlow opens an auth wizard if no main model key is found. Pick DeepSeek, OpenAI, OpenRouter, Qwen, or Venice, paste the key, and it saves to the ignored project-local `.aginti/.env` file with restricted permissions. You can rerun setup any time:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
91
|
aginti auth
|
|
92
92
|
aginti auth deepseek
|
|
93
|
+
aginti auth openrouter
|
|
93
94
|
aginti auth venice
|
|
94
95
|
aginti login grsai
|
|
95
96
|
```
|
|
@@ -99,6 +100,7 @@ Provider signup and key pages:
|
|
|
99
100
|
| Provider | Register / key page | API base URL used by AgInTiFlow |
|
|
100
101
|
| --- | --- | --- |
|
|
101
102
|
| DeepSeek | [https://platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys) | `https://api.deepseek.com` |
|
|
103
|
+
| OpenRouter | [https://openrouter.ai/settings/keys](https://openrouter.ai/settings/keys) | `https://openrouter.ai/api/v1` |
|
|
102
104
|
| Venice | [https://venice.ai/settings/api](https://venice.ai/settings/api) | `https://api.venice.ai/api/v1` |
|
|
103
105
|
| OpenAI | [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys) | `https://api.openai.com/v1` |
|
|
104
106
|
| Qwen / DashScope | [https://bailian.console.aliyun.com/](https://bailian.console.aliyun.com/) | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` |
|
|
@@ -273,6 +275,7 @@ Useful selectors:
|
|
|
273
275
|
```
|
|
274
276
|
|
|
275
277
|
Venice routes can be used for optional uncensored or less restricted creative work. DeepSeek remains the economic default for normal engineering workflows. See [docs/model-selection.md](docs/model-selection.md) and [references/venice-model-reference.md](references/venice-model-reference.md).
|
|
278
|
+
OpenRouter is available as a first-class OpenAI-compatible provider with one key and company-grouped model buckets such as OpenRouter OpenAI, Anthropic, Google, DeepSeek, Qwen, Meta, Mistral, Moonshot, and xAI.
|
|
276
279
|
|
|
277
280
|
## AAPS And Large Workflows
|
|
278
281
|
|
|
@@ -348,6 +351,9 @@ Common environment variables:
|
|
|
348
351
|
```bash
|
|
349
352
|
DEEPSEEK_API_KEY=...
|
|
350
353
|
OPENAI_API_KEY=...
|
|
354
|
+
OPENROUTER_API_KEY=...
|
|
355
|
+
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
|
356
|
+
OPENROUTER_MODEL=openrouter/auto
|
|
351
357
|
QWEN_API_KEY=...
|
|
352
358
|
VENICE_API_KEY=...
|
|
353
359
|
GRSAI_API_KEY=...
|
|
@@ -365,6 +371,7 @@ Project-local keys:
|
|
|
365
371
|
```bash
|
|
366
372
|
aginti init
|
|
367
373
|
printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
|
|
374
|
+
printf '%s' "$OPENROUTER_API_KEY" | aginti keys set openrouter --stdin
|
|
368
375
|
printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
|
|
369
376
|
```
|
|
370
377
|
|
package/docs/model-selection.md
CHANGED
|
@@ -25,6 +25,8 @@ aginti --list-routes
|
|
|
25
25
|
|
|
26
26
|
# one-shot overrides
|
|
27
27
|
aginti --route-model deepseek-v4-flash --main-model deepseek-v4-pro "fix this project"
|
|
28
|
+
aginti --provider openrouter --model openrouter/auto --routing manual "try a gateway route"
|
|
29
|
+
aginti --provider openrouter --model anthropic/claude-sonnet-4.6 --routing manual "draft a design review"
|
|
28
30
|
aginti --provider venice --model venice-uncensored-1-2 --routing manual "draft a note"
|
|
29
31
|
aginti --spare-provider openai --spare-model gpt-5.4 --spare-reasoning medium "review this design"
|
|
30
32
|
aginti --allow-wrappers --wrapper codex --wrapper-model gpt-5.5 "patch this bug"
|
|
@@ -63,6 +65,13 @@ Provider families:
|
|
|
63
65
|
| DeepSeek | `deepseek-v4-flash`, `deepseek-v4-pro` |
|
|
64
66
|
| Venice | `venice-uncensored-1-2`, `venice-uncensored`, `gemma-4-uncensored` |
|
|
65
67
|
| OpenAI | `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex`, `gpt-5.3-codex-spark`, `gpt-5.2`; each has low/medium/high/xhigh reasoning |
|
|
68
|
+
| OpenRouter | `openrouter/auto`, `openrouter/pareto-code`, `openrouter/free` |
|
|
69
|
+
| OpenRouter OpenAI | `openai/gpt-5.5`, `openai/gpt-5.4`, `openai/gpt-5.4-mini`, `openai/gpt-4o` |
|
|
70
|
+
| OpenRouter Anthropic | Claude routes such as `anthropic/claude-sonnet-4.6`, `anthropic/claude-opus-4.7`, `anthropic/claude-opus-4.8-fast` |
|
|
71
|
+
| OpenRouter Google | Gemini/Gemma routes such as `google/gemini-3.5-flash`, `google/gemini-3.1-flash-lite`, `google/gemma-4-31b-it` |
|
|
72
|
+
| OpenRouter DeepSeek | `deepseek/deepseek-v4-flash`, `deepseek/deepseek-v4-pro`, `deepseek/deepseek-r1-0528` |
|
|
73
|
+
| OpenRouter Qwen | Qwen routes such as `qwen/qwen3.7-max`, `qwen/qwen3.6-flash`, `qwen/qwen3.6-plus` |
|
|
74
|
+
| OpenRouter Meta/Mistral/Moonshot/xAI | `meta-llama/llama-4-maverick`, `mistralai/mistral-medium-3-5`, `moonshotai/kimi-k2.6`, `x-ai/grok-4.3` |
|
|
66
75
|
| Venice GPT | GPT-family Venice routes such as `openai-gpt-55`, `openai-gpt-54`, `openai-gpt-54-mini`, `openai-gpt-53-codex`, `openai-gpt-52` |
|
|
67
76
|
| Venice Gemma | Gemma instruct routes such as `google-gemma-4-31b-it`, `google-gemma-4-26b-a4b-it`, `google-gemma-3-27b-it` |
|
|
68
77
|
| Venice Claude | Claude Sonnet/Opus routes such as `claude-sonnet-4-6`, `claude-opus-4-7`, `claude-opus-4-6` |
|
|
@@ -96,6 +105,16 @@ It keeps smart routing enabled. If the Venice key is missing, run `/auth venice`
|
|
|
96
105
|
| --- | --- | --- |
|
|
97
106
|
| `deepseek` | DeepSeek | Default route/main because V4 Flash and V4 Pro are cheap and strong. |
|
|
98
107
|
| `openai` | OpenAI | Spare/frontier checks, Codex-family work, and explicit manual routes. |
|
|
108
|
+
| `openrouter` | OpenRouter | OpenAI-compatible gateway with auto/free/code routers. |
|
|
109
|
+
| `openrouter-openai` | OpenRouter | OpenAI-family models through OpenRouter. |
|
|
110
|
+
| `openrouter-anthropic` | OpenRouter | Claude-family models through OpenRouter. |
|
|
111
|
+
| `openrouter-google` | OpenRouter | Gemini/Gemma models through OpenRouter. |
|
|
112
|
+
| `openrouter-deepseek` | OpenRouter | DeepSeek models through OpenRouter. |
|
|
113
|
+
| `openrouter-qwen` | OpenRouter | Qwen models through OpenRouter. |
|
|
114
|
+
| `openrouter-meta` | OpenRouter | Meta Llama models through OpenRouter. |
|
|
115
|
+
| `openrouter-mistral` | OpenRouter | Mistral models through OpenRouter. |
|
|
116
|
+
| `openrouter-moonshot` | OpenRouter | Moonshot/Kimi models through OpenRouter. |
|
|
117
|
+
| `openrouter-xai` | OpenRouter | xAI Grok models through OpenRouter. |
|
|
99
118
|
| `qwen` | Qwen | Chinese and general-purpose OpenAI-compatible tasks. |
|
|
100
119
|
| `venice` | Venice | Venice-native text shortcuts: Venice 1.2, Venice 1.1, and Gemma 4 Uncensored. |
|
|
101
120
|
| `venice-gpt` | Venice | GPT-family models through Venice. |
|
|
@@ -118,8 +137,10 @@ Keys are set per project and stored only in ignored `.aginti/.env`:
|
|
|
118
137
|
|
|
119
138
|
```bash
|
|
120
139
|
aginti auth
|
|
140
|
+
aginti auth openrouter
|
|
121
141
|
aginti auth venice
|
|
122
142
|
aginti keys status
|
|
143
|
+
printf '%s' "$OPENROUTER_API_KEY" | aginti keys set openrouter --stdin
|
|
123
144
|
printf '%s' "$VENICE_API_KEY" | aginti keys set venice --stdin
|
|
124
145
|
```
|
|
125
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.189",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/public/app.js
CHANGED
|
@@ -711,8 +711,10 @@ const modelRoleGridEl = document.querySelector("#model-role-grid");
|
|
|
711
711
|
const modelRoutePillEl = document.querySelector("#model-route-pill");
|
|
712
712
|
const routeProviderField = document.querySelector("#routeProvider");
|
|
713
713
|
const routeModelField = document.querySelector("#routeModel");
|
|
714
|
+
const routeReasoningField = document.querySelector("#routeReasoning");
|
|
714
715
|
const mainProviderField = document.querySelector("#mainProvider");
|
|
715
716
|
const mainModelField = document.querySelector("#mainModel");
|
|
717
|
+
const mainReasoningField = document.querySelector("#mainReasoning");
|
|
716
718
|
const spareProviderField = document.querySelector("#spareProvider");
|
|
717
719
|
const spareModelField = document.querySelector("#spareModel");
|
|
718
720
|
const spareReasoningField = document.querySelector("#spareReasoning");
|
|
@@ -820,6 +822,7 @@ const ariaLabelNodes = [...document.querySelectorAll("[data-i18n-aria-label]")];
|
|
|
820
822
|
|
|
821
823
|
const defaults = {
|
|
822
824
|
openai: "gpt-5.4-mini",
|
|
825
|
+
openrouter: "openrouter/auto",
|
|
823
826
|
deepseek: "deepseek-v4-flash",
|
|
824
827
|
qwen: "qwen-plus",
|
|
825
828
|
venice: "venice-uncensored-1-2",
|
|
@@ -983,6 +986,7 @@ function renderKeyStatus(status = lastKeyStatus) {
|
|
|
983
986
|
if (!status) return;
|
|
984
987
|
const providers = [
|
|
985
988
|
["OpenAI", status.openai],
|
|
989
|
+
["OpenRouter", status.openrouter],
|
|
986
990
|
["DeepSeek", status.deepseek],
|
|
987
991
|
["Qwen", status.qwen],
|
|
988
992
|
["Venice", status.venice],
|
|
@@ -1009,7 +1013,7 @@ function renderKeyStatus(status = lastKeyStatus) {
|
|
|
1009
1013
|
})
|
|
1010
1014
|
);
|
|
1011
1015
|
}
|
|
1012
|
-
if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.deepseek || status.qwen || status.venice);
|
|
1016
|
+
if (setupCardEl) setupCardEl.hidden = Boolean(status.openai || status.openrouter || status.deepseek || status.qwen || status.venice);
|
|
1013
1017
|
}
|
|
1014
1018
|
|
|
1015
1019
|
function renderProjectStatus(info = projectInfo) {
|
|
@@ -1611,6 +1615,7 @@ function fieldValue(field) {
|
|
|
1611
1615
|
const providerLabels = {
|
|
1612
1616
|
deepseek: "DeepSeek",
|
|
1613
1617
|
openai: "OpenAI",
|
|
1618
|
+
openrouter: "OpenRouter",
|
|
1614
1619
|
qwen: "Qwen",
|
|
1615
1620
|
venice: "Venice",
|
|
1616
1621
|
mock: "Mock local",
|
|
@@ -1634,14 +1639,14 @@ function setChoiceOptions(select, options, selectedValue = "", fallbackValue = "
|
|
|
1634
1639
|
}
|
|
1635
1640
|
|
|
1636
1641
|
function configuredTextProviders({ includeMock = true } = {}) {
|
|
1637
|
-
const base = ["deepseek", "openai", "qwen", "venice"];
|
|
1642
|
+
const base = ["deepseek", "openai", "openrouter", "qwen", "venice"];
|
|
1638
1643
|
if (includeMock) base.push("mock");
|
|
1639
1644
|
if (Object.keys(modelCatalog || {}).length === 0) return base;
|
|
1640
1645
|
return base.filter((provider) => provider === "mock" || Array.isArray(modelCatalog[provider]));
|
|
1641
1646
|
}
|
|
1642
1647
|
|
|
1643
1648
|
function refreshProviderDropdowns() {
|
|
1644
|
-
setChoiceOptions(setupProviderField, ["deepseek", "openai", "qwen", "venice", "grsai"], setupProviderField?.value || "deepseek", "deepseek");
|
|
1649
|
+
setChoiceOptions(setupProviderField, ["deepseek", "openai", "openrouter", "qwen", "venice", "grsai"], setupProviderField?.value || "deepseek", "deepseek");
|
|
1645
1650
|
setChoiceOptions(providerField, configuredTextProviders({ includeMock: true }), providerField?.value || "deepseek", "deepseek");
|
|
1646
1651
|
setChoiceOptions(routeProviderField, configuredTextProviders({ includeMock: true }), routeProviderField?.value || "deepseek", "deepseek");
|
|
1647
1652
|
setChoiceOptions(mainProviderField, configuredTextProviders({ includeMock: false }), mainProviderField?.value || "deepseek", "deepseek");
|
|
@@ -1856,17 +1861,19 @@ function renderModelRoles() {
|
|
|
1856
1861
|
...(modelRoles.route || {}),
|
|
1857
1862
|
provider: routeProviderField?.value || modelRoles.route?.provider || "deepseek",
|
|
1858
1863
|
model: routeModelField?.value || modelRoles.route?.model || "deepseek-v4-flash",
|
|
1864
|
+
reasoning: routeReasoningField?.value ?? modelRoles.route?.reasoning ?? "",
|
|
1859
1865
|
},
|
|
1860
1866
|
main: {
|
|
1861
1867
|
...(modelRoles.main || {}),
|
|
1862
1868
|
provider: mainProviderField?.value || modelRoles.main?.provider || "deepseek",
|
|
1863
1869
|
model: mainModelField?.value || modelRoles.main?.model || "deepseek-v4-pro",
|
|
1870
|
+
reasoning: mainReasoningField?.value ?? modelRoles.main?.reasoning ?? "",
|
|
1864
1871
|
},
|
|
1865
1872
|
spare: {
|
|
1866
1873
|
...(modelRoles.spare || {}),
|
|
1867
1874
|
provider: spareProviderField?.value || modelRoles.spare?.provider || "openai",
|
|
1868
1875
|
model: spareModelField?.value || modelRoles.spare?.model || "gpt-5.4",
|
|
1869
|
-
reasoning: spareReasoningField?.value
|
|
1876
|
+
reasoning: spareReasoningField?.value ?? modelRoles.spare?.reasoning ?? "medium",
|
|
1870
1877
|
},
|
|
1871
1878
|
wrapper: {
|
|
1872
1879
|
...(modelRoles.wrapper || {}),
|
|
@@ -1882,7 +1889,7 @@ function renderModelRoles() {
|
|
|
1882
1889
|
};
|
|
1883
1890
|
modelRoleGridEl.innerHTML = Object.values(roles)
|
|
1884
1891
|
.map((role) => {
|
|
1885
|
-
const reasoning = role.reasoning ? ` · ${role.reasoning}` : "";
|
|
1892
|
+
const reasoning = role.reasoning ? ` · ${role.reasoning}` : role.reasoning === "" ? " · Provider default" : "";
|
|
1886
1893
|
return `
|
|
1887
1894
|
<div class="model-role-card">
|
|
1888
1895
|
<strong>${escapeHtml(role.label || role.id)}</strong>
|
|
@@ -2074,11 +2081,13 @@ function formPayload() {
|
|
|
2074
2081
|
model: fieldValue(modelField),
|
|
2075
2082
|
routeProvider: routeProviderField?.value || "deepseek",
|
|
2076
2083
|
routeModel: fieldValue(routeModelField) || "deepseek-v4-flash",
|
|
2084
|
+
routeReasoning: routeReasoningField?.value ?? "",
|
|
2077
2085
|
mainProvider: mainProviderField?.value || "deepseek",
|
|
2078
2086
|
mainModel: fieldValue(mainModelField) || "deepseek-v4-pro",
|
|
2087
|
+
mainReasoning: mainReasoningField?.value ?? "",
|
|
2079
2088
|
spareProvider: spareProviderField?.value || "openai",
|
|
2080
2089
|
spareModel: fieldValue(spareModelField) || "gpt-5.4",
|
|
2081
|
-
spareReasoning: spareReasoningField?.value
|
|
2090
|
+
spareReasoning: spareReasoningField?.value ?? "medium",
|
|
2082
2091
|
wrapperModel: fieldValue(wrapperModelField) || "gpt-5.5",
|
|
2083
2092
|
wrapperReasoning: wrapperReasoningField?.value || "medium",
|
|
2084
2093
|
auxiliaryProvider: auxiliaryProviderField?.value || "grsai",
|
|
@@ -3874,7 +3883,13 @@ deleteSessionButton.addEventListener("click", () => {
|
|
|
3874
3883
|
});
|
|
3875
3884
|
|
|
3876
3885
|
providerField.addEventListener("change", () => {
|
|
3877
|
-
if (
|
|
3886
|
+
if (
|
|
3887
|
+
providerField.value === "mock" ||
|
|
3888
|
+
providerField.value === "venice" ||
|
|
3889
|
+
providerField.value === "openai" ||
|
|
3890
|
+
providerField.value === "openrouter" ||
|
|
3891
|
+
providerField.value === "qwen"
|
|
3892
|
+
) {
|
|
3878
3893
|
routingModeField.value = "manual";
|
|
3879
3894
|
}
|
|
3880
3895
|
|
|
@@ -3909,7 +3924,7 @@ veniceModeToggle?.addEventListener("change", () => {
|
|
|
3909
3924
|
});
|
|
3910
3925
|
|
|
3911
3926
|
modelField.addEventListener("change", updateRoutingHint);
|
|
3912
|
-
[routeProviderField, routeModelField, mainProviderField, mainModelField, spareProviderField, spareModelField, spareReasoningField, wrapperModelField, wrapperReasoningField, auxiliaryProviderField, auxiliaryModelField]
|
|
3927
|
+
[routeProviderField, routeModelField, routeReasoningField, mainProviderField, mainModelField, mainReasoningField, spareProviderField, spareModelField, spareReasoningField, wrapperModelField, wrapperReasoningField, auxiliaryProviderField, auxiliaryModelField]
|
|
3913
3928
|
.filter(Boolean)
|
|
3914
3929
|
.forEach((field) => {
|
|
3915
3930
|
field.addEventListener("input", () => {
|
|
@@ -4208,6 +4223,7 @@ async function loadConfig() {
|
|
|
4208
4223
|
taskProfiles = data.taskProfiles || [];
|
|
4209
4224
|
projectInfo = data.project || null;
|
|
4210
4225
|
defaults.openai = data.defaults?.openai?.model || defaults.openai;
|
|
4226
|
+
defaults.openrouter = data.defaults?.openrouter?.model || defaults.openrouter;
|
|
4211
4227
|
defaults.qwen = data.defaults?.qwen?.model || defaults.qwen;
|
|
4212
4228
|
defaults.venice = data.defaults?.venice?.model || defaults.venice;
|
|
4213
4229
|
defaults.deepseek = routingPresets.fast?.model || data.defaults?.deepseek?.model || defaults.deepseek;
|
|
@@ -4220,9 +4236,11 @@ async function loadConfig() {
|
|
|
4220
4236
|
routingModeField.value = prefs.routingMode || "smart";
|
|
4221
4237
|
providerField.value = prefs.provider || "deepseek";
|
|
4222
4238
|
if (routeProviderField) routeProviderField.value = prefs.routeProvider || modelRoles.route?.provider || "deepseek";
|
|
4239
|
+
if (routeReasoningField) routeReasoningField.value = prefs.routeReasoning ?? modelRoles.route?.reasoning ?? "";
|
|
4223
4240
|
if (mainProviderField) mainProviderField.value = prefs.mainProvider || modelRoles.main?.provider || "deepseek";
|
|
4241
|
+
if (mainReasoningField) mainReasoningField.value = prefs.mainReasoning ?? modelRoles.main?.reasoning ?? "";
|
|
4224
4242
|
if (spareProviderField) spareProviderField.value = prefs.spareProvider || modelRoles.spare?.provider || "openai";
|
|
4225
|
-
if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning
|
|
4243
|
+
if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning ?? modelRoles.spare?.reasoning ?? "medium";
|
|
4226
4244
|
if (wrapperReasoningField) wrapperReasoningField.value = prefs.wrapperReasoning || modelRoles.wrapper?.reasoning || "medium";
|
|
4227
4245
|
if (auxiliaryProviderField) auxiliaryProviderField.value = prefs.auxiliaryProvider || modelRoles.auxiliary?.provider || "grsai";
|
|
4228
4246
|
setSelectOptions(
|
package/public/index.html
CHANGED
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
<div>
|
|
66
66
|
<strong data-i18n="setupTitle">Provider setup</strong>
|
|
67
67
|
<p class="subtle" data-i18n="setupHelp">
|
|
68
|
-
DeepSeek/OpenAI/Qwen/Venice keys are missing. Use mock mode, export an env var, or save a project-local key.
|
|
68
|
+
DeepSeek/OpenAI/OpenRouter/Qwen/Venice keys are missing. Use mock mode, export an env var, or save a project-local key.
|
|
69
69
|
</p>
|
|
70
70
|
<p class="subtle" data-i18n="setupEnvHelp">
|
|
71
|
-
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.
|
|
71
|
+
Env vars: DEEPSEEK_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, QWEN_API_KEY, VENICE_API_KEY, or LLM_API_KEY. Mock mode remains available for local tests.
|
|
72
72
|
</p>
|
|
73
73
|
<p class="subtle key-links">
|
|
74
74
|
<span data-i18n="setupKeyLinksLabel">Get keys:</span>
|
|
@@ -76,6 +76,8 @@
|
|
|
76
76
|
<span aria-hidden="true">·</span>
|
|
77
77
|
<a href="https://platform.openai.com/api-keys" target="_blank" rel="noreferrer" data-i18n="openaiKeyLink">OpenAI API keys</a>
|
|
78
78
|
<span aria-hidden="true">·</span>
|
|
79
|
+
<a href="https://openrouter.ai/settings/keys" target="_blank" rel="noreferrer">OpenRouter API keys</a>
|
|
80
|
+
<span aria-hidden="true">·</span>
|
|
79
81
|
<a href="https://venice.ai" target="_blank" rel="noreferrer">Venice API</a>
|
|
80
82
|
</p>
|
|
81
83
|
</div>
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
<select id="setup-provider">
|
|
86
88
|
<option value="deepseek">DeepSeek</option>
|
|
87
89
|
<option value="openai">OpenAI</option>
|
|
90
|
+
<option value="openrouter">OpenRouter</option>
|
|
88
91
|
<option value="qwen">Qwen</option>
|
|
89
92
|
<option value="venice">Venice</option>
|
|
90
93
|
<option value="grsai">GRS AI image</option>
|
|
@@ -132,6 +135,7 @@
|
|
|
132
135
|
<select id="provider" name="provider">
|
|
133
136
|
<option value="deepseek">DeepSeek</option>
|
|
134
137
|
<option value="openai">OpenAI</option>
|
|
138
|
+
<option value="openrouter">OpenRouter</option>
|
|
135
139
|
<option value="qwen">Qwen</option>
|
|
136
140
|
<option value="venice">Venice</option>
|
|
137
141
|
<option value="mock" data-i18n="mockProviderOption">Mock local</option>
|
|
@@ -414,6 +418,7 @@
|
|
|
414
418
|
<select id="routeProvider" name="routeProvider">
|
|
415
419
|
<option value="deepseek">DeepSeek</option>
|
|
416
420
|
<option value="openai">OpenAI</option>
|
|
421
|
+
<option value="openrouter">OpenRouter</option>
|
|
417
422
|
<option value="qwen">Qwen</option>
|
|
418
423
|
<option value="venice">Venice</option>
|
|
419
424
|
<option value="mock">Mock</option>
|
|
@@ -421,23 +426,41 @@
|
|
|
421
426
|
<select id="routeModel" name="routeModel">
|
|
422
427
|
<option value="deepseek-v4-flash">DeepSeek v4 flash</option>
|
|
423
428
|
</select>
|
|
429
|
+
<select id="routeReasoning" name="routeReasoning">
|
|
430
|
+
<option value="">Provider default</option>
|
|
431
|
+
<option value="minimal">minimal</option>
|
|
432
|
+
<option value="low">low</option>
|
|
433
|
+
<option value="medium">medium</option>
|
|
434
|
+
<option value="high">high</option>
|
|
435
|
+
<option value="xhigh">xhigh</option>
|
|
436
|
+
</select>
|
|
424
437
|
</label>
|
|
425
438
|
<label>
|
|
426
439
|
<span>Main</span>
|
|
427
440
|
<select id="mainProvider" name="mainProvider">
|
|
428
441
|
<option value="deepseek">DeepSeek</option>
|
|
429
442
|
<option value="openai">OpenAI</option>
|
|
443
|
+
<option value="openrouter">OpenRouter</option>
|
|
430
444
|
<option value="qwen">Qwen</option>
|
|
431
445
|
<option value="venice">Venice</option>
|
|
432
446
|
</select>
|
|
433
447
|
<select id="mainModel" name="mainModel">
|
|
434
448
|
<option value="deepseek-v4-pro">DeepSeek v4 pro</option>
|
|
435
449
|
</select>
|
|
450
|
+
<select id="mainReasoning" name="mainReasoning">
|
|
451
|
+
<option value="">Provider default</option>
|
|
452
|
+
<option value="minimal">minimal</option>
|
|
453
|
+
<option value="low">low</option>
|
|
454
|
+
<option value="medium">medium</option>
|
|
455
|
+
<option value="high">high</option>
|
|
456
|
+
<option value="xhigh">xhigh</option>
|
|
457
|
+
</select>
|
|
436
458
|
</label>
|
|
437
459
|
<label>
|
|
438
460
|
<span>Spare</span>
|
|
439
461
|
<select id="spareProvider" name="spareProvider">
|
|
440
462
|
<option value="openai">OpenAI</option>
|
|
463
|
+
<option value="openrouter">OpenRouter</option>
|
|
441
464
|
<option value="deepseek">DeepSeek</option>
|
|
442
465
|
<option value="qwen">Qwen</option>
|
|
443
466
|
<option value="venice">Venice</option>
|
|
@@ -446,6 +469,8 @@
|
|
|
446
469
|
<option value="gpt-5.4">GPT-5.4</option>
|
|
447
470
|
</select>
|
|
448
471
|
<select id="spareReasoning" name="spareReasoning">
|
|
472
|
+
<option value="">Provider default</option>
|
|
473
|
+
<option value="minimal">minimal</option>
|
|
449
474
|
<option value="low">low</option>
|
|
450
475
|
<option value="medium">medium</option>
|
|
451
476
|
<option value="high">high</option>
|
|
@@ -10,6 +10,28 @@ This note records the working publication route for AgInTiFlow so future release
|
|
|
10
10
|
|
|
11
11
|
Latest verified release:
|
|
12
12
|
|
|
13
|
+
- Version: `0.20.188`
|
|
14
|
+
- Commit: `939e2ec`
|
|
15
|
+
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26705729544`
|
|
16
|
+
- Workflow result: success
|
|
17
|
+
- npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.188`.
|
|
18
|
+
- Installed verification: `npm install -g @lazyingart/agintiflow@0.20.188` then `aginti --version` returned `0.20.188`.
|
|
19
|
+
- Webapp verification: `aginti webapp restart --port 3210` then `curl -fsS http://127.0.0.1:3210/health` returned `version":"0.20.188"` from the global npm package path.
|
|
20
|
+
- OpenRouter verification: `/api/config` reports the OpenRouter default provider as `openrouter/auto` with base URL `https://openrouter.ai/api/v1`; `aginti keys status` reports `openrouter=missing` because no local OpenRouter key was persisted during release verification.
|
|
21
|
+
|
|
22
|
+
Previous verified release:
|
|
23
|
+
|
|
24
|
+
- Version: `0.20.187`
|
|
25
|
+
- Commit: `9339b2e`
|
|
26
|
+
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26701125864`
|
|
27
|
+
- Workflow result: success
|
|
28
|
+
- npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.187`.
|
|
29
|
+
- Installed verification: `npm install -g @lazyingart/agintiflow@0.20.187` then `aginti --version` returned `0.20.187`.
|
|
30
|
+
- Webapp verification: `aginti webapp restart --port 3210` then `curl -fsS http://127.0.0.1:3210/health` returned `version":"0.20.187"` from the global npm package path.
|
|
31
|
+
- Workspace explorer verification: `POST /api/workspace/snapshot` returned no `.aginti/`, `.aginti-work/`, `.sessions/`, or `.env` entries for the project root.
|
|
32
|
+
|
|
33
|
+
Previous verified release:
|
|
34
|
+
|
|
13
35
|
- Version: `0.20.186`
|
|
14
36
|
- Commit: `f32a748`
|
|
15
37
|
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26700782132`
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# LazyingRouter Account Login Design For AgInTiFlow
|
|
2
|
+
|
|
3
|
+
Date: 2026-05-31
|
|
4
|
+
|
|
5
|
+
Purpose: define the later-stage integration where a user logs into LazyingRouter once, authorizes AgInTiFlow, and then uses AgInTiFlow without pasting OpenAI, DeepSeek, OpenRouter, Venice, GRS AI, or other upstream provider keys.
|
|
6
|
+
|
|
7
|
+
## Product Goal
|
|
8
|
+
|
|
9
|
+
AgInTiFlow should support two credential modes:
|
|
10
|
+
|
|
11
|
+
1. Direct provider keys for local power users and development.
|
|
12
|
+
2. LazyingRouter account login for normal users.
|
|
13
|
+
|
|
14
|
+
In LazyingRouter mode, the user pays LazyingRouter, LazyingRouter owns upstream provider keys, LazyingRouter checks balance and quota, and AgInTiFlow only stores one LazyingRouter-issued user token.
|
|
15
|
+
|
|
16
|
+
The user-facing experience should be:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
aginti login lazyingrouter
|
|
20
|
+
-> browser opens router.lazying.art
|
|
21
|
+
-> user signs in or registers
|
|
22
|
+
-> user authorizes "AgInTiFlow on this computer"
|
|
23
|
+
-> AgInTiFlow stores an implicit LazyingRouter token
|
|
24
|
+
-> AgInTiFlow runs with provider=lazyingrouter and model=lazying/auto
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
No user should need to understand upstream provider keys for the normal paid product path.
|
|
28
|
+
|
|
29
|
+
## Current AgInTiFlow State
|
|
30
|
+
|
|
31
|
+
AgInTiFlow is currently provider-key oriented.
|
|
32
|
+
|
|
33
|
+
Relevant files:
|
|
34
|
+
|
|
35
|
+
- `src/auth-onboarding.js`: defines `MAIN_AUTH_PROVIDERS`, normalizes auth provider names, and prompts for pasted provider keys.
|
|
36
|
+
- `src/project.js`: stores direct provider keys in ignored `.aginti/.env` with local permission control.
|
|
37
|
+
- `src/config.js`: picks the active provider from environment variables such as `DEEPSEEK_API_KEY`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `QWEN_API_KEY`, and `VENICE_API_KEY`.
|
|
38
|
+
- `src/model-routing.js`: returns provider defaults for `deepseek`, `openai`, `openrouter`, `qwen`, `venice`, and `mock`.
|
|
39
|
+
- `src/model-client.js`: creates an OpenAI SDK client from `apiKey` and `baseURL`.
|
|
40
|
+
- `web.js`: exposes key status and provider-key save APIs, and has a provider allow-list in preference normalization.
|
|
41
|
+
- `src/auxiliary-tools.js`: calls image providers directly through GRS AI and Venice keys.
|
|
42
|
+
|
|
43
|
+
This is a good base because LazyingRouter can be added as another OpenAI-compatible provider. The missing part is account login and a LazyingRouter-issued token.
|
|
44
|
+
|
|
45
|
+
## Current Key Flow
|
|
46
|
+
|
|
47
|
+
The current direct-key flow is:
|
|
48
|
+
|
|
49
|
+
1. User runs `aginti auth`, `aginti login <provider>`, or `aginti keys set <provider> --stdin`.
|
|
50
|
+
2. AgInTiFlow asks the user to paste a provider API key.
|
|
51
|
+
3. `setProviderKey()` writes that secret into project-local `.aginti/.env`.
|
|
52
|
+
4. `resolveRuntimeConfig()` selects a provider and builds `{provider, apiKey, baseURL, model}`.
|
|
53
|
+
5. `createClient()` sends requests through the OpenAI SDK.
|
|
54
|
+
|
|
55
|
+
This flow should stay available. It is useful for development, private labs, and users who intentionally bring their own keys.
|
|
56
|
+
|
|
57
|
+
## Proposed Minimal Design
|
|
58
|
+
|
|
59
|
+
Add `lazyingrouter` as a first-class provider, but do not ask the user to paste any upstream key.
|
|
60
|
+
|
|
61
|
+
Recommended environment shape:
|
|
62
|
+
|
|
63
|
+
```env
|
|
64
|
+
LAZYINGROUTER_API_KEY=
|
|
65
|
+
LAZYINGROUTER_BASE_URL=https://router.lazying.art/v1
|
|
66
|
+
LAZYINGROUTER_MODEL=lazying/auto
|
|
67
|
+
LAZYINGROUTER_ACCOUNT_URL=https://router.lazying.art
|
|
68
|
+
LAZYINGROUTER_TOKEN_ID=
|
|
69
|
+
LAZYINGROUTER_USER_ID=
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Only `LAZYINGROUTER_API_KEY` is secret. `TOKEN_ID` and `USER_ID` are useful for status, logout, and support but should not be treated as credentials.
|
|
73
|
+
|
|
74
|
+
Provider defaults:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
{
|
|
78
|
+
provider: "lazyingrouter",
|
|
79
|
+
apiKey: process.env.LAZYINGROUTER_API_KEY || "",
|
|
80
|
+
baseURL: process.env.LAZYINGROUTER_BASE_URL || "https://router.lazying.art/v1",
|
|
81
|
+
model: process.env.LAZYINGROUTER_MODEL || process.env.LLM_MODEL || "lazying/auto"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
AgInTiFlow model routing should remain role-based. LazyingRouter is supply and billing. It should not redefine AgInTiFlow's route/main/spare/SCS semantics.
|
|
86
|
+
|
|
87
|
+
## Account Authorization Flow
|
|
88
|
+
|
|
89
|
+
Use a simple device authorization flow instead of asking the user to copy keys.
|
|
90
|
+
|
|
91
|
+
1. User runs `aginti login lazyingrouter`, clicks "Login with LazyingRouter" in web settings, or starts AgInTiFlow with no direct provider key.
|
|
92
|
+
2. AgInTiFlow generates or reuses a local install id and calls:
|
|
93
|
+
|
|
94
|
+
```http
|
|
95
|
+
POST https://router.lazying.art/api/app-auth/device/start
|
|
96
|
+
Content-Type: application/json
|
|
97
|
+
|
|
98
|
+
{
|
|
99
|
+
"app": "agintiflow",
|
|
100
|
+
"device_name": "hostname/project",
|
|
101
|
+
"scopes": ["chat", "models", "usage", "artifacts"],
|
|
102
|
+
"client_nonce": "random"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
3. LazyingRouter returns:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"login_url": "https://router.lazying.art/app-auth/authorize?device_code=...",
|
|
111
|
+
"device_code": "opaque",
|
|
112
|
+
"user_code": "ABCD-EFGH",
|
|
113
|
+
"expires_at": 1790000000,
|
|
114
|
+
"poll_interval_seconds": 3
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
4. AgInTiFlow opens or prints `login_url`.
|
|
119
|
+
5. User signs into LazyingRouter, tops up if needed, and approves "AgInTiFlow on this computer".
|
|
120
|
+
6. AgInTiFlow polls:
|
|
121
|
+
|
|
122
|
+
```http
|
|
123
|
+
POST https://router.lazying.art/api/app-auth/device/poll
|
|
124
|
+
Content-Type: application/json
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
"device_code": "opaque",
|
|
128
|
+
"client_nonce": "same-random"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
7. On approval, LazyingRouter returns the token once:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"ok": true,
|
|
137
|
+
"api_base": "https://router.lazying.art/v1",
|
|
138
|
+
"api_key": "sk-...",
|
|
139
|
+
"model": "lazying/auto",
|
|
140
|
+
"token_id": 123,
|
|
141
|
+
"user_id": 456,
|
|
142
|
+
"display_name": "Lachlan",
|
|
143
|
+
"quota": {
|
|
144
|
+
"available": 123456,
|
|
145
|
+
"display": "$12.34",
|
|
146
|
+
"topup_url": "https://router.lazying.art/topup"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
8. AgInTiFlow stores the token locally with `0600` permissions and sets provider `lazyingrouter`.
|
|
152
|
+
|
|
153
|
+
The token should be a normal LazyingRouter user token, visible and revocable in LazyingRouter. It should be named like `AgInTiFlow - <hostname>`.
|
|
154
|
+
|
|
155
|
+
## Runtime And Quota Behavior
|
|
156
|
+
|
|
157
|
+
AgInTiFlow should use LazyingRouter as the authority for billing and quota.
|
|
158
|
+
|
|
159
|
+
Before a run:
|
|
160
|
+
|
|
161
|
+
- If provider is `lazyingrouter`, call a lightweight status endpoint such as `/api/app-auth/status` or existing `/api/usage/token/`.
|
|
162
|
+
- If no usable balance exists, stop before starting the model loop and print a top-up URL.
|
|
163
|
+
- If the status endpoint is unreachable but the token exists, allow one normal model request unless the user has configured strict preflight.
|
|
164
|
+
|
|
165
|
+
During a run:
|
|
166
|
+
|
|
167
|
+
- LazyingRouter remains the hard enforcement layer.
|
|
168
|
+
- If LazyingRouter returns `401`, mark the local token invalid and suggest `aginti login lazyingrouter`.
|
|
169
|
+
- If it returns quota/balance errors, stop gracefully and show the LazyingRouter top-up URL.
|
|
170
|
+
- Do not fall back to direct upstream keys unless the user explicitly configured fallback.
|
|
171
|
+
|
|
172
|
+
After a run:
|
|
173
|
+
|
|
174
|
+
- Show spent quota if LazyingRouter returned usage metadata.
|
|
175
|
+
- Store token status evidence in the session log without storing the raw token.
|
|
176
|
+
|
|
177
|
+
## Commands And UI
|
|
178
|
+
|
|
179
|
+
CLI additions:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
aginti login lazyingrouter
|
|
183
|
+
aginti logout lazyingrouter
|
|
184
|
+
aginti keys status
|
|
185
|
+
aginti provider lazyingrouter
|
|
186
|
+
/login lazyingrouter
|
|
187
|
+
/logout lazyingrouter
|
|
188
|
+
/provider lazyingrouter
|
|
189
|
+
/balance
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Web additions:
|
|
193
|
+
|
|
194
|
+
- Provider dropdown includes `LazyingRouter`.
|
|
195
|
+
- Settings has "Login with LazyingRouter", "Balance", "Top up", "Revoke this device", and "Use direct provider keys instead".
|
|
196
|
+
- Header status can show `provider=lazyingrouter`, `model=lazying/auto`, and a compact balance label.
|
|
197
|
+
|
|
198
|
+
## Implementation Points In AgInTiFlow
|
|
199
|
+
|
|
200
|
+
Add provider identity:
|
|
201
|
+
|
|
202
|
+
- Add `lazyingrouter` to `MAIN_AUTH_PROVIDERS` in `src/auth-onboarding.js`.
|
|
203
|
+
- Add aliases: `lazying`, `lzy`, `lazying-router`, `lazyingrouter`.
|
|
204
|
+
- Add `LAZYINGROUTER_*` keys to `LOCAL_ENV_KEYS` and provider key candidates in `src/project.js`.
|
|
205
|
+
- Add key status fields and preview masking in `providerKeyStatus()` and `providerKeyPreview()`.
|
|
206
|
+
- Add a `getProviderDefaults("lazyingrouter")` branch in `src/model-routing.js`.
|
|
207
|
+
- Update `resolveRuntimeConfig()` in `src/config.js` to choose LazyingRouter when `LAZYINGROUTER_API_KEY` exists.
|
|
208
|
+
- Update web preference allow-lists in `web.js`.
|
|
209
|
+
- Add command and interactive handlers for login/logout/status.
|
|
210
|
+
|
|
211
|
+
Do not overload `setProviderKey()` as the primary account-login path. It can save a LazyingRouter token for scripting, but the normal path should use device authorization.
|
|
212
|
+
|
|
213
|
+
Suggested storage:
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
.aginti/.env
|
|
217
|
+
.aginti/auth/lazyingrouter.json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`.aginti/.env` keeps compatibility with current config loading. The JSON file can keep non-secret account metadata, token id, account url, device name, and last status check.
|
|
221
|
+
|
|
222
|
+
## Auxiliary Tools
|
|
223
|
+
|
|
224
|
+
Text/chat integration can be v1 because LazyingRouter already exposes OpenAI-compatible model routes.
|
|
225
|
+
|
|
226
|
+
Image generation should be a separate v1.1 or v2 item unless LazyingRouter exposes an image endpoint with stable semantics. Current AgInTiFlow image generation depends on direct GRS AI and Venice keys in `src/auxiliary-tools.js`. Later options:
|
|
227
|
+
|
|
228
|
+
- LazyingRouter exposes `/v1/images/generations`, then AgInTiFlow routes image generation through LazyingRouter when provider is `lazyingrouter`.
|
|
229
|
+
- AgInTiFlow keeps direct image provider keys as optional advanced configuration.
|
|
230
|
+
- LazyingRouter returns a clear unsupported error for SVG/vector and can offer PNG generation when available.
|
|
231
|
+
|
|
232
|
+
## Security Rules
|
|
233
|
+
|
|
234
|
+
- Never store or display upstream provider keys in AgInTiFlow.
|
|
235
|
+
- Store the LazyingRouter user token with `0600` permissions.
|
|
236
|
+
- Redact `LAZYINGROUTER_API_KEY` in logs, command output, event JSON, and web state.
|
|
237
|
+
- Use a one-time token delivery response for device authorization.
|
|
238
|
+
- Support `logout lazyingrouter` by revoking the token server-side and deleting local secrets.
|
|
239
|
+
- Do not silently switch from LazyingRouter to direct provider keys on quota failure.
|
|
240
|
+
- Treat LazyingRouter resources and returned prompts as untrusted content, not system instructions.
|
|
241
|
+
|
|
242
|
+
## Acceptance Tests
|
|
243
|
+
|
|
244
|
+
Minimum TDV cases:
|
|
245
|
+
|
|
246
|
+
1. Fresh machine, no provider keys: `aginti login lazyingrouter` opens an authorization URL and stores a masked token after approval.
|
|
247
|
+
2. `aginti "hello"` uses `provider=lazyingrouter` and model `lazying/auto`.
|
|
248
|
+
3. `/balance` shows the same available balance as LazyingRouter token usage API.
|
|
249
|
+
4. Exhausted user/token balance stops before or during the run with a top-up URL and no fallback to upstream keys.
|
|
250
|
+
5. `aginti logout lazyingrouter` revokes the token and removes local secret material.
|
|
251
|
+
6. Web UI can login, show status, run, and logout with the same project state as CLI.
|
|
252
|
+
7. Session logs contain provider, model, token id, and quota status, but never the raw token.
|
|
253
|
+
|
|
254
|
+
## Open Design Decisions
|
|
255
|
+
|
|
256
|
+
1. Default precedence: if both `LAZYINGROUTER_API_KEY` and direct upstream keys exist, the recommended default is LazyingRouter unless the user explicitly selects direct provider mode.
|
|
257
|
+
2. Token funding: LazyingRouter must decide whether app tokens draw directly from user wallet/subscription or use allocated token quota. AgInTiFlow should not care as long as status and error semantics are stable.
|
|
258
|
+
3. Image generation: decide whether LazyingRouter will proxy image providers or leave direct auxiliary keys as the default advanced path.
|
|
259
|
+
|