@lazyingart/agintiflow 0.20.188 → 0.20.190
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/package.json +1 -1
- package/public/app.js +13 -5
- package/public/index.html +18 -0
- package/references/agintiflow-intent-routing-overpatch-audit-2026-06-01.md +74 -0
- package/references/agintiflow-npm-publication-memory.md +23 -1
- package/references/lazyingrouter-account-login-design.md +259 -0
- package/references/openai-compatible-base-url-and-reasoning-policy.md +183 -0
- package/scripts/smoke-coding-tools.js +18 -0
- package/scripts/smoke-model-roles.js +97 -1
- package/scripts/smoke-web-ui.js +3 -2
- package/src/agent-runner.js +48 -3
- package/src/cli.js +21 -3
- package/src/config.js +46 -6
- package/src/goal-intent.js +101 -0
- package/src/interactive-cli.js +43 -26
- package/src/model-client.js +40 -3
- package/src/model-routing.js +28 -10
- package/src/perception-tools.js +6 -2
- package/src/project.js +29 -0
- package/src/task-profiles.js +1 -1
- package/src/web-db.js +10 -1
- package/web.js +11 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.190",
|
|
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");
|
|
@@ -1859,17 +1861,19 @@ function renderModelRoles() {
|
|
|
1859
1861
|
...(modelRoles.route || {}),
|
|
1860
1862
|
provider: routeProviderField?.value || modelRoles.route?.provider || "deepseek",
|
|
1861
1863
|
model: routeModelField?.value || modelRoles.route?.model || "deepseek-v4-flash",
|
|
1864
|
+
reasoning: routeReasoningField?.value ?? modelRoles.route?.reasoning ?? "",
|
|
1862
1865
|
},
|
|
1863
1866
|
main: {
|
|
1864
1867
|
...(modelRoles.main || {}),
|
|
1865
1868
|
provider: mainProviderField?.value || modelRoles.main?.provider || "deepseek",
|
|
1866
1869
|
model: mainModelField?.value || modelRoles.main?.model || "deepseek-v4-pro",
|
|
1870
|
+
reasoning: mainReasoningField?.value ?? modelRoles.main?.reasoning ?? "",
|
|
1867
1871
|
},
|
|
1868
1872
|
spare: {
|
|
1869
1873
|
...(modelRoles.spare || {}),
|
|
1870
1874
|
provider: spareProviderField?.value || modelRoles.spare?.provider || "openai",
|
|
1871
1875
|
model: spareModelField?.value || modelRoles.spare?.model || "gpt-5.4",
|
|
1872
|
-
reasoning: spareReasoningField?.value
|
|
1876
|
+
reasoning: spareReasoningField?.value ?? modelRoles.spare?.reasoning ?? "medium",
|
|
1873
1877
|
},
|
|
1874
1878
|
wrapper: {
|
|
1875
1879
|
...(modelRoles.wrapper || {}),
|
|
@@ -1885,7 +1889,7 @@ function renderModelRoles() {
|
|
|
1885
1889
|
};
|
|
1886
1890
|
modelRoleGridEl.innerHTML = Object.values(roles)
|
|
1887
1891
|
.map((role) => {
|
|
1888
|
-
const reasoning = role.reasoning ? ` · ${role.reasoning}` : "";
|
|
1892
|
+
const reasoning = role.reasoning ? ` · ${role.reasoning}` : role.reasoning === "" ? " · Provider default" : "";
|
|
1889
1893
|
return `
|
|
1890
1894
|
<div class="model-role-card">
|
|
1891
1895
|
<strong>${escapeHtml(role.label || role.id)}</strong>
|
|
@@ -2077,11 +2081,13 @@ function formPayload() {
|
|
|
2077
2081
|
model: fieldValue(modelField),
|
|
2078
2082
|
routeProvider: routeProviderField?.value || "deepseek",
|
|
2079
2083
|
routeModel: fieldValue(routeModelField) || "deepseek-v4-flash",
|
|
2084
|
+
routeReasoning: routeReasoningField?.value ?? "",
|
|
2080
2085
|
mainProvider: mainProviderField?.value || "deepseek",
|
|
2081
2086
|
mainModel: fieldValue(mainModelField) || "deepseek-v4-pro",
|
|
2087
|
+
mainReasoning: mainReasoningField?.value ?? "",
|
|
2082
2088
|
spareProvider: spareProviderField?.value || "openai",
|
|
2083
2089
|
spareModel: fieldValue(spareModelField) || "gpt-5.4",
|
|
2084
|
-
spareReasoning: spareReasoningField?.value
|
|
2090
|
+
spareReasoning: spareReasoningField?.value ?? "medium",
|
|
2085
2091
|
wrapperModel: fieldValue(wrapperModelField) || "gpt-5.5",
|
|
2086
2092
|
wrapperReasoning: wrapperReasoningField?.value || "medium",
|
|
2087
2093
|
auxiliaryProvider: auxiliaryProviderField?.value || "grsai",
|
|
@@ -3918,7 +3924,7 @@ veniceModeToggle?.addEventListener("change", () => {
|
|
|
3918
3924
|
});
|
|
3919
3925
|
|
|
3920
3926
|
modelField.addEventListener("change", updateRoutingHint);
|
|
3921
|
-
[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]
|
|
3922
3928
|
.filter(Boolean)
|
|
3923
3929
|
.forEach((field) => {
|
|
3924
3930
|
field.addEventListener("input", () => {
|
|
@@ -4230,9 +4236,11 @@ async function loadConfig() {
|
|
|
4230
4236
|
routingModeField.value = prefs.routingMode || "smart";
|
|
4231
4237
|
providerField.value = prefs.provider || "deepseek";
|
|
4232
4238
|
if (routeProviderField) routeProviderField.value = prefs.routeProvider || modelRoles.route?.provider || "deepseek";
|
|
4239
|
+
if (routeReasoningField) routeReasoningField.value = prefs.routeReasoning ?? modelRoles.route?.reasoning ?? "";
|
|
4233
4240
|
if (mainProviderField) mainProviderField.value = prefs.mainProvider || modelRoles.main?.provider || "deepseek";
|
|
4241
|
+
if (mainReasoningField) mainReasoningField.value = prefs.mainReasoning ?? modelRoles.main?.reasoning ?? "";
|
|
4234
4242
|
if (spareProviderField) spareProviderField.value = prefs.spareProvider || modelRoles.spare?.provider || "openai";
|
|
4235
|
-
if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning
|
|
4243
|
+
if (spareReasoningField) spareReasoningField.value = prefs.spareReasoning ?? modelRoles.spare?.reasoning ?? "medium";
|
|
4236
4244
|
if (wrapperReasoningField) wrapperReasoningField.value = prefs.wrapperReasoning || modelRoles.wrapper?.reasoning || "medium";
|
|
4237
4245
|
if (auxiliaryProviderField) auxiliaryProviderField.value = prefs.auxiliaryProvider || modelRoles.auxiliary?.provider || "grsai";
|
|
4238
4246
|
setSelectOptions(
|
package/public/index.html
CHANGED
|
@@ -426,6 +426,14 @@
|
|
|
426
426
|
<select id="routeModel" name="routeModel">
|
|
427
427
|
<option value="deepseek-v4-flash">DeepSeek v4 flash</option>
|
|
428
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>
|
|
429
437
|
</label>
|
|
430
438
|
<label>
|
|
431
439
|
<span>Main</span>
|
|
@@ -439,6 +447,14 @@
|
|
|
439
447
|
<select id="mainModel" name="mainModel">
|
|
440
448
|
<option value="deepseek-v4-pro">DeepSeek v4 pro</option>
|
|
441
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>
|
|
442
458
|
</label>
|
|
443
459
|
<label>
|
|
444
460
|
<span>Spare</span>
|
|
@@ -453,6 +469,8 @@
|
|
|
453
469
|
<option value="gpt-5.4">GPT-5.4</option>
|
|
454
470
|
</select>
|
|
455
471
|
<select id="spareReasoning" name="spareReasoning">
|
|
472
|
+
<option value="">Provider default</option>
|
|
473
|
+
<option value="minimal">minimal</option>
|
|
456
474
|
<option value="low">low</option>
|
|
457
475
|
<option value="medium">medium</option>
|
|
458
476
|
<option value="high">high</option>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# AgInTiFlow Intent Routing Overpatch Audit
|
|
2
|
+
|
|
3
|
+
Date: 2026-06-01
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
A bare user message, `hello`, was handled as an execution task. In the observed run, AgInTiFlow planned to inspect the workspace, created `hello.py`, and then stopped without a natural chat answer. That is the wrong default behavior for a general agent UI: a greeting should be answered directly, not converted into a workspace mutation.
|
|
8
|
+
|
|
9
|
+
## Evidence
|
|
10
|
+
|
|
11
|
+
Observed session:
|
|
12
|
+
|
|
13
|
+
- Session: `/home/lachlan/.agintiflow/sessions/web-agent-f798c7cc-b3c2-439f-b2be-2a2746b4944d/state.json`
|
|
14
|
+
- Provider/model: `deepseek/deepseek-v4-flash`
|
|
15
|
+
- Goal: `hello`
|
|
16
|
+
- Task profile: `auto`
|
|
17
|
+
- Selected skills: `[]`
|
|
18
|
+
- Outcome shown by user: AgInTiFlow wrote `hello.py` with `print("Hello from agent!")`
|
|
19
|
+
|
|
20
|
+
The selected skills list was empty, so this was not caused by a custom skill. The behavior came from the core runtime prompt and the lack of a direct-answer lane.
|
|
21
|
+
|
|
22
|
+
## Root Cause
|
|
23
|
+
|
|
24
|
+
The runtime had one dominant path:
|
|
25
|
+
|
|
26
|
+
1. Create a plan.
|
|
27
|
+
2. Continue with tools until completion.
|
|
28
|
+
3. Prefer real workspace edits/checks over advice-only answers.
|
|
29
|
+
|
|
30
|
+
The strongest prompt pressure was in:
|
|
31
|
+
|
|
32
|
+
- `src/agent-runner.js`: system prompt said the plan is not final and the agent should actively use tools until complete.
|
|
33
|
+
- `src/model-client.js`: planner prompt said to prefer real workspace edits/checks over advice-only answers.
|
|
34
|
+
- `src/task-profiles.js`: the `auto` profile told the agent to infer task type and choose a tool mix, but did not define a no-tool conversational mode.
|
|
35
|
+
|
|
36
|
+
That design is useful for real tasks, but over-applies tool use to short social turns.
|
|
37
|
+
|
|
38
|
+
## Design Fix
|
|
39
|
+
|
|
40
|
+
Add a deterministic pre-plan goal-intent classifier:
|
|
41
|
+
|
|
42
|
+
- Greetings, thanks, and explicit greeting requests finish directly.
|
|
43
|
+
- Explicit create/edit/run/read/search/download/build/test/coding/file/command requests remain agentic.
|
|
44
|
+
- File paths, file extensions, and command-looking inputs remain agentic.
|
|
45
|
+
- The direct path runs before planning, SCS, surgical context, snapshots, browser, shell, canvas, or file tools.
|
|
46
|
+
|
|
47
|
+
This keeps the fix general. It does not special-case `hello.py`, mock mode, DeepSeek, or a single UI path.
|
|
48
|
+
|
|
49
|
+
The prompts were also softened:
|
|
50
|
+
|
|
51
|
+
- Tool use is required only when the user actually asks for workspace/browser/shell/web/canvas/image/MCP/specialist work.
|
|
52
|
+
- Greetings and simple conversational turns should finish directly.
|
|
53
|
+
- Canvas should not be used for ordinary chat replies.
|
|
54
|
+
|
|
55
|
+
## Regression Contract
|
|
56
|
+
|
|
57
|
+
Required behavior:
|
|
58
|
+
|
|
59
|
+
- `hello` returns a direct greeting.
|
|
60
|
+
- `hello` does not create a plan.
|
|
61
|
+
- `hello` does not call tools.
|
|
62
|
+
- `hello` does not create `hello.py`.
|
|
63
|
+
- `create hello.py` still requires tools.
|
|
64
|
+
- `write a hello-world Python script and run it` still requires tools.
|
|
65
|
+
|
|
66
|
+
## Product Lesson
|
|
67
|
+
|
|
68
|
+
AgInTiFlow should solve problems at the correct layer:
|
|
69
|
+
|
|
70
|
+
- Core system flaw: no conversational/direct-answer lane. Fix in runtime intent routing.
|
|
71
|
+
- Core general skill incompleteness: missing reusable task classifier. Add deterministic classifier and tests.
|
|
72
|
+
- Custom skill issue: not applicable here because no skill was selected.
|
|
73
|
+
|
|
74
|
+
Avoid overpatching by not making every short input a file task and not routing every interaction through heavyweight planning/SCS/tool execution.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Date: 2026-05-20
|
|
4
4
|
|
|
5
|
-
Updated: 2026-
|
|
5
|
+
Updated: 2026-06-01
|
|
6
6
|
|
|
7
7
|
This note records the working publication route for AgInTiFlow so future release work does not get blocked by stale local npm tokens or repeated discussion about why direct `npm publish` failed.
|
|
8
8
|
|
|
@@ -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.189`
|
|
14
|
+
- Commit: `0095284`
|
|
15
|
+
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26734901522`
|
|
16
|
+
- Workflow result: success
|
|
17
|
+
- npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.189`.
|
|
18
|
+
- Installed verification: `npm install -g @lazyingart/agintiflow@0.20.189` then `aginti --version` returned `0.20.189`.
|
|
19
|
+
- Webapp verification: `aginti webapp restart --port 3210` then `curl -fsS http://127.0.0.1:3210/health` returned `version":"0.20.189"` from the global npm package path.
|
|
20
|
+
- Feature verification: `npm run smoke:model-roles` covered `OPENAI_BASE_URL`, provider-default reasoning, OpenAI chat `reasoning_effort`, and retry-without-reasoning for OpenAI-compatible gateways.
|
|
21
|
+
|
|
22
|
+
Previous verified release:
|
|
23
|
+
|
|
24
|
+
- Version: `0.20.188`
|
|
25
|
+
- Commit: `939e2ec`
|
|
26
|
+
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26705729544`
|
|
27
|
+
- Workflow result: success
|
|
28
|
+
- npm registry check: `npm view @lazyingart/agintiflow version dist-tags.latest --registry=https://registry.npmjs.org` returned `0.20.188`.
|
|
29
|
+
- Installed verification: `npm install -g @lazyingart/agintiflow@0.20.188` then `aginti --version` returned `0.20.188`.
|
|
30
|
+
- 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.
|
|
31
|
+
- 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.
|
|
32
|
+
|
|
33
|
+
Previous verified release:
|
|
34
|
+
|
|
13
35
|
- Version: `0.20.187`
|
|
14
36
|
- Commit: `9339b2e`
|
|
15
37
|
- GitHub Actions run: `https://github.com/lazyingart/AgInTiFlow/actions/runs/26701125864`
|
|
@@ -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
|
+
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# OpenAI-Compatible Base URL And Reasoning Policy
|
|
2
|
+
|
|
3
|
+
Date: 2026-06-01
|
|
4
|
+
|
|
5
|
+
Purpose: record the decision for PR #1 (`feat/openai-compatible-routing`) and future OpenAI-compatible gateway support.
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
|
|
9
|
+
AgInTiFlow should incorporate the useful part of PR #1:
|
|
10
|
+
|
|
11
|
+
- Support `OPENAI_BASE_URL` for OpenAI-compatible gateways.
|
|
12
|
+
- Allow project-local env configuration to affect model/provider display.
|
|
13
|
+
|
|
14
|
+
AgInTiFlow should not incorporate the confusing part as-is:
|
|
15
|
+
|
|
16
|
+
- Do not add a built-in model named `gpt-5.4-high` as if it were an official OpenAI model.
|
|
17
|
+
- Do not encode reasoning effort inside built-in model identity.
|
|
18
|
+
|
|
19
|
+
The correct internal shape stays:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
provider=openai
|
|
23
|
+
model=gpt-5.4
|
|
24
|
+
reasoning=high
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For custom gateways that really expose a model string such as `gpt-5.4-high`, users can still set that exact model name manually:
|
|
28
|
+
|
|
29
|
+
```env
|
|
30
|
+
AGENT_PROVIDER=openai
|
|
31
|
+
OPENAI_BASE_URL=https://example-gateway.local/v1
|
|
32
|
+
OPENAI_DEFAULT_MODEL=gpt-5.4-high
|
|
33
|
+
AGINTI_MAIN_REASONING=none
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That sends the custom model name but omits AgInTiFlow's separate reasoning parameter.
|
|
37
|
+
|
|
38
|
+
## Rationale
|
|
39
|
+
|
|
40
|
+
AgInTiFlow's current model design separates three concepts:
|
|
41
|
+
|
|
42
|
+
- **Provider**: the API surface, such as `openai`, `deepseek`, `openrouter`, `qwen`, `venice`, or future `lazyingrouter`.
|
|
43
|
+
- **Model**: the provider model id or gateway alias.
|
|
44
|
+
- **Reasoning**: optional effort level, such as `low`, `medium`, `high`, or `xhigh`.
|
|
45
|
+
|
|
46
|
+
This separation matters because direct OpenAI, OpenRouter, Venice, and LazyingRouter-style gateways may all expose different model ids while AgInTiFlow still needs a consistent role-based routing layer.
|
|
47
|
+
|
|
48
|
+
Synthetic built-in ids like `gpt-5.4-high` make the UI look simple but blur the contract. They also risk sending an invalid `model` value to a provider that expects `gpt-5.4` plus a separate reasoning option.
|
|
49
|
+
|
|
50
|
+
## `OPENAI_BASE_URL`
|
|
51
|
+
|
|
52
|
+
`OPENAI_BASE_URL` should be accepted as an OpenAI-provider-specific base URL.
|
|
53
|
+
|
|
54
|
+
Recommended precedence:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
OPENAI_BASE_URL
|
|
58
|
+
LLM_BASE_URL
|
|
59
|
+
https://api.openai.com/v1
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Reason:
|
|
63
|
+
|
|
64
|
+
- `OPENAI_BASE_URL` is explicit and only affects OpenAI-compatible routing.
|
|
65
|
+
- `LLM_BASE_URL` remains a generic fallback.
|
|
66
|
+
- The official OpenAI endpoint remains the default.
|
|
67
|
+
|
|
68
|
+
Accepted local env keys should include:
|
|
69
|
+
|
|
70
|
+
```env
|
|
71
|
+
OPENAI_API_KEY=
|
|
72
|
+
OPENAI_BASE_URL=https://api.openai.com/v1
|
|
73
|
+
OPENAI_DEFAULT_MODEL=gpt-5.4-mini
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`OPENAI_BASE_URL` is not a secret, but it belongs in the same local environment mechanism because users often configure it together with `OPENAI_API_KEY`.
|
|
77
|
+
|
|
78
|
+
## Reasoning "None" Option
|
|
79
|
+
|
|
80
|
+
Reasoning must be optional. Some OpenAI-compatible gateways encode effort in the model name or do not support reasoning at all.
|
|
81
|
+
|
|
82
|
+
Recommended user-facing selector label:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
Provider default
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Recommended internal value:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
""
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Accepted CLI/env aliases:
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
none
|
|
98
|
+
off
|
|
99
|
+
default
|
|
100
|
+
provider-default
|
|
101
|
+
provider_default
|
|
102
|
+
auto
|
|
103
|
+
""
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All of those should normalize to an empty reasoning value, meaning AgInTiFlow should omit the reasoning option from provider requests.
|
|
107
|
+
|
|
108
|
+
Why not use `none` internally:
|
|
109
|
+
|
|
110
|
+
- `none` is a user-facing command word, not a provider value.
|
|
111
|
+
- Empty string already means "no explicit reasoning override" in current role config.
|
|
112
|
+
- Keeping the runtime value empty avoids accidentally sending `"reasoning": "none"` to providers.
|
|
113
|
+
|
|
114
|
+
## Selector Behavior
|
|
115
|
+
|
|
116
|
+
Model selectors should show reasoning as a separate field:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
Model: GPT-5.4
|
|
120
|
+
Reasoning: Provider default | low | medium | high | xhigh
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Preset examples:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
OpenAI GPT-5.4
|
|
127
|
+
provider=openai
|
|
128
|
+
model=gpt-5.4
|
|
129
|
+
reasoning=medium
|
|
130
|
+
|
|
131
|
+
OpenAI GPT-5.4 High Reasoning
|
|
132
|
+
provider=openai
|
|
133
|
+
model=gpt-5.4
|
|
134
|
+
reasoning=high
|
|
135
|
+
|
|
136
|
+
Custom OpenAI-Compatible Gateway Model
|
|
137
|
+
provider=openai
|
|
138
|
+
model=gpt-5.4-high
|
|
139
|
+
reasoning=
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The last case is a user or project configuration, not a built-in official OpenAI model.
|
|
143
|
+
|
|
144
|
+
## Incorporation Plan For PR #1
|
|
145
|
+
|
|
146
|
+
Cherry-pick the useful intent:
|
|
147
|
+
|
|
148
|
+
1. Add `OPENAI_BASE_URL` to the local env allow-list.
|
|
149
|
+
2. Update OpenAI provider defaults to prefer `OPENAI_BASE_URL`.
|
|
150
|
+
3. Add `OPENAI_BASE_URL` to `.aginti/.env.example` generated by project initialization.
|
|
151
|
+
4. Load project env before `aginti models`, but preserve normal shell-env precedence unless runtime behavior is intentionally changed.
|
|
152
|
+
5. Add a `Provider default` reasoning selector option and normalize common aliases to empty string.
|
|
153
|
+
|
|
154
|
+
Do not cherry-pick:
|
|
155
|
+
|
|
156
|
+
1. Built-in `PROVIDER_MODEL_CATALOG.openai` entry for `gpt-5.4-high`.
|
|
157
|
+
2. Provider dropdown defaulting OpenAI to `gpt-5.4-high`.
|
|
158
|
+
3. Examples implying `gpt-5.4-high` is a standard official OpenAI model.
|
|
159
|
+
|
|
160
|
+
## Tests To Add
|
|
161
|
+
|
|
162
|
+
Minimum regression tests:
|
|
163
|
+
|
|
164
|
+
1. `OPENAI_BASE_URL` is used before `LLM_BASE_URL` for provider `openai`.
|
|
165
|
+
2. `LLM_BASE_URL` still works when `OPENAI_BASE_URL` is unset.
|
|
166
|
+
3. `AGINTI_MAIN_REASONING=none` normalizes to empty reasoning.
|
|
167
|
+
4. `AGINTI_MAIN_REASONING=provider-default` normalizes to empty reasoning.
|
|
168
|
+
5. Official OpenAI presets keep `model=gpt-5.4` and a separate reasoning value.
|
|
169
|
+
6. A custom gateway config can use `OPENAI_DEFAULT_MODEL=gpt-5.4-high` with empty reasoning.
|
|
170
|
+
|
|
171
|
+
## Future LazyingRouter Alignment
|
|
172
|
+
|
|
173
|
+
This policy also matches the LazyingRouter design:
|
|
174
|
+
|
|
175
|
+
```env
|
|
176
|
+
AGENT_PROVIDER=lazyingrouter
|
|
177
|
+
LAZYINGROUTER_BASE_URL=https://router.lazying.art/v1
|
|
178
|
+
LAZYINGROUTER_MODEL=lazying/auto
|
|
179
|
+
AGINTI_MAIN_REASONING=provider-default
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
LazyingRouter can route `lazying/auto` internally without AgInTiFlow inventing provider-specific model aliases or leaking upstream routing semantics into the client UI.
|
|
183
|
+
|