dsh-connect-trae 2.0.1 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/lib/index.d.ts +40 -4
- package/lib/index.js +118 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.2 (2026-09-15)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **修复「Trae 目录里有、插件列表里没有」的整类问题**(issue #7:GLM-5.3 缺失)——根因是**模型调用的 `function` 选错了**,不是目录读取有误:
|
|
8
|
+
- Trae 把可调用目录**分散在多个 SOLO 模式 function 下**,且一个模型**只能通过列出它的那个 function 调用**。插件此前固定用 `solo_work_lite` 取目录与发请求,而 `glm-5.3` 只存在于 **`solo_work_remote`**:用前者调用会被上游拒绝(`4001 param is invalid`),用后者则正常流式返回(2026-09-15 受控实测,同一份请求体,仅 function 不同)。
|
|
9
|
+
- **目录发现改为多 function 并集**:CN 依序请求 `solo_work_remote` → `solo_work_lite`,国际请求 `solo_agent` → `solo_work_remote` → `solo_work_lite`;同一个 config 由**先列出的 function 拥有**(顺序即优先级)。某个 function 失败不再影响其它 function 的目录。
|
|
10
|
+
- **聊天请求按模型回放其来源 function**:每个模型随目录记下 `wireFunction`(并纳入设置 schema 持久化,重启后仍生效),调用时写回请求体——`glm-5.3` 因而会走 `solo_work_remote`。
|
|
11
|
+
- 顺带确认:CN 的两个登录(Trae CN IDE 与 TRAE SOLO CN)在同一 function 下拿到**完全相同**的列表,因此该修复对两者一致生效;国际版覆盖最全的仍是 `solo_agent`。
|
|
12
|
+
- 新增 4 个回归测试(多 function 并集、来源 function 标记、优先级归属、显式 function 覆盖默认值),230/230 全绿。
|
|
13
|
+
- **目录拉取失败时重试一次**——Trae 的目录端点存在间歇性 401/限流(实测同一凭证数秒前 200、随后 401)。启动时若恰好撞上,live 目录会拉取失败并**静默回退到上次保存的快照**,从而丢掉自那次保存以来的所有新模型(本版要放出的 glm-5.3 就在其中,实测复现)。现在合并骨架(remote 目录)失败会先重试一次(800ms 间隔)再降级。
|
|
14
|
+
|
|
3
15
|
## 2.0.1 (2026-09-15)
|
|
4
16
|
|
|
5
17
|
### Fixes
|
package/lib/index.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ interface TraeModelInfo {
|
|
|
109
109
|
maxContextWindow?: number;
|
|
110
110
|
/** The `config_name` `llm_utils_chat` accepts; absent means `id` is already the wire id. */
|
|
111
111
|
wireConfigName?: string;
|
|
112
|
+
/** The directory function this model must be called through (see {@link TraeWireModel.function}). */
|
|
113
|
+
wireFunction?: string;
|
|
112
114
|
}
|
|
113
115
|
/** Bootstrap catalog: identity only where current Trae metadata has not been fetched yet. */
|
|
114
116
|
declare const FALLBACK_TRAE_MODELS: readonly TraeModelInfo[];
|
|
@@ -147,6 +149,14 @@ interface TraeWireModel {
|
|
|
147
149
|
contextWindow?: number;
|
|
148
150
|
maxTokens?: number;
|
|
149
151
|
reasoning?: TraeReasoningCapability;
|
|
152
|
+
/**
|
|
153
|
+
* The `get_detail_param` function this config_name came from. Trae splits its
|
|
154
|
+
* callable roster across several functions (SOLO modes), and a model is only
|
|
155
|
+
* callable through the function that actually lists it — glm-5.3 answers
|
|
156
|
+
* `solo_work_remote` but rejects `solo_work_lite` with 4001. The chat call
|
|
157
|
+
* therefore has to replay the directory's own function.
|
|
158
|
+
*/
|
|
159
|
+
function?: string;
|
|
150
160
|
}
|
|
151
161
|
/**
|
|
152
162
|
* Merge the two Trae model sources into one authoritative catalog.
|
|
@@ -996,13 +1006,15 @@ declare function decodeTraeEvent(event: SseEvent): TraeStreamEvent;
|
|
|
996
1006
|
declare const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
997
1007
|
declare const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
|
|
998
1008
|
declare const TRAE_SOLO_MODELS_PATH = "/api/ide/v1/get_detail_param";
|
|
999
|
-
declare function prepareSoloBody(source: string, defaultModel?: string): string;
|
|
1009
|
+
declare function prepareSoloBody(source: string, defaultModel?: string, functionName?: string): string;
|
|
1000
1010
|
interface TraeSoloModel {
|
|
1001
1011
|
id: string;
|
|
1002
1012
|
name: string;
|
|
1003
1013
|
contextWindow?: number;
|
|
1004
1014
|
maxTokens?: number;
|
|
1005
1015
|
reasoning?: TraeReasoningCapability;
|
|
1016
|
+
/** The directory function that listed this config (replayed when calling it). */
|
|
1017
|
+
function?: string;
|
|
1006
1018
|
}
|
|
1007
1019
|
interface TraeSoloClientOptions {
|
|
1008
1020
|
credential(): Promise<TraeCredential>;
|
|
@@ -1015,15 +1027,39 @@ declare class TraeSoloUpstreamClient {
|
|
|
1015
1027
|
private readonly options;
|
|
1016
1028
|
private readonly fetchImpl;
|
|
1017
1029
|
constructor(options: TraeSoloClientOptions);
|
|
1030
|
+
/**
|
|
1031
|
+
* Read the callable roster for this credential's region.
|
|
1032
|
+
*
|
|
1033
|
+
* Every function in {@link TRAE_DIRECTORY_FUNCTIONS} is asked, in order, and
|
|
1034
|
+
* their answers are unioned: the first function to list a `config_name` owns
|
|
1035
|
+
* it. Trae splits its roster across SOLO modes, and a model is only callable
|
|
1036
|
+
* through the function that lists it (glm-5.3 exists solely under
|
|
1037
|
+
* `solo_work_remote` on the CN gateway). Asking one function therefore
|
|
1038
|
+
* silently hides models that the other one serves. The remote directory
|
|
1039
|
+
* remains the merge skeleton, so agent-internal entries (search_agent_*,
|
|
1040
|
+
* paygo variants) never surface even though they appear here.
|
|
1041
|
+
*/
|
|
1018
1042
|
fetchModels(signal?: AbortSignal): Promise<TraeSoloModel[]>;
|
|
1019
|
-
|
|
1043
|
+
/** Merge one function's config list into the shared catalogue (first wins). */
|
|
1044
|
+
private collectModels;
|
|
1045
|
+
chatStream(bodyJson: string, signal?: AbortSignal, functionName?: string): Promise<TraeChatResult>;
|
|
1020
1046
|
}
|
|
1021
1047
|
//#endregion
|
|
1022
1048
|
//#region src/solo-bridge.d.ts
|
|
1023
1049
|
/** Convert Trae's named SSE events into OpenAI chat-completion SSE chunks. */
|
|
1024
1050
|
declare function bridgeTraeSoloStream(response: Response, model: string): Response;
|
|
1025
|
-
/**
|
|
1026
|
-
|
|
1051
|
+
/**
|
|
1052
|
+
* One resolved wire target: the `config_name` `llm_utils_chat` accepts, plus
|
|
1053
|
+
* the directory function that listed it. Trae's roster is split across several
|
|
1054
|
+
* SOLO-mode functions and a model is only callable through the one listing it
|
|
1055
|
+
* (glm-5.3 answers only under `solo_work_remote`), so the chat call replays it.
|
|
1056
|
+
*/
|
|
1057
|
+
interface TraeWireTarget {
|
|
1058
|
+
configName: string;
|
|
1059
|
+
function?: string;
|
|
1060
|
+
}
|
|
1061
|
+
/** Resolves a display model id to its wire target (config_name + function). */
|
|
1062
|
+
type TraeWireResolver = (displayId: string) => TraeWireTarget | undefined;
|
|
1027
1063
|
/** Native SOLO client wrapper used by the loopback OpenAI adapter. */
|
|
1028
1064
|
declare class TraeSoloBridge implements TraeUpstreamClient {
|
|
1029
1065
|
private readonly upstream;
|
package/lib/index.js
CHANGED
|
@@ -156,7 +156,8 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
156
156
|
reasoning: model.reasoning,
|
|
157
157
|
reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
|
|
158
158
|
},
|
|
159
|
-
...wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {}
|
|
159
|
+
...wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {},
|
|
160
|
+
...wireModel.function === void 0 ? {} : { wireFunction: wireModel.function }
|
|
160
161
|
});
|
|
161
162
|
}
|
|
162
163
|
return result;
|
|
@@ -1818,6 +1819,25 @@ function applyReasoningEffort(body, effort, capability) {
|
|
|
1818
1819
|
//#endregion
|
|
1819
1820
|
//#region src/solo.ts
|
|
1820
1821
|
const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
1822
|
+
/**
|
|
1823
|
+
* Directory functions to union per region, in priority order.
|
|
1824
|
+
*
|
|
1825
|
+
* Trae spreads its callable roster across several SOLO-mode functions, and a
|
|
1826
|
+
* model is only usable through the one that lists it: `glm-5.3` is absent from
|
|
1827
|
+
* `solo_work_lite` but present in `solo_work_remote` (verified 2026-09-15 —
|
|
1828
|
+
* calling it through the former answers `4001 param is invalid`, through the
|
|
1829
|
+
* latter streams normally). Rather than betting on a single function, the
|
|
1830
|
+
* directory unions them; the first function to provide a config wins, so the
|
|
1831
|
+
* order below decides which wire name a model is called with.
|
|
1832
|
+
*/
|
|
1833
|
+
const TRAE_DIRECTORY_FUNCTIONS = {
|
|
1834
|
+
cn: ["solo_work_remote", TRAE_SOLO_FUNCTION],
|
|
1835
|
+
ai: [
|
|
1836
|
+
"solo_agent",
|
|
1837
|
+
"solo_work_remote",
|
|
1838
|
+
TRAE_SOLO_FUNCTION
|
|
1839
|
+
]
|
|
1840
|
+
};
|
|
1821
1841
|
const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
|
|
1822
1842
|
const TRAE_SOLO_MODELS_PATH = "/api/ide/v1/get_detail_param";
|
|
1823
1843
|
function classify(status) {
|
|
@@ -1831,14 +1851,14 @@ function classify(status) {
|
|
|
1831
1851
|
function finitePositive$1(value) {
|
|
1832
1852
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1833
1853
|
}
|
|
1834
|
-
function prepareSoloBody(source, defaultModel = "glm-5.2") {
|
|
1854
|
+
function prepareSoloBody(source, defaultModel = "glm-5.2", functionName) {
|
|
1835
1855
|
const input = JSON.parse(source);
|
|
1836
1856
|
const model = typeof input["model"] === "string" && input["model"].trim() !== "" ? input["model"].trim() : defaultModel;
|
|
1837
1857
|
const body = {
|
|
1838
1858
|
...Array.isArray(input["messages"]) ? { messages: input["messages"] } : {},
|
|
1839
1859
|
model,
|
|
1840
1860
|
config_name: model,
|
|
1841
|
-
function:
|
|
1861
|
+
function: typeof input["function"] === "string" && input["function"] !== "" ? input["function"] : functionName ?? "solo_work_lite",
|
|
1842
1862
|
stream: true,
|
|
1843
1863
|
...Array.isArray(input["tools"]) ? { tools: input["tools"] } : {},
|
|
1844
1864
|
...typeof input["reasoning_effort"] === "string" ? { reasoning_effort: input["reasoning_effort"] } : {}
|
|
@@ -1880,32 +1900,60 @@ var TraeSoloUpstreamClient = class {
|
|
|
1880
1900
|
this.options = options;
|
|
1881
1901
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
1882
1902
|
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Read the callable roster for this credential's region.
|
|
1905
|
+
*
|
|
1906
|
+
* Every function in {@link TRAE_DIRECTORY_FUNCTIONS} is asked, in order, and
|
|
1907
|
+
* their answers are unioned: the first function to list a `config_name` owns
|
|
1908
|
+
* it. Trae splits its roster across SOLO modes, and a model is only callable
|
|
1909
|
+
* through the function that lists it (glm-5.3 exists solely under
|
|
1910
|
+
* `solo_work_remote` on the CN gateway). Asking one function therefore
|
|
1911
|
+
* silently hides models that the other one serves. The remote directory
|
|
1912
|
+
* remains the merge skeleton, so agent-internal entries (search_agent_*,
|
|
1913
|
+
* paygo variants) never surface even though they appear here.
|
|
1914
|
+
*/
|
|
1883
1915
|
async fetchModels(signal) {
|
|
1884
1916
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
1885
1917
|
const region = regionOfCredential(credential);
|
|
1886
1918
|
const base = this.options.baseUrl ?? REGION_GATEWAYS[region].chat;
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1919
|
+
const headers = {
|
|
1920
|
+
...buildTraeCnHeaders(credential, identity),
|
|
1921
|
+
Accept: "application/json"
|
|
1922
|
+
};
|
|
1923
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1924
|
+
const failures = [];
|
|
1925
|
+
for (const directoryFunction of TRAE_DIRECTORY_FUNCTIONS[region]) {
|
|
1926
|
+
let list;
|
|
1927
|
+
try {
|
|
1928
|
+
const response = await this.fetchImpl(traeEndpoint(base, TRAE_SOLO_MODELS_PATH), {
|
|
1929
|
+
method: "POST",
|
|
1930
|
+
headers,
|
|
1931
|
+
body: JSON.stringify({
|
|
1932
|
+
function: directoryFunction,
|
|
1933
|
+
config_names: null,
|
|
1934
|
+
need_prompt: false,
|
|
1935
|
+
current_config_info: null,
|
|
1936
|
+
poly_prompt: true,
|
|
1937
|
+
mode_type: null,
|
|
1938
|
+
agent_type: null
|
|
1939
|
+
}),
|
|
1940
|
+
signal: signal ?? AbortSignal.timeout(3e4)
|
|
1941
|
+
});
|
|
1942
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1943
|
+
const document = await response.json();
|
|
1944
|
+
list = Array.isArray(document["config_info_list"]) ? document["config_info_list"] : [];
|
|
1945
|
+
} catch (error) {
|
|
1946
|
+
failures.push(`${directoryFunction}: ${String(error).slice(0, 80)}`);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
this.collectModels(list, directoryFunction, byId);
|
|
1950
|
+
}
|
|
1951
|
+
const models = [...byId.values()];
|
|
1952
|
+
if (models.length === 0) throw new Error(`Trae SOLO models response contained no models (${failures.join("; ") || "empty directory"})`);
|
|
1953
|
+
return models;
|
|
1954
|
+
}
|
|
1955
|
+
/** Merge one function's config list into the shared catalogue (first wins). */
|
|
1956
|
+
collectModels(list, directoryFunction, byId) {
|
|
1909
1957
|
for (const raw of list) {
|
|
1910
1958
|
if (typeof raw !== "object" || raw === null) continue;
|
|
1911
1959
|
const config = raw;
|
|
@@ -1923,21 +1971,21 @@ var TraeSoloUpstreamClient = class {
|
|
|
1923
1971
|
...config,
|
|
1924
1972
|
...detail
|
|
1925
1973
|
});
|
|
1926
|
-
|
|
1974
|
+
if (byId.has(id)) continue;
|
|
1975
|
+
byId.set(id, {
|
|
1927
1976
|
id,
|
|
1928
1977
|
name: typeof display["display_name"] === "string" && display["display_name"] !== "" ? display["display_name"] : id,
|
|
1929
1978
|
...contextWindow === void 0 ? {} : { contextWindow },
|
|
1930
1979
|
...maxTokens === void 0 ? {} : { maxTokens },
|
|
1931
|
-
...reasoning === void 0 ? {} : { reasoning }
|
|
1980
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
1981
|
+
function: directoryFunction
|
|
1932
1982
|
});
|
|
1933
1983
|
}
|
|
1934
|
-
if (models.length === 0) throw new Error("Trae SOLO models response contained no models");
|
|
1935
|
-
return models;
|
|
1936
1984
|
}
|
|
1937
|
-
async chatStream(bodyJson, signal) {
|
|
1985
|
+
async chatStream(bodyJson, signal, functionName) {
|
|
1938
1986
|
let prepared;
|
|
1939
1987
|
try {
|
|
1940
|
-
prepared = prepareSoloBody(bodyJson);
|
|
1988
|
+
prepared = prepareSoloBody(bodyJson, void 0, functionName);
|
|
1941
1989
|
} catch {
|
|
1942
1990
|
return {
|
|
1943
1991
|
ok: false,
|
|
@@ -2230,11 +2278,17 @@ var TraeSoloBridge = class {
|
|
|
2230
2278
|
const input = JSON.parse(bodyJson);
|
|
2231
2279
|
if (typeof input["model"] === "string" && input["model"] !== "") model = input["model"];
|
|
2232
2280
|
const entry = this.catalog?.current().find((item) => item.id === model);
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2281
|
+
const fromCatalog = entry?.wireConfigName === void 0 ? void 0 : {
|
|
2282
|
+
configName: entry.wireConfigName,
|
|
2283
|
+
...entry.wireFunction === void 0 ? {} : { function: entry.wireFunction }
|
|
2284
|
+
};
|
|
2285
|
+
const fromResolver = this.wireResolver?.(model) ?? this.wireResolver?.(entry?.name ?? "");
|
|
2286
|
+
const target = fromCatalog ?? fromResolver;
|
|
2287
|
+
const wireModel = target?.configName ?? model;
|
|
2288
|
+
const wireFunction = target?.function ?? entry?.wireFunction;
|
|
2289
|
+
if (wireModel !== input["model"]) input["model"] = wireModel;
|
|
2290
|
+
if (wireFunction !== void 0 && input["function"] !== wireFunction) input["function"] = wireFunction;
|
|
2291
|
+
if (wireModel !== JSON.parse(bodyJson)["model"] || wireFunction !== void 0) prepared = JSON.stringify(input);
|
|
2238
2292
|
if (typeof input["reasoning_effort"] === "string") {
|
|
2239
2293
|
const efforts = entry?.reasoningEfforts;
|
|
2240
2294
|
const requested = input["reasoning_effort"];
|
|
@@ -3467,7 +3521,8 @@ const modelConfig = z.object({
|
|
|
3467
3521
|
contextWindow: z.number().step(1).min(1),
|
|
3468
3522
|
maxTokens: z.number().step(1).min(1),
|
|
3469
3523
|
input: z.array(z.union(["text", "image"])),
|
|
3470
|
-
creditMultiplier: z.number()
|
|
3524
|
+
creditMultiplier: z.number(),
|
|
3525
|
+
wireFunction: z.string()
|
|
3471
3526
|
});
|
|
3472
3527
|
const regionStateConfig = z.object({
|
|
3473
3528
|
lastCatalog: z.array(modelConfig).default([]),
|
|
@@ -3553,6 +3608,25 @@ function apply(ctx, config) {
|
|
|
3553
3608
|
if (explicit !== void 0) return explicit;
|
|
3554
3609
|
return legacyAccountRegion === region ? value.accountId : void 0;
|
|
3555
3610
|
};
|
|
3611
|
+
/**
|
|
3612
|
+
* Retry a directory read once. The gateways answer an intermittent 401 for a
|
|
3613
|
+
* credential that works moments later (observed 2026-09-15), and a failed
|
|
3614
|
+
* skeleton read would otherwise drop the runtime catalog back to the saved
|
|
3615
|
+
* snapshot — hiding every model added since that save (issue #7's glm-5.3
|
|
3616
|
+
* among them).
|
|
3617
|
+
*/
|
|
3618
|
+
async function withDirectoryRetry(read) {
|
|
3619
|
+
try {
|
|
3620
|
+
return await read();
|
|
3621
|
+
} catch (first) {
|
|
3622
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
3623
|
+
try {
|
|
3624
|
+
return await read();
|
|
3625
|
+
} catch {
|
|
3626
|
+
throw first;
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3556
3630
|
const stacks = {};
|
|
3557
3631
|
for (const region of REGION_KEYS) {
|
|
3558
3632
|
const catalog = new TraeCatalog(region);
|
|
@@ -3654,8 +3728,7 @@ function apply(ctx, config) {
|
|
|
3654
3728
|
delegating,
|
|
3655
3729
|
usageClient: new TraeUsageClient({ credential: () => store.resolve() }),
|
|
3656
3730
|
discoverModels: async (signal) => {
|
|
3657
|
-
const
|
|
3658
|
-
const merged = mergeTraeModelSources(remote, wireModels);
|
|
3731
|
+
const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
|
|
3659
3732
|
wire.callableKeys.clear();
|
|
3660
3733
|
for (const model of merged) {
|
|
3661
3734
|
wire.callableKeys.add(model.id.trim().toLowerCase());
|
|
@@ -3665,8 +3738,12 @@ function apply(ctx, config) {
|
|
|
3665
3738
|
wire.byId.clear();
|
|
3666
3739
|
wire.byName.clear();
|
|
3667
3740
|
for (const model of merged) if (model.wireConfigName !== void 0) {
|
|
3668
|
-
|
|
3669
|
-
|
|
3741
|
+
const target = {
|
|
3742
|
+
configName: model.wireConfigName,
|
|
3743
|
+
...model.wireFunction === void 0 ? {} : { function: model.wireFunction }
|
|
3744
|
+
};
|
|
3745
|
+
wire.byId.set(model.id, target);
|
|
3746
|
+
wire.byName.set(model.name.trim().toLowerCase(), target);
|
|
3670
3747
|
}
|
|
3671
3748
|
return merged;
|
|
3672
3749
|
},
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "Connect locally signed-in Trae models to DeepSeek Harness, plus a read-only usage/credits summary.",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.2",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|