dsh-connect-trae 1.4.1 → 2.0.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 +45 -5
- package/README.en.md +15 -9
- package/README.md +15 -9
- package/lib/client.js +268 -76
- package/lib/index.d.ts +345 -51
- package/lib/index.js +781 -234
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createDecipheriv, createHash, randomBytes, randomUUID, timingSafeEqual
|
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
11
11
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
12
|
-
import { cpus, homedir, release } from "node:os";
|
|
12
|
+
import { cpus, homedir, hostname, release } from "node:os";
|
|
13
13
|
import { createServer } from "node:http";
|
|
14
14
|
import { Readable } from "node:stream";
|
|
15
15
|
import { execFile } from "node:child_process";
|
|
@@ -38,6 +38,52 @@ const FALLBACK_TRAE_MODELS = [
|
|
|
38
38
|
name: "Kimi-K2.6"
|
|
39
39
|
}
|
|
40
40
|
];
|
|
41
|
+
/**
|
|
42
|
+
* Bootstrap catalog for the international (ai) region, captured from the
|
|
43
|
+
* live `coresg-normal.trae.ai/api/remote/v1/models` directory on 2026-09-15
|
|
44
|
+
* (docs/INTL_SG_EVIDENCE.md §3). The two rosters barely overlap (the CN list
|
|
45
|
+
* has no Gemini/GPT/MiniMax entries), so an international account must never
|
|
46
|
+
* be seeded with the CN list. Like the CN fallback it is replaced by the live
|
|
47
|
+
* refresh; image input stays the user's explicit opt-in (`imageModelIds`).
|
|
48
|
+
*/
|
|
49
|
+
const FALLBACK_TRAE_MODELS_AI = [
|
|
50
|
+
{
|
|
51
|
+
id: "gemini-3.1-pro",
|
|
52
|
+
name: "Gemini-3.1-Pro-Preview"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "gemini-3-flash-solo",
|
|
56
|
+
name: "Gemini-3-Flash-Preview"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "minimax-m3",
|
|
60
|
+
name: "MiniMax-M3"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "minimax-m2.7",
|
|
64
|
+
name: "MiniMax-M2.7"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "kimi-k2.5",
|
|
68
|
+
name: "Kimi-K2.5"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "gpt-5.4",
|
|
72
|
+
name: "GPT-5.4"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "gpt-5.2",
|
|
76
|
+
name: "GPT-5.2"
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Static fallback directory for a region. Each region keeps its own model
|
|
81
|
+
* slot in settings; the fallback must match the region so an account never
|
|
82
|
+
* shows the other region's roster.
|
|
83
|
+
*/
|
|
84
|
+
function fallbackModelsFor(region) {
|
|
85
|
+
return region === "ai" ? FALLBACK_TRAE_MODELS_AI : FALLBACK_TRAE_MODELS;
|
|
86
|
+
}
|
|
41
87
|
/** Exact DSH modalities for one catalog entry; absent metadata is text-only. */
|
|
42
88
|
function traeInputModalities(model) {
|
|
43
89
|
return [...model.input ?? ["text"]];
|
|
@@ -167,7 +213,15 @@ function deriveCatalog(catalog, enabled, budgets = {}) {
|
|
|
167
213
|
return applyContextBudgets(enabled.size === 0 ? catalog : catalog.filter((model) => enabled.has(model.id)), budgets);
|
|
168
214
|
}
|
|
169
215
|
var TraeCatalog = class {
|
|
170
|
-
models
|
|
216
|
+
models;
|
|
217
|
+
/**
|
|
218
|
+
* @param region Seeds the static fallback for this region; each region's
|
|
219
|
+
* provider must never serve the other region's roster before its first live
|
|
220
|
+
* refresh lands.
|
|
221
|
+
*/
|
|
222
|
+
constructor(region = "cn") {
|
|
223
|
+
this.models = fallbackModelsFor(region);
|
|
224
|
+
}
|
|
171
225
|
current() {
|
|
172
226
|
return this.models;
|
|
173
227
|
}
|
|
@@ -181,7 +235,28 @@ var TraeCatalog = class {
|
|
|
181
235
|
};
|
|
182
236
|
//#endregion
|
|
183
237
|
//#region src/adapter.ts
|
|
238
|
+
/** Provider route this bundle owns for the domestic (CN) gateway. */
|
|
184
239
|
const TRAE_PROVIDER = "trae";
|
|
240
|
+
/**
|
|
241
|
+
* Provider route this bundle owns for the international (trae.ai) gateway.
|
|
242
|
+
* Named `-global` to match the plugin family's convention (`workbuddy-global`);
|
|
243
|
+
* the internal region bucket stays `ai`, which is the gateway/protocol name.
|
|
244
|
+
*/
|
|
245
|
+
const TRAE_AI_PROVIDER = "trae-global";
|
|
246
|
+
/** The provider id each region registers as. */
|
|
247
|
+
const TRAE_PROVIDERS = {
|
|
248
|
+
cn: TRAE_PROVIDER,
|
|
249
|
+
ai: TRAE_AI_PROVIDER
|
|
250
|
+
};
|
|
251
|
+
/** Human-readable provider names, shown in the DSH model picker. */
|
|
252
|
+
const TRAE_PROVIDER_DISPLAY_NAMES = {
|
|
253
|
+
cn: "Trae",
|
|
254
|
+
ai: "Trae Global"
|
|
255
|
+
};
|
|
256
|
+
/** Region a provider route id belongs to. */
|
|
257
|
+
function regionOfTraeProvider(provider) {
|
|
258
|
+
for (const [region, id] of Object.entries(TRAE_PROVIDERS)) if (id === provider) return region;
|
|
259
|
+
}
|
|
185
260
|
const TRAE_STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
186
261
|
const INERT_AUTH = {
|
|
187
262
|
credentials: {
|
|
@@ -206,12 +281,12 @@ const REQUEST_IMAGE_BUDGETS = {
|
|
|
206
281
|
requestImagePixelBudget: 4194304,
|
|
207
282
|
requestImageMaxBytes: 1048576
|
|
208
283
|
};
|
|
209
|
-
function toPiModel(info, baseUrl) {
|
|
284
|
+
function toPiModel(info, baseUrl, providerId) {
|
|
210
285
|
return {
|
|
211
286
|
id: info.id,
|
|
212
287
|
name: traeModelDisplayName(info),
|
|
213
288
|
api: "openai-completions",
|
|
214
|
-
provider:
|
|
289
|
+
provider: providerId,
|
|
215
290
|
baseUrl,
|
|
216
291
|
input: traeInputModalities(info),
|
|
217
292
|
cost: {
|
|
@@ -236,11 +311,13 @@ function toPiModel(info, baseUrl) {
|
|
|
236
311
|
};
|
|
237
312
|
}
|
|
238
313
|
function createTraeAdapter(options) {
|
|
239
|
-
const
|
|
314
|
+
const providerId = options.provider ?? "trae";
|
|
315
|
+
const providerName = options.displayName ?? "Trae";
|
|
316
|
+
const buildModels = () => options.catalog.current().map((info) => toPiModel(info, `${options.shim.baseUrl()}/v1`, providerId));
|
|
240
317
|
const provider = {
|
|
241
318
|
...createProvider({
|
|
242
|
-
id:
|
|
243
|
-
name:
|
|
319
|
+
id: providerId,
|
|
320
|
+
name: providerName,
|
|
244
321
|
auth: { apiKey: {
|
|
245
322
|
name: "Trae loopback secret",
|
|
246
323
|
async resolve({ credential }) {
|
|
@@ -257,8 +334,8 @@ function createTraeAdapter(options) {
|
|
|
257
334
|
getModels: () => buildModels()
|
|
258
335
|
};
|
|
259
336
|
const profile = {
|
|
260
|
-
provider:
|
|
261
|
-
displayName:
|
|
337
|
+
provider: providerId,
|
|
338
|
+
displayName: providerName,
|
|
262
339
|
streamIdleTimeoutMs: TRAE_STREAM_IDLE_TIMEOUT_MS,
|
|
263
340
|
retryPolicy: resolveRetryPolicy(void 0, "dsh-connect-trae retryPolicy"),
|
|
264
341
|
configuredMaxTokens: /* @__PURE__ */ new Map(),
|
|
@@ -266,7 +343,7 @@ function createTraeAdapter(options) {
|
|
|
266
343
|
...REQUEST_IMAGE_BUDGETS,
|
|
267
344
|
piProvider: provider
|
|
268
345
|
};
|
|
269
|
-
let profiles = /* @__PURE__ */ new Map([[
|
|
346
|
+
let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
|
|
270
347
|
return {
|
|
271
348
|
adapter: new PiAiAdapter({
|
|
272
349
|
profiles: () => profiles,
|
|
@@ -275,7 +352,7 @@ function createTraeAdapter(options) {
|
|
|
275
352
|
...options.resolveAttachments === void 0 ? {} : { resolveAttachments: options.resolveAttachments }
|
|
276
353
|
}),
|
|
277
354
|
invalidate() {
|
|
278
|
-
profiles = /* @__PURE__ */ new Map([[
|
|
355
|
+
profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
|
|
279
356
|
}
|
|
280
357
|
};
|
|
281
358
|
}
|
|
@@ -734,12 +811,86 @@ function traeCliCandidates(platform = process.platform, home = homedir(), env =
|
|
|
734
811
|
return result;
|
|
735
812
|
}
|
|
736
813
|
//#endregion
|
|
814
|
+
//#region src/region.ts
|
|
815
|
+
/**
|
|
816
|
+
* Verified gateway bases per region (docs/INTL_SG_EVIDENCE.md §2).
|
|
817
|
+
* The AI gateway is shared by both international installs (Trae desktop and
|
|
818
|
+
* TRAE SOLO); its mchost shards (`api16/api22-normal-alisg.mchost.guru`)
|
|
819
|
+
* answer the same bytes but stay internal — `coresg-normal.trae.ai` is the
|
|
820
|
+
* single stable entry point.
|
|
821
|
+
*/
|
|
822
|
+
const REGION_GATEWAYS = {
|
|
823
|
+
cn: {
|
|
824
|
+
chat: "https://trae-api-cn.mchost.guru",
|
|
825
|
+
remote: "https://solo.trae.cn/api/remote/v1",
|
|
826
|
+
pay: "https://api.trae.cn"
|
|
827
|
+
},
|
|
828
|
+
ai: {
|
|
829
|
+
chat: "https://coresg-normal.trae.ai",
|
|
830
|
+
remote: "https://coresg-normal.trae.ai/api/remote/v1",
|
|
831
|
+
pay: "https://growsg-normal.trae.ai"
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
/** Region for an edition label: the international installs belong to `ai`. */
|
|
835
|
+
function regionOfEdition(edition) {
|
|
836
|
+
return edition === "sg" || edition === "solo-sg" ? "ai" : "cn";
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Region from the credential's `userRegion` claim. The desktop storage spells
|
|
840
|
+
* it as an object (`{"region":"CN","_aiRegion":"CN"}`); the app logs also
|
|
841
|
+
* spell the bare value lowercase (`"sg"`). Both are accepted, case-blind.
|
|
842
|
+
*/
|
|
843
|
+
function regionOfUserRegion(value) {
|
|
844
|
+
const raw = typeof value === "object" && value !== null && !Array.isArray(value) ? value["region"] : value;
|
|
845
|
+
if (typeof raw !== "string") return void 0;
|
|
846
|
+
const lowered = raw.trim().toLowerCase();
|
|
847
|
+
if (lowered === "cn") return "cn";
|
|
848
|
+
if (lowered === "sg" || lowered === "ai") return "ai";
|
|
849
|
+
}
|
|
850
|
+
/** Region from a credential host (`.trae.ai` → ai, `.trae.cn`/`.trae.com.cn` → cn). */
|
|
851
|
+
function regionOfHost(host) {
|
|
852
|
+
if (host === void 0) return void 0;
|
|
853
|
+
const trimmed = host.trim();
|
|
854
|
+
if (trimmed === "") return void 0;
|
|
855
|
+
let hostname;
|
|
856
|
+
try {
|
|
857
|
+
hostname = new URL(trimmed.startsWith("http") ? trimmed : `https://${trimmed}`).hostname;
|
|
858
|
+
} catch {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (hostname === "trae.ai" || hostname.endsWith(".trae.ai")) return "ai";
|
|
862
|
+
if (hostname === "trae.cn" || hostname.endsWith(".trae.cn") || hostname.endsWith(".trae.com.cn")) return "cn";
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Region of a credential: the `userRegion` claim wins, the host suffix is the
|
|
866
|
+
* fallback, and the edition label is the last resort. Every level is derived
|
|
867
|
+
* from data the credential itself carries, so no user configuration is needed.
|
|
868
|
+
*/
|
|
869
|
+
function regionOfCredential(credential) {
|
|
870
|
+
return regionOfUserRegion(credential.userRegion) ?? regionOfHost(credential.host) ?? regionOfEdition(credential.edition);
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
737
873
|
//#region src/auth.ts
|
|
738
874
|
/** Host used for CLI tokens, which carry no host claim of their own. */
|
|
739
875
|
const CLI_DEFAULT_HOST = "https://api.trae.cn";
|
|
740
876
|
const OWN_VERSION = 1;
|
|
741
877
|
const TRAE_AUTH_FILENAME = ".trae-auth.json";
|
|
742
|
-
|
|
878
|
+
/** Prefix of the plugin-owned per-region credential copies. */
|
|
879
|
+
const TRAE_OWN_PREFIX = ".trae-auth";
|
|
880
|
+
/**
|
|
881
|
+
* Plugin-owned copy path for one region. Each region's store refreshes into
|
|
882
|
+
* its own file so two simultaneously signed-in regions never overwrite each
|
|
883
|
+
* other's refreshed token.
|
|
884
|
+
*/
|
|
885
|
+
function traeOwnAuthPath(region) {
|
|
886
|
+
return join(resolveDshHome(), `${TRAE_OWN_PREFIX}.${region}.json`);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Pre-dual-provider single-copy path. Still read as a migration source (a
|
|
890
|
+
* legacy credential serves the region it belongs to until that region's own
|
|
891
|
+
* first refresh writes the per-region file), and removed by `logout`.
|
|
892
|
+
*/
|
|
893
|
+
function legacyTraeOwnAuthPath() {
|
|
743
894
|
return join(resolveDshHome(), TRAE_AUTH_FILENAME);
|
|
744
895
|
}
|
|
745
896
|
function optionalString(value) {
|
|
@@ -753,6 +904,11 @@ function timeToMs(value) {
|
|
|
753
904
|
const parsed = Date.parse(value);
|
|
754
905
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
755
906
|
}
|
|
907
|
+
/** Extract the `userRegion.region` string from either on-disk shape. */
|
|
908
|
+
function userRegionOf(value) {
|
|
909
|
+
const raw = typeof value === "object" && value !== null && !Array.isArray(value) ? value["region"] : value;
|
|
910
|
+
return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : void 0;
|
|
911
|
+
}
|
|
756
912
|
function normalizeTraeCredential(raw, edition, source) {
|
|
757
913
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
758
914
|
const value = raw;
|
|
@@ -761,6 +917,7 @@ function normalizeTraeCredential(raw, edition, source) {
|
|
|
761
917
|
const expiresAtMs = timeToMs(value["expiredAt"] ?? value["expiresAt"]) ?? 0;
|
|
762
918
|
const refreshExpiresAtMs = timeToMs(value["refreshExpiredAt"] ?? value["refreshExpiresAt"]);
|
|
763
919
|
const refreshToken = optionalString(value["refreshToken"]);
|
|
920
|
+
const userRegion = userRegionOf(value["userRegion"]);
|
|
764
921
|
const accountName = optionalString((typeof value["account"] === "object" && value["account"] !== null && !Array.isArray(value["account"]) ? value["account"] : void 0)?.["username"]);
|
|
765
922
|
return {
|
|
766
923
|
accessToken,
|
|
@@ -768,6 +925,7 @@ function normalizeTraeCredential(raw, edition, source) {
|
|
|
768
925
|
userId: optionalString(value["userId"]) ?? "",
|
|
769
926
|
...accountName === void 0 ? {} : { accountName },
|
|
770
927
|
host: optionalString(value["host"]) ?? "",
|
|
928
|
+
...userRegion === void 0 ? {} : { userRegion },
|
|
771
929
|
expiresAtMs,
|
|
772
930
|
...refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs },
|
|
773
931
|
edition,
|
|
@@ -790,6 +948,7 @@ function parseOwn(text) {
|
|
|
790
948
|
refreshToken: stored["refreshToken"],
|
|
791
949
|
userId: stored["userId"],
|
|
792
950
|
host: stored["host"],
|
|
951
|
+
userRegion: stored["userRegion"],
|
|
793
952
|
account: stored["accountName"] === void 0 ? void 0 : { username: stored["accountName"] },
|
|
794
953
|
expiredAt: stored["expiresAtMs"],
|
|
795
954
|
refreshExpiredAt: stored["refreshExpiresAtMs"]
|
|
@@ -802,7 +961,10 @@ var TraeCredentialStore = class {
|
|
|
802
961
|
storagePathOverride;
|
|
803
962
|
edition;
|
|
804
963
|
accountId;
|
|
805
|
-
|
|
964
|
+
region;
|
|
965
|
+
ownPathExplicit;
|
|
966
|
+
legacyOwnPath;
|
|
967
|
+
legacyOwnPathExplicit;
|
|
806
968
|
refresh;
|
|
807
969
|
refreshMarginMs;
|
|
808
970
|
inflight;
|
|
@@ -810,10 +972,45 @@ var TraeCredentialStore = class {
|
|
|
810
972
|
this.storagePathOverride = options.storagePath;
|
|
811
973
|
this.edition = options.edition ?? "auto";
|
|
812
974
|
this.accountId = options.accountId;
|
|
813
|
-
this.
|
|
975
|
+
this.region = options.region;
|
|
976
|
+
this.ownPathExplicit = options.ownPath;
|
|
977
|
+
this.legacyOwnPath = options.legacyOwnPath ?? legacyTraeOwnAuthPath();
|
|
978
|
+
this.legacyOwnPathExplicit = options.legacyOwnPath;
|
|
814
979
|
this.refresh = options.refresh;
|
|
815
980
|
this.refreshMarginMs = options.refreshMarginMs ?? 3e5;
|
|
816
981
|
}
|
|
982
|
+
/** Whether a credential's own claim belongs to this store's region. */
|
|
983
|
+
matchesRegion(credential) {
|
|
984
|
+
return this.region === void 0 || regionOfCredential(credential) === this.region;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* The path this store refreshes into: the per-region file for a
|
|
988
|
+
* region-scoped store, the legacy single file otherwise, or an explicitly
|
|
989
|
+
* injected path in tests.
|
|
990
|
+
*/
|
|
991
|
+
ownAuthPath() {
|
|
992
|
+
if (this.ownPathExplicit !== void 0) return this.ownPathExplicit;
|
|
993
|
+
return this.region !== void 0 ? traeOwnAuthPath(this.region) : this.legacyOwnPath;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Every plugin-owned copy to read, most preferred first. A region-scoped
|
|
997
|
+
* store reads the legacy single copy as its migration source (readAll's
|
|
998
|
+
* region filter drops it when it carries the other region's credential); an
|
|
999
|
+
* unscoped store reads everything so diagnostics see both regions.
|
|
1000
|
+
*
|
|
1001
|
+
* With an explicitly injected own path the legacy source is read ONLY when
|
|
1002
|
+
* it was injected too — a test that pins one file must not accidentally see
|
|
1003
|
+
* the real machine's legacy copy.
|
|
1004
|
+
*/
|
|
1005
|
+
ownCandidates() {
|
|
1006
|
+
if (this.ownPathExplicit !== void 0) return this.legacyOwnPathExplicit !== void 0 ? [this.ownPathExplicit, this.legacyOwnPathExplicit] : [this.ownPathExplicit];
|
|
1007
|
+
if (this.region !== void 0) return [traeOwnAuthPath(this.region), this.legacyOwnPath];
|
|
1008
|
+
return [
|
|
1009
|
+
this.legacyOwnPath,
|
|
1010
|
+
traeOwnAuthPath("cn"),
|
|
1011
|
+
traeOwnAuthPath("ai")
|
|
1012
|
+
];
|
|
1013
|
+
}
|
|
817
1014
|
setSource(storagePath, edition = "auto", accountId) {
|
|
818
1015
|
this.storagePathOverride = storagePath;
|
|
819
1016
|
this.edition = edition;
|
|
@@ -838,7 +1035,8 @@ var TraeCredentialStore = class {
|
|
|
838
1035
|
}];
|
|
839
1036
|
}
|
|
840
1037
|
const all = traeStorageCandidates();
|
|
841
|
-
|
|
1038
|
+
const cliEdition = "cn";
|
|
1039
|
+
return this.edition === "auto" ? all.filter((candidate) => candidate.source === "desktop" || candidate.edition === cliEdition) : all.filter((candidate) => candidate.edition === this.edition && (candidate.source === "desktop" || candidate.edition === cliEdition));
|
|
842
1040
|
}
|
|
843
1041
|
/**
|
|
844
1042
|
* Deterministic default when no account is explicitly selected: the first
|
|
@@ -857,6 +1055,7 @@ var TraeCredentialStore = class {
|
|
|
857
1055
|
id: traeAccountId(credential),
|
|
858
1056
|
accountName: credential.accountName ?? (credential.userId || `${credential.edition} account`),
|
|
859
1057
|
edition: credential.edition,
|
|
1058
|
+
region: regionOfCredential(credential),
|
|
860
1059
|
source: credential.source,
|
|
861
1060
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
862
1061
|
selected: selectedExists ? traeAccountId(credential) === this.accountId : credential === defaultSelected
|
|
@@ -895,16 +1094,34 @@ var TraeCredentialStore = class {
|
|
|
895
1094
|
} catch {}
|
|
896
1095
|
return false;
|
|
897
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Remove every plugin-owned copy this store could read (per-region file,
|
|
1099
|
+
* legacy single file, and their lock siblings); the desktop storage files
|
|
1100
|
+
* are untouched. A region store's logout therefore also clears the legacy
|
|
1101
|
+
* migration source — deliberate: `logout` is the user's "forget what the
|
|
1102
|
+
* plugin stored" action, not a per-account toggle.
|
|
1103
|
+
*/
|
|
898
1104
|
async logout() {
|
|
899
|
-
|
|
900
|
-
|
|
1105
|
+
for (const path of this.ownCandidates()) {
|
|
1106
|
+
await rm(path, { force: true });
|
|
1107
|
+
await rm(`${path}.lock`, { force: true });
|
|
1108
|
+
}
|
|
901
1109
|
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Every local credential of this store's region, deduplicated by account id.
|
|
1112
|
+
* A region-scoped store sees only its own region's credentials: the other
|
|
1113
|
+
* region's accounts are invisible to selection, refresh, and status alike,
|
|
1114
|
+
* which is what keeps the two regions' providers from cross-billing.
|
|
1115
|
+
*/
|
|
902
1116
|
async readAll() {
|
|
903
1117
|
const { credentials: desktop } = await this.readDesktopAll();
|
|
904
|
-
const
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
1118
|
+
const credentials = [...desktop.filter((credential) => this.matchesRegion(credential))];
|
|
1119
|
+
for (const own of await this.readOwns()) {
|
|
1120
|
+
if (!this.matchesRegion(own)) continue;
|
|
1121
|
+
if (credentials.some((credential) => traeAccountId(credential) === traeAccountId(own))) continue;
|
|
1122
|
+
credentials.push(own);
|
|
1123
|
+
}
|
|
1124
|
+
return credentials;
|
|
908
1125
|
}
|
|
909
1126
|
/**
|
|
910
1127
|
* Which paths were tried and why each one failed. Read-only and token-free:
|
|
@@ -949,13 +1166,14 @@ var TraeCredentialStore = class {
|
|
|
949
1166
|
credentialFrom(candidate, text) {
|
|
950
1167
|
let credential;
|
|
951
1168
|
if (candidate.source === "cli") {
|
|
1169
|
+
if (regionOfEdition(candidate.edition) !== "cn") throw new Error(`Trae CLI tokens are only verified for the CN region; ${candidate.edition} CLI homes are not supported yet`);
|
|
952
1170
|
const claims = parseTraeCliToken(text);
|
|
953
1171
|
credential = normalizeTraeCredential({
|
|
954
1172
|
token: claims.accessToken,
|
|
955
1173
|
userId: claims.userId,
|
|
956
1174
|
host: CLI_DEFAULT_HOST,
|
|
957
1175
|
expiredAt: claims.expiresAtMs
|
|
958
|
-
}, candidate.edition
|
|
1176
|
+
}, candidate.edition, "cli");
|
|
959
1177
|
} else credential = normalizeTraeCredential(parseTraeStorageDocument(text), candidate.edition, "desktop");
|
|
960
1178
|
if (credential === void 0) throw new Error(`${candidate.source} candidate could not be normalized into a credential`);
|
|
961
1179
|
return credential;
|
|
@@ -982,12 +1200,17 @@ var TraeCredentialStore = class {
|
|
|
982
1200
|
failures
|
|
983
1201
|
};
|
|
984
1202
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1203
|
+
/**
|
|
1204
|
+
* Every readable plugin-owned copy, in candidate order; absent or corrupt
|
|
1205
|
+
* files are skipped rather than propagated.
|
|
1206
|
+
*/
|
|
1207
|
+
async readOwns() {
|
|
1208
|
+
const copies = [];
|
|
1209
|
+
for (const path of this.ownCandidates()) try {
|
|
1210
|
+
const parsed = parseOwn(await readFile(path, "utf8"));
|
|
1211
|
+
if (parsed !== void 0) copies.push(parsed);
|
|
1212
|
+
} catch {}
|
|
1213
|
+
return copies;
|
|
991
1214
|
}
|
|
992
1215
|
async refreshNow(credential) {
|
|
993
1216
|
if (credential.refreshToken === void 0 || credential.refreshExpiresAtMs !== void 0 && credential.refreshExpiresAtMs <= Date.now()) {
|
|
@@ -1005,8 +1228,9 @@ var TraeCredentialStore = class {
|
|
|
1005
1228
|
...outcome.host === void 0 ? {} : { host: outcome.host },
|
|
1006
1229
|
source: "dsh"
|
|
1007
1230
|
};
|
|
1008
|
-
|
|
1009
|
-
|
|
1231
|
+
const ownPath = this.ownAuthPath();
|
|
1232
|
+
await withFileLock(ownPath, async () => {
|
|
1233
|
+
await writeFileAtomic(ownPath, `${JSON.stringify({
|
|
1010
1234
|
version: OWN_VERSION,
|
|
1011
1235
|
credential: refreshed
|
|
1012
1236
|
}, null, 2)}\n`, {
|
|
@@ -1023,29 +1247,60 @@ var TraeCredentialStore = class {
|
|
|
1023
1247
|
};
|
|
1024
1248
|
//#endregion
|
|
1025
1249
|
//#region src/refresh.ts
|
|
1026
|
-
const
|
|
1027
|
-
cn:
|
|
1028
|
-
|
|
1250
|
+
const REFRESH_CONTRACT = {
|
|
1251
|
+
cn: {
|
|
1252
|
+
path: "/cloudide/api/v3/trae/oauth/ExchangeToken",
|
|
1253
|
+
clientId: "ono9krqynydwx5",
|
|
1254
|
+
deviceInfo: false
|
|
1255
|
+
},
|
|
1256
|
+
sg: {
|
|
1257
|
+
path: "/cloudide/api/v3/trae/oauth/ExchangeToken",
|
|
1258
|
+
clientId: "ono9krqynydwx5",
|
|
1259
|
+
deviceInfo: false
|
|
1260
|
+
},
|
|
1261
|
+
solo: {
|
|
1262
|
+
path: "/cloudide/api/v3/trae/oauth/ExchangeToken",
|
|
1263
|
+
clientId: "ono9krqynydwx5",
|
|
1264
|
+
deviceInfo: false
|
|
1265
|
+
},
|
|
1266
|
+
"solo-sg": {
|
|
1267
|
+
path: "/trae/api/v3/oauth/ExchangeToken",
|
|
1268
|
+
clientId: "en1oxy7wnw8j9n",
|
|
1269
|
+
deviceInfo: true
|
|
1270
|
+
}
|
|
1029
1271
|
};
|
|
1030
1272
|
function normalizeHost(host) {
|
|
1031
1273
|
const value = host.trim();
|
|
1032
1274
|
if (value === "") throw new Error("Trae refresh host is missing");
|
|
1033
1275
|
return value.replace(/\/$/, "");
|
|
1034
1276
|
}
|
|
1035
|
-
/**
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* Exchange a refresh token for a fresh access token, following the calling
|
|
1279
|
+
* edition's verified contract. `device` is only used by editions whose
|
|
1280
|
+
* official client sends a DeviceInfo body; when it cannot be resolved the
|
|
1281
|
+
* field is omitted rather than sent empty.
|
|
1282
|
+
*/
|
|
1283
|
+
async function refreshTraeCredential(credential, signal, device) {
|
|
1284
|
+
const contract = REFRESH_CONTRACT[credential.edition];
|
|
1285
|
+
if (contract === void 0) throw new Error(`Trae ${credential.edition} refresh contract is not verified`);
|
|
1039
1286
|
if (credential.refreshToken === void 0) throw new Error("Trae refresh token is missing");
|
|
1040
|
-
const
|
|
1287
|
+
const body = {
|
|
1288
|
+
ClientID: contract.clientId,
|
|
1289
|
+
ClientSecret: "-",
|
|
1290
|
+
RefreshToken: credential.refreshToken,
|
|
1291
|
+
UserID: credential.userId
|
|
1292
|
+
};
|
|
1293
|
+
if (contract.deviceInfo && device !== void 0) body["DeviceInfo"] = {
|
|
1294
|
+
DeviceID: device.deviceId,
|
|
1295
|
+
MachineID: device.machineId,
|
|
1296
|
+
PlatformCode: credential.edition === "solo-sg" ? "SOLO_PC" : "TRAE",
|
|
1297
|
+
DeviceType: "PC",
|
|
1298
|
+
DeviceName: hostname()
|
|
1299
|
+
};
|
|
1300
|
+
const response = await fetch(`${normalizeHost(credential.host)}${contract.path}`, {
|
|
1041
1301
|
method: "POST",
|
|
1042
1302
|
headers: { "Content-Type": "application/json" },
|
|
1043
|
-
body: JSON.stringify(
|
|
1044
|
-
ClientID: clientId,
|
|
1045
|
-
RefreshToken: credential.refreshToken,
|
|
1046
|
-
ClientSecret: "-",
|
|
1047
|
-
UserID: credential.userId
|
|
1048
|
-
}),
|
|
1303
|
+
body: JSON.stringify(body),
|
|
1049
1304
|
signal: signal ?? AbortSignal.timeout(3e4)
|
|
1050
1305
|
});
|
|
1051
1306
|
if (!response.ok) throw new Error(`Trae token refresh failed (http ${response.status})`);
|
|
@@ -1087,7 +1342,12 @@ async function readTraeIdentity(candidate, options = {}) {
|
|
|
1087
1342
|
if (machineId === void 0) throw new Error(`Trae ${candidate.edition} has no stable machine identity`);
|
|
1088
1343
|
const deviceId = dcDevice ?? devDevice ?? createHash("sha256").update(machineId).digest("hex").slice(0, 32);
|
|
1089
1344
|
const buildVersion = nonEmpty(storage["iCubeLastVersion"]);
|
|
1090
|
-
const appName =
|
|
1345
|
+
const appName = {
|
|
1346
|
+
cn: "Trae CN",
|
|
1347
|
+
sg: "Trae",
|
|
1348
|
+
solo: "TRAE SOLO CN",
|
|
1349
|
+
"solo-sg": "TRAE SOLO"
|
|
1350
|
+
}[candidate.edition];
|
|
1091
1351
|
const productPaths = [];
|
|
1092
1352
|
if (appName !== void 0 && (platform === "darwin" || platform === "win32")) {
|
|
1093
1353
|
if (platform === "darwin") productPaths.push(join("/Applications", `${appName}.app`, "Contents", "Resources", "app", "product.json"));
|
|
@@ -1381,8 +1641,13 @@ function normalizeTraeVersionCode(buildVersion) {
|
|
|
1381
1641
|
const trimmed = buildVersion.trim();
|
|
1382
1642
|
return /^\d+$/.test(trimmed) ? trimmed : TRAE_VERSION_CODE_FALLBACK;
|
|
1383
1643
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1644
|
+
/**
|
|
1645
|
+
* Headers shared by every Trae edition. The same shape is accepted by both
|
|
1646
|
+
* gateways (verified read-only on the international gateway 2026-09-15,
|
|
1647
|
+
* docs/INTL_SG_EVIDENCE.md §2.3: identical `x-app-id`, identity headers, and
|
|
1648
|
+
* `Cloud-IDE-JWT` auth), so there is no per-edition branch any more.
|
|
1649
|
+
*/
|
|
1650
|
+
function buildTraeHeaders(credential, identity, options = {}) {
|
|
1386
1651
|
const requestId = options.requestId ?? randomUUID();
|
|
1387
1652
|
const traceId = requestId.replaceAll("-", "").slice(0, 32);
|
|
1388
1653
|
const profile = options.profile ?? "agent-task";
|
|
@@ -1426,6 +1691,8 @@ function buildTraeCnHeaders(credential, identity, options = {}) {
|
|
|
1426
1691
|
"Accept": "text/event-stream"
|
|
1427
1692
|
};
|
|
1428
1693
|
}
|
|
1694
|
+
/** Backwards-compatible alias; the headers are no longer CN-specific. */
|
|
1695
|
+
const buildTraeCnHeaders = buildTraeHeaders;
|
|
1429
1696
|
function traeEndpoint(baseUrl, path) {
|
|
1430
1697
|
return `${baseUrl.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
|
|
1431
1698
|
}
|
|
@@ -1526,14 +1793,17 @@ var TraeSoloUpstreamClient = class {
|
|
|
1526
1793
|
}
|
|
1527
1794
|
async fetchModels(signal) {
|
|
1528
1795
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
1529
|
-
const
|
|
1796
|
+
const region = regionOfCredential(credential);
|
|
1797
|
+
const base = this.options.baseUrl ?? REGION_GATEWAYS[region].chat;
|
|
1798
|
+
const directoryFunction = region === "ai" ? "solo_agent" : TRAE_SOLO_FUNCTION;
|
|
1799
|
+
const response = await this.fetchImpl(traeEndpoint(base, TRAE_SOLO_MODELS_PATH), {
|
|
1530
1800
|
method: "POST",
|
|
1531
1801
|
headers: {
|
|
1532
1802
|
...buildTraeCnHeaders(credential, identity),
|
|
1533
1803
|
Accept: "application/json"
|
|
1534
1804
|
},
|
|
1535
1805
|
body: JSON.stringify({
|
|
1536
|
-
function:
|
|
1806
|
+
function: directoryFunction,
|
|
1537
1807
|
config_names: null,
|
|
1538
1808
|
need_prompt: false,
|
|
1539
1809
|
current_config_info: null,
|
|
@@ -1589,9 +1859,10 @@ var TraeSoloUpstreamClient = class {
|
|
|
1589
1859
|
}
|
|
1590
1860
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
1591
1861
|
const headers = buildTraeCnHeaders(credential, identity);
|
|
1862
|
+
const base = this.options.baseUrl ?? REGION_GATEWAYS[regionOfCredential(credential)].chat;
|
|
1592
1863
|
let response;
|
|
1593
1864
|
try {
|
|
1594
|
-
response = await this.fetchImpl(traeEndpoint(
|
|
1865
|
+
response = await this.fetchImpl(traeEndpoint(base, TRAE_SOLO_CHAT_PATH), {
|
|
1595
1866
|
method: "POST",
|
|
1596
1867
|
headers,
|
|
1597
1868
|
body: prepared,
|
|
@@ -1960,6 +2231,24 @@ function parseTraeRemoteModel(value) {
|
|
|
1960
2231
|
//#region src/solo-remote.ts
|
|
1961
2232
|
const TRAE_SOLO_REMOTE_BASE = "https://solo.trae.cn/api/remote/v1";
|
|
1962
2233
|
/**
|
|
2234
|
+
* Region-scoped request dressing. The CN portal is `solo.trae.cn` with the
|
|
2235
|
+
* CN locale headers; the international directory lives on the shared
|
|
2236
|
+
* `coresg-normal.trae.ai` gateway and was verified (2026-09-15) with the
|
|
2237
|
+
* English/Singapore headers — both forms are accepted, each region keeps the
|
|
2238
|
+
* shape its own portal sends.
|
|
2239
|
+
*/
|
|
2240
|
+
function remoteDressing(region) {
|
|
2241
|
+
return region === "ai" ? {
|
|
2242
|
+
referer: "https://coresg-normal.trae.ai/",
|
|
2243
|
+
timezone: "Asia/Singapore",
|
|
2244
|
+
language: "en"
|
|
2245
|
+
} : {
|
|
2246
|
+
referer: "https://solo.trae.cn/",
|
|
2247
|
+
timezone: "Asia/Shanghai",
|
|
2248
|
+
language: "zh-cn"
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
1963
2252
|
* Read-only model catalog client for the SOLO Web API.
|
|
1964
2253
|
*
|
|
1965
2254
|
* This deliberately has no chat/session method: the Remote session protocol
|
|
@@ -1972,22 +2261,26 @@ var TraeSoloRemoteCatalogClient = class {
|
|
|
1972
2261
|
constructor(options) {
|
|
1973
2262
|
this.options = options;
|
|
1974
2263
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
1975
|
-
this.baseUrl = options.baseUrl
|
|
2264
|
+
this.baseUrl = options.baseUrl;
|
|
1976
2265
|
}
|
|
1977
|
-
async headers() {
|
|
2266
|
+
async headers(region) {
|
|
2267
|
+
const credential = await this.options.credential();
|
|
2268
|
+
const dressing = remoteDressing(region);
|
|
1978
2269
|
return {
|
|
1979
|
-
"Authorization": `Cloud-IDE-JWT ${
|
|
2270
|
+
"Authorization": `Cloud-IDE-JWT ${credential.accessToken}`,
|
|
1980
2271
|
"Content-Type": "application/json",
|
|
1981
2272
|
"x-trae-client-type": "web",
|
|
1982
|
-
"x-trae-user-timezone":
|
|
1983
|
-
"x-preferenced-language":
|
|
1984
|
-
"Referer":
|
|
2273
|
+
"x-trae-user-timezone": dressing.timezone,
|
|
2274
|
+
"x-preferenced-language": dressing.language,
|
|
2275
|
+
"Referer": dressing.referer,
|
|
1985
2276
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
1986
2277
|
};
|
|
1987
2278
|
}
|
|
1988
2279
|
async fetchModels(signal) {
|
|
1989
|
-
const
|
|
1990
|
-
const
|
|
2280
|
+
const region = regionOfCredential(await this.options.credential());
|
|
2281
|
+
const base = this.baseUrl ?? REGION_GATEWAYS[region].remote;
|
|
2282
|
+
const headers = await this.headers(region);
|
|
2283
|
+
const response = await this.fetchImpl(`${base}/models?functions=solo_agent_remote,solo_work_remote`, {
|
|
1991
2284
|
headers,
|
|
1992
2285
|
signal: signal ?? AbortSignal.timeout(3e4)
|
|
1993
2286
|
});
|
|
@@ -2164,8 +2457,9 @@ var TraeRawChatUpstreamClient = class {
|
|
|
2164
2457
|
message: "messages are required"
|
|
2165
2458
|
};
|
|
2166
2459
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
2460
|
+
const requestId = crypto.randomUUID();
|
|
2167
2461
|
const headers = buildTraeCnHeaders(credential, identity, {
|
|
2168
|
-
requestId
|
|
2462
|
+
requestId,
|
|
2169
2463
|
profile: "raw-chat"
|
|
2170
2464
|
});
|
|
2171
2465
|
const runtime = this.options.config.runtime;
|
|
@@ -2190,7 +2484,7 @@ var TraeRawChatUpstreamClient = class {
|
|
|
2190
2484
|
};
|
|
2191
2485
|
let response;
|
|
2192
2486
|
try {
|
|
2193
|
-
response = await this.fetchImpl(traeEndpoint(this.options.baseUrl ?? credential.
|
|
2487
|
+
response = await this.fetchImpl(traeEndpoint(this.options.baseUrl ?? REGION_GATEWAYS[regionOfCredential(credential)].chat, TRAE_RAW_CHAT_V2_PATH), {
|
|
2194
2488
|
method: "POST",
|
|
2195
2489
|
headers,
|
|
2196
2490
|
body: JSON.stringify(body),
|
|
@@ -2497,11 +2791,13 @@ var TraeDelegatingUpstreamClient = class {
|
|
|
2497
2791
|
/**
|
|
2498
2792
|
* Read-only Trae usage/credits client.
|
|
2499
2793
|
*
|
|
2500
|
-
*
|
|
2501
|
-
* `docs/USAGE_API_RESEARCH.md`)
|
|
2502
|
-
*
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2794
|
+
* CN sources the verified `api.trae.cn` pay/ug endpoints (see
|
|
2795
|
+
* `docs/USAGE_API_RESEARCH.md`); the international (ai) region is
|
|
2796
|
+
* subscription-based and reads `ide_user_pay_status` on its own gateway
|
|
2797
|
+
* (verified 2026-09-15, docs/INTL_SG_EVIDENCE.md §4). All queries are
|
|
2798
|
+
* read-only and do not consume Trae credits. The per-session consumption
|
|
2799
|
+
* detail table is deliberately not included: the endpoint returns no rows
|
|
2800
|
+
* for the current account, so we only expose what is actually retrievable.
|
|
2505
2801
|
*/
|
|
2506
2802
|
const TRAE_PAY_BASE = "https://api.trae.cn";
|
|
2507
2803
|
function asNumber(value) {
|
|
@@ -2559,23 +2855,37 @@ var TraeUsageClient = class {
|
|
|
2559
2855
|
constructor(options) {
|
|
2560
2856
|
this.options = options;
|
|
2561
2857
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2562
|
-
this.baseUrl = options.baseUrl
|
|
2858
|
+
this.baseUrl = options.baseUrl;
|
|
2563
2859
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
2564
2860
|
}
|
|
2861
|
+
/** Region of the current credential; `cn` when unresolvable. */
|
|
2862
|
+
async currentRegion() {
|
|
2863
|
+
const credential = await this.options.credential();
|
|
2864
|
+
return credential === void 0 ? "cn" : regionOfCredential(credential);
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Pay base for the current credential's region. An explicit baseUrl (tests,
|
|
2868
|
+
* diagnostics) pins the endpoint; otherwise CN uses `api.trae.cn` and the
|
|
2869
|
+
* international region its own verified pay gateway.
|
|
2870
|
+
*/
|
|
2871
|
+
async payBase() {
|
|
2872
|
+
return this.baseUrl ?? REGION_GATEWAYS[await this.currentRegion()].pay;
|
|
2873
|
+
}
|
|
2565
2874
|
async authedHeaders() {
|
|
2566
2875
|
const credential = await this.options.credential();
|
|
2567
2876
|
if (credential === void 0 || credential.accessToken === "") throw new Error("Trae credential is not available; cannot query usage");
|
|
2877
|
+
const origin = await this.currentRegion() === "ai" ? "https://www.trae.ai" : "https://www.trae.cn";
|
|
2568
2878
|
return {
|
|
2569
2879
|
"Authorization": `Cloud-IDE-JWT ${credential.accessToken}`,
|
|
2570
2880
|
"Content-Type": "application/json",
|
|
2571
2881
|
"User-Agent": "Mozilla/5.0",
|
|
2572
|
-
"Origin":
|
|
2573
|
-
"Referer":
|
|
2882
|
+
"Origin": origin,
|
|
2883
|
+
"Referer": `${origin}/`
|
|
2574
2884
|
};
|
|
2575
2885
|
}
|
|
2576
2886
|
async post(path, data, signal) {
|
|
2577
2887
|
const headers = await this.authedHeaders();
|
|
2578
|
-
const response = await this.fetchImpl(`${this.
|
|
2888
|
+
const response = await this.fetchImpl(`${await this.payBase()}${path}`, {
|
|
2579
2889
|
method: "POST",
|
|
2580
2890
|
headers,
|
|
2581
2891
|
body: JSON.stringify(data),
|
|
@@ -2584,12 +2894,53 @@ var TraeUsageClient = class {
|
|
|
2584
2894
|
if (!response.ok) throw new Error(`Trae usage endpoint ${path} returned HTTP ${response.status}`);
|
|
2585
2895
|
return await response.json();
|
|
2586
2896
|
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Subscription/pay status of an international (ai) account. The CN region
|
|
2899
|
+
* never calls this (its contract is unverified there); the ai region uses
|
|
2900
|
+
* this instead of the Work-credit `snapshot`.
|
|
2901
|
+
*/
|
|
2902
|
+
async payStatus(signal) {
|
|
2903
|
+
if (await this.currentRegion() !== "ai") throw new Error("Trae pay status is only available for the international (ai) region");
|
|
2904
|
+
const payload = await this.post("/trae/api/v1/pay/ide_user_pay_status", {}, signal);
|
|
2905
|
+
const flag = (key) => payload[key] === true;
|
|
2906
|
+
const trial = typeof payload["trial_status"] === "object" && payload["trial_status"] !== null ? payload["trial_status"] : {};
|
|
2907
|
+
const fissionStart = asNumber(payload["solo_fission_start_time"]);
|
|
2908
|
+
const fissionExpire = asNumber(payload["solo_fission_expire_time"]);
|
|
2909
|
+
const fissionMax = asNumber(payload["solo_fission_max_usage"]);
|
|
2910
|
+
return {
|
|
2911
|
+
isDollarUsageBilling: flag("is_dollar_usage_billing"),
|
|
2912
|
+
hasPackage: flag("has_package"),
|
|
2913
|
+
isPayFreshman: flag("is_pay_freshman") || flag("is_pay_freshman_v2"),
|
|
2914
|
+
inTrial: trial["is_in_trial"] === true,
|
|
2915
|
+
trialEndTimeMs: asNumber(trial["trial_end_time"]) ?? 0,
|
|
2916
|
+
enableSoloLite: flag("enable_solo_lite"),
|
|
2917
|
+
enableSoloBuilder: flag("enable_solo_builder"),
|
|
2918
|
+
enableSoloCoder: flag("enable_solo_coder"),
|
|
2919
|
+
enableSoloWeb: flag("enable_solo_web"),
|
|
2920
|
+
...fissionStart === void 0 || fissionExpire === void 0 || fissionMax === void 0 ? {} : { fission: {
|
|
2921
|
+
startTimeMs: fissionStart,
|
|
2922
|
+
expireTimeMs: fissionExpire,
|
|
2923
|
+
maxUsage: fissionMax
|
|
2924
|
+
} }
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* The Work-credit endpoints are CN-only. The international region is
|
|
2929
|
+
* subscription-based and must read {@link payStatus} instead; guarding here
|
|
2930
|
+
* keeps the card's degraded `creditsError` message diagnosable rather than
|
|
2931
|
+
* letting an ai credential hit an unverified path on its pay gateway.
|
|
2932
|
+
*/
|
|
2933
|
+
async requireCnRegion(method) {
|
|
2934
|
+
if (await this.currentRegion() !== "cn") throw new Error(`Trae ${method} is only available for the CN region; the international (ai) region uses payStatus`);
|
|
2935
|
+
}
|
|
2587
2936
|
/** Total entitlements / credits and per-pack breakdown. */
|
|
2588
2937
|
async snapshot(signal) {
|
|
2938
|
+
await this.requireCnRegion("usage snapshot");
|
|
2589
2939
|
return parseUsageSnapshot(await this.post("/trae/api/v2/pay/web_user_ent_usage", { require_usage: true }, signal));
|
|
2590
2940
|
}
|
|
2591
2941
|
/** Daily check-in status. */
|
|
2592
2942
|
async checkinStatus(signal) {
|
|
2943
|
+
await this.requireCnRegion("check-in status");
|
|
2593
2944
|
const payload = await this.post("/trae/api/v2/ug/checkin_credits/status", {}, signal);
|
|
2594
2945
|
return {
|
|
2595
2946
|
checkedIn: payload["checked_in"] === true,
|
|
@@ -2599,6 +2950,7 @@ var TraeUsageClient = class {
|
|
|
2599
2950
|
}
|
|
2600
2951
|
/** Rewards / activity rules. */
|
|
2601
2952
|
async activities(signal) {
|
|
2953
|
+
await this.requireCnRegion("activities");
|
|
2602
2954
|
const payload = await this.post("/trae/api/v2/ug/activity/info", {}, signal);
|
|
2603
2955
|
const rawActivities = Array.isArray(payload["commercial_activities"]) ? payload["commercial_activities"] : [];
|
|
2604
2956
|
const activities = [];
|
|
@@ -2632,17 +2984,34 @@ var TraeUsageClient = class {
|
|
|
2632
2984
|
};
|
|
2633
2985
|
//#endregion
|
|
2634
2986
|
//#region src/status-paths.ts
|
|
2635
|
-
/**
|
|
2636
|
-
* Node-free constants and types shared by the Host and browser halves.
|
|
2637
|
-
* Mirrors the `dsh-workbuddy-connect` status-route pattern so the plugin card
|
|
2638
|
-
* stays consistent with that project's external presentation.
|
|
2639
|
-
*/
|
|
2640
2987
|
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
2641
2988
|
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
2642
2989
|
/** Plugin-owned live model refresh endpoint. */
|
|
2643
2990
|
const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
|
|
2644
2991
|
/** Plugin-owned local account rescan endpoint. */
|
|
2645
2992
|
const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
2993
|
+
/** Query parameter naming the region a card request addresses. */
|
|
2994
|
+
const TRAE_REGION_PARAM = "region";
|
|
2995
|
+
/** Every region, in card tab order. */
|
|
2996
|
+
const TRAE_REGIONS = ["cn", "ai"];
|
|
2997
|
+
/**
|
|
2998
|
+
* Address one region's status route. The two regions are separate provider
|
|
2999
|
+
* stacks; every card request carries the region whose tab the user is on.
|
|
3000
|
+
*/
|
|
3001
|
+
function withTraeRegion(path, region) {
|
|
3002
|
+
return `${path}?${TRAE_REGION_PARAM}=${region}`;
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Read the region parameter off a status-route URL. Absent means the domestic
|
|
3006
|
+
* tab (`cn`); a present-but-unknown value returns undefined so the route can
|
|
3007
|
+
* answer 400 instead of guessing.
|
|
3008
|
+
*/
|
|
3009
|
+
function regionOfTraeStatusUrl(url) {
|
|
3010
|
+
const at = url.indexOf("?");
|
|
3011
|
+
const value = at === -1 ? null : new URLSearchParams(url.slice(at + 1)).get(TRAE_REGION_PARAM);
|
|
3012
|
+
if (value === null || value === "") return "cn";
|
|
3013
|
+
return TRAE_REGIONS.includes(value) ? value : void 0;
|
|
3014
|
+
}
|
|
2646
3015
|
//#endregion
|
|
2647
3016
|
//#region src/web-status.ts
|
|
2648
3017
|
/** Redact token-like content before it crosses to the browser. */
|
|
@@ -2688,14 +3057,17 @@ function toCredits(snapshot) {
|
|
|
2688
3057
|
};
|
|
2689
3058
|
}
|
|
2690
3059
|
/**
|
|
2691
|
-
* Assemble
|
|
2692
|
-
*
|
|
2693
|
-
*
|
|
3060
|
+
* Assemble one region's card document. `region` is the tab the card is on; the
|
|
3061
|
+
* region-scoped store already answers with only that region's accounts, so the
|
|
3062
|
+
* document's model slots and account list are that region's by construction.
|
|
3063
|
+
* Sign-in state is read-only; credit is a live billing answer whose failure
|
|
3064
|
+
* degrades to `creditsError` rather than failing the whole document.
|
|
2694
3065
|
*/
|
|
2695
|
-
async function traeWebUsage(deps) {
|
|
2696
|
-
const
|
|
2697
|
-
|
|
2698
|
-
|
|
3066
|
+
async function traeWebUsage(deps, region) {
|
|
3067
|
+
const store = deps.store(region);
|
|
3068
|
+
const accounts = await store.accounts();
|
|
3069
|
+
if ((await store.status()).state !== "signed-in") {
|
|
3070
|
+
const { failures } = await store.diagnose();
|
|
2699
3071
|
return {
|
|
2700
3072
|
status: "signed-out",
|
|
2701
3073
|
accounts,
|
|
@@ -2710,7 +3082,7 @@ async function traeWebUsage(deps) {
|
|
|
2710
3082
|
}
|
|
2711
3083
|
let credential;
|
|
2712
3084
|
try {
|
|
2713
|
-
credential = await
|
|
3085
|
+
credential = await store.resolve();
|
|
2714
3086
|
} catch (error) {
|
|
2715
3087
|
return {
|
|
2716
3088
|
status: "signed-out",
|
|
@@ -2722,16 +3094,32 @@ async function traeWebUsage(deps) {
|
|
|
2722
3094
|
accountId: accounts.find((item) => item.selected)?.id ?? "",
|
|
2723
3095
|
accountName: credential.accountName ?? credential.userId,
|
|
2724
3096
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
3097
|
+
region,
|
|
2725
3098
|
accounts,
|
|
2726
|
-
models: deps.displayModels().map((model) => ({
|
|
3099
|
+
models: deps.displayModels(region).map((model) => ({
|
|
2727
3100
|
...model,
|
|
2728
3101
|
...model.input === void 0 ? {} : { input: [...model.input] }
|
|
2729
3102
|
})),
|
|
2730
|
-
enabledModelIds: [...deps.enabledModelIds()],
|
|
2731
|
-
...deps.rawDiagnostic === void 0 ? {} : { rawChat: deps.rawDiagnostic() }
|
|
3103
|
+
enabledModelIds: [...deps.enabledModelIds(region)],
|
|
3104
|
+
...deps.rawDiagnostic === void 0 ? {} : { rawChat: deps.rawDiagnostic(region) }
|
|
2732
3105
|
};
|
|
3106
|
+
const client = deps.client(region);
|
|
3107
|
+
if (region === "ai") try {
|
|
3108
|
+
const payStatus = await client.payStatus();
|
|
3109
|
+
return {
|
|
3110
|
+
status: "signed-in",
|
|
3111
|
+
...account,
|
|
3112
|
+
payStatus
|
|
3113
|
+
};
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
return {
|
|
3116
|
+
status: "signed-in",
|
|
3117
|
+
...account,
|
|
3118
|
+
payStatusError: safeMessage(error)
|
|
3119
|
+
};
|
|
3120
|
+
}
|
|
2733
3121
|
try {
|
|
2734
|
-
const snapshot = await
|
|
3122
|
+
const snapshot = await client.snapshot();
|
|
2735
3123
|
return {
|
|
2736
3124
|
status: "signed-in",
|
|
2737
3125
|
...account,
|
|
@@ -2745,6 +3133,18 @@ async function traeWebUsage(deps) {
|
|
|
2745
3133
|
};
|
|
2746
3134
|
}
|
|
2747
3135
|
}
|
|
3136
|
+
/**
|
|
3137
|
+
* The region a request addresses, or a 400 answer. Absent parameter means the
|
|
3138
|
+
* domestic tab; an unknown value is refused rather than guessed.
|
|
3139
|
+
*/
|
|
3140
|
+
function requestRegion(req, res) {
|
|
3141
|
+
const region = regionOfTraeStatusUrl(req.url ?? "/");
|
|
3142
|
+
if (region === void 0) {
|
|
3143
|
+
json(res, 400, { error: "unknown region" });
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
return region;
|
|
3147
|
+
}
|
|
2748
3148
|
/** Mount the GET usage route on an optional webServer context. */
|
|
2749
3149
|
function registerTraeUsageRoute(ctx, deps) {
|
|
2750
3150
|
ctx.effect(() => {
|
|
@@ -2760,8 +3160,10 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
2760
3160
|
json(res, 403, { error: "origin-not-trusted" });
|
|
2761
3161
|
return;
|
|
2762
3162
|
}
|
|
3163
|
+
const region = requestRegion(req, res);
|
|
3164
|
+
if (region === void 0) return;
|
|
2763
3165
|
try {
|
|
2764
|
-
json(res, 200, await traeWebUsage(deps));
|
|
3166
|
+
json(res, 200, await traeWebUsage(deps, region));
|
|
2765
3167
|
} catch (error) {
|
|
2766
3168
|
json(res, 500, { error: safeMessage(error) });
|
|
2767
3169
|
}
|
|
@@ -2773,8 +3175,10 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
2773
3175
|
handler: async (req, res) => {
|
|
2774
3176
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
2775
3177
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
3178
|
+
const region = requestRegion(req, res);
|
|
3179
|
+
if (region === void 0) return;
|
|
2776
3180
|
try {
|
|
2777
|
-
json(res, 200, { accounts: await deps.store.accounts() });
|
|
3181
|
+
json(res, 200, { accounts: await deps.store(region).accounts() });
|
|
2778
3182
|
} catch (error) {
|
|
2779
3183
|
json(res, 500, { error: safeMessage(error) });
|
|
2780
3184
|
}
|
|
@@ -2787,8 +3191,10 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
2787
3191
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
2788
3192
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
2789
3193
|
if (deps.discoverModels === void 0) return json(res, 503, { error: "model refresh unavailable" });
|
|
3194
|
+
const region = requestRegion(req, res);
|
|
3195
|
+
if (region === void 0) return;
|
|
2790
3196
|
try {
|
|
2791
|
-
json(res, 200, { models: await deps.discoverModels() });
|
|
3197
|
+
json(res, 200, { models: await deps.discoverModels(region) });
|
|
2792
3198
|
} catch (error) {
|
|
2793
3199
|
json(res, 500, { error: safeMessage(error) });
|
|
2794
3200
|
}
|
|
@@ -2946,6 +3352,26 @@ const inject = ["llm"];
|
|
|
2946
3352
|
* so no branding is needed for either host generation.
|
|
2947
3353
|
*/
|
|
2948
3354
|
const TRAE_SETTINGS_NS = "trae";
|
|
3355
|
+
/**
|
|
3356
|
+
* One region's saved state. A config written before the region split has only
|
|
3357
|
+
* the flat fields: those were always captured from the CN endpoint (the
|
|
3358
|
+
* plugin had no international support), so they are read as the CN state and
|
|
3359
|
+
* only when no explicit CN slot exists. The ai region never inherits them —
|
|
3360
|
+
* that inheritance is exactly the bug where a stale CN directory would be
|
|
3361
|
+
* intersected with the international catalog and silently drop the user's
|
|
3362
|
+
* picks (the same failure workbuddy fixed with its region split).
|
|
3363
|
+
*/
|
|
3364
|
+
function regionStateOf(config, region) {
|
|
3365
|
+
const stored = config.regions?.[region];
|
|
3366
|
+
if (stored !== void 0) return stored;
|
|
3367
|
+
if (region !== "cn") return {};
|
|
3368
|
+
return {
|
|
3369
|
+
...config.lastCatalog === void 0 ? {} : { lastCatalog: config.lastCatalog },
|
|
3370
|
+
...config.enabledModelIds === void 0 ? {} : { enabledModelIds: config.enabledModelIds },
|
|
3371
|
+
...config.imageModelIds === void 0 ? {} : { imageModelIds: config.imageModelIds },
|
|
3372
|
+
...config.contextBudgets === void 0 ? {} : { contextBudgets: config.contextBudgets }
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
2949
3375
|
const modelConfig = z.object({
|
|
2950
3376
|
id: z.string().required(),
|
|
2951
3377
|
name: z.string().required(),
|
|
@@ -2954,6 +3380,16 @@ const modelConfig = z.object({
|
|
|
2954
3380
|
input: z.array(z.union(["text", "image"])),
|
|
2955
3381
|
creditMultiplier: z.number()
|
|
2956
3382
|
});
|
|
3383
|
+
const regionStateConfig = z.object({
|
|
3384
|
+
lastCatalog: z.array(modelConfig).default([]),
|
|
3385
|
+
enabledModelIds: z.array(z.string()).default([]),
|
|
3386
|
+
imageModelIds: z.array(z.string()).default([]),
|
|
3387
|
+
contextBudgets: z.dict(z.number().step(1).min(1)).default({})
|
|
3388
|
+
});
|
|
3389
|
+
const accountSelectionConfig = z.object({
|
|
3390
|
+
cn: z.string().description("Selected domestic (CN) account id (never a token)"),
|
|
3391
|
+
ai: z.string().description("Selected international account id (never a token)")
|
|
3392
|
+
});
|
|
2957
3393
|
const Config = z.object({
|
|
2958
3394
|
authFile: z.string().description("Optional Trae storage.json path override"),
|
|
2959
3395
|
edition: z.union([
|
|
@@ -2963,137 +3399,231 @@ const Config = z.object({
|
|
|
2963
3399
|
"solo",
|
|
2964
3400
|
"solo-sg"
|
|
2965
3401
|
]).default("auto").description("Trae edition hint"),
|
|
2966
|
-
accountId: z.string().description("
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3402
|
+
accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
|
|
3403
|
+
accounts: accountSelectionConfig.description("Per-region account selections, keyed cn | ai"),
|
|
3404
|
+
regions: z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai"),
|
|
3405
|
+
lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
|
|
3406
|
+
enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
|
|
3407
|
+
contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Deprecated: pre-region-split CN context budgets"),
|
|
3408
|
+
imageModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN image opt-in"),
|
|
2971
3409
|
models: z.array(modelConfig).description("Legacy generated Trae model list")
|
|
2972
3410
|
});
|
|
3411
|
+
/** Every region, in card tab order. */
|
|
3412
|
+
const REGION_KEYS = ["cn", "ai"];
|
|
2973
3413
|
function apply(ctx, config) {
|
|
2974
|
-
|
|
2975
|
-
const enabledSet = (value) => new Set(value.enabledModelIds ?? []);
|
|
2976
|
-
const imageSet = (value) => new Set(value.imageModelIds ?? []);
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
3414
|
+
let current = () => config;
|
|
3415
|
+
const enabledSet = (value, region) => new Set(regionStateOf(value, region).enabledModelIds ?? []);
|
|
3416
|
+
const imageSet = (value, region) => new Set(regionStateOf(value, region).imageModelIds ?? []);
|
|
3417
|
+
/**
|
|
3418
|
+
* Per-region wire state. Each region's discovery owns its own maps: the CN
|
|
3419
|
+
* and international rosters overlap (and spell some ids differently), so a
|
|
3420
|
+
* shared map would let one region's answer filter the other region's
|
|
3421
|
+
* catalog. Display keys (lowercased id AND name) of every model known to be
|
|
3422
|
+
* callable via `llm_utils_chat` are recorded by that region's discovery; a
|
|
3423
|
+
* model whose id and name are both absent is a dead config_name (Remote
|
|
3424
|
+
* advertises it, `get_detail_param` has no match, e.g. `Doubao-Seed-Code` /
|
|
3425
|
+
* `glm-5.3`) and must never be served — even from a stale saved
|
|
3426
|
+
* `lastCatalog` / `models` / `enabledModelIds` that still lists it.
|
|
3427
|
+
*/
|
|
3428
|
+
const wireState = {};
|
|
3429
|
+
for (const region of REGION_KEYS) wireState[region] = {
|
|
3430
|
+
callableKeys: /* @__PURE__ */ new Set(),
|
|
3431
|
+
resolved: false,
|
|
3432
|
+
byId: /* @__PURE__ */ new Map(),
|
|
3433
|
+
byName: /* @__PURE__ */ new Map()
|
|
2982
3434
|
};
|
|
2983
|
-
const
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
return derived.length > 0 ? derived : fallbackModels(value);
|
|
3435
|
+
const dropDeadModels = (rows, region) => {
|
|
3436
|
+
const wire = wireState[region];
|
|
3437
|
+
if (!wire.resolved) return rows;
|
|
3438
|
+
return rows.filter((model) => wire.callableKeys.has(model.id.trim().toLowerCase()) || wire.callableKeys.has(model.name.trim().toLowerCase()));
|
|
2988
3439
|
};
|
|
2989
|
-
const
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
...config.accountId === void 0 ? {} : { accountId: config.accountId },
|
|
2995
|
-
refresh: (credential) => refreshTraeCredential(credential)
|
|
2996
|
-
});
|
|
2997
|
-
const identity = async () => {
|
|
2998
|
-
const candidates = config.authFile === void 0 ? traeStorageCandidates().filter((item) => item.source === "desktop" && (item.edition === "cn" || item.edition === "solo") && (config.edition === void 0 || config.edition === "auto" || item.edition === config.edition)) : [{
|
|
2999
|
-
edition: config.edition === void 0 || config.edition === "auto" ? "solo" : config.edition,
|
|
3000
|
-
path: config.authFile,
|
|
3001
|
-
source: "desktop"
|
|
3002
|
-
}];
|
|
3003
|
-
if (candidates.length === 0) throw new Error("Trae storage was not found");
|
|
3004
|
-
return pickTraeStorageIdentity(candidates);
|
|
3440
|
+
const fallbackModels = (value, region) => applyImageSelection(fallbackModelsFor(region), imageSet(value, region));
|
|
3441
|
+
const derive = (value, raw, region) => {
|
|
3442
|
+
const selectedImages = imageSet(value, region);
|
|
3443
|
+
const derived = deriveCatalog(applyImageSelection(sanitizeCatalog(dropDeadModels(raw, region)), selectedImages), enabledSet(value, region), regionStateOf(value, region).contextBudgets ?? {});
|
|
3444
|
+
return derived.length > 0 ? derived : fallbackModels(value, region);
|
|
3005
3445
|
};
|
|
3006
|
-
const
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
baseUrl: "https://trae-api-cn.mchost.guru",
|
|
3010
|
-
log: (message, detail) => ctx.logger.warn(message, detail)
|
|
3011
|
-
});
|
|
3012
|
-
const remoteCatalog = new TraeSoloRemoteCatalogClient({ credential: () => store.resolve() });
|
|
3013
|
-
const wireById = /* @__PURE__ */ new Map();
|
|
3014
|
-
const wireByName = /* @__PURE__ */ new Map();
|
|
3015
|
-
const wireResolver = (displayId) => wireById.get(displayId) ?? wireByName.get(displayId.trim().toLowerCase());
|
|
3016
|
-
const upstream = new TraeSoloBridge(solo, catalog, wireResolver);
|
|
3017
|
-
const delegating = new TraeDelegatingUpstreamClient(upstream);
|
|
3018
|
-
const shim = createTraeShim({
|
|
3019
|
-
catalog,
|
|
3020
|
-
client: delegating,
|
|
3021
|
-
logger: ctx.logger
|
|
3022
|
-
});
|
|
3023
|
-
let rawDiagnostic = () => ({ state: "disabled" });
|
|
3024
|
-
(async () => {
|
|
3025
|
-
try {
|
|
3026
|
-
const { identity, runtime } = await resolveTraeRawRuntime(store, "qwen-3.7-plus");
|
|
3027
|
-
const gateway = createTraeRawGateway({
|
|
3028
|
-
raw: new TraeRawChatUpstreamClient({
|
|
3029
|
-
credential: () => store.resolve(),
|
|
3030
|
-
identity: async () => identity,
|
|
3031
|
-
config: {
|
|
3032
|
-
model: runtime.modelName,
|
|
3033
|
-
configName: runtime.configName,
|
|
3034
|
-
passBackReasoning: true,
|
|
3035
|
-
runtime
|
|
3036
|
-
},
|
|
3037
|
-
baseUrl: "https://trae-api-cn.mchost.guru"
|
|
3038
|
-
}),
|
|
3039
|
-
solo: upstream,
|
|
3040
|
-
endpoint: "https://trae-api-cn.mchost.guru/api/ide/v2/llm_raw_chat",
|
|
3041
|
-
edition: identity.edition,
|
|
3042
|
-
identity: {
|
|
3043
|
-
appVersion: identity.appVersion ?? "",
|
|
3044
|
-
buildVersion: identity.buildVersion ?? ""
|
|
3045
|
-
},
|
|
3046
|
-
runtime,
|
|
3047
|
-
enabled: false
|
|
3048
|
-
});
|
|
3049
|
-
delegating.replace(gateway.upstream);
|
|
3050
|
-
rawDiagnostic = () => gateway.diagnostic();
|
|
3051
|
-
ctx.effect(() => () => gateway.invalidate(), "dsh-connect-trae: Raw capability invalidation");
|
|
3052
|
-
} catch (error) {
|
|
3053
|
-
ctx.logger.warn("dsh-connect-trae: Raw gateway unavailable; continuing with native SOLO tool-call channel", error);
|
|
3054
|
-
}
|
|
3055
|
-
})();
|
|
3056
|
-
const usageClient = new TraeUsageClient({ credential: () => store.resolve() });
|
|
3057
|
-
let current = () => config;
|
|
3058
|
-
let invalidateAdapter = () => {};
|
|
3059
|
-
const discoverModels = async (signal) => {
|
|
3060
|
-
const [remote, wire] = await Promise.all([remoteCatalog.fetchModels(signal), solo.fetchModels(signal)]);
|
|
3061
|
-
const merged = mergeTraeModelSources(remote, wire);
|
|
3062
|
-
callableKeys.clear();
|
|
3063
|
-
for (const model of merged) {
|
|
3064
|
-
callableKeys.add(model.id.trim().toLowerCase());
|
|
3065
|
-
callableKeys.add(model.name.trim().toLowerCase());
|
|
3066
|
-
}
|
|
3067
|
-
wireResolved = merged.length > 0;
|
|
3068
|
-
wireById.clear();
|
|
3069
|
-
wireByName.clear();
|
|
3070
|
-
for (const model of merged) if (model.wireConfigName !== void 0) {
|
|
3071
|
-
wireById.set(model.id, model.wireConfigName);
|
|
3072
|
-
wireByName.set(model.name.trim().toLowerCase(), model.wireConfigName);
|
|
3073
|
-
}
|
|
3074
|
-
const next = applyImageSelection(merged, imageSet(current()));
|
|
3075
|
-
catalog.set(next);
|
|
3076
|
-
return merged;
|
|
3446
|
+
const configuredModels = (value, region) => {
|
|
3447
|
+
const state = regionStateOf(value, region);
|
|
3448
|
+
return state.lastCatalog?.length ? derive(value, state.lastCatalog, region) : region === "cn" && value.models?.length ? derive(value, value.models, region) : fallbackModels(value, region);
|
|
3077
3449
|
};
|
|
3450
|
+
const displayModels = (value, region) => {
|
|
3451
|
+
const state = regionStateOf(value, region);
|
|
3452
|
+
return state.lastCatalog?.length ? dropDeadModels(sanitizeCatalog(state.lastCatalog), region) : region === "cn" && value.models?.length ? dropDeadModels(sanitizeCatalog(value.models), region) : fallbackModelsFor(region);
|
|
3453
|
+
};
|
|
3454
|
+
/**
|
|
3455
|
+
* Legacy migration for the pre-split single `accountId`: its region is
|
|
3456
|
+
* resolved once from the local account scan and the selection is then
|
|
3457
|
+
* attributed to that region ONLY — the other region keeps its documented
|
|
3458
|
+
* default (first discovered account of that region) instead of silently
|
|
3459
|
+
* inheriting a selection that belongs to the other side of the split.
|
|
3460
|
+
*/
|
|
3461
|
+
let legacyAccountRegion;
|
|
3462
|
+
const effectiveAccountFor = (region, value) => {
|
|
3463
|
+
const explicit = value.accounts?.[region];
|
|
3464
|
+
if (explicit !== void 0) return explicit;
|
|
3465
|
+
return legacyAccountRegion === region ? value.accountId : void 0;
|
|
3466
|
+
};
|
|
3467
|
+
const stacks = {};
|
|
3468
|
+
for (const region of REGION_KEYS) {
|
|
3469
|
+
const catalog = new TraeCatalog(region);
|
|
3470
|
+
const wire = wireState[region];
|
|
3471
|
+
/**
|
|
3472
|
+
* Best-effort device identity for this region's installation. Prefer the
|
|
3473
|
+
* selected credential's own edition so an international account reads its
|
|
3474
|
+
* own installation's identity (machine/device ids are per-install),
|
|
3475
|
+
* mirroring the credential store's account binding; the explicit `edition`
|
|
3476
|
+
* config still wins as the user's own narrowing. Candidates are then
|
|
3477
|
+
* narrowed to THIS region's editions, so the CN stack can never read an
|
|
3478
|
+
* international install's identity (and vice versa).
|
|
3479
|
+
*/
|
|
3480
|
+
const identity = async () => {
|
|
3481
|
+
let preferred;
|
|
3482
|
+
try {
|
|
3483
|
+
preferred = (await store.current())?.edition;
|
|
3484
|
+
} catch {}
|
|
3485
|
+
const hint = (config.edition !== void 0 && config.edition !== "auto" ? config.edition : void 0) ?? preferred;
|
|
3486
|
+
const candidates = config.authFile === void 0 ? traeStorageCandidates().filter((item) => item.source === "desktop" && (hint !== void 0 ? item.edition === hint : regionOfEdition(item.edition) === region)) : [{
|
|
3487
|
+
edition: hint ?? (region === "ai" ? "sg" : "solo"),
|
|
3488
|
+
path: config.authFile,
|
|
3489
|
+
source: "desktop"
|
|
3490
|
+
}];
|
|
3491
|
+
if (candidates.length === 0) throw new Error("Trae storage was not found");
|
|
3492
|
+
return pickTraeStorageIdentity(candidates);
|
|
3493
|
+
};
|
|
3494
|
+
/**
|
|
3495
|
+
* Resolution failures degrade to omitting DeviceInfo (its being required
|
|
3496
|
+
* is unverified, docs/INTL_SG_EVIDENCE.md §5) rather than failing the
|
|
3497
|
+
* refresh.
|
|
3498
|
+
*/
|
|
3499
|
+
const refreshDeviceSafe = async () => {
|
|
3500
|
+
try {
|
|
3501
|
+
const value = await identity();
|
|
3502
|
+
return {
|
|
3503
|
+
deviceId: value.deviceId,
|
|
3504
|
+
machineId: value.machineId
|
|
3505
|
+
};
|
|
3506
|
+
} catch {
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
};
|
|
3510
|
+
const store = new TraeCredentialStore({
|
|
3511
|
+
region,
|
|
3512
|
+
...config.authFile === void 0 ? {} : { storagePath: config.authFile },
|
|
3513
|
+
edition: config.edition ?? "auto",
|
|
3514
|
+
refresh: async (credential) => refreshTraeCredential(credential, void 0, await refreshDeviceSafe())
|
|
3515
|
+
});
|
|
3516
|
+
const solo = new TraeSoloUpstreamClient({
|
|
3517
|
+
credential: () => store.resolve(),
|
|
3518
|
+
identity,
|
|
3519
|
+
log: (message, detail) => ctx.logger.warn(message, detail)
|
|
3520
|
+
});
|
|
3521
|
+
const remoteCatalog = new TraeSoloRemoteCatalogClient({ credential: () => store.resolve() });
|
|
3522
|
+
const wireResolver = (displayId) => wire.byId.get(displayId) ?? wire.byName.get(displayId.trim().toLowerCase());
|
|
3523
|
+
const upstream = new TraeSoloBridge(solo, catalog, wireResolver);
|
|
3524
|
+
const delegating = new TraeDelegatingUpstreamClient(upstream);
|
|
3525
|
+
const shim = createTraeShim({
|
|
3526
|
+
catalog,
|
|
3527
|
+
client: delegating,
|
|
3528
|
+
logger: ctx.logger
|
|
3529
|
+
});
|
|
3530
|
+
let rawDiagnostic = () => ({ state: "disabled" });
|
|
3531
|
+
if (region === "cn") (async () => {
|
|
3532
|
+
try {
|
|
3533
|
+
const resolved = await resolveTraeRawRuntime(store, "qwen-3.7-plus");
|
|
3534
|
+
const gateway = createTraeRawGateway({
|
|
3535
|
+
raw: new TraeRawChatUpstreamClient({
|
|
3536
|
+
credential: () => store.resolve(),
|
|
3537
|
+
identity: async () => resolved.identity,
|
|
3538
|
+
config: {
|
|
3539
|
+
model: resolved.runtime.modelName,
|
|
3540
|
+
configName: resolved.runtime.configName,
|
|
3541
|
+
passBackReasoning: true,
|
|
3542
|
+
runtime: resolved.runtime
|
|
3543
|
+
}
|
|
3544
|
+
}),
|
|
3545
|
+
solo: upstream,
|
|
3546
|
+
endpoint: `${REGION_GATEWAYS.cn.chat}/api/ide/v2/llm_raw_chat`,
|
|
3547
|
+
edition: resolved.identity.edition,
|
|
3548
|
+
identity: {
|
|
3549
|
+
appVersion: resolved.identity.appVersion ?? "",
|
|
3550
|
+
buildVersion: resolved.identity.buildVersion ?? ""
|
|
3551
|
+
},
|
|
3552
|
+
runtime: resolved.runtime,
|
|
3553
|
+
enabled: false
|
|
3554
|
+
});
|
|
3555
|
+
delegating.replace(gateway.upstream);
|
|
3556
|
+
rawDiagnostic = () => gateway.diagnostic();
|
|
3557
|
+
ctx.effect(() => () => gateway.invalidate(), "dsh-connect-trae: Raw capability invalidation");
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
ctx.logger.warn("dsh-connect-trae: Raw gateway unavailable; continuing with native SOLO tool-call channel", error);
|
|
3560
|
+
}
|
|
3561
|
+
})();
|
|
3562
|
+
stacks[region] = {
|
|
3563
|
+
region,
|
|
3564
|
+
store,
|
|
3565
|
+
catalog,
|
|
3566
|
+
shim,
|
|
3567
|
+
delegating,
|
|
3568
|
+
usageClient: new TraeUsageClient({ credential: () => store.resolve() }),
|
|
3569
|
+
discoverModels: async (signal) => {
|
|
3570
|
+
const [remote, wireModels] = await Promise.all([remoteCatalog.fetchModels(signal), solo.fetchModels(signal)]);
|
|
3571
|
+
const merged = mergeTraeModelSources(remote, wireModels);
|
|
3572
|
+
wire.callableKeys.clear();
|
|
3573
|
+
for (const model of merged) {
|
|
3574
|
+
wire.callableKeys.add(model.id.trim().toLowerCase());
|
|
3575
|
+
wire.callableKeys.add(model.name.trim().toLowerCase());
|
|
3576
|
+
}
|
|
3577
|
+
wire.resolved = merged.length > 0;
|
|
3578
|
+
wire.byId.clear();
|
|
3579
|
+
wire.byName.clear();
|
|
3580
|
+
for (const model of merged) if (model.wireConfigName !== void 0) {
|
|
3581
|
+
wire.byId.set(model.id, model.wireConfigName);
|
|
3582
|
+
wire.byName.set(model.name.trim().toLowerCase(), model.wireConfigName);
|
|
3583
|
+
}
|
|
3584
|
+
return merged;
|
|
3585
|
+
},
|
|
3586
|
+
invalidateAdapter: () => {},
|
|
3587
|
+
rawDiagnostic: () => rawDiagnostic()
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3078
3590
|
ctx.inject(["webServer"], (webCtx) => registerTraeUsageRoute(webCtx, {
|
|
3079
|
-
store,
|
|
3080
|
-
client: usageClient,
|
|
3081
|
-
displayModels: () => displayModels(current()),
|
|
3082
|
-
enabledModelIds: () => current().enabledModelIds ?? [],
|
|
3083
|
-
discoverModels,
|
|
3084
|
-
rawDiagnostic: () => rawDiagnostic()
|
|
3591
|
+
store: (region) => stacks[region].store,
|
|
3592
|
+
client: (region) => stacks[region].usageClient,
|
|
3593
|
+
displayModels: (region) => displayModels(current(), region),
|
|
3594
|
+
enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
|
|
3595
|
+
discoverModels: (region, signal) => stacks[region].discoverModels(signal),
|
|
3596
|
+
rawDiagnostic: (region) => stacks[region].rawDiagnostic()
|
|
3085
3597
|
}));
|
|
3598
|
+
/** Push the current config into every region's store selection and catalog. */
|
|
3599
|
+
const applySelection = (value) => {
|
|
3600
|
+
for (const region of REGION_KEYS) {
|
|
3601
|
+
const stack = stacks[region];
|
|
3602
|
+
stack.store.setSource(value.authFile, value.edition ?? "auto", effectiveAccountFor(region, value));
|
|
3603
|
+
stack.catalog.set(configuredModels(value, region));
|
|
3604
|
+
stack.invalidateAdapter();
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3086
3607
|
const sectionHooks = {
|
|
3087
3608
|
setSource(source) {
|
|
3088
3609
|
current = source;
|
|
3089
3610
|
},
|
|
3090
3611
|
onChange() {
|
|
3091
|
-
|
|
3092
|
-
store.setSource(next.authFile, next.edition ?? "auto", next.accountId);
|
|
3093
|
-
catalog.set(configuredModels(next));
|
|
3094
|
-
invalidateAdapter();
|
|
3612
|
+
applySelection(current());
|
|
3095
3613
|
}
|
|
3096
3614
|
};
|
|
3615
|
+
applySelection(config);
|
|
3616
|
+
(async () => {
|
|
3617
|
+
const id = current().accountId;
|
|
3618
|
+
if (id === void 0) return;
|
|
3619
|
+
try {
|
|
3620
|
+
for (const region of REGION_KEYS) if ((await stacks[region].store.accounts()).some((account) => account.id === id)) {
|
|
3621
|
+
legacyAccountRegion = region;
|
|
3622
|
+
applySelection(current());
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
} catch {}
|
|
3626
|
+
})();
|
|
3097
3627
|
const legacyInstallSettingsSection = dshSettings.installSettingsSection;
|
|
3098
3628
|
if (typeof legacyInstallSettingsSection === "function") legacyInstallSettingsSection(ctx, TRAE_SETTINGS_NS, Config, config, sectionHooks);
|
|
3099
3629
|
else ctx.inject(["settings"], (settingsCtx) => {
|
|
@@ -3102,32 +3632,34 @@ function apply(ctx, config) {
|
|
|
3102
3632
|
let stopped = false;
|
|
3103
3633
|
ctx.effect(() => () => {
|
|
3104
3634
|
stopped = true;
|
|
3105
|
-
shim.close();
|
|
3635
|
+
for (const region of REGION_KEYS) stacks[region].shim.close();
|
|
3106
3636
|
});
|
|
3107
|
-
shim.ready.then(async () => {
|
|
3637
|
+
Promise.all(REGION_KEYS.map((region) => stacks[region].shim.ready)).then(async () => {
|
|
3108
3638
|
if (stopped) return;
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
} catch (error) {
|
|
3112
|
-
ctx.logger.warn("dsh-connect-trae: wire-id resolution failed at startup; falling back to display ids", error);
|
|
3113
|
-
}
|
|
3114
|
-
catalog.set(configuredModels(current()));
|
|
3115
|
-
const trae = createTraeAdapter({
|
|
3116
|
-
shim,
|
|
3117
|
-
catalog,
|
|
3118
|
-
resolveAttachments: () => ctx.get("attachments")
|
|
3119
|
-
});
|
|
3120
|
-
invalidateAdapter = () => {
|
|
3121
|
-
trae.invalidate();
|
|
3122
|
-
};
|
|
3123
|
-
let releaseAdapter;
|
|
3639
|
+
let releaseAdapterCn;
|
|
3640
|
+
let releaseAdapterAi;
|
|
3124
3641
|
let releaseDirectory;
|
|
3125
3642
|
try {
|
|
3126
|
-
|
|
3643
|
+
for (const region of REGION_KEYS) {
|
|
3644
|
+
const stack = stacks[region];
|
|
3645
|
+
const trae = createTraeAdapter({
|
|
3646
|
+
shim: stack.shim,
|
|
3647
|
+
catalog: stack.catalog,
|
|
3648
|
+
provider: TRAE_PROVIDERS[region],
|
|
3649
|
+
displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
|
|
3650
|
+
resolveAttachments: () => ctx.get("attachments")
|
|
3651
|
+
});
|
|
3652
|
+
stack.invalidateAdapter = () => {
|
|
3653
|
+
trae.invalidate();
|
|
3654
|
+
};
|
|
3655
|
+
if (region === "cn") releaseAdapterCn = ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter);
|
|
3656
|
+
else releaseAdapterAi = ctx.llm.registerAdapter([TRAE_AI_PROVIDER], trae.adapter);
|
|
3657
|
+
}
|
|
3127
3658
|
ctx.llm.registerModelDiscovery(TRAE_SETTINGS_NS, async (request, signal) => {
|
|
3128
|
-
|
|
3659
|
+
const region = regionOfTraeProvider(request.provider ?? "");
|
|
3660
|
+
if (region === void 0) return [];
|
|
3129
3661
|
const cancellation = signal ?? request.signal;
|
|
3130
|
-
return applyImageSelection(await discoverModels(cancellation), imageSet(current())).map((model) => ({
|
|
3662
|
+
return applyImageSelection(await stacks[region].discoverModels(cancellation), imageSet(current(), region)).map((model) => ({
|
|
3131
3663
|
id: model.id,
|
|
3132
3664
|
name: traeModelDisplayName(model),
|
|
3133
3665
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
@@ -3135,31 +3667,46 @@ function apply(ctx, config) {
|
|
|
3135
3667
|
inputModalities: traeInputModalities(model)
|
|
3136
3668
|
}));
|
|
3137
3669
|
});
|
|
3138
|
-
releaseDirectory = ctx.llm.registerConfigurableProviders(
|
|
3139
|
-
provider:
|
|
3140
|
-
displayName:
|
|
3670
|
+
releaseDirectory = ctx.llm.registerConfigurableProviders(REGION_KEYS.map((region) => ({
|
|
3671
|
+
provider: TRAE_PROVIDERS[region],
|
|
3672
|
+
displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
|
|
3141
3673
|
settingsNs: TRAE_SETTINGS_NS,
|
|
3142
3674
|
settingsPath: [],
|
|
3143
3675
|
declared: false
|
|
3144
|
-
}
|
|
3676
|
+
})));
|
|
3145
3677
|
} finally {
|
|
3146
|
-
if (
|
|
3147
|
-
|
|
3678
|
+
if (releaseAdapterCn === void 0 || releaseAdapterAi === void 0 || releaseDirectory === void 0) {
|
|
3679
|
+
releaseAdapterCn?.();
|
|
3680
|
+
releaseAdapterAi?.();
|
|
3148
3681
|
releaseDirectory?.();
|
|
3149
3682
|
}
|
|
3150
3683
|
}
|
|
3151
3684
|
try {
|
|
3152
3685
|
ctx.effect(() => () => {
|
|
3153
|
-
|
|
3686
|
+
releaseAdapterCn?.();
|
|
3687
|
+
releaseAdapterAi?.();
|
|
3154
3688
|
releaseDirectory?.();
|
|
3155
3689
|
});
|
|
3156
3690
|
} catch {
|
|
3157
|
-
|
|
3691
|
+
releaseAdapterCn?.();
|
|
3692
|
+
releaseAdapterAi?.();
|
|
3158
3693
|
releaseDirectory?.();
|
|
3159
3694
|
}
|
|
3695
|
+
for (const region of REGION_KEYS) (async () => {
|
|
3696
|
+
const stack = stacks[region];
|
|
3697
|
+
try {
|
|
3698
|
+
const models = await stack.discoverModels();
|
|
3699
|
+
if (stopped) return;
|
|
3700
|
+
stack.catalog.set(derive(current(), models, region));
|
|
3701
|
+
} catch (error) {
|
|
3702
|
+
ctx.logger.warn(`dsh-connect-trae: live ${region} model directory unavailable; serving the configured catalog`, error);
|
|
3703
|
+
stack.catalog.set(configuredModels(current(), region));
|
|
3704
|
+
}
|
|
3705
|
+
stack.invalidateAdapter();
|
|
3706
|
+
})();
|
|
3160
3707
|
}).catch((error) => {
|
|
3161
|
-
ctx.logger.error("dsh-connect-trae: loopback shim failed;
|
|
3708
|
+
ctx.logger.error("dsh-connect-trae: loopback shim failed; providers not registered", error);
|
|
3162
3709
|
});
|
|
3163
3710
|
}
|
|
3164
3711
|
//#endregion
|
|
3165
|
-
export { Config, FALLBACK_TRAE_MODELS, SseDecoder, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, 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, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, hashTraeRawChatArg, identityHeaders, inject, mergeTraeModelSources, name, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeIdentity, refreshTraeCredential, registerTraeUsageRoute, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage };
|
|
3712
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, 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, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeIdentity, refreshTraeCredential, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
|