dsh-connect-agnes-token-plan 0.8.0 → 0.8.1
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/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/client.js +81 -13
- package/lib/agnescode-llm-adapter-BmiNipn7.js +104 -0
- package/lib/{agnescode-models-eTXfaiwF.js → agnescode-models-w02GyYgT.js} +61 -15
- package/lib/index.js +47 -33
- package/lib/{llm-adapter-BgxoDG2y.js → llm-adapter-ZVFqrXHB.js} +17 -79
- package/lib/{llm-models-CvWAbIG8.js → llm-models-BZfJOFax.js} +92 -27
- package/lib/{llm-error-fix-cwd4vn26.js → pi-ai-adapter-core-D0iGPjK-.js} +124 -1
- package/package.json +11 -6
- package/lib/agnescode-llm-adapter-CTaTAHvp.js +0 -152
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
本文件只记**公开行为变化**(新增能力、破坏性改动、重要修复)。实现细节、重构与测试加固请直接看 `git log`。
|
|
4
4
|
|
|
5
|
+
## [0.8.1] — 2026-10-03
|
|
6
|
+
|
|
7
|
+
### 修复:AgnesCode provider 补全思考契约——档位可用,`off` 诚实不提供
|
|
8
|
+
|
|
9
|
+
- **背景**:AgnesCode 是桌面端上游,v1 一直按「wire 通道未验证」把 `reasoning` 钉成 `false`,DSH 里看不到思考档位。2026-10-03 对真 BFF(`agnes-3.0-flash`)补探 7 发,推翻两条假设。
|
|
10
|
+
- **实测**:`{bffBase}/v1/chat/completions` 在**不发任何思考字段**时仍返回 `message.reasoning_content`(406 字 / 320 reasoning_tokens)——默认即思考开;`reasoning_effort` 的 `none`/`low`/`medium`/`high` 全被接受(都 200)。
|
|
11
|
+
- **关键发现**:`reasoning_effort:"none"` **关不掉思考**(仍回 257 字 / 188 reasoning_tokens)。桌面端真正关思考的开关是 `request_params.agnes_thinking_enabled=false`(或 `thinking_effort:"off"`),而 pi-ai 的档位映射是字符串值、表达不了它。
|
|
12
|
+
- **落地**:descriptor 翻 `reasoning:true` + `agnescodeThinkingLevelMap()`(`off:null` 诚实不提供,避免「点关思考实际还开着」的静默谎言;`low/medium/high` 开;`xhigh/max` 关,BFF 未实测);profile 钉 `reasoning: DEFAULT_REASONING_EFFORT`(high),与 Token Plan 侧对齐。
|
|
13
|
+
- 影响面:`src/host/{agnescode-models, agnescode-llm-adapter}.ts`、`test/agnescode.test.mjs`、`docs/ROADMAP.md` §6.3、`lib/`(按 ADR-008 重建入库)。
|
|
14
|
+
|
|
5
15
|
## [0.8.0] — 2026-10-02
|
|
6
16
|
|
|
7
17
|
### 修复:AgnesCode provider 必须进 tab 才启用(mount seed 竞态)
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ npm run doctor:json # 机器读:JSON(可进你的巡检 / 工单脚本
|
|
|
90
90
|
- [docs/API.md](docs/API.md) — 路由与控制台端点
|
|
91
91
|
- [docs/AGNES-API.md](docs/AGNES-API.md) — Agnes 接口全集(控制台额度侧 + 推理侧)
|
|
92
92
|
- [docs/TESTING.md](docs/TESTING.md) — 测试体系
|
|
93
|
-
- [docs/PITFALLS.md](docs/PITFALLS.md) — 真实踩坑(
|
|
93
|
+
- [docs/PITFALLS.md](docs/PITFALLS.md) — 真实踩坑(41 条)
|
|
94
94
|
- [CHANGELOG.md](CHANGELOG.md) — 版本变化
|
|
95
95
|
|
|
96
96
|
AI 协作会话请先读 [AGENTS.md](AGENTS.md)。
|
package/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var dsh_connect_agnes_token_plan_client = (function() {
|
|
2
2
|
|
|
3
3
|
//#region \0rolldown/runtime.js
|
|
4
4
|
var __esmMin = (fn, res, err) => () => {
|
|
@@ -300,8 +300,8 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
300
300
|
"quota.perYear": " / yr",
|
|
301
301
|
"quota.cycle.monthly": "monthly",
|
|
302
302
|
"quota.cycle.yearly": "yearly",
|
|
303
|
-
"quota.unit.requests": "",
|
|
304
|
-
"quota.unit.images": "",
|
|
303
|
+
"quota.unit.requests": " times",
|
|
304
|
+
"quota.unit.images": " images",
|
|
305
305
|
"quota.used": "Used",
|
|
306
306
|
"quota.resetAt": "resets {time}",
|
|
307
307
|
"quota.resetCountdown": "resets in ~{minutes} min",
|
|
@@ -587,6 +587,21 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
587
587
|
//#endregion
|
|
588
588
|
//#region src/client/snapshot.ts
|
|
589
589
|
/**
|
|
590
|
+
* Whether a body is a DATA snapshot, as opposed to a refusal.
|
|
591
|
+
*
|
|
592
|
+
* The two `ok` checks in `interpretSnapshot` do the real work — this is the
|
|
593
|
+
* name for the fact they establish, so the caller types the payload as data
|
|
594
|
+
* instead of casting through `unknown`. A type predicate still trusts the
|
|
595
|
+
* wire (the Host owns the shape; `wire.ts` documents that types are loaded,
|
|
596
|
+
* never enforced at runtime), but it records that `ok:true` was seen, where
|
|
597
|
+
* `as unknown as` threw that fact away.
|
|
598
|
+
* @param {unknown} value - the body that reached the `ok:true` branch.
|
|
599
|
+
* @returns {boolean} whether the value is a data snapshot.
|
|
600
|
+
*/
|
|
601
|
+
function isSnapshotData(value) {
|
|
602
|
+
return typeof value === "object" && value !== null && value.ok === true;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
590
605
|
* Read one snapshot response into the (data, error) pair the panel renders.
|
|
591
606
|
*
|
|
592
607
|
* The Host answers HTTP 200 for every expected outcome and signals the
|
|
@@ -614,7 +629,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
614
629
|
error: "unexpected payload"
|
|
615
630
|
};
|
|
616
631
|
return {
|
|
617
|
-
data: payload,
|
|
632
|
+
data: isSnapshotData(payload) ? payload : null,
|
|
618
633
|
error: null
|
|
619
634
|
};
|
|
620
635
|
}
|
|
@@ -666,6 +681,21 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
666
681
|
shapeWarnings: Array.isArray(data?.shapeWarnings) ? data.shapeWarnings : []
|
|
667
682
|
};
|
|
668
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Whether to show the account management section.
|
|
686
|
+
*
|
|
687
|
+
* Decision: show the account editor UNCONDITIONALLY whenever the snapshot
|
|
688
|
+
* carries the Host's auth block. Gating on `hasAccount` / `needsAccount` made
|
|
689
|
+
* the "middle state" (grant still alive, saved account cleared) a dead end:
|
|
690
|
+
* the full-screen setup form lives behind `!data`, and the section card
|
|
691
|
+
* vanished with `hasAccount` — the user was locked out of their own account
|
|
692
|
+
* with no re-entry path until the grant died. This is a single-point-of-truth
|
|
693
|
+
* declaration; callers should use `shouldShowAccountManagement(auth)` rather
|
|
694
|
+
* than duplicating `auth !== null`.
|
|
695
|
+
*/
|
|
696
|
+
function shouldShowAccountManagement(auth) {
|
|
697
|
+
return auth !== null;
|
|
698
|
+
}
|
|
669
699
|
var GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES, CLIENT_CODE, COOLDOWN_TEXT, REFUSAL_TEXT;
|
|
670
700
|
var init_snapshot = __esmMin((() => {
|
|
671
701
|
init_format();
|
|
@@ -769,7 +799,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
769
799
|
tabActive: {
|
|
770
800
|
color: "var(--dsw-alias-label-primary)",
|
|
771
801
|
fontWeight: 600,
|
|
772
|
-
borderBottom: "2px solid var(--agnes-brand, #
|
|
802
|
+
borderBottom: "2px solid var(--agnes-brand, #1E40AF)"
|
|
773
803
|
},
|
|
774
804
|
updated: {
|
|
775
805
|
color: "var(--dsw-alias-label-secondary)",
|
|
@@ -946,7 +976,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
946
976
|
barFill: {
|
|
947
977
|
height: "100%",
|
|
948
978
|
borderRadius: 3,
|
|
949
|
-
background: "var(--agnes-brand, #
|
|
979
|
+
background: "var(--agnes-brand, #1E40AF)"
|
|
950
980
|
},
|
|
951
981
|
barFillWarn: { background: "var(--dsw-alias-state-warn-primary)" },
|
|
952
982
|
barFillError: { background: "var(--dsw-alias-state-error-primary)" },
|
|
@@ -1082,7 +1112,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
1082
1112
|
width: "100%",
|
|
1083
1113
|
minHeight: 2,
|
|
1084
1114
|
borderRadius: 2,
|
|
1085
|
-
background: "var(--agnes-brand, #
|
|
1115
|
+
background: "var(--agnes-brand, #1E40AF)"
|
|
1086
1116
|
},
|
|
1087
1117
|
usageAxis: {
|
|
1088
1118
|
display: "flex",
|
|
@@ -1208,7 +1238,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
1208
1238
|
width: 15,
|
|
1209
1239
|
height: 15,
|
|
1210
1240
|
cursor: "pointer",
|
|
1211
|
-
accentColor: "var(--agnes-brand, #
|
|
1241
|
+
accentColor: "var(--agnes-brand, #1E40AF)",
|
|
1212
1242
|
margin: 0
|
|
1213
1243
|
},
|
|
1214
1244
|
modelName: {
|
|
@@ -2642,6 +2672,42 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
2642
2672
|
//#endregion
|
|
2643
2673
|
//#region src/client/agnescode-tab.ts
|
|
2644
2674
|
/**
|
|
2675
|
+
* The tab's rendering decisions, lifted out of the JSX so they can be nailed.
|
|
2676
|
+
*
|
|
2677
|
+
* Three decisions here were each a REAL repair, and each is explained in a
|
|
2678
|
+
* comment at its old site — but none of them had a test, so the next person to
|
|
2679
|
+
* "simplify" the JSX could undo any of them silently. Same shape as `barPlan`
|
|
2680
|
+
* in `panel-page.ts`: the decision is a pure function of (state, error), so the
|
|
2681
|
+
* suite drives it directly instead of scraping a layout.
|
|
2682
|
+
*
|
|
2683
|
+
* The three, in the order they were learned:
|
|
2684
|
+
*
|
|
2685
|
+
* 1. `showError` — a route that ANSWERS with a failure is not "no local login
|
|
2686
|
+
* state". Naming the status is what stops a broken Host route from looking
|
|
2687
|
+
* exactly like a signed-out desktop App.
|
|
2688
|
+
* 2. `credentialCard` — withheld ONLY while a failed read leaves us knowing
|
|
2689
|
+
* NOTHING (`state === null`). Then `linked` is false and the unlinked copy
|
|
2690
|
+
* would accuse the reader's desktop App of not being signed in — a claim
|
|
2691
|
+
* this tab has no evidence for. Once ANY reading has landed, the card stays
|
|
2692
|
+
* up through a later failed poll: the honest statement is the error line
|
|
2693
|
+
* plus the LAST KNOWN account, not a blank. (`load` therefore never calls
|
|
2694
|
+
* `setState(null)` — clearing on failure is exactly the regression this
|
|
2695
|
+
* pins.)
|
|
2696
|
+
* 3. `linked` — reads `loggedIn` off the state, never inferred from the
|
|
2697
|
+
* absence of an error.
|
|
2698
|
+
*
|
|
2699
|
+
* @param {AgnescodeState|null} state - the last body the route answered, if any.
|
|
2700
|
+
* @param {string|null} error - the last read failure, if any.
|
|
2701
|
+
* @returns {{showError: boolean, credentialCard: boolean, linked: boolean}}
|
|
2702
|
+
*/
|
|
2703
|
+
function agnescodeView(state, error) {
|
|
2704
|
+
return {
|
|
2705
|
+
showError: error !== null,
|
|
2706
|
+
credentialCard: !(state === null && error !== null),
|
|
2707
|
+
linked: state?.loggedIn === true
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
/**
|
|
2645
2711
|
* The AgnesCode tab body.
|
|
2646
2712
|
* @param {object} props
|
|
2647
2713
|
* @param {Tt} props.tt - the dictionary.
|
|
@@ -2765,7 +2831,8 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
2765
2831
|
if (alive.current) load();
|
|
2766
2832
|
}, [load]);
|
|
2767
2833
|
const enabled = state?.enabled === true;
|
|
2768
|
-
const
|
|
2834
|
+
const view = agnescodeView(state, error);
|
|
2835
|
+
const loggedIn = view.linked;
|
|
2769
2836
|
const models = Array.isArray(state?.models) ? state.models : [];
|
|
2770
2837
|
const attempts = Array.isArray(state?.harvest?.attempts) ? state.harvest.attempts : [];
|
|
2771
2838
|
const balance = state?.balance ?? null;
|
|
@@ -2773,7 +2840,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
2773
2840
|
fontSize: 12,
|
|
2774
2841
|
color: "var(--dsw-alias-label-secondary)",
|
|
2775
2842
|
marginBottom: 12
|
|
2776
|
-
} }, tt("agnescode.desc")),
|
|
2843
|
+
} }, tt("agnescode.desc")), view.showError ? h("div", {
|
|
2777
2844
|
style: S.formError,
|
|
2778
2845
|
role: "alert"
|
|
2779
2846
|
}, format(tt("agnescode.error"), { error })) : null, h("label", { style: {
|
|
@@ -2805,7 +2872,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
2805
2872
|
} }, tt("agnescode.awaitingHarvest")) : state.providerRegistered === true ? null : h("div", { style: {
|
|
2806
2873
|
...S.muted,
|
|
2807
2874
|
fontSize: 12
|
|
2808
|
-
} }, tt("agnescode.unregistered")) : null,
|
|
2875
|
+
} }, tt("agnescode.unregistered")) : null, view.credentialCard ? h("div", { style: {
|
|
2809
2876
|
...S.card,
|
|
2810
2877
|
marginTop: 4
|
|
2811
2878
|
} }, loggedIn ? h("div", null, h("div", { style: {
|
|
@@ -2842,7 +2909,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
2842
2909
|
},
|
|
2843
2910
|
onClick: () => void harvest(),
|
|
2844
2911
|
disabled: harvestBusy
|
|
2845
|
-
}, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest")))), attempts.length > 0 ? h("div", {
|
|
2912
|
+
}, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest")))) : null, attempts.length > 0 ? h("div", {
|
|
2846
2913
|
style: {
|
|
2847
2914
|
...S.muted,
|
|
2848
2915
|
fontSize: 12,
|
|
@@ -3258,7 +3325,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
3258
3325
|
style: S.chip,
|
|
3259
3326
|
title: auth.error || guidance || ""
|
|
3260
3327
|
}, tt("auth.needsLogin")) : h("span", { style: S.chip }, tt("auth.selfRenew"));
|
|
3261
|
-
const authManage = auth
|
|
3328
|
+
const authManage = shouldShowAccountManagement(auth);
|
|
3262
3329
|
const tabBar = h("div", {
|
|
3263
3330
|
style: S.tabBar,
|
|
3264
3331
|
role: "tablist"
|
|
@@ -3546,6 +3613,7 @@ var dsh_connect_sensenova_token_plan_client = (function() {
|
|
|
3546
3613
|
viewOf,
|
|
3547
3614
|
errorOfStatus,
|
|
3548
3615
|
barPlan,
|
|
3616
|
+
agnescodeView,
|
|
3549
3617
|
dictionaries: Object.freeze({
|
|
3550
3618
|
zh,
|
|
3551
3619
|
en
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { t as DEFAULT_REASONING_EFFORT, w as name } from "./llm-models-BZfJOFax.js";
|
|
2
|
+
import { i as buildAgnescodeDescriptors, n as AGNESCODE_PROVIDER_ID, r as agnescodeRoster, t as AGNESCODE_DISPLAY_NAME } from "./agnescode-models-w02GyYgT.js";
|
|
3
|
+
import { i as buildRetryPolicyConfig, n as STREAM_IDLE_TIMEOUT_MS, r as createWrappedPiAiAdapter, t as REQUEST_IMAGE_BUDGETS } from "./pi-ai-adapter-core-D0iGPjK-.js";
|
|
4
|
+
import { createProvider } from "@earendil-works/pi-ai";
|
|
5
|
+
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
6
|
+
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
7
|
+
|
|
8
|
+
//#region src/host/agnescode-llm-adapter.ts
|
|
9
|
+
/**
|
|
10
|
+
* The peer-dependent half of the directly-registered AgnesCode provider —
|
|
11
|
+
* the desktop-app upstream (ROADMAP §6.3).
|
|
12
|
+
*
|
|
13
|
+
* ONE `PiAiAdapter` carrying ONE profile (`agnescode` → the credential's
|
|
14
|
+
* per-account BFF base), an INERT pi-ai auth plane (the AgnesCode JWT is
|
|
15
|
+
* resolved per request from the plugin's own credential store, pi-ai never
|
|
16
|
+
* manufactures it), and no token baked into the profile — the picker
|
|
17
|
+
* advertises models without one and a request fails at resolve time, where the
|
|
18
|
+
* panel status is visible.
|
|
19
|
+
*
|
|
20
|
+
* Both IMAGE hooks are wired, exactly as the Token Plan adapter
|
|
21
|
+
* (`llm-adapter.ts`) wires them — the shape a working vision route has. The
|
|
22
|
+
* catalogue still declares no modality field (`model_type: "text"`), so the
|
|
23
|
+
* vision CLAIM is borrowed from `PROBED_VISION` in the peer-free
|
|
24
|
+
* `agnescode-models.ts` (the Agnes family is the same gateway the Token Plan
|
|
25
|
+
* side probed accepting `image_url`); the descriptor's `input` and the adapter's
|
|
26
|
+
* hooks must agree, or a message carrying an image reaches the durable
|
|
27
|
+
* attachment service with no way to locate the image and fails. Wiring the
|
|
28
|
+
* hooks without the claim would be inert; making the claim without the hooks is
|
|
29
|
+
* the bug this closes.
|
|
30
|
+
*
|
|
31
|
+
* The descriptor mapping lives in the peer-free `agnescode-models.ts`, so this
|
|
32
|
+
* module holds only assembly against the runtime and is exercised by the
|
|
33
|
+
* wiring/e2e checks, exactly as `llm-adapter.ts` is. The assembly MECHANISM
|
|
34
|
+
* itself (auth plane, image budgets/hooks, 429 correction) is shared with the
|
|
35
|
+
* Token Plan route through `pi-ai-adapter-core.ts` — what stays here is the
|
|
36
|
+
* AgnesCode configuration: provider id, model builder, credential resolver, and
|
|
37
|
+
* a profile that pins the SAME effort default as the Token Plan route (probed
|
|
38
|
+
* live 2026-10-03: the BFF accepts `reasoning_effort` and defaults thinking ON,
|
|
39
|
+
* so an unselected effort must not reach pi-ai as "no effort").
|
|
40
|
+
*
|
|
41
|
+
* @module dsh-connect-agnes-token-plan/agnescode-llm-adapter
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Assemble the AgnesCode adapter for one credential/catalogue snapshot.
|
|
45
|
+
*
|
|
46
|
+
* A fresh instance per rebuild is deliberate: `PiAiAdapter` memoizes the
|
|
47
|
+
* profiles snapshot internally, so the caller REPLACES the registered
|
|
48
|
+
* adapter when the credential or catalogue changes and emits
|
|
49
|
+
* `llm/adapters-updated`.
|
|
50
|
+
* @param {object} options - wiring.
|
|
51
|
+
* @param {object[]} [options.rows] - the AgnesCode roster rows (`agnescodeRoster`).
|
|
52
|
+
* @param {string} options.bffBase - the credential's pinned per-account BFF base.
|
|
53
|
+
* @param {() => Promise<string>} options.resolveToken - resolves the live
|
|
54
|
+
* AgnesCode JWT per request (re-harvested by the caller when expired).
|
|
55
|
+
* @param {(service: string) => unknown} [options.get] - service resolver for
|
|
56
|
+
* the image hooks (`attachments`, `fs`); resolved lazily per request because
|
|
57
|
+
* a service may register after this adapter is built.
|
|
58
|
+
* @returns {{adapter: object, providerIds: string[]}} the adapter and the ids
|
|
59
|
+
* it owns.
|
|
60
|
+
*/
|
|
61
|
+
function createAgnescodeAdapter({ rows, bffBase, resolveToken, get } = {}) {
|
|
62
|
+
const models = buildAgnescodeDescriptors(rows ?? agnescodeRoster(null), { bffBase: String(bffBase ?? "") });
|
|
63
|
+
const provider = {
|
|
64
|
+
...createProvider({
|
|
65
|
+
id: AGNESCODE_PROVIDER_ID,
|
|
66
|
+
name: AGNESCODE_DISPLAY_NAME,
|
|
67
|
+
auth: { apiKey: {
|
|
68
|
+
name: "AgnesCode access token",
|
|
69
|
+
async resolve({ credential } = {}) {
|
|
70
|
+
const token = credential?.key;
|
|
71
|
+
return token === void 0 || token === "" ? void 0 : {
|
|
72
|
+
auth: { apiKey: token },
|
|
73
|
+
source: AGNESCODE_DISPLAY_NAME
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
} },
|
|
77
|
+
models,
|
|
78
|
+
api: openAICompletionsApi()
|
|
79
|
+
}),
|
|
80
|
+
getModels: () => models
|
|
81
|
+
};
|
|
82
|
+
const profiles = /* @__PURE__ */ new Map([[AGNESCODE_PROVIDER_ID, {
|
|
83
|
+
provider: AGNESCODE_PROVIDER_ID,
|
|
84
|
+
displayName: AGNESCODE_DISPLAY_NAME,
|
|
85
|
+
streamIdleTimeoutMs: STREAM_IDLE_TIMEOUT_MS,
|
|
86
|
+
retryPolicy: resolveRetryPolicy(buildRetryPolicyConfig(), `${name}.${AGNESCODE_PROVIDER_ID}.retryPolicy`),
|
|
87
|
+
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
88
|
+
modelErrors: /* @__PURE__ */ new Map(),
|
|
89
|
+
reasoning: DEFAULT_REASONING_EFFORT,
|
|
90
|
+
...REQUEST_IMAGE_BUDGETS,
|
|
91
|
+
piProvider: provider
|
|
92
|
+
}]]);
|
|
93
|
+
return {
|
|
94
|
+
adapter: createWrappedPiAiAdapter({
|
|
95
|
+
profiles,
|
|
96
|
+
resolveApiKey: async () => resolveToken?.() ?? "",
|
|
97
|
+
get
|
|
98
|
+
}),
|
|
99
|
+
providerIds: [AGNESCODE_PROVIDER_ID]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { createAgnescodeAdapter };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as num,
|
|
1
|
+
import { D as num, P as str, i as PROBED_VISION, k as obj, w as name } from "./llm-models-BZfJOFax.js";
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
@@ -610,8 +610,8 @@ function parse(raw) {
|
|
|
610
610
|
* @typedef {object} CatalogStore
|
|
611
611
|
* @property {() => Promise<object[]>} list - stored entries, `[]` when none usable.
|
|
612
612
|
* @property {() => Promise<string[]>} listEnabledIds - allow-list; `[]` means "no filter".
|
|
613
|
-
* @property {(entries: object[], enabledModelIds?: string[]) => Promise<
|
|
614
|
-
* @property {(ids: string[]) => Promise<
|
|
613
|
+
* @property {(entries: object[], enabledModelIds?: string[]) => Promise<boolean>} replace - swap the catalog, preserving the allow-list unless given a new one; returns whether the record actually reached disk.
|
|
614
|
+
* @property {(ids: string[]) => Promise<boolean>} setEnabledIds - swap ONLY the allow-list; same return contract.
|
|
615
615
|
* @property {() => Promise<void>} clear - remove the stored catalog.
|
|
616
616
|
*/
|
|
617
617
|
/**
|
|
@@ -662,15 +662,23 @@ function createFileCatalogStore(options = {}) {
|
|
|
662
662
|
* The temp path is process-plus-clock unique (`state-store.ts`'s
|
|
663
663
|
* `temporaryOf`), so two Host processes sharing this directory never write
|
|
664
664
|
* the same temp name and `rename` each other's half-written file away.
|
|
665
|
+
*
|
|
666
|
+
* Returns whether the record actually reached disk. The failure is still
|
|
667
|
+
* swallowed — a read-only Home must not break the panel — but the caller has
|
|
668
|
+
* to know it happened: an in-memory record the disk does not hold must not
|
|
669
|
+
* be treated as settled (PITFALLS §40).
|
|
670
|
+
* @returns {Promise<boolean>} `true` when disk now holds `held`, `false` when the write failed.
|
|
665
671
|
*/
|
|
666
672
|
const persist = async () => {
|
|
667
|
-
if (held === null) return;
|
|
673
|
+
if (held === null) return true;
|
|
668
674
|
const temporary = temporaryOf(stateDir$1, "catalog.json", now);
|
|
669
675
|
try {
|
|
670
676
|
await ensureStateDir(stateDir$1);
|
|
671
677
|
await writeStateFile(file, JSON.stringify(held), { temporary });
|
|
678
|
+
return true;
|
|
672
679
|
} catch {
|
|
673
680
|
await rm(temporary, { force: true }).catch(() => {});
|
|
681
|
+
return false;
|
|
674
682
|
}
|
|
675
683
|
};
|
|
676
684
|
return {
|
|
@@ -699,7 +707,7 @@ function createFileCatalogStore(options = {}) {
|
|
|
699
707
|
* PRESERVED across a catalog refresh unless a new one is supplied.
|
|
700
708
|
* @param {object[]} entries - the fresh catalog entries.
|
|
701
709
|
* @param {string[]} [enabledModelIds] - an optional replacement allow-list.
|
|
702
|
-
* @returns {Promise<
|
|
710
|
+
* @returns {Promise<boolean>} whether the record reached disk.
|
|
703
711
|
*/
|
|
704
712
|
async replace(entries, enabledModelIds) {
|
|
705
713
|
const current = await seen();
|
|
@@ -711,9 +719,12 @@ function createFileCatalogStore(options = {}) {
|
|
|
711
719
|
enabledModelIds: enabledModelIds === void 0 ? kept : normalizeEnabledIds(enabledModelIds)
|
|
712
720
|
};
|
|
713
721
|
cache.remember(held);
|
|
714
|
-
|
|
722
|
+
return persist();
|
|
715
723
|
},
|
|
716
|
-
/**
|
|
724
|
+
/**
|
|
725
|
+
* Replace ONLY the curated allow-list, keeping the cached catalog.
|
|
726
|
+
* @returns {Promise<boolean>} whether the record reached disk.
|
|
727
|
+
*/
|
|
717
728
|
async setEnabledIds(ids) {
|
|
718
729
|
const current = await seen();
|
|
719
730
|
const entries = current === null ? [] : current.entries;
|
|
@@ -725,7 +736,7 @@ function createFileCatalogStore(options = {}) {
|
|
|
725
736
|
enabledModelIds: normalizeEnabledIds(ids)
|
|
726
737
|
};
|
|
727
738
|
cache.remember(held);
|
|
728
|
-
|
|
739
|
+
return persist();
|
|
729
740
|
},
|
|
730
741
|
/** Remove the stored catalog (used when the API key is forgotten). */
|
|
731
742
|
async clear() {
|
|
@@ -1394,12 +1405,19 @@ const AGNESCODE_FALLBACK_MODELS = Object.freeze([
|
|
|
1394
1405
|
* is the peer-dependent half.
|
|
1395
1406
|
*
|
|
1396
1407
|
* Three decisions carried here:
|
|
1397
|
-
* 1. `reasoning:
|
|
1398
|
-
*
|
|
1399
|
-
* chat
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
1408
|
+
* 1. `reasoning: true` + a BFF-specific `thinkingLevelMap` — probed LIVE on
|
|
1409
|
+
* 2026-10-03 against the per-account BFF (`agnes-3.0-flash`,
|
|
1410
|
+
* `{bffBase}/v1/chat/completions`): the OpenAI-compatible `reasoning_effort`
|
|
1411
|
+
* ladder `none`/`low`/`medium`/`high` is ACCEPTED (HTTP 200 under every
|
|
1412
|
+
* one) and the BFF returns `message.reasoning_content` under each — even
|
|
1413
|
+
* with NO thinking field at all (the v1 shape), which is why v1's
|
|
1414
|
+
* `reasoning: false` never actually turned thinking off, it only hid the
|
|
1415
|
+
* selector. `off` is `null` on purpose: `reasoning_effort:"none"` does NOT
|
|
1416
|
+
* disable thinking (still 188 reasoning tokens), and the desktop App's real
|
|
1417
|
+
* off switch (`request_params.agnes_thinking_enabled:false`) is not
|
|
1418
|
+
* expressible in pi-ai's string-valued map. Offering "off" would promise
|
|
1419
|
+
* "thinking off" and deliver "still thinking". `xhigh`/`max` stay closed
|
|
1420
|
+
* until a probe proves them on the BFF.
|
|
1403
1421
|
* 2. The base URL is PER-ACCOUNT (`bffPublicBaseUrl` from the session
|
|
1404
1422
|
* file), so it rides through the builder's options instead of a module
|
|
1405
1423
|
* constant — the reference reverse-proxy's hardcoded `.com` constant is
|
|
@@ -1515,6 +1533,33 @@ function filterAgnescodeRows(rows, enabledIds) {
|
|
|
1515
1533
|
return list.filter((row) => keep.has(str(row?.id, "")));
|
|
1516
1534
|
}
|
|
1517
1535
|
/**
|
|
1536
|
+
* The thinking level map this provider offers, keyed on pi-ai's ladder.
|
|
1537
|
+
*
|
|
1538
|
+
* The BFF's `reasoning_effort` ladder was probed live on 2026-10-03
|
|
1539
|
+
* (`agnes-3.0-flash`): `none`/`low`/`medium`/`high` are all ACCEPTED (HTTP 200)
|
|
1540
|
+
* — but `none` does NOT disable thinking (the response still carried
|
|
1541
|
+
* `reasoning_content`, 188 reasoning tokens). The desktop App's real off switch
|
|
1542
|
+
* is `request_params.agnes_thinking_enabled:false` (or
|
|
1543
|
+
* `request_params.thinking_effort:"off"`), which a string-valued thinking map
|
|
1544
|
+
* cannot emit. So `off` is DELIBERATELY `null`: offering it would promise
|
|
1545
|
+
* "thinking off" and deliver "still thinking" — the same class of silent lie
|
|
1546
|
+
* `docs/DSH-LLM-DEVELOP.md` §4 warns about for the opposite direction. The
|
|
1547
|
+
* levels pi-ai actually sends (`low`/`medium`/`high`) are the ones probed 200.
|
|
1548
|
+
* `xhigh`/`max` stay closed until a probe proves them on the BFF.
|
|
1549
|
+
* @returns {object} the level → wire-spelling map.
|
|
1550
|
+
*/
|
|
1551
|
+
function agnescodeThinkingLevelMap() {
|
|
1552
|
+
return {
|
|
1553
|
+
off: null,
|
|
1554
|
+
minimal: null,
|
|
1555
|
+
low: "low",
|
|
1556
|
+
medium: "medium",
|
|
1557
|
+
high: "high",
|
|
1558
|
+
xhigh: null,
|
|
1559
|
+
max: null
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1518
1563
|
* Map one AgnesCode row onto the pi-ai model descriptor the adapter offers.
|
|
1519
1564
|
* @param {object} row - an {@link agnescodeRoster} row (must carry `id`).
|
|
1520
1565
|
* @param {object} [options] - `{ bffBase }` — the pinned per-account base.
|
|
@@ -1533,7 +1578,8 @@ function agnescodeToDescriptor(row, options = {}) {
|
|
|
1533
1578
|
provider: AGNESCODE_PROVIDER_ID,
|
|
1534
1579
|
baseUrl: bffBase,
|
|
1535
1580
|
input: vision ? ["text", "image"] : ["text"],
|
|
1536
|
-
reasoning:
|
|
1581
|
+
reasoning: true,
|
|
1582
|
+
thinkingLevelMap: agnescodeThinkingLevelMap(),
|
|
1537
1583
|
cost: { ...NO_COST },
|
|
1538
1584
|
contextWindow: num(row?.contextWindow) ?? 512e3,
|
|
1539
1585
|
maxTokens: num(row?.maxOutputLength) ?? 32e3,
|