dsh-connect-agnes-token-plan 0.10.1 → 0.10.2
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 +11 -0
- package/client.js +34 -103
- package/lib/agnescode-llm-adapter.js +1 -1
- package/lib/agnescode-models.js +73 -30
- package/lib/index.js +143 -95
- package/lib/llm-adapter.js +1 -1
- package/lib/llm-models.js +43 -36
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
本文件只记**公开行为变化**(新增能力、破坏性改动、重要修复)。实现细节、重构与测试加固请直接看 `git log`。
|
|
4
4
|
|
|
5
|
+
## [0.10.2] — 2026-10-10
|
|
6
|
+
|
|
7
|
+
### 修复:视频状态轮询断点续查、额度渲染与模型花名册的几处真实缺陷 + 凭据隔离加固
|
|
8
|
+
|
|
9
|
+
- **修(视频工具):异步视频轮询被拒时原本不带 `video_id` 且只走「超时 / 超轮次」两路径**,agent 首次查询拿不到 task id 就无法续查,任务卡在「查不到」。`video-client` 现改为携带 `video_id` 并有限重试(`queryRetries=2` → 初查 + 2 次重试 = 3 次调用),与超时 / 超轮次两条路径对齐。新增 `video.test.mjs` 覆盖耗尽路径与 `queryRetries=0` 边界。
|
|
10
|
+
- **修(额度渲染):`usagePct` 有值而 `used` 缺失时,`count(null)` 被渲染成 `0`,面板会打印「已用 0 / N」**(F1)。`UsageTotals` 四个主计数另加 `typeof` 门:缺席字段不再画成「0」——真实数据流下此门不触发(`countOf` 已把缺席平台字段压成数字 0、`overview` 失败时 `quota.totals:null` 见红线⑥ / ADR-007),属渲染层自防御,防未来 partial 对象被构造出来。
|
|
11
|
+
- **修(AgnesCode 花名册):两处目录正确性缺陷**。(F5)排除别名 `id === EXCLUDED_IDS[0]` 改为 `.includes(id)`,第二个别名不再静默失效、继续混进花名册;(F10)`provider-controls` 的 `idList` 用 `String(id)` 强转,`null` 会变成字符串 `"null"` 这个假模型 id 出现在候选列表里,改用 `models.ts` 的 `rosterIds`(只收非空字符串)。
|
|
12
|
+
- **修(凭据隔离):AgnesCode 的 DPAPI PowerShell 子进程不再继承含凭据的父进程 env**,新增 `dpapiChildEnv` 剥除四枚凭据名(`AGNES_PASSWORD` / `AGNES_USERNAME` / `AGNES_TOKEN_PLAN_API_KEY` / `AGNESCODE_CREDENTIAL`),与全仓凭据 REF 集合逐字一致——降低解密第三方桌面端会话文件那一步的凭据泄漏半径(红线①)。`harvest attempts[].detail` 与 `state().error` 出口统一过 `redactSecrets`。
|
|
13
|
+
- **内部加固 / 清理(不属公开行为变化,详见 `git log`)**:收敛三轮四域代码审查的约 22 项缺陷与计数器 / 方法归一化 / 单飞泄漏 / JSDoc 归位等低危项;删除 25 个零读者 style token(`details*` / `grant*` / `models*` / `trend*` / `poolsGrid` / `primaryHover`)与全仓零引用的孤儿 `jsqr` devDependency;统一 `countOf` 与 `Number(x)||0` 两处数值惯用法、`requestMethod` 六路由共用、BFF 门控头收敛为单一 `AGNESCODE_BASE_HEADERS`;CI `build-gate` 确保暂存目录存在再 `mkdtemp`。上述均以新增断言钉住,无破坏性改动。
|
|
14
|
+
- 影响面:`src/{host,client}/`(多文件)、`lib/`、`client.js`(随 src 同 commit 重建,构建零漂移)、`test/`(多套件 + 断言)、`package.json`(版本 + 去孤儿依赖)。`npm test` 全绿:28 套件 + tsc / build / dup / e2e 四门禁通过,e2e 66/66 PASSED。
|
|
15
|
+
|
|
5
16
|
## [0.10.1] — 2026-10-06
|
|
6
17
|
|
|
7
18
|
### 修复:AgnesCode 截图里的真实账号昵称已脱敏(该图曾随 0.10.0 发布)
|
package/client.js
CHANGED
|
@@ -550,11 +550,16 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
550
550
|
* Why two bases: the catalogue mixes them — windows and ceilings arrive as
|
|
551
551
|
* powers of two (1048576), while the plugin's own 128k fallback is the round
|
|
552
552
|
* decimal 128 000. A flat /1000 rounding once printed "1049k" for the 1M
|
|
553
|
-
* window and it read like a placeholder bug
|
|
554
|
-
* the
|
|
555
|
-
*
|
|
556
|
-
* ≥ 1G goes to G — the account-lifetime token total (billions)
|
|
557
|
-
* as a four-digit "5214M"
|
|
553
|
+
* window and it read like a placeholder bug. The guards below therefore run
|
|
554
|
+
* in the order the code checks them, largest first:
|
|
555
|
+
*
|
|
556
|
+
* - anything ≥ 1G goes to G — the account-lifetime token total (billions)
|
|
557
|
+
* must not print as a four-digit "5214M";
|
|
558
|
+
* - anything ≥ 1M goes to M — a 1M window (1048576) reads "1M", never
|
|
559
|
+
* "1049k" or "1024k";
|
|
560
|
+
* - below 1M, figures divisible by 1000 keep the decimal reading they were
|
|
561
|
+
* written with (128000 → "128K");
|
|
562
|
+
* - binary-only figures get the binary one (262144 → "256K", 65536 → "64K").
|
|
558
563
|
*/
|
|
559
564
|
function tokenSize(value) {
|
|
560
565
|
const number = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : 0;
|
|
@@ -977,12 +982,6 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
977
982
|
borderRadius: 12,
|
|
978
983
|
padding: 16
|
|
979
984
|
},
|
|
980
|
-
poolsGrid: {
|
|
981
|
-
display: "grid",
|
|
982
|
-
gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 320px), 1fr))",
|
|
983
|
-
gap: 12,
|
|
984
|
-
alignItems: "start"
|
|
985
|
-
},
|
|
986
985
|
cardHead: {
|
|
987
986
|
display: "flex",
|
|
988
987
|
alignItems: "center",
|
|
@@ -1098,40 +1097,6 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
1098
1097
|
},
|
|
1099
1098
|
barFillWarn: { background: "var(--dsw-alias-state-warn-primary)" },
|
|
1100
1099
|
barFillError: { background: "var(--dsw-alias-state-error-primary)" },
|
|
1101
|
-
details: {
|
|
1102
|
-
marginTop: 12,
|
|
1103
|
-
paddingTop: 10,
|
|
1104
|
-
borderTop: "1px solid var(--dsw-alias-border-l1)"
|
|
1105
|
-
},
|
|
1106
|
-
detailsSummary: {
|
|
1107
|
-
fontSize: 12,
|
|
1108
|
-
color: "var(--dsw-alias-label-secondary)",
|
|
1109
|
-
cursor: "pointer",
|
|
1110
|
-
userSelect: "none"
|
|
1111
|
-
},
|
|
1112
|
-
detailsBody: {
|
|
1113
|
-
display: "flex",
|
|
1114
|
-
flexDirection: "column",
|
|
1115
|
-
gap: 10,
|
|
1116
|
-
marginTop: 10
|
|
1117
|
-
},
|
|
1118
|
-
grant: {
|
|
1119
|
-
fontSize: 12,
|
|
1120
|
-
color: "var(--dsw-alias-label-secondary)"
|
|
1121
|
-
},
|
|
1122
|
-
models: {
|
|
1123
|
-
display: "flex",
|
|
1124
|
-
flexWrap: "wrap",
|
|
1125
|
-
gap: 6
|
|
1126
|
-
},
|
|
1127
|
-
modelTag: {
|
|
1128
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
1129
|
-
fontSize: 11,
|
|
1130
|
-
padding: "2px 6px",
|
|
1131
|
-
borderRadius: 6,
|
|
1132
|
-
background: "var(--dsw-alias-bg-layer-2)",
|
|
1133
|
-
border: "1px solid var(--dsw-alias-border-l1)"
|
|
1134
|
-
},
|
|
1135
1100
|
trendHead: {
|
|
1136
1101
|
display: "flex",
|
|
1137
1102
|
alignItems: "baseline",
|
|
@@ -1145,39 +1110,6 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
1145
1110
|
color: "var(--dsw-alias-label-secondary)",
|
|
1146
1111
|
fontWeight: 500
|
|
1147
1112
|
},
|
|
1148
|
-
trendRow: {
|
|
1149
|
-
display: "flex",
|
|
1150
|
-
flexDirection: "column",
|
|
1151
|
-
gap: 8,
|
|
1152
|
-
padding: "10px 0",
|
|
1153
|
-
borderBottom: "1px solid var(--dsw-alias-border-l1)"
|
|
1154
|
-
},
|
|
1155
|
-
trendRowHead: {
|
|
1156
|
-
display: "flex",
|
|
1157
|
-
alignItems: "baseline",
|
|
1158
|
-
justifyContent: "space-between",
|
|
1159
|
-
gap: 12,
|
|
1160
|
-
minWidth: 0
|
|
1161
|
-
},
|
|
1162
|
-
trendModel: {
|
|
1163
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
1164
|
-
fontSize: 12,
|
|
1165
|
-
minWidth: 0,
|
|
1166
|
-
overflow: "hidden",
|
|
1167
|
-
textOverflow: "ellipsis",
|
|
1168
|
-
whiteSpace: "nowrap"
|
|
1169
|
-
},
|
|
1170
|
-
trendCredits: {
|
|
1171
|
-
fontSize: 13,
|
|
1172
|
-
fontWeight: 600,
|
|
1173
|
-
fontVariantNumeric: "tabular-nums"
|
|
1174
|
-
},
|
|
1175
|
-
trendBar: {
|
|
1176
|
-
height: 6,
|
|
1177
|
-
borderRadius: 3,
|
|
1178
|
-
background: "var(--dsw-alias-bg-layer-2)",
|
|
1179
|
-
overflow: "hidden"
|
|
1180
|
-
},
|
|
1181
1113
|
trendLegend: {
|
|
1182
1114
|
marginTop: 10,
|
|
1183
1115
|
fontSize: 11,
|
|
@@ -1299,7 +1231,6 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
1299
1231
|
color: "var(--dsw-alias-label-primary-foreground)",
|
|
1300
1232
|
cursor: "pointer"
|
|
1301
1233
|
},
|
|
1302
|
-
primaryHover: { background: "var(--dsw-alias-button-primary-hover)" },
|
|
1303
1234
|
primaryBusy: {
|
|
1304
1235
|
opacity: .6,
|
|
1305
1236
|
cursor: "default"
|
|
@@ -1761,6 +1692,10 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
1761
1692
|
* @returns {unknown} the tools row element.
|
|
1762
1693
|
*/
|
|
1763
1694
|
function RosterTools({ query, setQuery, name, busy, tickedCount, visible, bulk, tt }) {
|
|
1695
|
+
const countLabel = format(tt("llm.rosterCount"), {
|
|
1696
|
+
selected: tickedCount,
|
|
1697
|
+
total: visible.length
|
|
1698
|
+
});
|
|
1764
1699
|
return h("div", { style: S.rosterTools }, h("input", {
|
|
1765
1700
|
type: "search",
|
|
1766
1701
|
style: {
|
|
@@ -1777,14 +1712,8 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
1777
1712
|
onChange: (event) => setQuery(event.target.value)
|
|
1778
1713
|
}), h("span", {
|
|
1779
1714
|
style: S.rosterCount,
|
|
1780
|
-
title:
|
|
1781
|
-
|
|
1782
|
-
total: visible.length
|
|
1783
|
-
})
|
|
1784
|
-
}, format(tt("llm.rosterCount"), {
|
|
1785
|
-
selected: tickedCount,
|
|
1786
|
-
total: visible.length
|
|
1787
|
-
})), h("button", {
|
|
1715
|
+
title: countLabel
|
|
1716
|
+
}, countLabel), h("button", {
|
|
1788
1717
|
type: "button",
|
|
1789
1718
|
style: S.rosterBulk,
|
|
1790
1719
|
disabled: busy === true || visible.length === 0,
|
|
@@ -2445,10 +2374,6 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2445
2374
|
role: "alert"
|
|
2446
2375
|
}, switchError) : null);
|
|
2447
2376
|
}
|
|
2448
|
-
/** Ids off a snapshot array field, coerced to strings and guarded against `null`. */
|
|
2449
|
-
function idList(value) {
|
|
2450
|
-
return Array.isArray(value) ? value.map((id) => String(id)) : [];
|
|
2451
|
-
}
|
|
2452
2377
|
/**
|
|
2453
2378
|
* The live draw-tool switch (docs/PROVIDER-HOT-RELOAD.md, same discipline
|
|
2454
2379
|
* as `ProviderSwitch`). Posts `{ enabled }` / `{ modelId }` to the plugin's
|
|
@@ -2466,7 +2391,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2466
2391
|
modelKey: "drawModelId",
|
|
2467
2392
|
enabled: llm?.drawEnabled === true,
|
|
2468
2393
|
hasKey: llm?.hasApiKey === true,
|
|
2469
|
-
candidates:
|
|
2394
|
+
candidates: rosterIds(llm?.drawCandidateIds),
|
|
2470
2395
|
preferred: llm?.drawPreferredModel != null ? String(llm.drawPreferredModel) : null,
|
|
2471
2396
|
effective: String(llm?.drawModel ?? ""),
|
|
2472
2397
|
onDone,
|
|
@@ -2487,7 +2412,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2487
2412
|
* reader wondering why a seconds-based model sits next to a frame-based one.
|
|
2488
2413
|
*/
|
|
2489
2414
|
function VideoSwitch({ llm, onDone, tt }) {
|
|
2490
|
-
const excluded =
|
|
2415
|
+
const excluded = rosterIds(llm?.video25ModelIds);
|
|
2491
2416
|
const extraNote = excluded.length > 0 ? h("div", { style: {
|
|
2492
2417
|
...S.muted,
|
|
2493
2418
|
fontSize: 12,
|
|
@@ -2502,7 +2427,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2502
2427
|
modelKey: "videoModelId",
|
|
2503
2428
|
enabled: llm?.videoEnabled === true,
|
|
2504
2429
|
hasKey: llm?.hasApiKey === true,
|
|
2505
|
-
candidates:
|
|
2430
|
+
candidates: rosterIds(llm?.videoCandidateIds),
|
|
2506
2431
|
preferred: llm?.videoPreferredModel != null ? String(llm.videoPreferredModel) : null,
|
|
2507
2432
|
effective: String(llm?.videoModel ?? ""),
|
|
2508
2433
|
extraNote,
|
|
@@ -2537,6 +2462,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2537
2462
|
init_format();
|
|
2538
2463
|
init_http();
|
|
2539
2464
|
init_model_picker();
|
|
2465
|
+
init_models();
|
|
2540
2466
|
init_runtime();
|
|
2541
2467
|
init_switch();
|
|
2542
2468
|
init_styles();
|
|
@@ -2777,7 +2703,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2777
2703
|
}, h("div", { style: {
|
|
2778
2704
|
...tone.fill,
|
|
2779
2705
|
width: `${pct}%`
|
|
2780
|
-
} })), pct === null && resetLine === null ? null : h("div", { style: S.quotaFoot }, pct === null ? null : h("span", { style: S.quotaUsed }, `${tt("quota.used")} ${count(used)} / ${count(limit)}`), resetLine === null ? null : h("span", { style: {
|
|
2706
|
+
} })), (pct === null || used === null) && resetLine === null ? null : h("div", { style: S.quotaFoot }, pct === null || used === null ? null : h("span", { style: S.quotaUsed }, `${tt("quota.used")} ${count(used)} / ${count(limit)}`), resetLine === null ? null : h("span", { style: {
|
|
2781
2707
|
...S.muted,
|
|
2782
2708
|
fontSize: 11
|
|
2783
2709
|
} }, resetLine)));
|
|
@@ -2881,29 +2807,34 @@ var dsh_connect_agnes_token_plan_client = (function() {
|
|
|
2881
2807
|
{
|
|
2882
2808
|
key: "requests",
|
|
2883
2809
|
label: tt("quota.total.requests"),
|
|
2884
|
-
value: count(source.totalRequests)
|
|
2810
|
+
value: count(source.totalRequests),
|
|
2811
|
+
on: typeof source.totalRequests === "number"
|
|
2885
2812
|
},
|
|
2886
2813
|
{
|
|
2887
2814
|
key: "tokens",
|
|
2888
2815
|
label: tt("quota.total.tokens"),
|
|
2889
|
-
value: tokenSize(source.totalTokens) || count(source.totalTokens)
|
|
2816
|
+
value: tokenSize(source.totalTokens) || count(source.totalTokens),
|
|
2817
|
+
on: typeof source.totalTokens === "number"
|
|
2890
2818
|
},
|
|
2891
2819
|
{
|
|
2892
2820
|
key: "images",
|
|
2893
2821
|
label: tt("quota.total.images"),
|
|
2894
|
-
value: count(source.totalImages)
|
|
2822
|
+
value: count(source.totalImages),
|
|
2823
|
+
on: typeof source.totalImages === "number"
|
|
2895
2824
|
},
|
|
2896
2825
|
{
|
|
2897
2826
|
key: "video",
|
|
2898
2827
|
label: tt("quota.total.video"),
|
|
2899
|
-
value: `${count(source.totalVideoSeconds)} ${tt("quota.unit.seconds")}
|
|
2828
|
+
value: `${count(source.totalVideoSeconds)} ${tt("quota.unit.seconds")}`,
|
|
2829
|
+
on: typeof source.totalVideoSeconds === "number"
|
|
2900
2830
|
},
|
|
2901
|
-
|
|
2831
|
+
{
|
|
2902
2832
|
key: "days",
|
|
2903
2833
|
label: tt("quota.total.activeDays"),
|
|
2904
|
-
value: count(source.activeDays)
|
|
2905
|
-
|
|
2906
|
-
|
|
2834
|
+
value: count(source.activeDays),
|
|
2835
|
+
on: typeof source.activeDays === "number"
|
|
2836
|
+
}
|
|
2837
|
+
].filter((cell) => cell.on);
|
|
2907
2838
|
return h("div", null, h("div", { style: {
|
|
2908
2839
|
...S.muted,
|
|
2909
2840
|
fontSize: 12,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { T as name, t as DEFAULT_REASONING_EFFORT } from "./llm-models.js";
|
|
2
2
|
import { i as buildAgnescodeDescriptors, n as AGNESCODE_PROVIDER_ID, r as agnescodeRoster, t as AGNESCODE_DISPLAY_NAME } from "./agnescode-models.js";
|
|
3
3
|
import { i as buildRetryPolicyConfig, n as STREAM_IDLE_TIMEOUT_MS, r as createWrappedPiAiAdapter, t as REQUEST_IMAGE_BUDGETS } from "./pi-ai-adapter-core.js";
|
|
4
4
|
import { createProvider } from "@earendil-works/pi-ai";
|
package/lib/agnescode-models.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as str, M as obj, T as name, i as PROBED_VISION, j as numZeroOk, k as num } from "./llm-models.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";
|
|
@@ -79,12 +79,12 @@ function isProfileSegment(value) {
|
|
|
79
79
|
* **怎么读它**:`ctx.get(name)` —— Cordis 自己的 "read a service without the
|
|
80
80
|
* inject requirement" 入口,未提供时安静返回 `undefined`。注意**别用属性访问**
|
|
81
81
|
* 去探:`ctx.profileContext` 会在服务缺失时**抛错**(`cannot get property
|
|
82
|
-
* "profileContext" without inject
|
|
82
|
+
* "profileContext" without inject`——cordis 的原文案)——这是本插件
|
|
83
83
|
* 实测踩到的,不是推测。`readOptionalService` 把两个入口都包了,属性访问只作为
|
|
84
84
|
* 测试桩的兜底留在最后。
|
|
85
85
|
*
|
|
86
|
-
* **为什么不用 `inject` 声明它**:`inject`
|
|
87
|
-
*
|
|
86
|
+
* **为什么不用 `inject` 声明它**:`inject` 里的是**硬依赖**(报错文案就叫
|
|
87
|
+
* "cannot get required service"),缺了 Cordis 根本不加载本插件。
|
|
88
88
|
* 而 `profileContext` 在官方 runtime 里是**可选**的(`@linxin666/
|
|
89
89
|
* dsh-client-ui-plugin-manager` 明确处理了"host 隐藏了它"的情形,
|
|
90
90
|
* `dsh-better-sidebar` 同理)。把它变成硬依赖,会让那些主机上整个插件消失
|
|
@@ -607,13 +607,19 @@ function createFileCatalogStore(options = {}) {
|
|
|
607
607
|
*
|
|
608
608
|
* @module dsh-connect-agnes-token-plan/agnescode
|
|
609
609
|
*/
|
|
610
|
-
/** The
|
|
611
|
-
|
|
610
|
+
/** The request headers every BFF call carries, with or without a token. The
|
|
611
|
+
* `X-App-Id` / `X-Platform` pair is the catalogue gate (probed: required) and
|
|
612
|
+
* the language header mirrors the desktop client. Declared ONCE here and spread
|
|
613
|
+
* by both builders — `agnescodeHeaders` (authenticated) and
|
|
614
|
+
* `agnescodeRequestHeaders` in `agnescode-models.ts` (anonymous) — so the two
|
|
615
|
+
* cannot drift into disagreeing gate values. */
|
|
616
|
+
const AGNESCODE_BASE_HEADERS = Object.freeze({
|
|
617
|
+
Accept: "application/json",
|
|
618
|
+
"Content-Type": "application/json",
|
|
612
619
|
"X-App-Id": "1",
|
|
613
|
-
"X-Platform": "1"
|
|
620
|
+
"X-Platform": "1",
|
|
621
|
+
"X-User-Language": "zh-Hans"
|
|
614
622
|
});
|
|
615
|
-
/** The request-language header the desktop client sends. */
|
|
616
|
-
const AGNESCODE_LANGUAGE_HEADER = "X-User-Language";
|
|
617
623
|
/**
|
|
618
624
|
* The session-file name pattern, matched case-insensitively against the App's
|
|
619
625
|
* roaming directory. The CN build ships `code-auth-session.cn.v1`; other
|
|
@@ -712,6 +718,10 @@ const AGNESCODE_HARVEST_TIER = Object.freeze({
|
|
|
712
718
|
* a non-model in the picker. Keyed by id on purpose: a rule tied to "has no
|
|
713
719
|
* multiplier" would silently start leaking `auto` back in the day the platform
|
|
714
720
|
* prices it.
|
|
721
|
+
*
|
|
722
|
+
* Matched as a SET (`includes`), never by index: comparing against `[0]` would
|
|
723
|
+
* make a second entry dead the moment it was added, and that failure would be
|
|
724
|
+
* silent.
|
|
715
725
|
*/
|
|
716
726
|
const AGNESCODE_CATALOGUE_EXCLUDED_IDS = Object.freeze(["auto"]);
|
|
717
727
|
/**
|
|
@@ -795,11 +805,8 @@ function decodeAgnescodeJwtExpMs(token) {
|
|
|
795
805
|
*/
|
|
796
806
|
function agnescodeHeaders(credential) {
|
|
797
807
|
return {
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
Authorization: `Bearer ${str(obj(credential).accessToken, "")}`,
|
|
801
|
-
...AGNESCODE_CATALOG_HEADERS,
|
|
802
|
-
[AGNESCODE_LANGUAGE_HEADER]: "zh-Hans"
|
|
808
|
+
...AGNESCODE_BASE_HEADERS,
|
|
809
|
+
Authorization: `Bearer ${str(obj(credential).accessToken, "")}`
|
|
803
810
|
};
|
|
804
811
|
}
|
|
805
812
|
/**
|
|
@@ -851,6 +858,29 @@ async function unwrapAgnescodeLocalStateKey(localStateRaw, dpapiUnprotect) {
|
|
|
851
858
|
return dpapiUnprotect(wrapped.subarray(5));
|
|
852
859
|
}
|
|
853
860
|
/**
|
|
861
|
+
* The env names that carry credentials. A spawned helper does not need them,
|
|
862
|
+
* and must not be able to read them off its own process environment.
|
|
863
|
+
*/
|
|
864
|
+
const DPAPI_CHILD_SECRET_ENV = [
|
|
865
|
+
"AGNES_PASSWORD",
|
|
866
|
+
"AGNES_USERNAME",
|
|
867
|
+
"AGNES_TOKEN_PLAN_API_KEY",
|
|
868
|
+
"AGNESCODE_CREDENTIAL"
|
|
869
|
+
];
|
|
870
|
+
/**
|
|
871
|
+
* The environment handed to the DPAPI PowerShell child: everything the shell
|
|
872
|
+
* actually needs (SystemRoot, PATH, TEMP, …) survives, only the credential
|
|
873
|
+
* names go. `spawn` inherits `process.env` by default; this is the explicit
|
|
874
|
+
* exception to that default.
|
|
875
|
+
* @param {object} [env] - the parent env (defaults to `process.env`).
|
|
876
|
+
* @returns {object} a copy with the credential names removed.
|
|
877
|
+
*/
|
|
878
|
+
function dpapiChildEnv(env = process.env) {
|
|
879
|
+
const out = { ...env };
|
|
880
|
+
for (const name of DPAPI_CHILD_SECRET_ENV) delete out[name];
|
|
881
|
+
return out;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
854
884
|
* The production DPAPI unwrap: a PowerShell child process. No native addon and
|
|
855
885
|
* no build step (the Host half is build-free, ROADMAP §6.2), and the unwrapped
|
|
856
886
|
* key transits the stdout pipe IN MEMORY — it is never printed, logged, or
|
|
@@ -881,7 +911,8 @@ async function defaultDpapiUnprotect(wrapped) {
|
|
|
881
911
|
"pipe",
|
|
882
912
|
"pipe"
|
|
883
913
|
],
|
|
884
|
-
windowsHide: true
|
|
914
|
+
windowsHide: true,
|
|
915
|
+
env: dpapiChildEnv()
|
|
885
916
|
});
|
|
886
917
|
const chunks = [];
|
|
887
918
|
let failure = "";
|
|
@@ -1113,7 +1144,7 @@ async function fetchAgnescodeCatalog(credential, fetcher) {
|
|
|
1113
1144
|
const model = obj(raw);
|
|
1114
1145
|
const id = str(model.id, "");
|
|
1115
1146
|
if (id === "" || seen.has(id)) continue;
|
|
1116
|
-
if (id
|
|
1147
|
+
if (AGNESCODE_CATALOGUE_EXCLUDED_IDS.includes(id)) continue;
|
|
1117
1148
|
if (str(model.model_type, "text") !== "text") continue;
|
|
1118
1149
|
seen.add(id);
|
|
1119
1150
|
const allowed = Array.isArray(model.allowed_subscription) ? model.allowed_subscription : [];
|
|
@@ -1172,12 +1203,26 @@ async function fetchAgnescodeBalance(credential, fetcher) {
|
|
|
1172
1203
|
}
|
|
1173
1204
|
}
|
|
1174
1205
|
/**
|
|
1175
|
-
* The static fallback roster: the eight models the CN BFF
|
|
1176
|
-
* (2026-10-01 night re-probe
|
|
1177
|
-
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1180
|
-
*
|
|
1206
|
+
* The static fallback roster: the eight models the CN BFF's `/v2/models`
|
|
1207
|
+
* listed at probe time (2026-10-01 night re-probe; rows and rates re-probed
|
|
1208
|
+
* 2026-10-04 against v2, ROADMAP §6.3.1). Used ONLY when
|
|
1209
|
+
* `fetchAgnescodeCatalog` comes back empty; a fresh catalogue always wins.
|
|
1210
|
+
*
|
|
1211
|
+
* `multiplier` is the BFF's `points_cost_multiplier` — a rate the PLATFORM
|
|
1212
|
+
* publishes per model, read once through `numZeroOk()`. A `0` is a published
|
|
1213
|
+
* price ("free"), NOT an absence: `num()` would turn it into "unpriced" and
|
|
1214
|
+
* the panel would lose the chip. The one v2 row with no rate at all is the
|
|
1215
|
+
* `auto` routing alias, which this list does not contain.
|
|
1216
|
+
*
|
|
1217
|
+
* `displayLabel` is the BFF's `display_label` promotional tag, present on one
|
|
1218
|
+
* row only, and passed through verbatim.
|
|
1219
|
+
*
|
|
1220
|
+
* SUPERSEDED READING, kept so the reversal is auditable: the 2026-10-01 v1
|
|
1221
|
+
* snapshot genuinely had "no multiplier concept — no row carries a rate key,
|
|
1222
|
+
* and all five rate-candidate endpoints 404". That was `/v1/models`, which
|
|
1223
|
+
* omits the field; the plugin reads v2 since 2026-10-04, where 8 of 9 rows
|
|
1224
|
+
* carry it. The lesson is the one in ROADMAP §6.3.1: deciding a field is
|
|
1225
|
+
* absent from a whole table requires scanning every row, not reading `rows[0]`.
|
|
1181
1226
|
*/
|
|
1182
1227
|
const AGNESCODE_FALLBACK_MODELS = Object.freeze([
|
|
1183
1228
|
{
|
|
@@ -1312,15 +1357,13 @@ const NO_COST = Object.freeze({
|
|
|
1312
1357
|
cacheRead: 0,
|
|
1313
1358
|
cacheWrite: 0
|
|
1314
1359
|
});
|
|
1315
|
-
/** The catalogue/identity headers every request carries (probed: required).
|
|
1360
|
+
/** The catalogue/identity headers every request carries (probed: required).
|
|
1361
|
+
* The anonymous half of one pair: `agnescodeHeaders`' map with its
|
|
1362
|
+
* `Authorization` line removed. The gate values come from
|
|
1363
|
+
* `AGNESCODE_BASE_HEADERS`, never a second copy here, so the two builders
|
|
1364
|
+
* cannot disagree. */
|
|
1316
1365
|
function agnescodeRequestHeaders() {
|
|
1317
|
-
return {
|
|
1318
|
-
Accept: "application/json",
|
|
1319
|
-
"Content-Type": "application/json",
|
|
1320
|
-
"X-App-Id": "1",
|
|
1321
|
-
"X-Platform": "1",
|
|
1322
|
-
"X-User-Language": "zh-Hans"
|
|
1323
|
-
};
|
|
1366
|
+
return { ...AGNESCODE_BASE_HEADERS };
|
|
1324
1367
|
}
|
|
1325
1368
|
/**
|
|
1326
1369
|
* Whether an AgnesCode roster row accepts image input.
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as numOrNull, C as inject, D as resolveSettings, E as resolveAuthOverrides, F as redactSecrets, I as retryBounded, L as str, M as obj, N as pluginError, O as forget, P as redactError, R as verbatim, S as hostName, T as name, _ as parseUsageOverview, b as readSubscriptionExpiry, c as summarizeCatalog, d as isVideoGenModel, f as checkShape, g as parseSubscriptionUsage, h as parsePlans, j as numZeroOk, k as num, l as visionOf, m as matchCurrentPlan, n as LLM_DISPLAY_NAME, o as filterByEnabled, p as countOf, r as LLM_PROVIDER_ID, s as rosterWithAvailability, t as DEFAULT_REASONING_EFFORT, u as isImageGenModel, v as parseUsageSeries, w as isAdmitted, x as CONFIG_DEFAULTS, y as quotaWindows } from "./llm-models.js";
|
|
2
2
|
import { S as writeStateFile, _ as profileSegment, a as filterAgnescodeRows, b as stateDir, c as fetchAgnescodeBalance, d as trustAgnescodeBffBase, f as createFileCatalogStore, g as ensureStateDir, h as createStateReadCache, l as fetchAgnescodeCatalog, m as STATE_READ_TTL_MS, n as AGNESCODE_PROVIDER_ID, o as AGNESCODE_FALLBACK_MODELS, p as normalizeEnabledIds, s as decodeAgnescodeJwtExpMs, t as AGNESCODE_DISPLAY_NAME, u as harvestAgnescodeLocalSession, v as profileStateDir, x as temporaryOf, y as readStateJson } from "./agnescode-models.js";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -1673,7 +1673,7 @@ function createTokenStore(options) {
|
|
|
1673
1673
|
ephemeral: ephemeral(),
|
|
1674
1674
|
retryAfterMs: inForceWaitMs$1(held),
|
|
1675
1675
|
needsUserAction: held !== null && held.parked,
|
|
1676
|
-
error: isFresh$1(stored) ? null : state.lastError === null ? null : state.lastError instanceof Error ? state.lastError.message : String(state.lastError)
|
|
1676
|
+
error: isFresh$1(stored) ? null : state.lastError === null ? null : redactSecrets(state.lastError instanceof Error ? state.lastError.message : String(state.lastError))
|
|
1677
1677
|
};
|
|
1678
1678
|
}
|
|
1679
1679
|
};
|
|
@@ -2127,7 +2127,7 @@ function createApiKeyStore({ credentials = null, env = process.env } = {}) {
|
|
|
2127
2127
|
*/
|
|
2128
2128
|
async save(apiKey) {
|
|
2129
2129
|
const value = verbatim(apiKey, "");
|
|
2130
|
-
if (
|
|
2130
|
+
if (value.trim() === "") throw new Error("an API key is required");
|
|
2131
2131
|
const service = resolveService();
|
|
2132
2132
|
if (service !== null && typeof service.set === "function") {
|
|
2133
2133
|
await service.set(API_KEY_REF, value);
|
|
@@ -2749,6 +2749,15 @@ function registerProviderPair(llm, built, target, { providerId, displayName }) {
|
|
|
2749
2749
|
*
|
|
2750
2750
|
* The module is loaded once and the factory is read off it once, so a Host
|
|
2751
2751
|
* whose peers resolve slowly pays that cost one time, not per publish.
|
|
2752
|
+
*
|
|
2753
|
+
* Only the SUCCESS is memoized. The memoized value used to be the promise
|
|
2754
|
+
* itself, which pinned a rejection in the slot for the life of the process:
|
|
2755
|
+
* after one failed `import()` every later publish rethrew the same error and
|
|
2756
|
+
* the provider could never register again — even after the operator fixed the
|
|
2757
|
+
* missing peer, which is exactly the remedy this file's own
|
|
2758
|
+
* `describeBuildFailure` prints for `ERR_MODULE_NOT_FOUND`. A recoverable
|
|
2759
|
+
* install problem cost a Host restart. Clearing the slot on failure keeps
|
|
2760
|
+
* "loaded once" for the happy path and gives the next publish a real retry.
|
|
2752
2761
|
* @param {() => Promise<unknown>} loadModule - resolves the adapter module.
|
|
2753
2762
|
* @param {string} exportName - the factory export to read off the module.
|
|
2754
2763
|
* @returns {() => Promise<any>} the memoized factory resolver.
|
|
@@ -2756,7 +2765,10 @@ function registerProviderPair(llm, built, target, { providerId, displayName }) {
|
|
|
2756
2765
|
function createAdapterFactoryResolver(loadModule, exportName) {
|
|
2757
2766
|
let adapterFactoryPromise;
|
|
2758
2767
|
return async () => {
|
|
2759
|
-
if (adapterFactoryPromise === void 0) adapterFactoryPromise = Promise.resolve(loadModule()).then((mod) => mod?.[exportName])
|
|
2768
|
+
if (adapterFactoryPromise === void 0) adapterFactoryPromise = Promise.resolve(loadModule()).then((mod) => mod?.[exportName]).catch((error) => {
|
|
2769
|
+
adapterFactoryPromise = void 0;
|
|
2770
|
+
throw error;
|
|
2771
|
+
});
|
|
2760
2772
|
return adapterFactoryPromise;
|
|
2761
2773
|
};
|
|
2762
2774
|
}
|
|
@@ -3190,7 +3202,6 @@ function wireAgnescodePublisher({ store, panelSwitch, enabledIds, getLlm, loadAd
|
|
|
3190
3202
|
if (credential === null) return "";
|
|
3191
3203
|
if (await store.isExpired().catch(() => false)) {
|
|
3192
3204
|
if (harvest.inFlight === null && Date.now() >= harvest.blockedUntil) harvest.inFlight = harvestAgnescodeLocalSession().then(async (walk) => {
|
|
3193
|
-
harvest.inFlight = null;
|
|
3194
3205
|
if (walk?.ok !== true) {
|
|
3195
3206
|
harvest.blockedUntil = Date.now() + AGNESCODE_REHARVEST_BACKOFF_MS;
|
|
3196
3207
|
logger?.warn?.(`agnescode: re-harvest found no usable session (${walk?.attempts?.length ?? 0} probed files); riding the stored token until ${new Date(harvest.blockedUntil).toISOString()}`);
|
|
@@ -3209,9 +3220,10 @@ function wireAgnescodePublisher({ store, panelSwitch, enabledIds, getLlm, loadAd
|
|
|
3209
3220
|
});
|
|
3210
3221
|
return walk;
|
|
3211
3222
|
}).catch(() => {
|
|
3212
|
-
harvest.inFlight = null;
|
|
3213
3223
|
harvest.blockedUntil = Date.now() + AGNESCODE_REHARVEST_BACKOFF_MS;
|
|
3214
3224
|
return null;
|
|
3225
|
+
}).finally(() => {
|
|
3226
|
+
harvest.inFlight = null;
|
|
3215
3227
|
});
|
|
3216
3228
|
await Promise.resolve(harvest.inFlight).catch(() => null);
|
|
3217
3229
|
}
|
|
@@ -3861,26 +3873,25 @@ function buildDrawEndpoint(apiBase) {
|
|
|
3861
3873
|
/**
|
|
3862
3874
|
* The draw-capable model ids of one catalog, de-duplicated in first-seen order.
|
|
3863
3875
|
*
|
|
3864
|
-
*
|
|
3865
|
-
*
|
|
3866
|
-
*
|
|
3876
|
+
* The ids are unique in first-seen order — a `Set` is all this needs. At the
|
|
3877
|
+
* id level there is no "which row survived" question the way `rosterOf` /
|
|
3878
|
+
* `buildDescriptors` / `catalog-store.normalizeEntries` have (they keep the
|
|
3879
|
+
* LAST row for an id, at its first-seen position), so the draw list and the
|
|
3880
|
+
* chat roster agree about which ids exist without sharing that row-level rule.
|
|
3881
|
+
* It WAS copied here, and the duplicate branch re-assigned the same id back to
|
|
3882
|
+
* its own slot — dead code that implied an ordering rule this function cannot
|
|
3883
|
+
* express.
|
|
3867
3884
|
* @param {object[]} entries - the normalized catalog entries.
|
|
3868
3885
|
* @returns {string[]}
|
|
3869
3886
|
*/
|
|
3870
3887
|
function imageGenModelIds(entries) {
|
|
3871
|
-
const
|
|
3872
|
-
const out = [];
|
|
3888
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3873
3889
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
3874
3890
|
if (!isImageGenModel(entry)) continue;
|
|
3875
3891
|
const id = str(entry?.id, "");
|
|
3876
|
-
if (id
|
|
3877
|
-
if (position.has(id)) out[position.get(id)] = id;
|
|
3878
|
-
else {
|
|
3879
|
-
position.set(id, out.length);
|
|
3880
|
-
out.push(id);
|
|
3881
|
-
}
|
|
3892
|
+
if (id !== "") ids.add(id);
|
|
3882
3893
|
}
|
|
3883
|
-
return
|
|
3894
|
+
return [...ids];
|
|
3884
3895
|
}
|
|
3885
3896
|
/**
|
|
3886
3897
|
* Choose the model one draw call addresses.
|
|
@@ -4223,27 +4234,24 @@ function isVideo25Flash(model) {
|
|
|
4223
4234
|
return isVideo25Family(model) && /flash/i.test(str(model, ""));
|
|
4224
4235
|
}
|
|
4225
4236
|
/**
|
|
4226
|
-
* The video-generation model ids of one catalog,
|
|
4237
|
+
* The video-generation model ids of one catalog, unique in first-seen order.
|
|
4227
4238
|
*
|
|
4228
4239
|
* Recognition is delegated to `modality.ts`, so this list and the chat roster
|
|
4229
|
-
* can never disagree about which entries exist.
|
|
4240
|
+
* can never disagree about which entries exist. A `Set` is all this needs: at
|
|
4241
|
+
* the id level there is no "which row survived" question, so the row-level
|
|
4242
|
+
* dedupe (`normalizeEntries`) is not copied here — see `imageGenModelIds` for
|
|
4243
|
+
* the same call.
|
|
4230
4244
|
* @param {object[]} entries - the normalized catalog entries.
|
|
4231
4245
|
* @returns {string[]}
|
|
4232
4246
|
*/
|
|
4233
4247
|
function videoGenModelIds(entries) {
|
|
4234
|
-
const
|
|
4235
|
-
const out = [];
|
|
4248
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4236
4249
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
4237
4250
|
if (!isVideoGenModel(entry)) continue;
|
|
4238
4251
|
const id = str(entry?.id, "");
|
|
4239
|
-
if (id
|
|
4240
|
-
if (position.has(id)) out[position.get(id)] = id;
|
|
4241
|
-
else {
|
|
4242
|
-
position.set(id, out.length);
|
|
4243
|
-
out.push(id);
|
|
4244
|
-
}
|
|
4252
|
+
if (id !== "") ids.add(id);
|
|
4245
4253
|
}
|
|
4246
|
-
return
|
|
4254
|
+
return [...ids];
|
|
4247
4255
|
}
|
|
4248
4256
|
/**
|
|
4249
4257
|
* The subset of video ids this module can actually address — the V2.0 family.
|
|
@@ -4397,25 +4405,32 @@ async function soft(run) {
|
|
|
4397
4405
|
/**
|
|
4398
4406
|
* The panel's projection of one catalogue entry: no `featureTexts`, no
|
|
4399
4407
|
* envelope leftovers — only the fields the quota screen reads or compares.
|
|
4408
|
+
*
|
|
4409
|
+
* The numeric pass is defensive rather than a conversion: `parsePlans` already
|
|
4410
|
+
* ran `countOf` over every figure, so by the time a row reaches here each
|
|
4411
|
+
* number is a number. `countOf` is kept because a plan may arrive by another
|
|
4412
|
+
* route (the doctor and the tests build rows directly), and `Number(x) || 0`
|
|
4413
|
+
* would let `Infinity` through while `countOf` reads it as 0 — the panel must
|
|
4414
|
+
* show a missing figure, never an unbounded one.
|
|
4400
4415
|
* @param {object} plan - one {@link parsePlans} entry.
|
|
4401
4416
|
* @returns {object} the projection.
|
|
4402
4417
|
*/
|
|
4403
4418
|
function planSummary(plan) {
|
|
4404
4419
|
return {
|
|
4405
4420
|
uuid: str(plan?.uuid, ""),
|
|
4406
|
-
planId:
|
|
4421
|
+
planId: countOf(plan?.planId),
|
|
4407
4422
|
name: str(plan?.name, ""),
|
|
4408
4423
|
displayName: str(plan?.displayName, ""),
|
|
4409
4424
|
billingCycle: str(plan?.billingCycle, ""),
|
|
4410
4425
|
displayCycle: str(plan?.displayCycle, ""),
|
|
4411
|
-
priceMinor:
|
|
4426
|
+
priceMinor: countOf(plan?.priceMinor),
|
|
4412
4427
|
currency: str(plan?.currency, ""),
|
|
4413
4428
|
limits: {
|
|
4414
|
-
requests5h:
|
|
4415
|
-
requestsWindowH:
|
|
4416
|
-
requestsWeekly:
|
|
4417
|
-
imagesDaily:
|
|
4418
|
-
videoDaily:
|
|
4429
|
+
requests5h: countOf(plan?.concurrencyLimit),
|
|
4430
|
+
requestsWindowH: countOf(plan?.concurrencyWindowH),
|
|
4431
|
+
requestsWeekly: countOf(plan?.textWeeklyLimit),
|
|
4432
|
+
imagesDaily: countOf(plan?.imageDailyLimit),
|
|
4433
|
+
videoDaily: countOf(plan?.videoDailyLimit)
|
|
4419
4434
|
}
|
|
4420
4435
|
};
|
|
4421
4436
|
}
|
|
@@ -4546,38 +4561,6 @@ function buildShapeWarnings(overview, series, plans) {
|
|
|
4546
4561
|
];
|
|
4547
4562
|
}
|
|
4548
4563
|
/**
|
|
4549
|
-
* Fetch the four console sources plus the model catalog and aggregate them
|
|
4550
|
-
* into the snapshot body the route writes.
|
|
4551
|
-
*
|
|
4552
|
-
* All five are fetched in parallel through the shared `cache` + `inflight`
|
|
4553
|
-
* maps (a single-flight per URL so concurrent polls share one call) and the
|
|
4554
|
-
* `tokenStore` (so a 401 triggers one renewal before the call). Only the
|
|
4555
|
-
* catalog is authenticated by the API key instead; a missing key degrades the
|
|
4556
|
-
* model lists, not the quota — resolved per poll so a key that arrives after
|
|
4557
|
-
* the plugin mounted still lights the lists on the next poll.
|
|
4558
|
-
*
|
|
4559
|
-
* @param {object} context
|
|
4560
|
-
* @param {object} context.settings - the resolved settings row.
|
|
4561
|
-
* @param {Map} context.cache - the console-response cache (shared across polls).
|
|
4562
|
-
* @param {Map} context.inflight - the single-flight map (shared across polls).
|
|
4563
|
-
* @param {object} context.tokenStore - the `createTokenStore` instance.
|
|
4564
|
-
* @param {object} context.apiKeyStore - the `createApiKeyStore` instance.
|
|
4565
|
-
* @param {object} context.publisher - the `createProviderPublisher` instance.
|
|
4566
|
-
* @param {object} context.catalogStore - the `createFileCatalogStore` instance.
|
|
4567
|
-
* @param {() => Promise<boolean|null>} context.panelSwitch - the panel-saved
|
|
4568
|
-
* provider switch (`provider-store.enabled()`); null when untouched.
|
|
4569
|
-
* @param {() => Promise<boolean|null>} context.drawSwitch - the panel-saved
|
|
4570
|
-
* draw-tool switch (`draw-store.enabled()`), same shape and precedence.
|
|
4571
|
-
* @param {() => Promise<string|null>} context.drawModelId - the panel-saved
|
|
4572
|
-
* draw-model preference (`draw-store.modelId()`); null when untouched.
|
|
4573
|
-
* @param {() => Promise<boolean|null>} context.videoSwitch - the panel-saved
|
|
4574
|
-
* video-tool switch (`video-store.enabled()`). A SEPARATE opt-in from the
|
|
4575
|
-
* draw switch: the two modalities are independent.
|
|
4576
|
-
* @param {() => Promise<string|null>} context.videoModelId - the panel-saved
|
|
4577
|
-
* video-model preference (`video-store.modelId()`); null when untouched.
|
|
4578
|
-
* @returns {Promise<object>} the snapshot body.
|
|
4579
|
-
*/
|
|
4580
|
-
/**
|
|
4581
4564
|
* The ONLY side effects `buildSnapshotBody` performs, isolated here so the
|
|
4582
4565
|
* aggregator above stays a pure projection of the console results.
|
|
4583
4566
|
*
|
|
@@ -4604,7 +4587,7 @@ async function applyCatalogEffects({ catalogStore, publisher, providerState, cat
|
|
|
4604
4587
|
const freshSignature = catalogSignature(catalog, enabledIds);
|
|
4605
4588
|
if (freshSignature !== providerState.signature) {
|
|
4606
4589
|
catalogChanged = true;
|
|
4607
|
-
if (await catalogStore.replace(catalog, enabledIds)
|
|
4590
|
+
if (await catalogStore.replace(catalog, enabledIds)) providerState.signature = freshSignature;
|
|
4608
4591
|
await publisher.publish(catalog, enabledIds, unavailableModelIds);
|
|
4609
4592
|
}
|
|
4610
4593
|
offered = catalog;
|
|
@@ -4619,6 +4602,38 @@ async function applyCatalogEffects({ catalogStore, publisher, providerState, cat
|
|
|
4619
4602
|
catalogChanged
|
|
4620
4603
|
};
|
|
4621
4604
|
}
|
|
4605
|
+
/**
|
|
4606
|
+
* Fetch the four console sources plus the model catalog and aggregate them
|
|
4607
|
+
* into the snapshot body the route writes.
|
|
4608
|
+
*
|
|
4609
|
+
* All five are fetched in parallel through the shared `cache` + `inflight`
|
|
4610
|
+
* maps (a single-flight per URL so concurrent polls share one call) and the
|
|
4611
|
+
* `tokenStore` (so a 401 triggers one renewal before the call). Only the
|
|
4612
|
+
* catalog is authenticated by the API key instead; a missing key degrades the
|
|
4613
|
+
* model lists, not the quota — resolved per poll so a key that arrives after
|
|
4614
|
+
* the plugin mounted still lights the lists on the next poll.
|
|
4615
|
+
*
|
|
4616
|
+
* @param {object} context
|
|
4617
|
+
* @param {object} context.settings - the resolved settings row.
|
|
4618
|
+
* @param {Map} context.cache - the console-response cache (shared across polls).
|
|
4619
|
+
* @param {Map} context.inflight - the single-flight map (shared across polls).
|
|
4620
|
+
* @param {object} context.tokenStore - the `createTokenStore` instance.
|
|
4621
|
+
* @param {object} context.apiKeyStore - the `createApiKeyStore` instance.
|
|
4622
|
+
* @param {object} context.publisher - the `createProviderPublisher` instance.
|
|
4623
|
+
* @param {object} context.catalogStore - the `createFileCatalogStore` instance.
|
|
4624
|
+
* @param {() => Promise<boolean|null>} context.panelSwitch - the panel-saved
|
|
4625
|
+
* provider switch (`provider-store.enabled()`); null when untouched.
|
|
4626
|
+
* @param {() => Promise<boolean|null>} context.drawSwitch - the panel-saved
|
|
4627
|
+
* draw-tool switch (`draw-store.enabled()`), same shape and precedence.
|
|
4628
|
+
* @param {() => Promise<string|null>} context.drawModelId - the panel-saved
|
|
4629
|
+
* draw-model preference (`draw-store.modelId()`); null when untouched.
|
|
4630
|
+
* @param {() => Promise<boolean|null>} context.videoSwitch - the panel-saved
|
|
4631
|
+
* video-tool switch (`video-store.enabled()`). A SEPARATE opt-in from the
|
|
4632
|
+
* draw switch: the two modalities are independent.
|
|
4633
|
+
* @param {() => Promise<string|null>} context.videoModelId - the panel-saved
|
|
4634
|
+
* video-model preference (`video-store.modelId()`); null when untouched.
|
|
4635
|
+
* @returns {Promise<object>} the snapshot body.
|
|
4636
|
+
*/
|
|
4622
4637
|
async function buildSnapshotBody({ settings, cache, inflight, tokenStore, apiKeyStore, publisher, catalogStore, panelSwitch, drawSwitch, drawModelId, videoSwitch, videoModelId }) {
|
|
4623
4638
|
const providerState = publisher.state;
|
|
4624
4639
|
const resolveApiKey = async () => (await apiKeyStore.resolve()).value;
|
|
@@ -4829,6 +4844,22 @@ function refuseOrigin(response) {
|
|
|
4829
4844
|
});
|
|
4830
4845
|
}
|
|
4831
4846
|
/**
|
|
4847
|
+
* The request's method, with the route's own fallback for a caller that omits
|
|
4848
|
+
* it.
|
|
4849
|
+
*
|
|
4850
|
+
* The web server always supplies a method, so the fallback is for the route
|
|
4851
|
+
* tests and any in-process caller. It is the ROUTE's choice, not a family
|
|
4852
|
+
* constant: a write route falls back to POST, a read route to GET, and the
|
|
4853
|
+
* wrong one turns a methodless request into a write. Shared here rather than
|
|
4854
|
+
* repeated, so the family's fallback wording has one home.
|
|
4855
|
+
* @param request - the incoming HTTP request.
|
|
4856
|
+
* @param fallback - the method to assume when the caller sent none.
|
|
4857
|
+
* @returns {string} the method.
|
|
4858
|
+
*/
|
|
4859
|
+
function requestMethod(request, fallback) {
|
|
4860
|
+
return request.method === void 0 ? fallback : request.method;
|
|
4861
|
+
}
|
|
4862
|
+
/**
|
|
4832
4863
|
* Refuse a disallowed method with the family's 405 shape.
|
|
4833
4864
|
*
|
|
4834
4865
|
* The 405 carries no `cache-control`: unlike a snapshot, a method refusal is
|
|
@@ -5099,7 +5130,7 @@ async function recordOriginlessWrite(method, options = {}) {
|
|
|
5099
5130
|
lastMethod
|
|
5100
5131
|
};
|
|
5101
5132
|
const temporary = temporaryOf(dir, FILE);
|
|
5102
|
-
await writeStateFile(file,
|
|
5133
|
+
await writeStateFile(file, payload, { temporary });
|
|
5103
5134
|
} catch {}
|
|
5104
5135
|
}
|
|
5105
5136
|
|
|
@@ -5196,7 +5227,7 @@ function registerAccountRoute(ctx, wiring) {
|
|
|
5196
5227
|
refuseOrigin(response);
|
|
5197
5228
|
return;
|
|
5198
5229
|
}
|
|
5199
|
-
const method = request
|
|
5230
|
+
const method = requestMethod(request, "POST");
|
|
5200
5231
|
if (method === "GET") {
|
|
5201
5232
|
writeJson(response, 200, {
|
|
5202
5233
|
ok: true,
|
|
@@ -5288,7 +5319,7 @@ function registerApiKeyRoute(ctx, wiring) {
|
|
|
5288
5319
|
refuseOrigin(response);
|
|
5289
5320
|
return;
|
|
5290
5321
|
}
|
|
5291
|
-
const method = request
|
|
5322
|
+
const method = requestMethod(request, "GET");
|
|
5292
5323
|
const answer = async (extra = {}) => writeJson(response, 200, {
|
|
5293
5324
|
ok: true,
|
|
5294
5325
|
...await apiKeyStore.state().catch(() => ({
|
|
@@ -5372,7 +5403,7 @@ function registerProviderRoute(ctx, wiring) {
|
|
|
5372
5403
|
refuseOrigin(response);
|
|
5373
5404
|
return;
|
|
5374
5405
|
}
|
|
5375
|
-
const method = request
|
|
5406
|
+
const method = requestMethod(request, "GET");
|
|
5376
5407
|
const answer = async (extra = {}) => {
|
|
5377
5408
|
const providerSwitch = resolveSwitchEnabled(await readPanelValue(() => providerStore.enabled()), settings.registerProvider);
|
|
5378
5409
|
writeJson(response, 200, {
|
|
@@ -5450,7 +5481,7 @@ function registerModelsRoute(ctx, wiring) {
|
|
|
5450
5481
|
refuseOrigin(response);
|
|
5451
5482
|
return;
|
|
5452
5483
|
}
|
|
5453
|
-
if ((request
|
|
5484
|
+
if (requestMethod(request, "POST") !== "POST") {
|
|
5454
5485
|
refuseMethod(response);
|
|
5455
5486
|
return;
|
|
5456
5487
|
}
|
|
@@ -5552,7 +5583,7 @@ function registerToolSwitchRoute(ctx, { path, label, store, enabledKey, enabledS
|
|
|
5552
5583
|
refuseOrigin(response);
|
|
5553
5584
|
return;
|
|
5554
5585
|
}
|
|
5555
|
-
const method = request
|
|
5586
|
+
const method = requestMethod(request, "GET");
|
|
5556
5587
|
const answer = async (extra = {}) => {
|
|
5557
5588
|
const panelEnabled = await readPanelValue(async () => await store?.enabled() ?? null);
|
|
5558
5589
|
const panelModel = await readPanelValue(async () => await store?.modelId() ?? null);
|
|
@@ -5720,6 +5751,13 @@ function registerVideoRoute(ctx, wiring) {
|
|
|
5720
5751
|
*
|
|
5721
5752
|
* @module dsh-connect-agnes-token-plan/routes/agnescode
|
|
5722
5753
|
*/
|
|
5754
|
+
/** Map the harvest rows through `redactSecrets`, at the single choke point. */
|
|
5755
|
+
function redactHarvestAttempts(attempts) {
|
|
5756
|
+
return attempts.map((attempt) => ({
|
|
5757
|
+
...attempt,
|
|
5758
|
+
detail: redactSecrets(attempt.detail)
|
|
5759
|
+
}));
|
|
5760
|
+
}
|
|
5723
5761
|
/** The AgnesCode provider route (ROADMAP §6.3 "third upstream provider"). */
|
|
5724
5762
|
const AGNESCODE_PATH = `/api/${name}/agnescode`;
|
|
5725
5763
|
/** Ceiling on an AgnesCode action body: every action posts a bare `{action}`. */
|
|
@@ -5754,6 +5792,7 @@ function registerAgnescodeRoute(ctx, wiring) {
|
|
|
5754
5792
|
let expiresAtMs = null;
|
|
5755
5793
|
let balance = null;
|
|
5756
5794
|
let error = null;
|
|
5795
|
+
let credential = null;
|
|
5757
5796
|
try {
|
|
5758
5797
|
if (agnescodeStore !== null && agnescodeStore !== void 0) {
|
|
5759
5798
|
const state = await agnescodeStore.state().catch(() => null);
|
|
@@ -5761,20 +5800,15 @@ function registerAgnescodeRoute(ctx, wiring) {
|
|
|
5761
5800
|
nickname = state?.nickname ?? "";
|
|
5762
5801
|
bffBase = state?.bffBase ?? "";
|
|
5763
5802
|
expiresAtMs = state?.expiresAtMs ?? null;
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
if (credential?.accessToken) balance = await fetchAgnescodeBalance(credential).catch(() => null);
|
|
5767
|
-
}
|
|
5803
|
+
credential = (await agnescodeStore.resolve().catch(() => ({ credential: null }))).credential ?? null;
|
|
5804
|
+
if (loggedIn && credential?.accessToken) balance = await fetchAgnescodeBalance(credential).catch(() => null);
|
|
5768
5805
|
}
|
|
5769
5806
|
} catch (why) {
|
|
5770
5807
|
error = redactSecrets(why instanceof Error ? why.message : String(why));
|
|
5771
5808
|
}
|
|
5772
5809
|
let models = null;
|
|
5773
5810
|
try {
|
|
5774
|
-
if (
|
|
5775
|
-
const { credential } = await agnescodeStore.resolve().catch(() => ({ credential: null }));
|
|
5776
|
-
if (credential?.accessToken) models = await fetchAgnescodeCatalog(credential).catch(() => null);
|
|
5777
|
-
}
|
|
5811
|
+
if (credential?.accessToken) models = await fetchAgnescodeCatalog(credential).catch(() => null);
|
|
5778
5812
|
} catch {
|
|
5779
5813
|
models = null;
|
|
5780
5814
|
}
|
|
@@ -5816,7 +5850,7 @@ function registerAgnescodeRoute(ctx, wiring) {
|
|
|
5816
5850
|
const enabledIds = agnescodeModels !== null && agnescodeModels !== void 0 ? await agnescodeModels.listEnabledIds().catch(() => []) : [];
|
|
5817
5851
|
await agnescodePublisher.publish(filterAgnescodeRows(rows, enabledIds), bffBase);
|
|
5818
5852
|
};
|
|
5819
|
-
const method = request
|
|
5853
|
+
const method = requestMethod(request, "GET");
|
|
5820
5854
|
if (method === "GET") {
|
|
5821
5855
|
let state = await agnescodeState();
|
|
5822
5856
|
const staleRegistration = state.providerRegistered !== true && state.providerError !== null && state.providerError !== void 0 && state.providerError !== "";
|
|
@@ -5915,13 +5949,14 @@ function registerAgnescodeRoute(ctx, wiring) {
|
|
|
5915
5949
|
try {
|
|
5916
5950
|
if (agnescodeHarvestInFlight === null) agnescodeHarvestInFlight = (async () => {
|
|
5917
5951
|
const walk = await harvestAgnescodeLocalSession();
|
|
5952
|
+
const attempts = redactHarvestAttempts(walk.attempts);
|
|
5918
5953
|
lastHarvest = {
|
|
5919
5954
|
ok: walk.ok,
|
|
5920
|
-
attempts
|
|
5955
|
+
attempts
|
|
5921
5956
|
};
|
|
5922
5957
|
if (walk.ok !== true) return {
|
|
5923
5958
|
ok: false,
|
|
5924
|
-
attempts
|
|
5959
|
+
attempts
|
|
5925
5960
|
};
|
|
5926
5961
|
const expMs = decodeAgnescodeJwtExpMs(walk.session.accessToken);
|
|
5927
5962
|
await agnescodeStore.save({
|
|
@@ -5933,7 +5968,7 @@ function registerAgnescodeRoute(ctx, wiring) {
|
|
|
5933
5968
|
});
|
|
5934
5969
|
return {
|
|
5935
5970
|
ok: true,
|
|
5936
|
-
attempts
|
|
5971
|
+
attempts
|
|
5937
5972
|
};
|
|
5938
5973
|
})().finally(() => {
|
|
5939
5974
|
agnescodeHarvestInFlight = null;
|
|
@@ -6712,27 +6747,40 @@ function defaultSleep(ms) {
|
|
|
6712
6747
|
* @param {string} [options.model] - sent as `model_name`.
|
|
6713
6748
|
* @param {number} [options.timeoutMs] - the poll budget.
|
|
6714
6749
|
* @param {number} [options.pollIntervalMs] - the gap between queries.
|
|
6750
|
+
* @param {number} [options.queryRetries] - how many times a REFUSED query is
|
|
6751
|
+
* retried before the task id is handed back to the agent (default 2).
|
|
6715
6752
|
* @param {Function} [options.sleep] - injected sleeper.
|
|
6716
6753
|
* @param {Function} [options.now] - injected clock.
|
|
6717
6754
|
* @param {Function} [options.isDisposed] - `() => boolean`, true after unmount.
|
|
6718
6755
|
* @returns {Promise<object>} the terminal {@link parseVideoQuery} result.
|
|
6719
6756
|
* @throws {Error} on timeout, disposal, or a refused query.
|
|
6720
6757
|
*/
|
|
6721
|
-
async function pollVideoResult({ fetchImpl, queryEndpoint, apiKey, videoId, model, timeoutMs = VIDEO_DEFAULT_TIMEOUT_MS, pollIntervalMs = VIDEO_POLL_INTERVAL_MS, sleep = defaultSleep, now = Date.now, isDisposed = () => false }) {
|
|
6758
|
+
async function pollVideoResult({ fetchImpl, queryEndpoint, apiKey, videoId, model, timeoutMs = VIDEO_DEFAULT_TIMEOUT_MS, pollIntervalMs = VIDEO_POLL_INTERVAL_MS, queryRetries = 2, sleep = defaultSleep, now = Date.now, isDisposed = () => false }) {
|
|
6722
6759
|
const budget = Math.max(1e3, Math.floor(num(timeoutMs, VIDEO_DEFAULT_TIMEOUT_MS)));
|
|
6723
6760
|
const interval = Math.max(100, Math.floor(num(pollIntervalMs, VIDEO_POLL_INTERVAL_MS)));
|
|
6761
|
+
const retries = Math.max(0, Math.floor(num(queryRetries, 2)));
|
|
6724
6762
|
const deadline = now() + budget;
|
|
6725
6763
|
let attempts = 0;
|
|
6726
6764
|
for (;;) {
|
|
6727
6765
|
if (isDisposed()) throw new Error("Agnes video tool is no longer mounted");
|
|
6728
6766
|
if (++attempts > 1e3) throw new Error(`视频生成轮询超过 ${VIDEO_MAX_POLLS} 次仍未结束(video_id=${videoId})——任务仍在平台侧运行,请稍后用 video_id 重新查询,不要重复提交`);
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6767
|
+
let value;
|
|
6768
|
+
let retried = 0;
|
|
6769
|
+
for (;;) try {
|
|
6770
|
+
value = await queryVideoTask({
|
|
6771
|
+
fetchImpl,
|
|
6772
|
+
queryEndpoint,
|
|
6773
|
+
apiKey,
|
|
6774
|
+
videoId,
|
|
6775
|
+
model
|
|
6776
|
+
});
|
|
6777
|
+
break;
|
|
6778
|
+
} catch (queryError) {
|
|
6779
|
+
const cause = queryError instanceof Error ? queryError.message : String(queryError);
|
|
6780
|
+
if (retried >= retries || isDisposed() || deadline - now() <= 0) throw new Error(`视频生成查询被拒(video_id=${videoId}):${cause}——任务仍在平台侧运行,可用 video_id=${videoId} 稍后重新查询,不要重复提交`);
|
|
6781
|
+
retried += 1;
|
|
6782
|
+
await sleep(Math.min(interval, deadline - now()));
|
|
6783
|
+
}
|
|
6736
6784
|
if (isVideoTerminal(value.status)) return value;
|
|
6737
6785
|
const remaining = deadline - now();
|
|
6738
6786
|
if (remaining <= 0) throw new Error(`视频生成超时(超过 ${budget}ms,任务仍处于 ${value.status === "" ? "unknown" : value.status})——任务仍在平台侧运行,可用 video_id=${videoId} 稍后重新查询,不要重复提交`);
|
package/lib/llm-adapter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as buildDescriptors, n as LLM_DISPLAY_NAME, r as LLM_PROVIDER_ID, t as DEFAULT_REASONING_EFFORT
|
|
1
|
+
import { T as name, a as buildDescriptors, n as LLM_DISPLAY_NAME, r as LLM_PROVIDER_ID, t as DEFAULT_REASONING_EFFORT } from "./llm-models.js";
|
|
2
2
|
import { i as buildRetryPolicyConfig, n as STREAM_IDLE_TIMEOUT_MS, r as createWrappedPiAiAdapter, t as REQUEST_IMAGE_BUDGETS } from "./pi-ai-adapter-core.js";
|
|
3
3
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
4
4
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
package/lib/llm-models.js
CHANGED
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
function num(value, fallback) {
|
|
25
25
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
26
26
|
}
|
|
27
|
-
/** Read a non-empty string, else the fallback. */
|
|
28
27
|
function str(value, fallback) {
|
|
29
28
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : fallback;
|
|
30
29
|
}
|
|
@@ -72,14 +71,6 @@ function redactError(error) {
|
|
|
72
71
|
function obj(value) {
|
|
73
72
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
74
73
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Read a string exactly as it was given, else the fallback.
|
|
77
|
-
*
|
|
78
|
-
* The companion to `str()` for secrets: a password is stored, read back and
|
|
79
|
-
* sent as typed, because trimming it is a change the user cannot see. A
|
|
80
|
-
* password of only whitespace is still "not filled in", which the caller
|
|
81
|
-
* judges with `.trim()`.
|
|
82
|
-
*/
|
|
83
74
|
function verbatim(value, fallback) {
|
|
84
75
|
return typeof value === "string" ? value : fallback;
|
|
85
76
|
}
|
|
@@ -1036,6 +1027,22 @@ function finiteOrNull(value) {
|
|
|
1036
1027
|
const parsed = Number(value);
|
|
1037
1028
|
return Number.isFinite(parsed) ? parsed : null;
|
|
1038
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Read `subscription.usage` into the panel's per-window facts.
|
|
1032
|
+
*
|
|
1033
|
+
* This is the missing half of the quota story. `quotaWindows` states the
|
|
1034
|
+
* LIMIT from the plan; this states what has actually been consumed inside the
|
|
1035
|
+
* current window, plus the window's own bounds and the moment it resets — all
|
|
1036
|
+
* figures the PLATFORM reported, not derived here. Nothing is subtracted:
|
|
1037
|
+
* `used` and `limit` are quoted side by side exactly as the console shows
|
|
1038
|
+
* them, so the panel's bar is a transcription, not an arithmetic claim.
|
|
1039
|
+
*
|
|
1040
|
+
* Returns `null` when the payload carries no `usage` at all — an account that
|
|
1041
|
+
* has never consumed anything simply may not report one, and that absence
|
|
1042
|
+
* must read as "no bar", never as "used 0".
|
|
1043
|
+
* @param {unknown} subscription - the unwrapped `/api/cn/user/subscription` data.
|
|
1044
|
+
* @returns {Record<string, SubscriptionUsageCell>|null}
|
|
1045
|
+
*/
|
|
1039
1046
|
function parseSubscriptionUsage(subscription) {
|
|
1040
1047
|
const usage = obj(obj(subscription).usage);
|
|
1041
1048
|
if (Object.keys(usage).length === 0) return null;
|
|
@@ -1536,32 +1543,6 @@ function maxOutputLengthOf(entry) {
|
|
|
1536
1543
|
return 0;
|
|
1537
1544
|
}
|
|
1538
1545
|
/**
|
|
1539
|
-
* The picker's 思考强度 levels, pinned to platform-valid wire spellings.
|
|
1540
|
-
*
|
|
1541
|
-
* DSH's picker offers levels from `getSupportedThinkingLevels(model)`
|
|
1542
|
-
* (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`), and pi-ai's
|
|
1543
|
-
* openai-completions dispatch sends `reasoning_effort = map[level] ?? level`.
|
|
1544
|
-
* Agnes's OpenAI-compat gateway rejects `off` (the OpenAI spelling) and
|
|
1545
|
-
* `minimal`; the extended levels are PER-MODEL, per the 2026-10-01 live
|
|
1546
|
-
* ladder (see PROBED_EFFORT): `xhigh` only on agnes-3.0-flash, `max` on
|
|
1547
|
-
* every Agnes chat model. So:
|
|
1548
|
-
*
|
|
1549
|
-
* - `off: "none"` — the picker's "关闭" must send `none`, not `off`;
|
|
1550
|
-
* - `low`/`medium` — 200 on every known chat model; a model ABSENT from
|
|
1551
|
-
* the table (an unknown id) still gets them via the Agnes safe-set so
|
|
1552
|
-
* the picker is never empty. A model PRESENT in the table with a level
|
|
1553
|
-
* set `false` keeps it closed. The live-contract replay
|
|
1554
|
-
* (`test/live-contract.mjs`) probes the per-model levels and flips the
|
|
1555
|
-
* table cells once a model's 200 is recorded.
|
|
1556
|
-
* - `xhigh`/`max` — per the table; the safe-set keeps both closed for
|
|
1557
|
-
* unknown ids.
|
|
1558
|
-
*
|
|
1559
|
-
* A value of `null` means "the picker must not offer this level"; a string is
|
|
1560
|
-
* the wire spelling the level dispatches to.
|
|
1561
|
-
* @param {object} entry - one normalized catalog entry.
|
|
1562
|
-
* @returns {object} the thinkingLevelMap.
|
|
1563
|
-
*/
|
|
1564
|
-
/**
|
|
1565
1546
|
* Per-model 思考档位 probe table (frozen 2026-10-01 from the live ladder
|
|
1566
1547
|
* replay, mirrored from `test/baselines/agnes-contract.json`
|
|
1567
1548
|
* §reasoningEffort — every cell is a platform answer, not a guess).
|
|
@@ -1614,6 +1595,32 @@ const PROBED_EFFORT = Object.freeze({
|
|
|
1614
1595
|
max: true
|
|
1615
1596
|
}
|
|
1616
1597
|
});
|
|
1598
|
+
/**
|
|
1599
|
+
* The picker's 思考强度 levels, pinned to platform-valid wire spellings.
|
|
1600
|
+
*
|
|
1601
|
+
* DSH's picker offers levels from `getSupportedThinkingLevels(model)`
|
|
1602
|
+
* (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`), and pi-ai's
|
|
1603
|
+
* openai-completions dispatch sends `reasoning_effort = map[level] ?? level`.
|
|
1604
|
+
* Agnes's OpenAI-compat gateway rejects `off` (the OpenAI spelling) and
|
|
1605
|
+
* `minimal`; the extended levels are PER-MODEL, per the 2026-10-01 live
|
|
1606
|
+
* ladder (see PROBED_EFFORT): `xhigh` only on agnes-3.0-flash, `max` on
|
|
1607
|
+
* every Agnes chat model. So:
|
|
1608
|
+
*
|
|
1609
|
+
* - `off: "none"` — the picker's "关闭" must send `none`, not `off`;
|
|
1610
|
+
* - `low`/`medium` — 200 on every known chat model; a model ABSENT from
|
|
1611
|
+
* the table (an unknown id) still gets them via the Agnes safe-set so
|
|
1612
|
+
* the picker is never empty. A model PRESENT in the table with a level
|
|
1613
|
+
* set `false` keeps it closed. The live-contract replay
|
|
1614
|
+
* (`test/live-contract.mjs`) probes the per-model levels and flips the
|
|
1615
|
+
* table cells once a model's 200 is recorded.
|
|
1616
|
+
* - `xhigh`/`max` — per the table; the safe-set keeps both closed for
|
|
1617
|
+
* unknown ids.
|
|
1618
|
+
*
|
|
1619
|
+
* A value of `null` means "the picker must not offer this level"; a string is
|
|
1620
|
+
* the wire spelling the level dispatches to.
|
|
1621
|
+
* @param {object} entry - one normalized catalog entry.
|
|
1622
|
+
* @returns {object} the thinkingLevelMap.
|
|
1623
|
+
*/
|
|
1617
1624
|
function thinkingLevelMapFor(entry) {
|
|
1618
1625
|
const id = str(entry?.id, "");
|
|
1619
1626
|
const probed = PROBED_EFFORT[id];
|
|
@@ -1866,4 +1873,4 @@ function summarizeCatalog(entries) {
|
|
|
1866
1873
|
}
|
|
1867
1874
|
|
|
1868
1875
|
//#endregion
|
|
1869
|
-
export {
|
|
1876
|
+
export { numOrNull as A, inject as C, resolveSettings as D, resolveAuthOverrides as E, redactSecrets as F, retryBounded as I, str as L, obj as M, pluginError as N, forget as O, redactError as P, verbatim as R, hostName as S, name as T, parseUsageOverview as _, buildDescriptors as a, readSubscriptionExpiry as b, summarizeCatalog as c, isVideoGenModel as d, checkShape as f, parseSubscriptionUsage as g, parsePlans as h, PROBED_VISION as i, numZeroOk as j, num as k, visionOf as l, matchCurrentPlan as m, LLM_DISPLAY_NAME as n, filterByEnabled as o, countOf as p, LLM_PROVIDER_ID as r, rosterWithAvailability as s, DEFAULT_REASONING_EFFORT as t, isImageGenModel as u, parseUsageSeries as v, isAdmitted as w, CONFIG_DEFAULTS as x, quotaWindows as y };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-agnes-token-plan",
|
|
3
3
|
"displayName": "DSH Connect Agnes Token Plan",
|
|
4
4
|
"icon": "./icon.svg",
|
|
5
|
-
"version": "0.10.
|
|
5
|
+
"version": "0.10.2",
|
|
6
6
|
"description": "Agnes connect bundle for the Harness Web UI: Token Plan quota panel (account-wide quota windows, cumulative usage, console sign-in) plus optional provider registration that brings Agnes models into DSH conversation, with host-side rate-limit self-healing.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|
|
@@ -95,7 +95,6 @@
|
|
|
95
95
|
"@commitlint/config-conventional": "^21.2.3",
|
|
96
96
|
"@types/node": "^26.6.3",
|
|
97
97
|
"jscpd": "^5.4.0",
|
|
98
|
-
"jsqr": "^1.4.0",
|
|
99
98
|
"tsdown": "^0.23.0",
|
|
100
99
|
"typescript": "^5.9.3"
|
|
101
100
|
}
|