@prestyj/core 5.8.0 → 5.10.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/dist/{chunk-GMAX7TES.js → chunk-TPGCBL7I.js} +483 -298
- package/dist/chunk-TPGCBL7I.js.map +1 -0
- package/dist/index.cjs +957 -381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -8
- package/dist/index.d.ts +168 -8
- package/dist/index.js +395 -13
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +92 -29
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +25 -3
- package/dist/model-registry.d.ts +25 -3
- package/dist/model-registry.js +9 -1
- package/package.json +2 -2
- package/dist/chunk-GMAX7TES.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthStorage,
|
|
3
3
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
4
|
+
LOCAL_AUTH_KEY_PREFIX,
|
|
4
5
|
MODELS,
|
|
5
6
|
MOONSHOT_OAUTH_KEY,
|
|
6
7
|
NotLoggedInError,
|
|
7
8
|
XIAOMI_CREDITS_KEY,
|
|
9
|
+
clearRuntimeModels,
|
|
8
10
|
closeLogger,
|
|
9
11
|
generatePKCE,
|
|
12
|
+
getAllModels,
|
|
10
13
|
getAuthStorageKey,
|
|
11
14
|
getAuthStorageKeys,
|
|
12
15
|
getClaudeCliUserAgent,
|
|
13
16
|
getClaudeCodeVersion,
|
|
14
17
|
getContextWindow,
|
|
15
18
|
getDefaultModel,
|
|
19
|
+
getDefaultThinkingLevel,
|
|
16
20
|
getFastModel,
|
|
17
21
|
getMaxThinkingLevel,
|
|
18
22
|
getModel,
|
|
@@ -31,14 +35,16 @@ import {
|
|
|
31
35
|
loginKimi,
|
|
32
36
|
loginOpenAI,
|
|
33
37
|
openLog,
|
|
38
|
+
readStoredBaseUrlSync,
|
|
34
39
|
refreshAnthropicToken,
|
|
35
40
|
refreshGeminiToken,
|
|
36
41
|
refreshKimiToken,
|
|
37
42
|
refreshOpenAIToken,
|
|
38
43
|
registerLogCleanup,
|
|
44
|
+
registerRuntimeModels,
|
|
39
45
|
usesOpenAICodexTransport,
|
|
40
46
|
withFileLock
|
|
41
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-TPGCBL7I.js";
|
|
42
48
|
import {
|
|
43
49
|
getAppPaths
|
|
44
50
|
} from "./chunk-WCIV25KV.js";
|
|
@@ -55,7 +61,7 @@ var OPENAI_GPT_56_THINKING_LEVELS = [
|
|
|
55
61
|
];
|
|
56
62
|
var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
|
|
57
63
|
var XAI_THINKING_LEVELS = ["low", "medium", "high"];
|
|
58
|
-
var
|
|
64
|
+
var ANTHROPIC_XHIGH_THINKING_LEVELS = [
|
|
59
65
|
"low",
|
|
60
66
|
"medium",
|
|
61
67
|
"high",
|
|
@@ -68,6 +74,8 @@ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
|
|
|
68
74
|
"high",
|
|
69
75
|
"max"
|
|
70
76
|
];
|
|
77
|
+
var MOONSHOT_K3_THINKING_LEVELS = ["low", "high", "max"];
|
|
78
|
+
var LOCAL_THINKING_LEVELS = ["low", "medium", "high", "max"];
|
|
71
79
|
function isOpenAIGptModel(provider, model) {
|
|
72
80
|
return provider === "openai" && model.startsWith("gpt-");
|
|
73
81
|
}
|
|
@@ -77,16 +85,25 @@ function isSakanaModel(provider) {
|
|
|
77
85
|
function isXaiModel(provider) {
|
|
78
86
|
return provider === "xai";
|
|
79
87
|
}
|
|
80
|
-
function
|
|
81
|
-
return provider === "
|
|
88
|
+
function isMoonshotK3Model(provider, model) {
|
|
89
|
+
return provider === "moonshot" && model === "kimi-k3";
|
|
90
|
+
}
|
|
91
|
+
function isAnthropicXhighModel(provider, model) {
|
|
92
|
+
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7/.test(model);
|
|
82
93
|
}
|
|
83
94
|
function isAnthropicAdaptiveModel(provider, model) {
|
|
84
|
-
return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
95
|
+
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
85
96
|
}
|
|
86
97
|
function getSupportedThinkingLevels(provider, model) {
|
|
98
|
+
if (provider === "local") {
|
|
99
|
+
const info = getModel(model);
|
|
100
|
+
if (!info?.supportsThinking) return [];
|
|
101
|
+
const maxIndex2 = LOCAL_THINKING_LEVELS.indexOf(info.maxThinkingLevel);
|
|
102
|
+
return maxIndex2 === -1 ? LOCAL_THINKING_LEVELS.slice(0, 3) : LOCAL_THINKING_LEVELS.slice(0, maxIndex2 + 1);
|
|
103
|
+
}
|
|
87
104
|
const maxLevel = getMaxThinkingLevel(model);
|
|
88
105
|
if (isAnthropicAdaptiveModel(provider, model)) {
|
|
89
|
-
const levels2 =
|
|
106
|
+
const levels2 = isAnthropicXhighModel(provider, model) ? ANTHROPIC_XHIGH_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
|
|
90
107
|
const maxIndex2 = levels2.indexOf(maxLevel);
|
|
91
108
|
if (maxIndex2 === -1) return ["low", "medium", "high"];
|
|
92
109
|
return levels2.slice(0, maxIndex2 + 1);
|
|
@@ -101,6 +118,7 @@ function getSupportedThinkingLevels(provider, model) {
|
|
|
101
118
|
if (maxIndex2 === -1) return XAI_THINKING_LEVELS;
|
|
102
119
|
return XAI_THINKING_LEVELS.slice(0, maxIndex2 + 1);
|
|
103
120
|
}
|
|
121
|
+
if (isMoonshotK3Model(provider, model)) return MOONSHOT_K3_THINKING_LEVELS;
|
|
104
122
|
if (!isOpenAIGptModel(provider, model)) return [maxLevel];
|
|
105
123
|
const levels = model.startsWith("gpt-5.6-") ? OPENAI_GPT_56_THINKING_LEVELS : OPENAI_GPT_THINKING_LEVELS;
|
|
106
124
|
const maxIndex = levels.indexOf(maxLevel);
|
|
@@ -112,7 +130,11 @@ function isThinkingLevelSupported(provider, model, level) {
|
|
|
112
130
|
}
|
|
113
131
|
function getNextThinkingLevel(provider, model, current) {
|
|
114
132
|
const supportedLevels = getSupportedThinkingLevels(provider, model);
|
|
115
|
-
const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider)
|
|
133
|
+
const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model) || // Local servers take a real effort level, not just on/off: Ollama accepts
|
|
134
|
+
// low/medium/high on `reasoning_effort` (verified against 0.32) and the
|
|
135
|
+
// other OpenAI-compatible servers use the same three. A model that can't
|
|
136
|
+
// reason at all already has no supported levels, so it never gets here.
|
|
137
|
+
provider === "local";
|
|
116
138
|
if (!shouldCycleLevels) {
|
|
117
139
|
return current ? void 0 : supportedLevels[0];
|
|
118
140
|
}
|
|
@@ -122,11 +144,259 @@ function getNextThinkingLevel(provider, model, current) {
|
|
|
122
144
|
return supportedLevels[index + 1];
|
|
123
145
|
}
|
|
124
146
|
|
|
147
|
+
// src/local-models.ts
|
|
148
|
+
var DEFAULT_LOCAL_ENDPOINTS = [
|
|
149
|
+
{ id: "ollama", label: "Ollama", baseUrl: "http://127.0.0.1:11434/v1", kind: "ollama" },
|
|
150
|
+
{ id: "lmstudio", label: "LM Studio", baseUrl: "http://127.0.0.1:1234/v1", kind: "lmstudio" },
|
|
151
|
+
{ id: "llamacpp", label: "llama.cpp", baseUrl: "http://127.0.0.1:8080/v1", kind: "llamacpp" },
|
|
152
|
+
{ id: "vllm", label: "vLLM", baseUrl: "http://127.0.0.1:8000/v1", kind: "vllm" }
|
|
153
|
+
];
|
|
154
|
+
var FALLBACK_CONTEXT_WINDOW = 8192;
|
|
155
|
+
var LOCAL_API_KEY_PLACEHOLDER = "local";
|
|
156
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 1200;
|
|
157
|
+
var ENRICH_CONCURRENCY = 6;
|
|
158
|
+
var CACHE_TTL_MS = 3e4;
|
|
159
|
+
var NON_CHAT_ID_PATTERN = /(?:^|[-_/])(?:embed|embedding|rerank|reranker|bge|nomic-embed)/i;
|
|
160
|
+
var LOCAL_ID_PREFIX = "local/";
|
|
161
|
+
function formatLocalModelId(endpointId, rawId) {
|
|
162
|
+
return `${LOCAL_ID_PREFIX}${endpointId}/${rawId}`;
|
|
163
|
+
}
|
|
164
|
+
function parseLocalModelId(id) {
|
|
165
|
+
if (!id.startsWith(LOCAL_ID_PREFIX)) return void 0;
|
|
166
|
+
const rest = id.slice(LOCAL_ID_PREFIX.length);
|
|
167
|
+
const slash = rest.indexOf("/");
|
|
168
|
+
if (slash <= 0 || slash === rest.length - 1) return void 0;
|
|
169
|
+
return { endpointId: rest.slice(0, slash), rawId: rest.slice(slash + 1) };
|
|
170
|
+
}
|
|
171
|
+
function isLocalModelId(id) {
|
|
172
|
+
return parseLocalModelId(id) !== void 0;
|
|
173
|
+
}
|
|
174
|
+
function localAuthStorageKey(endpointId) {
|
|
175
|
+
return `local:${endpointId}`;
|
|
176
|
+
}
|
|
177
|
+
function endpointRoot(baseUrl) {
|
|
178
|
+
return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
179
|
+
}
|
|
180
|
+
function authHeaders(endpoint) {
|
|
181
|
+
return {
|
|
182
|
+
Authorization: `Bearer ${endpoint.apiKey ?? LOCAL_API_KEY_PLACEHOLDER}`,
|
|
183
|
+
Accept: "application/json"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function fetchJson(url, endpoint, options) {
|
|
187
|
+
try {
|
|
188
|
+
const res = await fetchJsonOrThrow(url, endpoint, options);
|
|
189
|
+
return res;
|
|
190
|
+
} catch {
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function fetchJsonOrThrow(url, endpoint, { timeoutMs, signal, method = "GET", body }) {
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
197
|
+
const onAbort = () => controller.abort();
|
|
198
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
199
|
+
try {
|
|
200
|
+
const res = await fetch(url, {
|
|
201
|
+
method,
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
headers: body ? { ...authHeaders(endpoint), "Content-Type": "application/json" } : authHeaders(endpoint),
|
|
204
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
205
|
+
});
|
|
206
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
207
|
+
return await res.json();
|
|
208
|
+
} finally {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
signal?.removeEventListener("abort", onAbort);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function unreachableReason(endpoint, err) {
|
|
214
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
215
|
+
if (/abort/i.test(message)) return `No response from ${endpoint.baseUrl} (timed out)`;
|
|
216
|
+
if (/HTTP 401|HTTP 403/.test(message)) {
|
|
217
|
+
return `${endpoint.baseUrl} rejected the API key (HTTP ${message.includes("401") ? 401 : 403})`;
|
|
218
|
+
}
|
|
219
|
+
if (/HTTP \d+/.test(message)) return `${endpoint.baseUrl} returned ${message}`;
|
|
220
|
+
return `Not running at ${endpoint.baseUrl}`;
|
|
221
|
+
}
|
|
222
|
+
async function probeEndpoint(endpoint, { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = {}) {
|
|
223
|
+
const listUrl = `${endpoint.baseUrl.replace(/\/+$/, "")}/models`;
|
|
224
|
+
let list;
|
|
225
|
+
try {
|
|
226
|
+
list = await fetchJsonOrThrow(listUrl, endpoint, { timeoutMs, signal });
|
|
227
|
+
} catch (err) {
|
|
228
|
+
return { endpoint, reachable: false, reason: unreachableReason(endpoint, err), models: [] };
|
|
229
|
+
}
|
|
230
|
+
const entries = (list.data ?? []).filter(
|
|
231
|
+
(entry) => typeof entry.id === "string" && entry.id.length > 0 && !NON_CHAT_ID_PATTERN.test(entry.id)
|
|
232
|
+
);
|
|
233
|
+
const models = await enrich(endpoint, entries, { timeoutMs, signal });
|
|
234
|
+
log("INFO", "local-models", `Probed ${endpoint.label}`, {
|
|
235
|
+
baseUrl: endpoint.baseUrl,
|
|
236
|
+
models: String(models.length)
|
|
237
|
+
});
|
|
238
|
+
return { endpoint, reachable: true, models };
|
|
239
|
+
}
|
|
240
|
+
async function enrich(endpoint, entries, options) {
|
|
241
|
+
if (endpoint.kind === "lmstudio") return enrichLmStudio(endpoint, entries, options);
|
|
242
|
+
if (endpoint.kind === "ollama") return enrichOllama(endpoint, entries, options);
|
|
243
|
+
if (endpoint.kind === "llamacpp") return enrichLlamaCpp(endpoint, entries, options);
|
|
244
|
+
return entries.map((entry) => genericModel(endpoint, entry));
|
|
245
|
+
}
|
|
246
|
+
function genericModel(endpoint, entry) {
|
|
247
|
+
const declared = typeof entry.max_model_len === "number" ? entry.max_model_len : void 0;
|
|
248
|
+
return {
|
|
249
|
+
rawId: entry.id,
|
|
250
|
+
endpointId: endpoint.id,
|
|
251
|
+
contextWindow: declared ?? FALLBACK_CONTEXT_WINDOW,
|
|
252
|
+
contextWindowKnown: declared !== void 0,
|
|
253
|
+
supportsTools: true,
|
|
254
|
+
supportsImages: false,
|
|
255
|
+
supportsThinking: false
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function enrichOllama(endpoint, entries, options) {
|
|
259
|
+
const showUrl = `${endpointRoot(endpoint.baseUrl)}/api/show`;
|
|
260
|
+
const enriched = await mapLimited(entries, ENRICH_CONCURRENCY, async (entry) => {
|
|
261
|
+
const show = await fetchJson(showUrl, endpoint, {
|
|
262
|
+
...options,
|
|
263
|
+
method: "POST",
|
|
264
|
+
body: { model: entry.id }
|
|
265
|
+
});
|
|
266
|
+
if (!show) return genericModel(endpoint, entry);
|
|
267
|
+
const caps = show.capabilities ?? [];
|
|
268
|
+
if (caps.includes("embedding") && !caps.includes("completion")) return void 0;
|
|
269
|
+
const ctx = ollamaContextLength(show.model_info);
|
|
270
|
+
return {
|
|
271
|
+
rawId: entry.id,
|
|
272
|
+
endpointId: endpoint.id,
|
|
273
|
+
contextWindow: ctx ?? FALLBACK_CONTEXT_WINDOW,
|
|
274
|
+
contextWindowKnown: ctx !== void 0,
|
|
275
|
+
// Ollama reports capabilities honestly, so trust it here rather than
|
|
276
|
+
// using the optimistic generic default.
|
|
277
|
+
supportsTools: caps.includes("tools"),
|
|
278
|
+
supportsImages: caps.includes("vision"),
|
|
279
|
+
supportsThinking: caps.includes("thinking")
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
return enriched.filter((model) => model !== void 0);
|
|
283
|
+
}
|
|
284
|
+
function ollamaContextLength(info) {
|
|
285
|
+
if (!info) return void 0;
|
|
286
|
+
for (const [key, value] of Object.entries(info)) {
|
|
287
|
+
if (key.endsWith(".context_length") && typeof value === "number" && value > 0) return value;
|
|
288
|
+
}
|
|
289
|
+
return void 0;
|
|
290
|
+
}
|
|
291
|
+
async function enrichLmStudio(endpoint, entries, options) {
|
|
292
|
+
const detail = await fetchJson(
|
|
293
|
+
`${endpointRoot(endpoint.baseUrl)}/api/v0/models`,
|
|
294
|
+
endpoint,
|
|
295
|
+
options
|
|
296
|
+
);
|
|
297
|
+
if (!detail?.data) return entries.map((entry) => genericModel(endpoint, entry));
|
|
298
|
+
const byId = new Map(detail.data.filter((m) => m.id).map((m) => [m.id, m]));
|
|
299
|
+
const models = [];
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
const info = byId.get(entry.id);
|
|
302
|
+
if (info && info.type !== "llm" && info.type !== "vlm") continue;
|
|
303
|
+
const ctx = info?.max_context_length;
|
|
304
|
+
models.push({
|
|
305
|
+
rawId: entry.id,
|
|
306
|
+
endpointId: endpoint.id,
|
|
307
|
+
contextWindow: typeof ctx === "number" && ctx > 0 ? ctx : FALLBACK_CONTEXT_WINDOW,
|
|
308
|
+
contextWindowKnown: typeof ctx === "number" && ctx > 0,
|
|
309
|
+
// LM Studio doesn't report tool support; it gates per-model at request time.
|
|
310
|
+
supportsTools: true,
|
|
311
|
+
supportsImages: info?.type === "vlm",
|
|
312
|
+
supportsThinking: false,
|
|
313
|
+
loaded: info?.state === "loaded"
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return models;
|
|
317
|
+
}
|
|
318
|
+
async function enrichLlamaCpp(endpoint, entries, options) {
|
|
319
|
+
const props = await fetchJson(
|
|
320
|
+
`${endpointRoot(endpoint.baseUrl)}/props`,
|
|
321
|
+
endpoint,
|
|
322
|
+
options
|
|
323
|
+
);
|
|
324
|
+
const nCtx = props?.default_generation_settings?.n_ctx;
|
|
325
|
+
const known = typeof nCtx === "number" && nCtx > 0;
|
|
326
|
+
return entries.map((entry) => ({
|
|
327
|
+
...genericModel(endpoint, entry),
|
|
328
|
+
contextWindow: known ? nCtx : FALLBACK_CONTEXT_WINDOW,
|
|
329
|
+
contextWindowKnown: known
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
async function mapLimited(items, limit, fn) {
|
|
333
|
+
const results = new Array(items.length);
|
|
334
|
+
let next = 0;
|
|
335
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
336
|
+
while (next < items.length) {
|
|
337
|
+
const index = next++;
|
|
338
|
+
results[index] = await fn(items[index]);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
await Promise.all(workers);
|
|
342
|
+
return results;
|
|
343
|
+
}
|
|
344
|
+
function maxThinkingLevelFor(endpoint) {
|
|
345
|
+
return endpoint.kind === "ollama" ? "max" : "high";
|
|
346
|
+
}
|
|
347
|
+
function toModelInfo(model, endpoint) {
|
|
348
|
+
return {
|
|
349
|
+
id: formatLocalModelId(model.endpointId, model.rawId),
|
|
350
|
+
name: `${model.rawId} (${endpoint.label})`,
|
|
351
|
+
provider: "local",
|
|
352
|
+
contextWindow: model.contextWindow,
|
|
353
|
+
// Leave real headroom for the prompt on small local windows.
|
|
354
|
+
maxOutputTokens: Math.max(512, Math.min(4096, Math.floor(model.contextWindow / 4))),
|
|
355
|
+
supportsThinking: model.supportsThinking,
|
|
356
|
+
supportsImages: model.supportsImages,
|
|
357
|
+
supportsVideo: false,
|
|
358
|
+
costTier: "low",
|
|
359
|
+
maxThinkingLevel: maxThinkingLevelFor(endpoint),
|
|
360
|
+
authStorageKeys: [localAuthStorageKey(model.endpointId)]
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
var cache;
|
|
364
|
+
function cacheKey(endpoints) {
|
|
365
|
+
return endpoints.map((e) => `${e.id}@${e.baseUrl}`).join("|");
|
|
366
|
+
}
|
|
367
|
+
async function discoverLocalModels(endpoints = DEFAULT_LOCAL_ENDPOINTS, options = {}) {
|
|
368
|
+
const key = cacheKey(endpoints);
|
|
369
|
+
if (!options.force && cache && cache.key === key && Date.now() - cache.at < CACHE_TTL_MS) {
|
|
370
|
+
return cache.result;
|
|
371
|
+
}
|
|
372
|
+
const probes = await Promise.all(endpoints.map((endpoint) => probeEndpoint(endpoint, options)));
|
|
373
|
+
const models = probes.flatMap(
|
|
374
|
+
(probe) => probe.models.map((model) => toModelInfo(model, probe.endpoint))
|
|
375
|
+
);
|
|
376
|
+
const result = { probes, models };
|
|
377
|
+
cache = { key, at: Date.now(), result };
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
function clearLocalDiscoveryCache() {
|
|
381
|
+
cache = void 0;
|
|
382
|
+
}
|
|
383
|
+
function findProbedModel(probes, modelId) {
|
|
384
|
+
const parsed = parseLocalModelId(modelId);
|
|
385
|
+
if (!parsed) return void 0;
|
|
386
|
+
for (const probe of probes) {
|
|
387
|
+
if (probe.endpoint.id !== parsed.endpointId) continue;
|
|
388
|
+
const model = probe.models.find((m) => m.rawId === parsed.rawId);
|
|
389
|
+
if (model) return { model, endpoint: probe.endpoint };
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
|
|
125
394
|
// src/provider-usage.ts
|
|
126
395
|
var SubscriptionUsageError = class extends Error {
|
|
127
|
-
constructor(message, status) {
|
|
396
|
+
constructor(message, status, retryAfterMs2) {
|
|
128
397
|
super(message);
|
|
129
398
|
this.status = status;
|
|
399
|
+
this.retryAfterMs = retryAfterMs2;
|
|
130
400
|
this.name = "SubscriptionUsageError";
|
|
131
401
|
}
|
|
132
402
|
};
|
|
@@ -176,12 +446,20 @@ function normalizedCodexWindow(window, fallbackKind, now) {
|
|
|
176
446
|
resetsAt: codexResetAt(window, now)
|
|
177
447
|
};
|
|
178
448
|
}
|
|
179
|
-
|
|
449
|
+
function retryAfterMs(response, now) {
|
|
450
|
+
const value = response.headers.get("retry-after")?.trim();
|
|
451
|
+
if (!value) return void 0;
|
|
452
|
+
if (/^\d+(?:\.\d+)?$/.test(value)) return Math.ceil(Number(value) * 1e3);
|
|
453
|
+
const retryAt = Date.parse(value);
|
|
454
|
+
return Number.isFinite(retryAt) ? Math.max(0, retryAt - now) : void 0;
|
|
455
|
+
}
|
|
456
|
+
async function readUsageResponse(response, now) {
|
|
180
457
|
const text = await response.text();
|
|
181
458
|
if (!response.ok) {
|
|
182
459
|
throw new SubscriptionUsageError(
|
|
183
460
|
`Subscription usage request failed with HTTP ${response.status}`,
|
|
184
|
-
response.status
|
|
461
|
+
response.status,
|
|
462
|
+
retryAfterMs(response, now)
|
|
185
463
|
);
|
|
186
464
|
}
|
|
187
465
|
try {
|
|
@@ -202,7 +480,7 @@ async function fetchAnthropicUsage(credentials, fetchFn, signal, now) {
|
|
|
202
480
|
"User-Agent": "ezcoder"
|
|
203
481
|
}
|
|
204
482
|
});
|
|
205
|
-
const data = await readUsageResponse(response);
|
|
483
|
+
const data = await readUsageResponse(response, now());
|
|
206
484
|
const windows = [];
|
|
207
485
|
const currentPercent = clampPercent(data.five_hour?.utilization);
|
|
208
486
|
if (currentPercent !== void 0) {
|
|
@@ -238,7 +516,7 @@ async function fetchCodexUsage(credentials, fetchFn, signal, now) {
|
|
|
238
516
|
};
|
|
239
517
|
if (credentials.accountId) headers["ChatGPT-Account-Id"] = credentials.accountId;
|
|
240
518
|
const response = await fetchFn(CODEX_USAGE_URL, { method: "GET", signal, headers });
|
|
241
|
-
const data = await readUsageResponse(response);
|
|
519
|
+
const data = await readUsageResponse(response, now());
|
|
242
520
|
const windows = [
|
|
243
521
|
normalizedCodexWindow(data.rate_limit?.primary_window, "current", now()),
|
|
244
522
|
normalizedCodexWindow(data.rate_limit?.secondary_window, "weekly", now())
|
|
@@ -254,12 +532,97 @@ async function fetchCodexUsage(credentials, fetchFn, signal, now) {
|
|
|
254
532
|
fetchedAt: now()
|
|
255
533
|
};
|
|
256
534
|
}
|
|
535
|
+
function kimiNumber(value) {
|
|
536
|
+
if (typeof value === "string" && value.trim()) {
|
|
537
|
+
const parsed = Number(value);
|
|
538
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
539
|
+
}
|
|
540
|
+
return finiteNumber(value);
|
|
541
|
+
}
|
|
542
|
+
function kimiUsagePercent(detail) {
|
|
543
|
+
const used = kimiNumber(detail?.used);
|
|
544
|
+
const limit = kimiNumber(detail?.limit);
|
|
545
|
+
if (used === void 0 || limit === void 0 || limit <= 0) return void 0;
|
|
546
|
+
return Math.min(100, Math.max(0, used / limit * 100));
|
|
547
|
+
}
|
|
548
|
+
function kimiWindowMinutes(window) {
|
|
549
|
+
const duration = kimiNumber(window?.duration);
|
|
550
|
+
if (duration === void 0 || duration <= 0) return void 0;
|
|
551
|
+
switch (window?.timeUnit) {
|
|
552
|
+
case "TIME_UNIT_SECOND":
|
|
553
|
+
return duration / 60;
|
|
554
|
+
case "TIME_UNIT_MINUTE":
|
|
555
|
+
return duration;
|
|
556
|
+
case "TIME_UNIT_HOUR":
|
|
557
|
+
return duration * 60;
|
|
558
|
+
case "TIME_UNIT_DAY":
|
|
559
|
+
return duration * 24 * 60;
|
|
560
|
+
default:
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async function fetchKimiUsage(credentials, fetchFn, signal, now) {
|
|
565
|
+
const response = await fetchFn(`${kimiCodeBaseUrl()}/usages`, {
|
|
566
|
+
method: "GET",
|
|
567
|
+
signal,
|
|
568
|
+
headers: {
|
|
569
|
+
Authorization: "Bearer " + credentials.accessToken,
|
|
570
|
+
Accept: "application/json",
|
|
571
|
+
// The managed coding endpoint gates on the kimi-code-cli client identity,
|
|
572
|
+
// same as model requests.
|
|
573
|
+
...kimiCodingHeaders()
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
const data = await readUsageResponse(response, now());
|
|
577
|
+
const windows = [];
|
|
578
|
+
const weeklyPercent = kimiUsagePercent(data.usage);
|
|
579
|
+
if (weeklyPercent !== void 0) {
|
|
580
|
+
windows.push({
|
|
581
|
+
kind: "weekly",
|
|
582
|
+
label: "Weekly",
|
|
583
|
+
usedPercent: weeklyPercent,
|
|
584
|
+
resetsAt: isoTimestamp(data.usage?.resetTime)
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
for (const limit of data.limits ?? []) {
|
|
588
|
+
const minutes = kimiWindowMinutes(limit?.window);
|
|
589
|
+
const percent = kimiUsagePercent(limit?.detail);
|
|
590
|
+
if (minutes === void 0 || percent === void 0) continue;
|
|
591
|
+
if (minutes >= 6 * 24 * 60) {
|
|
592
|
+
if (!windows.some((window) => window.kind === "weekly")) {
|
|
593
|
+
windows.push({
|
|
594
|
+
kind: "weekly",
|
|
595
|
+
label: "Weekly",
|
|
596
|
+
usedPercent: percent,
|
|
597
|
+
resetsAt: isoTimestamp(limit?.detail?.resetTime)
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
windows.push({
|
|
603
|
+
kind: "current",
|
|
604
|
+
label: `${Math.max(1, Math.round(minutes / 60))}-hour`,
|
|
605
|
+
usedPercent: percent,
|
|
606
|
+
resetsAt: isoTimestamp(limit?.detail?.resetTime)
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
windows.sort((left, right) => {
|
|
610
|
+
if (left.kind === right.kind) return 0;
|
|
611
|
+
return left.kind === "current" ? -1 : 1;
|
|
612
|
+
});
|
|
613
|
+
return {
|
|
614
|
+
provider: "moonshot",
|
|
615
|
+
displayName: "Kimi",
|
|
616
|
+
windows,
|
|
617
|
+
fetchedAt: now()
|
|
618
|
+
};
|
|
619
|
+
}
|
|
257
620
|
async function fetchSubscriptionUsage(provider, credentials, options = {}) {
|
|
258
621
|
const fetchFn = options.fetchFn ?? fetch;
|
|
259
622
|
const now = options.now ?? Date.now;
|
|
260
623
|
const signal = AbortSignal.timeout(options.timeoutMs ?? 8e3);
|
|
261
624
|
try {
|
|
262
|
-
return provider === "anthropic" ? await fetchAnthropicUsage(credentials, fetchFn, signal, now) : await fetchCodexUsage(credentials, fetchFn, signal, now);
|
|
625
|
+
return provider === "anthropic" ? await fetchAnthropicUsage(credentials, fetchFn, signal, now) : provider === "openai" ? await fetchCodexUsage(credentials, fetchFn, signal, now) : await fetchKimiUsage(credentials, fetchFn, signal, now);
|
|
263
626
|
} catch (error) {
|
|
264
627
|
if (error instanceof SubscriptionUsageError) throw error;
|
|
265
628
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -744,19 +1107,30 @@ function createAutoUpdater(config) {
|
|
|
744
1107
|
}
|
|
745
1108
|
export {
|
|
746
1109
|
AuthStorage,
|
|
1110
|
+
DEFAULT_LOCAL_ENDPOINTS,
|
|
747
1111
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
1112
|
+
FALLBACK_CONTEXT_WINDOW,
|
|
1113
|
+
LOCAL_API_KEY_PLACEHOLDER,
|
|
1114
|
+
LOCAL_AUTH_KEY_PREFIX,
|
|
748
1115
|
MODELS,
|
|
749
1116
|
MOONSHOT_OAUTH_KEY,
|
|
750
1117
|
NotLoggedInError,
|
|
751
1118
|
SubscriptionUsageError,
|
|
752
1119
|
TelegramBot,
|
|
753
1120
|
XIAOMI_CREDITS_KEY,
|
|
1121
|
+
clearLocalDiscoveryCache,
|
|
1122
|
+
clearRuntimeModels,
|
|
754
1123
|
closeLogger,
|
|
755
1124
|
createAutoUpdater,
|
|
756
1125
|
decodeOggOpus,
|
|
1126
|
+
discoverLocalModels,
|
|
757
1127
|
downmixToMono,
|
|
1128
|
+
endpointRoot,
|
|
758
1129
|
fetchSubscriptionUsage,
|
|
1130
|
+
findProbedModel,
|
|
1131
|
+
formatLocalModelId,
|
|
759
1132
|
generatePKCE,
|
|
1133
|
+
getAllModels,
|
|
760
1134
|
getAppPaths,
|
|
761
1135
|
getAuthStorageKey,
|
|
762
1136
|
getAuthStorageKeys,
|
|
@@ -764,6 +1138,7 @@ export {
|
|
|
764
1138
|
getClaudeCodeVersion,
|
|
765
1139
|
getContextWindow,
|
|
766
1140
|
getDefaultModel,
|
|
1141
|
+
getDefaultThinkingLevel,
|
|
767
1142
|
getFastModel,
|
|
768
1143
|
getMaxThinkingLevel,
|
|
769
1144
|
getModel,
|
|
@@ -775,24 +1150,31 @@ export {
|
|
|
775
1150
|
getToolResultCharLimit,
|
|
776
1151
|
getVideoByteLimit,
|
|
777
1152
|
isKimiCodingEndpoint,
|
|
1153
|
+
isLocalModelId,
|
|
778
1154
|
isLoggerOpen,
|
|
779
1155
|
isModelLoaded,
|
|
780
1156
|
isThinkingLevelSupported,
|
|
781
1157
|
kimiCodeBaseUrl,
|
|
782
1158
|
kimiCodingHeaders,
|
|
1159
|
+
localAuthStorageKey,
|
|
783
1160
|
log,
|
|
784
1161
|
loginAnthropic,
|
|
785
1162
|
loginGemini,
|
|
786
1163
|
loginKimi,
|
|
787
1164
|
loginOpenAI,
|
|
788
1165
|
openLog,
|
|
1166
|
+
parseLocalModelId,
|
|
1167
|
+
probeEndpoint,
|
|
1168
|
+
readStoredBaseUrlSync,
|
|
789
1169
|
refreshAnthropicToken,
|
|
790
1170
|
refreshGeminiToken,
|
|
791
1171
|
refreshKimiToken,
|
|
792
1172
|
refreshOpenAIToken,
|
|
793
1173
|
registerLogCleanup,
|
|
1174
|
+
registerRuntimeModels,
|
|
794
1175
|
resample,
|
|
795
1176
|
setProgressCallback,
|
|
1177
|
+
toModelInfo,
|
|
796
1178
|
transcribeVoice,
|
|
797
1179
|
usesOpenAICodexTransport,
|
|
798
1180
|
withFileLock
|