dsh-connect-trae 4.0.1 → 4.1.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 +52 -0
- package/lib/index.d.ts +138 -2
- package/lib/index.js +302 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.1.0 (2026-10-21)
|
|
4
|
+
|
|
5
|
+
> **受限网络(企业网只放行少数域名)下「刷新模型 500 / 聊天 4001」的完整修复。**
|
|
6
|
+
>
|
|
7
|
+
> 链路是三段式的:`solo.trae.cn` 的 CONNECT 被代理拒绝(403)→ 目录 `fetch failed`(刷新 500)→
|
|
8
|
+
> 模型丢失 `wireFunction` → 聊天按默认 `solo_work_lite` 发请求 → 模型不在该 function 的 config 名单里就
|
|
9
|
+
> 4001 `config_name rejected`。本版对三段各有一处改动:目录 host 回退到聊天网关的同路径(带冷却重试)、
|
|
10
|
+
> 目录分组的 function 兜底 `wireFunction`(只认可聊分组)、bridge 对「只带 function」的目录行保留
|
|
11
|
+
> resolver 的 `config_name` 大小写(issue #38 的坑)。
|
|
12
|
+
>
|
|
13
|
+
> 合并 PR #43 前做了对抗性复现,修掉了四处会伤到真实用户的回归——**最关键的一处**:原实现把功能名
|
|
14
|
+
> 属于 `solo_agent`/`builder_v3` 的目录行整行丢弃,而受限网络下那正是 9 个只在 `solo_agent` 分组里
|
|
15
|
+
> 出现的模型(`gpt-5.5`、`gpt-5.6-*`、`gpt-6-astra`、`glm-5.2`、`kimi-k2.7-code`、`Dola-Seed-2.0-Code`、
|
|
16
|
+
> `deepseek-v4-flash-0731`),它们正是 issue #19 修复要暴露的那批、且实测在默认 chat function 下
|
|
17
|
+
> 都能正常应答。现在只把非可聊 function **降级清空**,行保留。
|
|
18
|
+
|
|
19
|
+
### 修复
|
|
20
|
+
|
|
21
|
+
- **受限网络下模型目录走聊天网关备用 host,目录分组 function 兜底 `wireFunction`**(`fix(cn)`)。修复分三层:
|
|
22
|
+
- **目录 host 回退**:官方 remote host 网络错误 / 超时 / 5xx 时,按序尝试聊天网关的同一路径
|
|
23
|
+
(CN 为 `trae-api-cn.mchost.guru/api/remote/v1`,实测同路径同响应结构);401/403/404 等语义性拒绝**不回退**。
|
|
24
|
+
进程内记住最近一次**给出了非空目录**的 host。官方 host 失败后进入 5 分钟冷却:冷却期内先走记住的备用
|
|
25
|
+
host、官方 host 排在链尾(备用也挂了时仍有一次机会),冷却到期后官方 host 重新排第一 —— 只把官方 host
|
|
26
|
+
「排在第二」并不能让它被重试,因为循环在第一个成功处返回,所以必须显式冷却重试,恢复的网络才会在
|
|
27
|
+
一个冷却窗口内重新接管。空目录(200-but-empty)既不算成功(不记忆)也不终止链路(继续试下一个 base),
|
|
28
|
+
否则一个还没同步的镜像会永久堵死官方 host。回退只作用于 remote/v1 目录这一个 base;
|
|
29
|
+
`get_detail_param`、`llm_utils_chat` 照旧走各自原有 host。
|
|
30
|
+
- **目录分组 function 归因**:`fetchModels` 不再丢弃 `data.list[].function`——解析时把**可聊**分组的 function
|
|
31
|
+
附到每个模型(同一模型出现于多个分组时按 `TRAE_DIRECTORY_FUNCTIONS` 的实测优先级取,而非请求参数
|
|
32
|
+
`functions=` 的顺序;后者是历史列表,与 wire roster 自己的收集顺序不同,混用会让同一模型在「roster 有没有
|
|
33
|
+
返回」两种网络下拿到不同 function,`solo_agent_remote` 在 Remote 付费门后,优先它会多出 1005)。
|
|
34
|
+
`mergeTraeModelSources` 的 `wireFunction` 优先取 `get_detail_param` 的结论,目录分组兜底(roster 知道
|
|
35
|
+
账号的付费/可用状态,目录不知道)。同一 function 名重复出现时按名分桶保留全部,不再只留最后一组。
|
|
36
|
+
- **非可聊 function 只降级、不丢行**:`solo_agent`、`builder_v3` 是 IDE/Remote-agent roster,聊天通道实测
|
|
37
|
+
4011/4001,所以**不把这两个 function 写进 `wireFunction`**;模型本身**保留**,改由默认 chat function
|
|
38
|
+
调用。把它们整行丢掉等于在本次修复针对的受限网络里重新藏起 issue #19 的成果(详见上方导语)。
|
|
39
|
+
仅带 `wireFunction`(无 `wireConfigName`)的目录行,聊天请求会带上该 function,但 `config_name` 仍由
|
|
40
|
+
resolver 提供(目录 id 只是展示名,网关 `config_name` 大小写敏感,见 issue #38)。默认 function 与
|
|
41
|
+
fallback 模型常量未动。
|
|
42
|
+
- 已知取舍:模型被目录放在 `chat_v3` 分组下、但实际不可聊时,插件仅凭目录数据无法区分,仍会展示,
|
|
43
|
+
聊天失败走现有 4001 提示翻译(诊断入口见 `docs/ISSUE19_DIAGNOSIS.md`)。
|
|
44
|
+
|
|
45
|
+
### 测试
|
|
46
|
+
|
|
47
|
+
- 回归用例:host 回退(官方网络错误 → 备用 host 同路径同查询;401 不回退;5xx 回退;**空目录继续尝试下一个
|
|
48
|
+
base 且不被记住**;记住最近成功 host;**冷却到期后官方 host 重新排第一**;显式 baseUrl 钉住单 host)、
|
|
49
|
+
分组 function 归因(可聊分组优先;**同一 function 名重复出现时不丢模型**;容量来自后分组时保留先到先得的
|
|
50
|
+
function)、合并兜底与 `withChatCallableFunctions` 的**降级而非丢弃**、bridge 对 wireFunction-only 目录行
|
|
51
|
+
保留 resolver 的 `config_name`(大小写)、以及受限网络端到端(wire roster 为空时 9 个 `solo_agent`-only
|
|
52
|
+
模型仍然在目录里)。
|
|
53
|
+
- 全仓 **921** 例通过,0 skipped;`tsc` 两个工程干净;`build` 通过;CI(ubuntu + windows)全绿。
|
|
54
|
+
|
|
3
55
|
## 4.0.1 (2026-10-19)
|
|
4
56
|
|
|
5
57
|
> CN 区模型卡「最大输出」整列空白(issue #42)修复:上游**是**发布输出上限的——`get_detail_param` 的
|
package/lib/index.d.ts
CHANGED
|
@@ -30,9 +30,29 @@ interface TraeDiscoveredModel {
|
|
|
30
30
|
creditMultiplier?: number;
|
|
31
31
|
reasoningSupported: boolean;
|
|
32
32
|
reasoning?: TraeDiscoveredReasoning;
|
|
33
|
+
/**
|
|
34
|
+
* The directory group the model was seen under (`data.list[].function`).
|
|
35
|
+
*
|
|
36
|
+
* The Remote `/models` answer is grouped by SOLO function and the group is
|
|
37
|
+
* the authoritative statement of which function the model belongs to — the
|
|
38
|
+
* `config_name` the gateway accepts is isolated per function, so a model in
|
|
39
|
+
* `solo_work_remote` answers 4001 when asked through `solo_work_lite`. The
|
|
40
|
+
* wire join (see `mergeTraeModelSources`) prefers its own
|
|
41
|
+
* `get_detail_param` function and uses this as the fallback when the roster
|
|
42
|
+
* has nothing to say (restricted networks often return an empty
|
|
43
|
+
* `config_info_list` for the chat functions).
|
|
44
|
+
*/
|
|
45
|
+
function?: string;
|
|
33
46
|
}
|
|
34
|
-
/**
|
|
35
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Parse only capabilities explicitly advertised by Trae's remote model API.
|
|
49
|
+
*
|
|
50
|
+
* @param groupFunction The `data.list[].function` of the group this raw row
|
|
51
|
+
* was read from; attached verbatim so downstream can replay the model's own
|
|
52
|
+
* function instead of the `llm_utils_chat` default (see
|
|
53
|
+
* {@link TraeDiscoveredModel.function}).
|
|
54
|
+
*/
|
|
55
|
+
declare function parseTraeRemoteModel(value: unknown, groupFunction?: string): TraeDiscoveredModel | undefined;
|
|
36
56
|
//#endregion
|
|
37
57
|
//#region src/paths.d.ts
|
|
38
58
|
type TraeEdition = 'cn' | 'sg' | 'solo' | 'solo-sg';
|
|
@@ -2810,6 +2830,13 @@ interface TraeSoloRemoteCatalogOptions {
|
|
|
2810
2830
|
credential(): Promise<TraeCredential>;
|
|
2811
2831
|
fetchImpl?: typeof fetch;
|
|
2812
2832
|
baseUrl?: string;
|
|
2833
|
+
/**
|
|
2834
|
+
* Diagnostics sink (wired to `ctx.logger.warn` by the plugin). Called only
|
|
2835
|
+
* on the interesting path — when a NON-official host served the directory
|
|
2836
|
+
* or is reused as the remembered first candidate — never on the happy path
|
|
2837
|
+
* of official-host-first.
|
|
2838
|
+
*/
|
|
2839
|
+
log?: (message: string, detail?: unknown) => void;
|
|
2813
2840
|
}
|
|
2814
2841
|
/**
|
|
2815
2842
|
* Read-only model catalog client for the SOLO Web API.
|
|
@@ -2821,9 +2848,118 @@ declare class TraeSoloRemoteCatalogClient {
|
|
|
2821
2848
|
private readonly options;
|
|
2822
2849
|
private readonly fetchImpl;
|
|
2823
2850
|
private readonly baseUrl;
|
|
2851
|
+
/**
|
|
2852
|
+
* The last base that answered the directory with a NON-EMPTY catalog in this
|
|
2853
|
+
* process.
|
|
2854
|
+
*
|
|
2855
|
+
* A proxy-blocked network pays the failing official attempt on EVERY refresh
|
|
2856
|
+
* otherwise, so a backup that has served the catalog is tried first on later
|
|
2857
|
+
* refreshes. Putting the official base "second" is NOT enough to re-probe it:
|
|
2858
|
+
* the loop returns on the first success, so a backup that keeps answering
|
|
2859
|
+
* means the official base is never asked again. The official host is
|
|
2860
|
+
* therefore re-probed on a COOLDOWN instead (see {@link OFFICIAL_RETRY_MS}):
|
|
2861
|
+
* while the cooldown runs the backup is used and the official host is
|
|
2862
|
+
* skipped; when it expires the official host is tried FIRST again, so a
|
|
2863
|
+
* healed network takes over within one cooldown window rather than never.
|
|
2864
|
+
*/
|
|
2865
|
+
private lastSuccessfulBase;
|
|
2866
|
+
/**
|
|
2867
|
+
* When the official base may be probed again after a transport/5xx failure,
|
|
2868
|
+
* as epoch milliseconds (0 = probe it on the next refresh).
|
|
2869
|
+
*/
|
|
2870
|
+
private officialRetryAtMs;
|
|
2871
|
+
/**
|
|
2872
|
+
* How long a failing official host is skipped before it is re-probed.
|
|
2873
|
+
*
|
|
2874
|
+
* Long enough that a blocked network stops paying the failing attempt on
|
|
2875
|
+
* every refresh (the point of the fallback), short enough that a healed
|
|
2876
|
+
* network takes over without a restart. Each probe costs one connect timeout
|
|
2877
|
+
* (30 s) in the worst case, so 5 minutes bounds that to ~1 attempt per 5 min.
|
|
2878
|
+
*/
|
|
2879
|
+
private static readonly OFFICIAL_RETRY_MS;
|
|
2824
2880
|
constructor(options: TraeSoloRemoteCatalogOptions);
|
|
2825
2881
|
private headers;
|
|
2826
2882
|
fetchModels(signal?: AbortSignal): Promise<TraeDiscoveredModel[]>;
|
|
2883
|
+
/**
|
|
2884
|
+
* Bases to try for one refresh, in order.
|
|
2885
|
+
*
|
|
2886
|
+
* While the official host is in its post-failure cooldown the backup hosts go
|
|
2887
|
+
* first and the official base is kept LAST as a final chance (so a still-down
|
|
2888
|
+
* backup does not fail the whole refresh while the official host happens to
|
|
2889
|
+
* be reachable). Once the cooldown expires the official base is first again:
|
|
2890
|
+
* that is what lets a healed network take over — the loop returns on the
|
|
2891
|
+
* first success, so simply placing the official base second never re-probes
|
|
2892
|
+
* it while a backup keeps answering.
|
|
2893
|
+
*/
|
|
2894
|
+
private candidateBases;
|
|
2895
|
+
/**
|
|
2896
|
+
* Union every returned group into one deduplicated list, attributing each
|
|
2897
|
+
* model to the CHAT function the directory listed it under.
|
|
2898
|
+
*
|
|
2899
|
+
* Each group is a roster the gateway is willing to serve; a model listed by
|
|
2900
|
+
* any of them is a model the IDE can offer, and the wire join downstream (see
|
|
2901
|
+
* mergeTraeModelSources) is what decides whether it is actually callable.
|
|
2902
|
+
* Restricting this to `solo_agent_remote` dropped the nine models issue #19
|
|
2903
|
+
* was opened about.
|
|
2904
|
+
*
|
|
2905
|
+
* FUNCTION ATTRIBUTION (2026-10-20): a group is the authoritative statement
|
|
2906
|
+
* of which SOLO function a model belongs to — the accepted `config_name` is
|
|
2907
|
+
* isolated per function (glm-5.3 answers `solo_work_remote` and 4001s under
|
|
2908
|
+
* `solo_work_lite`). The wire join prefers its own `get_detail_param`
|
|
2909
|
+
* function and falls back to this one when the roster says nothing, which
|
|
2910
|
+
* restricted networks often do (measured: an empty `config_info_list` for
|
|
2911
|
+
* every chat function but `chat_v3`).
|
|
2912
|
+
*
|
|
2913
|
+
* ONLY CHAT-CAPABLE GROUPS ARE ATTRIBUTED. `solo_agent` and `builder_v3` are
|
|
2914
|
+
* IDE/Remote-agent rosters: asking `llm_utils_chat` with either answers
|
|
2915
|
+
* 4011/4001 (measured 2026-10-01 on a live credential, issue #19). A model
|
|
2916
|
+
* that appears ONLY there is still not uncallable, though — the SAME models
|
|
2917
|
+
* answered normally under `solo_work_remote` / `solo_work_lite` /
|
|
2918
|
+
* `solo_agent_remote` (measured the same day, see `TRAE_DIRECTORY_FUNCTIONS`
|
|
2919
|
+
* in solo.ts), and the default function is what has always reached them:
|
|
2920
|
+
* they are exactly the nine models issue #19's fix exposes (`gpt-5.5`,
|
|
2921
|
+
* `gpt-5.6-*`, `gpt-6-astra`, `glm-5.2`, `kimi-k2.7-code`,
|
|
2922
|
+
* `Dola-Seed-2.0-Code`, `deepseek-v4-flash-0731` — docs/ISSUE19_DIAGNOSIS.md).
|
|
2923
|
+
* Stamping `solo_agent` onto such a row makes every call 4011; DROPPING it
|
|
2924
|
+
* undoes issue #19 for exactly the restricted-network users this fallback
|
|
2925
|
+
* exists for. It therefore keeps NO function, the bridge falls back to the
|
|
2926
|
+
* default chat function exactly as before this change, and a refusal (if the
|
|
2927
|
+
* model really is unreachable) comes back as a readable upstream message
|
|
2928
|
+
* instead of a silent absence.
|
|
2929
|
+
*
|
|
2930
|
+
* CHAT GROUPS ARE WALKED IN `TRAE_DIRECTORY_FUNCTIONS` ORDER, not in the
|
|
2931
|
+
* request's `functions=` order. That list is the measured precedence of which
|
|
2932
|
+
* function serves a shared config, and it is the order the wire roster itself
|
|
2933
|
+
* collects in (`solo.ts`) — walking a different order would attribute the
|
|
2934
|
+
* same model differently depending on whether the roster answered, and the
|
|
2935
|
+
* difference is not cosmetic: `solo_agent_remote` sits after the two
|
|
2936
|
+
* `solo_work_*` functions because it is behind the Remote plan gate, so
|
|
2937
|
+
* preferring it can turn a free-tier model into a `1005 requires a paid
|
|
2938
|
+
* plan` refusal.
|
|
2939
|
+
*
|
|
2940
|
+
* A model appears in SEVERAL groups and the groups disagree about its
|
|
2941
|
+
* capacity, so dedupe keeps the MOST CAPABLE row rather than the first one
|
|
2942
|
+
* (issue #23). "First wins" assumed the group order was stable; measured
|
|
2943
|
+
* 2026-10-05 on a live CN credential it is not — five calls returned five
|
|
2944
|
+
* different orders — so the published window became a coin flip:
|
|
2945
|
+
* `Doubao-Seed-2.1-Pro` came back as `116000`/no-Max (`builder_v3`), or
|
|
2946
|
+
* `256000`/no-Max (`solo_work_*`), or `256000`/`1000000` (`chat_v3`,
|
|
2947
|
+
* `solo_agent*`) depending on which group answered first. The Max tier is
|
|
2948
|
+
* the one users enable in the IDE, and `applyContextBudgets` can only raise
|
|
2949
|
+
* a window to `maxContextWindow` — so losing that field silently capped
|
|
2950
|
+
* every configured 1M model at 256K or less, and DSH compressed far earlier
|
|
2951
|
+
* than the chosen budget implied.
|
|
2952
|
+
*
|
|
2953
|
+
* Ordering rule: a row advertising a Max tier beats one that does not, a
|
|
2954
|
+
* larger Max wins over a smaller one, and with no Max on either side the
|
|
2955
|
+
* wider dev window wins. Every row for one id carries the same
|
|
2956
|
+
* `multimodal` / `reasoning` / credit facts (verified the same day), so
|
|
2957
|
+
* choosing on capacity loses no capability. The result no longer depends on
|
|
2958
|
+
* the gateway's group order — and the FUNCTION does not either: the winning
|
|
2959
|
+
* row's capacity may come from a later group, but the separately-tracked
|
|
2960
|
+
* attribution is re-asserted on it.
|
|
2961
|
+
*/
|
|
2962
|
+
private collectModels;
|
|
2827
2963
|
}
|
|
2828
2964
|
//#endregion
|
|
2829
2965
|
//#region src/delegating-upstream.d.ts
|
package/lib/index.js
CHANGED
|
@@ -235,6 +235,7 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
235
235
|
for (const model of remote) {
|
|
236
236
|
const wireModel = wireById.get(displayKey(model.id)) ?? wireByName.get(displayKey(model.name));
|
|
237
237
|
const creditMultiplier = wireModel?.creditMultiplier ?? model.creditMultiplier;
|
|
238
|
+
const wireFunction = wireModel?.function ?? model.function;
|
|
238
239
|
result.push({
|
|
239
240
|
id: model.id,
|
|
240
241
|
name: model.name,
|
|
@@ -250,12 +251,45 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
250
251
|
reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
|
|
251
252
|
},
|
|
252
253
|
...wireModel !== void 0 && wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {},
|
|
253
|
-
...
|
|
254
|
+
...wireFunction === void 0 ? {} : { wireFunction }
|
|
254
255
|
});
|
|
255
256
|
}
|
|
256
257
|
return result;
|
|
257
258
|
}
|
|
258
259
|
/**
|
|
260
|
+
* Demote a NON-chat `wireFunction` to "no function", keeping the row.
|
|
261
|
+
*
|
|
262
|
+
* A row pinned to an IDE/Remote-agent roster — `solo_agent`, `builder_v3` — is
|
|
263
|
+
* not callable through `llm_utils_chat` by THAT function: asking answers
|
|
264
|
+
* 4011/4001 (measured 2026-10-01, issue #19). Dropping the row is not the fix,
|
|
265
|
+
* though, and was a regression: a model can carry a non-chat function for a
|
|
266
|
+
* reason that says nothing about its chat availability. The directory lists
|
|
267
|
+
* `gpt-5.5`, `gpt-5.6-*`, `gpt-6-astra`, `glm-5.2`, `kimi-k2.7-code`,
|
|
268
|
+
* `Dola-Seed-2.0-Code` and `deepseek-v4-flash-0731` under `solo_agent` ALONE
|
|
269
|
+
* (docs/ISSUE19_DIAGNOSIS.md), yet the same models answer normally under
|
|
270
|
+
* `solo_work_remote` / `solo_work_lite` / `solo_agent_remote` — they are
|
|
271
|
+
* exactly the models issue #19's fix was opened to expose. Dropping them
|
|
272
|
+
* re-hid the whole set on the restricted networks this fallback targets, where
|
|
273
|
+
* the wire roster is empty and the directory group is the only attribution.
|
|
274
|
+
*
|
|
275
|
+
* Keeping the row with the function CLEARED preserves both facts: the model
|
|
276
|
+
* stays visible (issue #19), and the bridge sends the region's DEFAULT chat
|
|
277
|
+
* function instead of one that is guaranteed to refuse. That is the pre-change
|
|
278
|
+
* behaviour for these rows — a genuinely unreachable model still fails with a
|
|
279
|
+
* readable upstream message rather than a silent absence.
|
|
280
|
+
*
|
|
281
|
+
* `chatFunctions` must be the region's own `TRAE_DIRECTORY_FUNCTIONS` list —
|
|
282
|
+
* the functions `llm_utils_chat` accepts. Callers should log the demoted rows:
|
|
283
|
+
* "which function did it lose, and why" deserves an answer.
|
|
284
|
+
*/
|
|
285
|
+
function withChatCallableFunctions(rows, chatFunctions) {
|
|
286
|
+
return rows.map((model) => {
|
|
287
|
+
if (model.wireFunction === void 0 || chatFunctions.includes(model.wireFunction)) return model;
|
|
288
|
+
const { wireFunction: _demoted, ...withoutFunction } = model;
|
|
289
|
+
return withoutFunction;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
259
293
|
* Apply the saved local budget. Trae advertises two windows per model (dev and
|
|
260
294
|
* Max), so the budget may only switch a model to its own advertised Max value —
|
|
261
295
|
* never to a fabricated number. Everything else keeps the dev window.
|
|
@@ -277,6 +311,7 @@ function discoveredCatalog(models) {
|
|
|
277
311
|
input: ["text"],
|
|
278
312
|
...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
|
|
279
313
|
...model.multimodal === void 0 ? {} : { multimodal: model.multimodal },
|
|
314
|
+
...model.function === void 0 ? {} : { wireFunction: model.function },
|
|
280
315
|
reasoningSupported: model.reasoningSupported,
|
|
281
316
|
...model.reasoning === void 0 ? {} : {
|
|
282
317
|
reasoning: model.reasoning,
|
|
@@ -4702,8 +4737,9 @@ var TraeSoloBridge = class {
|
|
|
4702
4737
|
if (typeof input["model"] === "string" && input["model"] !== "") model = input["model"];
|
|
4703
4738
|
const entryKey = model.trim().toLowerCase();
|
|
4704
4739
|
const entry = this.catalog?.current().find((item) => item.id === model || item.id.trim().toLowerCase() === entryKey);
|
|
4705
|
-
const
|
|
4706
|
-
|
|
4740
|
+
const catalogConfigName = entry?.wireConfigName;
|
|
4741
|
+
const fromCatalog = entry === void 0 || catalogConfigName === void 0 && entry.wireFunction === void 0 ? void 0 : {
|
|
4742
|
+
configName: catalogConfigName ?? entry.id,
|
|
4707
4743
|
...entry.wireFunction === void 0 ? {} : { function: entry.wireFunction }
|
|
4708
4744
|
};
|
|
4709
4745
|
const fromResolver = this.wireResolver?.(model) ?? this.wireResolver?.(entry?.name ?? "");
|
|
@@ -4718,11 +4754,28 @@ var TraeSoloBridge = class {
|
|
|
4718
4754
|
* directory, so borrowing its function is safe exactly when both sources
|
|
4719
4755
|
* agree on the wire config_name; a disagreement means the resolver found
|
|
4720
4756
|
* a different row and its function would be wrong.
|
|
4757
|
+
*
|
|
4758
|
+
* The MIRROR case is equally load-bearing and is the reason the row's own
|
|
4759
|
+
* id is not accepted as its wire spelling: a row that carries only the
|
|
4760
|
+
* FUNCTION (`wireConfigName` absent) has no config_name of its own, and
|
|
4761
|
+
* its display id is the wrong-cased spelling of the wire name in exactly
|
|
4762
|
+
* the cases issue #38 is about. The resolver's config_name is then the
|
|
4763
|
+
* only correct one, so it must win for the NAME while the row keeps
|
|
4764
|
+
* winning for the FUNCTION.
|
|
4721
4765
|
*/
|
|
4722
4766
|
const sameWireConfig = fromCatalog !== void 0 && fromResolver !== void 0 && fromCatalog.configName.toLowerCase() === fromResolver.configName.toLowerCase();
|
|
4767
|
+
/**
|
|
4768
|
+
* Whether the catalog row knows its own wire `config_name` (as opposed to
|
|
4769
|
+
* only carrying the display id plus a function). Only such a row may
|
|
4770
|
+
* decide the name on its own.
|
|
4771
|
+
*/
|
|
4772
|
+
const catalogKnowsConfigName = catalogConfigName !== void 0;
|
|
4723
4773
|
const target = fromCatalog !== void 0 && fromCatalog.function === void 0 && sameWireConfig && fromResolver !== void 0 ? {
|
|
4724
4774
|
configName: fromCatalog.configName,
|
|
4725
4775
|
...fromResolver.function === void 0 ? {} : { function: fromResolver.function }
|
|
4776
|
+
} : fromCatalog !== void 0 && !catalogKnowsConfigName && fromResolver !== void 0 ? {
|
|
4777
|
+
configName: fromResolver.configName,
|
|
4778
|
+
...fromCatalog.function === void 0 ? {} : { function: fromCatalog.function }
|
|
4726
4779
|
} : fromCatalog ?? fromResolver;
|
|
4727
4780
|
/**
|
|
4728
4781
|
* Last line of defence: the discovered row's own id.
|
|
@@ -4786,8 +4839,15 @@ const EFFORT_MAP = {
|
|
|
4786
4839
|
high: "high",
|
|
4787
4840
|
extra_high: "xhigh"
|
|
4788
4841
|
};
|
|
4789
|
-
/**
|
|
4790
|
-
|
|
4842
|
+
/**
|
|
4843
|
+
* Parse only capabilities explicitly advertised by Trae's remote model API.
|
|
4844
|
+
*
|
|
4845
|
+
* @param groupFunction The `data.list[].function` of the group this raw row
|
|
4846
|
+
* was read from; attached verbatim so downstream can replay the model's own
|
|
4847
|
+
* function instead of the `llm_utils_chat` default (see
|
|
4848
|
+
* {@link TraeDiscoveredModel.function}).
|
|
4849
|
+
*/
|
|
4850
|
+
function parseTraeRemoteModel(value, groupFunction) {
|
|
4791
4851
|
const raw = record(value);
|
|
4792
4852
|
if (raw === void 0 || typeof raw.name !== "string" || raw.name === "") return void 0;
|
|
4793
4853
|
const context = record(raw.context_window_tokens);
|
|
@@ -4818,7 +4878,8 @@ function parseTraeRemoteModel(value) {
|
|
|
4818
4878
|
...supported.length === 0 ? {} : { reasoning: {
|
|
4819
4879
|
supported,
|
|
4820
4880
|
...defaultEffort === void 0 ? {} : { defaultEffort }
|
|
4821
|
-
} }
|
|
4881
|
+
} },
|
|
4882
|
+
...groupFunction === void 0 || groupFunction === "" ? {} : { function: groupFunction }
|
|
4822
4883
|
};
|
|
4823
4884
|
}
|
|
4824
4885
|
//#endregion
|
|
@@ -4885,6 +4946,23 @@ const TRAE_REMOTE_DIRECTORY_FUNCTIONS = {
|
|
|
4885
4946
|
]
|
|
4886
4947
|
};
|
|
4887
4948
|
/**
|
|
4949
|
+
* Backup bases for the model directory, in try order after the official base.
|
|
4950
|
+
*
|
|
4951
|
+
* In restricted (proxy-sandboxed) networks the official catalog host
|
|
4952
|
+
* `solo.trae.cn` is behind a CONNECT rejection while the region's chat gateway
|
|
4953
|
+
* stays reachable — and that gateway serves the SAME `/api/remote/v1/models`
|
|
4954
|
+
* endpoint with an identical response shape (verified against
|
|
4955
|
+
* `https://trae-api-cn.mchost.guru/api/remote/v1` 2026-10-20; `chat` already
|
|
4956
|
+
* carries `/api/remote/v1`'s parent in {@link REGION_GATEWAYS}). The backup is
|
|
4957
|
+
* therefore derived from the region's chat base, mirrored on the remote path;
|
|
4958
|
+
* `ai` shares a single host for chat and remote, so it yields no candidate and
|
|
4959
|
+
* keeps its single-host behavior. An explicit `baseUrl` disables the chain.
|
|
4960
|
+
*/
|
|
4961
|
+
function traeSoloRemoteFallbackBases(region, officialBase) {
|
|
4962
|
+
const backupBase = `${REGION_GATEWAYS[region].chat}/api/remote/v1`;
|
|
4963
|
+
return backupBase === officialBase ? [] : [backupBase];
|
|
4964
|
+
}
|
|
4965
|
+
/**
|
|
4888
4966
|
* Of two rows for the SAME model id, the one that advertises the stronger
|
|
4889
4967
|
* capacity (issue #23).
|
|
4890
4968
|
*
|
|
@@ -4942,10 +5020,39 @@ function remoteDressing(region) {
|
|
|
4942
5020
|
* This deliberately has no chat/session method: the Remote session protocol
|
|
4943
5021
|
* only exposes a final answer and cannot preserve DSH's structured tool loop.
|
|
4944
5022
|
*/
|
|
4945
|
-
var TraeSoloRemoteCatalogClient = class {
|
|
5023
|
+
var TraeSoloRemoteCatalogClient = class TraeSoloRemoteCatalogClient {
|
|
4946
5024
|
options;
|
|
4947
5025
|
fetchImpl;
|
|
4948
5026
|
baseUrl;
|
|
5027
|
+
/**
|
|
5028
|
+
* The last base that answered the directory with a NON-EMPTY catalog in this
|
|
5029
|
+
* process.
|
|
5030
|
+
*
|
|
5031
|
+
* A proxy-blocked network pays the failing official attempt on EVERY refresh
|
|
5032
|
+
* otherwise, so a backup that has served the catalog is tried first on later
|
|
5033
|
+
* refreshes. Putting the official base "second" is NOT enough to re-probe it:
|
|
5034
|
+
* the loop returns on the first success, so a backup that keeps answering
|
|
5035
|
+
* means the official base is never asked again. The official host is
|
|
5036
|
+
* therefore re-probed on a COOLDOWN instead (see {@link OFFICIAL_RETRY_MS}):
|
|
5037
|
+
* while the cooldown runs the backup is used and the official host is
|
|
5038
|
+
* skipped; when it expires the official host is tried FIRST again, so a
|
|
5039
|
+
* healed network takes over within one cooldown window rather than never.
|
|
5040
|
+
*/
|
|
5041
|
+
lastSuccessfulBase;
|
|
5042
|
+
/**
|
|
5043
|
+
* When the official base may be probed again after a transport/5xx failure,
|
|
5044
|
+
* as epoch milliseconds (0 = probe it on the next refresh).
|
|
5045
|
+
*/
|
|
5046
|
+
officialRetryAtMs = 0;
|
|
5047
|
+
/**
|
|
5048
|
+
* How long a failing official host is skipped before it is re-probed.
|
|
5049
|
+
*
|
|
5050
|
+
* Long enough that a blocked network stops paying the failing attempt on
|
|
5051
|
+
* every refresh (the point of the fallback), short enough that a healed
|
|
5052
|
+
* network takes over without a restart. Each probe costs one connect timeout
|
|
5053
|
+
* (30 s) in the worst case, so 5 minutes bounds that to ~1 attempt per 5 min.
|
|
5054
|
+
*/
|
|
5055
|
+
static OFFICIAL_RETRY_MS = 3e5;
|
|
4949
5056
|
constructor(options) {
|
|
4950
5057
|
this.options = options;
|
|
4951
5058
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -4966,24 +5073,183 @@ var TraeSoloRemoteCatalogClient = class {
|
|
|
4966
5073
|
}
|
|
4967
5074
|
async fetchModels(signal) {
|
|
4968
5075
|
const region = regionOfCredential(await this.options.credential());
|
|
4969
|
-
const
|
|
5076
|
+
const officialBase = this.baseUrl ?? REGION_GATEWAYS[region].remote;
|
|
4970
5077
|
const headers = await this.headers(region);
|
|
4971
5078
|
const functions = TRAE_REMOTE_DIRECTORY_FUNCTIONS[region].join(",");
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5079
|
+
let lastError;
|
|
5080
|
+
let emptyError;
|
|
5081
|
+
for (const base of this.candidateBases(region, officialBase)) {
|
|
5082
|
+
let response;
|
|
5083
|
+
try {
|
|
5084
|
+
response = await this.fetchImpl(`${base}/models?functions=${functions}`, {
|
|
5085
|
+
headers,
|
|
5086
|
+
signal: signal ?? AbortSignal.timeout(3e4)
|
|
5087
|
+
});
|
|
5088
|
+
} catch (error) {
|
|
5089
|
+
if (signal?.aborted) throw error;
|
|
5090
|
+
if (base === officialBase) this.officialRetryAtMs = Date.now() + TraeSoloRemoteCatalogClient.OFFICIAL_RETRY_MS;
|
|
5091
|
+
lastError = error;
|
|
5092
|
+
continue;
|
|
5093
|
+
}
|
|
5094
|
+
if (!response.ok) {
|
|
5095
|
+
if (response.status < 500) throw new Error(`SOLO remote models returned HTTP ${response.status}`);
|
|
5096
|
+
if (base === officialBase) this.officialRetryAtMs = Date.now() + TraeSoloRemoteCatalogClient.OFFICIAL_RETRY_MS;
|
|
5097
|
+
lastError = /* @__PURE__ */ new Error(`SOLO remote models returned HTTP ${response.status}`);
|
|
5098
|
+
continue;
|
|
5099
|
+
}
|
|
5100
|
+
const json = await response.json();
|
|
5101
|
+
const models = this.collectModels(region, json.data?.list ?? []);
|
|
5102
|
+
if (models.length === 0) {
|
|
5103
|
+
emptyError = /* @__PURE__ */ new Error("SOLO remote models response contained no models");
|
|
5104
|
+
lastError = emptyError;
|
|
5105
|
+
continue;
|
|
5106
|
+
}
|
|
5107
|
+
if (base !== officialBase) this.options.log?.(this.lastSuccessfulBase === base ? `model directory served by the remembered fallback host ${base}; the official host (${officialBase}) is re-probed every ${TraeSoloRemoteCatalogClient.OFFICIAL_RETRY_MS / 6e4} minutes` : `model directory served by fallback host ${base}; the official host (${officialBase}) failed this refresh`, { officialBase });
|
|
5108
|
+
else this.officialRetryAtMs = 0;
|
|
5109
|
+
this.lastSuccessfulBase = base;
|
|
5110
|
+
return models;
|
|
5111
|
+
}
|
|
5112
|
+
if (emptyError !== void 0) throw emptyError;
|
|
5113
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
5114
|
+
}
|
|
5115
|
+
/**
|
|
5116
|
+
* Bases to try for one refresh, in order.
|
|
5117
|
+
*
|
|
5118
|
+
* While the official host is in its post-failure cooldown the backup hosts go
|
|
5119
|
+
* first and the official base is kept LAST as a final chance (so a still-down
|
|
5120
|
+
* backup does not fail the whole refresh while the official host happens to
|
|
5121
|
+
* be reachable). Once the cooldown expires the official base is first again:
|
|
5122
|
+
* that is what lets a healed network take over — the loop returns on the
|
|
5123
|
+
* first success, so simply placing the official base second never re-probes
|
|
5124
|
+
* it while a backup keeps answering.
|
|
5125
|
+
*/
|
|
5126
|
+
candidateBases(region, officialBase) {
|
|
5127
|
+
if (this.baseUrl !== void 0) return [officialBase];
|
|
5128
|
+
const fallbacks = traeSoloRemoteFallbackBases(region, officialBase);
|
|
5129
|
+
if (this.officialRetryAtMs <= Date.now()) return [officialBase, ...fallbacks];
|
|
5130
|
+
const remembered = this.lastSuccessfulBase;
|
|
5131
|
+
return [
|
|
5132
|
+
...remembered === void 0 ? [] : [remembered],
|
|
5133
|
+
...fallbacks.filter((base) => base !== remembered),
|
|
5134
|
+
officialBase
|
|
5135
|
+
];
|
|
5136
|
+
}
|
|
5137
|
+
/**
|
|
5138
|
+
* Union every returned group into one deduplicated list, attributing each
|
|
5139
|
+
* model to the CHAT function the directory listed it under.
|
|
5140
|
+
*
|
|
5141
|
+
* Each group is a roster the gateway is willing to serve; a model listed by
|
|
5142
|
+
* any of them is a model the IDE can offer, and the wire join downstream (see
|
|
5143
|
+
* mergeTraeModelSources) is what decides whether it is actually callable.
|
|
5144
|
+
* Restricting this to `solo_agent_remote` dropped the nine models issue #19
|
|
5145
|
+
* was opened about.
|
|
5146
|
+
*
|
|
5147
|
+
* FUNCTION ATTRIBUTION (2026-10-20): a group is the authoritative statement
|
|
5148
|
+
* of which SOLO function a model belongs to — the accepted `config_name` is
|
|
5149
|
+
* isolated per function (glm-5.3 answers `solo_work_remote` and 4001s under
|
|
5150
|
+
* `solo_work_lite`). The wire join prefers its own `get_detail_param`
|
|
5151
|
+
* function and falls back to this one when the roster says nothing, which
|
|
5152
|
+
* restricted networks often do (measured: an empty `config_info_list` for
|
|
5153
|
+
* every chat function but `chat_v3`).
|
|
5154
|
+
*
|
|
5155
|
+
* ONLY CHAT-CAPABLE GROUPS ARE ATTRIBUTED. `solo_agent` and `builder_v3` are
|
|
5156
|
+
* IDE/Remote-agent rosters: asking `llm_utils_chat` with either answers
|
|
5157
|
+
* 4011/4001 (measured 2026-10-01 on a live credential, issue #19). A model
|
|
5158
|
+
* that appears ONLY there is still not uncallable, though — the SAME models
|
|
5159
|
+
* answered normally under `solo_work_remote` / `solo_work_lite` /
|
|
5160
|
+
* `solo_agent_remote` (measured the same day, see `TRAE_DIRECTORY_FUNCTIONS`
|
|
5161
|
+
* in solo.ts), and the default function is what has always reached them:
|
|
5162
|
+
* they are exactly the nine models issue #19's fix exposes (`gpt-5.5`,
|
|
5163
|
+
* `gpt-5.6-*`, `gpt-6-astra`, `glm-5.2`, `kimi-k2.7-code`,
|
|
5164
|
+
* `Dola-Seed-2.0-Code`, `deepseek-v4-flash-0731` — docs/ISSUE19_DIAGNOSIS.md).
|
|
5165
|
+
* Stamping `solo_agent` onto such a row makes every call 4011; DROPPING it
|
|
5166
|
+
* undoes issue #19 for exactly the restricted-network users this fallback
|
|
5167
|
+
* exists for. It therefore keeps NO function, the bridge falls back to the
|
|
5168
|
+
* default chat function exactly as before this change, and a refusal (if the
|
|
5169
|
+
* model really is unreachable) comes back as a readable upstream message
|
|
5170
|
+
* instead of a silent absence.
|
|
5171
|
+
*
|
|
5172
|
+
* CHAT GROUPS ARE WALKED IN `TRAE_DIRECTORY_FUNCTIONS` ORDER, not in the
|
|
5173
|
+
* request's `functions=` order. That list is the measured precedence of which
|
|
5174
|
+
* function serves a shared config, and it is the order the wire roster itself
|
|
5175
|
+
* collects in (`solo.ts`) — walking a different order would attribute the
|
|
5176
|
+
* same model differently depending on whether the roster answered, and the
|
|
5177
|
+
* difference is not cosmetic: `solo_agent_remote` sits after the two
|
|
5178
|
+
* `solo_work_*` functions because it is behind the Remote plan gate, so
|
|
5179
|
+
* preferring it can turn a free-tier model into a `1005 requires a paid
|
|
5180
|
+
* plan` refusal.
|
|
5181
|
+
*
|
|
5182
|
+
* A model appears in SEVERAL groups and the groups disagree about its
|
|
5183
|
+
* capacity, so dedupe keeps the MOST CAPABLE row rather than the first one
|
|
5184
|
+
* (issue #23). "First wins" assumed the group order was stable; measured
|
|
5185
|
+
* 2026-10-05 on a live CN credential it is not — five calls returned five
|
|
5186
|
+
* different orders — so the published window became a coin flip:
|
|
5187
|
+
* `Doubao-Seed-2.1-Pro` came back as `116000`/no-Max (`builder_v3`), or
|
|
5188
|
+
* `256000`/no-Max (`solo_work_*`), or `256000`/`1000000` (`chat_v3`,
|
|
5189
|
+
* `solo_agent*`) depending on which group answered first. The Max tier is
|
|
5190
|
+
* the one users enable in the IDE, and `applyContextBudgets` can only raise
|
|
5191
|
+
* a window to `maxContextWindow` — so losing that field silently capped
|
|
5192
|
+
* every configured 1M model at 256K or less, and DSH compressed far earlier
|
|
5193
|
+
* than the chosen budget implied.
|
|
5194
|
+
*
|
|
5195
|
+
* Ordering rule: a row advertising a Max tier beats one that does not, a
|
|
5196
|
+
* larger Max wins over a smaller one, and with no Max on either side the
|
|
5197
|
+
* wider dev window wins. Every row for one id carries the same
|
|
5198
|
+
* `multimodal` / `reasoning` / credit facts (verified the same day), so
|
|
5199
|
+
* choosing on capacity loses no capability. The result no longer depends on
|
|
5200
|
+
* the gateway's group order — and the FUNCTION does not either: the winning
|
|
5201
|
+
* row's capacity may come from a later group, but the separately-tracked
|
|
5202
|
+
* attribution is re-asserted on it.
|
|
5203
|
+
*/
|
|
5204
|
+
collectModels(region, groups) {
|
|
4978
5205
|
const byId = /* @__PURE__ */ new Map();
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
5206
|
+
const attributedFunction = /* @__PURE__ */ new Map();
|
|
5207
|
+
/**
|
|
5208
|
+
* The function a row may be stamped with: only a CHAT function. A non-chat
|
|
5209
|
+
* roster is not "the function this model belongs to" in any sense the chat
|
|
5210
|
+
* route can use (see the method note above), so it is not recorded at all.
|
|
5211
|
+
*/
|
|
5212
|
+
const chatFunctions = new Set(TRAE_DIRECTORY_FUNCTIONS[region]);
|
|
5213
|
+
const attribution = (groupFunction) => groupFunction !== void 0 && chatFunctions.has(groupFunction) ? groupFunction : void 0;
|
|
5214
|
+
const addGroup = (groupFunction, raws) => {
|
|
5215
|
+
const fn = attribution(groupFunction);
|
|
5216
|
+
for (const raw of raws) {
|
|
5217
|
+
const model = parseTraeRemoteModel(raw, fn);
|
|
5218
|
+
if (model === void 0) continue;
|
|
5219
|
+
if (fn !== void 0 && !attributedFunction.has(model.id)) attributedFunction.set(model.id, fn);
|
|
5220
|
+
byId.set(model.id, preferStrongerRow(model, byId.get(model.id)));
|
|
5221
|
+
}
|
|
5222
|
+
};
|
|
5223
|
+
/**
|
|
5224
|
+
* Every group the gateway sent, bucketed BY FUNCTION NAME and kept as a
|
|
5225
|
+
* LIST.
|
|
5226
|
+
*
|
|
5227
|
+
* A name→group `Map` would keep only the LAST group of a name and silently
|
|
5228
|
+
* drop the models of every earlier one: nothing documents that the gateway
|
|
5229
|
+
* sends a function at most once, and the pre-change code unioned every
|
|
5230
|
+
* element of `data.list`, so a repeated name lost real models. Bucketing
|
|
5231
|
+
* keeps that union while the walk below still imposes a deterministic
|
|
5232
|
+
* order.
|
|
5233
|
+
*/
|
|
5234
|
+
const byFunction = /* @__PURE__ */ new Map();
|
|
5235
|
+
for (const group of groups) {
|
|
5236
|
+
const key = group.function ?? "";
|
|
5237
|
+
const bucket = byFunction.get(key);
|
|
5238
|
+
if (bucket === void 0) byFunction.set(key, [group]);
|
|
5239
|
+
else bucket.push(group);
|
|
4983
5240
|
}
|
|
4984
|
-
const
|
|
4985
|
-
|
|
4986
|
-
|
|
5241
|
+
for (const name of TRAE_DIRECTORY_FUNCTIONS[region]) for (const group of byFunction.get(name) ?? []) addGroup(name, group.models ?? []);
|
|
5242
|
+
for (const [name, bucket] of byFunction) {
|
|
5243
|
+
if (chatFunctions.has(name)) continue;
|
|
5244
|
+
for (const group of bucket) addGroup(name, group.models ?? []);
|
|
5245
|
+
}
|
|
5246
|
+
return [...byId.values()].map((model) => {
|
|
5247
|
+
const fn = attributedFunction.get(model.id);
|
|
5248
|
+
return fn === void 0 || fn === model.function ? model : {
|
|
5249
|
+
...model,
|
|
5250
|
+
function: fn
|
|
5251
|
+
};
|
|
5252
|
+
});
|
|
4987
5253
|
}
|
|
4988
5254
|
};
|
|
4989
5255
|
//#endregion
|
|
@@ -8756,7 +9022,10 @@ function apply(ctx, config) {
|
|
|
8756
9022
|
...panel === "enterprise" ? { route: "enterprise" } : {},
|
|
8757
9023
|
log: (message, detail) => ctx.logger.warn(message, detail)
|
|
8758
9024
|
});
|
|
8759
|
-
const remoteCatalog = new TraeSoloRemoteCatalogClient({
|
|
9025
|
+
const remoteCatalog = new TraeSoloRemoteCatalogClient({
|
|
9026
|
+
credential: () => store.resolve(),
|
|
9027
|
+
log: (message, detail) => ctx.logger.warn(message, detail)
|
|
9028
|
+
});
|
|
8760
9029
|
const wireResolver = (displayId) => wire.byId.get(displayId) ?? wire.byName.get(displayId.trim().toLowerCase());
|
|
8761
9030
|
/**
|
|
8762
9031
|
* One client + bridge per provider id.
|
|
@@ -8909,18 +9178,24 @@ function apply(ctx, config) {
|
|
|
8909
9178
|
},
|
|
8910
9179
|
discoverModels: async (signal) => {
|
|
8911
9180
|
const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
|
|
8912
|
-
|
|
9181
|
+
const callable = withChatCallableFunctions(merged, TRAE_DIRECTORY_FUNCTIONS[region]);
|
|
9182
|
+
const chatFunctions = TRAE_DIRECTORY_FUNCTIONS[region];
|
|
8913
9183
|
for (const model of merged) {
|
|
9184
|
+
if (model.wireFunction === void 0 || chatFunctions.includes(model.wireFunction)) continue;
|
|
9185
|
+
ctx.logger.warn(`model directory row is pinned to a non-chat function and will be called through the default chat function instead: ${model.id} (pinned to function ${model.wireFunction})`);
|
|
9186
|
+
}
|
|
9187
|
+
wire.callableKeys.clear();
|
|
9188
|
+
for (const model of callable) {
|
|
8914
9189
|
wire.callableKeys.add(model.id.trim().toLowerCase());
|
|
8915
9190
|
wire.callableKeys.add(model.name.trim().toLowerCase());
|
|
8916
9191
|
}
|
|
8917
|
-
wire.resolved =
|
|
8918
|
-
const wireTargets = wireTargetsOf(
|
|
9192
|
+
wire.resolved = callable.length > 0;
|
|
9193
|
+
const wireTargets = wireTargetsOf(callable);
|
|
8919
9194
|
wire.byId = wireTargets.byId;
|
|
8920
9195
|
wire.byName = wireTargets.byName;
|
|
8921
9196
|
latestDiscovery.length = 0;
|
|
8922
|
-
latestDiscovery.push(...
|
|
8923
|
-
return
|
|
9197
|
+
latestDiscovery.push(...callable);
|
|
9198
|
+
return callable;
|
|
8924
9199
|
},
|
|
8925
9200
|
/**
|
|
8926
9201
|
* One-click availability test. The ids come from the card, so each one is
|
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.0
|
|
5
|
+
"version": "4.1.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|