dsh-plugin-subscription-usage 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +38 -2
- package/cordis.patch.yml +5 -0
- package/icon.svg +9 -0
- package/lib/auth/claude-code-creds.d.ts +23 -0
- package/lib/auth/claude-code-creds.js +172 -0
- package/lib/auth/device-flow.d.ts +55 -0
- package/lib/auth/device-flow.js +177 -0
- package/lib/auth/jwt.d.ts +10 -0
- package/lib/auth/jwt.js +25 -0
- package/lib/auth/oauth-flow.d.ts +91 -0
- package/lib/auth/oauth-flow.js +227 -0
- package/lib/auth/pkce.d.ts +31 -0
- package/lib/auth/pkce.js +35 -0
- package/lib/auth/rpc.d.ts +233 -0
- package/lib/auth/rpc.js +501 -0
- package/lib/auth/store.d.ts +198 -0
- package/lib/auth/store.js +473 -0
- package/lib/client/Footer.d.ts +3 -0
- package/lib/client/Footer.js +73 -0
- package/lib/client/Gauge.d.ts +28 -0
- package/lib/client/Gauge.js +139 -0
- package/lib/client/ImageGallery.d.ts +54 -0
- package/lib/client/ImageGallery.js +112 -0
- package/lib/client/ImageGenerateToolview.d.ts +48 -0
- package/lib/client/ImageGenerateToolview.js +130 -0
- package/lib/client/Notice.d.ts +15 -0
- package/lib/client/Notice.js +93 -0
- package/lib/client/ProviderCard.d.ts +35 -0
- package/lib/client/ProviderCard.js +240 -0
- package/lib/client/SpeedSelect.d.ts +72 -0
- package/lib/client/SpeedSelect.js +214 -0
- package/lib/client/Usage.d.ts +7 -0
- package/lib/client/Usage.js +83 -0
- package/lib/client/VideoGenerateToolview.d.ts +42 -0
- package/lib/client/VideoGenerateToolview.js +172 -0
- package/lib/client/context.d.ts +16 -0
- package/lib/client/context.js +11 -0
- package/lib/client/fast-command.d.ts +2 -0
- package/lib/client/fast-command.js +4 -0
- package/lib/client/hooks.d.ts +2 -0
- package/lib/client/hooks.js +10 -0
- package/lib/client/index.d.ts +3 -0
- package/lib/client/index.js +113 -0
- package/lib/client/locales.d.ts +139 -0
- package/lib/client/locales.js +267 -0
- package/lib/client/meter.d.ts +91 -0
- package/lib/client/meter.js +196 -0
- package/lib/client/prefs.d.ts +51 -0
- package/lib/client/prefs.js +71 -0
- package/lib/client/rpc.d.ts +161 -0
- package/lib/client/rpc.js +31 -0
- package/lib/client/store.d.ts +67 -0
- package/lib/client/store.js +129 -0
- package/lib/client/styles.d.ts +7 -0
- package/lib/client/styles.js +176 -0
- package/lib/client.js +3933 -0
- package/lib/client.js.map +1 -0
- package/lib/compat.d.ts +55 -0
- package/lib/compat.js +20 -0
- package/lib/http.d.ts +134 -0
- package/lib/http.js +431 -0
- package/lib/index.d.ts +121 -0
- package/lib/index.js +13210 -0
- package/lib/json-text.d.ts +2 -0
- package/lib/json-text.js +4 -0
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +238 -0
- package/lib/model-speeds.d.ts +10 -0
- package/lib/model-speeds.js +48 -0
- package/lib/models-directory.d.ts +17 -0
- package/lib/models-directory.js +25 -0
- package/lib/provider-settings.d.ts +55 -0
- package/lib/provider-settings.js +215 -0
- package/lib/providers/account-preferences.d.ts +46 -0
- package/lib/providers/account-preferences.js +188 -0
- package/lib/providers/accounts.d.ts +113 -0
- package/lib/providers/accounts.js +142 -0
- package/lib/providers/antigravity-oauth-client.d.ts +7 -0
- package/lib/providers/antigravity-oauth-client.js +7 -0
- package/lib/providers/antigravity.d.ts +119 -0
- package/lib/providers/antigravity.js +552 -0
- package/lib/providers/catalog-refresh.d.ts +4 -0
- package/lib/providers/catalog-refresh.js +30 -0
- package/lib/providers/catalog-store.d.ts +47 -0
- package/lib/providers/catalog-store.js +283 -0
- package/lib/providers/claude-cli-version.d.ts +15 -0
- package/lib/providers/claude-cli-version.js +24 -0
- package/lib/providers/claude.d.ts +212 -0
- package/lib/providers/claude.js +726 -0
- package/lib/providers/codex-client-version.d.ts +8 -0
- package/lib/providers/codex-client-version.js +17 -0
- package/lib/providers/codex-search.d.ts +29 -0
- package/lib/providers/codex-search.js +198 -0
- package/lib/providers/codex.d.ts +253 -0
- package/lib/providers/codex.js +930 -0
- package/lib/providers/common.d.ts +415 -0
- package/lib/providers/common.js +679 -0
- package/lib/providers/copilot.d.ts +365 -0
- package/lib/providers/copilot.js +956 -0
- package/lib/providers/grok.d.ts +214 -0
- package/lib/providers/grok.js +750 -0
- package/lib/providers/image-pool.d.ts +30 -0
- package/lib/providers/image-pool.js +89 -0
- package/lib/providers/npm-cli-version.d.ts +56 -0
- package/lib/providers/npm-cli-version.js +98 -0
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +91 -0
- package/lib/providers/pool-health.js +164 -0
- package/lib/providers/pool-usage.d.ts +106 -0
- package/lib/providers/pool-usage.js +302 -0
- package/lib/providers/pool.d.ts +115 -0
- package/lib/providers/pool.js +461 -0
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/providers/replay.d.ts +4 -0
- package/lib/providers/replay.js +37 -0
- package/lib/providers/reset-redemption.d.ts +20 -0
- package/lib/providers/reset-redemption.js +70 -0
- package/lib/tools/image-generate.d.ts +128 -0
- package/lib/tools/image-generate.js +438 -0
- package/lib/tools/registration.d.ts +15 -0
- package/lib/tools/registration.js +24 -0
- package/lib/tools/video-generate.d.ts +89 -0
- package/lib/tools/video-generate.js +257 -0
- package/lib/tools/x-search.d.ts +58 -0
- package/lib/tools/x-search.js +197 -0
- package/lib/translate/anthropic.d.ts +180 -0
- package/lib/translate/anthropic.js +534 -0
- package/lib/translate/antigravity-images.d.ts +11 -0
- package/lib/translate/antigravity-images.js +62 -0
- package/lib/translate/antigravity-schema.d.ts +4 -0
- package/lib/translate/antigravity-schema.js +145 -0
- package/lib/translate/antigravity-thinking.d.ts +5 -0
- package/lib/translate/antigravity-thinking.js +44 -0
- package/lib/translate/antigravity.d.ts +120 -0
- package/lib/translate/antigravity.js +412 -0
- package/lib/translate/chat-completions.d.ts +120 -0
- package/lib/translate/chat-completions.js +382 -0
- package/lib/translate/resolved.d.ts +98 -0
- package/lib/translate/resolved.js +186 -0
- package/lib/translate/responses.d.ts +192 -0
- package/lib/translate/responses.js +443 -0
- package/lib/translate/sse.d.ts +21 -0
- package/lib/translate/sse.js +62 -0
- package/lib/usage/headers.d.ts +21 -0
- package/lib/usage/headers.js +64 -0
- package/lib/usage/pace.d.ts +53 -0
- package/lib/usage/pace.js +48 -0
- package/lib/usage/service.d.ts +64 -0
- package/lib/usage/service.js +104 -0
- package/lib/usage-providers/common.d.ts +23 -0
- package/lib/usage-providers/common.js +28 -0
- package/lib/usage-providers/index.d.ts +42 -0
- package/lib/usage-providers/index.js +71 -0
- package/lib/usage-providers/keys.d.ts +11 -0
- package/lib/usage-providers/keys.js +52 -0
- package/lib/usage-providers/kimi.d.ts +4 -0
- package/lib/usage-providers/kimi.js +52 -0
- package/lib/usage-providers/ollama-cloud.d.ts +4 -0
- package/lib/usage-providers/ollama-cloud.js +42 -0
- package/lib/usage-providers/zai.d.ts +5 -0
- package/lib/usage-providers/zai.js +53 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +115 -4
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk discovered-model-catalog cache at
|
|
3
|
+
* `~/.dsh/plugins/subscriptions/models.json` — the durable half of each
|
|
4
|
+
* provider's {@link ModelCatalogCache}. One entry per provider: the last
|
|
5
|
+
* successfully discovered catalog with its fetch time, so capability metadata
|
|
6
|
+
* (reasoning efforts) survives restarts and network failures.
|
|
7
|
+
*
|
|
8
|
+
* Unlike the auth store, this file is a cache: a missing, corrupt, or
|
|
9
|
+
* malformed file silently reads as absent, because the next successful
|
|
10
|
+
* discovery rewrites it. Loads are strictly validated — a malformed entry
|
|
11
|
+
* passed through `resolveModel` would make the harness's metadata validation
|
|
12
|
+
* throw on every call, which is worse than having no fallback at all.
|
|
13
|
+
*/
|
|
14
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { dirname } from 'node:path';
|
|
16
|
+
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
17
|
+
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
18
|
+
import { parseJsonText } from '../json-text.js';
|
|
19
|
+
/**
|
|
20
|
+
* Absolute path of the catalog store file.
|
|
21
|
+
* @returns `dshHomePath('plugins', 'subscriptions', 'models.json')`.
|
|
22
|
+
*/
|
|
23
|
+
export function modelsFilePath() {
|
|
24
|
+
return dshHomePath('plugins', 'subscriptions', 'models.json');
|
|
25
|
+
}
|
|
26
|
+
/** The per-account section of the file, or undefined when absent or malformed. */
|
|
27
|
+
function accountSection(store, provider) {
|
|
28
|
+
const accounts = store.accounts;
|
|
29
|
+
if (typeof accounts !== 'object' || accounts === null || Array.isArray(accounts))
|
|
30
|
+
return undefined;
|
|
31
|
+
const section = accounts[provider];
|
|
32
|
+
if (typeof section !== 'object' || section === null || Array.isArray(section))
|
|
33
|
+
return undefined;
|
|
34
|
+
return section;
|
|
35
|
+
}
|
|
36
|
+
/** Validate one persisted reasoning block, or undefined when malformed. */
|
|
37
|
+
function sanitizeReasoning(value) {
|
|
38
|
+
if (typeof value !== 'object' || value === null)
|
|
39
|
+
return undefined;
|
|
40
|
+
const raw = value;
|
|
41
|
+
if (!Array.isArray(raw.efforts) || raw.efforts.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const efforts = [];
|
|
45
|
+
for (const entry of raw.efforts) {
|
|
46
|
+
if (typeof entry !== 'object' || entry === null)
|
|
47
|
+
return undefined;
|
|
48
|
+
const effort = entry;
|
|
49
|
+
if (typeof effort.id !== 'string' || effort.id.length === 0
|
|
50
|
+
|| typeof effort.name !== 'string' || effort.name.length === 0
|
|
51
|
+
|| (effort.description !== undefined && typeof effort.description !== 'string')
|
|
52
|
+
|| seen.has(effort.id))
|
|
53
|
+
return undefined;
|
|
54
|
+
seen.add(effort.id);
|
|
55
|
+
efforts.push({
|
|
56
|
+
id: ReasoningEffortId(effort.id),
|
|
57
|
+
name: effort.name,
|
|
58
|
+
...effort.description === undefined ? {} : { description: effort.description },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (raw.defaultEffort !== undefined
|
|
62
|
+
&& (typeof raw.defaultEffort !== 'string' || !seen.has(raw.defaultEffort)))
|
|
63
|
+
return undefined;
|
|
64
|
+
return {
|
|
65
|
+
efforts,
|
|
66
|
+
...raw.defaultEffort === undefined ? {} : { defaultEffort: ReasoningEffortId(raw.defaultEffort) },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Validate one persisted model, or undefined when malformed. */
|
|
70
|
+
function sanitizeModel(value) {
|
|
71
|
+
if (typeof value !== 'object' || value === null)
|
|
72
|
+
return undefined;
|
|
73
|
+
const raw = value;
|
|
74
|
+
if (typeof raw.id !== 'string' || raw.id.length === 0
|
|
75
|
+
|| typeof raw.name !== 'string' || raw.name.length === 0
|
|
76
|
+
|| (raw.description !== undefined && typeof raw.description !== 'string')
|
|
77
|
+
|| (raw.contextWindow !== undefined
|
|
78
|
+
&& (typeof raw.contextWindow !== 'number' || !Number.isInteger(raw.contextWindow) || raw.contextWindow <= 0))
|
|
79
|
+
|| (raw.maxContextWindow !== undefined
|
|
80
|
+
&& (typeof raw.maxContextWindow !== 'number' || !Number.isSafeInteger(raw.maxContextWindow) || raw.maxContextWindow <= 0))
|
|
81
|
+
|| (raw.maxOutputTokens !== undefined
|
|
82
|
+
&& (typeof raw.maxOutputTokens !== 'number' || !Number.isSafeInteger(raw.maxOutputTokens) || raw.maxOutputTokens <= 0))
|
|
83
|
+
|| (raw.priority !== undefined
|
|
84
|
+
&& (typeof raw.priority !== 'number' || !Number.isFinite(raw.priority))))
|
|
85
|
+
return undefined;
|
|
86
|
+
const reasoning = raw.reasoning === undefined ? undefined : sanitizeReasoning(raw.reasoning);
|
|
87
|
+
if (raw.reasoning !== undefined && reasoning === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
const thinkingType = raw.thinkingType;
|
|
90
|
+
if (thinkingType !== undefined && thinkingType !== 'enabled' && thinkingType !== 'adaptive')
|
|
91
|
+
return undefined;
|
|
92
|
+
const fastTier = raw.fastTier;
|
|
93
|
+
if (fastTier !== undefined && typeof fastTier !== 'boolean')
|
|
94
|
+
return undefined;
|
|
95
|
+
const copilotWire = raw.copilotWire;
|
|
96
|
+
if (copilotWire !== undefined && copilotWire !== 'chat-completions' && copilotWire !== 'responses') {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const copilotResponses = raw.copilotResponses;
|
|
100
|
+
if (copilotResponses !== undefined && typeof copilotResponses !== 'boolean')
|
|
101
|
+
return undefined;
|
|
102
|
+
const inputModalities = raw.inputModalities;
|
|
103
|
+
if (inputModalities !== undefined
|
|
104
|
+
&& (!Array.isArray(inputModalities) || inputModalities.length === 0
|
|
105
|
+
|| inputModalities.some(modality => modality !== 'text' && modality !== 'image')))
|
|
106
|
+
return undefined;
|
|
107
|
+
return {
|
|
108
|
+
id: raw.id,
|
|
109
|
+
name: raw.name,
|
|
110
|
+
...raw.description === undefined ? {} : { description: raw.description },
|
|
111
|
+
...raw.contextWindow === undefined ? {} : { contextWindow: raw.contextWindow },
|
|
112
|
+
...raw.maxContextWindow === undefined ? {} : { maxContextWindow: raw.maxContextWindow },
|
|
113
|
+
...raw.maxOutputTokens === undefined ? {} : { maxOutputTokens: raw.maxOutputTokens },
|
|
114
|
+
...raw.priority === undefined ? {} : { priority: raw.priority },
|
|
115
|
+
...reasoning === undefined ? {} : { reasoning },
|
|
116
|
+
...thinkingType === undefined ? {} : { thinkingType: thinkingType },
|
|
117
|
+
...fastTier === undefined ? {} : { fastTier },
|
|
118
|
+
...copilotWire === undefined ? {} : { copilotWire: copilotWire },
|
|
119
|
+
...copilotResponses === undefined ? {} : { copilotResponses },
|
|
120
|
+
...inputModalities === undefined ? {} : { inputModalities: [...inputModalities] },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate one persisted snapshot. Strict: any malformed field drops the
|
|
125
|
+
* whole snapshot rather than repairing it — the next successful discovery
|
|
126
|
+
* rewrites the entry anyway.
|
|
127
|
+
* @param value - the raw per-provider file entry.
|
|
128
|
+
* @returns the validated snapshot, or undefined when unusable.
|
|
129
|
+
*/
|
|
130
|
+
export function sanitizeSnapshot(value) {
|
|
131
|
+
if (typeof value !== 'object' || value === null)
|
|
132
|
+
return undefined;
|
|
133
|
+
const raw = value;
|
|
134
|
+
if (typeof raw.at !== 'number' || !Number.isFinite(raw.at))
|
|
135
|
+
return undefined;
|
|
136
|
+
if (!Array.isArray(raw.models) || raw.models.length === 0)
|
|
137
|
+
return undefined;
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
const models = [];
|
|
140
|
+
for (const entry of raw.models) {
|
|
141
|
+
const model = sanitizeModel(entry);
|
|
142
|
+
if (model === undefined || seen.has(model.id))
|
|
143
|
+
return undefined;
|
|
144
|
+
seen.add(model.id);
|
|
145
|
+
models.push(model);
|
|
146
|
+
}
|
|
147
|
+
return { at: raw.at, models };
|
|
148
|
+
}
|
|
149
|
+
/** Read the whole file; missing or unparsable reads as an empty cache. */
|
|
150
|
+
async function readCatalogFile(path) {
|
|
151
|
+
let text;
|
|
152
|
+
try {
|
|
153
|
+
text = await readFile(path, 'utf8');
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
// Tolerate a leading BOM, or a hand-edited cache would silently read as
|
|
160
|
+
// empty and the model dropdown would fall back to the static list.
|
|
161
|
+
const parsed = parseJsonText(text);
|
|
162
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
163
|
+
return {};
|
|
164
|
+
return parsed;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Windows refuses to replace a file another handle holds open without
|
|
172
|
+
* `FILE_SHARE_DELETE` (an indexer, antivirus scan, or a second dsh profile
|
|
173
|
+
* reading the cache), surfacing as a transient `EPERM`/`EBUSY`/`EACCES` on
|
|
174
|
+
* `rename` (issue #129). Retry briefly before giving the write up.
|
|
175
|
+
*/
|
|
176
|
+
const RENAME_RETRY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES']);
|
|
177
|
+
const RENAME_RETRIES = 5;
|
|
178
|
+
const RENAME_RETRY_DELAY_MS = 20;
|
|
179
|
+
/** `rename` with a short bounded retry on share-mode conflicts. */
|
|
180
|
+
async function renameWithRetry(from, to) {
|
|
181
|
+
for (let attempt = 0;; attempt++) {
|
|
182
|
+
try {
|
|
183
|
+
await rename(from, to);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const code = error.code;
|
|
188
|
+
if (attempt >= RENAME_RETRIES || code === undefined || !RENAME_RETRY_CODES.has(code))
|
|
189
|
+
throw error;
|
|
190
|
+
await new Promise(resolve => setTimeout(resolve, RENAME_RETRY_DELAY_MS * (attempt + 1)));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Persist the whole file atomically (tmp file + rename). */
|
|
195
|
+
async function writeCatalogFile(store, path) {
|
|
196
|
+
await mkdir(dirname(path), { recursive: true });
|
|
197
|
+
const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
198
|
+
try {
|
|
199
|
+
await writeFile(tmp, JSON.stringify(store, null, 2));
|
|
200
|
+
await renameWithRetry(tmp, path);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
await rm(tmp, { force: true });
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Serialize snapshot updates so parallel account discoveries cannot erase siblings. */
|
|
208
|
+
const catalogWrites = new Map();
|
|
209
|
+
function updateCatalogFile(path, update) {
|
|
210
|
+
const pending = (catalogWrites.get(path) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
211
|
+
const store = await readCatalogFile(path);
|
|
212
|
+
if (update(store))
|
|
213
|
+
await writeCatalogFile(store, path);
|
|
214
|
+
});
|
|
215
|
+
catalogWrites.set(path, pending);
|
|
216
|
+
void pending.finally(() => { if (catalogWrites.get(path) === pending)
|
|
217
|
+
catalogWrites.delete(path); }).catch(() => undefined);
|
|
218
|
+
return pending;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Build the durable half of one provider's catalog cache over the shared
|
|
222
|
+
* models.json file. Snapshot mutations are serialized across providers/accounts.
|
|
223
|
+
* @param provider - the provider route keying the file entry.
|
|
224
|
+
* @param path - store file path; defaults to {@link modelsFilePath}.
|
|
225
|
+
* @returns the persistence hooks for {@link ModelCatalogCache}.
|
|
226
|
+
*/
|
|
227
|
+
export function catalogStore(provider, path = modelsFilePath()) {
|
|
228
|
+
return {
|
|
229
|
+
async load() {
|
|
230
|
+
return sanitizeSnapshot((await readCatalogFile(path))[provider]);
|
|
231
|
+
},
|
|
232
|
+
async save(snapshot) {
|
|
233
|
+
await updateCatalogFile(path, store => { store[provider] = snapshot; return true; });
|
|
234
|
+
},
|
|
235
|
+
async clear() {
|
|
236
|
+
await updateCatalogFile(path, store => {
|
|
237
|
+
if (store[provider] === undefined)
|
|
238
|
+
return false;
|
|
239
|
+
delete store[provider];
|
|
240
|
+
return true;
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The durable half of one NON-default account's catalog cache. The provider
|
|
247
|
+
* entry belongs to the default account; every other account keeps its own
|
|
248
|
+
* snapshot under `accounts[provider][account]`, so a working secondary login
|
|
249
|
+
* survives restarts and network failures even when the default login is dead.
|
|
250
|
+
* @param provider - the provider route.
|
|
251
|
+
* @param account - the canonical account key.
|
|
252
|
+
* @param path - store file path; defaults to {@link modelsFilePath}.
|
|
253
|
+
* @returns the persistence hooks for {@link ModelCatalogCache}.
|
|
254
|
+
*/
|
|
255
|
+
export function accountCatalogStore(provider, account, path = modelsFilePath()) {
|
|
256
|
+
return {
|
|
257
|
+
async load() {
|
|
258
|
+
const section = accountSection(await readCatalogFile(path), provider);
|
|
259
|
+
return section !== undefined && Object.hasOwn(section, account) ? sanitizeSnapshot(section[account]) : undefined;
|
|
260
|
+
},
|
|
261
|
+
async save(snapshot) {
|
|
262
|
+
await updateCatalogFile(path, store => {
|
|
263
|
+
const accounts = typeof store.accounts === 'object' && store.accounts !== null && !Array.isArray(store.accounts)
|
|
264
|
+
? store.accounts
|
|
265
|
+
: {};
|
|
266
|
+
const section = { ...accountSection(store, provider) ?? {}, [account]: snapshot };
|
|
267
|
+
store.accounts = { ...accounts, [provider]: section };
|
|
268
|
+
return true;
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
async clear() {
|
|
272
|
+
await updateCatalogFile(path, store => {
|
|
273
|
+
const section = accountSection(store, provider);
|
|
274
|
+
if (section === undefined || !Object.hasOwn(section, account))
|
|
275
|
+
return false;
|
|
276
|
+
const rest = { ...section };
|
|
277
|
+
delete rest[account];
|
|
278
|
+
store.accounts = { ...store.accounts, [provider]: rest };
|
|
279
|
+
return true;
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FetchFn } from './common.js';
|
|
2
|
+
import { NpmCliVersionCache } from './npm-cli-version.js';
|
|
3
|
+
/**
|
|
4
|
+
* Public metadata only: never send subscription credentials to this endpoint.
|
|
5
|
+
* `latest`, not `stable`: the endpoint gates new models on versions that
|
|
6
|
+
* reach `stable` about a week later.
|
|
7
|
+
*/
|
|
8
|
+
export declare const CLAUDE_CLI_VERSION_URL = "https://registry.npmjs.org/@anthropic-ai%2fclaude-code/latest";
|
|
9
|
+
/**
|
|
10
|
+
* Lazy, shared-per-plugin lookup of Claude Code's newest release, never
|
|
11
|
+
* below the local CLI or the bundled fallback version.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ClaudeCliVersionCache extends NpmCliVersionCache {
|
|
14
|
+
constructor(fetchFn?: FetchFn, now?: () => number, timeoutMs?: number, detect?: () => string);
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { claudeCliVersionFloor } from './claude.js';
|
|
2
|
+
import { NpmCliVersionCache } from './npm-cli-version.js';
|
|
3
|
+
/**
|
|
4
|
+
* Public metadata only: never send subscription credentials to this endpoint.
|
|
5
|
+
* `latest`, not `stable`: the endpoint gates new models on versions that
|
|
6
|
+
* reach `stable` about a week later.
|
|
7
|
+
*/
|
|
8
|
+
export const CLAUDE_CLI_VERSION_URL = 'https://registry.npmjs.org/@anthropic-ai%2fclaude-code/latest';
|
|
9
|
+
/**
|
|
10
|
+
* Lazy, shared-per-plugin lookup of Claude Code's newest release, never
|
|
11
|
+
* below the local CLI or the bundled fallback version.
|
|
12
|
+
*/
|
|
13
|
+
export class ClaudeCliVersionCache extends NpmCliVersionCache {
|
|
14
|
+
constructor(fetchFn, now, timeoutMs, detect) {
|
|
15
|
+
super({
|
|
16
|
+
url: CLAUDE_CLI_VERSION_URL,
|
|
17
|
+
label: 'Claude Code',
|
|
18
|
+
floor: () => claudeCliVersionFloor(detect),
|
|
19
|
+
...fetchFn === undefined ? {} : { fetchFn },
|
|
20
|
+
...now === undefined ? {} : { now },
|
|
21
|
+
...timeoutMs === undefined ? {} : { timeoutMs },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Pro/Max subscription provider: OAuth against claude.ai /
|
|
3
|
+
* platform.claude.com with the Claude Code client id, and streaming against
|
|
4
|
+
* the Anthropic Messages API with the Claude Code identity headers.
|
|
5
|
+
*/
|
|
6
|
+
import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
7
|
+
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import type { FlowSpec } from '../auth/oauth-flow.js';
|
|
9
|
+
import type { ClaudeSession } from '../auth/store.js';
|
|
10
|
+
import type { PoolAdapter } from './pool.js';
|
|
11
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
12
|
+
import type { TranslatableMessage } from '../translate/resolved.js';
|
|
13
|
+
import type { PromptCacheTtl } from '../translate/anthropic.js';
|
|
14
|
+
import { AccountTokenManager } from './accounts.js';
|
|
15
|
+
import type { CatalogPersistence, DiscoveredModel, FetchFn, ModelEntry, ProviderUsage } from './common.js';
|
|
16
|
+
import type { CliVersion } from './npm-cli-version.js';
|
|
17
|
+
import type { RateLimitResetReader, RateLimitWait } from './rate-limit.js';
|
|
18
|
+
export declare const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
19
|
+
export declare const CLAUDE_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
20
|
+
export declare const CLAUDE_TOKEN_URL = "https://claude.ai/v1/oauth/token";
|
|
21
|
+
export declare const CLAUDE_API_URL = "https://api.anthropic.com/v1/messages?beta=true";
|
|
22
|
+
export declare const CLAUDE_PROFILE_URL = "https://api.anthropic.com/api/oauth/profile";
|
|
23
|
+
export declare const CLAUDE_MODELS_URL = "https://api.anthropic.com/v1/models?beta=true";
|
|
24
|
+
export declare const CLAUDE_SCOPE = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
25
|
+
export declare const CLAUDE_CALLBACK_PATH = "/callback";
|
|
26
|
+
/** Refresh when the access token has less than this much life left. */
|
|
27
|
+
export declare const CLAUDE_PREEMPT_MS: number;
|
|
28
|
+
/**
|
|
29
|
+
* Reads the reset instant of the Anthropic window that rejected a request.
|
|
30
|
+
*
|
|
31
|
+
* `anthropic-ratelimit-unified-*` is the subscription-plan family — the one
|
|
32
|
+
* Claude Code renders as "resets 3pm" — and is the only header that names the
|
|
33
|
+
* window which actually rejected this request. The per-bucket
|
|
34
|
+
* `anthropic-ratelimit-{requests,tokens,input-tokens,output-tokens}-reset`
|
|
35
|
+
* headers are deliberately not read: they are rollover snapshots attached to
|
|
36
|
+
* every response, so on a 429 they cannot say which bucket refused, and the
|
|
37
|
+
* earliest of them is typically the bucket that still had room — a wait that
|
|
38
|
+
* lands straight back in the closed window. They reach the operator through
|
|
39
|
+
* `rateLimitDiagnostics` instead.
|
|
40
|
+
*/
|
|
41
|
+
export declare const claudeRateLimitReset: RateLimitResetReader;
|
|
42
|
+
/**
|
|
43
|
+
* The subscription endpoint only serves requests presenting as Claude Code,
|
|
44
|
+
* so these headers impersonate the CLI; the harness attribution user-agent
|
|
45
|
+
* cannot be sent here (one user-agent slot, and the CLI's wins). The endpoint
|
|
46
|
+
* also gates new models on the CLI version, so the version normally comes
|
|
47
|
+
* from npm (`ClaudeCliVersionCache`); this is only the offline floor.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CLAUDE_CLI_FALLBACK_VERSION = "2.1.283";
|
|
50
|
+
export declare function detectClaudeVersion(): string;
|
|
51
|
+
/**
|
|
52
|
+
* The lowest version to present: the local CLI or the bundled fallback,
|
|
53
|
+
* whichever is newer. An outdated local install must not pin requests to a
|
|
54
|
+
* version the endpoint already rejects for new models.
|
|
55
|
+
* @param detect - local CLI probe (injectable for tests).
|
|
56
|
+
*/
|
|
57
|
+
export declare function claudeCliVersionFloor(detect?: () => string): CliVersion;
|
|
58
|
+
/** The User-Agent Claude Code sends at `version`. */
|
|
59
|
+
export declare function claudeCliUserAgent(version: string): string;
|
|
60
|
+
export declare const CLAUDE_BETA_FALLBACK: string;
|
|
61
|
+
/** Static claude flow facts for the OAuth flow engine. */
|
|
62
|
+
export declare const claudeFlow: FlowSpec;
|
|
63
|
+
/** Best-effort account profile; login must not fail when this does. */
|
|
64
|
+
export declare function fetchClaudeProfile(accessToken: string): Promise<Pick<ClaudeSession, 'emailAddress' | 'subscriptionType'>>;
|
|
65
|
+
/**
|
|
66
|
+
* Exchange an authorization code for a claude session (JSON grant).
|
|
67
|
+
* @param code - the authorization code from the callback.
|
|
68
|
+
* @param verifier - the PKCE verifier minted for the attempt.
|
|
69
|
+
* @param redirectUri - the attempt's redirect URI.
|
|
70
|
+
* @param state - the attempt's state (echoed to the token endpoint).
|
|
71
|
+
* @returns the session to store.
|
|
72
|
+
*/
|
|
73
|
+
export declare function exchangeClaudeCode(code: string, verifier: string, redirectUri: string, state: string): Promise<ClaudeSession>;
|
|
74
|
+
/**
|
|
75
|
+
* Refresh a claude session (JSON grant echoing the issued scope).
|
|
76
|
+
* @param session - the stored session.
|
|
77
|
+
* @returns the fresh session to store.
|
|
78
|
+
*/
|
|
79
|
+
export declare function refreshClaude(session: ClaudeSession): Promise<ClaudeSession>;
|
|
80
|
+
/**
|
|
81
|
+
* Whether a claude refresh failure means the login is permanently gone.
|
|
82
|
+
* @param error - the thrown refresh error.
|
|
83
|
+
* @returns true when re-login is the only fix.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isClaudePermanentRefreshError(error: unknown): boolean;
|
|
86
|
+
export declare const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
87
|
+
/**
|
|
88
|
+
* Fetch the claude subscription usage from the OAuth usage endpoint (the
|
|
89
|
+
* source of Claude Code's `/usage` screen). Newer responses carry a
|
|
90
|
+
* structured `limits` array; older ones the flat `five_hour`/`seven_day*`
|
|
91
|
+
* buckets — both shapes are read, the array winning when it has entries.
|
|
92
|
+
* @param session - the stored session (used as-is; never refreshed here).
|
|
93
|
+
* @param fetchFn - fetch implementation (injectable for tests).
|
|
94
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
95
|
+
* @param cliVersion - resolves the Claude Code version to present; defaults to the local floor.
|
|
96
|
+
* @returns the mapped usage snapshot.
|
|
97
|
+
*/
|
|
98
|
+
export declare function fetchClaudeUsage(session: ClaudeSession, fetchFn?: FetchFn, signal?: AbortSignal, cliVersion?: () => Promise<string>): Promise<ProviderUsage>;
|
|
99
|
+
/**
|
|
100
|
+
* Fetch the live model catalog from the subscription endpoint. `signal`
|
|
101
|
+
* cancels the request; `cliVersion` resolves the Claude Code version to
|
|
102
|
+
* present (defaults to the local floor).
|
|
103
|
+
*/
|
|
104
|
+
export declare function fetchClaudeModels(session: ClaudeSession, fetchFn?: FetchFn, signal?: AbortSignal, cliVersion?: () => Promise<string>): Promise<DiscoveredModel[]>;
|
|
105
|
+
/** Constructor dependencies for {@link ClaudeAdapter}. */
|
|
106
|
+
export interface ClaudeAdapterOptions {
|
|
107
|
+
/**
|
|
108
|
+
* Observes every upstream reply before its status is checked, with the
|
|
109
|
+
* resolved account key. Usage headers ride both 2xx and 429 replies.
|
|
110
|
+
*/
|
|
111
|
+
onResponse?: (response: Response, account: string, sessionId: string | undefined) => void;
|
|
112
|
+
models: readonly ModelEntry[];
|
|
113
|
+
streamIdleTimeoutMs: number;
|
|
114
|
+
tokens: AccountTokenManager<ClaudeSession>;
|
|
115
|
+
/** Late-bound pool facade (wired after adapter construction); pools list under their first member's provider. */
|
|
116
|
+
pool?: () => PoolAdapter | undefined;
|
|
117
|
+
/** Whether to fetch the live catalog when logged in (false when config `models` overrides). */
|
|
118
|
+
discovery: boolean;
|
|
119
|
+
fetchFn?: FetchFn;
|
|
120
|
+
onWarn?: (message: string) => void;
|
|
121
|
+
/** How long this route may hold a turn open waiting for a rate-limit window; defaults to waiting on, six-hour ceiling. */
|
|
122
|
+
rateLimit?: RateLimitWait;
|
|
123
|
+
/** Resolve the attachment service per request; absent means image requests fail loudly. */
|
|
124
|
+
resolveAttachments?: () => AttachmentStore | undefined;
|
|
125
|
+
/** Durable catalog store seeding capability metadata across restarts. */
|
|
126
|
+
catalogStore?: CatalogPersistence;
|
|
127
|
+
onCatalogChanged?: () => void;
|
|
128
|
+
accountCatalogStore?: (account: string) => CatalogPersistence;
|
|
129
|
+
/**
|
|
130
|
+
* Per-model default reasoning effort override (the Settings page's picker).
|
|
131
|
+
* Returns the user-configured default for one model, or undefined to follow
|
|
132
|
+
* the provider's own default.
|
|
133
|
+
*/
|
|
134
|
+
defaultEffortOf?: (model: string) => string | undefined;
|
|
135
|
+
/** Resolves the Claude Code version to present (npm-backed); absent means the local floor. */
|
|
136
|
+
resolveCliVersion?: () => Promise<string>;
|
|
137
|
+
/** Lifetime of the request's cache breakpoints; absent means Anthropic's five-minute default. */
|
|
138
|
+
promptCacheTtl?: PromptCacheTtl;
|
|
139
|
+
/**
|
|
140
|
+
* The user's Settings-page choice, read per request so a save applies to
|
|
141
|
+
* the next turn without a restart; undefined falls back to `promptCacheTtl`.
|
|
142
|
+
*/
|
|
143
|
+
promptCacheTtlOverride?: () => PromptCacheTtl | undefined;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Inline image data a Claude request may carry, in base64 bytes. Every turn
|
|
147
|
+
* resends the history, so screenshots accumulate until the request passes the
|
|
148
|
+
* API's 32MB body limit and every later turn and compaction fails with HTTP
|
|
149
|
+
* 413 `request_too_large`. Past this budget the host offloads the oldest
|
|
150
|
+
* images to text and retries; the remaining 12MB holds prompts, tools, and text.
|
|
151
|
+
*/
|
|
152
|
+
export declare const CLAUDE_REQUEST_IMAGE_BUDGET: number;
|
|
153
|
+
/**
|
|
154
|
+
* Assemble the Anthropic request body.
|
|
155
|
+
*
|
|
156
|
+
* Extracted from the adapter so the wire shape — cache breakpoints above all —
|
|
157
|
+
* is testable without a network round trip. The message array is marked before
|
|
158
|
+
* it is placed so the breakpoints land on the blocks the body ships: one on the
|
|
159
|
+
* last `system` block (covering `tools` + `system`, which render ahead of it)
|
|
160
|
+
* and up to three across the history, Anthropic's four-slot maximum.
|
|
161
|
+
* @param options - the generate request.
|
|
162
|
+
* @param messages - conversation messages with images already resolved.
|
|
163
|
+
* @param maxTokens - the resolved output cap.
|
|
164
|
+
* @param thinking - the thinking parameter, when the model takes one.
|
|
165
|
+
* @param effort - the reasoning effort, when the model advertises efforts.
|
|
166
|
+
* @param cacheTtl - lifetime of every cache breakpoint; defaults to five minutes.
|
|
167
|
+
* Requests that carry a `purpose` (compaction, session titles) always use five
|
|
168
|
+
* minutes to preserve their existing auxiliary-request behavior. A five-minute
|
|
169
|
+
* mark can still read an entry a one-hour mark wrote.
|
|
170
|
+
* @returns the JSON body to POST.
|
|
171
|
+
*/
|
|
172
|
+
export declare function claudeRequestBody(options: GenerateOptions, messages: readonly TranslatableMessage[], maxTokens: number, thinking?: Record<string, unknown>, effort?: string, cacheTtl?: PromptCacheTtl): Record<string, unknown>;
|
|
173
|
+
/** Claude wire adapter: one instance serves the `claude` provider route. */
|
|
174
|
+
export declare class ClaudeAdapter extends LlmAdapter {
|
|
175
|
+
private readonly options;
|
|
176
|
+
private readonly catalog;
|
|
177
|
+
/** In-memory catalogs for non-default accounts (the persisted cache is the default's). */
|
|
178
|
+
private readonly accountCatalogs;
|
|
179
|
+
private accountCatalogsInvalidated;
|
|
180
|
+
private readonly invalidatedAccounts;
|
|
181
|
+
/** Account whose snapshot currently lives in {@link catalog}; cleared on default change. */
|
|
182
|
+
private catalogOwner;
|
|
183
|
+
constructor(options: ClaudeAdapterOptions);
|
|
184
|
+
private fetchCatalog;
|
|
185
|
+
/** Drop cached catalogs after login/logout so the next list does not reuse a stale plan. */
|
|
186
|
+
clearAccountCatalog(account?: string): void;
|
|
187
|
+
/** Default and secondary accounts each seed their own persisted catalog. */
|
|
188
|
+
private catalogFor;
|
|
189
|
+
private discovered;
|
|
190
|
+
private staticModels;
|
|
191
|
+
providerInfo(provider: string): LlmProviderInfo;
|
|
192
|
+
providerRetryPolicy(provider: string): import("@deepseek-ai/dsh-llm").ResolvedRetryPolicy;
|
|
193
|
+
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
194
|
+
/** The provider's own catalog: union of every account, or one account when named. */
|
|
195
|
+
listOwnModels(provider: string, account?: string, signal?: AbortSignal, preferCached?: boolean): Promise<readonly LlmModelInfo[]>;
|
|
196
|
+
resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
197
|
+
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
198
|
+
resolveOwnModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
199
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
200
|
+
/** Pool seam: stream through one specific account instead of the default. */
|
|
201
|
+
streamAccount(options: GenerateOptions, account: string): AsyncIterable<StreamChunk>;
|
|
202
|
+
private streamCore;
|
|
203
|
+
/**
|
|
204
|
+
* `display: 'summarized'` is set explicitly on both shapes: `adaptive`-type
|
|
205
|
+
* models default to `display: 'omitted'`, which returns thinking blocks with
|
|
206
|
+
* an empty `thinking` field — without this override the "Think" panel would
|
|
207
|
+
* always render empty even though real reasoning (and billed thinking_tokens)
|
|
208
|
+
* ran.
|
|
209
|
+
*/
|
|
210
|
+
private thinkingParam;
|
|
211
|
+
private request;
|
|
212
|
+
}
|