dsh-connect-trae 2.0.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.en.md +7 -3
- package/README.md +7 -3
- package/lib/client.js +256 -16
- package/lib/index.d.ts +165 -5
- package/lib/index.js +275 -51
- package/package.json +8 -4
package/lib/index.js
CHANGED
|
@@ -3043,8 +3043,11 @@ function parseUsageSnapshot(payload) {
|
|
|
3043
3043
|
};
|
|
3044
3044
|
}
|
|
3045
3045
|
/**
|
|
3046
|
-
* A
|
|
3047
|
-
*
|
|
3046
|
+
* A client for the verified Trae usage/credits endpoints.
|
|
3047
|
+
*
|
|
3048
|
+
* Every method is read-only except {@link TraeUsageClient.claimCheckin}, which
|
|
3049
|
+
* claims the daily check-in reward — the one deliberate mutation, requested by
|
|
3050
|
+
* the user from the card and guarded before it is sent.
|
|
3048
3051
|
*/
|
|
3049
3052
|
var TraeUsageClient = class {
|
|
3050
3053
|
options;
|
|
@@ -3070,6 +3073,20 @@ var TraeUsageClient = class {
|
|
|
3070
3073
|
async payBase() {
|
|
3071
3074
|
return this.baseUrl ?? REGION_GATEWAYS[await this.currentRegion()].pay;
|
|
3072
3075
|
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Device id for the check-in routes, or undefined when the machine's
|
|
3078
|
+
* installation identity cannot be read. Best-effort on purpose: a missing
|
|
3079
|
+
* identity must not break the read-only status query, which the upstream
|
|
3080
|
+
* answers with or without the header.
|
|
3081
|
+
*/
|
|
3082
|
+
async deviceIdHeader() {
|
|
3083
|
+
try {
|
|
3084
|
+
const deviceId = await this.options.deviceId?.();
|
|
3085
|
+
return deviceId === void 0 || deviceId === "" ? {} : { "x-device-id": deviceId };
|
|
3086
|
+
} catch {
|
|
3087
|
+
return {};
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3073
3090
|
async authedHeaders() {
|
|
3074
3091
|
const credential = await this.options.credential();
|
|
3075
3092
|
if (credential === void 0 || credential.accessToken === "") throw new Error("Trae credential is not available; cannot query usage");
|
|
@@ -3082,8 +3099,11 @@ var TraeUsageClient = class {
|
|
|
3082
3099
|
"Referer": `${origin}/`
|
|
3083
3100
|
};
|
|
3084
3101
|
}
|
|
3085
|
-
async post(path, data, signal) {
|
|
3086
|
-
const headers =
|
|
3102
|
+
async post(path, data, signal, extraHeaders = {}) {
|
|
3103
|
+
const headers = {
|
|
3104
|
+
...await this.authedHeaders(),
|
|
3105
|
+
...extraHeaders
|
|
3106
|
+
};
|
|
3087
3107
|
const response = await this.fetchImpl(`${await this.payBase()}${path}`, {
|
|
3088
3108
|
method: "POST",
|
|
3089
3109
|
headers,
|
|
@@ -3137,14 +3157,46 @@ var TraeUsageClient = class {
|
|
|
3137
3157
|
await this.requireCnRegion("usage snapshot");
|
|
3138
3158
|
return parseUsageSnapshot(await this.post("/trae/api/v2/pay/web_user_ent_usage", { require_usage: true }, signal));
|
|
3139
3159
|
}
|
|
3140
|
-
/**
|
|
3160
|
+
/**
|
|
3161
|
+
* Daily check-in status. Read-only, and answered with or without the device
|
|
3162
|
+
* header — the claim is what needs it.
|
|
3163
|
+
*/
|
|
3141
3164
|
async checkinStatus(signal) {
|
|
3142
3165
|
await this.requireCnRegion("check-in status");
|
|
3143
|
-
const
|
|
3166
|
+
const headers = await this.deviceIdHeader();
|
|
3167
|
+
const payload = await this.post("/trae/api/v2/ug/checkin_credits/status", {}, signal, headers);
|
|
3168
|
+
const extraCredits = asNumber(payload["extra_credits"]);
|
|
3144
3169
|
return {
|
|
3145
3170
|
checkedIn: payload["checked_in"] === true,
|
|
3146
3171
|
credits: asNumber(payload["credits"]) ?? 0,
|
|
3147
|
-
enabled: payload["enable"] !== false
|
|
3172
|
+
enabled: payload["enable"] !== false,
|
|
3173
|
+
didCheckedIn: payload["did_checked_in"] === true,
|
|
3174
|
+
...extraCredits === void 0 || extraCredits <= 0 ? {} : { extraCredits }
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Claim today's check-in reward. The ONLY state-changing call in this client.
|
|
3179
|
+
*
|
|
3180
|
+
* The upstream is idempotent per Beijing day (verified 2026-09-24: repeating
|
|
3181
|
+
* the call on an already-claimed day answers `code: 0` while the entitlement
|
|
3182
|
+
* total stays byte-identical), so a double click cannot double-grant. The
|
|
3183
|
+
* card and its route still guard, because "cannot double-grant" is a property
|
|
3184
|
+
* of the upstream we verify rather than one we rely on.
|
|
3185
|
+
*
|
|
3186
|
+
* A business refusal arrives as HTTP 200 with a non-zero `code` — most often
|
|
3187
|
+
* `9004` when the request carries no `x-device-id`. That is reported as
|
|
3188
|
+
* `claimed: false` rather than thrown, so the caller can tell "the upstream
|
|
3189
|
+
* refused this" apart from "the request never arrived".
|
|
3190
|
+
*/
|
|
3191
|
+
async claimCheckin(signal) {
|
|
3192
|
+
await this.requireCnRegion("check-in claim");
|
|
3193
|
+
const headers = await this.deviceIdHeader();
|
|
3194
|
+
const payload = await this.post("/trae/api/v2/ug/checkin_credits/claim", {}, signal, headers);
|
|
3195
|
+
const code = asNumber(payload["code"]) ?? 0;
|
|
3196
|
+
return {
|
|
3197
|
+
claimed: code === 0,
|
|
3198
|
+
code,
|
|
3199
|
+
message: typeof payload["message"] === "string" ? payload["message"].slice(0, 200) : ""
|
|
3148
3200
|
};
|
|
3149
3201
|
}
|
|
3150
3202
|
/** Rewards / activity rules. */
|
|
@@ -3189,6 +3241,11 @@ const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
|
3189
3241
|
const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
|
|
3190
3242
|
/** Plugin-owned local account rescan endpoint. */
|
|
3191
3243
|
const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
3244
|
+
/**
|
|
3245
|
+
* Plugin-owned daily check-in claim endpoint. POST, loopback-only, and the only
|
|
3246
|
+
* route in this plugin that changes upstream account state.
|
|
3247
|
+
*/
|
|
3248
|
+
const TRAE_CHECKIN_PATH = "/plugins/dsh-connect-trae/checkin";
|
|
3192
3249
|
/** Query parameter naming the region a card request addresses. */
|
|
3193
3250
|
const TRAE_REGION_PARAM = "region";
|
|
3194
3251
|
/** Every region, in card tab order. */
|
|
@@ -3211,6 +3268,74 @@ function regionOfTraeStatusUrl(url) {
|
|
|
3211
3268
|
if (value === null || value === "") return "cn";
|
|
3212
3269
|
return TRAE_REGIONS.includes(value) ? value : void 0;
|
|
3213
3270
|
}
|
|
3271
|
+
/**
|
|
3272
|
+
* Build the next `regions` settings value for the card's save. The write
|
|
3273
|
+
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
3274
|
+
* is carried over untouched, so switching accounts never clobbers the other
|
|
3275
|
+
* region's picks. Tolerates any stored shape (absent, non-object) by starting
|
|
3276
|
+
* from an empty document.
|
|
3277
|
+
*/
|
|
3278
|
+
function nextRegionSlots(regions, region, slot) {
|
|
3279
|
+
return {
|
|
3280
|
+
...typeof regions === "object" && regions !== null && !Array.isArray(regions) ? regions : {},
|
|
3281
|
+
[region]: slot
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
/**
|
|
3285
|
+
* Narrow a settings value to the `regions` map. Accepts EITHER the whole
|
|
3286
|
+
* settings section (`{ regions: {...}, ... }`) or the `regions` map itself, and
|
|
3287
|
+
* unwraps the former. This tolerance is deliberate: passing the whole section
|
|
3288
|
+
* where the map was expected was a real shipped bug — the lookup then read
|
|
3289
|
+
* `section['cn']` (absent), so the card's checkbox reported `true` forever and
|
|
3290
|
+
* clicking it appeared to do nothing even though the write succeeded.
|
|
3291
|
+
*/
|
|
3292
|
+
function regionsMapOf(value) {
|
|
3293
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
|
|
3294
|
+
const record = value;
|
|
3295
|
+
const nested = record["regions"];
|
|
3296
|
+
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
|
|
3297
|
+
return record;
|
|
3298
|
+
}
|
|
3299
|
+
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
3300
|
+
function regionSlotOf(value, region) {
|
|
3301
|
+
const slot = regionsMapOf(value)[region];
|
|
3302
|
+
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Whether one region's provider is switched on. Opt-out semantics: only an
|
|
3306
|
+
* explicit `false` disables it, so a config written before this switch existed
|
|
3307
|
+
* (and the pre-region-split flat fields, which never carry `enabled`) keep both
|
|
3308
|
+
* providers running exactly as before. The Host reads the same rule through
|
|
3309
|
+
* `regionStateOf`, so card and Host can never disagree about a region's state.
|
|
3310
|
+
*
|
|
3311
|
+
* `value` may be the whole settings section or the `regions` map (see
|
|
3312
|
+
* {@link regionsMapOf}).
|
|
3313
|
+
*/
|
|
3314
|
+
function regionEnabledOf(value, region) {
|
|
3315
|
+
return regionSlotOf(value, region)["enabled"] !== false;
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Build the next `regions` settings value for a provider on/off toggle. ONLY
|
|
3319
|
+
* the target region's `enabled` flag changes: every other field of that slot
|
|
3320
|
+
* (its directory, selection, image opt-ins, context budgets) and every other
|
|
3321
|
+
* region's slot are carried over verbatim, so switching a provider off never
|
|
3322
|
+
* discards the user's model picks and switching it back on restores them.
|
|
3323
|
+
*
|
|
3324
|
+
* This is deliberately separate from {@link nextRegionSlots}: that helper
|
|
3325
|
+
* writes a whole slot from a signed-in tab's draft, while this one must work
|
|
3326
|
+
* for a region that is signed OUT — which is precisely the region a user wants
|
|
3327
|
+
* to switch off (no international install, no international account).
|
|
3328
|
+
*
|
|
3329
|
+
* `value` may be the whole settings section or the `regions` map; the RETURN
|
|
3330
|
+
* value is always the `regions` map, i.e. exactly what `settingsScope.set(
|
|
3331
|
+
* 'regions', ...)` needs.
|
|
3332
|
+
*/
|
|
3333
|
+
function nextRegionEnabled(value, region, enabled) {
|
|
3334
|
+
return nextRegionSlots(regionsMapOf(value), region, {
|
|
3335
|
+
...regionSlotOf(value, region),
|
|
3336
|
+
enabled
|
|
3337
|
+
});
|
|
3338
|
+
}
|
|
3214
3339
|
//#endregion
|
|
3215
3340
|
//#region src/web-status.ts
|
|
3216
3341
|
/** Redact token-like content before it crosses to the browser. */
|
|
@@ -3255,6 +3380,16 @@ function toCredits(snapshot) {
|
|
|
3255
3380
|
accounts
|
|
3256
3381
|
};
|
|
3257
3382
|
}
|
|
3383
|
+
/** Map the check-in answer to the card's compact document. */
|
|
3384
|
+
function toCheckin(status) {
|
|
3385
|
+
return {
|
|
3386
|
+
checkedIn: status.checkedIn,
|
|
3387
|
+
didCheckedIn: status.didCheckedIn,
|
|
3388
|
+
credits: status.credits,
|
|
3389
|
+
enabled: status.enabled,
|
|
3390
|
+
...status.extraCredits === void 0 ? {} : { extraCredits: status.extraCredits }
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3258
3393
|
/**
|
|
3259
3394
|
* Assemble one region's card document. `region` is the tab the card is on; the
|
|
3260
3395
|
* region-scoped store already answers with only that region's accounts, so the
|
|
@@ -3264,12 +3399,14 @@ function toCredits(snapshot) {
|
|
|
3264
3399
|
*/
|
|
3265
3400
|
async function traeWebUsage(deps, region) {
|
|
3266
3401
|
const store = deps.store(region);
|
|
3402
|
+
const enabled = deps.regionEnabled(region);
|
|
3267
3403
|
const accounts = await store.accounts();
|
|
3268
3404
|
if ((await store.status()).state !== "signed-in") {
|
|
3269
3405
|
const { failures } = await store.diagnose();
|
|
3270
3406
|
return {
|
|
3271
3407
|
status: "signed-out",
|
|
3272
3408
|
accounts,
|
|
3409
|
+
enabled,
|
|
3273
3410
|
searched: failures.map((failure) => ({
|
|
3274
3411
|
path: failure.path,
|
|
3275
3412
|
edition: failure.edition,
|
|
@@ -3286,6 +3423,7 @@ async function traeWebUsage(deps, region) {
|
|
|
3286
3423
|
return {
|
|
3287
3424
|
status: "signed-out",
|
|
3288
3425
|
accounts,
|
|
3426
|
+
enabled,
|
|
3289
3427
|
message: safeMessage(error)
|
|
3290
3428
|
};
|
|
3291
3429
|
}
|
|
@@ -3294,6 +3432,7 @@ async function traeWebUsage(deps, region) {
|
|
|
3294
3432
|
accountName: credential.accountName ?? credential.userId,
|
|
3295
3433
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
3296
3434
|
region,
|
|
3435
|
+
enabled,
|
|
3297
3436
|
accounts,
|
|
3298
3437
|
models: deps.displayModels(region).map((model) => ({
|
|
3299
3438
|
...model,
|
|
@@ -3317,20 +3456,13 @@ async function traeWebUsage(deps, region) {
|
|
|
3317
3456
|
payStatusError: safeMessage(error)
|
|
3318
3457
|
};
|
|
3319
3458
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
} catch (error) {
|
|
3328
|
-
return {
|
|
3329
|
-
status: "signed-in",
|
|
3330
|
-
...account,
|
|
3331
|
-
creditsError: safeMessage(error)
|
|
3332
|
-
};
|
|
3333
|
-
}
|
|
3459
|
+
const [snapshotResult, checkinResult] = await Promise.allSettled([client.snapshot(), client.checkinStatus()]);
|
|
3460
|
+
return {
|
|
3461
|
+
status: "signed-in",
|
|
3462
|
+
...account,
|
|
3463
|
+
...snapshotResult.status === "fulfilled" ? { credits: toCredits(snapshotResult.value) } : { creditsError: safeMessage(snapshotResult.reason) },
|
|
3464
|
+
...checkinResult.status === "fulfilled" ? { checkin: toCheckin(checkinResult.value) } : { checkinError: safeMessage(checkinResult.reason) }
|
|
3465
|
+
};
|
|
3334
3466
|
}
|
|
3335
3467
|
/**
|
|
3336
3468
|
* The region a request addresses, or a 400 answer. Absent parameter means the
|
|
@@ -3399,7 +3531,49 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
3399
3531
|
}
|
|
3400
3532
|
}
|
|
3401
3533
|
});
|
|
3534
|
+
/**
|
|
3535
|
+
* Daily check-in claim — the ONLY route in this plugin that mutates
|
|
3536
|
+
* upstream account state, so it is guarded more tightly than its siblings:
|
|
3537
|
+
* POST only, loopback origin only, and the status read runs FIRST so an
|
|
3538
|
+
* already-claimed day never reaches the upstream claim at all. The upstream
|
|
3539
|
+
* is idempotent per Beijing day anyway (verified 2026-09-24: a repeat claim
|
|
3540
|
+
* answers `code: 0` with the entitlement total unchanged), but relying on
|
|
3541
|
+
* that for correctness would put the guard in someone else's hands.
|
|
3542
|
+
*/
|
|
3543
|
+
const disposeCheckin = ctx.webServer.register({
|
|
3544
|
+
kind: "exact",
|
|
3545
|
+
path: TRAE_CHECKIN_PATH,
|
|
3546
|
+
handler: async (req, res) => {
|
|
3547
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
3548
|
+
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
3549
|
+
const region = requestRegion(req, res);
|
|
3550
|
+
if (region === void 0) return;
|
|
3551
|
+
if (region !== "cn") return json(res, 404, { error: "check-in is not available for the international region" });
|
|
3552
|
+
try {
|
|
3553
|
+
const client = deps.client(region);
|
|
3554
|
+
const current = await client.checkinStatus();
|
|
3555
|
+
if (!current.enabled) return json(res, 409, { error: "check-in is not enabled for this account" });
|
|
3556
|
+
if (current.checkedIn || current.didCheckedIn) return json(res, 200, {
|
|
3557
|
+
claimed: false,
|
|
3558
|
+
alreadyCheckedIn: true,
|
|
3559
|
+
checkin: toCheckin(current)
|
|
3560
|
+
});
|
|
3561
|
+
const claim = await client.claimCheckin();
|
|
3562
|
+
const checkin = await client.checkinStatus();
|
|
3563
|
+
json(res, 200, {
|
|
3564
|
+
claimed: claim.claimed,
|
|
3565
|
+
alreadyCheckedIn: false,
|
|
3566
|
+
code: claim.code,
|
|
3567
|
+
message: claim.message,
|
|
3568
|
+
checkin: toCheckin(checkin)
|
|
3569
|
+
});
|
|
3570
|
+
} catch (error) {
|
|
3571
|
+
json(res, 500, { error: safeMessage(error) });
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3402
3575
|
return () => {
|
|
3576
|
+
disposeCheckin();
|
|
3403
3577
|
disposeRefresh();
|
|
3404
3578
|
disposeAccounts();
|
|
3405
3579
|
disposeUsage();
|
|
@@ -3571,6 +3745,17 @@ function regionStateOf(config, region) {
|
|
|
3571
3745
|
...config.contextBudgets === void 0 ? {} : { contextBudgets: config.contextBudgets }
|
|
3572
3746
|
};
|
|
3573
3747
|
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Whether one region's provider is switched on (issue #11). Opt-out semantics:
|
|
3750
|
+
* only an explicit `false` disables it, so every config written before this
|
|
3751
|
+
* switch existed — including the pre-region-split flat fields, which never
|
|
3752
|
+
* carry `enabled` — keeps both providers running exactly as before. The card
|
|
3753
|
+
* reads the same rule through `regionEnabledOf`, so the two halves can never
|
|
3754
|
+
* disagree about a region's state.
|
|
3755
|
+
*/
|
|
3756
|
+
function regionEnabled(config, region) {
|
|
3757
|
+
return regionStateOf(config, region).enabled !== false;
|
|
3758
|
+
}
|
|
3574
3759
|
const modelConfig = z.object({
|
|
3575
3760
|
id: z.string().required(),
|
|
3576
3761
|
name: z.string().required(),
|
|
@@ -3581,6 +3766,7 @@ const modelConfig = z.object({
|
|
|
3581
3766
|
wireFunction: z.string()
|
|
3582
3767
|
});
|
|
3583
3768
|
const regionStateConfig = z.object({
|
|
3769
|
+
enabled: z.boolean().default(true).description("Whether this region's provider is offered to DSH (opt-out; false withdraws it entirely)"),
|
|
3584
3770
|
lastCatalog: z.array(modelConfig).default([]),
|
|
3585
3771
|
enabledModelIds: z.array(z.string()).default([]),
|
|
3586
3772
|
imageModelIds: z.array(z.string()).default([]),
|
|
@@ -3782,7 +3968,10 @@ function apply(ctx, config) {
|
|
|
3782
3968
|
catalog,
|
|
3783
3969
|
shim,
|
|
3784
3970
|
delegating,
|
|
3785
|
-
usageClient: new TraeUsageClient({
|
|
3971
|
+
usageClient: new TraeUsageClient({
|
|
3972
|
+
credential: () => store.resolve(),
|
|
3973
|
+
deviceId: async () => (await identity()).deviceId
|
|
3974
|
+
}),
|
|
3786
3975
|
discoverModels: async (signal) => {
|
|
3787
3976
|
const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
|
|
3788
3977
|
wire.callableKeys.clear();
|
|
@@ -3812,9 +4001,42 @@ function apply(ctx, config) {
|
|
|
3812
4001
|
client: (region) => stacks[region].usageClient,
|
|
3813
4002
|
displayModels: (region) => displayModels(current(), region),
|
|
3814
4003
|
enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
|
|
4004
|
+
regionEnabled: (region) => regionEnabled(current(), region),
|
|
3815
4005
|
discoverModels: (region, signal) => stacks[region].discoverModels(signal),
|
|
3816
4006
|
rawDiagnostic: (region) => stacks[region].rawDiagnostic()
|
|
3817
4007
|
}));
|
|
4008
|
+
/**
|
|
4009
|
+
* Live registration handles per region, filled once the shim is listening.
|
|
4010
|
+
* A disabled region holds ZERO routes while staying registered: DSH allows
|
|
4011
|
+
* `replace([])` for exactly this case ("a settings section that emptied holds
|
|
4012
|
+
* zero routes while staying registered"), which is what makes the on/off
|
|
4013
|
+
* switch reversible without a restart. Withdrawing the adapter route is what
|
|
4014
|
+
* actually removes the region's models from DSH's model picker — hiding the
|
|
4015
|
+
* card tab alone would leave every model selectable.
|
|
4016
|
+
*/
|
|
4017
|
+
const registration = {
|
|
4018
|
+
cn: {},
|
|
4019
|
+
ai: {}
|
|
4020
|
+
};
|
|
4021
|
+
/**
|
|
4022
|
+
* Publish each region's on/off state to the harness (issue #11). Both swaps
|
|
4023
|
+
* are single synchronous sections, so no request can observe a half-applied
|
|
4024
|
+
* state, and `replace` announces itself through `llm/adapters-updated`, which
|
|
4025
|
+
* is what makes third-party consumers (e.g. a vision router deriving
|
|
4026
|
+
* `<provider>-vision` variants) drop the region too. No-op until the shim has
|
|
4027
|
+
* registered; `applySelection` runs again on every card write, so a toggle
|
|
4028
|
+
* lands immediately.
|
|
4029
|
+
*/
|
|
4030
|
+
const syncRegionRegistration = (value) => {
|
|
4031
|
+
for (const region of REGION_KEYS) registration[region].adapter?.replace(regionEnabled(value, region) ? [TRAE_PROVIDERS[region]] : []);
|
|
4032
|
+
registration.cn.directory?.replace(REGION_KEYS.filter((region) => regionEnabled(value, region)).map((region) => ({
|
|
4033
|
+
provider: TRAE_PROVIDERS[region],
|
|
4034
|
+
displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
|
|
4035
|
+
settingsNs: TRAE_SETTINGS_NS,
|
|
4036
|
+
settingsPath: [],
|
|
4037
|
+
declared: false
|
|
4038
|
+
})));
|
|
4039
|
+
};
|
|
3818
4040
|
/** Push the current config into every region's store selection and catalog. */
|
|
3819
4041
|
const applySelection = (value) => {
|
|
3820
4042
|
for (const region of REGION_KEYS) {
|
|
@@ -3823,6 +4045,7 @@ function apply(ctx, config) {
|
|
|
3823
4045
|
stack.catalog.set(configuredModels(value, region));
|
|
3824
4046
|
stack.invalidateAdapter();
|
|
3825
4047
|
}
|
|
4048
|
+
syncRegionRegistration(value);
|
|
3826
4049
|
};
|
|
3827
4050
|
const sectionHooks = {
|
|
3828
4051
|
setSource(source) {
|
|
@@ -3856,9 +4079,6 @@ function apply(ctx, config) {
|
|
|
3856
4079
|
});
|
|
3857
4080
|
Promise.all(REGION_KEYS.map((region) => stacks[region].shim.ready)).then(async () => {
|
|
3858
4081
|
if (stopped) return;
|
|
3859
|
-
let releaseAdapterCn;
|
|
3860
|
-
let releaseAdapterAi;
|
|
3861
|
-
let releaseDirectory;
|
|
3862
4082
|
try {
|
|
3863
4083
|
for (const region of REGION_KEYS) {
|
|
3864
4084
|
const stack = stacks[region];
|
|
@@ -3872,12 +4092,12 @@ function apply(ctx, config) {
|
|
|
3872
4092
|
stack.invalidateAdapter = () => {
|
|
3873
4093
|
trae.invalidate();
|
|
3874
4094
|
};
|
|
3875
|
-
|
|
3876
|
-
else releaseAdapterAi = ctx.llm.registerAdapter([TRAE_AI_PROVIDER], trae.adapter);
|
|
4095
|
+
registration[region].adapter = region === "cn" ? ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter) : ctx.llm.registerAdapter([TRAE_AI_PROVIDER], trae.adapter);
|
|
3877
4096
|
}
|
|
3878
4097
|
ctx.llm.registerModelDiscovery(TRAE_SETTINGS_NS, async (request, signal) => {
|
|
3879
4098
|
const region = regionOfTraeProvider(request.provider ?? "");
|
|
3880
4099
|
if (region === void 0) return [];
|
|
4100
|
+
if (!regionEnabled(current(), region)) return [];
|
|
3881
4101
|
const cancellation = signal ?? request.signal;
|
|
3882
4102
|
return applyImageSelection(await stacks[region].discoverModels(cancellation), imageSet(current(), region)).map((model) => ({
|
|
3883
4103
|
id: model.id,
|
|
@@ -3887,46 +4107,50 @@ function apply(ctx, config) {
|
|
|
3887
4107
|
inputModalities: traeInputModalities(model)
|
|
3888
4108
|
}));
|
|
3889
4109
|
});
|
|
3890
|
-
|
|
4110
|
+
registration.cn.directory = ctx.llm.registerConfigurableProviders(REGION_KEYS.map((region) => ({
|
|
3891
4111
|
provider: TRAE_PROVIDERS[region],
|
|
3892
4112
|
displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
|
|
3893
4113
|
settingsNs: TRAE_SETTINGS_NS,
|
|
3894
4114
|
settingsPath: [],
|
|
3895
4115
|
declared: false
|
|
3896
4116
|
})));
|
|
4117
|
+
syncRegionRegistration(current());
|
|
3897
4118
|
} finally {
|
|
3898
|
-
if (
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4119
|
+
if (registration.cn.adapter === void 0 || registration.ai.adapter === void 0 || registration.cn.directory === void 0) {
|
|
4120
|
+
registration.cn.adapter?.();
|
|
4121
|
+
registration.ai.adapter?.();
|
|
4122
|
+
registration.cn.directory?.();
|
|
3902
4123
|
}
|
|
3903
4124
|
}
|
|
3904
4125
|
try {
|
|
3905
4126
|
ctx.effect(() => () => {
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
4127
|
+
registration.cn.adapter?.();
|
|
4128
|
+
registration.ai.adapter?.();
|
|
4129
|
+
registration.cn.directory?.();
|
|
3909
4130
|
});
|
|
3910
4131
|
} catch {
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4132
|
+
registration.cn.adapter?.();
|
|
4133
|
+
registration.ai.adapter?.();
|
|
4134
|
+
registration.cn.directory?.();
|
|
4135
|
+
}
|
|
4136
|
+
for (const region of REGION_KEYS) {
|
|
4137
|
+
if (!regionEnabled(current(), region)) continue;
|
|
4138
|
+
(async () => {
|
|
4139
|
+
const stack = stacks[region];
|
|
4140
|
+
try {
|
|
4141
|
+
const models = await stack.discoverModels();
|
|
4142
|
+
if (stopped) return;
|
|
4143
|
+
stack.catalog.set(derive(current(), models, region));
|
|
4144
|
+
} catch (error) {
|
|
4145
|
+
ctx.logger.warn(`dsh-connect-trae: live ${region} model directory unavailable; serving the configured catalog`, error);
|
|
4146
|
+
stack.catalog.set(configuredModels(current(), region));
|
|
4147
|
+
}
|
|
4148
|
+
stack.invalidateAdapter();
|
|
4149
|
+
})();
|
|
3914
4150
|
}
|
|
3915
|
-
for (const region of REGION_KEYS) (async () => {
|
|
3916
|
-
const stack = stacks[region];
|
|
3917
|
-
try {
|
|
3918
|
-
const models = await stack.discoverModels();
|
|
3919
|
-
if (stopped) return;
|
|
3920
|
-
stack.catalog.set(derive(current(), models, region));
|
|
3921
|
-
} catch (error) {
|
|
3922
|
-
ctx.logger.warn(`dsh-connect-trae: live ${region} model directory unavailable; serving the configured catalog`, error);
|
|
3923
|
-
stack.catalog.set(configuredModels(current(), region));
|
|
3924
|
-
}
|
|
3925
|
-
stack.invalidateAdapter();
|
|
3926
|
-
})();
|
|
3927
4151
|
}).catch((error) => {
|
|
3928
4152
|
ctx.logger.error("dsh-connect-trae: loopback shim failed; providers not registered", error);
|
|
3929
4153
|
});
|
|
3930
4154
|
}
|
|
3931
4155
|
//#endregion
|
|
3932
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
|
|
4156
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
|
-
"description": "Connect locally signed-in Trae models to DeepSeek Harness,
|
|
5
|
-
"version": "2.0
|
|
4
|
+
"description": "Connect locally signed-in Trae models to DeepSeek Harness, with a usage/credits summary and daily check-in claiming.",
|
|
5
|
+
"version": "2.1.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|
|
@@ -75,12 +75,12 @@
|
|
|
75
75
|
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
76
76
|
"@deepseek-ai/dsh-atomic-write": ">=0.1.5-0 <0.2.0-0",
|
|
77
77
|
"@deepseek-ai/dsh-attachment": ">=0.1.5-0 <0.2.0-0",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-0 <0.2.0-0",
|
|
78
79
|
"@deepseek-ai/dsh-home-paths": ">=0.1.5-0 <0.2.0-0",
|
|
79
80
|
"@deepseek-ai/dsh-host-webserver": ">=0.1.5-0 <0.2.0-0",
|
|
80
81
|
"@deepseek-ai/dsh-llm": ">=0.1.5-0 <0.2.0-0",
|
|
81
82
|
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.5-0 <0.2.0-0",
|
|
82
83
|
"@deepseek-ai/dsh-settings": ">=0.1.5-0 <0.2.0-0",
|
|
83
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.5-0 <0.2.0-0",
|
|
84
84
|
"@deepseek-ai/schemastery": "^3.18.1-rc.1",
|
|
85
85
|
"@earendil-works/pi-ai": ">=0.85.1",
|
|
86
86
|
"react": "^18.2.0"
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
"@deepseek-ai/dsh-atomic-write": "0.1.5-rc.2",
|
|
91
91
|
"@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
|
|
92
92
|
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
|
|
93
94
|
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
|
|
94
95
|
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
95
96
|
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-rc.2",
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
|
|
97
97
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
|
|
98
98
|
"@deepseek-ai/dsh-home-paths": "0.1.5-rc.2",
|
|
99
99
|
"@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
|
|
@@ -102,9 +102,13 @@
|
|
|
102
102
|
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
103
103
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
104
104
|
"@earendil-works/pi-ai": "^0.85.1",
|
|
105
|
+
"@testing-library/dom": "^10.4.2",
|
|
106
|
+
"@testing-library/react": "^16.3.3",
|
|
105
107
|
"@types/node": "^22.20.0",
|
|
106
108
|
"@types/react": "~18.3.31",
|
|
109
|
+
"jsdom": "^25.0.1",
|
|
107
110
|
"react": "^18.3.1",
|
|
111
|
+
"react-dom": "18.3.1",
|
|
108
112
|
"tsdown": "^0.22.2",
|
|
109
113
|
"typescript": "^5.9.3",
|
|
110
114
|
"vitest": "^4.1.8"
|