dsh-connect-trae 2.3.0 → 2.4.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 +138 -2
- package/README.en.md +102 -10
- package/README.md +109 -10
- package/lib/client.js +68 -14
- package/lib/index.d.ts +185 -13
- package/lib/index.js +233 -19
- package/package.json +26 -23
- package/scripts/verify-windows.mjs +311 -0
package/lib/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createServer } from "node:http";
|
|
|
13
13
|
import { Readable } from "node:stream";
|
|
14
14
|
import { execFile } from "node:child_process";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
16
17
|
//#region src/catalog.ts
|
|
17
18
|
/**
|
|
18
19
|
* Bootstrap catalog: identity only where current Trae metadata has not been
|
|
@@ -131,11 +132,45 @@ function traeInputModalities(model) {
|
|
|
131
132
|
function traeModelDisplayName(model) {
|
|
132
133
|
return model.creditMultiplier === void 0 ? model.name : `${model.name} · x${model.creditMultiplier.toFixed(2)}`;
|
|
133
134
|
}
|
|
134
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Whether one model should be offered image input.
|
|
137
|
+
*
|
|
138
|
+
* Two independent sources, unioned (issue #16):
|
|
139
|
+
*
|
|
140
|
+
* - the user's explicit `imageModelIds` opt-in, which stays authoritative and
|
|
141
|
+
* can still switch an individual model on;
|
|
142
|
+
* - Trae's own `multimodal` flag, which is what makes "auto" work at all.
|
|
143
|
+
*
|
|
144
|
+
* Rationale: Trae's IDE enables its attach-image button purely from this flag
|
|
145
|
+
* (`metadata.capabilities.vision`), with no user switch, so a model Trae itself
|
|
146
|
+
* calls multimodal is one the vendor expects to receive images. Requiring a
|
|
147
|
+
* manual tick for those made this plugin the only place where a vendor-declared
|
|
148
|
+
* capability had to be re-enabled by hand.
|
|
149
|
+
*
|
|
150
|
+
* Why the flag alone is not enough, and the union is:
|
|
151
|
+
*
|
|
152
|
+
* - the flag is advisory, not a contract. `llm_utils_chat` is a lightweight
|
|
153
|
+
* SOLO channel and this plugin has been burned by `4001 param is invalid`
|
|
154
|
+
* for ids that LOOKED valid, so the flag is not treated as proof of
|
|
155
|
+
* callability — a model still has to have survived the wire join to appear
|
|
156
|
+
* at all. The flag only decides image input, never model availability.
|
|
157
|
+
* - Users who already ticked a model keep that tick even after a refresh stops
|
|
158
|
+
* reporting the flag, so the union never silently revokes a working setup.
|
|
159
|
+
* - `input` remains the ONLY thing told to DSH: a model that ends up without
|
|
160
|
+
* `image` here is one DSH will degrade images to text for, which is the
|
|
161
|
+
* safe direction for a model whose capability we cannot confirm.
|
|
162
|
+
*
|
|
163
|
+
* `multimodal === true` is a strict test: absent, `false`, or any non-boolean
|
|
164
|
+
* leaves the model text-only unless the user opted in.
|
|
165
|
+
*/
|
|
166
|
+
function traeModelAcceptsImage(model, selected) {
|
|
167
|
+
return selected.has(model.id) || model.multimodal === true;
|
|
168
|
+
}
|
|
169
|
+
/** Apply the user's explicit image opt-ins plus Trae's own multimodal flag. */
|
|
135
170
|
function applyImageSelection(models, selected) {
|
|
136
171
|
return models.map((model) => ({
|
|
137
172
|
...model,
|
|
138
|
-
input:
|
|
173
|
+
input: traeModelAcceptsImage(model, selected) ? ["text", "image"] : ["text"]
|
|
139
174
|
}));
|
|
140
175
|
}
|
|
141
176
|
/** Normalise a display name for cross-source joining. */
|
|
@@ -191,6 +226,7 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
191
226
|
...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
|
|
192
227
|
...creditMultiplier === void 0 ? {} : { creditMultiplier },
|
|
193
228
|
input: ["text"],
|
|
229
|
+
...model.multimodal === void 0 ? {} : { multimodal: model.multimodal },
|
|
194
230
|
reasoningSupported: model.reasoningSupported,
|
|
195
231
|
...model.reasoning === void 0 ? {} : {
|
|
196
232
|
reasoning: model.reasoning,
|
|
@@ -213,7 +249,7 @@ function applyContextBudgets(catalog, budgets = {}) {
|
|
|
213
249
|
...model.maxContextWindow !== void 0 && budgets[model.id] === model.maxContextWindow ? { contextWindow: model.maxContextWindow } : {}
|
|
214
250
|
}));
|
|
215
251
|
}
|
|
216
|
-
/** Convert Trae metadata into
|
|
252
|
+
/** Convert Trae metadata into catalog rows; image input is decided by {@link applyImageSelection}. */
|
|
217
253
|
function discoveredCatalog(models) {
|
|
218
254
|
const result = [];
|
|
219
255
|
for (const model of models) result.push({
|
|
@@ -223,6 +259,7 @@ function discoveredCatalog(models) {
|
|
|
223
259
|
...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
|
|
224
260
|
input: ["text"],
|
|
225
261
|
...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
|
|
262
|
+
...model.multimodal === void 0 ? {} : { multimodal: model.multimodal },
|
|
226
263
|
reasoningSupported: model.reasoningSupported,
|
|
227
264
|
...model.reasoning === void 0 ? {} : {
|
|
228
265
|
reasoning: model.reasoning,
|
|
@@ -800,6 +837,43 @@ const LINUX_APP_NAMES = {
|
|
|
800
837
|
solo: ["trae-solo-cn", "TRAE SOLO CN"],
|
|
801
838
|
"solo-sg": ["trae-solo", "TRAE SOLO"]
|
|
802
839
|
};
|
|
840
|
+
/**
|
|
841
|
+
* Windows desktop config directory names.
|
|
842
|
+
*
|
|
843
|
+
* Trae is a VS Code-family Electron app, and that family names its per-user
|
|
844
|
+
* data directory from the installer-registered product name — which on Windows
|
|
845
|
+
* is `product.json`'s `win32DirName`, NOT the macOS bundle spelling. Measured
|
|
846
|
+
* from the shipped bundles (2026-09-26):
|
|
847
|
+
*
|
|
848
|
+
* Trae CN -> win32DirName = "Trae CN" (applicationName trae-cn)
|
|
849
|
+
* TRAE SOLO CN -> win32DirName = "TRAE SOLO CN" (applicationName trae-solo-cn)
|
|
850
|
+
*
|
|
851
|
+
* So the macOS spellings happen to be right here, and `win32DirName` is the
|
|
852
|
+
* authority that says so. The lowercase `applicationName` spellings
|
|
853
|
+
* (`trae-cn`, `trae-solo-cn`) are probed as well because the same family uses
|
|
854
|
+
* them for the Linux config directory, and which of the two a given installer
|
|
855
|
+
* writes has never been confirmed on a real Windows host
|
|
856
|
+
* (docs/WINDOWS_TOKEN_PROBE.md). Windows file systems are case-insensitive, so
|
|
857
|
+
* listing `trae cn` beside `Trae CN` would be the same directory twice — only
|
|
858
|
+
* genuinely different spellings are listed.
|
|
859
|
+
*/
|
|
860
|
+
const WINDOWS_APP_NAMES = {
|
|
861
|
+
cn: ["Trae CN", "trae-cn"],
|
|
862
|
+
sg: ["Trae"],
|
|
863
|
+
solo: ["TRAE SOLO CN", "trae-solo-cn"],
|
|
864
|
+
"solo-sg": ["TRAE SOLO"]
|
|
865
|
+
};
|
|
866
|
+
/**
|
|
867
|
+
* One edition's plausible Windows directory spellings, in probe order.
|
|
868
|
+
*
|
|
869
|
+
* Exported so the identity reader and the credential scanner probe the SAME
|
|
870
|
+
* names: the two resolve different files (the install's `product.json` vs the
|
|
871
|
+
* per-user `storage.json`) but they describe one installation, and a spelling
|
|
872
|
+
* that works for one but not the other is a bug rather than a preference.
|
|
873
|
+
*/
|
|
874
|
+
function traeWindowsAppNames(edition) {
|
|
875
|
+
return WINDOWS_APP_NAMES[edition];
|
|
876
|
+
}
|
|
803
877
|
function traeStorageCandidates(platform = process.platform, home = homedir(), env = process.env) {
|
|
804
878
|
const result = [];
|
|
805
879
|
for (const edition of [
|
|
@@ -816,7 +890,7 @@ function traeStorageCandidates(platform = process.platform, home = homedir(), en
|
|
|
816
890
|
appNames = [app];
|
|
817
891
|
} else if (platform === "win32") {
|
|
818
892
|
roots = [env.APPDATA, join(home, "AppData", "Roaming")].filter((value, index, all) => typeof value === "string" && value !== "" && all.indexOf(value) === index);
|
|
819
|
-
appNames = [
|
|
893
|
+
appNames = WINDOWS_APP_NAMES[edition];
|
|
820
894
|
} else if (platform === "linux") {
|
|
821
895
|
roots = [env.XDG_CONFIG_HOME || join(home, ".config")];
|
|
822
896
|
appNames = LINUX_APP_NAMES[edition];
|
|
@@ -1402,7 +1476,7 @@ async function readTraeIdentity(candidate, options = {}) {
|
|
|
1402
1476
|
if (platform === "darwin") productPaths.push(join("/Applications", `${appName}.app`, "Contents", "Resources", "app", "product.json"));
|
|
1403
1477
|
else {
|
|
1404
1478
|
const localRoots = [env.LOCALAPPDATA, join(home, "AppData", "Local")].filter((value) => typeof value === "string" && value !== "").filter((value, index, all) => all.indexOf(value) === index);
|
|
1405
|
-
for (const root of localRoots) productPaths.push(join(root, "Programs",
|
|
1479
|
+
for (const root of localRoots) for (const spelling of traeWindowsAppNames(candidate.edition)) productPaths.push(join(root, "Programs", spelling, "resources", "app", "product.json"));
|
|
1406
1480
|
}
|
|
1407
1481
|
}
|
|
1408
1482
|
let product = {};
|
|
@@ -2952,6 +3026,8 @@ function createTraeRawGateway(options) {
|
|
|
2952
3026
|
//#endregion
|
|
2953
3027
|
//#region src/model-cache.ts
|
|
2954
3028
|
const execFileAsync = promisify(execFile);
|
|
3029
|
+
/** Basename of the SQLite cache sitting beside the credential document. */
|
|
3030
|
+
const TRAE_STATE_DB_FILENAME = "state.vscdb";
|
|
2955
3031
|
function positive$1(value) {
|
|
2956
3032
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
2957
3033
|
}
|
|
@@ -2979,18 +3055,59 @@ function parseTraeCachedModel(value) {
|
|
|
2979
3055
|
};
|
|
2980
3056
|
}
|
|
2981
3057
|
/**
|
|
3058
|
+
* The cache paths to try, most-specific first.
|
|
3059
|
+
*
|
|
3060
|
+
* `state.vscdb` is a SIBLING of the credential document (both sit in
|
|
3061
|
+
* `globalStorage`), so a candidate's own path is the authority for its cache —
|
|
3062
|
+
* this is why the database is derived from {@link traeStorageCandidates} rather
|
|
3063
|
+
* than re-spelled here.
|
|
3064
|
+
*
|
|
3065
|
+
* It previously hardcoded the single macOS spelling `Trae CN`, which was wrong
|
|
3066
|
+
* in two ways on Windows: `paths.ts` probes `Trae CN` AND `trae-cn`, and the
|
|
3067
|
+
* edition actually installed is often SOLO (`TRAE SOLO CN`). This module would
|
|
3068
|
+
* then point at a directory that does not exist. That went unnoticed only
|
|
3069
|
+
* because the Windows `sqlite3` dependency is absent, so the lookup failed and
|
|
3070
|
+
* callers fell back — masking the wrong path behind a different error. Sharing
|
|
3071
|
+
* one path table means a spelling that works for sign-in also works for the
|
|
3072
|
+
* cache.
|
|
3073
|
+
*/
|
|
3074
|
+
function traeStateDatabaseCandidates(options = {}) {
|
|
3075
|
+
const platform = options.platform ?? process.platform;
|
|
3076
|
+
const home = options.home ?? homedir();
|
|
3077
|
+
const env = options.env ?? process.env;
|
|
3078
|
+
const paths = [];
|
|
3079
|
+
const push = (storagePath) => {
|
|
3080
|
+
const database = join(dirname(storagePath), TRAE_STATE_DB_FILENAME);
|
|
3081
|
+
if (!paths.some((existing) => existing.toLowerCase() === database.toLowerCase())) paths.push(database);
|
|
3082
|
+
};
|
|
3083
|
+
if (options.candidate !== void 0 && options.candidate.source === "desktop") push(options.candidate.path);
|
|
3084
|
+
for (const candidate of traeStorageCandidates(platform, home, env)) {
|
|
3085
|
+
if (candidate.source !== "desktop") continue;
|
|
3086
|
+
if (options.candidate !== void 0 && candidate.edition !== options.candidate.edition) continue;
|
|
3087
|
+
push(candidate.path);
|
|
3088
|
+
}
|
|
3089
|
+
return paths;
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
2982
3092
|
* Read Trae's own current user's model map via sqlite3 without exposing
|
|
2983
3093
|
* secrets. The sqlite3 command line is a macOS prerequisite; on Windows it is
|
|
2984
3094
|
* typically absent, so the call fails and callers fall back gracefully.
|
|
3095
|
+
*
|
|
3096
|
+
* Every candidate database is tried in order and the first that EXISTS is
|
|
3097
|
+
* queried. Existence is checked before spawning because a missing file and a
|
|
3098
|
+
* missing `sqlite3` binary are different problems: choosing the wrong directory
|
|
3099
|
+
* would otherwise surface as a confusing `sqlite3` failure, hiding a path bug
|
|
3100
|
+
* behind a dependency error.
|
|
2985
3101
|
*/
|
|
2986
3102
|
async function readTraeCachedModel(functionName, modelName, userId, options = {}) {
|
|
2987
|
-
const
|
|
2988
|
-
const
|
|
2989
|
-
const
|
|
2990
|
-
|
|
3103
|
+
const exists = options.exists ?? existsSync;
|
|
3104
|
+
const databases = traeStateDatabaseCandidates(options);
|
|
3105
|
+
const fallback = databases[0];
|
|
3106
|
+
if (fallback === void 0) throw new Error("trae: no desktop storage candidate to locate the model cache from");
|
|
3107
|
+
const database = databases.find((candidate) => exists(candidate)) ?? fallback;
|
|
2991
3108
|
const key = `${userId}_AI.agent.model.model_list_map`;
|
|
2992
3109
|
const sql = `select value from ItemTable where key=${JSON.stringify(key)} limit 1;`;
|
|
2993
|
-
const { stdout } = await execFileAsync("sqlite3", [
|
|
3110
|
+
const { stdout } = await (options.runSqlite ?? ((db, statement) => execFileAsync("sqlite3", [db, statement], { maxBuffer: 8388608 })))(database, sql);
|
|
2994
3111
|
const document = JSON.parse(stdout);
|
|
2995
3112
|
return parseTraeCachedModel((Array.isArray(document[functionName]) ? document[functionName] : []).find((item) => typeof item === "object" && item !== null && item.name === modelName));
|
|
2996
3113
|
}
|
|
@@ -3002,7 +3119,7 @@ async function resolveTraeRawRuntime(store, modelName) {
|
|
|
3002
3119
|
const candidate = store.candidates().find((item) => item.edition === credential.edition);
|
|
3003
3120
|
if (candidate === void 0) throw new Error(`no Trae ${credential.edition} storage candidate for Raw Chat identity`);
|
|
3004
3121
|
const identity = await readTraeIdentity(candidate);
|
|
3005
|
-
const cached = credential.edition === "cn" ? await readTraeCachedModel("solo_agent", modelName, credential.userId).catch(() => void 0) : void 0;
|
|
3122
|
+
const cached = credential.edition === "cn" ? await readTraeCachedModel("solo_agent", modelName, credential.userId, { candidate }).catch(() => void 0) : void 0;
|
|
3006
3123
|
return {
|
|
3007
3124
|
identity,
|
|
3008
3125
|
runtime: buildTraeRawChatRuntimeConfig(modelName, cached, {
|
|
@@ -3202,6 +3319,12 @@ var TraeUsageClient = class {
|
|
|
3202
3319
|
/**
|
|
3203
3320
|
* Daily check-in status. Read-only, and answered with or without the device
|
|
3204
3321
|
* header — the claim is what needs it.
|
|
3322
|
+
*
|
|
3323
|
+
* The read DOES carry the header whenever this installation has one, and
|
|
3324
|
+
* that is deliberate rather than incidental: `did_checked_in` is answered
|
|
3325
|
+
* per device, so omitting the header would make a device that already claimed
|
|
3326
|
+
* today look identical to one that never has (measured 2026-09-26, see
|
|
3327
|
+
* {@link TraeCheckinStatus.didCheckedIn}).
|
|
3205
3328
|
*/
|
|
3206
3329
|
async checkinStatus(signal) {
|
|
3207
3330
|
await this.requireCnRegion("check-in status");
|
|
@@ -3423,6 +3546,16 @@ function nextRegionEnabled(value, region, enabled) {
|
|
|
3423
3546
|
}
|
|
3424
3547
|
//#endregion
|
|
3425
3548
|
//#region src/web-status.ts
|
|
3549
|
+
/**
|
|
3550
|
+
* The upstream's business code for "该设备今日已参与签到" — this DEVICE already
|
|
3551
|
+
* used today's check-in (measured 2026-09-26 on a CN account whose claim was
|
|
3552
|
+
* refused with exactly this code after an account switch on the same machine).
|
|
3553
|
+
*
|
|
3554
|
+
* It arrives as HTTP 200 with a non-zero `code`, which is why it has to be
|
|
3555
|
+
* matched on the code rather than on the HTTP status: read as a transport
|
|
3556
|
+
* failure it looks like the plugin is broken, when the day is simply spent.
|
|
3557
|
+
*/
|
|
3558
|
+
const CHECKIN_DEVICE_ALREADY_CLAIMED = 9095;
|
|
3426
3559
|
/** Redact token-like content before it crosses to the browser. */
|
|
3427
3560
|
function safeMessage(error) {
|
|
3428
3561
|
return (error instanceof Error ? error.message : String(error)).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[redacted token]").replace(/(\b(?:code|token|refresh_token|access_token)=)[^&\s]+/giu, "$1[redacted]").slice(0, 500);
|
|
@@ -3619,11 +3752,22 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
3619
3752
|
/**
|
|
3620
3753
|
* Daily check-in claim — the ONLY route in this plugin that mutates
|
|
3621
3754
|
* upstream account state, so it is guarded more tightly than its siblings:
|
|
3622
|
-
* POST only, loopback origin only, and the status read runs FIRST so
|
|
3623
|
-
* already
|
|
3624
|
-
*
|
|
3625
|
-
*
|
|
3626
|
-
*
|
|
3755
|
+
* POST only, loopback origin only, and the status read runs FIRST so a day
|
|
3756
|
+
* the ACCOUNT has already been paid for never reaches the upstream claim.
|
|
3757
|
+
*
|
|
3758
|
+
* `alreadyCheckedIn` and `deviceCheckedIn` are deliberately separate
|
|
3759
|
+
* answers, because the two flags mean different things (see
|
|
3760
|
+
* {@link TraeWebCheckin}): `checkedIn` is the account's reward for today
|
|
3761
|
+
* existing, `didCheckedIn` is this MACHINE having spent its check-in —
|
|
3762
|
+
* possibly for a different account. Treating the latter as "claimed today"
|
|
3763
|
+
* was a shipped bug: after switching accounts the card said "claimed
|
|
3764
|
+
* today" and disabled the button, which is the right call for the wrong
|
|
3765
|
+
* reason and hides that the new account was never rewarded.
|
|
3766
|
+
*
|
|
3767
|
+
* The upstream is idempotent per Beijing day (verified 2026-09-24: a
|
|
3768
|
+
* repeat claim answers `code: 0` with the entitlement total unchanged), but
|
|
3769
|
+
* relying on that for correctness would put the guard in someone else's
|
|
3770
|
+
* hands.
|
|
3627
3771
|
*/
|
|
3628
3772
|
const disposeCheckin = ctx.webServer.register({
|
|
3629
3773
|
kind: "exact",
|
|
@@ -3638,16 +3782,27 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
3638
3782
|
const client = deps.client(region);
|
|
3639
3783
|
const current = await client.checkinStatus();
|
|
3640
3784
|
if (!current.enabled) return json(res, 409, { error: "check-in is not enabled for this account" });
|
|
3641
|
-
if (current.checkedIn
|
|
3785
|
+
if (current.checkedIn) return json(res, 200, {
|
|
3642
3786
|
claimed: false,
|
|
3643
3787
|
alreadyCheckedIn: true,
|
|
3788
|
+
deviceCheckedIn: current.didCheckedIn,
|
|
3789
|
+
checkin: toCheckin(current)
|
|
3790
|
+
});
|
|
3791
|
+
if (current.didCheckedIn) return json(res, 200, {
|
|
3792
|
+
claimed: false,
|
|
3793
|
+
alreadyCheckedIn: false,
|
|
3794
|
+
deviceCheckedIn: true,
|
|
3795
|
+
code: CHECKIN_DEVICE_ALREADY_CLAIMED,
|
|
3796
|
+
message: "this device already used today's check-in",
|
|
3644
3797
|
checkin: toCheckin(current)
|
|
3645
3798
|
});
|
|
3646
3799
|
const claim = await client.claimCheckin();
|
|
3647
3800
|
const checkin = await client.checkinStatus();
|
|
3801
|
+
const deviceClaimed = claim.code === CHECKIN_DEVICE_ALREADY_CLAIMED;
|
|
3648
3802
|
json(res, 200, {
|
|
3649
3803
|
claimed: claim.claimed,
|
|
3650
3804
|
alreadyCheckedIn: false,
|
|
3805
|
+
...deviceClaimed ? { deviceCheckedIn: true } : {},
|
|
3651
3806
|
code: claim.code,
|
|
3652
3807
|
message: claim.message,
|
|
3653
3808
|
checkin: toCheckin(checkin)
|
|
@@ -3764,6 +3919,64 @@ function buildTraeModelDetailRequest(configName = "", custom = false) {
|
|
|
3764
3919
|
};
|
|
3765
3920
|
}
|
|
3766
3921
|
//#endregion
|
|
3922
|
+
//#region src/redact.ts
|
|
3923
|
+
/**
|
|
3924
|
+
* Redaction helpers for diagnostics that a user may paste into a public issue.
|
|
3925
|
+
*
|
|
3926
|
+
* A verification report is only useful if it can be shared, and it is only safe
|
|
3927
|
+
* to share if it carries no personal identifiers. Paths are the hard case:
|
|
3928
|
+
* `C:\Users\<real name>\AppData\Roaming\Trae CN\...` contains both a real
|
|
3929
|
+
* account name AND the single most diagnostic token in the whole report (the
|
|
3930
|
+
* Trae data directory spelling). Redacting the whole path would make the report
|
|
3931
|
+
* worthless; printing it whole would leak the user's name. So the user segment
|
|
3932
|
+
* is replaced and the meaningful tail is preserved.
|
|
3933
|
+
*
|
|
3934
|
+
* @module dsh-connect-trae/redact
|
|
3935
|
+
*/
|
|
3936
|
+
/**
|
|
3937
|
+
* Replace the user segment of a path with `<user>`, keeping the rest intact.
|
|
3938
|
+
*
|
|
3939
|
+
* Handles the shapes a Trae install can produce:
|
|
3940
|
+
* - `C:\Users\<name>\...` and `C:\Documents and Settings\<name>\...` (Windows)
|
|
3941
|
+
* - `/Users/<name>/...` and `/home/<name>/...` (macOS / Linux)
|
|
3942
|
+
*
|
|
3943
|
+
* Anything after that segment — edition directory, `User\globalStorage`,
|
|
3944
|
+
* `storage.json` — is preserved verbatim, because that is what a report is
|
|
3945
|
+
* actually read for. Cross-platform on purpose: the function is called by
|
|
3946
|
+
* Windows-focused diagnostics, but the same helpers run in tests and on other
|
|
3947
|
+
* hosts, and a report from the wrong platform must not leak either.
|
|
3948
|
+
*/
|
|
3949
|
+
function maskUserPath(path) {
|
|
3950
|
+
return String(path).replace(/([A-Za-z]:\\Users\\)[^\\]+/i, "$1<user>").replace(/([A-Za-z]:\\Documents and Settings\\)[^\\]+/i, "$1<user>").replace(/^(\/(?:Users|home)\/)[^/]+/, "$1<user>");
|
|
3951
|
+
}
|
|
3952
|
+
/**
|
|
3953
|
+
* Describe a name by shape instead of by value: a character count, plus whether
|
|
3954
|
+
* it is purely numeric.
|
|
3955
|
+
*
|
|
3956
|
+
* This is the deliberate middle ground for account names. A Trae account name
|
|
3957
|
+
* is often a real name or a phone number, so it must never be printed; but the
|
|
3958
|
+
* report still needs to show that an account WAS resolved, and the shape
|
|
3959
|
+
* distinguishes a real nickname from a generated `用户<n>` id — which is the
|
|
3960
|
+
* only thing a reader needs to judge whether account discovery worked.
|
|
3961
|
+
*/
|
|
3962
|
+
function describeNameShape(name) {
|
|
3963
|
+
const text = String(name ?? "");
|
|
3964
|
+
return `${text.length} 字符${/^\d+$/.test(text) ? ",纯数字" : ""}`;
|
|
3965
|
+
}
|
|
3966
|
+
/**
|
|
3967
|
+
* Describe an identifier by shape instead of by value.
|
|
3968
|
+
*
|
|
3969
|
+
* A device id (`x-device-id`) is a stable installation identifier and is
|
|
3970
|
+
* directly tied to the upstream's per-device rules, so it is never printed.
|
|
3971
|
+
* Its shape still proves which resolution path ran: a real `icube-dc` id is
|
|
3972
|
+
* 15 digits, while the fallback is a 32-character hex hash.
|
|
3973
|
+
*/
|
|
3974
|
+
function describeIdShape(value) {
|
|
3975
|
+
const text = String(value ?? "");
|
|
3976
|
+
if (text === "") return "(空)";
|
|
3977
|
+
return `${text.length} 位${/^\d+$/.test(text) ? ",纯数字" : ",含非数字"}`;
|
|
3978
|
+
}
|
|
3979
|
+
//#endregion
|
|
3767
3980
|
//#region src/raw-envelope.ts
|
|
3768
3981
|
/** Stable JSON hash candidate used by the native DTO's arg/args_hash fields. */
|
|
3769
3982
|
function hashTraeRawChatArg(arg) {
|
|
@@ -3886,7 +4099,8 @@ const modelConfig = z.object({
|
|
|
3886
4099
|
maxTokens: z.number().step(1).min(1),
|
|
3887
4100
|
input: z.array(z.union(["text", "image"])),
|
|
3888
4101
|
creditMultiplier: z.number(),
|
|
3889
|
-
wireFunction: z.string()
|
|
4102
|
+
wireFunction: z.string(),
|
|
4103
|
+
multimodal: z.boolean()
|
|
3890
4104
|
});
|
|
3891
4105
|
const regionStateConfig = z.object({
|
|
3892
4106
|
enabled: z.boolean().default(true).description("Whether this region's provider is offered to DSH (opt-out; false withdraws it entirely)"),
|
|
@@ -4273,4 +4487,4 @@ function apply(ctx, config) {
|
|
|
4273
4487
|
});
|
|
4274
4488
|
}
|
|
4275
4489
|
//#endregion
|
|
4276
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
4490
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "2.
|
|
4
|
+
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
+
"version": "2.4.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"lib",
|
|
35
35
|
"icons",
|
|
36
36
|
"docs/assets/dsh-connect-trae-usage-card.png",
|
|
37
|
+
"scripts/verify-windows.mjs",
|
|
37
38
|
"screenshots.json",
|
|
38
39
|
"cordis.patch.yml",
|
|
39
40
|
"README.md",
|
|
@@ -75,42 +76,44 @@
|
|
|
75
76
|
},
|
|
76
77
|
"peerDependencies": {
|
|
77
78
|
"@deepseek-ai/cordis": ">=4.0.4 <5.0.0",
|
|
78
|
-
"@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.
|
|
79
|
-
"@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.
|
|
80
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-rc.1 <0.
|
|
81
|
-
"@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.
|
|
82
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.
|
|
83
|
-
"@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.
|
|
84
|
-
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.
|
|
85
|
-
"@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.
|
|
79
|
+
"@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.3.0-0",
|
|
80
|
+
"@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.3.0-0",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-rc.1 <0.3.0-0",
|
|
82
|
+
"@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.3.0-0",
|
|
83
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.3.0-0",
|
|
84
|
+
"@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.3.0-0",
|
|
85
|
+
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
|
|
86
|
+
"@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
|
|
86
87
|
"@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
|
|
87
88
|
"@earendil-works/pi-ai": ">=0.85.1",
|
|
88
89
|
"react": "^18.2.0"
|
|
89
90
|
},
|
|
90
91
|
"devDependencies": {
|
|
91
92
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
92
|
-
"@deepseek-ai/dsh-atomic-write": "0.
|
|
93
|
-
"@deepseek-ai/dsh-attachment": "0.
|
|
94
|
-
"@deepseek-ai/dsh-client-locale": "0.
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.
|
|
97
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.
|
|
98
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.
|
|
99
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.
|
|
100
|
-
"@deepseek-ai/dsh-home-paths": "0.
|
|
101
|
-
"@deepseek-ai/dsh-host-webserver": "0.
|
|
102
|
-
"@deepseek-ai/dsh-llm": "0.
|
|
103
|
-
"@deepseek-ai/dsh-llm-pi-ai": "0.
|
|
104
|
-
"@deepseek-ai/dsh-settings": "0.
|
|
93
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.1",
|
|
94
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.0-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.1",
|
|
102
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.1",
|
|
105
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.1",
|
|
105
106
|
"@deepseek-ai/schemastery": "3.18.4",
|
|
106
107
|
"@earendil-works/pi-ai": "^0.85.1",
|
|
107
108
|
"@testing-library/dom": "^10.4.2",
|
|
108
109
|
"@testing-library/react": "^16.3.3",
|
|
109
110
|
"@types/node": "^22.20.0",
|
|
110
111
|
"@types/react": "~18.3.31",
|
|
112
|
+
"@types/semver": "^7.7.0",
|
|
111
113
|
"jsdom": "^25.0.1",
|
|
112
114
|
"react": "^18.3.1",
|
|
113
115
|
"react-dom": "18.3.1",
|
|
116
|
+
"semver": "^7.7.2",
|
|
114
117
|
"tsdown": "^0.22.2",
|
|
115
118
|
"typescript": "^5.9.3",
|
|
116
119
|
"vitest": "^4.1.8"
|