@eddyskywalker/dsh-chatgpt-subscription 0.14.0 → 0.15.0
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 +24 -0
- package/README.md +1 -0
- package/lib/index.js +462 -18
- package/lib/types/host/reasoning-collapse-guard/index.d.ts +166 -0
- package/lib/types/host/reasoning-collapse-guard/index.d.ts.map +1 -1
- package/lib/types/host/workbuddy/client.d.ts +1 -1
- package/lib/types/host/workbuddy/client.d.ts.map +1 -1
- package/lib/types/host/workbuddy/model-catalog.d.ts +77 -4
- package/lib/types/host/workbuddy/model-catalog.d.ts.map +1 -1
- package/lib/types/host/workbuddy/routes.d.ts +7 -1
- package/lib/types/host/workbuddy/routes.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.15.0 - 2026-10-09
|
|
6
|
+
|
|
7
|
+
- **[WorkBuddy] 补上网关「在服务但从不公布」的模型,并按厂商归拢模型列表**
|
|
8
|
+
- **根因是一处反直觉的实测事实:`/v3/config` 不是服务全集。** 国际区实测,`gpt-6-sol`、`gpt-6-luna`、`gemini-3.8-flash` 三个 id 都能正常返回 200 的流式补全,却**完全不在目录里**(该接口只公布 `gpt-6-astra` 一个 GPT-6 成员)。判据只有一条——直接问:`code 11102`「service info not found」是没有这个模型,`code 11133`「provider rejected params」是模型存在、只是参数被拒。**`11133` 不能当存在性证据**:`max_tokens` 给得太小会把目录里的正常模型也波及进来(`gpt-5.5`、`gpt-5.4` 都会落到这一类)。
|
|
9
|
+
- **只加进内置表等于加了个看不见的模型。** 联网时实时目录会**整体替换**内置表,所以这三个 id 单列一份实测表(`UNPUBLISHED_MODELS`),并在**实时目录解析**与**快照反序列化**两处合并进去;已公布的条目永远优先,合并只填目录没提到的 id。国际区因此从 22 个变 25 个。
|
|
10
|
+
- **哪些字段是量出来的、哪些是继承的,界线写在代码里。** 存在性、思考档位、图片支持是**打接口测出来的**(`gpt-6-sol` 精确接受 `low`/`medium`/`high`/`xhigh`/`max`、拒绝 `minimal`,与 `gpt-6-astra` 公布的档位表一致;两者都接受 1×1 PNG)。上下文窗口、输出上限、`canDisableThinking` 则是**继承同族已公布的兄弟模型**(`gpt-6-astra` / `gemini-3.5-flash`)并就地标注——网关对这些 id 不公布任何信息,而 `max_tokens` 也不受窗口约束(连 `gpt-6-astra` 自己都接受远超其公布上限的值),从请求侧无法反推。三者仍可在设置页逐模型覆盖。
|
|
11
|
+
- **同一份现场实读还修正了国际区三处数值**:`glm-5.3-flash` 国际区也开始服务(此前标为仅国区);`glm-5.3` / `glm-5.2` 国际区输出上限是 48000、`kimi-k2.8-preview` 是 32000。**一条表同时覆盖两区时取小值**:高报会让请求被网关拒绝,低报只是提前压缩,且仍可覆盖。
|
|
12
|
+
- **国区同步**:新增 `hy4-preview-f`、`space-bunny`,`hy4-preview-x` 已下线。
|
|
13
|
+
- **模型列表按厂商分组,不再让网关的排列顺序决定观感。** 网关给 `/v3/config` 的顺序是交错厂商的:一行 DeepSeek、一行 GPT-6-Astra、两行混元、一行 Kimi、再三行 GPT——八个 OpenAI 模型在设置页里从不挨在一起。现在按厂商族分组,族内**新代在前**。
|
|
14
|
+
- **组内排序按版本号比较,而不是照抄目录序。** 这是被实测逼出来的:只做分组时「在服务但不公布」的模型因为追加在已公布列表之后,`gpt-6-sol` 会渲染到 `gpt-5.4` **下面**——最新的沉底。改成比较 id 里的数字段后归位,且数字按**数值**比(`gpt-6.10` 在 `gpt-6.6` 之前),同版本保持目录序(`-flash` 仍领着 `-flash-sg`),读不出版本的 id(`primary-model` 这类别名)保持原位而不瞎猜。
|
|
15
|
+
- **分组放在 host 而不是设置页。** `modelsForRegion` 同样是请求路径解析模型的入口,在那儿排一次,picker 与 adapter 不可能出现两套顺序;客户端一行未改。
|
|
16
|
+
- 厂商归属有个坑:`hy3` / `hy4-preview` / `hunyuan-chat` 是腾讯混元的三种写法,必须归到同一组;没有任何规则命中的 id **自成一族**,而不是被扫进一个共享的 `other` 桶。
|
|
17
|
+
- 测试:新增分组与排序断言(族必须连续、组内新代在前、数值比较、同版本保持目录序),并扩充三个既有目录测试。
|
|
18
|
+
- 验证:`npm run typecheck` 0 错误;全量 `npm test` **2886 passed / 7 skipped**。另加两个一次性探针(`scripts/probe-workbuddy-catalog.probe.ts` 拉实时目录做差异、`scripts/probe-workbuddy-unlisted.probe.ts` 给目录外的 id 测档位/图片/输出上限),本条的每一项数值都是它们的输出。
|
|
19
|
+
|
|
20
|
+
- **[思维保护] 推理坍缩守卫补上第二条独立信号:改写型死循环**
|
|
21
|
+
- **原守卫对「换个说法反复说同一件事」是瞎的。** 唯一率信号只看得见**逐字**重复;一段把同一结论用不同措辞重述的流实测只有 0.48,而阈值是 0.85,于是一路跑到输出上限也没人拦。这不是理论问题——归档样本 `paraphrased-loop-reasoning.txt` 就是这种形态。
|
|
22
|
+
- **第二条信号量的是「复述」而非「新颖」。** 把窗口切成句子,逐句与至少 `semanticLag` 句之前的内容做 3-gram Jaccard 相似度并取最大值。**枚举式推理也会重复,但它是相邻重复**——每一项借用上一项的句式;复述信号**忽略相邻性**,这正是区分两者的关键,所以健康的枚举流不会被误伤。
|
|
23
|
+
- **两条路径,低门槛那条在每一步都更严。** 高门槛路径(原规则,未改动)要求唯一率 ≥ 0.85 且复述 ≥ 0.35;低门槛路径(新)允许唯一率低到 0.6,代价是复述门槛抬到 0.45、**外加一道高路径不需要的新颖度上限**(不同句比例 ≤ 0.47),且需要更多次确认(5 次 vs 3 次)。**用更弱的证据换更多、更长的确认。**
|
|
24
|
+
- **确认是连续窗口计数,不是单窗口触发。** 两个信号必须在同一窗口同时过线,且连续 `semanticConfirmations` 次才动手;只有一方过线、或只有单个异常窗口,一律**只记日志不动作**。断流后连击归零,下一次尝试不会继承它没挣到的确认数。
|
|
25
|
+
- **近失也留痕。** 单信号命中有专门的 alone 日志(每种每轮只报一次,避免数百行刷屏),连击在够数前断掉也有 cleared 日志——否则两段短连击在日志里与一段长连击长得一模一样,确认数事后无从推理。
|
|
26
|
+
- 测试:守卫测试 **39 → 80 例**(源文件 480 → 948 行),新增归档样本 fixture 与真实的改写型坍缩样本。
|
|
27
|
+
- 验证:全量 `npm test` 2886 passed / 7 skipped;守卫专项 80 例全通过。
|
|
28
|
+
|
|
5
29
|
## 0.14.0 - 2026-10-08
|
|
6
30
|
|
|
7
31
|
- **[Claude] 支持 Claude Haiku 5.5(`claude-haiku-5-5`),并把申报的客户端版本抬到 2.1.293**
|
package/README.md
CHANGED
|
@@ -153,6 +153,7 @@
|
|
|
153
153
|
- 插件托管账号可真正删除;桌面扫描账号只能从本插件隐藏/恢复,永不删除 CodeBuddy 的原文件;
|
|
154
154
|
- 两区模型清单不同,把模型发到不服务它的区会返回 400 `code 11102`,因此模型选择器**按当前账号区域过滤**;
|
|
155
155
|
- **模型目录取自网关自己的 `/v3/config`**(官方 CLI 启动时读的就是它):每个模型的真实上下文上限、输出上限、是否接受图片、以及可用的思考档位都在这里,不做任何按模型名猜测。`/v1/models` 在这条线路上是 404,所以此前只能靠内置表——现在内置表只作为离线兜底,且是**从真实 `/v3/config` 转录**的(早期手写版本把 `glm-5.3`、`kimi-k3` 的窗口猜成 200K/256K,实际都是 1M);
|
|
156
|
+
- **目录不是服务全集,模型必须实测**:实测国际区的 `gpt-6-sol`、`gpt-6-luna`、`gemini-3.8-flash` 都能正常返回 200,却**完全不在 `/v3/config` 里**(该接口只公布 `gpt-6-astra`)。而联网时目录会**整体替换**内置表,只加进内置表等于加了个看不见的模型——所以这三个单列一份实测表(`UNPUBLISHED_MODELS`),并合并进实时目录。存在性、思考档位、图片支持是**打接口量出来的**(`gpt-6-sol` 只收 `low`/`medium`/`high`/`xhigh`/`max`,`minimal` 被拒);上下文与输出上限**继承同族已公布的兄弟模型**(`gpt-6-astra` / `gemini-3.5-flash`),因为网关对这些 id 不公布任何信息,而 `max_tokens` 也不受窗口约束(连 `gpt-6-astra` 自己都接受远超其公布上限的值),无从反推。三者仍可在设置页逐模型覆盖;判定一个 id 是否存在只有一条路——直接问:`code 11102` 是没有这个模型,`code 11133` 是模型存在但参数被拒;
|
|
156
157
|
- 目录同时区分**默认服务的上下文长度**与**模型上限**(如 `deepseek-v4.1-flash` 默认 300K、最大 1M)。本线路不发送显式长度参数,所以 DSH 的压缩与溢出判断按**默认服务长度**计算,不会让请求越过后端实际接受的窗口;
|
|
157
158
|
- 思考档位**逐模型**取目录声明的档位表,回落顺序为「调用方显式指定 → 用户配置的默认档 → 目录为该模型声明的默认档」;
|
|
158
159
|
- **最后一档不能省**:上游在请求不带 `reasoning_effort` 时返回**空的 `reasoning_content`**(实测同一提示:不带字段 0 字符,带字段 130–215 字符)——不发送等于静默丢弃模型的思考;
|
package/lib/index.js
CHANGED
|
@@ -30276,6 +30276,94 @@ function convergeWorkBuddyEffort(effort, ladder) {
|
|
|
30276
30276
|
//#endregion
|
|
30277
30277
|
//#region src/host/workbuddy/model-catalog.ts
|
|
30278
30278
|
/**
|
|
30279
|
+
* Models the gateway serves but does not publish in `/v3/config`.
|
|
30280
|
+
*
|
|
30281
|
+
* The catalog is a published list, not the served one: measured on the
|
|
30282
|
+
* international backend, `gpt-6-sol`, `gpt-6-luna` and `gemini-3.8-flash` all
|
|
30283
|
+
* answer a normal streaming completion with 200 while being absent from
|
|
30284
|
+
* `/v3/config` entirely, and a reachable gateway *replaces* the shipped table
|
|
30285
|
+
* with that list. Without these rows the picker would simply never show them, so
|
|
30286
|
+
* they are declared here and merged into a live catalog too
|
|
30287
|
+
* ({@link withUnpublishedModels}) rather than living in the fallback alone.
|
|
30288
|
+
*
|
|
30289
|
+
* What each row records, and how it was established, differs by field:
|
|
30290
|
+
*
|
|
30291
|
+
* - **Existence, the reasoning ladder, image support** were measured against the
|
|
30292
|
+
* live endpoint, not inferred from the name. A rejected level answers 400
|
|
30293
|
+
* `code 11133` with `extError` `400002`; `gpt-6-sol` takes exactly the ladder
|
|
30294
|
+
* `gpt-6-astra` publishes (`minimal` refused, `low`/`medium`/`high`/`xhigh`/
|
|
30295
|
+
* `max` accepted) and both accept a 1x1 PNG.
|
|
30296
|
+
* - **Context window, output cap and `canDisableThinking` are inherited from the
|
|
30297
|
+
* published sibling in the same family** — `gpt-6-astra` for `gpt-6-sol` and
|
|
30298
|
+
* `gpt-6-luna`, `gemini-3.5-flash` for `gemini-3.8-flash`. They are marked
|
|
30299
|
+
* here rather than measured because the gateway publishes nothing for these
|
|
30300
|
+
* ids and offers no cheap way to read the real figure: it accepts a
|
|
30301
|
+
* `max_tokens` larger than any published window (even for `gpt-6-astra`, whose
|
|
30302
|
+
* own catalog caps at 128000), so the number is neither enforced nor
|
|
30303
|
+
* discoverable from the request side.
|
|
30304
|
+
*
|
|
30305
|
+
* Every field stays overridable per model from the settings card, and an
|
|
30306
|
+
* inherited window errs low rather than high: over-declaring overflows the
|
|
30307
|
+
* gateway mid-session, while under-declaring only compresses earlier.
|
|
30308
|
+
*/
|
|
30309
|
+
const UNPUBLISHED_MODELS = [
|
|
30310
|
+
{
|
|
30311
|
+
id: "gpt-6-sol",
|
|
30312
|
+
name: "GPT-6-Sol",
|
|
30313
|
+
contextWindow: 4e5,
|
|
30314
|
+
maxContextWindow: 1e6,
|
|
30315
|
+
maxTokens: 128e3,
|
|
30316
|
+
regions: ["intl"],
|
|
30317
|
+
supportsImage: true,
|
|
30318
|
+
reasoningEfforts: [
|
|
30319
|
+
"low",
|
|
30320
|
+
"medium",
|
|
30321
|
+
"high",
|
|
30322
|
+
"xhigh",
|
|
30323
|
+
"max"
|
|
30324
|
+
],
|
|
30325
|
+
defaultReasoningEffort: "high",
|
|
30326
|
+
canDisableThinking: true,
|
|
30327
|
+
description: "OpenAI 旗舰模型,擅长复杂推理与长程任务"
|
|
30328
|
+
},
|
|
30329
|
+
{
|
|
30330
|
+
id: "gpt-6-luna",
|
|
30331
|
+
name: "GPT-6-Luna",
|
|
30332
|
+
contextWindow: 4e5,
|
|
30333
|
+
maxContextWindow: 1e6,
|
|
30334
|
+
maxTokens: 128e3,
|
|
30335
|
+
regions: ["intl"],
|
|
30336
|
+
supportsImage: true,
|
|
30337
|
+
reasoningEfforts: [
|
|
30338
|
+
"low",
|
|
30339
|
+
"medium",
|
|
30340
|
+
"high",
|
|
30341
|
+
"xhigh",
|
|
30342
|
+
"max"
|
|
30343
|
+
],
|
|
30344
|
+
defaultReasoningEffort: "high",
|
|
30345
|
+
canDisableThinking: true,
|
|
30346
|
+
description: "OpenAI 轻量模型,响应快速,适合日常任务"
|
|
30347
|
+
},
|
|
30348
|
+
{
|
|
30349
|
+
id: "gemini-3.8-flash",
|
|
30350
|
+
name: "Gemini-3.8-Flash",
|
|
30351
|
+
contextWindow: 1e6,
|
|
30352
|
+
maxContextWindow: 1e6,
|
|
30353
|
+
maxTokens: 65536,
|
|
30354
|
+
regions: ["intl"],
|
|
30355
|
+
supportsImage: true,
|
|
30356
|
+
reasoningEfforts: [
|
|
30357
|
+
"low",
|
|
30358
|
+
"high",
|
|
30359
|
+
"max"
|
|
30360
|
+
],
|
|
30361
|
+
defaultReasoningEffort: "medium",
|
|
30362
|
+
canDisableThinking: false,
|
|
30363
|
+
description: "能力均衡,适合日常使用"
|
|
30364
|
+
}
|
|
30365
|
+
];
|
|
30366
|
+
/**
|
|
30279
30367
|
* Offline fallback catalog, transcribed from a live `/v3/config` read.
|
|
30280
30368
|
*
|
|
30281
30369
|
* Used when the gateway cannot be reached (first run before sign-in, or a
|
|
@@ -30372,6 +30460,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30372
30460
|
canDisableThinking: false,
|
|
30373
30461
|
description: "深度推理,适合深度分析与难题"
|
|
30374
30462
|
},
|
|
30463
|
+
...UNPUBLISHED_MODELS.filter((model) => model.id === "gpt-6-sol" || model.id === "gpt-6-luna"),
|
|
30375
30464
|
{
|
|
30376
30465
|
id: "gpt-6-astra",
|
|
30377
30466
|
name: "GPT-6-Astra",
|
|
@@ -30489,7 +30578,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30489
30578
|
name: "GLM-5.3",
|
|
30490
30579
|
contextWindow: 1e6,
|
|
30491
30580
|
maxContextWindow: 1e6,
|
|
30492
|
-
maxTokens:
|
|
30581
|
+
maxTokens: 48e3,
|
|
30493
30582
|
regions: ["intl", "cn"],
|
|
30494
30583
|
supportsImage: true,
|
|
30495
30584
|
reasoningEfforts: [
|
|
@@ -30504,10 +30593,10 @@ const FALLBACK_MODELS$1 = [
|
|
|
30504
30593
|
{
|
|
30505
30594
|
id: "glm-5.3-flash",
|
|
30506
30595
|
name: "GLM-5.3-Flash",
|
|
30507
|
-
contextWindow:
|
|
30596
|
+
contextWindow: 3e5,
|
|
30508
30597
|
maxContextWindow: 1e6,
|
|
30509
30598
|
maxTokens: 32e3,
|
|
30510
|
-
regions: ["cn"],
|
|
30599
|
+
regions: ["intl", "cn"],
|
|
30511
30600
|
supportsImage: true,
|
|
30512
30601
|
reasoningEfforts: [
|
|
30513
30602
|
"low",
|
|
@@ -30523,7 +30612,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30523
30612
|
name: "GLM-5.2",
|
|
30524
30613
|
contextWindow: 1e6,
|
|
30525
30614
|
maxContextWindow: 1e6,
|
|
30526
|
-
maxTokens:
|
|
30615
|
+
maxTokens: 48e3,
|
|
30527
30616
|
regions: ["intl", "cn"],
|
|
30528
30617
|
supportsImage: true,
|
|
30529
30618
|
reasoningEfforts: ["high", "xhigh"],
|
|
@@ -30677,7 +30766,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30677
30766
|
name: "Kimi-K2.8-Preview",
|
|
30678
30767
|
contextWindow: 3e5,
|
|
30679
30768
|
maxContextWindow: 1e6,
|
|
30680
|
-
maxTokens:
|
|
30769
|
+
maxTokens: 32e3,
|
|
30681
30770
|
regions: ["intl", "cn"],
|
|
30682
30771
|
supportsImage: true,
|
|
30683
30772
|
reasoningEfforts: [
|
|
@@ -30856,7 +30945,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30856
30945
|
description: "混元思考模型,具有增强的推理能力"
|
|
30857
30946
|
},
|
|
30858
30947
|
{
|
|
30859
|
-
id: "hy4-preview-
|
|
30948
|
+
id: "hy4-preview-f",
|
|
30860
30949
|
name: "Hy4 preview",
|
|
30861
30950
|
contextWindow: 1e6,
|
|
30862
30951
|
maxContextWindow: 1e6,
|
|
@@ -30911,6 +31000,25 @@ const FALLBACK_MODELS$1 = [
|
|
|
30911
31000
|
canDisableThinking: false,
|
|
30912
31001
|
description: ""
|
|
30913
31002
|
},
|
|
31003
|
+
{
|
|
31004
|
+
id: "space-bunny",
|
|
31005
|
+
name: "Space-Bunny",
|
|
31006
|
+
contextWindow: 1e6,
|
|
31007
|
+
maxContextWindow: 1e6,
|
|
31008
|
+
maxTokens: 128e3,
|
|
31009
|
+
regions: ["cn"],
|
|
31010
|
+
supportsImage: true,
|
|
31011
|
+
reasoningEfforts: [
|
|
31012
|
+
"low",
|
|
31013
|
+
"medium",
|
|
31014
|
+
"high",
|
|
31015
|
+
"xhigh",
|
|
31016
|
+
"max"
|
|
31017
|
+
],
|
|
31018
|
+
defaultReasoningEffort: "max",
|
|
31019
|
+
canDisableThinking: false,
|
|
31020
|
+
description: "推理速度极快,编码能力强劲,并支持原生多模态输入的匿名大模型"
|
|
31021
|
+
},
|
|
30914
31022
|
{
|
|
30915
31023
|
id: "minimax-m3",
|
|
30916
31024
|
name: "MiniMax-M3",
|
|
@@ -30962,6 +31070,7 @@ const FALLBACK_MODELS$1 = [
|
|
|
30962
31070
|
canDisableThinking: false,
|
|
30963
31071
|
description: ""
|
|
30964
31072
|
},
|
|
31073
|
+
...UNPUBLISHED_MODELS.filter((model) => model.id === "gemini-3.8-flash"),
|
|
30965
31074
|
{
|
|
30966
31075
|
id: "gemini-3.5-flash",
|
|
30967
31076
|
name: "Gemini-3.5-Flash",
|
|
@@ -30997,6 +31106,21 @@ const DEFAULT_VISIBLE_MODEL_IDS$1 = [
|
|
|
30997
31106
|
/** Every id the fallback knows, in declaration order. */
|
|
30998
31107
|
const WORKBUDDY_MODEL_IDS = FALLBACK_MODELS$1.map((model) => model.id);
|
|
30999
31108
|
/**
|
|
31109
|
+
* Add the served-but-unpublished models to a live `/v3/config` catalog.
|
|
31110
|
+
*
|
|
31111
|
+
* A reachable gateway replaces the shipped table with the list it publishes, so
|
|
31112
|
+
* a model it serves without listing ({@link UNPUBLISHED_MODELS}) would drop out
|
|
31113
|
+
* of the picker the moment the catalog loads. Merging here keeps it offered.
|
|
31114
|
+
*
|
|
31115
|
+
* A published entry always wins: the gateway is the authority on anything it
|
|
31116
|
+
* does describe, so these rows only fill ids the payload is silent about.
|
|
31117
|
+
*/
|
|
31118
|
+
function withUnpublishedModels(catalog, region) {
|
|
31119
|
+
const known = new Set(catalog.map((model) => model.id));
|
|
31120
|
+
const extra = UNPUBLISHED_MODELS.filter((model) => model.regions.includes(region) && !known.has(model.id));
|
|
31121
|
+
return extra.length > 0 ? [...catalog, ...extra] : [...catalog];
|
|
31122
|
+
}
|
|
31123
|
+
/**
|
|
31000
31124
|
* Resolve one entry from a catalog.
|
|
31001
31125
|
*
|
|
31002
31126
|
* An unknown id resolves to a synthetic text-only entry rather than the first
|
|
@@ -31028,9 +31152,131 @@ function resolveWorkBuddyModel(id, catalog = FALLBACK_MODELS$1) {
|
|
|
31028
31152
|
description: ""
|
|
31029
31153
|
};
|
|
31030
31154
|
}
|
|
31031
|
-
/**
|
|
31155
|
+
/**
|
|
31156
|
+
* Vendor families, resolved from a model id.
|
|
31157
|
+
*
|
|
31158
|
+
* The gateway orders `/v3/config` the way it feels like, which interleaves
|
|
31159
|
+
* vendors: a DeepSeek row, then a GPT row, then two Hunyuan rows, then a Kimi
|
|
31160
|
+
* row, then three more GPT rows. In a picker that reads as noise — the eye
|
|
31161
|
+
* cannot tell that the GPT entries belong together — so the list is grouped by
|
|
31162
|
+
* family before it is shown.
|
|
31163
|
+
*
|
|
31164
|
+
* The rules are ordered, first match wins. Only families this subscription
|
|
31165
|
+
* actually serves are listed: a rule for a model that cannot appear would be a
|
|
31166
|
+
* guess about a future catalog.
|
|
31167
|
+
*
|
|
31168
|
+
* An id no rule claims is its own family rather than a shared `other` bucket, so
|
|
31169
|
+
* an unrecognised model keeps its place in the order instead of being swept into
|
|
31170
|
+
* a pile of models it has nothing to do with.
|
|
31171
|
+
*/
|
|
31172
|
+
const WORKBUDDY_VENDOR_RULES = [
|
|
31173
|
+
{
|
|
31174
|
+
re: /^(default|fast|balanced|primary|deep)(-model)?$/,
|
|
31175
|
+
family: "alias"
|
|
31176
|
+
},
|
|
31177
|
+
{
|
|
31178
|
+
re: /^gpt-/,
|
|
31179
|
+
family: "openai"
|
|
31180
|
+
},
|
|
31181
|
+
{
|
|
31182
|
+
re: /^gemini-/,
|
|
31183
|
+
family: "google"
|
|
31184
|
+
},
|
|
31185
|
+
{
|
|
31186
|
+
re: /^glm-/,
|
|
31187
|
+
family: "zhipu"
|
|
31188
|
+
},
|
|
31189
|
+
{
|
|
31190
|
+
re: /^kimi-/,
|
|
31191
|
+
family: "moonshot"
|
|
31192
|
+
},
|
|
31193
|
+
{
|
|
31194
|
+
re: /^deepseek-/,
|
|
31195
|
+
family: "deepseek"
|
|
31196
|
+
},
|
|
31197
|
+
{
|
|
31198
|
+
re: /^hy[0-9]/,
|
|
31199
|
+
family: "tencent"
|
|
31200
|
+
},
|
|
31201
|
+
{
|
|
31202
|
+
re: /^hunyuan/,
|
|
31203
|
+
family: "tencent"
|
|
31204
|
+
},
|
|
31205
|
+
{
|
|
31206
|
+
re: /^minimax-/,
|
|
31207
|
+
family: "minimax"
|
|
31208
|
+
},
|
|
31209
|
+
{
|
|
31210
|
+
re: /^space-bunny$/,
|
|
31211
|
+
family: "space-bunny"
|
|
31212
|
+
}
|
|
31213
|
+
];
|
|
31214
|
+
/** The vendor family one model belongs to. */
|
|
31215
|
+
function workBuddyModelVendor(id) {
|
|
31216
|
+
const value = id.toLowerCase();
|
|
31217
|
+
for (const rule of WORKBUDDY_VENDOR_RULES) if (rule.re.test(value)) return rule.family;
|
|
31218
|
+
return id;
|
|
31219
|
+
}
|
|
31220
|
+
/**
|
|
31221
|
+
* Numeric segments of a model id, e.g. `gpt-6-astra` → [6].
|
|
31222
|
+
*
|
|
31223
|
+
* Version numbers are compared as numbers, so GPT-6.0 sorts below GPT-6.6 rather
|
|
31224
|
+
* than above it as it would if the strings were compared.
|
|
31225
|
+
*/
|
|
31226
|
+
function versionSegments$1(id) {
|
|
31227
|
+
return [...id.toLowerCase().matchAll(/\d+/g)].map((match) => Number(match[0]));
|
|
31228
|
+
}
|
|
31229
|
+
/**
|
|
31230
|
+
* Order two models of one family, newest generation first.
|
|
31231
|
+
*
|
|
31232
|
+
* Catalog order alone is not good enough. The models served but not published
|
|
31233
|
+
* are appended after the published list, which left `gpt-6-sol` below
|
|
31234
|
+
* `gpt-5.4` — the newest models at the bottom of their own group. Comparing the
|
|
31235
|
+
* numeric segments puts every GPT-6 row together and ahead of every GPT-5 one,
|
|
31236
|
+
* and leaves an id whose version cannot be read (an alias, a nickname) in the
|
|
31237
|
+
* catalog's own order rather than guessing where it belongs.
|
|
31238
|
+
*
|
|
31239
|
+
* Ties keep catalog order, so `deepseek-v4.1-flash` still leads its
|
|
31240
|
+
* `-flash-sg` sibling.
|
|
31241
|
+
*/
|
|
31242
|
+
function compareWithinFamily(a, b) {
|
|
31243
|
+
const left = versionSegments$1(a.id);
|
|
31244
|
+
const right = versionSegments$1(b.id);
|
|
31245
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
31246
|
+
const x = left[index];
|
|
31247
|
+
const y = right[index];
|
|
31248
|
+
if (x === void 0) return 1;
|
|
31249
|
+
if (y === void 0) return -1;
|
|
31250
|
+
if (x !== y) return y - x;
|
|
31251
|
+
}
|
|
31252
|
+
return 0;
|
|
31253
|
+
}
|
|
31254
|
+
/**
|
|
31255
|
+
* Group a list so every vendor's models sit together.
|
|
31256
|
+
*
|
|
31257
|
+
* Families appear in the order their first model does — the catalog decides
|
|
31258
|
+
* which vendor leads, not this function — and each family is ordered newest
|
|
31259
|
+
* generation first.
|
|
31260
|
+
*/
|
|
31261
|
+
function groupWorkBuddyModelsByVendor(models) {
|
|
31262
|
+
const families = /* @__PURE__ */ new Map();
|
|
31263
|
+
for (const model of models) {
|
|
31264
|
+
const family = workBuddyModelVendor(model.id);
|
|
31265
|
+
const bucket = families.get(family);
|
|
31266
|
+
if (bucket === void 0) families.set(family, [model]);
|
|
31267
|
+
else bucket.push(model);
|
|
31268
|
+
}
|
|
31269
|
+
return [...families.values()].map((bucket) => [...bucket].sort(compareWithinFamily)).flat();
|
|
31270
|
+
}
|
|
31271
|
+
/**
|
|
31272
|
+
* Models an account in `region` can actually call, grouped by vendor.
|
|
31273
|
+
*
|
|
31274
|
+
* Grouped here rather than in the settings card because this is also what the
|
|
31275
|
+
* request path resolves against: one ordering rule, so the picker and the
|
|
31276
|
+
* adapter can never disagree about which models a region offers.
|
|
31277
|
+
*/
|
|
31032
31278
|
function modelsForRegion(region, catalog = FALLBACK_MODELS$1) {
|
|
31033
|
-
return catalog.filter((model) => model.regions.includes(region));
|
|
31279
|
+
return groupWorkBuddyModelsByVendor(catalog.filter((model) => model.regions.includes(region)));
|
|
31034
31280
|
}
|
|
31035
31281
|
/** Whether one model accepts image input. */
|
|
31036
31282
|
function workBuddyModelSupportsImage(id, catalog = FALLBACK_MODELS$1) {
|
|
@@ -31980,7 +32226,7 @@ async function fetchConfigCatalog(credentials, options = {}) {
|
|
|
31980
32226
|
signal: timeoutSignal$1(options.signal, DISCOVERY_TIMEOUT_MS$1)
|
|
31981
32227
|
});
|
|
31982
32228
|
if (!response.ok) throw new Error(`${PROVIDER_NAME$1} model catalog failed: ${response.status}`);
|
|
31983
|
-
return parseConfigModels(await response.json(), credentials.region);
|
|
32229
|
+
return withUnpublishedModels(parseConfigModels(await response.json(), credentials.region), credentials.region);
|
|
31984
32230
|
}
|
|
31985
32231
|
/** Cached catalog with a TTL; a failed refresh keeps the previous snapshot.
|
|
31986
32232
|
*
|
|
@@ -32014,7 +32260,7 @@ async function loadConfigCatalog(credentials, options = {}) {
|
|
|
32014
32260
|
}, (fetchedAt, models) => {
|
|
32015
32261
|
cachedCatalog = {
|
|
32016
32262
|
region: credentials.region,
|
|
32017
|
-
models,
|
|
32263
|
+
models: withUnpublishedModels(models, credentials.region),
|
|
32018
32264
|
fetchedAt
|
|
32019
32265
|
};
|
|
32020
32266
|
}, 0) > 0) {
|
|
@@ -32540,7 +32786,13 @@ function resolveEnabledModelIds$2(stored, available, enabled = true) {
|
|
|
32540
32786
|
const known = new Set(available);
|
|
32541
32787
|
return stored.filter((id) => known.has(id));
|
|
32542
32788
|
}
|
|
32543
|
-
/**
|
|
32789
|
+
/**
|
|
32790
|
+
* Build the model options the settings card renders.
|
|
32791
|
+
*
|
|
32792
|
+
* Order comes from the caller, which passes a vendor-grouped list so a family
|
|
32793
|
+
* is not interleaved with the next one; this preserves whatever order it is
|
|
32794
|
+
* given rather than imposing a second one.
|
|
32795
|
+
*/
|
|
32544
32796
|
function buildModelOptions(catalog, available, enabledModelIds, overrides) {
|
|
32545
32797
|
const enabled = new Set(enabledModelIds);
|
|
32546
32798
|
return available.map((id) => {
|
|
@@ -45550,6 +45802,22 @@ function installRelayProbe(ctx, options) {
|
|
|
45550
45802
|
* degenerate stream scores near one. On a hit it stops yielding chunks, so the
|
|
45551
45803
|
* in-flight request ends, and lets the turn resume on a fresh step.
|
|
45552
45804
|
*
|
|
45805
|
+
* Uniqueness alone only sees verbatim repetition. A stream can restate one
|
|
45806
|
+
* conclusion over and over in fresh words, score 0.48 where the threshold is
|
|
45807
|
+
* 0.85, and run to the output ceiling untouched. So a second, independent
|
|
45808
|
+
* signal scores the same window for recurrence: how strongly each sentence
|
|
45809
|
+
* repeats something the model already said at least `semanticLag` sentences
|
|
45810
|
+
* earlier. Enumerative reasoning repeats itself too, but adjacently — each item
|
|
45811
|
+
* borrows the previous item's frame — and recurrence ignores adjacency, which
|
|
45812
|
+
* is what separates the two.
|
|
45813
|
+
*
|
|
45814
|
+
* Neither signal stops anything on its own. A break needs both over their
|
|
45815
|
+
* thresholds in the same window, and then `semanticConfirmations` consecutive
|
|
45816
|
+
* such windows: one signal alone, or a single anomalous window, is logged and
|
|
45817
|
+
* released. That gradient is the whole design. A guard that cuts a healthy
|
|
45818
|
+
* answer is worse than one that lets a runaway finish, because the user sees a
|
|
45819
|
+
* turn stop mid-sentence with no explanation, and the fix costs a whole turn.
|
|
45820
|
+
*
|
|
45553
45821
|
* Seam choice: the guard wraps `llm/stream`, not `agent/assistant-stream`.
|
|
45554
45822
|
* `agent/assistant-stream` is emit-mode — it can observe but cannot stop a
|
|
45555
45823
|
* stream — and it does not exist before harness 0.1.5, which would make the
|
|
@@ -45586,6 +45854,15 @@ const DEFAULT_GUARD_OPTIONS = {
|
|
|
45586
45854
|
maxBreaksPerTurn: 3,
|
|
45587
45855
|
cooldownMs: 3e4,
|
|
45588
45856
|
includeModels: [],
|
|
45857
|
+
semanticThreshold: .35,
|
|
45858
|
+
semanticLag: 6,
|
|
45859
|
+
semanticMinChars: 360,
|
|
45860
|
+
semanticMinSentences: 8,
|
|
45861
|
+
semanticConfirmations: 3,
|
|
45862
|
+
semanticLiteralFloor: .6,
|
|
45863
|
+
semanticStrictThreshold: .45,
|
|
45864
|
+
semanticDistinctCeiling: .47,
|
|
45865
|
+
semanticStrictConfirmations: 5,
|
|
45589
45866
|
now: () => Date.now()
|
|
45590
45867
|
};
|
|
45591
45868
|
function pruneUndefined(options) {
|
|
@@ -45611,8 +45888,21 @@ function resolveGuardOptions(options = {}) {
|
|
|
45611
45888
|
if (!Number.isFinite(resolved.threshold) || resolved.threshold <= 0 || resolved.threshold >= 1) throw new Error(`reasoning-collapse-guard: invalid threshold ${String(resolved.threshold)} — must be strictly between 0 and 1`);
|
|
45612
45889
|
if (!Number.isInteger(resolved.maxBreaksPerTurn) || resolved.maxBreaksPerTurn < 1) throw new Error(`reasoning-collapse-guard: invalid maxBreaksPerTurn ${String(resolved.maxBreaksPerTurn)} — must be an integer >= 1`);
|
|
45613
45890
|
if (!Number.isInteger(resolved.cooldownMs) || resolved.cooldownMs < 0) throw new Error(`reasoning-collapse-guard: invalid cooldownMs ${String(resolved.cooldownMs)} — must be an integer >= 0`);
|
|
45891
|
+
if (!Number.isFinite(resolved.semanticThreshold) || resolved.semanticThreshold <= 0 || resolved.semanticThreshold >= 1) throw new Error(`reasoning-collapse-guard: invalid semanticThreshold ${String(resolved.semanticThreshold)} — must be strictly between 0 and 1`);
|
|
45892
|
+
if (!Number.isInteger(resolved.semanticLag) || resolved.semanticLag < 1) throw new Error(`reasoning-collapse-guard: invalid semanticLag ${String(resolved.semanticLag)} — must be an integer >= 1`);
|
|
45893
|
+
if (!Number.isInteger(resolved.semanticMinChars) || resolved.semanticMinChars < 1) throw new Error(`reasoning-collapse-guard: invalid semanticMinChars ${String(resolved.semanticMinChars)} — must be an integer >= 1`);
|
|
45894
|
+
if (resolved.semanticMinChars > resolved.windowChars) throw new Error(`reasoning-collapse-guard: invalid semanticMinChars ${resolved.semanticMinChars} — must not exceed windowChars ${resolved.windowChars}`);
|
|
45895
|
+
if (!Number.isInteger(resolved.semanticMinSentences) || resolved.semanticMinSentences < 2) throw new Error(`reasoning-collapse-guard: invalid semanticMinSentences ${String(resolved.semanticMinSentences)} — must be an integer >= 2`);
|
|
45896
|
+
if (!Number.isInteger(resolved.semanticConfirmations) || resolved.semanticConfirmations < 1) throw new Error(`reasoning-collapse-guard: invalid semanticConfirmations ${String(resolved.semanticConfirmations)} — must be an integer >= 1`);
|
|
45897
|
+
if (!Number.isFinite(resolved.semanticLiteralFloor) || resolved.semanticLiteralFloor <= 0 || resolved.semanticLiteralFloor >= 1) throw new Error(`reasoning-collapse-guard: invalid semanticLiteralFloor ${String(resolved.semanticLiteralFloor)} — must be strictly between 0 and 1`);
|
|
45898
|
+
if (!Number.isFinite(resolved.semanticStrictThreshold) || resolved.semanticStrictThreshold <= 0 || resolved.semanticStrictThreshold >= 1) throw new Error(`reasoning-collapse-guard: invalid semanticStrictThreshold ${String(resolved.semanticStrictThreshold)} — must be strictly between 0 and 1`);
|
|
45899
|
+
if (!Number.isFinite(resolved.semanticDistinctCeiling) || resolved.semanticDistinctCeiling <= 0 || resolved.semanticDistinctCeiling >= 1) throw new Error(`reasoning-collapse-guard: invalid semanticDistinctCeiling ${String(resolved.semanticDistinctCeiling)} — must be strictly between 0 and 1`);
|
|
45900
|
+
if (!Number.isInteger(resolved.semanticStrictConfirmations) || resolved.semanticStrictConfirmations < 1) throw new Error(`reasoning-collapse-guard: invalid semanticStrictConfirmations ${String(resolved.semanticStrictConfirmations)} — must be an integer >= 1`);
|
|
45614
45901
|
if (!Array.isArray(resolved.includeModels) || resolved.includeModels.some((entry) => typeof entry !== "string")) throw new Error("reasoning-collapse-guard: includeModels must be an array of strings");
|
|
45615
45902
|
if (typeof resolved.now !== "function") throw new Error("reasoning-collapse-guard: now must be a function");
|
|
45903
|
+
resolved.semanticStrictThreshold = Math.max(resolved.semanticStrictThreshold, resolved.semanticThreshold);
|
|
45904
|
+
resolved.semanticStrictConfirmations = Math.max(resolved.semanticStrictConfirmations, resolved.semanticConfirmations + 1);
|
|
45905
|
+
resolved.semanticLiteralFloor = Math.min(resolved.semanticLiteralFloor, resolved.threshold);
|
|
45616
45906
|
return resolved;
|
|
45617
45907
|
}
|
|
45618
45908
|
/**
|
|
@@ -45635,6 +45925,92 @@ function collapseScore(window, n) {
|
|
|
45635
45925
|
if (total === 0) return 0;
|
|
45636
45926
|
return 1 - grams.size / total;
|
|
45637
45927
|
}
|
|
45928
|
+
/**
|
|
45929
|
+
* Split reasoning text into sentences, for mixed CJK and Latin streams.
|
|
45930
|
+
*
|
|
45931
|
+
* A Chinese full stop ends a sentence whatever follows it. An ASCII `.!?` only
|
|
45932
|
+
* ends one when whitespace or the end of the text follows, because the same
|
|
45933
|
+
* characters carry out decimals, file names and version numbers
|
|
45934
|
+
* (`selectors.test.ts:745`, `3.14`) that must not become sentence
|
|
45935
|
+
* edges. Whitespace runs collapse and case folds, so a difference in spacing
|
|
45936
|
+
* or capitalisation cannot read as a difference in wording.
|
|
45937
|
+
*
|
|
45938
|
+
* @param text - reasoning text, typically one trailing window.
|
|
45939
|
+
* @returns non-empty normalized sentences, in order.
|
|
45940
|
+
*/
|
|
45941
|
+
function splitSentences(text) {
|
|
45942
|
+
const parts = [];
|
|
45943
|
+
let start = 0;
|
|
45944
|
+
for (let i = 0; i < text.length; i++) {
|
|
45945
|
+
const char = text[i];
|
|
45946
|
+
const next = text[i + 1];
|
|
45947
|
+
const cjk = CJK_TERMINATORS.has(char);
|
|
45948
|
+
const latin = LATIN_TERMINATORS.has(char) && (next === void 0 || TRAILING_SPACE.test(next));
|
|
45949
|
+
if (!cjk && !latin) continue;
|
|
45950
|
+
parts.push(text.slice(start, i + 1));
|
|
45951
|
+
start = i + 1;
|
|
45952
|
+
}
|
|
45953
|
+
parts.push(text.slice(start));
|
|
45954
|
+
const sentences = [];
|
|
45955
|
+
for (const part of parts) {
|
|
45956
|
+
const normalized = part.replace(WHITESPACE_RUN, " ").trim().toLowerCase();
|
|
45957
|
+
if (normalized.length > 0) sentences.push(normalized);
|
|
45958
|
+
}
|
|
45959
|
+
return sentences;
|
|
45960
|
+
}
|
|
45961
|
+
/** The three Chinese sentence terminators, which need no trailing space. */
|
|
45962
|
+
const CJK_TERMINATORS = /* @__PURE__ */ new Set([
|
|
45963
|
+
"。",
|
|
45964
|
+
"!",
|
|
45965
|
+
"?"
|
|
45966
|
+
]);
|
|
45967
|
+
/** The three ASCII terminators, which only close a sentence before whitespace. */
|
|
45968
|
+
const LATIN_TERMINATORS = /* @__PURE__ */ new Set([
|
|
45969
|
+
".",
|
|
45970
|
+
"!",
|
|
45971
|
+
"?"
|
|
45972
|
+
]);
|
|
45973
|
+
const TRAILING_SPACE = /\s/;
|
|
45974
|
+
const WHITESPACE_RUN = /\s+/g;
|
|
45975
|
+
/** Character n-grams of one normalized sentence, as a set. */
|
|
45976
|
+
function sentenceGrams(sentence) {
|
|
45977
|
+
const grams = /* @__PURE__ */ new Set();
|
|
45978
|
+
for (let i = 0; i + SEMANTIC_NGRAM_SIZE <= sentence.length; i++) grams.add(sentence.slice(i, i + SEMANTIC_NGRAM_SIZE));
|
|
45979
|
+
return grams;
|
|
45980
|
+
}
|
|
45981
|
+
function gramJaccard(a, b) {
|
|
45982
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
45983
|
+
let shared = 0;
|
|
45984
|
+
for (const gram of a) if (b.has(gram)) shared++;
|
|
45985
|
+
return shared / (a.size + b.size - shared);
|
|
45986
|
+
}
|
|
45987
|
+
/** {@link semanticRecurrence} over sentences the caller has already split. */
|
|
45988
|
+
function sentenceRecurrence(sentences, lag) {
|
|
45989
|
+
const grams = sentences.map(sentenceGrams);
|
|
45990
|
+
if (grams.length <= lag) return 0;
|
|
45991
|
+
let total = 0;
|
|
45992
|
+
let counted = 0;
|
|
45993
|
+
for (let i = lag; i < grams.length; i++) {
|
|
45994
|
+
const current = grams[i];
|
|
45995
|
+
let best = 0;
|
|
45996
|
+
for (let j = 0; j <= i - lag; j++) {
|
|
45997
|
+
const earlier = grams[j];
|
|
45998
|
+
if (Math.min(current.size, earlier.size) / Math.max(current.size, earlier.size) <= best) continue;
|
|
45999
|
+
const similarity = gramJaccard(current, earlier);
|
|
46000
|
+
if (similarity > best) best = similarity;
|
|
46001
|
+
}
|
|
46002
|
+
total += best;
|
|
46003
|
+
counted++;
|
|
46004
|
+
}
|
|
46005
|
+
return counted === 0 ? 0 : total / counted;
|
|
46006
|
+
}
|
|
46007
|
+
/** Character n-gram size both halves of the semantic signal are built on. */
|
|
46008
|
+
const SEMANTIC_NGRAM_SIZE = 3;
|
|
46009
|
+
/** {@link distinctSentenceRatio} over sentences the caller already split. */
|
|
46010
|
+
function distinctRatioOf(sentences) {
|
|
46011
|
+
if (sentences.length < 2) return 1;
|
|
46012
|
+
return new Set(sentences).size / sentences.length;
|
|
46013
|
+
}
|
|
45638
46014
|
const RESUME_HINT = "Your previous reasoning became repetitive and was stopped before it finished. Resume from what you already established instead of restarting the analysis, and keep the rest of this response short.";
|
|
45639
46015
|
/** The second resume message, used once a resume has also collapsed. */
|
|
45640
46016
|
const RESUME_HINT_STRICT = "Your reasoning has now repeated across two attempts. Do not re-derive the analysis a third time. Either state the conclusion you already reached, or make the single next tool call that moves the task forward. Keep this response to a few sentences.";
|
|
@@ -45643,7 +46019,11 @@ function createBreakerState() {
|
|
|
45643
46019
|
breaksThisTurn: 0,
|
|
45644
46020
|
lastBreakAt: -Infinity,
|
|
45645
46021
|
exhausted: false,
|
|
45646
|
-
resuming: false
|
|
46022
|
+
resuming: false,
|
|
46023
|
+
dualStreak: 0,
|
|
46024
|
+
lowStreak: 0,
|
|
46025
|
+
notedLiteralOnly: false,
|
|
46026
|
+
notedSemanticOnly: false
|
|
45647
46027
|
};
|
|
45648
46028
|
}
|
|
45649
46029
|
/**
|
|
@@ -45798,10 +46178,12 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
45798
46178
|
* point — and the log says so, because a break that cannot resume is
|
|
45799
46179
|
* indistinguishable from a broken turn to the user.
|
|
45800
46180
|
*/
|
|
45801
|
-
function breakOff(state,
|
|
46181
|
+
function breakOff(state, literal, semantic, path, agent, sessionId) {
|
|
45802
46182
|
state.breaksThisTurn++;
|
|
45803
46183
|
state.lastBreakAt = resolved.now();
|
|
45804
|
-
|
|
46184
|
+
state.dualStreak = 0;
|
|
46185
|
+
state.lowStreak = 0;
|
|
46186
|
+
ctx.logger?.warn(`reasoning-collapse-guard: stopped degenerate reasoning on the ${path} path after ${path === "high-literal" ? resolved.semanticConfirmations : resolved.semanticStrictConfirmations} confirmed windows (literal ${literal.toFixed(3)}, semantic ${semantic.toFixed(3)}, break ${state.breaksThisTurn}/${resolved.maxBreaksPerTurn}, session ${sessionId ?? "unidentified"}${agent === void 0 ? ", no agent to resume" : ""})`);
|
|
45805
46187
|
if (agent === void 0) return;
|
|
45806
46188
|
agent.cancel?.({
|
|
45807
46189
|
kind: "hook",
|
|
@@ -45819,6 +46201,48 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
45819
46201
|
}
|
|
45820
46202
|
});
|
|
45821
46203
|
}
|
|
46204
|
+
/**
|
|
46205
|
+
* Report one signal holding without the other.
|
|
46206
|
+
*
|
|
46207
|
+
* Reported once per kind per turn, not once per window: a runaway can hold
|
|
46208
|
+
* one signal for hundreds of windows, and a line each would bury the report
|
|
46209
|
+
* of the one that mattered. This is the diagnostic half of the gradient —
|
|
46210
|
+
* the reason a near miss is still diagnosable after the fact.
|
|
46211
|
+
*/
|
|
46212
|
+
function noteLoneSignal(state, kind, literal, semantic, sessionId) {
|
|
46213
|
+
if (kind === "literal" ? state.notedLiteralOnly : state.notedSemanticOnly) return;
|
|
46214
|
+
if (kind === "literal") state.notedLiteralOnly = true;
|
|
46215
|
+
else state.notedSemanticOnly = true;
|
|
46216
|
+
ctx.logger?.warn(`reasoning-collapse-guard: observed the ${kind} signal alone (literal ${literal === void 0 ? "not scored" : literal.toFixed(3)}, semantic ${semantic === void 0 ? "not scored" : semantic.toFixed(3)}, session ${sessionId ?? "unidentified"}) — the other signal has not agreed, not acting`);
|
|
46217
|
+
}
|
|
46218
|
+
/**
|
|
46219
|
+
* Report a run of agreeing windows that ended before it reached the count.
|
|
46220
|
+
*
|
|
46221
|
+
* Without this, two short runs of agreeing windows look in the log exactly
|
|
46222
|
+
* like one long one that simply stopped short, and the confirmation count
|
|
46223
|
+
* becomes impossible to reason about after the fact.
|
|
46224
|
+
*/
|
|
46225
|
+
function noteStreakCleared(state, reached, sessionId) {
|
|
46226
|
+
ctx.logger?.warn(`reasoning-collapse-guard: the signals stopped agreeing after ${reached} of ${resolved.semanticConfirmations} windows (session ${sessionId ?? "unidentified"}) — streak cleared, not acting`);
|
|
46227
|
+
}
|
|
46228
|
+
/** Report the low-literal run that ended before it reached its longer count. */
|
|
46229
|
+
function noteLowStreakCleared(reached, sessionId) {
|
|
46230
|
+
ctx.logger?.warn(`reasoning-collapse-guard: the low-literal bars stopped holding after ${reached} of ${resolved.semanticStrictConfirmations} windows (session ${sessionId ?? "unidentified"}) — streak cleared, not acting`);
|
|
46231
|
+
}
|
|
46232
|
+
/** Report a low-literal window that qualifies but has not been confirmed enough. */
|
|
46233
|
+
function notePendingLowConfirmation(state, literal, semantic, distinct, sessionId) {
|
|
46234
|
+
ctx.logger?.warn(`reasoning-collapse-guard: low-literal bars hold in window ${state.lowStreak}/${resolved.semanticStrictConfirmations} (literal ${literal.toFixed(3)} >= ${resolved.semanticLiteralFloor}, semantic ${semantic.toFixed(3)} >= ${resolved.semanticStrictThreshold}, distinct ${distinct.toFixed(3)} <= ${resolved.semanticDistinctCeiling}, session ${sessionId ?? "unidentified"}) — not acting yet`);
|
|
46235
|
+
}
|
|
46236
|
+
/**
|
|
46237
|
+
* Report a window where both signals held but the streak is still short.
|
|
46238
|
+
*
|
|
46239
|
+
* `semanticConfirmations` is what makes a break a decision rather than a
|
|
46240
|
+
* reflex, so each pending confirmation is named, and the log then says how
|
|
46241
|
+
* close a stream came to being cut by a guard that did not cut it.
|
|
46242
|
+
*/
|
|
46243
|
+
function notePendingConfirmation(state, literal, semantic, sessionId) {
|
|
46244
|
+
ctx.logger?.warn(`reasoning-collapse-guard: both signals hold in window ${state.dualStreak}/${resolved.semanticConfirmations} (literal ${literal.toFixed(3)}, semantic ${semantic.toFixed(3)}, session ${sessionId ?? "unidentified"}) — not acting yet`);
|
|
46245
|
+
}
|
|
45822
46246
|
function guardStream(request, next) {
|
|
45823
46247
|
const state = breakerFor(request.signal ?? STATIC_KEY);
|
|
45824
46248
|
if (state.exhausted || !watchedModel(request.model) || !conversationCall(request)) return next();
|
|
@@ -45829,13 +46253,33 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
45829
46253
|
if (chunk.type === "block-start") window = "";
|
|
45830
46254
|
if (chunk.type === "reasoning-delta" && typeof chunk.text === "string") {
|
|
45831
46255
|
window = appendWindow(window, chunk.text, resolved.windowChars);
|
|
45832
|
-
if (
|
|
45833
|
-
const
|
|
45834
|
-
|
|
46256
|
+
if (resolved.now() - state.lastBreakAt >= resolved.cooldownMs) {
|
|
46257
|
+
const literal = window.length >= resolved.minWindowChars ? collapseScore(window, resolved.ngramSize) : void 0;
|
|
46258
|
+
const sentences = window.length >= resolved.semanticMinChars ? splitSentences(window) : [];
|
|
46259
|
+
const semantic = sentences.length >= resolved.semanticMinSentences ? sentenceRecurrence(sentences, resolved.semanticLag) : void 0;
|
|
46260
|
+
const distinct = sentences.length >= resolved.semanticMinSentences ? distinctRatioOf(sentences) : void 0;
|
|
46261
|
+
const high = literal !== void 0 && literal >= resolved.threshold && semantic !== void 0 && semantic >= resolved.semanticThreshold;
|
|
46262
|
+
const low = literal !== void 0 && literal >= resolved.semanticLiteralFloor && literal < resolved.threshold && semantic !== void 0 && semantic >= resolved.semanticStrictThreshold && distinct !== void 0 && distinct <= resolved.semanticDistinctCeiling;
|
|
46263
|
+
const reachedHigh = state.dualStreak;
|
|
46264
|
+
const reachedLow = state.lowStreak;
|
|
46265
|
+
state.dualStreak = high ? reachedHigh + 1 : 0;
|
|
46266
|
+
state.lowStreak = low ? reachedLow + 1 : 0;
|
|
46267
|
+
if (reachedHigh !== 0 && state.dualStreak === 0) noteStreakCleared(state, reachedHigh, request.sessionId);
|
|
46268
|
+
if (reachedLow !== 0 && state.lowStreak === 0) noteLowStreakCleared(reachedLow, request.sessionId);
|
|
46269
|
+
if (high && state.dualStreak >= resolved.semanticConfirmations) if (state.breaksThisTurn >= resolved.maxBreaksPerTurn) state.exhausted = true;
|
|
46270
|
+
else {
|
|
46271
|
+
breakOff(state, literal, semantic, "high-literal", agent, request.sessionId);
|
|
46272
|
+
return;
|
|
46273
|
+
}
|
|
46274
|
+
else if (low && state.lowStreak >= resolved.semanticStrictConfirmations) if (state.breaksThisTurn >= resolved.maxBreaksPerTurn) state.exhausted = true;
|
|
45835
46275
|
else {
|
|
45836
|
-
breakOff(state,
|
|
46276
|
+
breakOff(state, literal, semantic, "low-literal", agent, request.sessionId);
|
|
45837
46277
|
return;
|
|
45838
46278
|
}
|
|
46279
|
+
else if (high) notePendingConfirmation(state, literal, semantic, request.sessionId);
|
|
46280
|
+
else if (low) notePendingLowConfirmation(state, literal, semantic, distinct, request.sessionId);
|
|
46281
|
+
else if (literal !== void 0 && literal >= resolved.threshold) noteLoneSignal(state, "literal", literal, semantic, request.sessionId);
|
|
46282
|
+
else if (semantic !== void 0 && semantic >= resolved.semanticThreshold) noteLoneSignal(state, "semantic", literal, semantic, request.sessionId);
|
|
45839
46283
|
}
|
|
45840
46284
|
}
|
|
45841
46285
|
yield chunk;
|
|
@@ -12,6 +12,22 @@
|
|
|
12
12
|
* degenerate stream scores near one. On a hit it stops yielding chunks, so the
|
|
13
13
|
* in-flight request ends, and lets the turn resume on a fresh step.
|
|
14
14
|
*
|
|
15
|
+
* Uniqueness alone only sees verbatim repetition. A stream can restate one
|
|
16
|
+
* conclusion over and over in fresh words, score 0.48 where the threshold is
|
|
17
|
+
* 0.85, and run to the output ceiling untouched. So a second, independent
|
|
18
|
+
* signal scores the same window for recurrence: how strongly each sentence
|
|
19
|
+
* repeats something the model already said at least `semanticLag` sentences
|
|
20
|
+
* earlier. Enumerative reasoning repeats itself too, but adjacently — each item
|
|
21
|
+
* borrows the previous item's frame — and recurrence ignores adjacency, which
|
|
22
|
+
* is what separates the two.
|
|
23
|
+
*
|
|
24
|
+
* Neither signal stops anything on its own. A break needs both over their
|
|
25
|
+
* thresholds in the same window, and then `semanticConfirmations` consecutive
|
|
26
|
+
* such windows: one signal alone, or a single anomalous window, is logged and
|
|
27
|
+
* released. That gradient is the whole design. A guard that cuts a healthy
|
|
28
|
+
* answer is worse than one that lets a runaway finish, because the user sees a
|
|
29
|
+
* turn stop mid-sentence with no explanation, and the fix costs a whole turn.
|
|
30
|
+
*
|
|
15
31
|
* Seam choice: the guard wraps `llm/stream`, not `agent/assistant-stream`.
|
|
16
32
|
* `agent/assistant-stream` is emit-mode — it can observe but cannot stop a
|
|
17
33
|
* stream — and it does not exist before harness 0.1.5, which would make the
|
|
@@ -120,6 +136,91 @@ export interface GuardOptions {
|
|
|
120
136
|
cooldownMs?: number;
|
|
121
137
|
/** Model ids to watch; empty watches every model. */
|
|
122
138
|
includeModels?: string[];
|
|
139
|
+
/**
|
|
140
|
+
* Recurrence score at or above which the semantic signal is considered to
|
|
141
|
+
* hold (default 0.35).
|
|
142
|
+
*
|
|
143
|
+
* Measured on the trailing window: the three healthy fixtures peak at 0.19,
|
|
144
|
+
* 0.17 and 0.17 across every streaming prefix, while a paraphrased loop and
|
|
145
|
+
* the verbatim loop score 0.75 and 0.99. 0.35 sits near the geometric mean
|
|
146
|
+
* of that gap, roughly 1.8x above the worst healthy prefix.
|
|
147
|
+
*/
|
|
148
|
+
semanticThreshold?: number;
|
|
149
|
+
/**
|
|
150
|
+
* Sentences a claim must be separated from to count as recurrence
|
|
151
|
+
* (default 6).
|
|
152
|
+
*
|
|
153
|
+
* Zero would make healthy enumeration look degenerate: item N of a list
|
|
154
|
+
* legitimately shares most of its wording with item N-1. Requiring the
|
|
155
|
+
* earlier sentence to be at least this far back keeps the measurement on
|
|
156
|
+
* "the model said this before" rather than "the model is still on this point".
|
|
157
|
+
*/
|
|
158
|
+
semanticLag?: number;
|
|
159
|
+
/**
|
|
160
|
+
* Characters required before the semantic signal is scored (default 360).
|
|
161
|
+
*
|
|
162
|
+
* Deliberately its own floor, well below `minWindowChars`: a real trace
|
|
163
|
+
* emitted 16 steps of which only one crossed 1500 characters, so the
|
|
164
|
+
* literal signal saw a single step of a whole turn. The two floors are
|
|
165
|
+
* separate knobs because they answer different questions — the literal one
|
|
166
|
+
* is about how much text a duplicate needs to be visible in, the semantic
|
|
167
|
+
* one about how much text a recurrence needs to be measurable in.
|
|
168
|
+
*/
|
|
169
|
+
semanticMinChars?: number;
|
|
170
|
+
/** Sentences required before recurrence is scored (default 8). */
|
|
171
|
+
semanticMinSentences?: number;
|
|
172
|
+
/**
|
|
173
|
+
* Consecutive windows in which both signals must hold before a break
|
|
174
|
+
* (default 3).
|
|
175
|
+
*
|
|
176
|
+
* Counted independently of the per-turn break budget, which bounds how often
|
|
177
|
+
* the guard may act, not how sure it must be. A stream delivers a scoring
|
|
178
|
+
* window every few dozen characters, so three confirmations cost a few
|
|
179
|
+
* hundred wasted tokens against a runaway measured in the tens of thousands.
|
|
180
|
+
*/
|
|
181
|
+
semanticConfirmations?: number;
|
|
182
|
+
/**
|
|
183
|
+
* Lowest literal score the low-literal path will act on (default 0.6).
|
|
184
|
+
*
|
|
185
|
+
* A paraphrased loop sits far below `threshold` — measured at 0.676 for a
|
|
186
|
+
* model restating twelve conclusions in fresh words, against 0.85 for
|
|
187
|
+
* verbatim repetition. So a strong semantic signal has to be allowed to
|
|
188
|
+
* carry a decision the literal signal cannot make on its own.
|
|
189
|
+
*
|
|
190
|
+
* The floor exists because a low score is not the same as a high one: 0.6 is
|
|
191
|
+
* evidence that something is repeating, 0.2 is a short trace that has not
|
|
192
|
+
* repeated long enough to say anything. Measured healthy literal peaks at
|
|
193
|
+
* 0.276, so 0.6 keeps a 2.2x margin over every healthy prefix.
|
|
194
|
+
*/
|
|
195
|
+
semanticLiteralFloor?: number;
|
|
196
|
+
/**
|
|
197
|
+
* Semantic score the low-literal path demands (default 0.45).
|
|
198
|
+
*
|
|
199
|
+
* Strictly greater than `semanticThreshold`, and validated as such. The literal
|
|
200
|
+
* evidence on this path is weaker by construction, so the semantic evidence
|
|
201
|
+
* has to be stronger: 0.45 against a measured healthy peak of 0.194 is a
|
|
202
|
+
* 2.3x margin, while a paraphrase loop measures 0.77.
|
|
203
|
+
*/
|
|
204
|
+
semanticStrictThreshold?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Most of a window's sentences may be distinct and still be eligible for the
|
|
207
|
+
* low-literal path (default 0.47).
|
|
208
|
+
*
|
|
209
|
+
* This is the dimension that separates a model circling from a model
|
|
210
|
+
* listing. Both reuse a frame heavily and both score high on recurrence, but
|
|
211
|
+
* a list writes a sentence that has never been written before on every line
|
|
212
|
+
* — measured at 1.000 distinct, against 0.42 for a paraphrase loop and 0.075
|
|
213
|
+
* for a verbatim one. Recurrence says the wording comes back; this says
|
|
214
|
+
* whether anything at all was new.
|
|
215
|
+
*/
|
|
216
|
+
semanticDistinctCeiling?: number;
|
|
217
|
+
/**
|
|
218
|
+
* Consecutive windows the low-literal path needs (default 5).
|
|
219
|
+
*
|
|
220
|
+
* Strictly greater than `semanticConfirmations`, and validated as such. The path is
|
|
221
|
+
* acting on weaker evidence, so it also waits longer before it acts.
|
|
222
|
+
*/
|
|
223
|
+
semanticStrictConfirmations?: number;
|
|
123
224
|
/** Injectable clock, so the cooldown is testable without a timer. */
|
|
124
225
|
now?: () => number;
|
|
125
226
|
}
|
|
@@ -132,6 +233,15 @@ export interface ResolvedGuardOptions {
|
|
|
132
233
|
maxBreaksPerTurn: number;
|
|
133
234
|
cooldownMs: number;
|
|
134
235
|
includeModels: string[];
|
|
236
|
+
semanticThreshold: number;
|
|
237
|
+
semanticLag: number;
|
|
238
|
+
semanticMinChars: number;
|
|
239
|
+
semanticMinSentences: number;
|
|
240
|
+
semanticConfirmations: number;
|
|
241
|
+
semanticLiteralFloor: number;
|
|
242
|
+
semanticStrictThreshold: number;
|
|
243
|
+
semanticDistinctCeiling: number;
|
|
244
|
+
semanticStrictConfirmations: number;
|
|
135
245
|
now: () => number;
|
|
136
246
|
}
|
|
137
247
|
export declare const DEFAULT_GUARD_OPTIONS: ResolvedGuardOptions;
|
|
@@ -151,6 +261,62 @@ export declare function resolveGuardOptions(options?: GuardOptions): ResolvedGua
|
|
|
151
261
|
* @returns collapse score in [0,1]; 0 means every n-gram is unique.
|
|
152
262
|
*/
|
|
153
263
|
export declare function collapseScore(window: string, n: number): number;
|
|
264
|
+
/**
|
|
265
|
+
* Split reasoning text into sentences, for mixed CJK and Latin streams.
|
|
266
|
+
*
|
|
267
|
+
* A Chinese full stop ends a sentence whatever follows it. An ASCII `.!?` only
|
|
268
|
+
* ends one when whitespace or the end of the text follows, because the same
|
|
269
|
+
* characters carry out decimals, file names and version numbers
|
|
270
|
+
* (`selectors.test.ts:745`, `3.14`) that must not become sentence
|
|
271
|
+
* edges. Whitespace runs collapse and case folds, so a difference in spacing
|
|
272
|
+
* or capitalisation cannot read as a difference in wording.
|
|
273
|
+
*
|
|
274
|
+
* @param text - reasoning text, typically one trailing window.
|
|
275
|
+
* @returns non-empty normalized sentences, in order.
|
|
276
|
+
*/
|
|
277
|
+
export declare function splitSentences(text: string): string[];
|
|
278
|
+
/**
|
|
279
|
+
* How strongly a window keeps returning to sentences it already stated.
|
|
280
|
+
*
|
|
281
|
+
* Each sentence is compared against every sentence at least `lag` positions earlier
|
|
282
|
+
* and keeps the best match; the score is the mean of those best matches. The
|
|
283
|
+
* comparison is normalized (Jaccard), so two sentences match on shared wording
|
|
284
|
+
* rather than on shared length, and character n-grams are used rather than words
|
|
285
|
+
* because no tokenizer is available here, and none would survive a CJK/Latin
|
|
286
|
+
* mix without segmenting Chinese first.
|
|
287
|
+
*
|
|
288
|
+
* Two properties matter more than the exact number. The score saturates — a
|
|
289
|
+
* twelve-sentence loop and a four-hundred-sentence one both approach one — so
|
|
290
|
+
* it does not drift with length the way a mean pairwise similarity does, and
|
|
291
|
+
* it does not reward a long answer merely for containing more pairs. And the
|
|
292
|
+
* lag is what makes it safe to ship: healthy enumeration reuses its framing
|
|
293
|
+
* from the sentence immediately before, which this metric cannot see.
|
|
294
|
+
*
|
|
295
|
+
* @param window - trailing reasoning characters.
|
|
296
|
+
* @param lag - sentences a claim must be separated from to count as recurrence.
|
|
297
|
+
* @returns recurrence score in [0,1]; 0 means nothing recurs, or too little text to tell.
|
|
298
|
+
*/
|
|
299
|
+
export declare function semanticRecurrence(window: string, lag: number): number;
|
|
300
|
+
/**
|
|
301
|
+
* The share of a window's sentences that have never been said before.
|
|
302
|
+
*
|
|
303
|
+
* Recurrence cannot tell a model that is circling from a model that is
|
|
304
|
+
* listing, because a list reuses its frame as heavily as a loop does and
|
|
305
|
+
* scores just as high. The difference is that a list writes a new sentence on
|
|
306
|
+
* every line. Measured over a trailing window: 0.075 for the verbatim loop,
|
|
307
|
+
* 0.42 for a paraphrase loop, 1.000 for every healthy fixture and for every
|
|
308
|
+
* list shape tried.
|
|
309
|
+
*
|
|
310
|
+
* Equality is exact and on the normalized sentence, which is what keeps the
|
|
311
|
+
* lists out. Two rows differing only by a row number are 0.86 similar by
|
|
312
|
+
* `semanticRecurrence` and 1.000 distinct here, and that gap is the whole point: the
|
|
313
|
+
* fuzzy measure asks whether the wording came back, this one asks whether
|
|
314
|
+
* anything was new.
|
|
315
|
+
*
|
|
316
|
+
* @param window - trailing reasoning characters.
|
|
317
|
+
* @returns distinct share in (0,1]; 1 when every sentence is unique.
|
|
318
|
+
*/
|
|
319
|
+
export declare function distinctSentenceRatio(window: string): number;
|
|
154
320
|
export declare const RESUME_HINT: string;
|
|
155
321
|
/** The second resume message, used once a resume has also collapsed. */
|
|
156
322
|
export declare const RESUME_HINT_STRICT: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/host/reasoning-collapse-guard/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/host/reasoning-collapse-guard/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import type { WorkBuddyAccount, WorkBuddyAccountQuota, WorkBuddyMeter } from '..
|
|
|
2
2
|
import { PROVIDER_ID, PROVIDER_NAME } from './types.ts';
|
|
3
3
|
import { FileCredentialStore, type WorkBuddyCredentials } from './token-store.ts';
|
|
4
4
|
import type { WorkBuddyTokenIdentity } from './identity.ts';
|
|
5
|
-
import type
|
|
5
|
+
import { type WorkBuddyModelEntry } from './model-catalog.ts';
|
|
6
6
|
export interface WorkBuddyRequestOptions {
|
|
7
7
|
fetchFn?: typeof fetch;
|
|
8
8
|
signal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACf,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAiBL,WAAW,EACX,aAAa,EAKd,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,mBAAmB,EAAsB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACrG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,9 +11,21 @@
|
|
|
11
11
|
* The fallback is a verbatim transcription of a live `/v3/config` read, not
|
|
12
12
|
* hand-written guesses: an earlier revision of this file guessed context
|
|
13
13
|
* windows from vendor marketing and was wrong in both directions (`glm-5.3`
|
|
14
|
-
* and `kimi-k3` are 1M, not 200K/256K).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* and `kimi-k3` are 1M, not 200K/256K).
|
|
15
|
+
*
|
|
16
|
+
* One entry serves both regions, so a model the regions disagree about needs a
|
|
17
|
+
* merged figure. The rule is asymmetric on purpose: the **larger maximum** is
|
|
18
|
+
* kept, while the **served default** and the **output cap** take the **smaller**
|
|
19
|
+
* of the two. Over-declaring either one is a hard failure — DSH would send a
|
|
20
|
+
* request the gateway rejects — whereas under-declaring only compresses a
|
|
21
|
+
* conversation earlier than needed, and both remain overridable per model from
|
|
22
|
+
* the settings card. Measured disagreements (`glm-5.3-flash`, `glm-5.3`,
|
|
23
|
+
* `glm-5.2`, `kimi-k2.8-preview` served 300000/48000/48000/32000 by intl
|
|
24
|
+
* against 1000000/64000/64000/64000 by cn) are reconciled that way.
|
|
25
|
+
*
|
|
26
|
+
* The regions also do not serve the same ids. intl added `glm-5.3-flash` and
|
|
27
|
+
* keeps `gemini-3.5-flash`, `kimi-k3` and the `*-model` aliases to itself; cn
|
|
28
|
+
* keeps `glm-5.3-flash`, `kimi-k3-1` and its own `-x` variants.
|
|
17
29
|
*
|
|
18
30
|
* `regions` is a routing fact, not a preference: asking a region for a model it
|
|
19
31
|
* does not serve answers 400 `code 11102`.
|
|
@@ -67,6 +79,38 @@ export interface WorkBuddyModelEntry {
|
|
|
67
79
|
/** One-line note shown beside the model in the settings card. */
|
|
68
80
|
description: string;
|
|
69
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Models the gateway serves but does not publish in `/v3/config`.
|
|
84
|
+
*
|
|
85
|
+
* The catalog is a published list, not the served one: measured on the
|
|
86
|
+
* international backend, `gpt-6-sol`, `gpt-6-luna` and `gemini-3.8-flash` all
|
|
87
|
+
* answer a normal streaming completion with 200 while being absent from
|
|
88
|
+
* `/v3/config` entirely, and a reachable gateway *replaces* the shipped table
|
|
89
|
+
* with that list. Without these rows the picker would simply never show them, so
|
|
90
|
+
* they are declared here and merged into a live catalog too
|
|
91
|
+
* ({@link withUnpublishedModels}) rather than living in the fallback alone.
|
|
92
|
+
*
|
|
93
|
+
* What each row records, and how it was established, differs by field:
|
|
94
|
+
*
|
|
95
|
+
* - **Existence, the reasoning ladder, image support** were measured against the
|
|
96
|
+
* live endpoint, not inferred from the name. A rejected level answers 400
|
|
97
|
+
* `code 11133` with `extError` `400002`; `gpt-6-sol` takes exactly the ladder
|
|
98
|
+
* `gpt-6-astra` publishes (`minimal` refused, `low`/`medium`/`high`/`xhigh`/
|
|
99
|
+
* `max` accepted) and both accept a 1x1 PNG.
|
|
100
|
+
* - **Context window, output cap and `canDisableThinking` are inherited from the
|
|
101
|
+
* published sibling in the same family** — `gpt-6-astra` for `gpt-6-sol` and
|
|
102
|
+
* `gpt-6-luna`, `gemini-3.5-flash` for `gemini-3.8-flash`. They are marked
|
|
103
|
+
* here rather than measured because the gateway publishes nothing for these
|
|
104
|
+
* ids and offers no cheap way to read the real figure: it accepts a
|
|
105
|
+
* `max_tokens` larger than any published window (even for `gpt-6-astra`, whose
|
|
106
|
+
* own catalog caps at 128000), so the number is neither enforced nor
|
|
107
|
+
* discoverable from the request side.
|
|
108
|
+
*
|
|
109
|
+
* Every field stays overridable per model from the settings card, and an
|
|
110
|
+
* inherited window errs low rather than high: over-declaring overflows the
|
|
111
|
+
* gateway mid-session, while under-declaring only compresses earlier.
|
|
112
|
+
*/
|
|
113
|
+
export declare const UNPUBLISHED_MODELS: readonly WorkBuddyModelEntry[];
|
|
70
114
|
/**
|
|
71
115
|
* Offline fallback catalog, transcribed from a live `/v3/config` read.
|
|
72
116
|
*
|
|
@@ -85,6 +129,17 @@ export declare const FALLBACK_MODELS: readonly WorkBuddyModelEntry[];
|
|
|
85
129
|
export declare const DEFAULT_VISIBLE_MODEL_IDS: readonly string[];
|
|
86
130
|
/** Every id the fallback knows, in declaration order. */
|
|
87
131
|
export declare const WORKBUDDY_MODEL_IDS: readonly string[];
|
|
132
|
+
/**
|
|
133
|
+
* Add the served-but-unpublished models to a live `/v3/config` catalog.
|
|
134
|
+
*
|
|
135
|
+
* A reachable gateway replaces the shipped table with the list it publishes, so
|
|
136
|
+
* a model it serves without listing ({@link UNPUBLISHED_MODELS}) would drop out
|
|
137
|
+
* of the picker the moment the catalog loads. Merging here keeps it offered.
|
|
138
|
+
*
|
|
139
|
+
* A published entry always wins: the gateway is the authority on anything it
|
|
140
|
+
* does describe, so these rows only fill ids the payload is silent about.
|
|
141
|
+
*/
|
|
142
|
+
export declare function withUnpublishedModels(catalog: readonly WorkBuddyModelEntry[], region: WorkBuddyRegion): WorkBuddyModelEntry[];
|
|
88
143
|
/**
|
|
89
144
|
* Resolve one entry from a catalog.
|
|
90
145
|
*
|
|
@@ -95,7 +150,25 @@ export declare const WORKBUDDY_MODEL_IDS: readonly string[];
|
|
|
95
150
|
export declare function resolveWorkBuddyModel(id: string, catalog?: readonly WorkBuddyModelEntry[]): WorkBuddyModelEntry;
|
|
96
151
|
/** Whether one catalog knows this exact id. */
|
|
97
152
|
export declare function isWorkBuddyModelId(id: unknown, catalog?: readonly WorkBuddyModelEntry[]): id is string;
|
|
98
|
-
/**
|
|
153
|
+
/** The vendor family one model belongs to. */
|
|
154
|
+
export declare function workBuddyModelVendor(id: string): string;
|
|
155
|
+
/**
|
|
156
|
+
* Group a list so every vendor's models sit together.
|
|
157
|
+
*
|
|
158
|
+
* Families appear in the order their first model does — the catalog decides
|
|
159
|
+
* which vendor leads, not this function — and each family is ordered newest
|
|
160
|
+
* generation first.
|
|
161
|
+
*/
|
|
162
|
+
export declare function groupWorkBuddyModelsByVendor<T extends {
|
|
163
|
+
id: string;
|
|
164
|
+
}>(models: readonly T[]): T[];
|
|
165
|
+
/**
|
|
166
|
+
* Models an account in `region` can actually call, grouped by vendor.
|
|
167
|
+
*
|
|
168
|
+
* Grouped here rather than in the settings card because this is also what the
|
|
169
|
+
* request path resolves against: one ordering rule, so the picker and the
|
|
170
|
+
* adapter can never disagree about which models a region offers.
|
|
171
|
+
*/
|
|
99
172
|
export declare function modelsForRegion(region: WorkBuddyRegion, catalog?: readonly WorkBuddyModelEntry[]): WorkBuddyModelEntry[];
|
|
100
173
|
/** Whether one model accepts image input. */
|
|
101
174
|
export declare function workBuddyModelSupportsImage(id: string, catalog?: readonly WorkBuddyModelEntry[]): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/model-catalog.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/model-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAA;IACxB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,OAAO,EAAE,SAAS,eAAe,EAAE,CAAA;IACnC,6CAA6C;IAC7C,aAAa,EAAE,OAAO,CAAA;IACtB,2EAA2E;IAC3E,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,2DAA2D;IAC3D,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,gEAAgE;IAChE,kBAAkB,EAAE,OAAO,CAAA;IAC3B,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,mBAAmB,EAwC5D,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,mBAAmB,EAwkBzD,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAKtD,CAAA;AAED,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAA6C,CAAA;AAE9F;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,mBAAmB,EAAE,EACvC,MAAM,EAAE,eAAe,GACtB,mBAAmB,EAAE,CAMvB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,mBAAmB,CA0BrB;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,OAAO,EACX,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,EAAE,IAAI,MAAM,CAEd;AAoCD,8CAA8C;AAC9C,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMvD;AAwCD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAWhG;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,mBAAmB,EAAE,CAEvB;AAED,6CAA6C;AAC7C,wBAAgB,2BAA2B,CACzC,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,OAAO,CAET;AAED,0CAA0C;AAC1C,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,SAAS,MAAM,EAAE,CAEnB;AAED,sDAAsD;AACtD,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,MAAM,CAER;AAED,wDAAwD;AACxD,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,SAAS,mBAAmB,EAAoB,GACxD,MAAM,CAER"}
|
|
@@ -15,7 +15,13 @@ import type { WorkBuddyAccountPool } from './account-pool.ts';
|
|
|
15
15
|
* behind an unedited default. Any explicit edit is honoured exactly.
|
|
16
16
|
*/
|
|
17
17
|
export declare function resolveEnabledModelIds(stored: readonly string[], available: readonly string[], enabled?: boolean): string[];
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Build the model options the settings card renders.
|
|
20
|
+
*
|
|
21
|
+
* Order comes from the caller, which passes a vendor-grouped list so a family
|
|
22
|
+
* is not interleaved with the next one; this preserves whatever order it is
|
|
23
|
+
* given rather than imposing a second one.
|
|
24
|
+
*/
|
|
19
25
|
export declare function buildModelOptions(catalog: readonly WorkBuddyModelEntry[], available: readonly string[], enabledModelIds: readonly string[], overrides: Record<string, number>): WorkBuddyModelOption[];
|
|
20
26
|
export interface WorkBuddyStatusOptions {
|
|
21
27
|
fetchFn?: typeof fetch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,OAAO,EAGL,sBAAsB,EACtB,WAAW,EACX,aAAa,EAEd,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EAGtB,KAAK,wBAAwB,EAC9B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAKL,cAAc,EAKf,MAAM,aAAa,CAAA;AACpB,OAAO,EASL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAIV,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAG3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AA6C7D;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,UAAO,GACb,MAAM,EAAE,CASV;AAED
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/host/workbuddy/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAIlD,OAAO,EAGL,sBAAsB,EACtB,WAAW,EACX,aAAa,EAEd,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EAGtB,KAAK,wBAAwB,EAC9B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAKL,cAAc,EAKf,MAAM,aAAa,CAAA;AACpB,OAAO,EASL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAIV,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAG3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AA6C7D;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,OAAO,UAAO,GACb,MAAM,EAAE,CASV;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,mBAAmB,EAAE,EACvC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,oBAAoB,EAAE,CAqBxB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;IACtB,sFAAsF;IACtF,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,CAAA;IACnC,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAA;IAChD,4FAA4F;IAC5F,WAAW,CAAC,EAAE,oBAAoB,CAAA;IAClC,6EAA6E;IAC7E,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CAC5D;AAMD,mFAAmF;AACnF,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,mBAAmB,EAC1B,aAAa,EAAE,sBAAsB,EACrC,WAAW,CAAC,EAAE,wBAAwB,EACtC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,kBAAkB,CAAC,CA2E7B;AAED,qEAAqE;AACrE,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,mBAAmB,EAC1B,aAAa,EAAE,sBAAsB,EACrC,WAAW,CAAC,EAAE,wBAAwB,EACtC,OAAO,GAAE,sBAA2B,GACnC,MAAM,IAAI,CAkXZ;AAED,OAAO,EACL,WAAW,EACX,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,GACxB,CAAA"}
|