dsh-connect-trae 3.7.0 → 3.8.1
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 +147 -0
- package/README.en.md +17 -11
- package/README.md +20 -14
- package/lib/client.js +92 -37
- package/lib/index.d.ts +281 -38
- package/lib/index.js +787 -270
- package/package.json +16 -15
package/lib/index.js
CHANGED
|
@@ -129,9 +129,16 @@ function traeInputModalities(model) {
|
|
|
129
129
|
* (wire resolution, callable-key filtering) must keep matching the
|
|
130
130
|
* undecorated name; only the model rows handed to DSH (adapter catalog and
|
|
131
131
|
* model discovery) use this decorated name.
|
|
132
|
+
*
|
|
133
|
+
* `withRate` is false for the ENTERPRISE panel. The multiplier is a
|
|
134
|
+
* consumer-pool figure — personal credits per token, read from the consumer
|
|
135
|
+
* directory — and the tenant pool does not bill against it, so printing it in
|
|
136
|
+
* the model picker would price a model the tenant is not charged for. The rate
|
|
137
|
+
* is part of the NAME that reaches DSH, which is why this is a parameter here
|
|
138
|
+
* and not a rendering decision downstream.
|
|
132
139
|
*/
|
|
133
|
-
function traeModelDisplayName(model) {
|
|
134
|
-
return model.creditMultiplier === void 0 ? model.name : `${model.name} · x${model.creditMultiplier.toFixed(2)}`;
|
|
140
|
+
function traeModelDisplayName(model, withRate = true) {
|
|
141
|
+
return !withRate || model.creditMultiplier === void 0 ? model.name : `${model.name} · x${model.creditMultiplier.toFixed(2)}`;
|
|
135
142
|
}
|
|
136
143
|
/**
|
|
137
144
|
* Whether one model should be offered image input.
|
|
@@ -499,24 +506,61 @@ function readManifest(directory) {
|
|
|
499
506
|
/** Provider route this bundle owns for the domestic (CN) gateway. */
|
|
500
507
|
const TRAE_PROVIDER = "trae";
|
|
501
508
|
/**
|
|
509
|
+
* Provider route this bundle owns for the CN **enterprise (tenant)** gateway.
|
|
510
|
+
*
|
|
511
|
+
* A separate provider id, not a mode switch, because the two pools bill
|
|
512
|
+
* differently: a tenant account (`tob_online`) is served by BOTH gateways with
|
|
513
|
+
* the SAME credential, and only the enterprise one draws on the enterprise
|
|
514
|
+
* pool. Exposing one id would force a per-call guess about who pays.
|
|
515
|
+
*
|
|
516
|
+
* It never falls back to the consumer pool: picking this provider is the user
|
|
517
|
+
* saying "bill the tenant", so a refusal is reported as an error instead of
|
|
518
|
+
* being silently re-run against the personal quota.
|
|
519
|
+
*/
|
|
520
|
+
const TRAE_ENTERPRISE_PROVIDER = "trae-enterprise";
|
|
521
|
+
/**
|
|
502
522
|
* Provider route this bundle owns for the international (trae.ai) gateway.
|
|
503
523
|
* Named `-global` to match the plugin family's convention (`workbuddy-global`);
|
|
504
524
|
* the internal region bucket stays `ai`, which is the gateway/protocol name.
|
|
505
525
|
*/
|
|
506
526
|
const TRAE_AI_PROVIDER = "trae-global";
|
|
507
|
-
/** The provider
|
|
527
|
+
/** The provider ids one region publishes, in model-picker order. */
|
|
528
|
+
const TRAE_PROVIDER_IDS = {
|
|
529
|
+
cn: [TRAE_PROVIDER, TRAE_ENTERPRISE_PROVIDER],
|
|
530
|
+
ai: [TRAE_AI_PROVIDER]
|
|
531
|
+
};
|
|
532
|
+
/** Each region's default (first) provider id. */
|
|
508
533
|
const TRAE_PROVIDERS = {
|
|
509
534
|
cn: TRAE_PROVIDER,
|
|
510
535
|
ai: TRAE_AI_PROVIDER
|
|
511
536
|
};
|
|
537
|
+
const TRAE_PROVIDER_ROUTES = {
|
|
538
|
+
[TRAE_PROVIDER]: "solo",
|
|
539
|
+
[TRAE_ENTERPRISE_PROVIDER]: "enterprise",
|
|
540
|
+
[TRAE_AI_PROVIDER]: "solo"
|
|
541
|
+
};
|
|
542
|
+
/** Chat pool a provider id bills against; `undefined` for an unknown id. */
|
|
543
|
+
function routeOfTraeProvider(provider) {
|
|
544
|
+
return TRAE_PROVIDER_ROUTES[provider];
|
|
545
|
+
}
|
|
512
546
|
/** Human-readable provider names, shown in the DSH model picker. */
|
|
513
547
|
const TRAE_PROVIDER_DISPLAY_NAMES = {
|
|
514
548
|
cn: "Trae",
|
|
515
549
|
ai: "Trae Global"
|
|
516
550
|
};
|
|
551
|
+
/** Display name per provider id (the region table above only names the default). */
|
|
552
|
+
const TRAE_PROVIDER_ID_DISPLAY_NAMES = {
|
|
553
|
+
[TRAE_PROVIDER]: "Trae",
|
|
554
|
+
[TRAE_ENTERPRISE_PROVIDER]: "Trae 企业版",
|
|
555
|
+
[TRAE_AI_PROVIDER]: "Trae Global"
|
|
556
|
+
};
|
|
557
|
+
/** Display name of a provider id. */
|
|
558
|
+
function displayNameOfTraeProvider(provider) {
|
|
559
|
+
return TRAE_PROVIDER_ID_DISPLAY_NAMES[provider] ?? provider;
|
|
560
|
+
}
|
|
517
561
|
/** Region a provider route id belongs to. */
|
|
518
562
|
function regionOfTraeProvider(provider) {
|
|
519
|
-
for (const [region,
|
|
563
|
+
for (const [region, ids] of Object.entries(TRAE_PROVIDER_IDS)) if (ids.includes(provider)) return region;
|
|
520
564
|
}
|
|
521
565
|
const TRAE_STREAM_IDLE_TIMEOUT_MS = 3e5;
|
|
522
566
|
const INERT_AUTH = {
|
|
@@ -549,10 +593,10 @@ const REQUEST_IMAGE_BUDGETS = {
|
|
|
549
593
|
* failing the entire provider route (`INVALID_MODEL_CONTEXT`).
|
|
550
594
|
*/
|
|
551
595
|
const FALLBACK_CONTEXT_WINDOW = 2e5;
|
|
552
|
-
function toPiModel(info, baseUrl, providerId) {
|
|
596
|
+
function toPiModel(info, baseUrl, providerId, withRate = true) {
|
|
553
597
|
return {
|
|
554
598
|
id: info.id,
|
|
555
|
-
name: traeModelDisplayName(info),
|
|
599
|
+
name: traeModelDisplayName(info, withRate),
|
|
556
600
|
api: "openai-completions",
|
|
557
601
|
provider: providerId,
|
|
558
602
|
baseUrl,
|
|
@@ -770,7 +814,12 @@ function createTraeAdapter(options) {
|
|
|
770
814
|
const providerId = options.provider ?? "trae";
|
|
771
815
|
const providerName = options.displayName ?? "Trae";
|
|
772
816
|
const generation = options.piAiGeneration ?? piAiRuntimeInfo().generation;
|
|
773
|
-
|
|
817
|
+
/**
|
|
818
|
+
* The enterprise provider shows no rate: the multiplier belongs to the
|
|
819
|
+
* consumer pool, and this provider bills the tenant pool.
|
|
820
|
+
*/
|
|
821
|
+
const showRate = options.provider !== TRAE_ENTERPRISE_PROVIDER;
|
|
822
|
+
const buildModels = () => options.catalog.current().map((info) => toPiModel(info, `${options.shim.baseUrl()}/v1`, providerId, showRate));
|
|
774
823
|
const provider = {
|
|
775
824
|
...createProvider({
|
|
776
825
|
id: providerId,
|
|
@@ -806,7 +855,7 @@ function createTraeAdapter(options) {
|
|
|
806
855
|
adapter: new PiAiAdapter({
|
|
807
856
|
profiles: () => profiles,
|
|
808
857
|
auth: INERT_AUTH,
|
|
809
|
-
resolveApiKey: async () => options.shim.token(),
|
|
858
|
+
resolveApiKey: async () => options.secret ?? options.shim.token(),
|
|
810
859
|
...options.resolveAttachments === void 0 ? {} : { resolveAttachments: options.resolveAttachments }
|
|
811
860
|
}),
|
|
812
861
|
invalidate() {
|
|
@@ -815,7 +864,113 @@ function createTraeAdapter(options) {
|
|
|
815
864
|
};
|
|
816
865
|
}
|
|
817
866
|
//#endregion
|
|
867
|
+
//#region src/region.ts
|
|
868
|
+
/** Every panel, in card tab order. */
|
|
869
|
+
const TRAE_PANELS = [
|
|
870
|
+
"cn",
|
|
871
|
+
"enterprise",
|
|
872
|
+
"ai"
|
|
873
|
+
];
|
|
874
|
+
/** The region whose credential/account pool a panel draws on. */
|
|
875
|
+
function regionOfPanel(panel) {
|
|
876
|
+
return panel === "ai" ? "ai" : "cn";
|
|
877
|
+
}
|
|
878
|
+
/** The provider id a panel publishes. */
|
|
879
|
+
function providerOfPanel(panel) {
|
|
880
|
+
if (panel === "enterprise") return "trae-enterprise";
|
|
881
|
+
return panel === "ai" ? "trae-global" : "trae";
|
|
882
|
+
}
|
|
883
|
+
/** The panel that owns a provider id, or undefined for an unknown id. */
|
|
884
|
+
function panelOfProvider(provider) {
|
|
885
|
+
return TRAE_PANELS.find((panel) => providerOfPanel(panel) === provider);
|
|
886
|
+
}
|
|
887
|
+
/** Narrow a stored settings key to a panel. */
|
|
888
|
+
function panelOfKey(value) {
|
|
889
|
+
return TRAE_PANELS.includes(value) ? value : void 0;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Verified gateway bases per region (docs/INTL_SG_EVIDENCE.md §2).
|
|
893
|
+
* The AI gateway is shared by both international installs (Trae desktop and
|
|
894
|
+
* TRAE SOLO); its mchost shards (`api16/api22-normal-alisg.mchost.guru`)
|
|
895
|
+
* answer the same bytes but stay internal — `coresg-normal.trae.ai` is the
|
|
896
|
+
* single stable entry point.
|
|
897
|
+
*/
|
|
898
|
+
const REGION_GATEWAYS = {
|
|
899
|
+
cn: {
|
|
900
|
+
chat: "https://trae-api-cn.mchost.guru",
|
|
901
|
+
remote: "https://solo.trae.cn/api/remote/v1",
|
|
902
|
+
pay: "https://api.trae.cn"
|
|
903
|
+
},
|
|
904
|
+
ai: {
|
|
905
|
+
chat: "https://coresg-normal.trae.ai",
|
|
906
|
+
remote: "https://coresg-normal.trae.ai/api/remote/v1",
|
|
907
|
+
pay: "https://growsg-normal.trae.ai"
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* Enterprise (tenant) chat gateway for a CN credential that belongs to a
|
|
912
|
+
* `tob_online` tenant.
|
|
913
|
+
*
|
|
914
|
+
* This is a SECOND chat gateway on the same credential, not a second region:
|
|
915
|
+
* the roster, the credential and the protocol are the same as `cn`, but a
|
|
916
|
+
* tenant account is billed against the enterprise pool when the request goes
|
|
917
|
+
* here. Measured 2026-10-17 on a live `Trae CN` tenant account: the same model
|
|
918
|
+
* served from this host does not move the personal `consumed_amount`, while
|
|
919
|
+
* the same call to `trae-api-cn.mchost.guru` charges it.
|
|
920
|
+
*
|
|
921
|
+
* Two wire differences, both load-bearing:
|
|
922
|
+
* - the request must carry a per-call `session_id` (the consumer gateway
|
|
923
|
+
* rejects it with `4001`), and
|
|
924
|
+
* - a refused call answers **HTTP 200** whose FIRST SSE frame is
|
|
925
|
+
* `event:error` with `code: 4001` — not an HTTP error status, so the
|
|
926
|
+
* refusal is only visible if the response body is inspected.
|
|
927
|
+
*/
|
|
928
|
+
const TRAE_ENTERPRISE_HOST = "https://console.enterprise.trae.cn";
|
|
929
|
+
/** Region for an edition label: the international installs belong to `ai`. */
|
|
930
|
+
function regionOfEdition(edition) {
|
|
931
|
+
return edition === "sg" || edition === "solo-sg" ? "ai" : "cn";
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Region from the credential's `userRegion` claim. The desktop storage spells
|
|
935
|
+
* it as an object (`{"region":"CN","_aiRegion":"CN"}`); the app logs also
|
|
936
|
+
* spell the bare value lowercase (`"sg"`). Both are accepted, case-blind.
|
|
937
|
+
*/
|
|
938
|
+
function regionOfUserRegion(value) {
|
|
939
|
+
const raw = typeof value === "object" && value !== null && !Array.isArray(value) ? value["region"] : value;
|
|
940
|
+
if (typeof raw !== "string") return void 0;
|
|
941
|
+
const lowered = raw.trim().toLowerCase();
|
|
942
|
+
if (lowered === "cn") return "cn";
|
|
943
|
+
if (lowered === "sg" || lowered === "ai") return "ai";
|
|
944
|
+
}
|
|
945
|
+
/** Region from a credential host (`.trae.ai` → ai, `.trae.cn`/`.trae.com.cn` → cn). */
|
|
946
|
+
function regionOfHost(host) {
|
|
947
|
+
if (host === void 0) return void 0;
|
|
948
|
+
const trimmed = host.trim();
|
|
949
|
+
if (trimmed === "") return void 0;
|
|
950
|
+
let hostname;
|
|
951
|
+
try {
|
|
952
|
+
hostname = new URL(trimmed.startsWith("http") ? trimmed : `https://${trimmed}`).hostname;
|
|
953
|
+
} catch {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (hostname === "trae.ai" || hostname.endsWith(".trae.ai")) return "ai";
|
|
957
|
+
if (hostname === "trae.cn" || hostname.endsWith(".trae.cn") || hostname.endsWith(".trae.com.cn")) return "cn";
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Region of a credential: the `userRegion` claim wins, the host suffix is the
|
|
961
|
+
* fallback, and the edition label is the last resort. Every level is derived
|
|
962
|
+
* from data the credential itself carries, so no user configuration is needed.
|
|
963
|
+
*/
|
|
964
|
+
function regionOfCredential(credential) {
|
|
965
|
+
return regionOfUserRegion(credential.userRegion) ?? regionOfHost(credential.host) ?? regionOfEdition(credential.edition);
|
|
966
|
+
}
|
|
967
|
+
//#endregion
|
|
818
968
|
//#region src/status-paths.ts
|
|
969
|
+
/**
|
|
970
|
+
* Node-free constants and types shared by the Host and browser halves.
|
|
971
|
+
* Mirrors the `dsh-workbuddy-connect` status-route pattern so the plugin card
|
|
972
|
+
* stays consistent with that project's external presentation.
|
|
973
|
+
*/
|
|
819
974
|
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
820
975
|
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
821
976
|
/** Plugin-owned live model refresh endpoint. */
|
|
@@ -882,8 +1037,8 @@ const TRAE_REGIONS = ["cn", "ai"];
|
|
|
882
1037
|
* Address one region's status route. The two regions are separate provider
|
|
883
1038
|
* stacks; every card request carries the region whose tab the user is on.
|
|
884
1039
|
*/
|
|
885
|
-
function withTraeRegion(path,
|
|
886
|
-
return `${path}?${TRAE_REGION_PARAM}=${
|
|
1040
|
+
function withTraeRegion(path, panel) {
|
|
1041
|
+
return `${path}?${TRAE_REGION_PARAM}=${panel}`;
|
|
887
1042
|
}
|
|
888
1043
|
/**
|
|
889
1044
|
* Read the region parameter off a status-route URL. Absent means the domestic
|
|
@@ -894,7 +1049,7 @@ function regionOfTraeStatusUrl(url) {
|
|
|
894
1049
|
const at = url.indexOf("?");
|
|
895
1050
|
const value = at === -1 ? null : new URLSearchParams(url.slice(at + 1)).get(TRAE_REGION_PARAM);
|
|
896
1051
|
if (value === null || value === "") return "cn";
|
|
897
|
-
return
|
|
1052
|
+
return TRAE_PANELS.includes(value) ? value : void 0;
|
|
898
1053
|
}
|
|
899
1054
|
/**
|
|
900
1055
|
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
@@ -971,11 +1126,11 @@ function followAppLoginEnabled(configured) {
|
|
|
971
1126
|
* region's picks. Tolerates any stored shape (absent, non-object) by starting
|
|
972
1127
|
* from an empty document.
|
|
973
1128
|
*/
|
|
974
|
-
function nextRegionSlots(regions,
|
|
1129
|
+
function nextRegionSlots(regions, panel, slot) {
|
|
975
1130
|
const unwrapped = unwrapVolatileDeep(regions);
|
|
976
1131
|
return {
|
|
977
1132
|
...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
|
|
978
|
-
[
|
|
1133
|
+
[panel]: slot
|
|
979
1134
|
};
|
|
980
1135
|
}
|
|
981
1136
|
/**
|
|
@@ -1000,8 +1155,8 @@ function regionsMapOf(value) {
|
|
|
1000
1155
|
return record;
|
|
1001
1156
|
}
|
|
1002
1157
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
1003
|
-
function regionSlotOf(value,
|
|
1004
|
-
const slot = unwrapVolatile(regionsMapOf(value)[
|
|
1158
|
+
function regionSlotOf(value, panel) {
|
|
1159
|
+
const slot = unwrapVolatile(regionsMapOf(value)[panel]);
|
|
1005
1160
|
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
1006
1161
|
}
|
|
1007
1162
|
/**
|
|
@@ -1014,8 +1169,10 @@ function regionSlotOf(value, region) {
|
|
|
1014
1169
|
* `value` may be the whole settings section or the `regions` map (see
|
|
1015
1170
|
* {@link regionsMapOf}).
|
|
1016
1171
|
*/
|
|
1017
|
-
function regionEnabledOf(value,
|
|
1018
|
-
|
|
1172
|
+
function regionEnabledOf(value, panel) {
|
|
1173
|
+
const stored = regionSlotOf(value, panel)["enabled"];
|
|
1174
|
+
if (typeof stored === "boolean") return stored;
|
|
1175
|
+
return panel !== "enterprise";
|
|
1019
1176
|
}
|
|
1020
1177
|
/**
|
|
1021
1178
|
* Build the next `regions` settings value for a provider on/off toggle. ONLY
|
|
@@ -1033,9 +1190,9 @@ function regionEnabledOf(value, region) {
|
|
|
1033
1190
|
* value is always the `regions` map, i.e. exactly what `settingsScope.set(
|
|
1034
1191
|
* 'regions', ...)` needs.
|
|
1035
1192
|
*/
|
|
1036
|
-
function nextRegionEnabled(value,
|
|
1037
|
-
return nextRegionSlots(regionsMapOf(value),
|
|
1038
|
-
...regionSlotOf(value,
|
|
1193
|
+
function nextRegionEnabled(value, panel, enabled) {
|
|
1194
|
+
return nextRegionSlots(regionsMapOf(value), panel, {
|
|
1195
|
+
...regionSlotOf(value, panel),
|
|
1039
1196
|
enabled
|
|
1040
1197
|
});
|
|
1041
1198
|
}
|
|
@@ -1052,8 +1209,8 @@ function nextRegionEnabled(value, region, enabled) {
|
|
|
1052
1209
|
* `value` may be the whole settings section or the `regions` map (see
|
|
1053
1210
|
* {@link regionsMapOf}).
|
|
1054
1211
|
*/
|
|
1055
|
-
function regionCreditPositionOf(value,
|
|
1056
|
-
return regionSlotOf(value,
|
|
1212
|
+
function regionCreditPositionOf(value, panel) {
|
|
1213
|
+
return regionSlotOf(value, panel)["creditPosition"] === "stats" ? "stats" : "composer";
|
|
1057
1214
|
}
|
|
1058
1215
|
/**
|
|
1059
1216
|
* Build the next `regions` settings value for one region's credit seat.
|
|
@@ -1066,9 +1223,9 @@ function regionCreditPositionOf(value, region) {
|
|
|
1066
1223
|
* value is always the `regions` map, i.e. exactly what `settingsScope.set(
|
|
1067
1224
|
* 'regions', ...)` needs.
|
|
1068
1225
|
*/
|
|
1069
|
-
function nextRegionCreditPosition(value,
|
|
1070
|
-
return nextRegionSlots(regionsMapOf(value),
|
|
1071
|
-
...regionSlotOf(value,
|
|
1226
|
+
function nextRegionCreditPosition(value, panel, position) {
|
|
1227
|
+
return nextRegionSlots(regionsMapOf(value), panel, {
|
|
1228
|
+
...regionSlotOf(value, panel),
|
|
1072
1229
|
creditPosition: position
|
|
1073
1230
|
});
|
|
1074
1231
|
}
|
|
@@ -1564,65 +1721,6 @@ function traeCliCandidates(platform = process.platform, home = homedir(), env =
|
|
|
1564
1721
|
return result;
|
|
1565
1722
|
}
|
|
1566
1723
|
//#endregion
|
|
1567
|
-
//#region src/region.ts
|
|
1568
|
-
/**
|
|
1569
|
-
* Verified gateway bases per region (docs/INTL_SG_EVIDENCE.md §2).
|
|
1570
|
-
* The AI gateway is shared by both international installs (Trae desktop and
|
|
1571
|
-
* TRAE SOLO); its mchost shards (`api16/api22-normal-alisg.mchost.guru`)
|
|
1572
|
-
* answer the same bytes but stay internal — `coresg-normal.trae.ai` is the
|
|
1573
|
-
* single stable entry point.
|
|
1574
|
-
*/
|
|
1575
|
-
const REGION_GATEWAYS = {
|
|
1576
|
-
cn: {
|
|
1577
|
-
chat: "https://trae-api-cn.mchost.guru",
|
|
1578
|
-
remote: "https://solo.trae.cn/api/remote/v1",
|
|
1579
|
-
pay: "https://api.trae.cn"
|
|
1580
|
-
},
|
|
1581
|
-
ai: {
|
|
1582
|
-
chat: "https://coresg-normal.trae.ai",
|
|
1583
|
-
remote: "https://coresg-normal.trae.ai/api/remote/v1",
|
|
1584
|
-
pay: "https://growsg-normal.trae.ai"
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1587
|
-
/** Region for an edition label: the international installs belong to `ai`. */
|
|
1588
|
-
function regionOfEdition(edition) {
|
|
1589
|
-
return edition === "sg" || edition === "solo-sg" ? "ai" : "cn";
|
|
1590
|
-
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Region from the credential's `userRegion` claim. The desktop storage spells
|
|
1593
|
-
* it as an object (`{"region":"CN","_aiRegion":"CN"}`); the app logs also
|
|
1594
|
-
* spell the bare value lowercase (`"sg"`). Both are accepted, case-blind.
|
|
1595
|
-
*/
|
|
1596
|
-
function regionOfUserRegion(value) {
|
|
1597
|
-
const raw = typeof value === "object" && value !== null && !Array.isArray(value) ? value["region"] : value;
|
|
1598
|
-
if (typeof raw !== "string") return void 0;
|
|
1599
|
-
const lowered = raw.trim().toLowerCase();
|
|
1600
|
-
if (lowered === "cn") return "cn";
|
|
1601
|
-
if (lowered === "sg" || lowered === "ai") return "ai";
|
|
1602
|
-
}
|
|
1603
|
-
/** Region from a credential host (`.trae.ai` → ai, `.trae.cn`/`.trae.com.cn` → cn). */
|
|
1604
|
-
function regionOfHost(host) {
|
|
1605
|
-
if (host === void 0) return void 0;
|
|
1606
|
-
const trimmed = host.trim();
|
|
1607
|
-
if (trimmed === "") return void 0;
|
|
1608
|
-
let hostname;
|
|
1609
|
-
try {
|
|
1610
|
-
hostname = new URL(trimmed.startsWith("http") ? trimmed : `https://${trimmed}`).hostname;
|
|
1611
|
-
} catch {
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
if (hostname === "trae.ai" || hostname.endsWith(".trae.ai")) return "ai";
|
|
1615
|
-
if (hostname === "trae.cn" || hostname.endsWith(".trae.cn") || hostname.endsWith(".trae.com.cn")) return "cn";
|
|
1616
|
-
}
|
|
1617
|
-
/**
|
|
1618
|
-
* Region of a credential: the `userRegion` claim wins, the host suffix is the
|
|
1619
|
-
* fallback, and the edition label is the last resort. Every level is derived
|
|
1620
|
-
* from data the credential itself carries, so no user configuration is needed.
|
|
1621
|
-
*/
|
|
1622
|
-
function regionOfCredential(credential) {
|
|
1623
|
-
return regionOfUserRegion(credential.userRegion) ?? regionOfHost(credential.host) ?? regionOfEdition(credential.edition);
|
|
1624
|
-
}
|
|
1625
|
-
//#endregion
|
|
1626
1724
|
//#region src/auth.ts
|
|
1627
1725
|
/** Host used for CLI tokens, which carry no host claim of their own. */
|
|
1628
1726
|
const CLI_DEFAULT_HOST = "https://api.trae.cn";
|
|
@@ -1688,12 +1786,15 @@ function normalizeTraeCredential(raw, edition, source) {
|
|
|
1688
1786
|
const refreshExpiresAtMs = timeToMs(value["refreshExpiredAt"] ?? value["refreshExpiresAt"]);
|
|
1689
1787
|
const refreshToken = optionalString(value["refreshToken"]);
|
|
1690
1788
|
const userRegion = userRegionOf(value["userRegion"]);
|
|
1691
|
-
const
|
|
1789
|
+
const account = typeof value["account"] === "object" && value["account"] !== null && !Array.isArray(value["account"]) ? value["account"] : void 0;
|
|
1790
|
+
const accountName = optionalString(account?.["username"]);
|
|
1791
|
+
const tenantId = optionalString(account?.["tenant_id"]) ?? (typeof account?.["tenant_id"] === "number" ? String(account["tenant_id"]) : void 0);
|
|
1692
1792
|
return {
|
|
1693
1793
|
accessToken,
|
|
1694
1794
|
...refreshToken === void 0 ? {} : { refreshToken },
|
|
1695
1795
|
userId: optionalString(value["userId"]) ?? "",
|
|
1696
1796
|
...accountName === void 0 ? {} : { accountName },
|
|
1797
|
+
...tenantId === void 0 ? {} : { tenantId },
|
|
1697
1798
|
host: optionalString(value["host"]) ?? "",
|
|
1698
1799
|
...userRegion === void 0 ? {} : { userRegion },
|
|
1699
1800
|
expiresAtMs,
|
|
@@ -1702,6 +1803,18 @@ function normalizeTraeCredential(raw, edition, source) {
|
|
|
1702
1803
|
source
|
|
1703
1804
|
};
|
|
1704
1805
|
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Whether this account belongs to a Trae tenant, i.e. may bill the enterprise
|
|
1808
|
+
* pool.
|
|
1809
|
+
*
|
|
1810
|
+
* Evidence (2026-10-17, live): the tenant's own account carries
|
|
1811
|
+
* `account.tenant_id` / `tenant_name` and `scope: 'saas'`; the personal SOLO
|
|
1812
|
+
* account carries neither and reports `scope: 'marscode'`. Only the tenant one
|
|
1813
|
+
* streams on the enterprise gateway — the other answers `4019` risk control.
|
|
1814
|
+
*/
|
|
1815
|
+
function isEnterpriseAccount(credential) {
|
|
1816
|
+
return credential?.tenantId !== void 0 && credential.tenantId.trim() !== "";
|
|
1817
|
+
}
|
|
1705
1818
|
function traeAccountId(credential) {
|
|
1706
1819
|
const stable = `${credential.edition}\0${credential.userId || credential.accountName || "unknown"}`;
|
|
1707
1820
|
return createHash("sha256").update(stable).digest("hex").slice(0, 24);
|
|
@@ -1718,7 +1831,10 @@ function credentialOfStored(stored) {
|
|
|
1718
1831
|
userId: record["userId"],
|
|
1719
1832
|
host: record["host"],
|
|
1720
1833
|
userRegion: record["userRegion"],
|
|
1721
|
-
account: record["accountName"] === void 0 ? void 0 : {
|
|
1834
|
+
account: record["accountName"] === void 0 && record["tenantId"] === void 0 ? void 0 : {
|
|
1835
|
+
...record["accountName"] === void 0 ? {} : { username: record["accountName"] },
|
|
1836
|
+
...record["tenantId"] === void 0 ? {} : { tenant_id: record["tenantId"] }
|
|
1837
|
+
},
|
|
1722
1838
|
expiredAt: record["expiresAtMs"],
|
|
1723
1839
|
refreshExpiredAt: record["refreshExpiredAtMs"]
|
|
1724
1840
|
}, edition, "dsh");
|
|
@@ -1773,6 +1889,8 @@ var TraeCredentialStore = class {
|
|
|
1773
1889
|
edition;
|
|
1774
1890
|
accountId;
|
|
1775
1891
|
region;
|
|
1892
|
+
requireTenant;
|
|
1893
|
+
hideTenantAccounts;
|
|
1776
1894
|
ownPathExplicit;
|
|
1777
1895
|
legacyOwnPath;
|
|
1778
1896
|
legacyOwnPathExplicit;
|
|
@@ -1784,6 +1902,8 @@ var TraeCredentialStore = class {
|
|
|
1784
1902
|
this.edition = options.edition ?? "auto";
|
|
1785
1903
|
this.accountId = options.accountId;
|
|
1786
1904
|
this.region = options.region;
|
|
1905
|
+
this.requireTenant = options.requireTenant === true;
|
|
1906
|
+
this.hideTenantAccounts = options.hideTenantAccounts === true;
|
|
1787
1907
|
this.ownPathExplicit = options.ownPath;
|
|
1788
1908
|
this.legacyOwnPath = options.legacyOwnPath ?? legacyTraeOwnAuthPath();
|
|
1789
1909
|
this.legacyOwnPathExplicit = options.legacyOwnPath;
|
|
@@ -1867,7 +1987,9 @@ var TraeCredentialStore = class {
|
|
|
1867
1987
|
* the plugin performed is never discarded by this preference.
|
|
1868
1988
|
*/
|
|
1869
1989
|
preferred(credentials) {
|
|
1870
|
-
|
|
1990
|
+
const eligible = this.requireTenant ? credentials.filter((credential) => isEnterpriseAccount(credential)) : credentials;
|
|
1991
|
+
if (eligible.length === 0) return void 0;
|
|
1992
|
+
return eligible.find((credential) => credential.source === "desktop") ?? eligible[0];
|
|
1871
1993
|
}
|
|
1872
1994
|
/**
|
|
1873
1995
|
* The account the Trae app is signed in to right now, when that differs from
|
|
@@ -1891,6 +2013,7 @@ var TraeCredentialStore = class {
|
|
|
1891
2013
|
id: liveId,
|
|
1892
2014
|
accountName: live.accountName ?? (live.userId || `${live.edition} account`),
|
|
1893
2015
|
edition: live.edition,
|
|
2016
|
+
enterprise: isEnterpriseAccount(live),
|
|
1894
2017
|
region: regionOfCredential(live),
|
|
1895
2018
|
source: live.source,
|
|
1896
2019
|
tokenExpiresAtMs: live.expiresAtMs,
|
|
@@ -1898,25 +2021,94 @@ var TraeCredentialStore = class {
|
|
|
1898
2021
|
selected: false
|
|
1899
2022
|
};
|
|
1900
2023
|
}
|
|
2024
|
+
/**
|
|
2025
|
+
* The accounts this store is willing to OFFER.
|
|
2026
|
+
*
|
|
2027
|
+
* Scoped to who this store may bill:
|
|
2028
|
+
* - a consumer store withholds tenant accounts (the enterprise pool is a
|
|
2029
|
+
* separate tab and a separate billing decision), and
|
|
2030
|
+
* - a tenant store withholds personal accounts (they cannot bill that
|
|
2031
|
+
* pool; the tenant gateway answers their chat with 4019).
|
|
2032
|
+
*
|
|
2033
|
+
* Either filter backs off when it would hide EVERYTHING, so a machine whose
|
|
2034
|
+
* only login is on the other side keeps working instead of reading as
|
|
2035
|
+
* "signed out".
|
|
2036
|
+
*/
|
|
2037
|
+
offered(discovered) {
|
|
2038
|
+
const eligible = this.requireTenant ? discovered.filter((credential) => isEnterpriseAccount(credential)) : this.hideTenantAccounts ? discovered.filter((credential) => !isEnterpriseAccount(credential)) : discovered;
|
|
2039
|
+
return eligible.length > 0 ? eligible : discovered;
|
|
2040
|
+
}
|
|
1901
2041
|
async accounts() {
|
|
1902
|
-
const
|
|
1903
|
-
const
|
|
1904
|
-
const
|
|
2042
|
+
const discovered = await this.readAll();
|
|
2043
|
+
const offered = this.offered(discovered);
|
|
2044
|
+
const inUse = this.inUse(discovered, offered);
|
|
2045
|
+
/**
|
|
2046
|
+
* Never hide the account we are actually billing.
|
|
2047
|
+
*
|
|
2048
|
+
* An explicit binding outranks the display filter (`inUse` honours it even
|
|
2049
|
+
* when the offered list withholds it), and a list whose active row is
|
|
2050
|
+
* invisible is worse than no list: the card's "save the selected account"
|
|
2051
|
+
* press reads `selected`, so a hidden binding would be rewritten to whichever
|
|
2052
|
+
* account merely happened to be DISPLAYED — silently moving the user's
|
|
2053
|
+
* billing to an account they never chose. Re-appending it costs one extra
|
|
2054
|
+
* row and makes the card tell the truth.
|
|
2055
|
+
*/
|
|
2056
|
+
const credentials = inUse !== void 0 && !offered.includes(inUse) ? [...offered, inUse] : offered;
|
|
2057
|
+
const inUseId = inUse === void 0 ? void 0 : traeAccountId(inUse);
|
|
1905
2058
|
return credentials.map((credential) => ({
|
|
1906
2059
|
id: traeAccountId(credential),
|
|
1907
2060
|
accountName: credential.accountName ?? (credential.userId || `${credential.edition} account`),
|
|
1908
2061
|
edition: credential.edition,
|
|
2062
|
+
enterprise: isEnterpriseAccount(credential),
|
|
1909
2063
|
region: regionOfCredential(credential),
|
|
1910
2064
|
source: credential.source,
|
|
1911
2065
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
1912
2066
|
...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
|
|
1913
|
-
selected:
|
|
2067
|
+
selected: inUseId !== void 0 && traeAccountId(credential) === inUseId
|
|
1914
2068
|
}));
|
|
1915
2069
|
}
|
|
2070
|
+
/**
|
|
2071
|
+
* The credential this store bills, given every credential it can see.
|
|
2072
|
+
*
|
|
2073
|
+
* This is the ONE resolution rule, and it exists so that `current()` and
|
|
2074
|
+
* `accounts()` cannot disagree: `current()` applies it to `readAll()`, and
|
|
2075
|
+
* `accounts()` applies the SAME verdict to decide which row reads as
|
|
2076
|
+
* `selected`. Deriving that flag independently is exactly how the two drifted
|
|
2077
|
+
* apart — and a card that marks a different account than the one the chat
|
|
2078
|
+
* bills is worse than no card at all, because a press that "saves the
|
|
2079
|
+
* selected account" then rewrites the binding to the account that was only
|
|
2080
|
+
* ever DISPLAYED.
|
|
2081
|
+
*
|
|
2082
|
+
* Order of business:
|
|
2083
|
+
* 1. No binding — the detected default (`preferred`) is in use, chosen from
|
|
2084
|
+
* the OFFERED list rather than from every login on the machine. A
|
|
2085
|
+
* consumer store that withholds tenant accounts must not default its
|
|
2086
|
+
* billing to the tenant pool merely because that login sorts first;
|
|
2087
|
+
* withholding is a billing decision, not a display detail.
|
|
2088
|
+
* 2. A binding — honour it verbatim, even when the OFFERED list hides that
|
|
2089
|
+
* account. Resolution is never scoped by `hideTenantAccounts`: the user's
|
|
2090
|
+
* explicit choice outranks a display filter.
|
|
2091
|
+
* 3. A tenant-scoped store cannot bill through a personal account, and the
|
|
2092
|
+
* tenant gateway only reveals that at the first chat (`4019`). Honour the
|
|
2093
|
+
* binding when it is eligible; otherwise fall back to the preferred
|
|
2094
|
+
* tenant account, which is the one the user's enterprise tab is meant to
|
|
2095
|
+
* use.
|
|
2096
|
+
* 4. A saved account can disappear when Trae replaces its local login. Do
|
|
2097
|
+
* NOT silently fall back to a different account: that would bill a
|
|
2098
|
+
* different account than the one the user explicitly selected. Return
|
|
2099
|
+
* undefined so the caller surfaces "no signed-in account" and the user
|
|
2100
|
+
* can re-select, instead of the plugin quietly switching accounts behind
|
|
2101
|
+
* their back.
|
|
2102
|
+
*/
|
|
2103
|
+
inUse(discovered, offered) {
|
|
2104
|
+
if (this.accountId === void 0) return this.preferred(offered);
|
|
2105
|
+
const selected = discovered.find((credential) => traeAccountId(credential) === this.accountId);
|
|
2106
|
+
if (this.requireTenant && !isEnterpriseAccount(selected)) return this.preferred(offered);
|
|
2107
|
+
return selected;
|
|
2108
|
+
}
|
|
1916
2109
|
async current() {
|
|
1917
|
-
const
|
|
1918
|
-
|
|
1919
|
-
return credentials.find((credential) => traeAccountId(credential) === this.accountId);
|
|
2110
|
+
const discovered = await this.readAll();
|
|
2111
|
+
return this.inUse(discovered, this.offered(discovered));
|
|
1920
2112
|
}
|
|
1921
2113
|
/**
|
|
1922
2114
|
* The stored credential of one specific account, without changing which
|
|
@@ -2664,7 +2856,19 @@ function readBody(req) {
|
|
|
2664
2856
|
});
|
|
2665
2857
|
}
|
|
2666
2858
|
function createTraeShim(options) {
|
|
2667
|
-
|
|
2859
|
+
/**
|
|
2860
|
+
* Resolve the route table. The single-client form becomes a one-entry table
|
|
2861
|
+
* labelled `solo`, so existing call sites and tests keep one code path.
|
|
2862
|
+
*/
|
|
2863
|
+
const routes = options.routes !== void 0 ? options.routes.map((route) => ({
|
|
2864
|
+
...route,
|
|
2865
|
+
secret: randomBytes(32).toString("base64url")
|
|
2866
|
+
})) : [{
|
|
2867
|
+
label: "solo",
|
|
2868
|
+
client: options.client,
|
|
2869
|
+
secret: randomBytes(32).toString("base64url")
|
|
2870
|
+
}];
|
|
2871
|
+
const secret = routes[0]?.secret ?? randomBytes(32).toString("base64url");
|
|
2668
2872
|
const sockets = /* @__PURE__ */ new Set();
|
|
2669
2873
|
const server = createServer((req, res) => {
|
|
2670
2874
|
handle(req, res);
|
|
@@ -2678,18 +2882,22 @@ function createTraeShim(options) {
|
|
|
2678
2882
|
server.once("error", reject);
|
|
2679
2883
|
});
|
|
2680
2884
|
server.listen(0, "127.0.0.1");
|
|
2681
|
-
|
|
2885
|
+
/** Route whose secret matches the request's bearer, or `undefined`. */
|
|
2886
|
+
function routeOf(req) {
|
|
2682
2887
|
const match = typeof req.headers.authorization === "string" ? /^Bearer\s+(.+)$/i.exec(req.headers.authorization.trim()) : null;
|
|
2683
|
-
if (match === null) return
|
|
2888
|
+
if (match === null) return void 0;
|
|
2684
2889
|
const actual = Buffer.from(match[1] ?? "");
|
|
2685
|
-
const
|
|
2686
|
-
|
|
2890
|
+
for (const route of routes) {
|
|
2891
|
+
const expected = Buffer.from(route.secret);
|
|
2892
|
+
if (actual.length === expected.length && timingSafeEqual(actual, expected)) return route;
|
|
2893
|
+
}
|
|
2687
2894
|
}
|
|
2688
2895
|
async function handle(req, res) {
|
|
2689
2896
|
try {
|
|
2690
2897
|
if (!hostIsLoopback(req.headers.host)) return writeError(res, 403, "host_not_allowed", "Host must be loopback");
|
|
2691
2898
|
if (!originIsLoopback(req.headers.origin)) return writeError(res, 403, "origin_not_allowed", "Origin must be loopback");
|
|
2692
|
-
|
|
2899
|
+
const route = routeOf(req);
|
|
2900
|
+
if (route === void 0) return writeError(res, 401, "unauthorized", "Missing or invalid bearer");
|
|
2693
2901
|
const url = req.url ?? "/";
|
|
2694
2902
|
if (req.method === "GET" && (url === "/healthz" || url === "/healthz/")) return writeJson(res, 200, { ok: true });
|
|
2695
2903
|
if (req.method === "GET" && (url === "/v1/models" || url === "/v1/models/")) return writeJson(res, 200, {
|
|
@@ -2723,7 +2931,7 @@ function createTraeShim(options) {
|
|
|
2723
2931
|
const abort = () => controller.abort();
|
|
2724
2932
|
req.once("aborted", abort);
|
|
2725
2933
|
req.socket.once("close", abort);
|
|
2726
|
-
const result = await
|
|
2934
|
+
const result = await route.client.chatStream(raw, controller.signal);
|
|
2727
2935
|
if (!result.ok) return writeError(res, STATUS_BY_KIND[result.kind], result.kind, result.message);
|
|
2728
2936
|
res.writeHead(200, {
|
|
2729
2937
|
"Content-Type": "text/event-stream",
|
|
@@ -2759,6 +2967,12 @@ function createTraeShim(options) {
|
|
|
2759
2967
|
return `http://127.0.0.1:${address.port}`;
|
|
2760
2968
|
},
|
|
2761
2969
|
token: () => secret,
|
|
2970
|
+
tokenOf(label) {
|
|
2971
|
+
if (label === void 0) return secret;
|
|
2972
|
+
const route = routes.find((entry) => entry.label === label);
|
|
2973
|
+
if (route === void 0) throw new Error(`trae shim has no route named ${label}`);
|
|
2974
|
+
return route.secret;
|
|
2975
|
+
},
|
|
2762
2976
|
close: () => new Promise((resolve, reject) => {
|
|
2763
2977
|
for (const socket of sockets) socket.destroy();
|
|
2764
2978
|
server.close((error) => error === void 0 ? resolve() : reject(error));
|
|
@@ -3107,7 +3321,7 @@ var TraeSoloUpstreamClient = class {
|
|
|
3107
3321
|
async fetchModels(signal) {
|
|
3108
3322
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
3109
3323
|
const region = regionOfCredential(credential);
|
|
3110
|
-
const base = this.options.baseUrl ??
|
|
3324
|
+
const base = this.options.baseUrl ?? this.chatBaseOf(credential);
|
|
3111
3325
|
const headers = {
|
|
3112
3326
|
...buildTraeCnHeaders(credential, identity),
|
|
3113
3327
|
Accept: "application/json"
|
|
@@ -3193,7 +3407,19 @@ var TraeSoloUpstreamClient = class {
|
|
|
3193
3407
|
}
|
|
3194
3408
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
3195
3409
|
const headers = buildTraeCnHeaders(credential, identity);
|
|
3196
|
-
const base = this.options.baseUrl ??
|
|
3410
|
+
const base = this.options.baseUrl ?? this.chatBaseOf(credential);
|
|
3411
|
+
if (this.options.route === "enterprise") try {
|
|
3412
|
+
const parsed = JSON.parse(prepared);
|
|
3413
|
+
if (parsed["session_id"] === void 0) parsed["session_id"] = randomUUID();
|
|
3414
|
+
prepared = JSON.stringify(parsed);
|
|
3415
|
+
} catch {
|
|
3416
|
+
return {
|
|
3417
|
+
ok: false,
|
|
3418
|
+
status: 400,
|
|
3419
|
+
kind: "client",
|
|
3420
|
+
message: "invalid JSON request"
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3197
3423
|
let response;
|
|
3198
3424
|
try {
|
|
3199
3425
|
response = await this.fetchImpl(traeEndpoint(base, TRAE_SOLO_CHAT_PATH), {
|
|
@@ -3210,10 +3436,36 @@ var TraeSoloUpstreamClient = class {
|
|
|
3210
3436
|
message: `transport error: ${String(error)}`
|
|
3211
3437
|
};
|
|
3212
3438
|
}
|
|
3213
|
-
if (response.ok)
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3439
|
+
if (response.ok) {
|
|
3440
|
+
/**
|
|
3441
|
+
* The enterprise gateway reports a refusal as **HTTP 200** whose first
|
|
3442
|
+
* SSE frame is `event:error`. Handing that stream back as a success would
|
|
3443
|
+
* surface the refusal as an empty assistant turn, so it is converted to
|
|
3444
|
+
* a real error here — the same shape the consumer gateway uses for its
|
|
3445
|
+
* non-2xx failures.
|
|
3446
|
+
*/
|
|
3447
|
+
if (this.options.route !== "enterprise") return {
|
|
3448
|
+
ok: true,
|
|
3449
|
+
response
|
|
3450
|
+
};
|
|
3451
|
+
const classified = await classifyEnterpriseResponse(response);
|
|
3452
|
+
if ("response" in classified) return {
|
|
3453
|
+
ok: true,
|
|
3454
|
+
response: classified.response
|
|
3455
|
+
};
|
|
3456
|
+
this.options.log?.("dsh-connect-trae: enterprise llm_utils_chat refused", {
|
|
3457
|
+
status: 200,
|
|
3458
|
+
model: JSON.parse(prepared)["model"],
|
|
3459
|
+
configName: JSON.parse(prepared)["config_name"],
|
|
3460
|
+
body: classified.refusal
|
|
3461
|
+
});
|
|
3462
|
+
return {
|
|
3463
|
+
ok: false,
|
|
3464
|
+
status: 200,
|
|
3465
|
+
kind: "client",
|
|
3466
|
+
message: classified.refusal
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3217
3469
|
const text = (await response.text()).slice(0, 1024);
|
|
3218
3470
|
this.options.log?.("dsh-connect-trae: llm_utils_chat rejected", {
|
|
3219
3471
|
status: response.status,
|
|
@@ -3229,7 +3481,77 @@ var TraeSoloUpstreamClient = class {
|
|
|
3229
3481
|
message: text || `Trae SOLO returned HTTP ${response.status}`
|
|
3230
3482
|
};
|
|
3231
3483
|
}
|
|
3484
|
+
/** Chat gateway for this client's pinned route. */
|
|
3485
|
+
chatBaseOf(credential) {
|
|
3486
|
+
if (this.options.route === "enterprise") return TRAE_ENTERPRISE_HOST;
|
|
3487
|
+
return REGION_GATEWAYS[regionOfCredential(credential)].chat;
|
|
3488
|
+
}
|
|
3232
3489
|
};
|
|
3490
|
+
/**
|
|
3491
|
+
* Return the refusal frame of an enterprise 200, or `undefined` when the stream
|
|
3492
|
+
* is a real answer.
|
|
3493
|
+
*
|
|
3494
|
+
* Reads (and re-plays) only the first SSE frame: a refusal is emitted before
|
|
3495
|
+
* any output token, so peeking that far is enough to classify the call, and
|
|
3496
|
+
* the stream is rebuilt so a caller that got `undefined` still sees every byte.
|
|
3497
|
+
*/
|
|
3498
|
+
function concatStream(head, reader) {
|
|
3499
|
+
return new ReadableStream({ start(controller) {
|
|
3500
|
+
if (head !== "") controller.enqueue(new TextEncoder().encode(head));
|
|
3501
|
+
const drain = () => {
|
|
3502
|
+
reader.read().then((result) => {
|
|
3503
|
+
if (result.done) {
|
|
3504
|
+
controller.close();
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
controller.enqueue(result.value);
|
|
3508
|
+
drain();
|
|
3509
|
+
}).catch((error) => controller.error(error));
|
|
3510
|
+
};
|
|
3511
|
+
drain();
|
|
3512
|
+
} });
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Classify an enterprise 200.
|
|
3516
|
+
*
|
|
3517
|
+
* Returns `{ refusal }` when the first SSE frame is `event:error`, otherwise
|
|
3518
|
+
* `{ response }` carrying a REBUILT response whose body re-emits every byte —
|
|
3519
|
+
* including the peeked frame. A refusal is emitted before any output token, so
|
|
3520
|
+
* peeking one frame is enough to classify the call.
|
|
3521
|
+
*
|
|
3522
|
+
* The response is rebuilt rather than patched: the original's body has been
|
|
3523
|
+
* partially read, so its `text()`/`json()` would keep failing with "Body has
|
|
3524
|
+
* already been read" even after `body` is swapped.
|
|
3525
|
+
*/
|
|
3526
|
+
async function classifyEnterpriseResponse(response) {
|
|
3527
|
+
const body = response.body;
|
|
3528
|
+
if (body === null) return { response };
|
|
3529
|
+
const reader = body.getReader();
|
|
3530
|
+
const decoder = new TextDecoder();
|
|
3531
|
+
let head = "";
|
|
3532
|
+
try {
|
|
3533
|
+
while (!head.includes("\n\n")) {
|
|
3534
|
+
const { done, value } = await reader.read();
|
|
3535
|
+
if (done) break;
|
|
3536
|
+
head += decoder.decode(value, { stream: true });
|
|
3537
|
+
if (head.length > 8192) break;
|
|
3538
|
+
}
|
|
3539
|
+
} catch {
|
|
3540
|
+
return { response };
|
|
3541
|
+
}
|
|
3542
|
+
const frame = head.split("\n\n")[0] ?? "";
|
|
3543
|
+
const event = /^event:(\S+)/m.exec(frame)?.[1];
|
|
3544
|
+
const data = frame.split("\n").find((line) => line.startsWith("data:"))?.slice(5).trim() ?? "";
|
|
3545
|
+
if (event !== "error") return { response: new Response(concatStream(head, reader), {
|
|
3546
|
+
status: response.status,
|
|
3547
|
+
statusText: response.statusText,
|
|
3548
|
+
headers: response.headers
|
|
3549
|
+
}) };
|
|
3550
|
+
try {
|
|
3551
|
+
await reader.cancel();
|
|
3552
|
+
} catch {}
|
|
3553
|
+
return { refusal: data === "" ? "enterprise gateway refused the request" : data };
|
|
3554
|
+
}
|
|
3233
3555
|
//#endregion
|
|
3234
3556
|
//#region src/sse.ts
|
|
3235
3557
|
/** Incremental SSE decoder supporting CRLF, chunk splits and multi-line data. */
|
|
@@ -3370,6 +3692,13 @@ function decodeTraeEvent(event) {
|
|
|
3370
3692
|
* two different statements, and a user hits the second without touching the
|
|
3371
3693
|
* first (issue #33).
|
|
3372
3694
|
* - `4023` — the model name is unknown on the route that was asked.
|
|
3695
|
+
* - `4222` — the ENTERPRISE tenant's allowance is exhausted. Measured
|
|
3696
|
+
* 2026-10-17 on a live tenant account whose enterprise credit ran out: the
|
|
3697
|
+
* gateway answered HTTP 200 with `{"code":4222,"message":""}` for a model
|
|
3698
|
+
* that streamed normally moments earlier (and still streams on the consumer
|
|
3699
|
+
* gateway). Like `4008` it is a billing fact rather than a config one, but
|
|
3700
|
+
* it is specific to the tenant pool -- the consumer balance is unaffected,
|
|
3701
|
+
* which is why it must not be folded into the `4008` reading.
|
|
3373
3702
|
* - `2001` — the upstream has not published its function config for the route
|
|
3374
3703
|
* that was asked. Evidence is issue #39: on a live CN account every SOLO
|
|
3375
3704
|
* function's `get_detail_param` answered HTTP 200 with `config_info_list:
|
|
@@ -3387,7 +3716,8 @@ const TRAE_ERROR_HINTS = {
|
|
|
3387
3716
|
4001: "Trae does not serve this model under the SOLO function the request used (config_name rejected)",
|
|
3388
3717
|
4008: "Trae reports the account's SOLO quota is exhausted for this request",
|
|
3389
3718
|
4011: "Trae throttled the SOLO channel for this account (a rate limit, not the credit balance)",
|
|
3390
|
-
4023: "Trae does not recognise this model name on the SOLO route that was asked"
|
|
3719
|
+
4023: "Trae does not recognise this model name on the SOLO route that was asked",
|
|
3720
|
+
4222: "The enterprise tenant allowance is exhausted (tenant spend is separate from the personal credit balance; the consumer gateway still has its own)"
|
|
3391
3721
|
};
|
|
3392
3722
|
/** `extra` is a JSON *string* on the wire; unwrap it to read `plan`. */
|
|
3393
3723
|
function traeErrorPlan(extra) {
|
|
@@ -3717,14 +4047,29 @@ var TraeSoloBridge = class {
|
|
|
3717
4047
|
try {
|
|
3718
4048
|
const input = JSON.parse(bodyJson);
|
|
3719
4049
|
if (typeof input["model"] === "string" && input["model"] !== "") model = input["model"];
|
|
3720
|
-
const
|
|
4050
|
+
const entryKey = model.trim().toLowerCase();
|
|
4051
|
+
const entry = this.catalog?.current().find((item) => item.id === model || item.id.trim().toLowerCase() === entryKey);
|
|
3721
4052
|
const fromCatalog = entry?.wireConfigName === void 0 ? void 0 : {
|
|
3722
4053
|
configName: entry.wireConfigName,
|
|
3723
4054
|
...entry.wireFunction === void 0 ? {} : { function: entry.wireFunction }
|
|
3724
4055
|
};
|
|
3725
4056
|
const fromResolver = this.wireResolver?.(model) ?? this.wireResolver?.(entry?.name ?? "");
|
|
3726
4057
|
const target = fromCatalog ?? fromResolver;
|
|
3727
|
-
|
|
4058
|
+
/**
|
|
4059
|
+
* Last line of defence: the discovered row's own id.
|
|
4060
|
+
*
|
|
4061
|
+
* For models whose directory id IS the gateway `config_name` the merge
|
|
4062
|
+
* deliberately emits no `wireConfigName` (there is nothing to map), so a
|
|
4063
|
+
* requested id that differs only by case has neither mapping source —
|
|
4064
|
+
* and the row we just matched case-blindly is exactly the canonical
|
|
4065
|
+
* spelling. Sending it is what a correct mapping would have produced.
|
|
4066
|
+
*
|
|
4067
|
+
* Deliberately NOT derived from the row's `name`: the rosters spell most
|
|
4068
|
+
* ids in the opposite case of their display names (`glm-5.3` vs
|
|
4069
|
+
* `GLM-5.3`) and the gateway accepts the LOWERCASE id for those — a
|
|
4070
|
+
* name-derived rewrite answered `4001` for 4 of 5 probed models.
|
|
4071
|
+
*/
|
|
4072
|
+
const wireModel = target?.configName ?? entry?.id ?? model;
|
|
3728
4073
|
const wireFunction = target?.function ?? entry?.wireFunction;
|
|
3729
4074
|
if (wireModel !== input["model"]) input["model"] = wireModel;
|
|
3730
4075
|
if (wireFunction !== void 0 && input["function"] !== wireFunction) input["function"] = wireFunction;
|
|
@@ -5469,16 +5814,26 @@ function toCheckin(status) {
|
|
|
5469
5814
|
};
|
|
5470
5815
|
}
|
|
5471
5816
|
/**
|
|
5472
|
-
* Assemble one region's card document. `
|
|
5473
|
-
*
|
|
5817
|
+
* Assemble one region's card document. `panel` is the tab the card is on; the
|
|
5818
|
+
* panel-scoped store already answers with only that region's accounts, so the
|
|
5474
5819
|
* document's model slots and account list are that region's by construction.
|
|
5475
5820
|
* Sign-in state is read-only; credit is a live billing answer whose failure
|
|
5476
5821
|
* degrades to `creditsError` rather than failing the whole document.
|
|
5477
5822
|
*/
|
|
5478
|
-
async function traeWebUsage(deps,
|
|
5479
|
-
const store = deps.store(
|
|
5480
|
-
const enabled = deps.regionEnabled(
|
|
5481
|
-
|
|
5823
|
+
async function traeWebUsage(deps, panel) {
|
|
5824
|
+
const store = deps.store(panel);
|
|
5825
|
+
const enabled = deps.regionEnabled(panel);
|
|
5826
|
+
/**
|
|
5827
|
+
* Accounts this panel may actually bill.
|
|
5828
|
+
*
|
|
5829
|
+
* Filtered ONCE, at the source, because this list is spliced into every
|
|
5830
|
+
* response shape below (`signed-out`, `signed-in`, the credits route). The
|
|
5831
|
+
* enterprise pool can only bill a tenant account, and the tenant gateway does
|
|
5832
|
+
* not make that obvious: it answers a personal token's DIRECTORY read in
|
|
5833
|
+
* full and only refuses its chat (`4019`). Listing a personal account there
|
|
5834
|
+
* would offer a binding that cannot work.
|
|
5835
|
+
*/
|
|
5836
|
+
const accounts = (await store.accounts()).filter((account) => panel !== "enterprise" || account.enterprise);
|
|
5482
5837
|
if ((await store.status()).state !== "signed-in") {
|
|
5483
5838
|
const { failures } = await store.diagnose();
|
|
5484
5839
|
return {
|
|
@@ -5507,26 +5862,26 @@ async function traeWebUsage(deps, region) {
|
|
|
5507
5862
|
}
|
|
5508
5863
|
let appLogin;
|
|
5509
5864
|
try {
|
|
5510
|
-
appLogin = await deps.liveLogin?.(
|
|
5865
|
+
appLogin = await deps.liveLogin?.(panel);
|
|
5511
5866
|
} catch {}
|
|
5512
5867
|
const account = {
|
|
5513
5868
|
accountId: accounts.find((item) => item.selected)?.id ?? "",
|
|
5514
5869
|
accountName: credential.accountName ?? credential.userId,
|
|
5515
5870
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
5516
5871
|
...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
|
|
5517
|
-
region,
|
|
5872
|
+
region: regionOfPanel(panel),
|
|
5518
5873
|
enabled,
|
|
5519
5874
|
accounts,
|
|
5520
5875
|
...appLogin === void 0 ? {} : { appLogin },
|
|
5521
|
-
models: deps.displayModels(
|
|
5876
|
+
models: deps.displayModels(panel).map((model) => ({
|
|
5522
5877
|
...model,
|
|
5523
5878
|
...model.input === void 0 ? {} : { input: [...model.input] }
|
|
5524
5879
|
})),
|
|
5525
|
-
enabledModelIds: [...deps.enabledModelIds(
|
|
5526
|
-
...deps.rawDiagnostic === void 0 ? {} : { rawChat: deps.rawDiagnostic(
|
|
5880
|
+
enabledModelIds: [...deps.enabledModelIds(panel)],
|
|
5881
|
+
...deps.rawDiagnostic === void 0 ? {} : { rawChat: deps.rawDiagnostic(panel) }
|
|
5527
5882
|
};
|
|
5528
|
-
const client = deps.client(
|
|
5529
|
-
if (
|
|
5883
|
+
const client = deps.client(panel);
|
|
5884
|
+
if (regionOfPanel(panel) === "ai") try {
|
|
5530
5885
|
const payStatus = await client.payStatus();
|
|
5531
5886
|
return {
|
|
5532
5887
|
status: "signed-in",
|
|
@@ -5540,9 +5895,25 @@ async function traeWebUsage(deps, region) {
|
|
|
5540
5895
|
payStatusError: safeMessage(error)
|
|
5541
5896
|
};
|
|
5542
5897
|
}
|
|
5898
|
+
/**
|
|
5899
|
+
* The enterprise panel shows NO credit figure, deliberately.
|
|
5900
|
+
*
|
|
5901
|
+
* `client.snapshot()` reads the CONSUMER packs, and that is the personal
|
|
5902
|
+
* balance — not what a tenant turn spends. There is no enterprise counterpart
|
|
5903
|
+
* to read: every tenant quota endpoint answers 404 (probed 2026-10-17), and
|
|
5904
|
+
* the tenant console at console.enterprise.trae.cn is the only place the
|
|
5905
|
+
* enterprise balance exists. Showing the personal number under an enterprise
|
|
5906
|
+
* heading would be actively misleading, so the panel reports the absence
|
|
5907
|
+
* explicitly and the card explains where the real figure lives.
|
|
5908
|
+
*/
|
|
5909
|
+
if (panel === "enterprise") return {
|
|
5910
|
+
status: "signed-in",
|
|
5911
|
+
...account,
|
|
5912
|
+
creditsUnavailable: "enterprise"
|
|
5913
|
+
};
|
|
5543
5914
|
const [snapshotResult, checkinResult] = await Promise.allSettled([client.snapshot(), client.checkinStatus()]);
|
|
5544
5915
|
const credits = snapshotResult.status === "fulfilled" ? toCredits(snapshotResult.value) : void 0;
|
|
5545
|
-
const alternatives = credits !== void 0 && credits.workAvailable <= 0 && credits.generalAvailable <= 0 ? await creditAlternatives(deps,
|
|
5916
|
+
const alternatives = credits !== void 0 && credits.workAvailable <= 0 && credits.generalAvailable <= 0 ? await creditAlternatives(deps, panel, accounts) : void 0;
|
|
5546
5917
|
return {
|
|
5547
5918
|
status: "signed-in",
|
|
5548
5919
|
...account,
|
|
@@ -5563,13 +5934,13 @@ async function traeWebUsage(deps, region) {
|
|
|
5563
5934
|
* problem to explain. The list is capped so a machine with many stale
|
|
5564
5935
|
* credentials cannot turn one status read into a burst of upstream calls.
|
|
5565
5936
|
*/
|
|
5566
|
-
async function creditAlternatives(deps,
|
|
5937
|
+
async function creditAlternatives(deps, panel, accounts) {
|
|
5567
5938
|
if (deps.creditsOfAccount === void 0) return void 0;
|
|
5568
|
-
const candidates = accounts.filter((account) => !account.selected && account.region ===
|
|
5939
|
+
const candidates = accounts.filter((account) => !account.selected && account.region === regionOfPanel(panel)).filter((account) => panel !== "enterprise" || account.enterprise).slice(0, TRAE_CREDIT_ALTERNATIVE_LIMIT);
|
|
5569
5940
|
if (candidates.length === 0) return void 0;
|
|
5570
5941
|
const alternatives = (await Promise.all(candidates.map(async (account) => {
|
|
5571
5942
|
try {
|
|
5572
|
-
const credits = await deps.creditsOfAccount(
|
|
5943
|
+
const credits = await deps.creditsOfAccount(panel, account.id);
|
|
5573
5944
|
if (credits === void 0) return void 0;
|
|
5574
5945
|
if (credits.workAvailable <= 0 && credits.generalAvailable <= 0) return void 0;
|
|
5575
5946
|
return {
|
|
@@ -5586,11 +5957,11 @@ async function creditAlternatives(deps, region, accounts) {
|
|
|
5586
5957
|
return alternatives.length === 0 ? void 0 : alternatives;
|
|
5587
5958
|
}
|
|
5588
5959
|
/**
|
|
5589
|
-
* The
|
|
5960
|
+
* The panel a request addresses, or a 400 answer. Absent parameter means the
|
|
5590
5961
|
* domestic tab; an unknown value is refused rather than guessed.
|
|
5591
5962
|
*/
|
|
5592
5963
|
/**
|
|
5593
|
-
* The general balance of every account in one
|
|
5964
|
+
* The general balance of every account in one panel.
|
|
5594
5965
|
*
|
|
5595
5966
|
* One upstream read per account — the same call `creditsOfAccount` already
|
|
5596
5967
|
* makes for the exhausted-balance alternatives, widened to every account and to
|
|
@@ -5601,16 +5972,16 @@ async function creditAlternatives(deps, region, accounts) {
|
|
|
5601
5972
|
* figure: the table lists how many accounts exist even when one cannot be
|
|
5602
5973
|
* quoted, rather than dropping it and making the switch unavailable.
|
|
5603
5974
|
*/
|
|
5604
|
-
async function traeAccountCredits(deps,
|
|
5605
|
-
const accounts = (await deps.store(
|
|
5975
|
+
async function traeAccountCredits(deps, panel) {
|
|
5976
|
+
const accounts = (await deps.store(panel).accounts()).filter((account) => account.region === regionOfPanel(panel)).filter((account) => panel !== "enterprise" || account.enterprise);
|
|
5606
5977
|
if (accounts.length === 0) return [];
|
|
5607
5978
|
return Promise.all(accounts.map(async (account) => {
|
|
5608
5979
|
let generalAvailable;
|
|
5609
5980
|
if (account.selected) try {
|
|
5610
|
-
generalAvailable = toCredits(await deps.client(
|
|
5981
|
+
generalAvailable = toCredits(await deps.client(panel).snapshot()).generalAvailable;
|
|
5611
5982
|
} catch {}
|
|
5612
5983
|
else if (deps.creditsOfAccount !== void 0) try {
|
|
5613
|
-
generalAvailable = (await deps.creditsOfAccount(
|
|
5984
|
+
generalAvailable = (await deps.creditsOfAccount(panel, account.id))?.generalAvailable;
|
|
5614
5985
|
} catch {}
|
|
5615
5986
|
return {
|
|
5616
5987
|
id: account.id,
|
|
@@ -5621,12 +5992,12 @@ async function traeAccountCredits(deps, region) {
|
|
|
5621
5992
|
}));
|
|
5622
5993
|
}
|
|
5623
5994
|
function requestRegion(req, res) {
|
|
5624
|
-
const
|
|
5625
|
-
if (
|
|
5995
|
+
const panel = regionOfTraeStatusUrl(req.url ?? "/");
|
|
5996
|
+
if (panel === void 0) {
|
|
5626
5997
|
json(res, 400, { error: "unknown region" });
|
|
5627
5998
|
return;
|
|
5628
5999
|
}
|
|
5629
|
-
return
|
|
6000
|
+
return panel;
|
|
5630
6001
|
}
|
|
5631
6002
|
/** Mount the GET usage route on an optional webServer context. */
|
|
5632
6003
|
function registerTraeUsageRoute(ctx, deps) {
|
|
@@ -5643,17 +6014,17 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5643
6014
|
json(res, 403, { error: "origin-not-trusted" });
|
|
5644
6015
|
return;
|
|
5645
6016
|
}
|
|
5646
|
-
const
|
|
5647
|
-
if (
|
|
6017
|
+
const panel = requestRegion(req, res);
|
|
6018
|
+
if (panel === void 0) return;
|
|
5648
6019
|
try {
|
|
5649
|
-
json(res, 200, await traeWebUsage(deps,
|
|
6020
|
+
json(res, 200, await traeWebUsage(deps, panel));
|
|
5650
6021
|
} catch (error) {
|
|
5651
6022
|
json(res, 500, { error: safeMessage(error) });
|
|
5652
6023
|
}
|
|
5653
6024
|
}
|
|
5654
6025
|
});
|
|
5655
6026
|
/**
|
|
5656
|
-
* General balance of every account in the
|
|
6027
|
+
* General balance of every account in the panel — read only when the
|
|
5657
6028
|
* composer panel is opened. One upstream read per account, paid once per
|
|
5658
6029
|
* open rather than on the 5-minute cycle (see
|
|
5659
6030
|
* {@link TRAE_ACCOUNT_CREDITS_PATH}).
|
|
@@ -5664,10 +6035,10 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5664
6035
|
handler: async (req, res) => {
|
|
5665
6036
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
5666
6037
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5667
|
-
const
|
|
5668
|
-
if (
|
|
6038
|
+
const panel = requestRegion(req, res);
|
|
6039
|
+
if (panel === void 0) return;
|
|
5669
6040
|
try {
|
|
5670
|
-
json(res, 200, { accounts: await traeAccountCredits(deps,
|
|
6041
|
+
json(res, 200, { accounts: await traeAccountCredits(deps, panel) });
|
|
5671
6042
|
} catch (error) {
|
|
5672
6043
|
json(res, 500, { error: safeMessage(error) });
|
|
5673
6044
|
}
|
|
@@ -5687,12 +6058,12 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5687
6058
|
if (req.method !== "GET") return json(res, 405, { error: "method not allowed" });
|
|
5688
6059
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5689
6060
|
if (deps.accountStateOf === void 0) return json(res, 503, { error: "account status unavailable" });
|
|
5690
|
-
const
|
|
5691
|
-
if (
|
|
6061
|
+
const panel = requestRegion(req, res);
|
|
6062
|
+
if (panel === void 0) return;
|
|
5692
6063
|
const accountId = new URL(req.url ?? "/", "http://localhost").searchParams.get("accountId");
|
|
5693
6064
|
if (accountId === null || accountId.trim() === "") return json(res, 400, { error: "missing accountId" });
|
|
5694
6065
|
try {
|
|
5695
|
-
const state = await deps.accountStateOf(
|
|
6066
|
+
const state = await deps.accountStateOf(panel, accountId);
|
|
5696
6067
|
if (state === void 0) return json(res, 404, { error: "unknown account" });
|
|
5697
6068
|
json(res, 200, state);
|
|
5698
6069
|
} catch (error) {
|
|
@@ -5706,10 +6077,10 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5706
6077
|
handler: async (req, res) => {
|
|
5707
6078
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5708
6079
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5709
|
-
const
|
|
5710
|
-
if (
|
|
6080
|
+
const panel = requestRegion(req, res);
|
|
6081
|
+
if (panel === void 0) return;
|
|
5711
6082
|
try {
|
|
5712
|
-
json(res, 200, { accounts: await deps.store(
|
|
6083
|
+
json(res, 200, { accounts: (await deps.store(panel).accounts()).filter((account) => panel !== "enterprise" || account.enterprise) });
|
|
5713
6084
|
} catch (error) {
|
|
5714
6085
|
json(res, 500, { error: safeMessage(error) });
|
|
5715
6086
|
}
|
|
@@ -5730,15 +6101,15 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5730
6101
|
handler: async (req, res) => {
|
|
5731
6102
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5732
6103
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5733
|
-
const
|
|
5734
|
-
if (
|
|
6104
|
+
const panel = requestRegion(req, res);
|
|
6105
|
+
if (panel === void 0) return;
|
|
5735
6106
|
const action = transferActionOf(req.url ?? "");
|
|
5736
6107
|
if (action === void 0) return json(res, 400, { error: "unknown transfer action" });
|
|
5737
6108
|
try {
|
|
5738
6109
|
const body = await readTransferBody(req);
|
|
5739
6110
|
if (action === "export") {
|
|
5740
6111
|
const wanted = new Set(Array.isArray(body["accountIds"]) ? body["accountIds"].filter((id) => typeof id === "string") : []);
|
|
5741
|
-
const store = deps.store(
|
|
6112
|
+
const store = deps.store(panel);
|
|
5742
6113
|
const records = [];
|
|
5743
6114
|
for (const account of await store.accounts()) {
|
|
5744
6115
|
if (!wanted.has(account.id)) continue;
|
|
@@ -5765,8 +6136,8 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5765
6136
|
const text = typeof body["text"] === "string" ? body["text"] : "";
|
|
5766
6137
|
if (action === "preview") return json(res, 200, previewTransferFile(text));
|
|
5767
6138
|
const indexes = Array.isArray(body["indexes"]) ? body["indexes"].filter((index) => typeof index === "number" && Number.isInteger(index)) : [];
|
|
5768
|
-
const { changes, imported, skipped } = mergeTransferRecords(parseTransferFile(text), indexes,
|
|
5769
|
-
if (changes.size > 0) await deps.store(
|
|
6139
|
+
const { changes, imported, skipped } = mergeTransferRecords(parseTransferFile(text), indexes, regionOfPanel(panel));
|
|
6140
|
+
if (changes.size > 0) await deps.store(panel).importAccounts([...changes.values()]);
|
|
5770
6141
|
json(res, 200, {
|
|
5771
6142
|
imported,
|
|
5772
6143
|
skipped
|
|
@@ -5783,10 +6154,10 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5783
6154
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5784
6155
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5785
6156
|
if (deps.discoverModels === void 0) return json(res, 503, { error: "model refresh unavailable" });
|
|
5786
|
-
const
|
|
5787
|
-
if (
|
|
6157
|
+
const panel = requestRegion(req, res);
|
|
6158
|
+
if (panel === void 0) return;
|
|
5788
6159
|
try {
|
|
5789
|
-
json(res, 200, { models: await deps.discoverModels(
|
|
6160
|
+
json(res, 200, { models: await deps.discoverModels(panel) });
|
|
5790
6161
|
} catch (error) {
|
|
5791
6162
|
json(res, 500, { error: safeMessage(error) });
|
|
5792
6163
|
}
|
|
@@ -5809,8 +6180,8 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5809
6180
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5810
6181
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5811
6182
|
if (deps.probeModels === void 0) return json(res, 503, { error: "model test unavailable" });
|
|
5812
|
-
const
|
|
5813
|
-
if (
|
|
6183
|
+
const panel = requestRegion(req, res);
|
|
6184
|
+
if (panel === void 0) return;
|
|
5814
6185
|
let ids;
|
|
5815
6186
|
try {
|
|
5816
6187
|
ids = await readModelTestBody(req);
|
|
@@ -5820,7 +6191,7 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5820
6191
|
if (ids.length === 0) return json(res, 400, { error: "no models to test" });
|
|
5821
6192
|
if (ids.length > 64) return json(res, 400, { error: `too many models (limit ${String(64)})` });
|
|
5822
6193
|
try {
|
|
5823
|
-
json(res, 200, await deps.probeModels(
|
|
6194
|
+
json(res, 200, await deps.probeModels(panel, ids));
|
|
5824
6195
|
} catch (error) {
|
|
5825
6196
|
json(res, 500, { error: safeMessage(error) });
|
|
5826
6197
|
}
|
|
@@ -5852,9 +6223,9 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5852
6223
|
handler: async (req, res) => {
|
|
5853
6224
|
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
5854
6225
|
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
5855
|
-
const
|
|
5856
|
-
if (
|
|
5857
|
-
if (
|
|
6226
|
+
const panel = requestRegion(req, res);
|
|
6227
|
+
if (panel === void 0) return;
|
|
6228
|
+
if (regionOfPanel(panel) !== "cn") return json(res, 404, { error: "check-in is not available for the international region" });
|
|
5858
6229
|
try {
|
|
5859
6230
|
let accountId;
|
|
5860
6231
|
try {
|
|
@@ -5864,7 +6235,7 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5864
6235
|
}
|
|
5865
6236
|
if (accountId !== void 0) {
|
|
5866
6237
|
if (deps.claimCheckinOf === void 0) return json(res, 503, { error: "check-in by account unavailable" });
|
|
5867
|
-
const outcome = await deps.claimCheckinOf(
|
|
6238
|
+
const outcome = await deps.claimCheckinOf(panel, accountId);
|
|
5868
6239
|
if (outcome === void 0) return json(res, 404, { error: "unknown account" });
|
|
5869
6240
|
const { claim, checkin: after } = outcome;
|
|
5870
6241
|
const deviceClaimed = claim.code === CHECKIN_DEVICE_ALREADY_CLAIMED;
|
|
@@ -5877,7 +6248,7 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
5877
6248
|
checkin: toCheckin(after)
|
|
5878
6249
|
});
|
|
5879
6250
|
}
|
|
5880
|
-
const client = deps.client(
|
|
6251
|
+
const client = deps.client(panel);
|
|
5881
6252
|
const current = await client.checkinStatus();
|
|
5882
6253
|
if (!current.enabled) return json(res, 409, { error: "check-in is not enabled for this account" });
|
|
5883
6254
|
if (current.checkedIn) return json(res, 200, {
|
|
@@ -6146,18 +6517,24 @@ function settingsNamespaceOf(ctx) {
|
|
|
6146
6517
|
return typeof id === "string" && id !== "" ? id : TRAE_SETTINGS_NS;
|
|
6147
6518
|
}
|
|
6148
6519
|
/**
|
|
6149
|
-
* One
|
|
6520
|
+
* One panel's saved state. A config written before the region split has only
|
|
6150
6521
|
* the flat fields: those were always captured from the CN endpoint (the
|
|
6151
6522
|
* plugin had no international support), so they are read as the CN state and
|
|
6152
6523
|
* only when no explicit CN slot exists. The ai region never inherits them —
|
|
6153
6524
|
* that inheritance is exactly the bug where a stale CN directory would be
|
|
6154
6525
|
* intersected with the international catalog and silently drop the user's
|
|
6155
6526
|
* picks (the same failure workbuddy fixed with its region split).
|
|
6527
|
+
*
|
|
6528
|
+
* Keyed by PANEL (`cn` | `enterprise` | `ai`), because each panel carries its
|
|
6529
|
+
* own switch, account binding and model selection. `enterprise` has no legacy
|
|
6530
|
+
* equivalent: it starts from an EMPTY state (its own directory, its own model
|
|
6531
|
+
* picks) and never inherits CN's, so switching it on cannot silently activate
|
|
6532
|
+
* a model list the user never chose for the tenant pool.
|
|
6156
6533
|
*/
|
|
6157
|
-
function regionStateOf(config,
|
|
6158
|
-
const stored = unwrapVolatile(unwrapVolatile(config.regions)?.[
|
|
6534
|
+
function regionStateOf(config, panel) {
|
|
6535
|
+
const stored = unwrapVolatile(unwrapVolatile(config.regions)?.[panel]);
|
|
6159
6536
|
if (stored !== void 0) return stored;
|
|
6160
|
-
if (
|
|
6537
|
+
if (panel !== "cn") return {};
|
|
6161
6538
|
return {
|
|
6162
6539
|
...config.lastCatalog === void 0 ? {} : { lastCatalog: config.lastCatalog },
|
|
6163
6540
|
...config.enabledModelIds === void 0 ? {} : { enabledModelIds: config.enabledModelIds },
|
|
@@ -6166,15 +6543,23 @@ function regionStateOf(config, region) {
|
|
|
6166
6543
|
};
|
|
6167
6544
|
}
|
|
6168
6545
|
/**
|
|
6169
|
-
* Whether one
|
|
6546
|
+
* Whether one panel's provider is switched on (issue #11). Opt-out semantics:
|
|
6170
6547
|
* only an explicit `false` disables it, so every config written before this
|
|
6171
6548
|
* switch existed — including the pre-region-split flat fields, which never
|
|
6172
6549
|
* carry `enabled` — keeps both providers running exactly as before. The card
|
|
6173
6550
|
* reads the same rule through `regionEnabledOf`, so the two halves can never
|
|
6174
6551
|
* disagree about a region's state.
|
|
6552
|
+
*
|
|
6553
|
+
* The ENTERPRISE panel is the one deliberate exception: it defaults OFF. A
|
|
6554
|
+
* consumer-only account (`tenant 7o2d894p7dr0o4`) cannot use that pool at all,
|
|
6555
|
+
* so opting every existing user into a second CN provider would put a route in
|
|
6556
|
+
* their model picker that fails on first use. It is switched on from its card
|
|
6557
|
+
* tab, by someone whose account actually belongs to a tenant.
|
|
6175
6558
|
*/
|
|
6176
|
-
function regionEnabled(config,
|
|
6177
|
-
|
|
6559
|
+
function regionEnabled(config, panel) {
|
|
6560
|
+
const stored = regionStateOf(config, panel).enabled;
|
|
6561
|
+
if (stored !== void 0) return stored !== false;
|
|
6562
|
+
return panel !== "enterprise";
|
|
6178
6563
|
}
|
|
6179
6564
|
/**
|
|
6180
6565
|
* WHERE one region's credit readout sits (learned from workbuddy #62).
|
|
@@ -6216,10 +6601,16 @@ const modelConfig = z.object({
|
|
|
6216
6601
|
input: z.array(z.union(["text", "image"])),
|
|
6217
6602
|
creditMultiplier: z.number(),
|
|
6218
6603
|
wireFunction: z.string(),
|
|
6604
|
+
wireConfigName: z.string(),
|
|
6219
6605
|
multimodal: z.boolean()
|
|
6220
6606
|
});
|
|
6607
|
+
/**
|
|
6608
|
+
* One panel's saved state. Panels are keyed `cn | enterprise | ai` — see
|
|
6609
|
+
* {@link TraePanel}; `enterprise` is a second CN provider with its own switch,
|
|
6610
|
+
* account binding and model selection.
|
|
6611
|
+
*/
|
|
6221
6612
|
const regionStateConfig = z.object({
|
|
6222
|
-
enabled: z.boolean().
|
|
6613
|
+
enabled: z.boolean().description("Whether this panel's provider is offered to DSH (false withdraws it entirely)"),
|
|
6223
6614
|
lastCatalog: z.array(modelConfig).default([]),
|
|
6224
6615
|
enabledModelIds: z.array(z.string()).default([]),
|
|
6225
6616
|
imageModelIds: z.array(z.string()).default([]),
|
|
@@ -6228,7 +6619,8 @@ const regionStateConfig = z.object({
|
|
|
6228
6619
|
});
|
|
6229
6620
|
const accountSelectionConfig = z.object({
|
|
6230
6621
|
cn: z.string().description("Selected domestic (CN) account id (never a token)"),
|
|
6231
|
-
ai: z.string().description("Selected international account id (never a token)")
|
|
6622
|
+
ai: z.string().description("Selected international account id (never a token)"),
|
|
6623
|
+
enterprise: z.string().description("Selected account id for the CN enterprise pool (never a token)")
|
|
6232
6624
|
});
|
|
6233
6625
|
const Config = z.object({
|
|
6234
6626
|
authFile: asVolatile(z.string().description("Optional Trae storage.json path override")),
|
|
@@ -6242,8 +6634,8 @@ const Config = z.object({
|
|
|
6242
6634
|
accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
|
|
6243
6635
|
showPointsInMainUi: asVolatile(z.boolean().default(false).description("在输入框工具栏显示积分读数「Trae CN · <数值>」(仅当选中的模型属于本插件时出现,可点击查看账号与积分明细)")),
|
|
6244
6636
|
followAppLogin: asVolatile(z.boolean().default(false).description("跟随 Trae 桌面端当前登录账号换绑(打开后每次配置生效时重绑到 App 现在登录的账号)")),
|
|
6245
|
-
accounts: asVolatile(accountSelectionConfig.description("Per-
|
|
6246
|
-
regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-
|
|
6637
|
+
accounts: asVolatile(accountSelectionConfig.description("Per-panel account selections, keyed cn | enterprise | ai")),
|
|
6638
|
+
regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-panel model directory and selection, keyed cn | enterprise | ai")),
|
|
6247
6639
|
lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
|
|
6248
6640
|
enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
|
|
6249
6641
|
contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Deprecated: pre-region-split CN context budgets"),
|
|
@@ -6255,8 +6647,8 @@ const REGION_KEYS = ["cn", "ai"];
|
|
|
6255
6647
|
function apply(ctx, config) {
|
|
6256
6648
|
const settingsNs = settingsNamespaceOf(ctx);
|
|
6257
6649
|
let current = () => config;
|
|
6258
|
-
const enabledSet = (value,
|
|
6259
|
-
const imageSet = (value,
|
|
6650
|
+
const enabledSet = (value, panel) => new Set(regionStateOf(value, panel).enabledModelIds ?? []);
|
|
6651
|
+
const imageSet = (value, panel) => new Set(regionStateOf(value, panel).imageModelIds ?? []);
|
|
6260
6652
|
/**
|
|
6261
6653
|
* Per-region wire state. Each region's discovery owns its own maps: the CN
|
|
6262
6654
|
* and international rosters overlap (and spell some ids differently), so a
|
|
@@ -6269,30 +6661,30 @@ function apply(ctx, config) {
|
|
|
6269
6661
|
* `lastCatalog` / `models` / `enabledModelIds` that still lists it.
|
|
6270
6662
|
*/
|
|
6271
6663
|
const wireState = {};
|
|
6272
|
-
for (const
|
|
6664
|
+
for (const panel of TRAE_PANELS) wireState[panel] = {
|
|
6273
6665
|
callableKeys: /* @__PURE__ */ new Set(),
|
|
6274
6666
|
resolved: false,
|
|
6275
6667
|
byId: /* @__PURE__ */ new Map(),
|
|
6276
6668
|
byName: /* @__PURE__ */ new Map()
|
|
6277
6669
|
};
|
|
6278
|
-
const dropDeadModels = (rows,
|
|
6279
|
-
const wire = wireState[
|
|
6670
|
+
const dropDeadModels = (rows, panel) => {
|
|
6671
|
+
const wire = wireState[panel];
|
|
6280
6672
|
if (!wire.resolved) return rows;
|
|
6281
6673
|
return rows.filter((model) => wire.callableKeys.has(model.id.trim().toLowerCase()) || wire.callableKeys.has(model.name.trim().toLowerCase()));
|
|
6282
6674
|
};
|
|
6283
|
-
const fallbackModels = (value,
|
|
6284
|
-
const derive = (value, raw,
|
|
6285
|
-
const selectedImages = imageSet(value,
|
|
6286
|
-
const derived = deriveCatalog(applyImageSelection(sanitizeCatalog(dropDeadModels(raw,
|
|
6287
|
-
return derived.length > 0 ? derived : fallbackModels(value,
|
|
6675
|
+
const fallbackModels = (value, panel) => applyImageSelection(fallbackModelsFor(regionOfPanel(panel)), imageSet(value, panel));
|
|
6676
|
+
const derive = (value, raw, panel) => {
|
|
6677
|
+
const selectedImages = imageSet(value, panel);
|
|
6678
|
+
const derived = deriveCatalog(applyImageSelection(sanitizeCatalog(dropDeadModels(raw, panel)), selectedImages), enabledSet(value, panel), regionStateOf(value, panel).contextBudgets ?? {});
|
|
6679
|
+
return derived.length > 0 ? derived : fallbackModels(value, panel);
|
|
6288
6680
|
};
|
|
6289
|
-
const configuredModels = (value,
|
|
6290
|
-
const state = regionStateOf(value,
|
|
6291
|
-
return state.lastCatalog?.length ? derive(value, state.lastCatalog,
|
|
6681
|
+
const configuredModels = (value, panel) => {
|
|
6682
|
+
const state = regionStateOf(value, panel);
|
|
6683
|
+
return state.lastCatalog?.length ? derive(value, state.lastCatalog, panel) : panel === "cn" && value.models?.length ? derive(value, value.models, panel) : fallbackModels(value, panel);
|
|
6292
6684
|
};
|
|
6293
|
-
const displayModels = (value,
|
|
6294
|
-
const state = regionStateOf(value,
|
|
6295
|
-
return state.lastCatalog?.length ? dropDeadModels(sanitizeCatalog(state.lastCatalog),
|
|
6685
|
+
const displayModels = (value, panel) => {
|
|
6686
|
+
const state = regionStateOf(value, panel);
|
|
6687
|
+
return state.lastCatalog?.length ? dropDeadModels(sanitizeCatalog(state.lastCatalog), panel) : panel === "cn" && value.models?.length ? dropDeadModels(sanitizeCatalog(value.models), panel) : fallbackModelsFor(regionOfPanel(panel));
|
|
6296
6688
|
};
|
|
6297
6689
|
/**
|
|
6298
6690
|
* Legacy migration for the pre-split single `accountId`: its region is
|
|
@@ -6302,9 +6694,20 @@ function apply(ctx, config) {
|
|
|
6302
6694
|
* inheriting a selection that belongs to the other side of the split.
|
|
6303
6695
|
*/
|
|
6304
6696
|
let legacyAccountRegion;
|
|
6305
|
-
|
|
6306
|
-
|
|
6697
|
+
/**
|
|
6698
|
+
* Which account a panel is bound to.
|
|
6699
|
+
*
|
|
6700
|
+
* Keyed by PANEL: the enterprise pool draws on the same CN credentials but
|
|
6701
|
+
* keeps its OWN binding, so pointing the consumer tab at a personal account
|
|
6702
|
+
* never moves the enterprise tab (and vice versa). A panel with no explicit
|
|
6703
|
+
* binding falls through to that REGION's default selection, which is the
|
|
6704
|
+
* store's own "preferred account for this region".
|
|
6705
|
+
*/
|
|
6706
|
+
const effectiveAccountFor = (panel, value) => {
|
|
6707
|
+
const explicit = unwrapVolatile(value.accounts)?.[panel];
|
|
6307
6708
|
if (explicit !== void 0) return explicit;
|
|
6709
|
+
const region = regionOfPanel(panel);
|
|
6710
|
+
if (panel === "enterprise") return void 0;
|
|
6308
6711
|
return legacyAccountRegion === region ? value.accountId : void 0;
|
|
6309
6712
|
};
|
|
6310
6713
|
/**
|
|
@@ -6326,10 +6729,29 @@ function apply(ctx, config) {
|
|
|
6326
6729
|
}
|
|
6327
6730
|
}
|
|
6328
6731
|
}
|
|
6732
|
+
/**
|
|
6733
|
+
* One stack per PANEL.
|
|
6734
|
+
*
|
|
6735
|
+
* `cn` and `enterprise` share a credential REGION but not a directory, a wire
|
|
6736
|
+
* map or a model selection: the two pools publish different rosters, so a
|
|
6737
|
+
* shared catalog would let one pool's answer filter the other's models. Each
|
|
6738
|
+
* panel therefore gets its own catalog, wire state, clients and shim routes,
|
|
6739
|
+
* and the credential store below is the only thing they have in common.
|
|
6740
|
+
*/
|
|
6329
6741
|
const stacks = {};
|
|
6330
|
-
for (const
|
|
6742
|
+
for (const panel of TRAE_PANELS) {
|
|
6743
|
+
const region = regionOfPanel(panel);
|
|
6744
|
+
/**
|
|
6745
|
+
* Whether tenant (enterprise) accounts are also offered by the CONSUMER
|
|
6746
|
+
* panels.
|
|
6747
|
+
*
|
|
6748
|
+
* Off by default, so a published build offers an enterprise login only
|
|
6749
|
+
* under the enterprise panel. Opt-in per machine, and intentionally not
|
|
6750
|
+
* surfaced in user-facing documentation.
|
|
6751
|
+
*/
|
|
6752
|
+
const tenantLocal = process.env["DSH_TRAE_TENANT_IN_CONSUMER"] === "1";
|
|
6331
6753
|
const catalog = new TraeCatalog(region);
|
|
6332
|
-
const wire = wireState[
|
|
6754
|
+
const wire = wireState[panel];
|
|
6333
6755
|
/**
|
|
6334
6756
|
* Best-effort device identity for this region's installation. Prefer the
|
|
6335
6757
|
* selected credential's own edition so an international account reads its
|
|
@@ -6382,6 +6804,7 @@ function apply(ctx, config) {
|
|
|
6382
6804
|
};
|
|
6383
6805
|
const store = new TraeCredentialStore({
|
|
6384
6806
|
region,
|
|
6807
|
+
...panel === "enterprise" ? { requireTenant: true } : { hideTenantAccounts: !tenantLocal },
|
|
6385
6808
|
...unwrapVolatile(config.authFile) === void 0 ? {} : { storagePath: unwrapVolatile(config.authFile) },
|
|
6386
6809
|
edition: unwrapVolatile(config.edition) ?? "auto",
|
|
6387
6810
|
refresh: async (credential) => refreshTraeCredential(credential, void 0, await refreshDeviceSafe())
|
|
@@ -6389,19 +6812,65 @@ function apply(ctx, config) {
|
|
|
6389
6812
|
const solo = new TraeSoloUpstreamClient({
|
|
6390
6813
|
credential: () => store.resolve(),
|
|
6391
6814
|
identity,
|
|
6815
|
+
...panel === "enterprise" ? { route: "enterprise" } : {},
|
|
6392
6816
|
log: (message, detail) => ctx.logger.warn(message, detail)
|
|
6393
6817
|
});
|
|
6394
6818
|
const remoteCatalog = new TraeSoloRemoteCatalogClient({ credential: () => store.resolve() });
|
|
6395
6819
|
const wireResolver = (displayId) => wire.byId.get(displayId) ?? wire.byName.get(displayId.trim().toLowerCase());
|
|
6396
|
-
|
|
6397
|
-
|
|
6820
|
+
/**
|
|
6821
|
+
* One client + bridge per provider id.
|
|
6822
|
+
*
|
|
6823
|
+
* `routeOfTraeProvider` pins each to its billing pool: the consumer ids get
|
|
6824
|
+
* the region gateway exactly as before, `trae-enterprise` gets the tenant
|
|
6825
|
+
* gateway and stamps the `session_id` it requires. The discovery client
|
|
6826
|
+
* below stays UNROUTED on purpose — it owns the roster/probe path and, for
|
|
6827
|
+
* a tenant credential, reads both pools so the shared catalogue keeps every
|
|
6828
|
+
* model either one serves.
|
|
6829
|
+
*/
|
|
6830
|
+
/**
|
|
6831
|
+
* Latest successful discovery for this panel, mutated by discoverModels
|
|
6832
|
+
* and READ by applySelection (via the stack): a settings write rebuilds
|
|
6833
|
+
* the catalog from saved rows that may predate wireConfigName, and the
|
|
6834
|
+
* merge below is what keeps the case mapping alive across it.
|
|
6835
|
+
*/
|
|
6836
|
+
const latestDiscovery = [];
|
|
6837
|
+
const routeClients = {};
|
|
6838
|
+
const shimRoutes = [];
|
|
6839
|
+
for (const provider of [providerOfPanel(panel)]) {
|
|
6840
|
+
const route = routeOfTraeProvider(provider) ?? "solo";
|
|
6841
|
+
const client = new TraeSoloUpstreamClient({
|
|
6842
|
+
credential: () => store.resolve(),
|
|
6843
|
+
identity,
|
|
6844
|
+
route,
|
|
6845
|
+
log: (message, detail) => ctx.logger.warn(message, detail)
|
|
6846
|
+
});
|
|
6847
|
+
const delegating = new TraeDelegatingUpstreamClient(new TraeSoloBridge(client, catalog, wireResolver));
|
|
6848
|
+
routeClients[provider] = {
|
|
6849
|
+
provider,
|
|
6850
|
+
route,
|
|
6851
|
+
client,
|
|
6852
|
+
upstream: delegating,
|
|
6853
|
+
secret: ""
|
|
6854
|
+
};
|
|
6855
|
+
shimRoutes.push({
|
|
6856
|
+
label: route,
|
|
6857
|
+
client: delegating
|
|
6858
|
+
});
|
|
6859
|
+
}
|
|
6398
6860
|
const shim = createTraeShim({
|
|
6399
6861
|
catalog,
|
|
6400
|
-
|
|
6862
|
+
routes: shimRoutes,
|
|
6401
6863
|
logger: ctx.logger
|
|
6402
6864
|
});
|
|
6865
|
+
for (const entry of Object.values(routeClients)) entry.secret = shim.tokenOf(entry.route);
|
|
6866
|
+
/**
|
|
6867
|
+
* The region's PRIMARY provider plumbing, used by region-wide paths that
|
|
6868
|
+
* are not pool-specific: model probing, Raw-Chat status, and the stack's
|
|
6869
|
+
* `delegating` handle. Per-provider turns never go through this.
|
|
6870
|
+
*/
|
|
6871
|
+
const primary = routeClients[providerOfPanel(panel)];
|
|
6403
6872
|
let rawDiagnostic = () => ({ state: "disabled" });
|
|
6404
|
-
if (
|
|
6873
|
+
if (panel === "cn") (async () => {
|
|
6405
6874
|
try {
|
|
6406
6875
|
const resolved = await resolveTraeRawRuntime(store, "qwen-3.7-plus");
|
|
6407
6876
|
const gateway = createTraeRawGateway({
|
|
@@ -6415,7 +6884,7 @@ function apply(ctx, config) {
|
|
|
6415
6884
|
runtime: resolved.runtime
|
|
6416
6885
|
}
|
|
6417
6886
|
}),
|
|
6418
|
-
solo:
|
|
6887
|
+
solo: new TraeSoloBridge(primary.client, catalog, wireResolver),
|
|
6419
6888
|
endpoint: `${REGION_GATEWAYS.cn.chat}/api/ide/v2/llm_raw_chat`,
|
|
6420
6889
|
edition: resolved.identity.edition,
|
|
6421
6890
|
identity: {
|
|
@@ -6425,7 +6894,7 @@ function apply(ctx, config) {
|
|
|
6425
6894
|
runtime: resolved.runtime,
|
|
6426
6895
|
enabled: false
|
|
6427
6896
|
});
|
|
6428
|
-
|
|
6897
|
+
primary.upstream.replace(gateway.upstream);
|
|
6429
6898
|
rawDiagnostic = () => gateway.diagnostic();
|
|
6430
6899
|
ctx.effect(() => () => gateway.invalidate(), "dsh-connect-trae: Raw capability invalidation");
|
|
6431
6900
|
} catch (error) {
|
|
@@ -6436,12 +6905,14 @@ function apply(ctx, config) {
|
|
|
6436
6905
|
credential: () => store.resolve(),
|
|
6437
6906
|
deviceId: async () => (await identity()).deviceId
|
|
6438
6907
|
});
|
|
6439
|
-
stacks[
|
|
6908
|
+
stacks[panel] = {
|
|
6440
6909
|
region,
|
|
6441
6910
|
store,
|
|
6442
6911
|
catalog,
|
|
6443
6912
|
shim,
|
|
6444
|
-
|
|
6913
|
+
lastDiscovery: latestDiscovery,
|
|
6914
|
+
delegating: routeClients[providerOfPanel(panel)]?.upstream,
|
|
6915
|
+
routeClients,
|
|
6445
6916
|
usageClient,
|
|
6446
6917
|
/**
|
|
6447
6918
|
* Credit balance of one named account, WITHOUT rebinding the store
|
|
@@ -6510,6 +6981,8 @@ function apply(ctx, config) {
|
|
|
6510
6981
|
wire.byId.set(model.id, target);
|
|
6511
6982
|
wire.byName.set(model.name.trim().toLowerCase(), target);
|
|
6512
6983
|
}
|
|
6984
|
+
latestDiscovery.length = 0;
|
|
6985
|
+
latestDiscovery.push(...merged);
|
|
6513
6986
|
return merged;
|
|
6514
6987
|
},
|
|
6515
6988
|
/**
|
|
@@ -6532,7 +7005,7 @@ function apply(ctx, config) {
|
|
|
6532
7005
|
const skipped = [];
|
|
6533
7006
|
for (const id of ids) if (known.has(id.trim().toLowerCase())) testable.push(id);
|
|
6534
7007
|
else skipped.push(id);
|
|
6535
|
-
const results = await probeModelsSequentially((body, signal) => upstream.chatStream(body, signal), testable);
|
|
7008
|
+
const results = await probeModelsSequentially((body, signal) => primary.upstream.chatStream(body, signal), testable);
|
|
6536
7009
|
return skipped.length === 0 ? { results } : {
|
|
6537
7010
|
results,
|
|
6538
7011
|
skipped
|
|
@@ -6548,9 +7021,10 @@ function apply(ctx, config) {
|
|
|
6548
7021
|
* never moves from here — the card's hint and the follow switch below are
|
|
6549
7022
|
* the only writers.
|
|
6550
7023
|
*/
|
|
6551
|
-
const liveLoginOf = async (
|
|
7024
|
+
const liveLoginOf = async (panel) => {
|
|
7025
|
+
const region = regionOfPanel(panel);
|
|
6552
7026
|
try {
|
|
6553
|
-
const live = await stacks[
|
|
7027
|
+
const live = await stacks[panel].store.liveLogin();
|
|
6554
7028
|
if (live === void 0 || live.region !== region) return void 0;
|
|
6555
7029
|
return {
|
|
6556
7030
|
id: live.id,
|
|
@@ -6574,30 +7048,30 @@ function apply(ctx, config) {
|
|
|
6574
7048
|
* event (`loader/volatile-update`) is what re-runs `applySelection` with the
|
|
6575
7049
|
* updated document — the same channel every card write already uses.
|
|
6576
7050
|
*/
|
|
6577
|
-
const followAppLogin = async (
|
|
6578
|
-
const live = await liveLoginOf(
|
|
7051
|
+
const followAppLogin = async (panel) => {
|
|
7052
|
+
const live = await liveLoginOf(panel);
|
|
6579
7053
|
if (live === void 0 || writeSettings === void 0) return void 0;
|
|
6580
7054
|
const configured = configuredAccountsOf(current());
|
|
6581
|
-
if (configured[
|
|
7055
|
+
if (configured[panel] === live.id) return live;
|
|
6582
7056
|
await writeSettings({ accounts: {
|
|
6583
7057
|
...configured,
|
|
6584
|
-
[
|
|
7058
|
+
[panel]: live.id
|
|
6585
7059
|
} });
|
|
6586
7060
|
return live;
|
|
6587
7061
|
};
|
|
6588
7062
|
ctx.inject(["webServer"], (webCtx) => registerTraeUsageRoute(webCtx, {
|
|
6589
|
-
store: (
|
|
6590
|
-
client: (
|
|
6591
|
-
displayModels: (
|
|
6592
|
-
enabledModelIds: (
|
|
6593
|
-
regionEnabled: (
|
|
6594
|
-
discoverModels: (
|
|
6595
|
-
probeModels: (
|
|
6596
|
-
rawDiagnostic: (
|
|
6597
|
-
creditsOfAccount: (
|
|
6598
|
-
accountStateOf: (
|
|
6599
|
-
claimCheckinOf: (
|
|
6600
|
-
liveLogin: (
|
|
7063
|
+
store: (panel) => stacks[panel].store,
|
|
7064
|
+
client: (panel) => stacks[panel].usageClient,
|
|
7065
|
+
displayModels: (panel) => displayModels(current(), panel),
|
|
7066
|
+
enabledModelIds: (panel) => regionStateOf(current(), panel).enabledModelIds ?? [],
|
|
7067
|
+
regionEnabled: (panel) => regionEnabled(current(), panel),
|
|
7068
|
+
discoverModels: (panel, signal) => stacks[panel].discoverModels(signal),
|
|
7069
|
+
probeModels: (panel, ids) => stacks[panel].probeModels(ids),
|
|
7070
|
+
rawDiagnostic: (panel) => stacks[panel].rawDiagnostic(),
|
|
7071
|
+
creditsOfAccount: (panel, accountId) => stacks[panel].creditsOfAccount(accountId),
|
|
7072
|
+
accountStateOf: (panel, accountId) => stacks[panel].accountStateOf(accountId),
|
|
7073
|
+
claimCheckinOf: (panel, accountId) => stacks[panel].claimCheckinOf(accountId),
|
|
7074
|
+
liveLogin: (panel) => liveLoginOf(panel)
|
|
6601
7075
|
}));
|
|
6602
7076
|
/**
|
|
6603
7077
|
* Live registration handles per region, filled once the shim is listening.
|
|
@@ -6609,8 +7083,18 @@ function apply(ctx, config) {
|
|
|
6609
7083
|
* card tab alone would leave every model selectable.
|
|
6610
7084
|
*/
|
|
6611
7085
|
const registration = {
|
|
6612
|
-
cn: {},
|
|
6613
|
-
|
|
7086
|
+
cn: { adapters: [] },
|
|
7087
|
+
enterprise: { adapters: [] },
|
|
7088
|
+
ai: { adapters: [] }
|
|
7089
|
+
};
|
|
7090
|
+
/** Release every live registration this apply() created. */
|
|
7091
|
+
const disposeRegistration = () => {
|
|
7092
|
+
for (const panel of TRAE_PANELS) {
|
|
7093
|
+
for (const entry of registration[panel].adapters) entry.handle();
|
|
7094
|
+
registration[panel].adapters.length = 0;
|
|
7095
|
+
}
|
|
7096
|
+
registration.cn.directory?.();
|
|
7097
|
+
delete registration.cn.directory;
|
|
6614
7098
|
};
|
|
6615
7099
|
/**
|
|
6616
7100
|
* Publish each region's on/off state to the harness (issue #11). Both swaps
|
|
@@ -6622,21 +7106,53 @@ function apply(ctx, config) {
|
|
|
6622
7106
|
* lands immediately.
|
|
6623
7107
|
*/
|
|
6624
7108
|
const syncRegionRegistration = (value) => {
|
|
6625
|
-
for (const
|
|
6626
|
-
|
|
6627
|
-
provider:
|
|
6628
|
-
|
|
7109
|
+
for (const panel of TRAE_PANELS) {
|
|
7110
|
+
const enabled = regionEnabled(value, panel);
|
|
7111
|
+
for (const entry of registration[panel].adapters) entry.handle.replace(enabled ? [entry.provider] : []);
|
|
7112
|
+
}
|
|
7113
|
+
registration.cn.directory?.replace(TRAE_PANELS.filter((panel) => regionEnabled(value, panel)).map((panel) => ({
|
|
7114
|
+
provider: providerOfPanel(panel),
|
|
7115
|
+
displayName: displayNameOfTraeProvider(providerOfPanel(panel)),
|
|
6629
7116
|
settingsNs,
|
|
6630
7117
|
settingsPath: [],
|
|
6631
7118
|
declared: false
|
|
6632
7119
|
})));
|
|
6633
7120
|
};
|
|
6634
|
-
/** Push the current config into every
|
|
7121
|
+
/** Push the current config into every panel's store selection and catalog. */
|
|
6635
7122
|
const applySelection = (value) => {
|
|
6636
|
-
for (const
|
|
6637
|
-
const stack = stacks[
|
|
6638
|
-
stack.store.setSource(unwrapVolatile(value.authFile), unwrapVolatile(value.edition) ?? "auto", effectiveAccountFor(
|
|
6639
|
-
|
|
7123
|
+
for (const panel of TRAE_PANELS) {
|
|
7124
|
+
const stack = stacks[panel];
|
|
7125
|
+
stack.store.setSource(unwrapVolatile(value.authFile), unwrapVolatile(value.edition) ?? "auto", effectiveAccountFor(panel, value));
|
|
7126
|
+
/**
|
|
7127
|
+
* Re-merge the wire mapping the last discovery taught us.
|
|
7128
|
+
*
|
|
7129
|
+
* configuredModels rebuilds from the SAVED rows, and rows saved before
|
|
7130
|
+
* wireConfigName existed carry wireFunction alone. Every settings write
|
|
7131
|
+
* goes through here (card writes, follow-app-login, the initial seed),
|
|
7132
|
+
* so without this each write would strip the case mapping until the next
|
|
7133
|
+
* discovery -- the exact window that turns deepseek-v4.1-flash into a
|
|
7134
|
+
* 4001, because the gateway is case-sensitive and the lowercase
|
|
7135
|
+
* directory id is the one spelling it refuses.
|
|
7136
|
+
*/
|
|
7137
|
+
const known = /* @__PURE__ */ new Map();
|
|
7138
|
+
for (const model of stack.lastDiscovery) {
|
|
7139
|
+
known.set(model.id, model);
|
|
7140
|
+
known.set(model.id.trim().toLowerCase(), model);
|
|
7141
|
+
if (model.name.trim() !== "") known.set(model.name.trim().toLowerCase(), model);
|
|
7142
|
+
}
|
|
7143
|
+
const rows = configuredModels(value, panel).map((model) => {
|
|
7144
|
+
const match = known.get(model.id) ?? known.get(model.id.trim().toLowerCase()) ?? (model.name.trim() !== "" ? known.get(model.name.trim().toLowerCase()) : void 0);
|
|
7145
|
+
if (match === void 0) return model;
|
|
7146
|
+
const wireConfigName = model.wireConfigName ?? match.wireConfigName;
|
|
7147
|
+
const wireFunction = model.wireFunction ?? match.wireFunction;
|
|
7148
|
+
if (wireConfigName === model.wireConfigName && wireFunction === model.wireFunction) return model;
|
|
7149
|
+
return {
|
|
7150
|
+
...model,
|
|
7151
|
+
...wireConfigName === void 0 ? {} : { wireConfigName },
|
|
7152
|
+
...wireFunction === void 0 ? {} : { wireFunction }
|
|
7153
|
+
};
|
|
7154
|
+
});
|
|
7155
|
+
stack.catalog.set(rows);
|
|
6640
7156
|
stack.invalidateAdapter();
|
|
6641
7157
|
}
|
|
6642
7158
|
syncRegionRegistration(value);
|
|
@@ -6695,9 +7211,9 @@ function apply(ctx, config) {
|
|
|
6695
7211
|
if (followInFlight || !followAppLoginEnabled(current())) return;
|
|
6696
7212
|
followInFlight = true;
|
|
6697
7213
|
try {
|
|
6698
|
-
for (const
|
|
6699
|
-
if (!regionEnabled(current(),
|
|
6700
|
-
await followAppLogin(
|
|
7214
|
+
for (const panel of TRAE_PANELS) {
|
|
7215
|
+
if (!regionEnabled(current(), panel)) continue;
|
|
7216
|
+
await followAppLogin(panel);
|
|
6701
7217
|
}
|
|
6702
7218
|
} catch (error) {
|
|
6703
7219
|
ctx.logger.warn("dsh-connect-trae: follow-app-login rebind skipped", error);
|
|
@@ -6709,77 +7225,78 @@ function apply(ctx, config) {
|
|
|
6709
7225
|
let stopped = false;
|
|
6710
7226
|
ctx.effect(() => () => {
|
|
6711
7227
|
stopped = true;
|
|
6712
|
-
for (const
|
|
7228
|
+
for (const panel of TRAE_PANELS) stacks[panel].shim.close();
|
|
6713
7229
|
});
|
|
6714
|
-
Promise.all(
|
|
7230
|
+
Promise.all(TRAE_PANELS.map((panel) => stacks[panel].shim.ready)).then(async () => {
|
|
6715
7231
|
if (stopped) return;
|
|
6716
7232
|
try {
|
|
6717
7233
|
const piAi = piAiRuntimeInfo();
|
|
6718
7234
|
ctx.logger.info(`dsh-connect-trae: pi-ai ${piAi.version ?? "unknown"} (${piAi.generation} generation)${piAi.inspected ? "" : " — not resolved locally, the host supplies it"}`);
|
|
6719
|
-
for (const
|
|
6720
|
-
const stack = stacks[
|
|
7235
|
+
for (const panel of TRAE_PANELS) {
|
|
7236
|
+
const stack = stacks[panel];
|
|
7237
|
+
const provider = providerOfPanel(panel);
|
|
7238
|
+
const entry = stack.routeClients[provider];
|
|
7239
|
+
if (entry === void 0) continue;
|
|
6721
7240
|
const trae = createTraeAdapter({
|
|
6722
7241
|
shim: stack.shim,
|
|
6723
7242
|
catalog: stack.catalog,
|
|
6724
|
-
provider
|
|
6725
|
-
displayName:
|
|
7243
|
+
provider,
|
|
7244
|
+
displayName: displayNameOfTraeProvider(provider),
|
|
7245
|
+
secret: entry.secret,
|
|
6726
7246
|
resolveAttachments: () => ctx.get("attachments")
|
|
6727
7247
|
});
|
|
6728
7248
|
stack.invalidateAdapter = () => {
|
|
6729
7249
|
trae.invalidate();
|
|
6730
7250
|
};
|
|
6731
|
-
registration[
|
|
7251
|
+
registration[panel].adapters.push({
|
|
7252
|
+
provider,
|
|
7253
|
+
handle: ctx.llm.registerAdapter([provider], trae.adapter)
|
|
7254
|
+
});
|
|
6732
7255
|
}
|
|
6733
7256
|
ctx.llm.registerModelDiscovery(settingsNs, async (request, signal) => {
|
|
6734
|
-
const
|
|
6735
|
-
if (
|
|
6736
|
-
if (!regionEnabled(current(),
|
|
7257
|
+
const panel = panelOfProvider(request.provider ?? "");
|
|
7258
|
+
if (panel === void 0) return [];
|
|
7259
|
+
if (!regionEnabled(current(), panel)) return [];
|
|
6737
7260
|
const cancellation = signal ?? request.signal;
|
|
6738
|
-
|
|
7261
|
+
const next = applyImageSelection(await stacks[panel].discoverModels(cancellation), imageSet(current(), panel));
|
|
7262
|
+
const withRate = panel !== "enterprise";
|
|
7263
|
+
return next.map((model) => ({
|
|
6739
7264
|
id: model.id,
|
|
6740
|
-
name: traeModelDisplayName(model),
|
|
7265
|
+
name: traeModelDisplayName(model, withRate),
|
|
6741
7266
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
6742
7267
|
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
|
|
6743
7268
|
inputModalities: traeInputModalities(model)
|
|
6744
7269
|
}));
|
|
6745
7270
|
});
|
|
6746
|
-
registration.cn.directory = ctx.llm.registerConfigurableProviders(
|
|
6747
|
-
provider:
|
|
6748
|
-
displayName:
|
|
7271
|
+
registration.cn.directory = ctx.llm.registerConfigurableProviders(TRAE_PANELS.map((panel) => ({
|
|
7272
|
+
provider: providerOfPanel(panel),
|
|
7273
|
+
displayName: displayNameOfTraeProvider(providerOfPanel(panel)),
|
|
6749
7274
|
settingsNs,
|
|
6750
7275
|
settingsPath: [],
|
|
6751
7276
|
declared: false
|
|
6752
7277
|
})));
|
|
6753
7278
|
syncRegionRegistration(current());
|
|
6754
7279
|
} finally {
|
|
6755
|
-
if (registration.cn.
|
|
6756
|
-
registration.cn.adapter?.();
|
|
6757
|
-
registration.ai.adapter?.();
|
|
6758
|
-
registration.cn.directory?.();
|
|
6759
|
-
}
|
|
7280
|
+
if (registration.cn.adapters.length === 0 || registration.ai.adapters.length === 0 || registration.cn.directory === void 0) disposeRegistration();
|
|
6760
7281
|
}
|
|
6761
7282
|
try {
|
|
6762
7283
|
ctx.effect(() => () => {
|
|
6763
|
-
|
|
6764
|
-
registration.ai.adapter?.();
|
|
6765
|
-
registration.cn.directory?.();
|
|
7284
|
+
disposeRegistration();
|
|
6766
7285
|
});
|
|
6767
7286
|
} catch {
|
|
6768
|
-
|
|
6769
|
-
registration.ai.adapter?.();
|
|
6770
|
-
registration.cn.directory?.();
|
|
7287
|
+
disposeRegistration();
|
|
6771
7288
|
}
|
|
6772
|
-
for (const
|
|
6773
|
-
if (!regionEnabled(current(),
|
|
7289
|
+
for (const panel of TRAE_PANELS) {
|
|
7290
|
+
if (!regionEnabled(current(), panel)) continue;
|
|
6774
7291
|
(async () => {
|
|
6775
|
-
const stack = stacks[
|
|
7292
|
+
const stack = stacks[panel];
|
|
6776
7293
|
try {
|
|
6777
7294
|
const models = await stack.discoverModels();
|
|
6778
7295
|
if (stopped) return;
|
|
6779
|
-
stack.catalog.set(derive(current(), models,
|
|
7296
|
+
stack.catalog.set(derive(current(), models, panel));
|
|
6780
7297
|
} catch (error) {
|
|
6781
|
-
ctx.logger.warn(`dsh-connect-trae: live ${
|
|
6782
|
-
stack.catalog.set(configuredModels(current(),
|
|
7298
|
+
ctx.logger.warn(`dsh-connect-trae: live ${panel} model directory unavailable; serving the configured catalog`, error);
|
|
7299
|
+
stack.catalog.set(configuredModels(current(), panel));
|
|
6783
7300
|
}
|
|
6784
7301
|
stack.invalidateAdapter();
|
|
6785
7302
|
})();
|
|
@@ -6789,4 +7306,4 @@ function apply(ctx, config) {
|
|
|
6789
7306
|
});
|
|
6790
7307
|
}
|
|
6791
7308
|
//#endregion
|
|
6792
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, creditPosition, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, generationOf, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionCreditPositionOf, 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, withContextAdaptation, withTraeRegion };
|
|
7309
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PANELS, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_PROVIDER_IDS, TRAE_PROVIDER_ID_DISPLAY_NAMES, TRAE_PROVIDER_ROUTES, 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, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, creditPosition, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, describeTraeFailure, discoveredCatalog, displayNameOfTraeProvider, fallbackModelsFor, followAppLoginEnabled, generationOf, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, providerOfPanel, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionCreditPositionOf, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfPanel, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, routeOfTraeProvider, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withContextAdaptation, withTraeRegion };
|