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/lib/index.js CHANGED
@@ -3043,8 +3043,11 @@ function parseUsageSnapshot(payload) {
3043
3043
  };
3044
3044
  }
3045
3045
  /**
3046
- * A read-only client for the verified Trae usage/credits endpoints.
3047
- * All methods are safe to call from a plugin and never mutate account state.
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 = await this.authedHeaders();
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
- /** Daily check-in status. */
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 payload = await this.post("/trae/api/v2/ug/checkin_credits/status", {}, signal);
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
- try {
3321
- const snapshot = await client.snapshot();
3322
- return {
3323
- status: "signed-in",
3324
- ...account,
3325
- credits: toCredits(snapshot)
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({ credential: () => store.resolve() }),
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
- if (region === "cn") releaseAdapterCn = ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter);
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
- releaseDirectory = ctx.llm.registerConfigurableProviders(REGION_KEYS.map((region) => ({
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 (releaseAdapterCn === void 0 || releaseAdapterAi === void 0 || releaseDirectory === void 0) {
3899
- releaseAdapterCn?.();
3900
- releaseAdapterAi?.();
3901
- releaseDirectory?.();
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
- releaseAdapterCn?.();
3907
- releaseAdapterAi?.();
3908
- releaseDirectory?.();
4127
+ registration.cn.adapter?.();
4128
+ registration.ai.adapter?.();
4129
+ registration.cn.directory?.();
3909
4130
  });
3910
4131
  } catch {
3911
- releaseAdapterCn?.();
3912
- releaseAdapterAi?.();
3913
- releaseDirectory?.();
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, plus a read-only usage/credits summary.",
5
- "version": "2.0.5",
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"