dsh-connect-trae 4.17.0 → 4.18.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 +93 -0
- package/README.en.md +1 -0
- package/README.md +1 -0
- package/lib/index.d.ts +24 -5
- package/lib/index.js +78 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.18.0 (2026-11-09)
|
|
4
|
+
|
|
5
|
+
### 企业面板的发现层收录租户自配模型(`config_source: 2`)
|
|
6
|
+
|
|
7
|
+
企业租户可以在 Trae 企业控制台配置自己的模型。Trae 自己的选择器能看到它们,插件的企业
|
|
8
|
+
面板一直不列——这批行由企业侧 `get_detail_param` 返回(`config_source: 2` +
|
|
9
|
+
`usage: 'chat_completion'`),**不在**企业侧 Remote 目录里,所以只有「wire 提升」一条路
|
|
10
|
+
能看见它们;而提升门槛只接受 `config_source === 1`,又拒绝「`display_name` 等于
|
|
11
|
+
`config_name`」的行——那恰好是租户配置模型的常见形态。两条门槛各自都足以挡掉它们。
|
|
12
|
+
|
|
13
|
+
**改法。** 提升门槛接受 `2`,且这类行允许用 `config_name` 当显示名。提升改为 **opt-in**
|
|
14
|
+
(`mergeTraeModelSources(..., { allowTenantConfigured })`),**只有企业面板传 `true`**:
|
|
15
|
+
这些行计费落在租户池,放进消费面板等于让用户选到一个不属于自己计费面的模型。另外两条
|
|
16
|
+
区域本来也取不到它们——它们的 wire 客户端钉在消费网关,那批行只出现在企业网关的名册里。
|
|
17
|
+
|
|
18
|
+
**这批模型现在调用不通,这是刻意不做的部分。** 租户配置的模型属于
|
|
19
|
+
`custom_openai_compatible`、客户端直连(`use_remote_service: false`、自带 base_url),
|
|
20
|
+
插件没有对应传输通道,实测走 `llm_utils_chat` 返回 `4001`。本次只修发现层,通道另开
|
|
21
|
+
issue 讨论——不做「静默假设能调」这种事。真要选中时,拿到的是桥接层翻译过的可读错误
|
|
22
|
+
(`4001` → 该 SOLO function 不提供此模型),不是一次空白回复。
|
|
23
|
+
|
|
24
|
+
### 企业版图片请求预算降到 8 MiB:网关只有 10 MiB,图文长任务不再 413
|
|
25
|
+
|
|
26
|
+
**症状。** 企业版跑带图的多步任务,跑到某一步整轮失败:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
400: {"message":"<html>...<title>413 Request Entity Too Large</title>...openresty/1.27.1.2...",
|
|
30
|
+
"type":"client","code":"client"}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
换模型、重试、少给几张图都没用——任务只要「长到某个长度」就必然挂。实测一次真实会话:
|
|
34
|
+
15 张图、base64 合计 **12.32 MB**,在第 20 步被拒。
|
|
35
|
+
|
|
36
|
+
**根因。** `maxRequestImageBytes` 不是口味设置,它决定宿主要不要走「卸掉最老的图、重发」
|
|
37
|
+
那条路:DSH 拿它和请求里**所有图片的 base64 总量**比较,超了就抛 `IMAGE_OFFLOAD_REQUIRED`,
|
|
38
|
+
由 `dsh-compaction-image-offload` 写占位符重试。**这条链路只有在预算低于网关实测上限时才成立**;
|
|
39
|
+
预算高于上限,宿主认为「装得下」,请求照发,被网关拍死在传输层——没有模型回复,无从恢复。
|
|
40
|
+
|
|
41
|
+
而 `REQUEST_IMAGE_BUDGETS` 把**所有区域**都钉在宿主默认的 20 MiB,注释理由是「对齐宿主默认值」。
|
|
42
|
+
那个理由对计数字段(`maxRequestImages` / `imageOffloadCountQuantum`,确是宿主契约)成立,
|
|
43
|
+
对**字节上限**不成立:字节上限要对齐的是**网关**,而三个网关差 2.5 倍。
|
|
44
|
+
|
|
45
|
+
**实测(2026-11-09,未鉴权探针)。** openresty 在**鉴权之前**就按 body 体积拒绝,所以状态码本身就是
|
|
46
|
+
上限,且不消耗任何凭据或积分:
|
|
47
|
+
|
|
48
|
+
| 网关 | 10,485,758 字节 | 10,485,762 字节 | 结论 |
|
|
49
|
+
| --- | --- | --- | --- |
|
|
50
|
+
| `console.enterprise.trae.cn`(`create_agent_task`) | `401` | **`413`** | 上限 10 MiB |
|
|
51
|
+
| `console.enterprise.trae.cn`(`llm_utils_chat`) | `401` | **`413`** | 上限 10 MiB |
|
|
52
|
+
| `trae-api-cn.mchost.guru` | `401`(测到 48 MiB) | — | 未触顶 |
|
|
53
|
+
| `coresg-normal.trae.ai` | `401`(测到 20 MiB) | — | 未触顶 |
|
|
54
|
+
|
|
55
|
+
**修复。** 只有企业版降到 **8 MiB**,另外两条区域保持 20 MiB:
|
|
56
|
+
|
|
57
|
+
| 区域 | 改前 | 改后 |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `trae-enterprise` | 20 MiB | **8 MiB** |
|
|
60
|
+
| `trae`(CN) | 20 MiB | 20 MiB |
|
|
61
|
+
| `trae-global`(国际) | 20 MiB | 20 MiB |
|
|
62
|
+
|
|
63
|
+
**为什么是 8 而不是 10。** 预算只管图片,请求还要带系统提示、工具声明和消息文本。8 MiB 留 2 MiB
|
|
64
|
+
信封,而实测请求远没到这个量级(工具声明约 49 KB)。**为什么不干脆把三条都降下来**:CN 与国际
|
|
65
|
+
网关实测 48 MiB / 20 MiB 都收,降它们只会白白卸掉用户本来能看到的图——那是静默的质量回退,
|
|
66
|
+
不是修复。
|
|
67
|
+
|
|
68
|
+
用那次失败会话的同一批图片跑宿主真实的卸载算法(`imageOffloadCountQuantum: 10`,超额按 10 的
|
|
69
|
+
整数倍卸):
|
|
70
|
+
|
|
71
|
+
| | 改前(20 MiB) | 改后(8 MiB) |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| 卸载是否触发 | 从不 | 第 13 张图时 |
|
|
74
|
+
| 卸载数量 | 0 / 15 | 5 / 15 |
|
|
75
|
+
| 实际发出 | ≈11.92 MiB → **413** | ≈7.99 MiB → 通过 |
|
|
76
|
+
|
|
77
|
+
**没选的做法。** ① 改宿主默认值——那是所有 provider 共享的契约,不该为一个网关让步。② 直接把
|
|
78
|
+
`imageOffloadCountQuantum` 调小——卸载粒度变细会增加重试轮次,而这次的问题是预算根本没触发
|
|
79
|
+
卸载,与粒度无关。③ 只发占位符不重试——用户会永久失去图片内容,而重试路径本来就是为此设计的。
|
|
80
|
+
|
|
81
|
+
### Tests
|
|
82
|
+
|
|
83
|
+
- 新增 `tests/adapter-image-budget.spec.ts`(3 例):企业版预算**严格小于**实测 10 MiB 上限;
|
|
84
|
+
CN 与国际**仍是**宿主默认 20 MiB;三条区域的计数上限都保持 50 / 10。
|
|
85
|
+
第一例在修复前失败(`expected 20971520 to be less than 10485760`),修复后通过。
|
|
86
|
+
另外两例钉住「不要把三条一起降下来」这条回退路径。
|
|
87
|
+
- **合并时补了一条下界**(≥ 5 MiB,即实测上限的一半):「小于 10 MiB」是个**任何更小的数**
|
|
88
|
+
都满足的断言——改成 1 MiB 也照样绿,而那会把网关收得下的图白白卸掉,正是另外两条区域
|
|
89
|
+
被保护着不许犯的那个错,只是从保守那一侧犯。实测把常量改成 `1_048_576` 会红
|
|
90
|
+
(`expected 1048576 to be greater than or equal to 5242880`)。
|
|
91
|
+
- `tests/catalog.spec.ts` 新增 4 例(租户自配模型):被提升(含 `config_name` 当显示名的形态);
|
|
92
|
+
未 opt-in 时一行都不出(缺省与显式 `false` 两路);`usage` 非 `chat_completion` 仍被拒;
|
|
93
|
+
空名与 `-` 仍被拒。变异验证:删掉 `allowTenantConfigured` 门控 → 第二例立刻变红。
|
|
94
|
+
- 全仓 **114 个文件 / 1469 条测试**通过。
|
|
95
|
+
|
|
3
96
|
## 4.17.0 (2026-11-09)
|
|
4
97
|
|
|
5
98
|
### 自动刷新改为可选项:整个插件一个节奏,默认 5 分钟
|
package/README.en.md
CHANGED
|
@@ -44,6 +44,7 @@ In the account pool, open **More settings** and choose **Claude style** or **Def
|
|
|
44
44
|
- **Three parallel providers** — the CN side registers as the `trae` provider (e.g. `DeepSeek-V4-Flash`, `DeepSeek-V4-Pro`), the enterprise side as `trae-enterprise`, and the international side as `trae-global` (the Gemini/GPT/MiniMax roster); **all three rosters appear in the DSH model picker simultaneously**: different sessions can each pick a side without interfering.
|
|
45
45
|
- **Enterprise is a separate billing channel, not an alias for CN** — the same CN credential, when it belongs to a `tob_online` tenant, is served by **two** gateways: the consumer pool (billed against personal credits) and the tenant pool (billed against the enterprise allowance). Same model, two payers, so **you** choose who pays; the plugin never guesses. `trae` never touches the enterprise gateway, and `trae-enterprise` **never falls back** — a refusal is reported as a refusal instead of quietly re-running on personal credits. Enterprise is **off by default**: a personal-only account cannot use the tenant pool, so shipping it enabled would mean handing everyone a provider that fails on first use.
|
|
46
46
|
- **Any panel can be switched off on its own** — each tab carries its own checkbox (CN and international checked by default, enterprise not): unchecking **withdraws that provider from the DSH model picker entirely** (not merely hiding the tab) and stops all of its requests; checking it again restores it with the account, directory, picks, and context budgets intact. If you never use the international side, just uncheck it.
|
|
47
|
+
- **Enterprise models configured by the tenant now show up** — a tenant can configure models of its own in the Trae enterprise console. Those rows (`config_source: 2`) are absent from the enterprise-side Remote directory, so the plugin used to hide them entirely; they now appear on the enterprise panel and in the DSH picker. **They are not callable yet**: those models are client-direct (`use_remote_service: false`, carrying their own `base_url`) and the plugin has no transport for that channel, so selecting one fails honestly (`4001` → this SOLO function does not serve it) instead of failing silently or pretending to answer. They bill the tenant pool, which is why they appear **only on the enterprise panel**; CN and international are untouched — those two read consumer-gateway rosters to begin with.
|
|
47
48
|
- **Tabbed settings card** — the card's top carries a "Domestic / Enterprise / Global" tab bar; each tab holds its own account picker, usage overview, and model management. Account, directory, selection, and unsaved drafts are fully isolated per tab — switching accounts or refreshing models on one tab never touches the other side's runtime catalog or sessions. CN and enterprise share one CN credential pool but keep **separate account bindings and model picks**: the two rosters differ (`DeepSeek-V4.1-Flash` is listed only on the enterprise pool), so picks never overwrite each other.
|
|
48
49
|
- **The card then splits into "Account / Models / Diagnostics"** (learned from the sibling `dsh-connect-workbuddy`). The region row above is the **scope** — which provider this whole card is about — while these are sections of that one card: Account covers who serves, what is left, the check-in and the pool; Models covers which models to tick, image input, the context budget and Save; Diagnostics read-only lists the panel, the provider id, the serving account, both token deadlines, the catalog/enabled model counts, whether the pool and the check-in are offered, and the **verbatim** text of this panel's last reported failure. **As of 4.4.7 the diagnostics section carries one more line, "quarantined copies"**: a credential copy that could not be read — and was therefore renamed aside instead of being erased — is named there, together with how many accounts it still holds. "Where did the account I remember go?" is now answered by the card instead of by browsing `~/.dsh` for a file name; the signed-out screen shows the same notice, because that is the screen a user lands on after an account disappears. The split exists because the two halves **commit differently**: an account change lands on the click, a catalog change is a draft until Save — so a single long column put the save button three screens away from what it saves. Switching sections never refetches, never drops a half-filled model draft, and never rebuilds the pool's log. A signed-out card renders no section strip, so its one screen of instructions cannot be hidden behind a tab the user cannot see. **The Models section lays every model out at once, with no inner scrollbar** — it is not a little window scrolling inside a page that already scrolls; the card's own page is the only scroll.
|
|
49
50
|
- **Multiplier in the model name** — model names show the credit multiplier in Trae's own menu format (e.g. `GLM-5.2 · x0.79`), updated with each directory refresh.
|
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
- **文生图(`trae_draw_image`)** —— 把登录账号的 Trae 文生图能力接进会话:模型画一张图,插件把**图片字节一起取回来**、写进会话目录再显示给你。**默认关闭、按面板单独开**——生图真实消耗积分(实测 CN 个人版 `1920x1920` 一张约 8.8),所以它和识图那种「让不让看图」的开关不同,只能由你点头。协议、尺寸边界、取图那两步,以及为什么「取图」那条 GET 其实是又一次生成,见下方「[文生图](#文生图trae_draw_image)」。
|
|
57
57
|
- **国内国际双区域自动识别** —— 自动发现 Trae CN / TRAE SOLO CN / Trae / TRAE SOLO 四个本地安装的登录账号;区域由凭证自带的 `userRegion` 声明自动判定(host 后缀与 edition 标签兜底),无需手动指定。**美国区(`userRegion: "US"` / `*.traeapi.us`)目前「识别但不接入」**:插件不会把 US 凭据当成新加坡区发出去,而会在账号面板明确写「美国区(US)暂不支持」并点名账号,同时说明重新登录不会改变结果(issue #66)。
|
|
58
58
|
- **目录与勾选按面板隔离** —— 三个面板各一套模型目录、勾选、图片开关与上下文预算,各自读各自的槽位。名录从**该面板所钉的那条网关**读取——原本钉在企业池的客户端去读消费池名录,会永远发现不了自己池的独占模型。
|
|
59
|
+
- **企业面板会列出租户自配的模型** —— 企业租户可以在 Trae 企业控制台配置自己的模型,这类行(`config_source: 2`)不在企业侧的 Remote 目录里,过去在插件里完全看不见,现在会出现在企业面板与 DSH 选择器里。**它们目前还调不通**:这批模型是客户端直连(`use_remote_service: false`、自带 `base_url`),插件没有对应传输通道,选中后调用会如实报错(`4001` → 该 SOLO function 不提供此模型),不会静默失败或假装成功。计费属租户池,所以**只在企业面板出现**,国内版与国际版不受影响——那两个面板本来也只读消费网关的名录。
|
|
59
60
|
- **识图能力跟随 Trae 自身的标注** —— Trae 把某个模型标为 `multimodal` 时,插件直接按多模态对外声明,无需你在卡片上逐一手勾(issue #16)。判据是「**你手动勾选的 ∪ Trae 标注多模态的**」:Trae 明确标 `false` 或未标注的模型保持纯文本,你可以照旧手动为它们打开图片输入。卡片上被自动开启的那一行显示为已勾选且只读,并说明原因。详见下方「图片输入(识图)」。
|
|
60
61
|
- **多账号切换** —— 支持重新读取 Token 列表并选择账号;Token 不写入 DSH 设置。**你的选择会被记住并长期生效**:插件不会因为某个账号额度用尽、或检测到另一个可用账号就自作主张替你切换(换账号意味着换人计费)——唯一的例外是你**亲自打开**的账号池(见下一条),那是一个显式选择,而不是插件的自作主张。所以当绑定的账号额度耗尽时,卡片会**明确告诉你**,并把本机其他还有额度的账号连同其可用积分数列出来,由你一键切换——而不是让面板一直空着、也不自动把你切走。「刷新账号与 Token」每次都会回报结果(找到几个账号、当前仍绑定谁),不会再「点了没反应」。下拉里显示的是产品名(`Trae` / `TRAE SOLO` / 国际版),便于分辨凭据来自哪个安装。
|
|
61
62
|
- **账号导入 / 导出** —— 不用再为了用第二个账号而把凭据手工写进默认路径、顶掉上一个。在卡片账号区的「导入 / 导出账号」里:**导出**勾选若干账号下载一份 JSON;**导入**选择文件后先看**脱敏预览**(只显示账号标识与「是否有 Token」,不含凭据本身)再勾选导入。文件格式与同族 `dsh-connect-workbuddy` **互通**,两边导出的文件可以互相导入。导入按账号去重(同一账号再导入即覆盖更新),**认不出所属区域或因缺 Token 的记录会被跳过并计数**——国内版与国际版是两条并行计费通道,跨区导入会导致跨区计费,所以这里只认同能**正向识别**的区域(企业版共用 CN 凭据池,不构成第三条导入通道)。导出文件里是**有效 Token,请当作密码保管**。(issue #37)
|
package/lib/index.d.ts
CHANGED
|
@@ -462,14 +462,22 @@ interface TraeWireModel {
|
|
|
462
462
|
* account's custom models (`usage: 'custom_model'`); none of those belong
|
|
463
463
|
* in a model picker. Measured: this one test removed 23 of 50 wire-only
|
|
464
464
|
* rows, and every row it removed was a tool or a custom model.
|
|
465
|
-
* - `config_source === 1`
|
|
466
|
-
*
|
|
467
|
-
*
|
|
465
|
+
* - `config_source === 1`, or `2` once the caller opts in — `1` is a Trae
|
|
466
|
+
* preset; `2` is a model the tenant's own enterprise console configured
|
|
467
|
+
* (see `allowTenantConfigured` on {@link mergeTraeModelSources}, which is
|
|
468
|
+
* what decides whether a `2` may pass). `3` is the account's own custom
|
|
469
|
+
* model, carrying the user's own base_url and key: the plugin must not
|
|
470
|
+
* offer to bill a model it does not host, so `3` never passes.
|
|
468
471
|
* - a REAL display name — not empty, not the upstream placeholder `-`, not a
|
|
469
472
|
* bare config identifier (`search_agent_qwen_fast`,
|
|
470
473
|
* `computer_use_subagent`), and not the config's OWN id. A model with no
|
|
471
474
|
* name has nothing to show in the picker.
|
|
472
475
|
*
|
|
476
|
+
* A tenant-configured row (`2`) is EXEMPT from the "own id" clause: those
|
|
477
|
+
* rows commonly publish the config_name as the display name, so the clause
|
|
478
|
+
* would reject precisely the rows the opt-in exists to surface. The other
|
|
479
|
+
* name tests still apply to them.
|
|
480
|
+
*
|
|
473
481
|
* The display-name-collision test lives in the merge itself (it needs the
|
|
474
482
|
* Remote rows to compare against); see {@link mergeTraeModelSources}. It is a
|
|
475
483
|
* CAPABILITY test there, not a name test: a collision only suppresses a row
|
|
@@ -535,8 +543,19 @@ declare function isPromotableWireModel(model: TraeWireModel): boolean;
|
|
|
535
543
|
* PROMOTED (issue #46) — subject to {@link isPromotableWireModel}. The
|
|
536
544
|
* skeleton systematically omits the dated snapshot configs, so without
|
|
537
545
|
* promotion every snapshot the IDE offers is unreachable from the plugin.
|
|
538
|
-
|
|
539
|
-
|
|
546
|
+
*
|
|
547
|
+
* @param options.allowTenantConfigured - may a `config_source: 2` row (a model
|
|
548
|
+
* the tenant's enterprise console configured) be promoted? Opt-in, and only
|
|
549
|
+
* the enterprise panel opts in: those rows bill the tenant pool, so surfacing
|
|
550
|
+
* them on a consumer panel would offer a model outside the caller's billing
|
|
551
|
+
* face. The rows also are not callable through `llm_utils_chat` today — they
|
|
552
|
+
* are client-direct with their own base_url (`use_remote_service: false`) and
|
|
553
|
+
* the gateway answers `4001` — which the discovery layer does not hide, but
|
|
554
|
+
* which is why this is a narrow opt-in rather than a default.
|
|
555
|
+
*/
|
|
556
|
+
declare function mergeTraeModelSources(remote: readonly TraeDiscoveredModel[], wire: readonly TraeWireModel[], options?: {
|
|
557
|
+
allowTenantConfigured?: boolean;
|
|
558
|
+
}): TraeModelInfo[];
|
|
540
559
|
/**
|
|
541
560
|
* Demote a NON-chat `wireFunction` to "no function", keeping the row.
|
|
542
561
|
*
|
package/lib/index.js
CHANGED
|
@@ -371,14 +371,22 @@ function applyImageSelection(models, selected) {
|
|
|
371
371
|
* account's custom models (`usage: 'custom_model'`); none of those belong
|
|
372
372
|
* in a model picker. Measured: this one test removed 23 of 50 wire-only
|
|
373
373
|
* rows, and every row it removed was a tool or a custom model.
|
|
374
|
-
* - `config_source === 1`
|
|
375
|
-
*
|
|
376
|
-
*
|
|
374
|
+
* - `config_source === 1`, or `2` once the caller opts in — `1` is a Trae
|
|
375
|
+
* preset; `2` is a model the tenant's own enterprise console configured
|
|
376
|
+
* (see `allowTenantConfigured` on {@link mergeTraeModelSources}, which is
|
|
377
|
+
* what decides whether a `2` may pass). `3` is the account's own custom
|
|
378
|
+
* model, carrying the user's own base_url and key: the plugin must not
|
|
379
|
+
* offer to bill a model it does not host, so `3` never passes.
|
|
377
380
|
* - a REAL display name — not empty, not the upstream placeholder `-`, not a
|
|
378
381
|
* bare config identifier (`search_agent_qwen_fast`,
|
|
379
382
|
* `computer_use_subagent`), and not the config's OWN id. A model with no
|
|
380
383
|
* name has nothing to show in the picker.
|
|
381
384
|
*
|
|
385
|
+
* A tenant-configured row (`2`) is EXEMPT from the "own id" clause: those
|
|
386
|
+
* rows commonly publish the config_name as the display name, so the clause
|
|
387
|
+
* would reject precisely the rows the opt-in exists to surface. The other
|
|
388
|
+
* name tests still apply to them.
|
|
389
|
+
*
|
|
382
390
|
* The display-name-collision test lives in the merge itself (it needs the
|
|
383
391
|
* Remote rows to compare against); see {@link mergeTraeModelSources}. It is a
|
|
384
392
|
* CAPABILITY test there, not a name test: a collision only suppresses a row
|
|
@@ -391,9 +399,10 @@ function applyImageSelection(models, selected) {
|
|
|
391
399
|
*/
|
|
392
400
|
function isPromotableWireModel(model) {
|
|
393
401
|
if (model.usage !== "chat_completion") return false;
|
|
394
|
-
if (model.configSource !== 1) return false;
|
|
402
|
+
if (model.configSource !== 1 && model.configSource !== 2) return false;
|
|
395
403
|
const name = model.name.trim();
|
|
396
404
|
if (name === "" || name === "-") return false;
|
|
405
|
+
if (model.configSource === 2) return true;
|
|
397
406
|
if (name === model.id.trim()) return false;
|
|
398
407
|
if (!name.includes(" ") && name.includes("_")) return false;
|
|
399
408
|
return true;
|
|
@@ -503,8 +512,17 @@ function wireEffortsOf(supported) {
|
|
|
503
512
|
* PROMOTED (issue #46) — subject to {@link isPromotableWireModel}. The
|
|
504
513
|
* skeleton systematically omits the dated snapshot configs, so without
|
|
505
514
|
* promotion every snapshot the IDE offers is unreachable from the plugin.
|
|
515
|
+
*
|
|
516
|
+
* @param options.allowTenantConfigured - may a `config_source: 2` row (a model
|
|
517
|
+
* the tenant's enterprise console configured) be promoted? Opt-in, and only
|
|
518
|
+
* the enterprise panel opts in: those rows bill the tenant pool, so surfacing
|
|
519
|
+
* them on a consumer panel would offer a model outside the caller's billing
|
|
520
|
+
* face. The rows also are not callable through `llm_utils_chat` today — they
|
|
521
|
+
* are client-direct with their own base_url (`use_remote_service: false`) and
|
|
522
|
+
* the gateway answers `4001` — which the discovery layer does not hide, but
|
|
523
|
+
* which is why this is a narrow opt-in rather than a default.
|
|
506
524
|
*/
|
|
507
|
-
function mergeTraeModelSources(remote, wire) {
|
|
525
|
+
function mergeTraeModelSources(remote, wire, options = {}) {
|
|
508
526
|
const wireByName = /* @__PURE__ */ new Map();
|
|
509
527
|
const wireById = /* @__PURE__ */ new Map();
|
|
510
528
|
for (const model of wire) if (!wireById.has(displayKey(model.id))) wireById.set(displayKey(model.id), model);
|
|
@@ -574,6 +592,7 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
574
592
|
for (const model of remote) claim(model.name, model.contextWindow);
|
|
575
593
|
for (const wireModel of wire) {
|
|
576
594
|
if (remoteIds.has(displayKey(wireModel.id))) continue;
|
|
595
|
+
if (wireModel.configSource === 2 && options.allowTenantConfigured !== true) continue;
|
|
577
596
|
if (!isPromotableWireModel(wireModel)) continue;
|
|
578
597
|
const collides = claimedNames.has(displayKey(wireModel.name));
|
|
579
598
|
if (collides && (wireModel.contextWindow ?? 0) <= (claimedNames.get(displayKey(wireModel.name)) ?? 0)) continue;
|
|
@@ -1504,6 +1523,10 @@ const INERT_AUTH = {
|
|
|
1504
1523
|
*
|
|
1505
1524
|
* `imageOffloadCountQuantum` must be a positive integer no greater than
|
|
1506
1525
|
* `maxRequestImages`; 10 ≤ 50 keeps the host's default ratio.
|
|
1526
|
+
*
|
|
1527
|
+
* The BYTE bound is the one field here that is not a host contract: it has to
|
|
1528
|
+
* match the gateway at the other end, and the gateways disagree (issue #81).
|
|
1529
|
+
* See {@link ENTERPRISE_MAX_REQUEST_IMAGE_BYTES}.
|
|
1507
1530
|
*/
|
|
1508
1531
|
const REQUEST_IMAGE_BUDGETS = {
|
|
1509
1532
|
maxRequestImageBytes: 20971520,
|
|
@@ -1512,6 +1535,54 @@ const REQUEST_IMAGE_BUDGETS = {
|
|
|
1512
1535
|
maxRequestImages: 50,
|
|
1513
1536
|
imageOffloadCountQuantum: 10
|
|
1514
1537
|
};
|
|
1538
|
+
/**
|
|
1539
|
+
* Request-image byte bound for the enterprise route, in bytes (8 MiB).
|
|
1540
|
+
*
|
|
1541
|
+
* WHY THE HOST DEFAULT IS WRONG HERE. `maxRequestImageBytes` is not a taste
|
|
1542
|
+
* setting — DSH compares it against the base64 aggregate of every image a
|
|
1543
|
+
* request re-inlines, and raising `IMAGE_OFFLOAD_REQUIRED` is how the oldest
|
|
1544
|
+
* images get replaced by placeholders and the request retried. That only works
|
|
1545
|
+
* if the bound is BELOW what the gateway accepts: a bound above it means the
|
|
1546
|
+
* host believes an unsendable request fits and sends it anyway.
|
|
1547
|
+
*
|
|
1548
|
+
* Measured 2026-11-09 with unauthenticated probes (openresty rejects on body
|
|
1549
|
+
* size before authentication, so no credentials or credits are involved):
|
|
1550
|
+
*
|
|
1551
|
+
* console.enterprise.trae.cn, /api/agent/v3/create_agent_task AND
|
|
1552
|
+
* /api/agent/v3/llm_utils_chat:
|
|
1553
|
+
* 10_485_758 bytes -> 401 10_485_762 bytes -> 413
|
|
1554
|
+
* => the cap is exactly 10 MiB (10_485_760)
|
|
1555
|
+
*
|
|
1556
|
+
* The other two regions have no comparable cap: `trae-api-cn.mchost.guru`
|
|
1557
|
+
* answered 401 at 48 MiB and `coresg-normal.trae.ai` at 20 MiB, so they keep
|
|
1558
|
+
* the host default — lowering them would offload images those gateways accept.
|
|
1559
|
+
*
|
|
1560
|
+
* WHY 8 MiB AND NOT 10. The budget bounds the IMAGES; the request also carries
|
|
1561
|
+
* the system prompt, tool declarations and message text. 8 MiB leaves 2 MiB of
|
|
1562
|
+
* envelope, which the measured requests did not come close to (tool
|
|
1563
|
+
* declarations ~49 KB). The failure this fixes: a real 15-image session reached
|
|
1564
|
+
* 12.32 MB of base64 image payload, under the old 20 MiB budget and over the
|
|
1565
|
+
* 10 MiB cap, and died at step 20 with `413 Request Entity Too Large`.
|
|
1566
|
+
*/
|
|
1567
|
+
const ENTERPRISE_MAX_REQUEST_IMAGE_BYTES = 8388608;
|
|
1568
|
+
/**
|
|
1569
|
+
* Image-request budgets for one route.
|
|
1570
|
+
*
|
|
1571
|
+
* The byte bound is region-specific; every other field is shared. Kept as a
|
|
1572
|
+
* function rather than two constants so the shared fields cannot drift apart.
|
|
1573
|
+
*
|
|
1574
|
+
* The return type is the profile's own field types rather than
|
|
1575
|
+
* `typeof REQUEST_IMAGE_BUDGETS`: that constant is `as const`, so its
|
|
1576
|
+
* `maxRequestImageBytes` is the LITERAL `20_971_520` and the enterprise arm
|
|
1577
|
+
* would not typecheck against it.
|
|
1578
|
+
* @param providerId - the route whose gateway will receive the request.
|
|
1579
|
+
*/
|
|
1580
|
+
function requestImageBudgets(providerId) {
|
|
1581
|
+
return providerId === "trae-enterprise" ? {
|
|
1582
|
+
...REQUEST_IMAGE_BUDGETS,
|
|
1583
|
+
maxRequestImageBytes: ENTERPRISE_MAX_REQUEST_IMAGE_BYTES
|
|
1584
|
+
} : REQUEST_IMAGE_BUDGETS;
|
|
1585
|
+
}
|
|
1515
1586
|
function toPiModel(info, baseUrl, providerId, withRate = true) {
|
|
1516
1587
|
return {
|
|
1517
1588
|
id: info.id,
|
|
@@ -1885,7 +1956,7 @@ function createTraeAdapter(options) {
|
|
|
1885
1956
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
1886
1957
|
modelErrors: /* @__PURE__ */ new Map(),
|
|
1887
1958
|
defaultContextWindow: FALLBACK_CONTEXT_WINDOW,
|
|
1888
|
-
...
|
|
1959
|
+
...requestImageBudgets(providerId),
|
|
1889
1960
|
piProvider: provider
|
|
1890
1961
|
};
|
|
1891
1962
|
let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
|
|
@@ -12127,7 +12198,7 @@ function apply(ctx, config) {
|
|
|
12127
12198
|
discoverModels: async (signal) => {
|
|
12128
12199
|
const remote = await withDirectoryRetry(() => remoteCatalog.fetchModels(signal));
|
|
12129
12200
|
const [wireModels, roster] = await Promise.all([solo.fetchModels(signal), officialRoster.fetch(signal)]);
|
|
12130
|
-
const merged = mergeTraeModelSources(remote, wireModels);
|
|
12201
|
+
const merged = mergeTraeModelSources(remote, wireModels, { allowTenantConfigured: panel === "enterprise" });
|
|
12131
12202
|
const callable = withChatCallableFunctions(merged, TRAE_DIRECTORY_FUNCTIONS[region]);
|
|
12132
12203
|
const chatFunctions = TRAE_DIRECTORY_FUNCTIONS[region];
|
|
12133
12204
|
for (const model of merged) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.18.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|