dsh-connect-trae 4.0.1 → 4.2.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.d.ts CHANGED
@@ -3,6 +3,46 @@ import { Context, ProviderStreams } from "@earendil-works/pi-ai";
3
3
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
4
4
  import { Context as Context$1 } from "@deepseek-ai/cordis";
5
5
  import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
6
+ //#region src/promo.d.ts
7
+ /** One price pair, as upstream published it. Rates are credits per Mtoken. */
8
+ interface TraePromoPrice {
9
+ /** Undiscounted rate. */
10
+ beforeRate?: number;
11
+ /** Discounted rate, when upstream stated one for this branch. */
12
+ rate?: number;
13
+ /**
14
+ * Pay ratio in percent, straight from `discount`: 100 = full price, 50 = 五折,
15
+ * 25 = 二五折. NOT a 0–1 multiplier — read literally, the way it is written.
16
+ */
17
+ discountPercent?: number;
18
+ }
19
+ /** One `off_peak` window: ISO weekdays 1(Monday)–7(Sunday), minutes 0–1440. */
20
+ interface TraePromoWindow {
21
+ weekdays: number[];
22
+ /** Inclusive. */
23
+ startMinute: number;
24
+ /** Exclusive; 1440 is upstream's way of writing "end of day". */
25
+ endMinute: number;
26
+ }
27
+ /** The whole `activity_discount` document, kept unflattened on purpose. */
28
+ interface TraeActivityDiscount {
29
+ /** Upstream's machine name for the campaign, e.g. `subsidy_member_discount`. */
30
+ subKey?: string;
31
+ /** Upstream's own "right now" verdict. */
32
+ current?: TraePromoPrice & {
33
+ discountType?: string;
34
+ };
35
+ /** Subscription price — hover material only, see the module note. */
36
+ member?: TraePromoPrice;
37
+ /** Recurring cheap hours, judged locally against each model's own zone. */
38
+ offPeak?: TraePromoPrice & {
39
+ timeZone?: string;
40
+ windows: TraePromoWindow[];
41
+ };
42
+ /** Subsidy branch, when upstream names it separately from `current`. */
43
+ subsidy?: TraePromoPrice;
44
+ }
45
+ //#endregion
6
46
  //#region src/reasoning.d.ts
7
47
  declare const TRAE_REASONING_EFFORTS: readonly ["minimal", "low", "medium", "high", "xhigh"];
8
48
  type TraeReasoningEffort = typeof TRAE_REASONING_EFFORTS[number];
@@ -30,9 +70,34 @@ interface TraeDiscoveredModel {
30
70
  creditMultiplier?: number;
31
71
  reasoningSupported: boolean;
32
72
  reasoning?: TraeDiscoveredReasoning;
73
+ /**
74
+ * The directory group the model was seen under (`data.list[].function`).
75
+ *
76
+ * The Remote `/models` answer is grouped by SOLO function and the group is
77
+ * the authoritative statement of which function the model belongs to — the
78
+ * `config_name` the gateway accepts is isolated per function, so a model in
79
+ * `solo_work_remote` answers 4001 when asked through `solo_work_lite`. The
80
+ * wire join (see `mergeTraeModelSources`) prefers its own
81
+ * `get_detail_param` function and uses this as the fallback when the roster
82
+ * has nothing to say (restricted networks often return an empty
83
+ * `config_info_list` for the chat functions).
84
+ */
85
+ function?: string;
86
+ /**
87
+ * Trae's discount rule for this row (`features.activity_discount`), kept whole
88
+ * so the card can judge it against the clock at paint time (src/promo.ts).
89
+ */
90
+ activityDiscount?: TraeActivityDiscount;
33
91
  }
34
- /** Parse only capabilities explicitly advertised by Trae's remote model API. */
35
- declare function parseTraeRemoteModel(value: unknown): TraeDiscoveredModel | undefined;
92
+ /**
93
+ * Parse only capabilities explicitly advertised by Trae's remote model API.
94
+ *
95
+ * @param groupFunction The `data.list[].function` of the group this raw row
96
+ * was read from; attached verbatim so downstream can replay the model's own
97
+ * function instead of the `llm_utils_chat` default (see
98
+ * {@link TraeDiscoveredModel.function}).
99
+ */
100
+ declare function parseTraeRemoteModel(value: unknown, groupFunction?: string): TraeDiscoveredModel | undefined;
36
101
  //#endregion
37
102
  //#region src/paths.d.ts
38
103
  type TraeEdition = 'cn' | 'sg' | 'solo' | 'solo-sg';
@@ -161,6 +226,16 @@ interface TraeModelInfo {
161
226
  * image to a model that cannot read one burns credits for a silent failure.
162
227
  */
163
228
  multimodal?: boolean;
229
+ /**
230
+ * Trae's discount RULE for this model, exactly as published (see src/promo.ts).
231
+ *
232
+ * Kept unflattened and unjudged: whether a badge is painted depends on the
233
+ * clock, so the card judges it at paint time rather than trusting a verdict
234
+ * frozen into a saved catalog. Absent means the model carries no promotion
235
+ * document — including every `ai` (international) row, whose subscription
236
+ * models publish no `consumption_rate` at all.
237
+ */
238
+ activityDiscount?: TraeActivityDiscount;
164
239
  }
165
240
  /**
166
241
  * Bootstrap catalog: identity only where current Trae metadata has not been
@@ -247,6 +322,15 @@ interface TraeWireModel {
247
322
  * therefore has to replay the directory's own function.
248
323
  */
249
324
  function?: string;
325
+ /**
326
+ * The row's own `activity_discount`, read from `display_contact_config`.
327
+ *
328
+ * The wire roster is the richer of the two directories (8 rows carrying a
329
+ * discount vs the Remote directory's 4), and the same model can name a
330
+ * different campaign on each side, so this value wins over the Remote one
331
+ * during the merge — the precedence `creditMultiplier` already follows.
332
+ */
333
+ activityDiscount?: TraeActivityDiscount;
250
334
  }
251
335
  /**
252
336
  * Merge the two Trae model sources into one authoritative catalog.
@@ -728,6 +812,13 @@ interface TraeWebModel {
728
812
  probe?: TraeModelProbeVerdict;
729
813
  /** Upstream's own words for the verdict, when it gave any. */
730
814
  probeDetail?: string;
815
+ /**
816
+ * Trae's discount rule for this row, straight from the directory that
817
+ * advertised it (see src/promo.ts). The CARD decides whether it is live right
818
+ * now: the rule carries weekday/time windows in an upstream-declared zone, so
819
+ * a verdict baked in here would be stale by the next morning.
820
+ */
821
+ activityDiscount?: TraeActivityDiscount;
731
822
  }
732
823
  /**
733
824
  * One model's availability verdict from the one-click test.
@@ -2697,6 +2788,11 @@ interface TraeSoloModel {
2697
2788
  reasoning?: TraeReasoningCapability;
2698
2789
  /** Effective (post-discount) credit multiplier, as the Trae IDE shows it. */
2699
2790
  creditMultiplier?: number;
2791
+ /**
2792
+ * The row's `activity_discount` rule, read from `display_contact_config`.
2793
+ * Display data only: src/promo.ts decides at paint time whether a badge shows.
2794
+ */
2795
+ activityDiscount?: TraeActivityDiscount;
2700
2796
  /** The directory function that listed this config (replayed when calling it). */
2701
2797
  function?: string;
2702
2798
  }
@@ -2810,6 +2906,13 @@ interface TraeSoloRemoteCatalogOptions {
2810
2906
  credential(): Promise<TraeCredential>;
2811
2907
  fetchImpl?: typeof fetch;
2812
2908
  baseUrl?: string;
2909
+ /**
2910
+ * Diagnostics sink (wired to `ctx.logger.warn` by the plugin). Called only
2911
+ * on the interesting path — when a NON-official host served the directory
2912
+ * or is reused as the remembered first candidate — never on the happy path
2913
+ * of official-host-first.
2914
+ */
2915
+ log?: (message: string, detail?: unknown) => void;
2813
2916
  }
2814
2917
  /**
2815
2918
  * Read-only model catalog client for the SOLO Web API.
@@ -2821,9 +2924,118 @@ declare class TraeSoloRemoteCatalogClient {
2821
2924
  private readonly options;
2822
2925
  private readonly fetchImpl;
2823
2926
  private readonly baseUrl;
2927
+ /**
2928
+ * The last base that answered the directory with a NON-EMPTY catalog in this
2929
+ * process.
2930
+ *
2931
+ * A proxy-blocked network pays the failing official attempt on EVERY refresh
2932
+ * otherwise, so a backup that has served the catalog is tried first on later
2933
+ * refreshes. Putting the official base "second" is NOT enough to re-probe it:
2934
+ * the loop returns on the first success, so a backup that keeps answering
2935
+ * means the official base is never asked again. The official host is
2936
+ * therefore re-probed on a COOLDOWN instead (see {@link OFFICIAL_RETRY_MS}):
2937
+ * while the cooldown runs the backup is used and the official host is
2938
+ * skipped; when it expires the official host is tried FIRST again, so a
2939
+ * healed network takes over within one cooldown window rather than never.
2940
+ */
2941
+ private lastSuccessfulBase;
2942
+ /**
2943
+ * When the official base may be probed again after a transport/5xx failure,
2944
+ * as epoch milliseconds (0 = probe it on the next refresh).
2945
+ */
2946
+ private officialRetryAtMs;
2947
+ /**
2948
+ * How long a failing official host is skipped before it is re-probed.
2949
+ *
2950
+ * Long enough that a blocked network stops paying the failing attempt on
2951
+ * every refresh (the point of the fallback), short enough that a healed
2952
+ * network takes over without a restart. Each probe costs one connect timeout
2953
+ * (30 s) in the worst case, so 5 minutes bounds that to ~1 attempt per 5 min.
2954
+ */
2955
+ private static readonly OFFICIAL_RETRY_MS;
2824
2956
  constructor(options: TraeSoloRemoteCatalogOptions);
2825
2957
  private headers;
2826
2958
  fetchModels(signal?: AbortSignal): Promise<TraeDiscoveredModel[]>;
2959
+ /**
2960
+ * Bases to try for one refresh, in order.
2961
+ *
2962
+ * While the official host is in its post-failure cooldown the backup hosts go
2963
+ * first and the official base is kept LAST as a final chance (so a still-down
2964
+ * backup does not fail the whole refresh while the official host happens to
2965
+ * be reachable). Once the cooldown expires the official base is first again:
2966
+ * that is what lets a healed network take over — the loop returns on the
2967
+ * first success, so simply placing the official base second never re-probes
2968
+ * it while a backup keeps answering.
2969
+ */
2970
+ private candidateBases;
2971
+ /**
2972
+ * Union every returned group into one deduplicated list, attributing each
2973
+ * model to the CHAT function the directory listed it under.
2974
+ *
2975
+ * Each group is a roster the gateway is willing to serve; a model listed by
2976
+ * any of them is a model the IDE can offer, and the wire join downstream (see
2977
+ * mergeTraeModelSources) is what decides whether it is actually callable.
2978
+ * Restricting this to `solo_agent_remote` dropped the nine models issue #19
2979
+ * was opened about.
2980
+ *
2981
+ * FUNCTION ATTRIBUTION (2026-10-20): a group is the authoritative statement
2982
+ * of which SOLO function a model belongs to — the accepted `config_name` is
2983
+ * isolated per function (glm-5.3 answers `solo_work_remote` and 4001s under
2984
+ * `solo_work_lite`). The wire join prefers its own `get_detail_param`
2985
+ * function and falls back to this one when the roster says nothing, which
2986
+ * restricted networks often do (measured: an empty `config_info_list` for
2987
+ * every chat function but `chat_v3`).
2988
+ *
2989
+ * ONLY CHAT-CAPABLE GROUPS ARE ATTRIBUTED. `solo_agent` and `builder_v3` are
2990
+ * IDE/Remote-agent rosters: asking `llm_utils_chat` with either answers
2991
+ * 4011/4001 (measured 2026-10-01 on a live credential, issue #19). A model
2992
+ * that appears ONLY there is still not uncallable, though — the SAME models
2993
+ * answered normally under `solo_work_remote` / `solo_work_lite` /
2994
+ * `solo_agent_remote` (measured the same day, see `TRAE_DIRECTORY_FUNCTIONS`
2995
+ * in solo.ts), and the default function is what has always reached them:
2996
+ * they are exactly the nine models issue #19's fix exposes (`gpt-5.5`,
2997
+ * `gpt-5.6-*`, `gpt-6-astra`, `glm-5.2`, `kimi-k2.7-code`,
2998
+ * `Dola-Seed-2.0-Code`, `deepseek-v4-flash-0731` — docs/ISSUE19_DIAGNOSIS.md).
2999
+ * Stamping `solo_agent` onto such a row makes every call 4011; DROPPING it
3000
+ * undoes issue #19 for exactly the restricted-network users this fallback
3001
+ * exists for. It therefore keeps NO function, the bridge falls back to the
3002
+ * default chat function exactly as before this change, and a refusal (if the
3003
+ * model really is unreachable) comes back as a readable upstream message
3004
+ * instead of a silent absence.
3005
+ *
3006
+ * CHAT GROUPS ARE WALKED IN `TRAE_DIRECTORY_FUNCTIONS` ORDER, not in the
3007
+ * request's `functions=` order. That list is the measured precedence of which
3008
+ * function serves a shared config, and it is the order the wire roster itself
3009
+ * collects in (`solo.ts`) — walking a different order would attribute the
3010
+ * same model differently depending on whether the roster answered, and the
3011
+ * difference is not cosmetic: `solo_agent_remote` sits after the two
3012
+ * `solo_work_*` functions because it is behind the Remote plan gate, so
3013
+ * preferring it can turn a free-tier model into a `1005 requires a paid
3014
+ * plan` refusal.
3015
+ *
3016
+ * A model appears in SEVERAL groups and the groups disagree about its
3017
+ * capacity, so dedupe keeps the MOST CAPABLE row rather than the first one
3018
+ * (issue #23). "First wins" assumed the group order was stable; measured
3019
+ * 2026-10-05 on a live CN credential it is not — five calls returned five
3020
+ * different orders — so the published window became a coin flip:
3021
+ * `Doubao-Seed-2.1-Pro` came back as `116000`/no-Max (`builder_v3`), or
3022
+ * `256000`/no-Max (`solo_work_*`), or `256000`/`1000000` (`chat_v3`,
3023
+ * `solo_agent*`) depending on which group answered first. The Max tier is
3024
+ * the one users enable in the IDE, and `applyContextBudgets` can only raise
3025
+ * a window to `maxContextWindow` — so losing that field silently capped
3026
+ * every configured 1M model at 256K or less, and DSH compressed far earlier
3027
+ * than the chosen budget implied.
3028
+ *
3029
+ * Ordering rule: a row advertising a Max tier beats one that does not, a
3030
+ * larger Max wins over a smaller one, and with no Max on either side the
3031
+ * wider dev window wins. Every row for one id carries the same
3032
+ * `multimodal` / `reasoning` / credit facts (verified the same day), so
3033
+ * choosing on capacity loses no capability. The result no longer depends on
3034
+ * the gateway's group order — and the FUNCTION does not either: the winning
3035
+ * row's capacity may come from a later group, but the separately-tracked
3036
+ * attribution is re-asserted on it.
3037
+ */
3038
+ private collectModels;
2827
3039
  }
2828
3040
  //#endregion
2829
3041
  //#region src/delegating-upstream.d.ts