dsh-llm-adapters 1.2.0 → 1.2.2

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.
@@ -30,12 +30,12 @@ export interface RaccoonAccountRecord {
30
30
  loginAt: number;
31
31
  }
32
32
  /**
33
- * One Trae web login.
33
+ * One Trae login.
34
34
  *
35
- * `accessToken` is the `Cloud-IDE-JWT` the website itself uses. `deviceId` and
36
- * `machineId` are the fingerprints captured from that web session — this
37
- * plugin never reads a desktop install — and are replayed on chat, quota and
38
- * check-in requests.
35
+ * `accessToken` is the `Cloud-IDE-JWT` Trae's own clients use. `deviceId` and
36
+ * `machineId` are replayed on chat, quota and check-in requests. A web login
37
+ * captures them from its browser session; a `client` login copies the desktop
38
+ * client's own values, which is the only source the check-in route accepts.
39
39
  */
40
40
  export interface TraeAccountRecord {
41
41
  accessToken: string;
@@ -45,6 +45,8 @@ export interface TraeAccountRecord {
45
45
  userId?: string;
46
46
  name?: string;
47
47
  loginAt: number;
48
+ /** Where the token came from; absent means the plugin's browser login. */
49
+ source?: 'web' | 'client';
48
50
  /** The `Cloud-IDE-JWT` expiry (about 8 hours); absent when the JWT carries none. */
49
51
  expiresAt?: number;
50
52
  /**
@@ -81,12 +83,19 @@ export interface CodeArtsAccountRecord {
81
83
  d: string;
82
84
  };
83
85
  userName?: string;
86
+ /**
87
+ * The `x-ot-session-id` this account chats under: one stable value instead of
88
+ * one per request, because the gateway counts concurrent sessions by it.
89
+ *
90
+ * A record written before the field existed derives the same value from the
91
+ * refresh token's profile, so an existing sign-in needs no new login.
92
+ */
93
+ otSessionId?: string;
84
94
  /**
85
95
  * Models the account answered `InferHub.4004.200` for.
86
96
  *
87
- * The verdict is worth persisting: asking with `maas_type: benefit` spends
88
- * one of the three chat slots the plan allows per minute, even though the
89
- * call cannot succeed.
97
+ * Worth persisting: the account has no allowance pack for them, so without the
98
+ * verdict every call would pay for a request that cannot succeed.
90
99
  */
91
100
  noBenefit?: string[];
92
101
  loginAt: number;
@@ -16,7 +16,7 @@ import { LoomyProvider } from './loomy.ts';
16
16
  import { RaccoonProvider } from './raccoon.ts';
17
17
  import { TraeProvider } from './trae.ts';
18
18
  import type { AuthStatus } from './auth.ts';
19
- import type { CheckinStatus, ProviderStatus, QuotaSnapshot } from './types.ts';
19
+ import type { CheckinStatus, ClientLoginStatus, ProviderStatus, QuotaSnapshot } from './types.ts';
20
20
  import type { ReadImage } from './wire.ts';
21
21
  export interface AdapterOptions {
22
22
  brand: BrandId;
@@ -33,7 +33,7 @@ export interface AdapterOptions {
33
33
  *
34
34
  * Declared as an interface rather than a union so a new provider only has to
35
35
  * implement the behaviour, not edit the adapter's type list. The optional
36
- * check-in methods exist only on Trae.
36
+ * client-login and check-in methods exist only on Trae.
37
37
  */
38
38
  export interface ProviderClient {
39
39
  status(): Promise<AuthStatus>;
@@ -51,6 +51,10 @@ export interface ProviderClient {
51
51
  invalidateModels(): void;
52
52
  invalidateQuota(): void;
53
53
  close(): void;
54
+ /** Trae only: the local desktop client's current sign-in state. */
55
+ localClientLogin?(): Promise<ClientLoginStatus>;
56
+ /** Trae only: copy that state into the plugin's own account store. */
57
+ importClientLogin?(): Promise<void>;
54
58
  checkinStatus?(): Promise<CheckinStatus>;
55
59
  claimCheckin?(): Promise<CheckinStatus>;
56
60
  }
@@ -85,6 +89,8 @@ export declare abstract class LocalAdapter extends LlmAdapter {
85
89
  cancelBrowserLogin(): Promise<void>;
86
90
  /** Claim today's reward; providers without one reject it. */
87
91
  claimCheckin(): Promise<CheckinStatus>;
92
+ /** Import a local client login and return the refreshed account reading. */
93
+ importClientLogin(): Promise<ProviderStatus>;
88
94
  invalidateCatalog(): void;
89
95
  invalidateUsage(): void;
90
96
  /** Login state plus quota, shaped for the settings page. */
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Credential state for every provider.
3
3
  *
4
- * Sign-in happens in the provider's own web page, so credentials exist in
5
- * exactly one place: the plugin's own account store. No desktop client file is
6
- * discovered, read, or written, and no token leaves this module in errors or
7
- * display fields.
4
+ * Sign-in happens in the provider's own web page. Trae additionally offers an
5
+ * explicit, read-only import of the local desktop client's login state; nothing
6
+ * is ever written back to that client. Either way credentials end up in exactly
7
+ * one place, the plugin's own account store, and no token leaves this module in
8
+ * errors or display fields.
8
9
  *
9
10
  * @module dsh-llm-adapters/auth
10
11
  */
@@ -26,6 +27,8 @@ export interface AuthStatus {
26
27
  possiblyExpired?: boolean;
27
28
  account?: LocalAccount;
28
29
  error?: string;
30
+ /** Trae only: where the token came from. */
31
+ loginSource?: 'web' | 'client';
29
32
  }
30
33
  export declare class LoomyAuth {
31
34
  private readonly store;
@@ -41,6 +44,13 @@ export interface JwtFacts {
41
44
  phone?: string;
42
45
  ownerType?: string;
43
46
  sessionId?: string;
47
+ /**
48
+ * The `sub` claim, which is what an account is called on the Raccoon gateway.
49
+ *
50
+ * Unlike the token itself it survives a re-sign, so it is the seed a stable
51
+ * per-account session id is derived from.
52
+ */
53
+ subject?: string;
44
54
  }
45
55
  /** Read non-secret JWT claims; malformed tokens simply yield no facts. */
46
56
  export declare function decodeJwtFacts(token: string): JwtFacts;
@@ -58,12 +68,13 @@ export declare class RaccoonAuth {
58
68
  logout(): Promise<void>;
59
69
  }
60
70
  /**
61
- * Trae's web login, one per region.
71
+ * Trae's login, one per region.
62
72
  *
63
- * The `Cloud-IDE-JWT` the website issues lives about eight hours, but the
64
- * website session behind it lives about fourteen days. The cookies captured at
65
- * sign-in are replayed against `GetUserToken` to renew the short token silently;
66
- * only when the session itself dies does the user have to sign in again.
73
+ * A web login stores the `Cloud-IDE-JWT` from the website plus the cookies
74
+ * behind it, so the short token can be renewed silently until the website
75
+ * session dies. A client login stores the token the desktop client already
76
+ * uses; that token has no website session, and the client itself refreshes it.
77
+ * Only the client source carries the device id the check-in route accepts.
67
78
  */
68
79
  export declare class TraeAuth {
69
80
  private readonly store;
@@ -77,8 +88,10 @@ export declare class TraeAuth {
77
88
  /**
78
89
  * The bearer token, or an actionable error when there is none.
79
90
  *
80
- * A token near its expiry is renewed first; `force` means the gateway already
81
- * rejected it, so the renewal is attempted even if the clock disagrees.
91
+ * A web token near its expiry is renewed first; `force` means the gateway
92
+ * already rejected it, so the renewal is attempted even if the clock
93
+ * disagrees. A client token is returned until its own expiry and then asks
94
+ * for a fresh import, because only the client can refresh it.
82
95
  */
83
96
  accessToken(force?: boolean): Promise<string>;
84
97
  /** Renew the eight-hour JWT with the stored website cookies. */
@@ -28,6 +28,17 @@ export interface CodeArtsCredential {
28
28
  dpopPrivateJwk?: DpopPrivateJwk;
29
29
  /** Display only, from the refresh token's profile; never used for signing. */
30
30
  userName?: string;
31
+ /**
32
+ * The id the gateway counts concurrent sessions against (`x-ot-session-id`).
33
+ *
34
+ * Measured on a real account: the three-session cap is keyed by this header,
35
+ * not by `Chat-Id` / `Session-Id`. A per-request value makes every call a new
36
+ * session, so the account spends its own budget on its own history and the
37
+ * fourth call of the minute comes back `TM.00001041`. Derived from the account
38
+ * identity instead of stored at random: the same value in every process, and
39
+ * one that survives a token renewal.
40
+ */
41
+ otSessionId?: string;
31
42
  }
32
43
  /** Who the credential belongs to, as the portal encoded it in the token. */
33
44
  export interface CodeArtsUserProfile {
@@ -75,6 +86,14 @@ export declare function readJwtExpiry(token: string): number | undefined;
75
86
  * request; without it the field stays empty and the page says so.
76
87
  */
77
88
  export declare function readUserProfile(token: string | undefined): CodeArtsUserProfile | undefined;
89
+ /**
90
+ * The session id this account should chat under, derived from who the account is.
91
+ *
92
+ * 32 lowercase hex, the shape the IDE's own kernel sends. Nothing but stability
93
+ * matters here — the gateway uses the value as a key, not as an identity — so it
94
+ * is hashed from the principal id rather than kept in a file.
95
+ */
96
+ export declare function otSessionIdOf(profile: CodeArtsUserProfile | undefined): string | undefined;
78
97
  /** The portal URL the browser opens; the callback is on loopback. */
79
98
  export declare function buildAuthorizeUrl(port: number, pkce: PkcePair, ticketId: string): string;
80
99
  /** Where the browser lands after a successful or failed exchange. */
@@ -19,7 +19,6 @@ import type { AccountStore } from './account-store.ts';
19
19
  import { CodeArtsAuth } from './auth.ts';
20
20
  import type { AuthStatus } from './auth.ts';
21
21
  import type { ProviderModel } from './catalog.ts';
22
- import { CodeArtsSessionWindow } from './codearts-window.ts';
23
22
  import type { LoginStatus } from './login-page.ts';
24
23
  import type { OpenRouterLookup } from './openrouter.ts';
25
24
  import type { CheckinStatus, QuotaPack, QuotaSnapshot } from './types.ts';
@@ -72,8 +71,6 @@ export interface CodeArtsProviderOptions {
72
71
  loginTimeoutMs?: number;
73
72
  /** Injectable OpenRouter lookup for tests; defaults to the live catalog. */
74
73
  openRouter?: OpenRouterLookup;
75
- /** Chat budget; defaults to the process-wide window (survives hot reloads). */
76
- window?: CodeArtsSessionWindow;
77
74
  }
78
75
  /** One roster entry before it becomes a model row. */
79
76
  export interface CodeArtsModelRow {
@@ -110,7 +107,6 @@ export declare class CodeArtsProvider {
110
107
  /** Models that answered `InferHub.4004.200`: no allowance pack to bill. */
111
108
  private readonly noBenefit;
112
109
  private noBenefitLoaded;
113
- private readonly window;
114
110
  private readonly openRouter;
115
111
  private readonly sessionId;
116
112
  /**
@@ -159,7 +155,7 @@ export declare class CodeArtsProvider {
159
155
  * so a conversation pays for the description once.
160
156
  */
161
157
  private relayImages;
162
- /** Ask the vision model to describe one image, within the same chat budget. */
158
+ /** Ask the vision model to describe one image; another turn on the same session. */
163
159
  private describeImage;
164
160
  stream(options: GenerateOptions, model: string, readImage: ReadImage | undefined, onInvalidate: () => void): AsyncGenerator<StreamChunk>;
165
161
  private send;
@@ -45,8 +45,6 @@ export interface Config {
45
45
  catalogFetchTimeoutSeconds?: number;
46
46
  /** Minutes between automatic OpenRouter syncs; 0 disables the schedule. */
47
47
  openRouterSyncMinutes?: number;
48
- /** Minutes between Raccoon daily-point checks; 0 disables the monitor. */
49
- raccoonCheckinMinutes?: number;
50
48
  }
51
49
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
52
50
  loomyEnabled: z<boolean, boolean, "volatile-defined">;
@@ -82,7 +80,6 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
82
80
  modelCacheTtlSeconds: z<number, number, "volatile-defined">;
83
81
  catalogFetchTimeoutSeconds: z<number, number, "volatile-defined">;
84
82
  openRouterSyncMinutes: z<number, number, "volatile-defined">;
85
- raccoonCheckinMinutes: z<number, number, "volatile-defined">;
86
83
  }>>, Schemastery.ObjectT<NoInfer<{
87
84
  loomyEnabled: z<boolean, boolean, "volatile-defined">;
88
85
  raccoonEnabled: z<boolean, boolean, "volatile-defined">;
@@ -117,7 +114,6 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
117
114
  modelCacheTtlSeconds: z<number, number, "volatile-defined">;
118
115
  catalogFetchTimeoutSeconds: z<number, number, "volatile-defined">;
119
116
  openRouterSyncMinutes: z<number, number, "volatile-defined">;
120
- raccoonCheckinMinutes: z<number, number, "volatile-defined">;
121
117
  }>>, "plain">;
122
118
  export interface ResolvedConfig {
123
119
  loomyEnabled: boolean;
@@ -153,7 +149,6 @@ export interface ResolvedConfig {
153
149
  modelCacheTtlMs: number;
154
150
  catalogFetchTimeoutMs: number;
155
151
  openRouterSyncMinutes: number;
156
- raccoonCheckinMinutes: number;
157
152
  }
158
153
  export type ParsedConfig = {
159
154
  readonly [K in keyof Config]?: unknown;
@@ -59,27 +59,28 @@ export { OpenRouterCatalog, buildOpenRouterIndex, parseOpenRouterModels, readOpe
59
59
  export type { OpenRouterIndex, OpenRouterLookup, OpenRouterModel, OpenRouterStatus, OpenRouterSyncResult, } from './openrouter.ts';
60
60
  export { LoomyProvider, LOOMY_BASE, LOOMY_VERSION, loomyGatewaySessionValid } from './loomy.ts';
61
61
  export { RaccoonProvider, RACCOON_BASE, RACCOON_CLIENT_VERSION, raccoonAccessTokenValid, raccoonRefreshCandidate, } from './raccoon.ts';
62
- export { TraeProvider, TRAE_CHAT_FUNCTIONS, TRAE_DIRECTORY_FUNCTIONS, mergeTraeModels, normalizeEffort, openTraeStream, parseTraeGlobalUsage, parseTraeRemoteModels, parseTraeUsage, parseTraeWireModels, traeMessages, traeTools, } from './trae.ts';
62
+ export { TraeProvider, TRAE_CHAT_FUNCTIONS, TRAE_CHECKIN_REQ_SOURCE, TRAE_DIRECTORY_FUNCTIONS, mergeTraeModels, normalizeEffort, openTraeStream, parseTraeCheckin, parseTraeGlobalUsage, parseTraeRemoteModels, parseTraeUsage, parseTraeWireModels, traeMessages, traeTools, } from './trae.ts';
63
63
  export type { TraeCatalog, TraeModelMeta, TraeProviderOptions, TraeRemoteModel, TraeWireModel, } from './trae.ts';
64
64
  export { TRAE_APP_ID, TRAE_REGION_PROFILES, TRAE_SOLO_CHAT_PATH, TRAE_WEB_USER_AGENT } from './trae-endpoints.ts';
65
65
  export type { TraeRegionProfile } from './trae-endpoints.ts';
66
+ export { parseTraeClientStorage, readTraeClientLogin, traeClientDataDirs } from './trae-client.ts';
67
+ export type { TraeClientDetection, TraeClientLoginState, TraeClientReadOptions } from './trae-client.ts';
66
68
  export { TraeWebLogin, machineIdOf } from './trae-login.ts';
67
69
  export type { TraeLoginAccount, TraeWebLoginOptions } from './trae-login.ts';
68
70
  export { CodeArtsProvider, CODEARTS_DEFAULT_EFFORT, CODEARTS_EFFORTS, clampMaxTokens, codeartsEfforts, normalizeCodeArtsModelId, parseCodeArtsPackage, parseCodeArtsRoster, pickActiveCredit, priceFactorOfLabel, } from './codearts.ts';
69
71
  export type { CodeArtsActivity, CodeArtsModelRow, CodeArtsPackageInfo, CodeArtsProviderOptions, } from './codearts.ts';
70
72
  export { CODEARTS_BENEFIT_FALLBACK, CODEARTS_CHAT_URL, CODEARTS_CONTEXT_WINDOWS, CODEARTS_EFFORT_LEVELS, CODEARTS_FALLBACK_CONTEXT_WINDOW, CODEARTS_MAX_OUTPUT_TOKENS, CODEARTS_SNAP_BASE, CODEARTS_VISION_MODELS, codeartsContextWindow, } from './codearts-endpoints.ts';
71
73
  export { headersForFetch, signRequestHuawei } from './codearts-sign.ts';
72
- export { RefreshTokenExpiredError, buildAuthorizeUrl, exchangeAuthorizationCode, exchangeRefreshToken, generateDpopKeyPair, generatePkcePair, readJwtExpiry, signDpopJws, } from './codearts-oauth.ts';
74
+ export { RefreshTokenExpiredError, buildAuthorizeUrl, exchangeAuthorizationCode, exchangeRefreshToken, generateDpopKeyPair, generatePkcePair, otSessionIdOf, readJwtExpiry, signDpopJws, } from './codearts-oauth.ts';
73
75
  export type { CodeArtsCredential, DpopKeyPair, DpopPrivateJwk, PkcePair } from './codearts-oauth.ts';
74
76
  export { CodeArtsLogin } from './codearts-login.ts';
75
77
  export type { CodeArtsLoginAccount, CodeArtsLoginOptions } from './codearts-login.ts';
76
- export { CODEARTS_SESSION_LIMIT, CODEARTS_SESSION_WINDOW_MS, CodeArtsSessionWindow, sharedSessionWindow, } from './codearts-window.ts';
77
78
  export { ProviderHttpError, buildMessages, buildTools, consumeOpenAiStream, openAiStream, sseEvents } from './wire.ts';
78
79
  export type { WireMessage } from './wire.ts';
79
80
  export { API_PREFIX, buildRows } from './api.ts';
80
81
  export { liveConfig, resolveConfig } from './config.ts';
81
82
  export type { Config as ConfigShape, LiveConfig, ParsedConfig, ResolvedConfig } from './config.ts';
82
- export type { CheckinStatus, ProviderStatus, QuotaPack, QuotaSnapshot } from './types.ts';
83
+ export type { CheckinStatus, ClientLoginStatus, ProviderStatus, QuotaPack, QuotaSnapshot } from './types.ts';
83
84
  export declare const name = "llm-adapters";
84
85
  export declare const inject: string[];
85
86
  export declare const Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
@@ -116,7 +117,6 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
116
117
  modelCacheTtlSeconds: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
117
118
  catalogFetchTimeoutSeconds: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
118
119
  openRouterSyncMinutes: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
119
- raccoonCheckinMinutes: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
120
120
  }>>, Schemastery.ObjectT<NoInfer<{
121
121
  loomyEnabled: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
122
122
  raccoonEnabled: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
@@ -151,6 +151,5 @@ export declare const Config: import("@deepseek-ai/schemastery").default<Schemast
151
151
  modelCacheTtlSeconds: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
152
152
  catalogFetchTimeoutSeconds: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
153
153
  openRouterSyncMinutes: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
154
- raccoonCheckinMinutes: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
155
154
  }>>, "plain">;
156
155
  export declare function apply(ctx: Context, config: ParsedConfig): void;
@@ -15,6 +15,7 @@ import { ModelCatalog } from './catalog.ts';
15
15
  import type { ProviderModel } from './catalog.ts';
16
16
  import type { OpenRouterLookup } from './openrouter.ts';
17
17
  import type { CheckinStatus, QuotaSnapshot } from './types.ts';
18
+ import { ProviderHttpError } from './wire.ts';
18
19
  import type { ReadImage } from './wire.ts';
19
20
  export declare const RACCOON_BASE = "https://xiaohuanxiong.com/api/web";
20
21
  export declare const RACCOON_CLIENT_VERSION = "v1.0.35";
@@ -30,7 +31,68 @@ export interface RaccoonProviderOptions {
30
31
  fetchImpl?: typeof fetch;
31
32
  /** Injectable OpenRouter lookup for tests; defaults to the live catalog. */
32
33
  openRouter?: OpenRouterLookup;
34
+ /** Injectable session-id bookkeeping for tests; defaults to the real one. */
35
+ sessionIds?: RaccoonSessionIds;
33
36
  }
37
+ /**
38
+ * The upstream code for the account's token-throughput wall.
39
+ *
40
+ * It reads like a request-rate complaint (`您的模型请求频率超限`) and is not
41
+ * one: measured on a live account, ten large requests in ten seconds hit it
42
+ * while fifty small ones did not, and the window refills by throughput rather
43
+ * than by request count. The gateway sends no `Retry-After` and no
44
+ * `x-ratelimit-*` header, so nothing here can compute a wait either.
45
+ */
46
+ export declare const RACCOON_RATE_LIMIT_CODE = "user_model_rate_limited";
47
+ /**
48
+ * Whether a rejection is the token wall rather than a generic failure.
49
+ *
50
+ * Matched on the status, the code and the wording, because the same condition
51
+ * arrives as an HTTP 429 and as an error frame inside a 200 stream.
52
+ */
53
+ export declare function isRaccoonRateLimited(status: number | undefined, text: string): boolean;
54
+ /**
55
+ * The wall, as an error the caller can tell apart from a generic 429.
56
+ *
57
+ * A subclass rather than a flag: the in-band path and the HTTP path would
58
+ * otherwise both be caught by the same handler, and the second pass would wrap
59
+ * the message the first pass had already rewritten.
60
+ */
61
+ export declare class RaccoonRateLimitedError extends ProviderHttpError {
62
+ constructor(message: string);
63
+ }
64
+ /**
65
+ * One conversation's session id, stable across the turns of that conversation.
66
+ *
67
+ * The official client never opens a session per request — measured on its own
68
+ * logs, 125 calls over 26 minutes all carried one `sess-0-53927d74` — while an
69
+ * implementation that mints a fresh id per request tells the gateway that one
70
+ * account opened hundreds of sessions within minutes.
71
+ *
72
+ * The id is a pure function of (account, model) rather than a random value, so a
73
+ * restarted or hot-reloaded process continues the same conversation instead of
74
+ * presenting a new session, and two models stay two conversations. The principal
75
+ * itself never leaves the process — only a truncated digest of it does.
76
+ */
77
+ export declare class RaccoonSessionIds {
78
+ private readonly principal;
79
+ /**
80
+ * @param principal - a value that is stable for one account and identifies it
81
+ * without being the token itself (the JWT's `sub`).
82
+ */
83
+ constructor(principal: () => Promise<string | undefined>);
84
+ for(model: string): Promise<string | undefined>;
85
+ }
86
+ /**
87
+ * `sess-<sequence>-<8 hex>`, the shape the official client sends.
88
+ *
89
+ * The sequence is the official client's process-local counter and is pinned to 0
90
+ * here on purpose: an incrementing value would make one conversation look like a
91
+ * different session after a restart, which is the one thing this id exists to
92
+ * prevent. Identity lives in the digest, which is truncated on purpose — the id
93
+ * must separate accounts without naming them on the wire.
94
+ */
95
+ export declare function raccoonSessionId(principal: string, model: string): string;
34
96
  /** Whether the gateway accepts a token harvested from the browser. */
35
97
  export declare function raccoonAccessTokenValid(token: string, fetchImpl?: typeof fetch): Promise<boolean>;
36
98
  /**
@@ -43,9 +105,9 @@ export declare class RaccoonProvider {
43
105
  readonly catalog: ModelCatalog;
44
106
  private quotaCache;
45
107
  private quotaInflight;
46
- private checkinInflight;
47
108
  private loginFlow;
48
109
  private readonly openRouter;
110
+ private readonly sessionIds;
49
111
  constructor(options: RaccoonProviderOptions);
50
112
  status(): Promise<import("./auth.ts").AuthStatus>;
51
113
  signedIn(): Promise<boolean>;
@@ -58,6 +120,14 @@ export declare class RaccoonProvider {
58
120
  private login;
59
121
  logoutAccount(): Promise<void>;
60
122
  private headers;
123
+ /**
124
+ * The chat headers, including the session id the gateway counts sessions by.
125
+ *
126
+ * Only chat carries it: the catalogue, quota and check-in routes are plain
127
+ * business calls the official client sends outside any session, so leaving
128
+ * them alone keeps this change to the one route it was measured on.
129
+ */
130
+ private chatHeaders;
61
131
  /** Run one JSON call; a rejected token means this login needs a new scan. */
62
132
  private authorizedJson;
63
133
  private fetchModels;
@@ -76,12 +146,15 @@ export declare class RaccoonProvider {
76
146
  * `point_grant_popups` on `setting_info`; the bills list is the durable proof.
77
147
  */
78
148
  private dailyGrant;
79
- checkinStatus(): Promise<CheckinStatus>;
80
- claimCheckin(): Promise<CheckinStatus>;
81
149
  /**
82
- * One automatic check-in pass: report today's grant, or ask the website path
83
- * to issue it. Shared so overlapping timers cannot spend two call slots.
150
+ * Read today's grant. **Read-only on purpose.**
151
+ *
152
+ * The gateway has no claim endpoint: points are issued on the day's first
153
+ * website visit. Replaying that visit (`setting_info`) can trigger the grant
154
+ * from here, but doing so takes a state-changing action on the account without
155
+ * the user asking for it in that moment, so the plugin only reports. Claiming
156
+ * is the website's job, and the card links there.
84
157
  */
85
- ensureDailyCheckin(): Promise<CheckinStatus>;
158
+ checkinStatus(): Promise<CheckinStatus>;
86
159
  stream(options: GenerateOptions, model: string, readImage: ReadImage | undefined, onUnauthorized?: () => void): AsyncGenerator<StreamChunk>;
87
160
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The local Trae desktop client's sign-in state.
3
+ *
4
+ * Trae's check-in route insists on the device id its own client registered, so
5
+ * the website login cannot use it. When the client is installed and already
6
+ * signed in, the plugin can reuse that state instead:
7
+ *
8
+ * - the `Cloud-IDE-JWT` stored under `iCubeAuthInfo://icube.cloudide`;
9
+ * - the 16-digit device id encoded in the `iCubeAuthInfo://icube-dc:<id>` key;
10
+ * - `telemetry.machineId`, the value the client sends as `x-machine-id`.
11
+ *
12
+ * The stored value uses the client's own AES-CBC envelope: six magic bytes, a
13
+ * 32-byte key material, then the ciphertext. The AES key and IV are derived
14
+ * from that key material with SHA-512 and a fixed salt, and the plaintext is a
15
+ * SHA-512 digest followed by the JSON document. Nothing here writes to the
16
+ * client, and the token is never returned by the settings API.
17
+ *
18
+ * @module dsh-llm-adapters/trae-client
19
+ */
20
+ import type { TraeRegion } from './brand.ts';
21
+ import type { ClientLoginStatus } from './types.ts';
22
+ /** What the provider stores after importing a client login. */
23
+ export interface TraeClientLoginState {
24
+ region: TraeRegion;
25
+ token: string;
26
+ deviceId: string;
27
+ machineId: string;
28
+ userId?: string;
29
+ name?: string;
30
+ expiresAt?: number;
31
+ }
32
+ /** The settings-facing half (no token) plus the internal state when usable. */
33
+ export interface TraeClientDetection {
34
+ status: ClientLoginStatus;
35
+ state?: TraeClientLoginState;
36
+ }
37
+ export interface TraeClientReadOptions {
38
+ platform?: NodeJS.Platform;
39
+ env?: NodeJS.ProcessEnv;
40
+ home?: string;
41
+ }
42
+ /**
43
+ * The client's data directory, most specific first.
44
+ *
45
+ * `DSH_TRAE_CLIENT_DIR` is a portable/test override; the region-specific
46
+ * variables let one machine keep separate CN and international installs.
47
+ */
48
+ export declare function traeClientDataDirs(region: TraeRegion, options?: TraeClientReadOptions): string[];
49
+ /** Parse one `storage.json` document; exported for tests and diagnostics. */
50
+ export declare function parseTraeClientStorage(region: TraeRegion, storage: Record<string, unknown>): TraeClientDetection;
51
+ /** Read the currently selected region's client state from this machine. */
52
+ export declare function readTraeClientLogin(region: TraeRegion, options?: TraeClientReadOptions): TraeClientDetection;
@@ -33,6 +33,8 @@ export interface TraeRegionProfile {
33
33
  * the dollar allowance instead.
34
34
  */
35
35
  usageV2: boolean;
36
+ /** Daily check-in exists only on the mainland deployment. */
37
+ checkin: boolean;
36
38
  }
37
39
  export declare const TRAE_REGION_PROFILES: Record<TraeRegion, TraeRegionProfile>;
38
40
  /** The app id Trae's own clients send; not account-specific. */
@@ -1,15 +1,17 @@
1
1
  /**
2
2
  * Trae sign-in through the provider's own website.
3
3
  *
4
- * The plugin does not read a desktop install: it opens a dedicated browser
4
+ * This flow does not read a desktop install: it opens a dedicated browser
5
5
  * profile, lets the user sign in on trae.cn / trae.ai, and then takes the
6
6
  * session the website itself created. Two things come out of that session:
7
7
  *
8
8
  * - the `Cloud-IDE-JWT` token, fetched with the page's own cookies through
9
9
  * `/cloudide/api/v3/common/GetUserToken` — the same call the site makes;
10
10
  * - the device fingerprint (`__msuuid__` from the page, or one generated in
11
- * the page), which Trae's check-in route requires and which therefore must
12
- * belong to this web session rather than to a machine install.
11
+ * the page), stored with the web login.
12
+ *
13
+ * The check-in route does not accept this web fingerprint, so check-in is
14
+ * enabled only for the separate client-login import in `trae-client.ts`.
13
15
  *
14
16
  * Cookies are captured over the DevTools protocol and kept in the plugin's own
15
17
  * account store so the short-lived JWT can be renewed later; the dedicated
@@ -1,8 +1,10 @@
1
1
  /**
2
- * Trae provider: model directory, SOLO chat and quota.
2
+ * Trae provider: model directory, SOLO chat, quota and client check-in.
3
3
  *
4
4
  * Trae's website login yields the same `Cloud-IDE-JWT` its own clients use, so
5
- * the plugin talks to the same gateways they do. Two catalog sources are merged:
5
+ * the plugin talks to the same gateways they do. A second sign-in method copies
6
+ * that token from the local desktop client; only that method carries the device
7
+ * id the daily check-in route accepts. Two catalog sources are merged:
6
8
  *
7
9
  * - the Remote directory (`/api/remote/v1/models`) is the model skeleton —
8
10
  * display name, context window, multiplier, reasoning options, multimodal;
@@ -29,7 +31,8 @@ import { ModelCatalog } from './catalog.ts';
29
31
  import type { ProviderModel } from './catalog.ts';
30
32
  import type { LoginStatus } from './login-page.ts';
31
33
  import type { OpenRouterIndex, OpenRouterLookup } from './openrouter.ts';
32
- import type { QuotaSnapshot } from './types.ts';
34
+ import type { TraeClientDetection } from './trae-client.ts';
35
+ import type { CheckinStatus, ClientLoginStatus, QuotaSnapshot } from './types.ts';
33
36
  import type { ReadImage, WireMessage } from './wire.ts';
34
37
  /** Chat functions, in the order Trae itself prefers them. */
35
38
  export declare const TRAE_CHAT_FUNCTIONS: readonly string[];
@@ -103,6 +106,8 @@ export interface TraeProviderOptions {
103
106
  logger?: (message: string) => void;
104
107
  /** The shared OpenRouter snapshot; a private one is created when omitted. */
105
108
  openRouter?: OpenRouterLookup;
109
+ /** Overridable in tests; defaults to reading this machine's Trae client. */
110
+ clientLogin?: () => TraeClientDetection;
106
111
  }
107
112
  /**
108
113
  * The persistent browser profile used for one region.
@@ -154,6 +159,12 @@ export declare function parseTraeUsage(payload: unknown): QuotaSnapshot;
154
159
  * dollars and must not be printed as credits.
155
160
  */
156
161
  export declare function parseTraeGlobalUsage(payload: unknown): QuotaSnapshot;
162
+ /**
163
+ * The check-in body's `req_source`: 1 is the IDE, 2 is Trae Work. The desktop
164
+ * client sends 1; the claim route rejects the plugin's former empty body.
165
+ */
166
+ export declare const TRAE_CHECKIN_REQ_SOURCE = 1;
167
+ export declare function parseTraeCheckin(payload: unknown): CheckinStatus;
157
168
  export declare class TraeProvider {
158
169
  private readonly options;
159
170
  private catalogInstance;
@@ -185,6 +196,7 @@ export declare class TraeProvider {
185
196
  possiblyExpired?: boolean;
186
197
  account?: import("./auth.ts").LocalAccount;
187
198
  error?: string;
199
+ loginSource?: "web" | "client";
188
200
  }>;
189
201
  signedIn(): Promise<boolean>;
190
202
  models(fresh?: boolean): Promise<readonly ProviderModel[]>;
@@ -199,6 +211,20 @@ export declare class TraeProvider {
199
211
  private fetchWire;
200
212
  private fetchCatalog;
201
213
  quota(fresh?: boolean): Promise<QuotaSnapshot>;
214
+ /**
215
+ * The second sign-in method: the desktop client's current state.
216
+ *
217
+ * Read-only; the settings page only receives the non-secret status, never
218
+ * the token.
219
+ */
220
+ localClientLogin(): Promise<ClientLoginStatus>;
221
+ private readClientLogin;
222
+ /** Import the desktop client's token, device id and machine id. */
223
+ importClientLogin(): Promise<void>;
224
+ private checkinContext;
225
+ checkinStatus(): Promise<CheckinStatus>;
226
+ /** Claim today's reward. The only state-changing call in this provider. */
227
+ claimCheckin(): Promise<CheckinStatus>;
202
228
  stream(options: GenerateOptions, model: string, readImage: ReadImage | undefined, onInvalidate: () => void): AsyncGenerator<StreamChunk>;
203
229
  startBrowserLogin(region?: TraeRegion): Promise<LoginStatus>;
204
230
  browserLoginStatus(): LoginStatus;
@@ -53,6 +53,22 @@ export interface CheckinStatus {
53
53
  /** A page the user can open when the provider has no claimable API. */
54
54
  url?: string;
55
55
  }
56
+ /**
57
+ * The local Trae client's state, as shown on the settings page.
58
+ *
59
+ * Never carries the token: only whether the client was found, whether a usable
60
+ * login was read, and the non-secret account fields.
61
+ */
62
+ export interface ClientLoginStatus {
63
+ /** A Trae client data directory exists on this machine. */
64
+ available: boolean;
65
+ /** The stored login is present, unexpired and usable. */
66
+ signedIn: boolean;
67
+ name?: string;
68
+ userId?: string;
69
+ expiresAt?: number;
70
+ error?: string;
71
+ }
56
72
  export interface ProviderStatus extends AuthStatus {
57
73
  brand: BrandId;
58
74
  /** Trae only: the region this reading belongs to. */
@@ -60,7 +76,9 @@ export interface ProviderStatus extends AuthStatus {
60
76
  /** Present only after a successful quota read. */
61
77
  quota?: QuotaSnapshot;
62
78
  quotaError?: string;
63
- /** Trae CN only. */
79
+ /** Trae only: the second sign-in method, when the desktop client exists. */
80
+ clientLogin?: ClientLoginStatus;
81
+ /** Trae CN only, and only for a login imported from the client. */
64
82
  checkin?: CheckinStatus;
65
83
  }
66
84
  export type { LocalAccount };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-llm-adapters",
3
3
  "displayName": "Local LLM Adapters",
4
- "description": "DeepSeek Harness adapters for Loomy, SenseTime Raccoon and Trae. Sign in through the provider’s own web page (WeChat QR for Loomy and Raccoon, a dedicated browser window for Trae), then use the account’s models with quota, thinking levels and per-model settings pages.",
5
- "version": "1.2.0",
4
+ "description": "DeepSeek Harness adapters for Loomy, SenseTime Raccoon and Trae. Sign in through the provider’s own web page (WeChat QR for Loomy and Raccoon, a dedicated browser window for Trae), or reuse a signed-in local Trae client for the mainland check-in, then use the account’s models with quota, thinking levels and per-model settings pages.",
5
+ "version": "1.2.2",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "author": "k1-h2",
@@ -19,6 +19,7 @@
19
19
  "qr-login",
20
20
  "wechat-login",
21
21
  "web-login",
22
+ "client-login",
22
23
  "openai-compatible",
23
24
  "model-provider",
24
25
  "trae",