@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 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
 
@@ -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.187",
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 || modelRoles.spare?.reasoning || "medium",
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 || "medium",
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 (providerField.value === "mock" || providerField.value === "venice" || providerField.value === "openai" || providerField.value === "qwen") {
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 || modelRoles.spare?.reasoning || "medium";
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
+