dsh-connect-trae 4.6.11 → 4.8.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
@@ -1,7 +1,7 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { createProvider } from "@earendil-works/pi-ai";
3
3
  import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
4
- import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
+ import { ReasoningEffortId, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
5
5
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { basename, dirname, join } from "node:path";
@@ -66,6 +66,46 @@ function contextBudgetOptions(model) {
66
66
  //#endregion
67
67
  //#region src/catalog.ts
68
68
  /**
69
+ * Conservative context capacity used when a row must be advertised with a
70
+ * number but states no usable one of its own. Every fallback row and every
71
+ * live Trae row is expected to carry its real window; this is the backstop
72
+ * that keeps one unsized row from failing the entire provider route
73
+ * (`INVALID_MODEL_CONTEXT`).
74
+ *
75
+ * APPLIED AT EVERY EXIT TOWARD DSH, NOT ONLY IN THE ADAPTER. The pi-ai
76
+ * profile's `defaultContextWindow` is read by the kernel's catalog-resolution
77
+ * step, which this plugin never runs: it builds its provider BY HAND through
78
+ * `createProvider({ models })`, so `Models.getModel()` returns the raw
79
+ * `toPiModel` row with no defaulting applied anywhere. Leaving the fallback on
80
+ * the profile alone is what produced
81
+ * `adapter returned invalid context metadata for provider "trae" model
82
+ * "doubao_1.8"` — a row the live directory published without a window reached
83
+ * DSH unsized and took the whole `trae` route down (#70).
84
+ *
85
+ * The model-DISCOVERY callback is a second exit with no adapter in between
86
+ * (#72): the host passes its rows through UNVALIDATED, and a settings surface
87
+ * adopts one verbatim into a `models` entry, where the schema demands a
88
+ * positive integer. So this rule guards the row PRODUCERS here as well.
89
+ */
90
+ const FALLBACK_CONTEXT_WINDOW = 2e5;
91
+ /**
92
+ * The window a row is actually advertised with.
93
+ *
94
+ * Positive integers pass through untouched — a row's own window is never
95
+ * second-guessed. Anything else (missing, `NaN`, `0`, a negative or a
96
+ * fractional count from a malformed directory entry) falls back, because DSH
97
+ * accepts ONLY a positive integer and rejects the entire provider otherwise.
98
+ *
99
+ * The fractional case is not hypothetical: the parsers that fill these rows
100
+ * gate the field through `finitePositive`, which checks
101
+ * `Number.isFinite && > 0` and nothing about integer-ness, so a directory or
102
+ * wire roster publishing `200000.5` reaches here intact.
103
+ */
104
+ function servedContextWindow(info) {
105
+ const window = info.contextWindow;
106
+ return Number.isInteger(window) && window > 0 ? window : FALLBACK_CONTEXT_WINDOW;
107
+ }
108
+ /**
69
109
  * Bootstrap catalog: identity only where current Trae metadata has not been
70
110
  * fetched yet.
71
111
  *
@@ -397,7 +437,7 @@ function mergeTraeModelSources(remote, wire) {
397
437
  result.push({
398
438
  id: model.id,
399
439
  name: model.name,
400
- ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
440
+ ...model.contextWindow === void 0 ? {} : { contextWindow: servedContextWindow(model) },
401
441
  ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
402
442
  ...creditMultiplier === void 0 ? {} : { creditMultiplier },
403
443
  ...activityDiscount === void 0 ? {} : { activityDiscount },
@@ -469,7 +509,7 @@ function mergeTraeModelSources(remote, wire) {
469
509
  result.push({
470
510
  id: wireModel.id,
471
511
  name,
472
- ...wireModel.contextWindow === void 0 ? {} : { contextWindow: wireModel.contextWindow },
512
+ ...wireModel.contextWindow === void 0 ? {} : { contextWindow: servedContextWindow(wireModel) },
473
513
  ...wireModel.maxTokens === void 0 ? {} : { maxTokens: wireModel.maxTokens },
474
514
  ...wireModel.creditMultiplier === void 0 ? {} : { creditMultiplier: wireModel.creditMultiplier },
475
515
  ...wireModel.activityDiscount === void 0 ? {} : { activityDiscount: wireModel.activityDiscount },
@@ -544,7 +584,7 @@ function discoveredCatalog(models) {
544
584
  for (const model of models) result.push({
545
585
  id: model.id,
546
586
  name: model.name,
547
- ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
587
+ ...model.contextWindow === void 0 ? {} : { contextWindow: servedContextWindow(model) },
548
588
  ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
549
589
  input: ["text"],
550
590
  ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
@@ -777,6 +817,144 @@ function readManifest(directory) {
777
817
  }
778
818
  }
779
819
  //#endregion
820
+ //#region src/reasoning.ts
821
+ /**
822
+ * The DSH thinking levels this plugin can carry, in ascending order.
823
+ *
824
+ * `max` is the fifth level Trae's own client advertises — its bundle ships the
825
+ * i18n keys `trae-chat-core.model.reasoning_effort.{low,medium,high,extra_high,max}`
826
+ * and `ai.model.reasoning_effort.{low,medium,high,xhigh,max}`, and publishes the
827
+ * per-model list from a remote config (`AI.agent.model.reasoning_effort_level_by_agent_model_v2`).
828
+ * Leaving it out of this list meant a level the vendor could start serving was
829
+ * filtered away at PARSE time, before anything downstream could decide (issue
830
+ * #60). Listing it does not fabricate the level: `supported` is still built
831
+ * only from what the upstream response actually contains, so a model that does
832
+ * not advertise `max` never offers it.
833
+ *
834
+ * `minimal` is kept for the same reason in reverse — it is a DSH level, so the
835
+ * type must be able to represent it even though no CN model has advertised it.
836
+ */
837
+ const TRAE_REASONING_EFFORTS = [
838
+ "minimal",
839
+ "low",
840
+ "medium",
841
+ "high",
842
+ "xhigh",
843
+ "max"
844
+ ];
845
+ function parseReasoningCapability(value) {
846
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
847
+ const record = value;
848
+ const supported = (Array.isArray(record["reasoning_effort_options"]) ? record["reasoning_effort_options"] : []).filter((item) => typeof item === "string" && TRAE_REASONING_EFFORTS.includes(item));
849
+ const rawDefault = record["default_reasoning_effort"];
850
+ const defaultEffort = typeof rawDefault === "string" && supported.includes(rawDefault) ? rawDefault : void 0;
851
+ if (supported.length === 0 && defaultEffort === void 0) return void 0;
852
+ return {
853
+ supported,
854
+ ...defaultEffort === void 0 ? {} : { defaultEffort }
855
+ };
856
+ }
857
+ /** Add effort only when the selected model advertises that exact value. */
858
+ function applyReasoningEffort(body, effort, capability) {
859
+ if (effort === void 0) return body;
860
+ if (capability === void 0 || !capability.supported.includes(effort)) throw new Error(`Trae model does not advertise reasoning effort ${effort}`);
861
+ return {
862
+ ...body,
863
+ reasoning_effort: effort
864
+ };
865
+ }
866
+ //#endregion
867
+ //#region src/default-effort.ts
868
+ /**
869
+ * Which thinking level a model runs at when the caller names none.
870
+ *
871
+ * ## Why this module exists
872
+ *
873
+ * Trae's directory already tells us each model's own default level
874
+ * (`reasoning_effort_config.default_level`, parsed into `reasoning.defaultEffort`
875
+ * by `model-metadata.ts`) — measured 2026-11-03 on a live CN credential:
876
+ * `deepseek-v4.1-flash` says `high`, `glm-5.3` says `xhigh`. The card's meta row
877
+ * names the tiers it accepts, so the fact is known.
878
+ *
879
+ * DSH's model picker nevertheless reads **Default**, because the adapter never
880
+ * reports that level upward (`reasoning.defaultEffort` is absent from what
881
+ * {@link toPiModel} hands the kernel). The picker therefore cannot say which
882
+ * tier a request will run at, and the request itself carries no
883
+ * `reasoning_effort` — the upstream falls back to the same level it already
884
+ * published, so nothing is broken, but nothing is knowable either, and the user
885
+ * has no way to say "always use high on this model, regardless of what Trae
886
+ * ships as its default".
887
+ *
888
+ * ## The three seams that must agree (any one missing and the picker still says Default)
889
+ *
890
+ * 1. **The adapter** must report the level as `LlmResolvedModelInfo.reasoning.defaultEffort`.
891
+ * The kernel materialises that into every request that names no effort
892
+ * (`dsh-llm` `resolveCallWithInfo`: `const effective = requested ?? reasoning.defaultEffort`),
893
+ * and DSH's model picker takes its "current level" from the same metadata.
894
+ * 2. **A per-model saved choice** must survive a settings save. The settings
895
+ * channel rebuilds the whole region document through its schema, so a field
896
+ * absent from that schema is dropped on every save — the failure that once
897
+ * reset `wireConfigName` (see the `modelConfig` schema).
898
+ * 3. **The bridge** must translate the level it receives into Trae's wire name.
899
+ * `solo-bridge.ts` already does exactly that through the model's own
900
+ * `reasoningEfforts` table, and deletes the field when the model does not
901
+ * advertise it — so a level is never sent under a name the vendor never said.
902
+ *
903
+ * ## Precedence
904
+ *
905
+ * The user's explicit choice > the level upstream declares > this model's own
906
+ * middle tier.
907
+ *
908
+ * @module dsh-connect-trae/default-effort
909
+ */
910
+ /**
911
+ * The tier to fall back to when a model publishes a tier list but names no
912
+ * default of its own and the user has never chosen one.
913
+ *
914
+ * `medium` is tried first because it is the only middle tier shared by the
915
+ * families seen so far. It is deliberately not the only fallback: a model that
916
+ * publishes `low / high` (measured on `Doubao-Seed-2.1-Pro` and
917
+ * `step-5-preview`) has no `medium` at all, and picking its weakest tier instead
918
+ * would silently mean "thinking off" — that is not a fallback, that is the worst
919
+ * possible default.
920
+ */
921
+ const TRAE_FALLBACK_EFFORT = "medium";
922
+ /**
923
+ * The tiers a model actually declares, filtered to ones DSH can name.
924
+ *
925
+ * Upstream's vocabulary is not DSH's: Trae publishes `light / high / extra_high`
926
+ * and the parser maps them to `low / high / xhigh` (`EFFORT_MAP`). Anything that
927
+ * still fails to land in DSH's six names is dropped rather than passed through —
928
+ * a level the kernel cannot name would be rejected with
929
+ * `UNSUPPORTED_REASONING_EFFORT` and take the whole request with it.
930
+ *
931
+ * The result keeps `TRAE_REASONING_EFFORTS` order, so it reads weakest-first.
932
+ */
933
+ function declaredEfforts(model) {
934
+ const supported = model.supported ?? [];
935
+ return TRAE_REASONING_EFFORTS.filter((level) => supported.includes(level));
936
+ }
937
+ /**
938
+ * The tier this model should run at when a request names none.
939
+ *
940
+ * @param model - the model, with the tiers it declares and its upstream default.
941
+ * @param saved - the user's per-model choices (model id -> tier). A missing key
942
+ * means the user never chose, which is different from choosing the tier that
943
+ * happens to equal the upstream default.
944
+ * @returns a tier **this model declares**, or `undefined` when it declares none.
945
+ */
946
+ function defaultEffortFor(model, saved = {}) {
947
+ const declared = declaredEfforts(model);
948
+ if (declared.length === 0) return void 0;
949
+ const asked = saved[model.id];
950
+ const chosen = declared.find((level) => level === asked);
951
+ if (chosen !== void 0) return chosen;
952
+ const upstream = declared.find((level) => level === model.defaultEffort);
953
+ if (upstream !== void 0) return upstream;
954
+ if (declared.includes("medium")) return TRAE_FALLBACK_EFFORT;
955
+ return declared[Math.floor((declared.length - 1) / 2)];
956
+ }
957
+ //#endregion
780
958
  //#region src/adapter.ts
781
959
  /** Provider route this bundle owns for the domestic (CN) gateway. */
782
960
  const TRAE_PROVIDER = "trae";
@@ -861,35 +1039,6 @@ const REQUEST_IMAGE_BUDGETS = {
861
1039
  requestImagePixelBudget: 4194304,
862
1040
  requestImageMaxBytes: 1048576
863
1041
  };
864
- /**
865
- * Conservative context capacity used only when a served model row carries no
866
- * window of its own. Every fallback row and every live Trae row is expected to
867
- * state its real window; this is the backstop that keeps one unsized row from
868
- * failing the entire provider route (`INVALID_MODEL_CONTEXT`).
869
- *
870
- * APPLIED IN {@link toPiModel}, NOT ONLY ON THE PROFILE. The profile's
871
- * `defaultContextWindow` is read by the kernel's catalog-resolution step, which
872
- * this plugin never runs: it builds its pi-ai provider BY HAND through
873
- * `createProvider({ models })`, and `Models.getModel()` then returns the raw
874
- * `toPiModel` row with no defaulting applied anywhere. Leaving the fallback on
875
- * the profile alone is what produced
876
- * `adapter returned invalid context metadata for provider "trae" model
877
- * "doubao_1.8"` — a row the live directory published without a window reached
878
- * DSH unsized and took the whole `trae` route down.
879
- */
880
- const FALLBACK_CONTEXT_WINDOW = 2e5;
881
- /**
882
- * The window a served row is actually advertised with.
883
- *
884
- * Positive integers pass through untouched — a row's own window is never
885
- * second-guessed. Anything else (missing, `NaN`, `0`, a negative or a
886
- * fractional count from a malformed directory entry) falls back, because DSH
887
- * accepts ONLY a positive integer and rejects the entire provider otherwise.
888
- */
889
- function servedContextWindow(info) {
890
- const window = info.contextWindow;
891
- return Number.isInteger(window) && window > 0 ? window : FALLBACK_CONTEXT_WINDOW;
892
- }
893
1042
  function toPiModel(info, baseUrl, providerId, withRate = true) {
894
1043
  return {
895
1044
  id: info.id,
@@ -1121,6 +1270,65 @@ function withContextAdaptation(api, generation) {
1121
1270
  streamSimple: (model, context, options) => api.streamSimple(model, forApi(context), options)
1122
1271
  };
1123
1272
  }
1273
+ /**
1274
+ * The adapter, with the level each model runs at reported upward.
1275
+ *
1276
+ * WHY THIS SUBCLASS EXISTS. The kernel asks the adapter for a model's reasoning
1277
+ * metadata and materialises `reasoning.defaultEffort` into every request that
1278
+ * names no level (`dsh-llm` `resolveCallWithInfo`), and DSH's model picker reads
1279
+ * the same field to say which level is current. This plugin knows the answer —
1280
+ * Trae publishes a per-model default and the user may override it — but the
1281
+ * hand-built provider cannot express a per-model default through pi-ai's `Model`
1282
+ * (its shape has one `reasoning: boolean` for the whole row). Reporting it here,
1283
+ * at the seam the kernel actually consults, is what makes the picker name a
1284
+ * level instead of `Default`. See `default-effort.ts` for the precedence.
1285
+ *
1286
+ * The override is deliberately narrow: it rewrites ONE field of an object the
1287
+ * parent just built, and only when the model genuinely declares that level.
1288
+ */
1289
+ var DefaultEffortAdapter = class extends PiAiAdapter {
1290
+ /**
1291
+ * The level one model should run at; `undefined` means it offers no reasoning.
1292
+ *
1293
+ * Read on every resolution rather than captured once, so a catalog refresh or
1294
+ * a settings change reaches the very next request without a restart.
1295
+ */
1296
+ defaultEffort;
1297
+ constructor(config, defaultEffort = () => void 0) {
1298
+ super(config);
1299
+ this.defaultEffort = defaultEffort;
1300
+ }
1301
+ resolveModel(provider, model, signal) {
1302
+ return super.resolveModel(provider, model, signal).then((info) => this.advertiseDefaultEffort(info));
1303
+ }
1304
+ prepareCall(provider, model, signal) {
1305
+ return super.prepareCall(provider, model, signal).then((call) => ({
1306
+ ...call,
1307
+ model: this.advertiseDefaultEffort(call.model)
1308
+ }));
1309
+ }
1310
+ /**
1311
+ * Write the level into the model metadata.
1312
+ *
1313
+ * It must NEVER contradict the tier list: reporting a level the model does not
1314
+ * offer makes the kernel reject the request outright with
1315
+ * `INVALID_MODEL_REASONING` — far worse than the picker saying `Default`.
1316
+ */
1317
+ advertiseDefaultEffort(info) {
1318
+ if (info.reasoning === void 0) return info;
1319
+ const effort = this.defaultEffort(info.id);
1320
+ if (effort === void 0) return info;
1321
+ if (!info.reasoning.efforts.some((level) => level.id === effort)) return info;
1322
+ if (info.reasoning.defaultEffort === effort) return info;
1323
+ return {
1324
+ ...info,
1325
+ reasoning: {
1326
+ ...info.reasoning,
1327
+ defaultEffort: ReasoningEffortId(effort)
1328
+ }
1329
+ };
1330
+ }
1331
+ };
1124
1332
  function createTraeAdapter(options) {
1125
1333
  const providerId = options.provider ?? "trae";
1126
1334
  const providerName = options.displayName ?? "Trae";
@@ -1162,13 +1370,30 @@ function createTraeAdapter(options) {
1162
1370
  piProvider: provider
1163
1371
  };
1164
1372
  let profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
1373
+ /**
1374
+ * The level one model should run at, resolved from the LIVE catalog.
1375
+ *
1376
+ * The catalog, not the saved `lastCatalog`, because `catalog.current()` is
1377
+ * what the provider serves and what `toPiModel` sized the tier list from —
1378
+ * answering from a different source could name a level that row does not
1379
+ * offer, which the kernel rejects.
1380
+ */
1381
+ const resolveDefaultEffort = (model) => {
1382
+ const info = options.catalog.current().find((entry) => entry.id === model);
1383
+ if (info === void 0 || info.reasoning === void 0) return void 0;
1384
+ return defaultEffortFor({
1385
+ id: info.id,
1386
+ supported: info.reasoning.supported,
1387
+ defaultEffort: info.reasoning.defaultEffort
1388
+ }, options.defaultEfforts?.() ?? {});
1389
+ };
1165
1390
  return {
1166
- adapter: new PiAiAdapter({
1391
+ adapter: new DefaultEffortAdapter({
1167
1392
  profiles: () => profiles,
1168
1393
  auth: INERT_AUTH,
1169
1394
  resolveApiKey: async () => options.secret ?? options.shim.token(),
1170
1395
  ...options.resolveAttachments === void 0 ? {} : { resolveAttachments: options.resolveAttachments }
1171
- }),
1396
+ }, resolveDefaultEffort),
1172
1397
  invalidate() {
1173
1398
  profiles = /* @__PURE__ */ new Map([[providerId, profile]]);
1174
1399
  }
@@ -4994,53 +5219,6 @@ function parseTraeActivityDiscount(value) {
4994
5219
  return parsed.current === void 0 && parsed.member === void 0 && parsed.offPeak === void 0 && parsed.subsidy === void 0 ? void 0 : parsed;
4995
5220
  }
4996
5221
  //#endregion
4997
- //#region src/reasoning.ts
4998
- /**
4999
- * The DSH thinking levels this plugin can carry, in ascending order.
5000
- *
5001
- * `max` is the fifth level Trae's own client advertises — its bundle ships the
5002
- * i18n keys `trae-chat-core.model.reasoning_effort.{low,medium,high,extra_high,max}`
5003
- * and `ai.model.reasoning_effort.{low,medium,high,xhigh,max}`, and publishes the
5004
- * per-model list from a remote config (`AI.agent.model.reasoning_effort_level_by_agent_model_v2`).
5005
- * Leaving it out of this list meant a level the vendor could start serving was
5006
- * filtered away at PARSE time, before anything downstream could decide (issue
5007
- * #60). Listing it does not fabricate the level: `supported` is still built
5008
- * only from what the upstream response actually contains, so a model that does
5009
- * not advertise `max` never offers it.
5010
- *
5011
- * `minimal` is kept for the same reason in reverse — it is a DSH level, so the
5012
- * type must be able to represent it even though no CN model has advertised it.
5013
- */
5014
- const TRAE_REASONING_EFFORTS = [
5015
- "minimal",
5016
- "low",
5017
- "medium",
5018
- "high",
5019
- "xhigh",
5020
- "max"
5021
- ];
5022
- function parseReasoningCapability(value) {
5023
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
5024
- const record = value;
5025
- const supported = (Array.isArray(record["reasoning_effort_options"]) ? record["reasoning_effort_options"] : []).filter((item) => typeof item === "string" && TRAE_REASONING_EFFORTS.includes(item));
5026
- const rawDefault = record["default_reasoning_effort"];
5027
- const defaultEffort = typeof rawDefault === "string" && supported.includes(rawDefault) ? rawDefault : void 0;
5028
- if (supported.length === 0 && defaultEffort === void 0) return void 0;
5029
- return {
5030
- supported,
5031
- ...defaultEffort === void 0 ? {} : { defaultEffort }
5032
- };
5033
- }
5034
- /** Add effort only when the selected model advertises that exact value. */
5035
- function applyReasoningEffort(body, effort, capability) {
5036
- if (effort === void 0) return body;
5037
- if (capability === void 0 || !capability.supported.includes(effort)) throw new Error(`Trae model does not advertise reasoning effort ${effort}`);
5038
- return {
5039
- ...body,
5040
- reasoning_effort: effort
5041
- };
5042
- }
5043
- //#endregion
5044
5222
  //#region src/solo.ts
5045
5223
  /**
5046
5224
  * `llm_utils_chat` functions to union per region, in priority order.
@@ -8646,6 +8824,18 @@ async function traeWebUsage(deps, panel, options = {}) {
8646
8824
  const store = deps.store(panel);
8647
8825
  const enabled = deps.regionEnabled(panel);
8648
8826
  /**
8827
+ * GLOBAL, read once per answer and repeated on every panel: the card polls
8828
+ * one region at a time, so the sidebar switch has to be present on whichever
8829
+ * tab is open. `=== true` for the same reason as every opt-in switch here —
8830
+ * the row claims a seat every bundle shares, so an absent answer (an older
8831
+ * Host that cannot answer) must never read as "the user asked for this".
8832
+ *
8833
+ * The signed-OUT branches below carry it too: the row is a property of the
8834
+ * plugin, not of a signed-in account, and a signed-out card is exactly where
8835
+ * a user may still want the page it opens.
8836
+ */
8837
+ const sidebarPanelEnabled = deps.sidebarPanelEnabled?.() === true;
8838
+ /**
8649
8839
  * Accounts this panel may actually bill.
8650
8840
  *
8651
8841
  * Filtered ONCE, at the source, because this list is spliced into every
@@ -8690,6 +8880,7 @@ async function traeWebUsage(deps, panel, options = {}) {
8690
8880
  status: "signed-out",
8691
8881
  accounts,
8692
8882
  enabled,
8883
+ sidebarPanelEnabled,
8693
8884
  quarantined,
8694
8885
  ...unsupportedRegion === void 0 ? {} : { unsupportedRegion },
8695
8886
  searched: failures.map((failure) => ({
@@ -8711,6 +8902,7 @@ async function traeWebUsage(deps, panel, options = {}) {
8711
8902
  status: "signed-out",
8712
8903
  accounts,
8713
8904
  enabled,
8905
+ sidebarPanelEnabled,
8714
8906
  quarantined,
8715
8907
  ...unsupportedRegion === void 0 ? {} : { unsupportedRegion },
8716
8908
  ...explained ? {} : { message: safeMessage(error) }
@@ -8723,6 +8915,7 @@ async function traeWebUsage(deps, panel, options = {}) {
8723
8915
  ...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
8724
8916
  region: regionOfPanel(panel),
8725
8917
  enabled,
8918
+ sidebarPanelEnabled,
8726
8919
  ...unsupportedRegion === void 0 ? {} : { unsupportedRegion },
8727
8920
  accounts,
8728
8921
  models: deps.displayModels(panel).map((model) => ({
@@ -9041,12 +9234,34 @@ async function traeAccountCredits(deps, panel) {
9041
9234
  if (accounts.length === 0) return [];
9042
9235
  return Promise.all(accounts.map(async (account) => {
9043
9236
  let generalAvailable;
9044
- if (account.selected) try {
9045
- generalAvailable = toCredits(await deps.client(panel).snapshot()).generalAvailable;
9046
- } catch {}
9047
- else if (deps.creditsOfAccount !== void 0) try {
9237
+ /**
9238
+ * EVERY row goes through `creditsOfAccount`, INCLUDING the bound one.
9239
+ *
9240
+ * That is a correctness fix, not a simplification. The bound row used to
9241
+ * take `deps.client(panel).snapshot()` — the shared client, which reads
9242
+ * through `store.resolve()` and therefore whatever account is bound. The
9243
+ * branch existed because the bound account's stored token is usually
9244
+ * EXPIRED, and `creditsOfAccount` used to be a pure lookup that handed a
9245
+ * stale token back as-is. `126faa9` removed that reason: `creditsOfAccount`
9246
+ * now resolves each named account's credential, refreshing it first.
9247
+ *
9248
+ * What was left behind was a way to report the WRONG account's balance. The
9249
+ * shared client answers for the binding, so if the store's binding moved
9250
+ * between the `accounts()` read and the snapshot — or `resolve()` picked a
9251
+ * different account than the `selected` flag names — this row carried the
9252
+ * OTHER account's figure. The visible symptom was two rows showing the same
9253
+ * number (issue #74 follow-up, 2026-11-03): both accounts read 644.29 while
9254
+ * the upstream said 678.54 and 644.00.
9255
+ *
9256
+ * Reading by id is also what makes the rows independent: each request names
9257
+ * its account, so no row can be answered by another row's credential.
9258
+ */
9259
+ if (deps.creditsOfAccount !== void 0) try {
9048
9260
  generalAvailable = (await deps.creditsOfAccount(panel, account.id))?.generalAvailable;
9049
9261
  } catch {}
9262
+ else if (account.selected) try {
9263
+ generalAvailable = toCredits(await deps.client(panel).snapshot()).generalAvailable;
9264
+ } catch {}
9050
9265
  return {
9051
9266
  id: account.id,
9052
9267
  accountName: account.accountName,
@@ -10831,6 +11046,41 @@ function creditPosition(config, region) {
10831
11046
  return regionStateOf(config, region).creditPosition === "stats" ? "stats" : "composer";
10832
11047
  }
10833
11048
  /**
11049
+ * The user's thinking-level choices for one panel (model id -> DSH level).
11050
+ *
11051
+ * Read through `regionStateOf` so the region split, the legacy CN fallback and
11052
+ * the volatile unwrap all apply exactly once, in one place. Values are filtered
11053
+ * to non-empty strings: an empty string is not a level, and passing one on would
11054
+ * make the model report a default the kernel cannot name.
11055
+ *
11056
+ * A saved level is NOT validated against the model's current tier list here —
11057
+ * that belongs where the tiers are known (`default-effort.ts`
11058
+ * `defaultEffortFor`), which is also where a withdrawn tier is dropped instead
11059
+ * of being sent.
11060
+ */
11061
+ function thinkingEffortsOf(config, region) {
11062
+ const raw = regionStateOf(config, region).thinkingEfforts;
11063
+ if (raw === void 0 || raw === null || typeof raw !== "object") return {};
11064
+ const out = {};
11065
+ for (const [id, level] of Object.entries(raw)) if (typeof level === "string" && level !== "") out[id] = level;
11066
+ return out;
11067
+ }
11068
+ /**
11069
+ * Whether the OPTIONAL sidebar row is switched on (`Config.sidebarPanel`).
11070
+ *
11071
+ * `=== true` on purpose, exactly like the sibling workbuddy reader: the row
11072
+ * claims a GLOBAL sidebar seat every installed bundle shares, so an absent,
11073
+ * unreadable or hand-edited value must read as "the user did not ask for this",
11074
+ * never as consent. Only an explicit boolean `true` turns the row on.
11075
+ *
11076
+ * Read through `unwrapVolatile` because the field is volatile on 0.1.7: the
11077
+ * live `{get(): T}` reference would otherwise be a truthy non-boolean and every
11078
+ * config would look switched on.
11079
+ */
11080
+ function sidebarPanelEnabled(config) {
11081
+ return unwrapVolatile(config.sidebarPanel) === true;
11082
+ }
11083
+ /**
10834
11084
  * Mark a schema's field as volatile.
10835
11085
  *
10836
11086
  * `volatile()` exists from schemastery 3.18.3; since 2.3.0 the peer range
@@ -10872,6 +11122,7 @@ const regionStateConfig = z.object({
10872
11122
  imageModelIds: z.array(z.string()).default([]),
10873
11123
  drawImageEnabled: z.boolean().default(false).description("Allow the model to generate images with trae_draw_image on this panel (consumes credits; default off)"),
10874
11124
  contextBudgets: z.dict(z.number().step(1).min(1)).default({}),
11125
+ thinkingEfforts: z.dict(z.string()).default({}).description("每个模型的思考强度(模型 id → 档位)。留空 = 跟随该模型自己的默认档。"),
10875
11126
  creditPosition: z.union([z.const("composer"), z.const("stats")]).default("composer").description("积分读数显示位置:\"composer\" = 输入框工具栏(默认),\"stats\" = 输入框下方(与「轮次 / 步数 / token」统计同一行)。装了别的插件后读数不显示时改成 stats。"),
10876
11127
  pool: z.object({
10877
11128
  enabled: z.boolean().default(false).description("账号池:打开后本面板由池中可用账号轮流计费(关闭时按下面的绑定账号)"),
@@ -10896,6 +11147,7 @@ const Config = z.object({
10896
11147
  ]).default("auto").description("Trae edition hint")),
10897
11148
  accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
10898
11149
  showPointsInMainUi: asVolatile(z.boolean().default(false).description("在输入框工具栏显示积分读数「Trae CN · <数值>」(仅当选中的模型属于本插件时出现,可点击查看账号与积分明细)")),
11150
+ sidebarPanel: asVolatile(z.boolean().default(false).description("在 DSH 侧边栏显示「Trae」面板:点开是这张卡片的整页版。默认关闭——这一行占的是所有插件共用的全局侧边栏座位,而「插件」页本来就能看到同样的内容。点一下即生效,不用保存。")),
10899
11151
  accounts: asVolatile(accountSelectionConfig.description("Per-panel account selections, keyed cn | enterprise | ai")),
10900
11152
  regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-panel model directory and selection, keyed cn | enterprise | ai")),
10901
11153
  lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
@@ -11870,6 +12122,7 @@ function apply(ctx, config) {
11870
12122
  displayModels: (panel) => displayModels(current(), panel),
11871
12123
  enabledModelIds: (panel) => regionStateOf(current(), panel).enabledModelIds ?? [],
11872
12124
  regionEnabled: (panel) => regionEnabled(current(), panel),
12125
+ sidebarPanelEnabled: () => sidebarPanelEnabled(current()),
11873
12126
  discoverModels: (panel, signal) => stacks[panel].discoverModels(signal),
11874
12127
  probeModels: async (panel, ids) => {
11875
12128
  await warmWireMap(panel);
@@ -12097,7 +12350,8 @@ function apply(ctx, config) {
12097
12350
  provider,
12098
12351
  displayName: displayNameOfTraeProvider(provider),
12099
12352
  secret: entry.secret,
12100
- resolveAttachments: () => ctx.get("attachments")
12353
+ resolveAttachments: () => ctx.get("attachments"),
12354
+ defaultEfforts: () => thinkingEffortsOf(current(), panel)
12101
12355
  });
12102
12356
  stack.invalidateAdapter = () => {
12103
12357
  trae.invalidate();
@@ -12117,7 +12371,7 @@ function apply(ctx, config) {
12117
12371
  return next.map((model) => ({
12118
12372
  id: model.id,
12119
12373
  name: traeModelDisplayName(model, withRate),
12120
- ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
12374
+ contextWindow: servedContextWindow(model),
12121
12375
  ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
12122
12376
  inputModalities: traeInputModalities(model)
12123
12377
  }));
@@ -12160,4 +12414,4 @@ function apply(ctx, config) {
12160
12414
  });
12161
12415
  }
12162
12416
  //#endregion
12163
- export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, MAX_PROBE_INPUT_TOKENS, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHANNEL_PRIORITY, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_COMMON_FUNCTIONS, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_OFFICIAL_ROSTER_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, TraeOfficialRosterClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyOfficialRoster, 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, filterTraeModels, generationOf, groupTraeModels, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, isPromotableWireModel, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeToolSchema, normalizeTraeToolSchemas, normalizeTraeVersionCode, officialVerdictOf, orderTraeModels, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeOfficialRoster, 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, scopeTraeModels, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelCallChannel, traeModelDisplayName, traeModelInView, traeModelMatchesQuery, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, wireTargetsOf, withChatCallableFunctions, withContextAdaptation, withTraeRegion };
12417
+ export { Config, FALLBACK_CONTEXT_WINDOW, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, MAX_PROBE_INPUT_TOKENS, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHANNEL_PRIORITY, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_COMMON_FUNCTIONS, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_OFFICIAL_ROSTER_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, TraeOfficialRosterClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyOfficialRoster, 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, filterTraeModels, generationOf, groupTraeModels, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, isPromotableWireModel, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeToolSchema, normalizeTraeToolSchemas, normalizeTraeVersionCode, officialVerdictOf, orderTraeModels, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeOfficialRoster, 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, scopeTraeModels, servedContextWindow, settingsNamespaceOf, sidebarPanelEnabled, thinkingEffortsOf, traeEndpoint, traeInputModalities, traeModelCallChannel, traeModelDisplayName, traeModelInView, traeModelMatchesQuery, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, wireTargetsOf, withChatCallableFunctions, withContextAdaptation, withTraeRegion };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-connect-trae",
3
3
  "displayName": "DSH Connect Trae",
4
4
  "description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
5
- "version": "4.6.11",
5
+ "version": "4.8.0",
6
6
  "author": "dingminhua",
7
7
  "copyright": "Copyright (c) 2026 LaoDing",
8
8
  "license": "MIT",
@@ -34,6 +34,7 @@
34
34
  "lib",
35
35
  "icons",
36
36
  "docs/assets/dsh-connect-trae-usage-card.png",
37
+ "docs/assets/dsh-connect-trae-sidebar-page.png",
37
38
  "scripts/verify-windows.mjs",
38
39
  "screenshots.json",
39
40
  "cordis.patch.yml",