@eddyskywalker/dsh-chatgpt-subscription 0.8.5 → 0.9.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +93 -38
  3. package/lib/client.js +2126 -1693
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +7588 -2772
  6. package/lib/types/client/claude/ClaudeComposerQuota.d.ts +33 -0
  7. package/lib/types/client/claude/ClaudeComposerQuota.d.ts.map +1 -0
  8. package/lib/types/client/{zhipu/ZhipuSection.d.ts → claude/ClaudeSection.d.ts} +2 -2
  9. package/lib/types/client/claude/ClaudeSection.d.ts.map +1 -0
  10. package/lib/types/client/claude/locales.d.ts +444 -0
  11. package/lib/types/client/claude/locales.d.ts.map +1 -0
  12. package/lib/types/client/claude/styles.d.ts +15 -0
  13. package/lib/types/client/claude/styles.d.ts.map +1 -0
  14. package/lib/types/client/index.d.ts +1 -1
  15. package/lib/types/client/index.d.ts.map +1 -1
  16. package/lib/types/host/claude/__probe-assert.d.ts +2 -0
  17. package/lib/types/host/claude/__probe-assert.d.ts.map +1 -0
  18. package/lib/types/host/claude/account-pool.d.ts +580 -0
  19. package/lib/types/host/claude/account-pool.d.ts.map +1 -0
  20. package/lib/types/host/claude/adapter.d.ts +267 -0
  21. package/lib/types/host/claude/adapter.d.ts.map +1 -0
  22. package/lib/types/host/claude/adopt.d.ts +292 -0
  23. package/lib/types/host/claude/adopt.d.ts.map +1 -0
  24. package/lib/types/host/claude/client.d.ts +513 -0
  25. package/lib/types/host/claude/client.d.ts.map +1 -0
  26. package/lib/types/host/claude/mapper.d.ts +510 -0
  27. package/lib/types/host/claude/mapper.d.ts.map +1 -0
  28. package/lib/types/host/claude/model-catalog.d.ts +365 -0
  29. package/lib/types/host/claude/model-catalog.d.ts.map +1 -0
  30. package/lib/types/host/claude/oauth.d.ts +393 -0
  31. package/lib/types/host/claude/oauth.d.ts.map +1 -0
  32. package/lib/types/host/claude/routes.d.ts +128 -0
  33. package/lib/types/host/claude/routes.d.ts.map +1 -0
  34. package/lib/types/host/claude/token-store.d.ts +312 -0
  35. package/lib/types/host/claude/token-store.d.ts.map +1 -0
  36. package/lib/types/host/claude/types.d.ts +237 -0
  37. package/lib/types/host/claude/types.d.ts.map +1 -0
  38. package/lib/types/index.d.ts +10 -7
  39. package/lib/types/index.d.ts.map +1 -1
  40. package/lib/types/shared/__probe-claude-import.d.ts +2 -0
  41. package/lib/types/shared/__probe-claude-import.d.ts.map +1 -0
  42. package/lib/types/shared/claude-contracts.d.ts +337 -0
  43. package/lib/types/shared/claude-contracts.d.ts.map +1 -0
  44. package/package.json +1 -1
  45. package/lib/types/client/zhipu/ZhipuComposerQuota.d.ts +0 -26
  46. package/lib/types/client/zhipu/ZhipuComposerQuota.d.ts.map +0 -1
  47. package/lib/types/client/zhipu/ZhipuSection.d.ts.map +0 -1
  48. package/lib/types/client/zhipu/locales.d.ts +0 -275
  49. package/lib/types/client/zhipu/locales.d.ts.map +0 -1
  50. package/lib/types/client/zhipu/styles.d.ts +0 -12
  51. package/lib/types/client/zhipu/styles.d.ts.map +0 -1
  52. package/lib/types/host/zhipu/account-pool.d.ts +0 -43
  53. package/lib/types/host/zhipu/account-pool.d.ts.map +0 -1
  54. package/lib/types/host/zhipu/adapter.d.ts +0 -93
  55. package/lib/types/host/zhipu/adapter.d.ts.map +0 -1
  56. package/lib/types/host/zhipu/client.d.ts +0 -153
  57. package/lib/types/host/zhipu/client.d.ts.map +0 -1
  58. package/lib/types/host/zhipu/mapper.d.ts +0 -111
  59. package/lib/types/host/zhipu/mapper.d.ts.map +0 -1
  60. package/lib/types/host/zhipu/model-catalog.d.ts +0 -73
  61. package/lib/types/host/zhipu/model-catalog.d.ts.map +0 -1
  62. package/lib/types/host/zhipu/routes.d.ts +0 -33
  63. package/lib/types/host/zhipu/routes.d.ts.map +0 -1
  64. package/lib/types/host/zhipu/token-store.d.ts +0 -112
  65. package/lib/types/host/zhipu/token-store.d.ts.map +0 -1
  66. package/lib/types/host/zhipu/types.d.ts +0 -125
  67. package/lib/types/host/zhipu/types.d.ts.map +0 -1
  68. package/lib/types/shared/zhipu-contracts.d.ts +0 -164
  69. package/lib/types/shared/zhipu-contracts.d.ts.map +0 -1
@@ -0,0 +1,267 @@
1
+ /**
2
+ * DSH adapter for the `claude-subscription` route.
3
+ *
4
+ * This file is the only place the harness talks to. It owns: the model catalog
5
+ * the picker sees, the per-model resolution DSH asks for before a call, the one
6
+ * request loop, and the mapping of a failed response onto a DSH failure. It owns
7
+ * NO credential lifecycle of its own — refreshing is oauth.ts's job, storage is
8
+ * token-store.ts's job, the wire is client.ts's, the body and the SSE parse are
9
+ * mapper.ts's — and it re-implements none of them.
10
+ *
11
+ * ---------------------------------------------------------------------------
12
+ * 1. THE TWO LIMITS ON ACCOUNT ROTATION (the part that is easy to get wrong)
13
+ * ---------------------------------------------------------------------------
14
+ *
15
+ * With a pool installed, one failed response may be retried on a DIFFERENT
16
+ * account. Two rules bound that, and both are load-bearing:
17
+ *
18
+ * (a) WHAT may rotate. Only two conditions are attributable to one account:
19
+ * a credential verdict (that account's token is dead) and an
20
+ * ACCOUNT-SCOPED rate limit (that account's own usage window is spent).
21
+ * Everything else — a global rate limit, an `overloaded_error`, a generic
22
+ * 5xx — is shared by every account behind the same gateway, so rotating
23
+ * against it burns the pool and can loop. client.ts's classifyFailure
24
+ * already draws exactly this line (its `accountScoped` field), so the
25
+ * decision reads that rather than re-deriving it from headers here.
26
+ *
27
+ * (b) HOW FAR. At most {@link MAX_ATTEMPTS} requests per stream, ever. The loop
28
+ * is additionally driven by the pool's `hasAnotherAvailableAccount`, but
29
+ * that is a pool property, not a bound: a pool that keeps answering "yes"
30
+ * would otherwise be retried forever.
31
+ *
32
+ * And one hard rule that outranks both: NO ROTATION ONCE ANYTHING HAS REACHED
33
+ * THE CALLER. A retried request would repeat text the user has already read and
34
+ * re-issue a tool call that may already have executed. Rotation happens only
35
+ * before the response body is touched, which is where a fresh request is still
36
+ * free; once the first chunk has been yielded, a failure is SURFACED instead of
37
+ * retried. That rule is stated once in {@link shouldRotateAccount} so it cannot
38
+ * be quietly lost by a later reordering of this file.
39
+ *
40
+ * ---------------------------------------------------------------------------
41
+ * 2. ONE TOOL-NAME TABLE FOR BOTH DIRECTIONS
42
+ * ---------------------------------------------------------------------------
43
+ *
44
+ * The body renames every offered tool to its canonical wire spelling
45
+ * ('read' -> 'Read'), and the response side maps that spelling back to what the
46
+ * caller declared. Two independent calls to yaml's translation builder would
47
+ * produce two DIFFERENT tables — a collision that disables normalization on one
48
+ * side only, or a set that changed between build and parse — and the visible
49
+ * failure is a tool call dispatched to a tool the caller never offered. So the
50
+ * table is built once, per request, and handed to BOTH the body builder and
51
+ * `createStreamState`.
52
+ *
53
+ * ---------------------------------------------------------------------------
54
+ * 3. THE PRE-EXISTING SINGLE-CREDENTIAL PATH, AND THE THIN STORE ADAPTER
55
+ * ---------------------------------------------------------------------------
56
+ *
57
+ * Without a pool this route serves the account the credential document offers
58
+ * first — or the one the user pinned in `selectedAccountId`, which the pool
59
+ * mirrors as its primary. oauth.ts's `ensureAccessToken` was written against a
60
+ * two-method `{ read, write }` store because it must not learn the document
61
+ * layout, while FileCredentialStore is a multi-account document keyed by an
62
+ * immutable `internalId`. {@link ClaudeAccountTokenStore} is that seam: it
63
+ * presents exactly one account's credentials as the two methods, and records
64
+ * WHICH record it handed out so a rotated refresh token is written back into
65
+ * that record and not into whichever account happens to be first by the time
66
+ * the refresh lands.
67
+ *
68
+ * ---------------------------------------------------------------------------
69
+ * 4. FAILURE CLASSIFICATION
70
+ * ---------------------------------------------------------------------------
71
+ *
72
+ * client.ts classifies; this file only decides which DSH code that verdict
73
+ * becomes, and what the user is told:
74
+ *
75
+ * - `credential` -> INVALID_CREDENTIAL (sign in again);
76
+ * - an account-scoped or global limit -> RATE_LIMIT;
77
+ * - `overloaded` and `server` -> SERVER;
78
+ * - `request` -> PROVIDER_ERROR;
79
+ * - `network` -> TRANSPORT.
80
+ *
81
+ * A reported-client-version rejection is a REQUEST problem even though it
82
+ * arrives as a 400 that mentions the client: it must NOT sign the user out, and
83
+ * it has a real remedy (raise the reported version), so it is handled
84
+ * explicitly with that remedy in the message.
85
+ *
86
+ * ---------------------------------------------------------------------------
87
+ * 5. ASSUMPTIONS AND JUDGEMENT CALLS
88
+ * ---------------------------------------------------------------------------
89
+ *
90
+ * - A1. The pool seam is typed STRUCTURALLY here rather than against
91
+ * './account-pool.ts'. That file is written by a sibling chunk of this line;
92
+ * importing it before it exists would make this adapter uncompilable, and
93
+ * importing it afterwards changes nothing, because the only methods used are
94
+ * the five in {@link ClaudeAccountPoolLike}.
95
+ * - A2. The live `GET /v1/models` listing is NOT read on the request path. It
96
+ * is authoritative for the context window alone (see client.ts), and the only
97
+ * model fact the body depends on — the thinking form — comes from the frozen
98
+ * catalog. Loading it per request would add a cached round trip that cannot
99
+ * change a byte of the body.
100
+ * - A3. A 429 with no `retry-after` and no reset instant cools the account for
101
+ * {@link POOL_COOLDOWN_MS}. That is deliberately shorter than the 5-hour
102
+ * window: an account parked for hours after a burst limit recovers would cost
103
+ * the user the pool, while a too-short cooldown costs one rotation.
104
+ * - A4. Adoption (adopt.ts) is not handled here. An adopted Claude Code
105
+ * credential is a pool concern — the pool is what must refuse to refresh it —
106
+ * and this adapter only ever sees the credential the pool or the document
107
+ * hands it.
108
+ * - A5. The enabled-model rule is stated locally in
109
+ * {@link resolveEnabledModelIds} because './routes.ts' (the sibling chunk that
110
+ * owns the settings card for this line) does not exist yet. It mirrors the
111
+ * sibling providers' rule exactly; once routes.ts lands it is the natural home
112
+ * for this function and the two should be one.
113
+ */
114
+ import { LlmAdapter, LlmError, ReasoningEffortId, type GenerateOptions, type LlmModelInfo, type LlmProviderInfo, type LlmResolvedModelInfo, type PreparedAdapterCall, type ResolvedRetryPolicy, type StreamChunk } from '@deepseek-ai/dsh-llm';
115
+ import { PROVIDER_ID, PROVIDER_NAME } from './types.ts';
116
+ import { FileCredentialStore, FileModelSettingsStore, type ClaudeCredentials, type ClaudePreferenceStore } from './token-store.ts';
117
+ import { type ClaudeModelEntry } from './model-catalog.ts';
118
+ import { type ClaudeFailure } from './client.ts';
119
+ import { type AttachmentImageReader } from './mapper.ts';
120
+ /**
121
+ * Cooldown a 429 imposes when the service names no reset instant.
122
+ *
123
+ * Deliberately far shorter than the 5-hour window it may be reporting: the
124
+ * accurate wait arrives as `retry-after` or as the ISO instant in the error
125
+ * message, and this is only the fallback for a response that states neither.
126
+ */
127
+ export declare const POOL_COOLDOWN_MS: number;
128
+ /**
129
+ * The pool surface this adapter uses, stated structurally.
130
+ *
131
+ * See module note A1: './account-pool.ts' is a sibling chunk's file, and typing
132
+ * against the five methods actually called keeps this adapter compilable
133
+ * whether or not that file has landed.
134
+ */
135
+ export interface ClaudeAccountPoolLike {
136
+ getEffectiveAccount(excludeIds?: ReadonlySet<string>, fetchFn?: typeof fetch): Promise<{
137
+ account: {
138
+ id: string;
139
+ };
140
+ credentials: ClaudeCredentials;
141
+ }>;
142
+ hasAnotherAvailableAccount(triedAccountIds: ReadonlySet<string>): Promise<boolean>;
143
+ markCooldown(accountId: string, durationMs: number, reason: string): Promise<void>;
144
+ markAuthFailed(accountId: string, reason: string): Promise<void>;
145
+ }
146
+ export interface ClaudeAdapterOptions {
147
+ fetchFn?: typeof fetch;
148
+ /** Attachment seam: durable images become inline bytes for one request. */
149
+ attachments?: AttachmentImageReader;
150
+ /** Live catalog loader seam; defaults to this account's managed listing. */
151
+ loadCatalog?: (credentials: ClaudeCredentials) => Promise<readonly ClaudeModelEntry[]>;
152
+ /**
153
+ * Multi-account pool this adapter rotates through.
154
+ *
155
+ * Absent leaves the line on its single-credential path, which is what a
156
+ * headless test and any caller that predates the pool use.
157
+ */
158
+ accountPool?: ClaudeAccountPoolLike;
159
+ }
160
+ export declare class ClaudeAdapter extends LlmAdapter {
161
+ private readonly store;
162
+ private readonly modelSettings;
163
+ private readonly preferences?;
164
+ private readonly options;
165
+ /** Null keeps the pre-pool single-credential path. */
166
+ private readonly accountPool;
167
+ constructor(store?: FileCredentialStore, modelSettings?: FileModelSettingsStore, preferences?: ClaudePreferenceStore | undefined, options?: ClaudeAdapterOptions);
168
+ providerInfo(provider: string): LlmProviderInfo;
169
+ providerRetryPolicy(): ResolvedRetryPolicy;
170
+ /**
171
+ * This route prices no visual tokens.
172
+ *
173
+ * The subscription bills in subscription windows rather than per image, so
174
+ * there is no per-image price to state and the harness's neutral estimate is
175
+ * the honest answer. Stated explicitly, as every sibling line does, so the
176
+ * absence is a decision rather than an unimplemented method.
177
+ */
178
+ imageRequestPricing(): undefined;
179
+ private settings;
180
+ /**
181
+ * The account record the single-credential path serves.
182
+ *
183
+ * The pinned account wins while it exists in the document — that is the
184
+ * account the user chose on the card, and honouring it here keeps the
185
+ * single-credential path agreeing with what the card shows. Otherwise the
186
+ * document is in insertion order and the first record is the primary one,
187
+ * which is also what the pool mirrors into this store.
188
+ */
189
+ private primaryAccount;
190
+ /**
191
+ * Catalog for one credential: the account's live listing, or the shipped table.
192
+ *
193
+ * client.ts's loader never rejects and never returns empty — it answers with
194
+ * the shipped table when the call fails, precisely so a failed listing cannot
195
+ * become the hard "model must be in the catalog" gate. The guard here is for a
196
+ * caller-supplied seam that does neither.
197
+ */
198
+ private catalog;
199
+ listModels(provider?: string): Promise<readonly LlmModelInfo[]>;
200
+ resolveModel(provider: string, modelId: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
201
+ prepareCall(provider: string, model: string, signal?: AbortSignal): Promise<PreparedAdapterCall>;
202
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
203
+ /**
204
+ * Read one credential for this attempt.
205
+ *
206
+ * Pooled and pre-pool are two different questions. With a pool this is a
207
+ * ROUTING decision — which account should serve this request — answered
208
+ * through cooldowns, auth status and the rotation strategy; the pool also owns
209
+ * refreshing the credential it hands back. Without one there is exactly one
210
+ * account, so the question is whether its token is still usable, and
211
+ * ensureAccessToken answers it single-flightedly (the refresh token rotates,
212
+ * so two callers must not each spend it).
213
+ */
214
+ private resolveCredential;
215
+ /**
216
+ * One request attempt, after the credential for it has been chosen.
217
+ *
218
+ * A SEPARATE function from the rotation loop on purpose: importing
219
+ * './account-pool.ts' would make this adapter uncompilable while that sibling
220
+ * file is still being written (see the module note), so the rotation loop is
221
+ * driven by {@link shouldRotateAccount}, which bypasses the pool ENTIRELY when
222
+ * no credentials were resolved for it.
223
+ */
224
+ private attemptRequest;
225
+ private requestStream;
226
+ }
227
+ /**
228
+ * Effort to materialize as this route's default, or nothing.
229
+ *
230
+ * Membership, not convergence: the catalog's ladder IS the wire vocabulary, and
231
+ * a configured level outside it is dropped rather than moved onto a neighbour.
232
+ * Moving it would silently change how hard the model thinks, and the four
233
+ * thinking forms each carry the effort differently, so there is no single
234
+ * correct neighbour to pick. 'xhigh' and 'max' are real rungs on this route and
235
+ * pass through untouched.
236
+ */
237
+ export declare function resolveDefaultReasoningEffort(efforts: readonly string[], configuredEffort?: string | null): ReasoningEffortId | undefined;
238
+ /**
239
+ * Whether one failed response may be retried on another account.
240
+ *
241
+ * Only two conditions qualify, and the reason is the same for both: they are
242
+ * properties of ONE account. A credential verdict means that account's token is
243
+ * finished; an ACCOUNT-SCOPED rate limit means that account's own window is
244
+ * spent. A global rate limit, an overload and a generic 5xx are shared by every
245
+ * account behind the same gateway — rotating against them burns the pool and can
246
+ * loop, because the next account fails identically.
247
+ *
248
+ * The one absolute refusal is `outputStarted`: after the first chunk has
249
+ * reached the caller, a retry would repeat text and re-issue a tool call, so the
250
+ * failure is surfaced instead.
251
+ */
252
+ export declare function shouldRotateAccount(failure: ClaudeFailure, outputStarted: boolean): boolean;
253
+ /** How long a rate-limited account stays out of rotation. */
254
+ export declare function cooldownMsFor(failure: ClaudeFailure, now?: number): number;
255
+ /** The DSH failure code one classified failure becomes. */
256
+ export declare function codeForFailure(failure: ClaudeFailure): string;
257
+ /**
258
+ * Turn one classified failure into the DSH error the caller sees.
259
+ *
260
+ * The facts the harness needs ride along: the HTTP status, and the delay the
261
+ * provider asked for, which is where the retry policy reads its backoff from.
262
+ * An unreadable status (0 from a transport failure) is omitted rather than
263
+ * passed on, because LlmError validates it as a real HTTP status.
264
+ */
265
+ export declare function toLlmError(failure: ClaudeFailure): LlmError;
266
+ export { PROVIDER_ID, PROVIDER_NAME };
267
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/host/claude/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,292 @@
1
+ /**
2
+ * OPT-IN adoption of a pre-existing Claude Code sign-in.
3
+ *
4
+ * WHAT THIS IS FOR. A user who already runs the unmodified Claude Code client is
5
+ * already signed in to the very subscription this provider serves. Asking them
6
+ * to sign in a second time is pure friction, so this module offers the one thing
7
+ * that removes it: reading back the credential Claude Code itself stored. It is
8
+ * opt-in — nothing here runs until the user asks for it.
9
+ *
10
+ * ---------------------------------------------------------------------------
11
+ * RULE 1 — THIS MODULE NEVER WRITES. IT IS A READER, AND ONLY A READER.
12
+ * ---------------------------------------------------------------------------
13
+ * It must not create, modify, move or delete any file belonging to Claude Code.
14
+ * No file this module touches is ever opened for writing, no directory is
15
+ * created, nothing is renamed and nothing is unlinked. The guarantee is
16
+ * structural rather than a promise in prose: the module imports exactly two
17
+ * functions BY NAME from 'node:fs/promises' — readFile and stat — instead of the
18
+ * whole namespace, so introducing a write would require a visible change to the
19
+ * import list, which is the line a reviewer reads first. Nothing exported here
20
+ * writes. The tests assert the stronger, observable form of the rule: a
21
+ * credential file's bytes, size and mtime are identical before and after both
22
+ * entry points run, and its directory gains no entries.
23
+ *
24
+ * ---------------------------------------------------------------------------
25
+ * RULE 2 — PRESENCE AND CONTENT ARE TWO SEPARATE FUNCTIONS
26
+ * ---------------------------------------------------------------------------
27
+ * claudeCodeCredentialPresence() is cheap enough to call on plugin startup and
28
+ * answers only yes/no: it stats candidates and never opens one for reading. It
29
+ * exists so the settings card can offer the import before the user has agreed to
30
+ * it, and so a user who never opts in never has their token read at all.
31
+ *
32
+ * readClaudeCodeCredentials() is the full read, and it is called only AFTER the
33
+ * user explicitly opts in. Keeping the two apart is the whole point: a startup
34
+ * path that slurped the file "just in case" would make the opt-in decorative.
35
+ *
36
+ * ---------------------------------------------------------------------------
37
+ * RULE 3 — AN ADOPTED CREDENTIAL IS A SNAPSHOT, AND THIS PLUGIN NEVER REFRESHES IT
38
+ * ---------------------------------------------------------------------------
39
+ * An adopted credential is used only while its own accessToken is still valid.
40
+ * Once it is expired we report it as expired and tell the user to sign in again
41
+ * in Claude Code and re-adopt. That is deliberate, not a gap:
42
+ *
43
+ * Claude Code's refresh token is ROTATING — every refresh both consumes the
44
+ * stored refresh token and issues a new one. Two processes refreshing the same
45
+ * grant therefore invalidate each other: whoever loses the race holds a token
46
+ * the server has already retired, and the user ends up signed OUT of Claude
47
+ * Code by this plugin's good intentions. An in-process single-flight cannot fix
48
+ * that, because the race is across PROCESSES: this plugin and Claude Code are
49
+ * separate programs with separate memories, and the only shared state between
50
+ * them is the file — which this module must not write back into (Rule 1).
51
+ *
52
+ * So the honest contract is: borrow the access token for as long as Claude Code
53
+ * itself vouched for it, and never touch the refresh token. Expiry is therefore
54
+ * part of the adoption decision rather than a detail — see
55
+ * {@link isAdoptedCredentialExpired}.
56
+ *
57
+ * The marker the pool chunk must honour is the literal 'adopted: true' on every
58
+ * returned snapshot, plus {@link isAdoptedClaudeCredential}; an account carrying
59
+ * it must be treated as read-only, non-refreshable and disposable. There is
60
+ * deliberately no refresh function in this module to find in the first place.
61
+ *
62
+ * ---------------------------------------------------------------------------
63
+ * RULE 4 — AN UNRECOGNISED SHAPE YIELDS undefined, NEVER AN EXCEPTION
64
+ * ---------------------------------------------------------------------------
65
+ * The card shows 'unrecognised local sign-in format' for undefined, which is a
66
+ * far better outcome than a thrown error escaping into the settings route.
67
+ * Nothing is guessed: a document missing either token, a claudeAiOauth that is
68
+ * not an object, an unreadable or unparsable file — all of them are undefined.
69
+ * Extra and unknown fields are ignored rather than rejected, so a future Claude
70
+ * Code that adds one does not break adoption.
71
+ *
72
+ * THE TRAP THIS PARSER EXISTS TO AVOID. The credential document is NOT a flat bag
73
+ * of tokens. The same file also carries MCP server sign-ins under
74
+ * mcpOAuth.*.accessToken, and those are DIFFERENT tokens for DIFFERENT services.
75
+ * A "find the first accessToken anywhere in the JSON" scan silently picks
76
+ * whichever one the serializer happened to emit first, which is how an MCP token
77
+ * ends up presented to the Messages API as the user's subscription credential.
78
+ * The read is therefore a direct, specific lookup of claudeAiOauth.accessToken —
79
+ * never a tree walk — and the test suite holds a fixture containing BOTH tokens
80
+ * and asserts which one comes back.
81
+ *
82
+ * ---------------------------------------------------------------------------
83
+ * WHAT COULD NOT BE VERIFIED, AND IS THEREFORE NOT CLAIMED
84
+ * ---------------------------------------------------------------------------
85
+ * The file layout below (a credential file named .credentials.json inside the
86
+ * config home, JSON of the shape { "claudeAiOauth": { accessToken, refreshToken,
87
+ * expiresAt, scopes, subscriptionType } } with expiresAt in EPOCH MILLISECONDS)
88
+ * is assembled from secondary sources. This machine has NO Claude Code
89
+ * installation and NO such file — all candidate paths were checked and every one
90
+ * is absent — so this reader is proven by CONSTRUCTION against fixtures and NOT
91
+ * by a live read of a real client's file. A layout difference in a future Claude
92
+ * Code would surface as 'unrecognised local sign-in format' rather than as a
93
+ * wrong token, because the parser is written to refuse rather than to improvise.
94
+ *
95
+ * A KNOWN NON-GOAL: on macOS Claude Code also keeps an entry in the login
96
+ * Keychain (reported as service 'Claude Code-credentials'). This module does NOT
97
+ * read it. The service name is unverified — a third-party guide and the real
98
+ * client disagree about it — and asking the Keychain for a name that is not the
99
+ * real one either finds nothing or, worse, finds somebody else's item. A file
100
+ * path that can be printed in the UI and inspected by the user beats a guessed
101
+ * Keychain query, so the Keychain is left strictly alone.
102
+ */
103
+ import type { ClaudeCredentials } from './token-store.ts';
104
+ /**
105
+ * Environment variable that relocates Claude Code's config directory.
106
+ *
107
+ * Highest precedence, and honoured exactly as the client honours it: when it is
108
+ * set to a non-blank value it names the config home, whatever the platform's
109
+ * conventional location happens to be. This is also what makes the whole module
110
+ * testable without touching a developer's real profile.
111
+ */
112
+ export declare const CLAUDE_CONFIG_DIR_ENV = "CLAUDE_CONFIG_DIR";
113
+ /** File name of the credential document inside the config home. */
114
+ export declare const CLAUDE_CODE_CREDENTIAL_FILE = ".credentials.json";
115
+ /**
116
+ * Top-level key holding the subscription sign-in.
117
+ *
118
+ * Named as a constant because the specificity of this key IS the defence
119
+ * described in the module comment: the read addresses this key, not the first
120
+ * token it can find anywhere in the document.
121
+ */
122
+ export declare const CLAUDE_CODE_CREDENTIAL_KEY = "claudeAiOauth";
123
+ /** Where an adopted snapshot came from, for the card and for the pool record. */
124
+ export declare const CLAUDE_CODE_CREDENTIAL_SOURCE = "claude-code";
125
+ /**
126
+ * Expiry value stamped on a credential that states no usable one.
127
+ *
128
+ * Epoch zero, and it is a deliberate choice over "now" or "Infinity". Infinity
129
+ * would turn a missing field into a credential that never expires — the exact
130
+ * inversion of the safe reading, because a credential whose validity cannot be
131
+ * established must not be used. Epoch zero is unambiguous, survives a JSON round
132
+ * trip, and compares as expired against any clock a user's machine can hold, so
133
+ * a damaged document degrades into "expired, sign in again" instead of into a
134
+ * request carrying a token nobody vouched for.
135
+ */
136
+ export declare const UNKNOWN_CREDENTIAL_EXPIRY = 0;
137
+ /**
138
+ * What the user is told when an adopted credential has aged out.
139
+ *
140
+ * Exported so the settings card and the pool report the same sentence, and so the
141
+ * instruction is stated once: re-sign-in happens in CLAUDE CODE, not here, and
142
+ * the import has to be repeated afterwards.
143
+ */
144
+ export declare const ADOPTED_CREDENTIAL_EXPIRED_HINT: string;
145
+ /** One adopted credential: the store's own credential shape plus its provenance. */
146
+ export interface ClaudeCodeAdoptedCredential {
147
+ /**
148
+ * The pool marker. Literal true on every snapshot this module returns.
149
+ *
150
+ * A discriminant rather than a description: a pool chunk that stores this
151
+ * record must refuse to refresh it, and a boolean it can test is harder to
152
+ * overlook than a comment it can read past.
153
+ */
154
+ readonly adopted: true;
155
+ /** Which local sign-in this was taken from. */
156
+ readonly source: typeof CLAUDE_CODE_CREDENTIAL_SOURCE;
157
+ /** The exact file it was read from, so the card can name it. */
158
+ readonly sourcePath: string;
159
+ /** The credential in the store's own shape, ready to be persisted by the caller. */
160
+ readonly credentials: ClaudeCredentials;
161
+ }
162
+ /** Answer to the startup question: is there anything to adopt? */
163
+ export interface ClaudeCodeCredentialPresence {
164
+ /** Whether a candidate exists. Says nothing about whether it is usable. */
165
+ present: boolean;
166
+ /** The first candidate that exists, or null when none does. */
167
+ path: string | null;
168
+ /**
169
+ * Every candidate consulted, in precedence order, whether or not it existed.
170
+ *
171
+ * Returned so the card can print the exact list it looked at — the difference
172
+ * between "not found" and "your client stores it somewhere else" is invisible
173
+ * to a user who is only told the first path.
174
+ */
175
+ searched: string[];
176
+ }
177
+ /**
178
+ * Claude Code's config home.
179
+ *
180
+ * The environment override wins whenever it is set to a non-blank value — a blank
181
+ * or whitespace-only variable is treated as unset rather than as "the directory
182
+ * whose name is empty", which on Windows would resolve to a drive-relative path.
183
+ * Otherwise the conventional home/.claude directory is used, os.homedir() being
184
+ * the same %USERPROFILE% on Windows and ~ on POSIX that the client itself uses.
185
+ *
186
+ * Read at CALL time rather than captured in a module constant: a test (or an
187
+ * embedder) that sets the variable after import must see the change.
188
+ */
189
+ export declare function claudeConfigDir(): string;
190
+ /**
191
+ * Every path this module will look at, most authoritative first.
192
+ *
193
+ * The FIRST entry is always the config home from {@link claudeConfigDir} — the
194
+ * location the client is documented to use — so the card can print it as "the"
195
+ * path.
196
+ *
197
+ * The conventional home/.claude/.credentials.json follows as a FALLBACK whenever
198
+ * it is not already the first entry. Judgement call, stated plainly: treating the
199
+ * environment override as absolutely exclusive would make a stale or
200
+ * freshly-created override directory — a user who once pointed the variable at a
201
+ * scratch profile and forgot — report "no sign-in found" while a perfectly good
202
+ * one sits in the conventional location. The fallback costs one stat, is
203
+ * consulted only when the earlier candidate yields nothing usable, and the file
204
+ * actually used is always reported back in
205
+ * {@link ClaudeCodeAdoptedCredential.sourcePath}. The residual risk is adopting a
206
+ * snapshot belonging to a different profile than the one Claude Code is currently
207
+ * using; it is bounded by the fact that such a snapshot is only ever used while
208
+ * its own access token is unexpired, and it is visible to the user because the
209
+ * source path is shown.
210
+ *
211
+ * A pure function — no filesystem access at all — so it is safe to call just to
212
+ * render the card, and so its precedence is testable without touching a disk.
213
+ */
214
+ export declare function claudeCodeCredentialPaths(): string[];
215
+ /**
216
+ * Is there a local Claude Code sign-in to offer?
217
+ *
218
+ * Safe on plugin startup: this NEVER opens a candidate for reading, so a user who
219
+ * never opts in never has the credential's contents touched — not by this
220
+ * function, not by the route that renders the card. Only stat runs, which reveals
221
+ * existence and timestamps and nothing else.
222
+ *
223
+ * Deliberately NOT reported here: whether the file is valid, expired, or even
224
+ * JSON. Answering that would require the read this function exists to avoid, and
225
+ * the card does not need it — validity is answered by the read, after opt-in, and
226
+ * lands on the card as either a credential or 'unrecognised local sign-in
227
+ * format'.
228
+ *
229
+ * paths is injectable so tests drive it against fixtures instead of a real
230
+ * profile; production callers take the default.
231
+ */
232
+ export declare function claudeCodeCredentialPresence(paths?: readonly string[]): Promise<ClaudeCodeCredentialPresence>;
233
+ /**
234
+ * Map one parsed credential document onto the store's credential shape.
235
+ *
236
+ * Pure and exported so the format rules can be tested without a filesystem, and
237
+ * so a later chunk that reads the document from somewhere else (a backup, a
238
+ * pasted blob) reuses the one set of rules instead of growing a second, drifting
239
+ * copy.
240
+ *
241
+ * Returns undefined for every shape it does not recognise, and never throws — see
242
+ * Rule 4 in the module comment. The gate is deliberately narrow: the document must
243
+ * be a JSON object, claudeAiOauth must be an object, and it must carry BOTH a
244
+ * non-empty accessToken and a non-empty refreshToken. An access token alone is not
245
+ * adopted because the store's record is a sign-in, not a bare token: its identity
246
+ * aliases are derived from the refresh token, and a record with an empty one would
247
+ * mint an identity that matches nothing and can never be re-recognised.
248
+ *
249
+ * The MCP trap is handled by construction here: value.claudeAiOauth is addressed
250
+ * directly and no other key of the document is ever traversed, so whatever
251
+ * mcpOAuth holds cannot reach the result.
252
+ */
253
+ export declare function parseAdoptedClaudeCredential(value: unknown, sourcePath: string): ClaudeCodeAdoptedCredential | undefined;
254
+ /**
255
+ * Read the local Claude Code sign-in; the first usable candidate wins.
256
+ *
257
+ * ONLY call this after the user has explicitly opted in — see the module comment.
258
+ *
259
+ * Never throws and never rejects: an absent file, an unreadable file, a file that
260
+ * is not JSON, and a JSON document of an unrecognised shape all produce
261
+ * undefined, which the card renders as 'unrecognised local sign-in format'. A
262
+ * settings route must not be able to fail because a user's file was mid-write.
263
+ *
264
+ * A candidate that EXISTS but cannot be parsed does not stop the search; the next
265
+ * candidate is tried. That trade-off is stated in {@link claudeCodeCredentialPaths}
266
+ * and the file actually used is reported in the result, so a fallback is always
267
+ * visible rather than silent.
268
+ */
269
+ export declare function readClaudeCodeCredentials(paths?: readonly string[]): Promise<ClaudeCodeAdoptedCredential | undefined>;
270
+ /**
271
+ * Whether a value carries the adopted marker.
272
+ *
273
+ * The predicate the pool chunk tests a stored account with before deciding whether
274
+ * it may ever be refreshed. It recognises this module's snapshot and nothing else:
275
+ * a plain store credential has no 'adopted' field and must NOT be mistaken for
276
+ * one, because treating a plugin-owned account as a disposable snapshot would
277
+ * discard an account the user added here.
278
+ */
279
+ export declare function isAdoptedClaudeCredential(value: unknown): value is ClaudeCodeAdoptedCredential;
280
+ /**
281
+ * Whether an adopted credential's access token is already unusable.
282
+ *
283
+ * A structural mirror of the expiry test the OAuth module applies to its own
284
+ * credentials (expiresAt - Date.now() > 0), asked of an adopted snapshot so the
285
+ * pool can refuse it WITHOUT ever reaching for a refresh this module deliberately
286
+ * does not perform. A non-finite expiresAt counts as expired, matching
287
+ * {@link finiteEpochMs}: no field, no validity.
288
+ *
289
+ * now is injectable so a test can reason about a fixed clock.
290
+ */
291
+ export declare function isAdoptedCredentialExpired(credential: Pick<ClaudeCodeAdoptedCredential, 'credentials'>, now?: number): boolean;
292
+ //# sourceMappingURL=adopt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adopt.d.ts","sourceRoot":"","sources":["../../../../src/host/claude/adopt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AAUH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,sBAAsB,CAAA;AAExD,mEAAmE;AACnE,eAAO,MAAM,2BAA2B,sBAAsB,CAAA;AAE9D;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,kBAAkB,CAAA;AAEzD,iFAAiF;AACjF,eAAO,MAAM,6BAA6B,gBAAgB,CAAA;AAE1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C;;;;;;GAMG;AACH,eAAO,MAAM,+BAA+B,QAEa,CAAA;AAEzD,oFAAoF;AACpF,MAAM,WAAW,2BAA2B;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,OAAO,6BAA6B,CAAA;IACrD,gEAAgE;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,oFAAoF;IACpF,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAA;CACxC;AAED,kEAAkE;AAClE,MAAM,WAAW,4BAA4B;IAC3C,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAA;IAChB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAaD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAIxC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,EAAE,CAKpD;AAqBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,GAAE,SAAS,MAAM,EAAgC,GACrD,OAAO,CAAC,4BAA4B,CAAC,CAMvC;AA4CD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,OAAO,EACd,UAAU,EAAE,MAAM,GACjB,2BAA2B,GAAG,SAAS,CA+BzC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,yBAAyB,CAC7C,KAAK,GAAE,SAAS,MAAM,EAAgC,GACrD,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAclD;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,2BAA2B,CAG9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,IAAI,CAAC,2BAA2B,EAAE,aAAa,CAAC,EAC5D,GAAG,GAAE,MAAmB,GACvB,OAAO,CAGT"}