@prestyj/core 5.9.0 → 5.11.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-WCIV25KV.js → chunk-CRU3SSNX.js} +2 -1
- package/dist/chunk-CRU3SSNX.js.map +1 -0
- package/dist/{chunk-JXPW4JKV.js → chunk-WAG5K2MH.js} +85 -11
- package/dist/chunk-WAG5K2MH.js.map +1 -0
- package/dist/index.cjs +378 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -2
- package/dist/index.d.ts +139 -2
- package/dist/index.js +287 -8
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +47 -5
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +12 -3
- package/dist/model-registry.d.ts +12 -3
- package/dist/model-registry.js +8 -2
- package/dist/paths.cjs +1 -0
- package/dist/paths.cjs.map +1 -1
- package/dist/paths.d.cts +2 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-JXPW4JKV.js.map +0 -1
- package/dist/chunk-WCIV25KV.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -31,19 +31,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AuthStorage: () => AuthStorage,
|
|
34
|
+
DEFAULT_LOCAL_ENDPOINTS: () => DEFAULT_LOCAL_ENDPOINTS,
|
|
34
35
|
DEFAULT_MAX_VIDEO_BYTES: () => DEFAULT_MAX_VIDEO_BYTES,
|
|
36
|
+
FALLBACK_CONTEXT_WINDOW: () => FALLBACK_CONTEXT_WINDOW,
|
|
37
|
+
LOCAL_API_KEY_PLACEHOLDER: () => LOCAL_API_KEY_PLACEHOLDER,
|
|
38
|
+
LOCAL_AUTH_KEY_PREFIX: () => LOCAL_AUTH_KEY_PREFIX,
|
|
35
39
|
MODELS: () => MODELS,
|
|
36
40
|
MOONSHOT_OAUTH_KEY: () => MOONSHOT_OAUTH_KEY,
|
|
37
41
|
NotLoggedInError: () => NotLoggedInError,
|
|
38
42
|
SubscriptionUsageError: () => SubscriptionUsageError,
|
|
39
43
|
TelegramBot: () => TelegramBot,
|
|
40
44
|
XIAOMI_CREDITS_KEY: () => XIAOMI_CREDITS_KEY,
|
|
45
|
+
clearLocalDiscoveryCache: () => clearLocalDiscoveryCache,
|
|
46
|
+
clearRuntimeModels: () => clearRuntimeModels,
|
|
41
47
|
closeLogger: () => closeLogger,
|
|
42
48
|
createAutoUpdater: () => createAutoUpdater,
|
|
43
49
|
decodeOggOpus: () => decodeOggOpus,
|
|
50
|
+
discoverLocalModels: () => discoverLocalModels,
|
|
44
51
|
downmixToMono: () => downmixToMono,
|
|
52
|
+
endpointRoot: () => endpointRoot,
|
|
45
53
|
fetchSubscriptionUsage: () => fetchSubscriptionUsage,
|
|
54
|
+
findProbedModel: () => findProbedModel,
|
|
55
|
+
formatLocalModelId: () => formatLocalModelId,
|
|
46
56
|
generatePKCE: () => generatePKCE,
|
|
57
|
+
getAllModels: () => getAllModels,
|
|
47
58
|
getAppPaths: () => getAppPaths,
|
|
48
59
|
getAuthStorageKey: () => getAuthStorageKey,
|
|
49
60
|
getAuthStorageKeys: () => getAuthStorageKeys,
|
|
@@ -63,25 +74,31 @@ __export(index_exports, {
|
|
|
63
74
|
getToolResultCharLimit: () => getToolResultCharLimit,
|
|
64
75
|
getVideoByteLimit: () => getVideoByteLimit,
|
|
65
76
|
isKimiCodingEndpoint: () => isKimiCodingEndpoint,
|
|
77
|
+
isLocalModelId: () => isLocalModelId,
|
|
66
78
|
isLoggerOpen: () => isLoggerOpen,
|
|
67
79
|
isModelLoaded: () => isModelLoaded,
|
|
68
80
|
isThinkingLevelSupported: () => isThinkingLevelSupported,
|
|
69
81
|
kimiCodeBaseUrl: () => kimiCodeBaseUrl,
|
|
70
82
|
kimiCodingHeaders: () => kimiCodingHeaders,
|
|
83
|
+
localAuthStorageKey: () => localAuthStorageKey,
|
|
71
84
|
log: () => log,
|
|
72
85
|
loginAnthropic: () => loginAnthropic,
|
|
73
86
|
loginGemini: () => loginGemini,
|
|
74
87
|
loginKimi: () => loginKimi,
|
|
75
88
|
loginOpenAI: () => loginOpenAI,
|
|
76
89
|
openLog: () => openLog,
|
|
90
|
+
parseLocalModelId: () => parseLocalModelId,
|
|
91
|
+
probeEndpoint: () => probeEndpoint,
|
|
77
92
|
readStoredBaseUrlSync: () => readStoredBaseUrlSync,
|
|
78
93
|
refreshAnthropicToken: () => refreshAnthropicToken,
|
|
79
94
|
refreshGeminiToken: () => refreshGeminiToken,
|
|
80
95
|
refreshKimiToken: () => refreshKimiToken,
|
|
81
96
|
refreshOpenAIToken: () => refreshOpenAIToken,
|
|
82
97
|
registerLogCleanup: () => registerLogCleanup,
|
|
98
|
+
registerRuntimeModels: () => registerRuntimeModels,
|
|
83
99
|
resample: () => resample,
|
|
84
100
|
setProgressCallback: () => setProgressCallback,
|
|
101
|
+
toModelInfo: () => toModelInfo,
|
|
85
102
|
transcribeVoice: () => transcribeVoice,
|
|
86
103
|
usesOpenAICodexTransport: () => usesOpenAICodexTransport,
|
|
87
104
|
withFileLock: () => withFileLock
|
|
@@ -111,6 +128,7 @@ function getAppPaths() {
|
|
|
111
128
|
agentHomeFile: import_node_path.default.join(agentDir, "agent-home.json"),
|
|
112
129
|
mcpFile: import_node_path.default.join(agentDir, "mcp.json"),
|
|
113
130
|
mcpAuthFile: import_node_path.default.join(agentDir, "mcp-auth.json"),
|
|
131
|
+
mcpCatalogFile: import_node_path.default.join(agentDir, "mcp-catalog.json"),
|
|
114
132
|
logFile: import_node_path.default.join(agentDir, "debug.log"),
|
|
115
133
|
skillsDir: import_node_path.default.join(agentDir, "skills"),
|
|
116
134
|
extensionsDir: import_node_path.default.join(agentDir, "extensions"),
|
|
@@ -239,6 +257,7 @@ function credsFromTokenResponse(data, opts) {
|
|
|
239
257
|
accessToken,
|
|
240
258
|
refreshToken,
|
|
241
259
|
expiresAt: Date.now() + expiresIn * 1e3,
|
|
260
|
+
expiresIn,
|
|
242
261
|
baseUrl: kimiCodeBaseUrl()
|
|
243
262
|
};
|
|
244
263
|
}
|
|
@@ -1271,6 +1290,8 @@ function isAlive(pid) {
|
|
|
1271
1290
|
// src/auth-storage.ts
|
|
1272
1291
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1273
1292
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1293
|
+
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1294
|
+
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1274
1295
|
function activeBaseUrlEntry(data, provider) {
|
|
1275
1296
|
if (provider === "moonshot") {
|
|
1276
1297
|
const oauth = data[MOONSHOT_OAUTH_KEY];
|
|
@@ -1291,7 +1312,12 @@ function readStoredBaseUrlSync(authFile, provider) {
|
|
|
1291
1312
|
return void 0;
|
|
1292
1313
|
}
|
|
1293
1314
|
}
|
|
1294
|
-
var
|
|
1315
|
+
var MIN_REFRESH_THRESHOLD_MS = 3e5;
|
|
1316
|
+
var REFRESH_THRESHOLD_RATIO = 0.5;
|
|
1317
|
+
function refreshThresholdMs(creds) {
|
|
1318
|
+
const lifetimeMs = (creds.expiresIn ?? 0) * 1e3;
|
|
1319
|
+
return Math.max(MIN_REFRESH_THRESHOLD_MS, lifetimeMs * REFRESH_THRESHOLD_RATIO);
|
|
1320
|
+
}
|
|
1295
1321
|
var USAGE_EXHAUSTED_DEFAULT_MS = 15 * 60 * 1e3;
|
|
1296
1322
|
var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1297
1323
|
"glm",
|
|
@@ -1301,7 +1327,9 @@ var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
|
1301
1327
|
"deepseek",
|
|
1302
1328
|
"openrouter",
|
|
1303
1329
|
"sakana",
|
|
1304
|
-
"xai"
|
|
1330
|
+
"xai",
|
|
1331
|
+
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
1332
|
+
"local"
|
|
1305
1333
|
]);
|
|
1306
1334
|
var AuthStorage = class {
|
|
1307
1335
|
data = {};
|
|
@@ -1350,8 +1378,33 @@ var AuthStorage = class {
|
|
|
1350
1378
|
if (provider === "xiaomi") {
|
|
1351
1379
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
1352
1380
|
}
|
|
1381
|
+
if (provider === "local") {
|
|
1382
|
+
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
1383
|
+
}
|
|
1353
1384
|
return Boolean(this.data[provider]);
|
|
1354
1385
|
}
|
|
1386
|
+
/** Endpoint ids that currently have a `local:<id>` credential stored. */
|
|
1387
|
+
async listLocalEndpointIds() {
|
|
1388
|
+
await this.ensureLoaded();
|
|
1389
|
+
return Object.keys(this.data).filter((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX)).map((key) => key.slice(LOCAL_AUTH_KEY_PREFIX.length));
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Write (or refresh) the credential for one local endpoint. The `baseUrl` is
|
|
1393
|
+
* what `effectiveBaseUrl` later picks up, and `accessToken` is the endpoint's
|
|
1394
|
+
* key — a placeholder for the servers that ignore it.
|
|
1395
|
+
*/
|
|
1396
|
+
async setLocalEndpoint(endpointId, baseUrl, apiKey) {
|
|
1397
|
+
await this.setCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`, {
|
|
1398
|
+
accessToken: apiKey && apiKey.length > 0 ? apiKey : "local",
|
|
1399
|
+
refreshToken: "",
|
|
1400
|
+
expiresAt: Date.now() + LOCAL_CREDENTIAL_LIFETIME_MS,
|
|
1401
|
+
baseUrl
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/** Remove one local endpoint's credential. No-op when it isn't stored. */
|
|
1405
|
+
async removeLocalEndpoint(endpointId) {
|
|
1406
|
+
await this.clearCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`);
|
|
1407
|
+
}
|
|
1355
1408
|
/**
|
|
1356
1409
|
* True if the active credential for `provider` is a static API key with no
|
|
1357
1410
|
* refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
|
|
@@ -1534,7 +1587,7 @@ var AuthStorage = class {
|
|
|
1534
1587
|
if (STATIC_API_KEY_PROVIDERS.has(provider)) {
|
|
1535
1588
|
return creds;
|
|
1536
1589
|
}
|
|
1537
|
-
if (!opts?.forceRefresh && Date.now() < creds.expiresAt -
|
|
1590
|
+
if (!opts?.forceRefresh && Date.now() < creds.expiresAt - refreshThresholdMs(creds)) {
|
|
1538
1591
|
return creds;
|
|
1539
1592
|
}
|
|
1540
1593
|
const existing = this.refreshLocks.get(provider);
|
|
@@ -1547,7 +1600,7 @@ var AuthStorage = class {
|
|
|
1547
1600
|
throw new NotLoggedInError(provider);
|
|
1548
1601
|
}
|
|
1549
1602
|
const credentialWasReplaced = latestCreds.accessToken !== creds.accessToken || latestCreds.refreshToken !== creds.refreshToken || latestCreds.expiresAt !== creds.expiresAt;
|
|
1550
|
-
if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt -
|
|
1603
|
+
if (credentialWasReplaced || !opts?.forceRefresh && Date.now() < latestCreds.expiresAt - refreshThresholdMs(latestCreds)) {
|
|
1551
1604
|
this.data = latest;
|
|
1552
1605
|
return latestCreds;
|
|
1553
1606
|
}
|
|
@@ -1659,8 +1712,12 @@ var MODELS = [
|
|
|
1659
1712
|
// maxThinkingLevel: "max",
|
|
1660
1713
|
// },
|
|
1661
1714
|
{
|
|
1662
|
-
|
|
1663
|
-
|
|
1715
|
+
// Released 2026-07-24 — "For complex agentic coding and enterprise work".
|
|
1716
|
+
// Near-Fable capability at half the price ($5/$25 vs $10/$50). Adaptive
|
|
1717
|
+
// thinking with the full effort ladder (low→max, xhigh included); dateless
|
|
1718
|
+
// ID is the canonical pinned snapshot (post-4.6 naming scheme).
|
|
1719
|
+
id: "claude-opus-5",
|
|
1720
|
+
name: "Claude Opus 5",
|
|
1664
1721
|
provider: "anthropic",
|
|
1665
1722
|
contextWindow: 1e6,
|
|
1666
1723
|
maxOutputTokens: 128e3,
|
|
@@ -2046,14 +2103,30 @@ var MODELS = [
|
|
|
2046
2103
|
maxThinkingLevel: "high"
|
|
2047
2104
|
}
|
|
2048
2105
|
];
|
|
2106
|
+
var runtimeModels = /* @__PURE__ */ new Map();
|
|
2107
|
+
function registerRuntimeModels(models) {
|
|
2108
|
+
for (const model of models) runtimeModels.set(model.id, model);
|
|
2109
|
+
}
|
|
2110
|
+
function clearRuntimeModels(predicate) {
|
|
2111
|
+
if (!predicate) {
|
|
2112
|
+
runtimeModels.clear();
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
for (const [id, model] of runtimeModels) {
|
|
2116
|
+
if (predicate(model)) runtimeModels.delete(id);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function getAllModels() {
|
|
2120
|
+
return [...MODELS, ...runtimeModels.values()];
|
|
2121
|
+
}
|
|
2049
2122
|
function getModel(id) {
|
|
2050
|
-
return MODELS.find((m) => m.id === id);
|
|
2123
|
+
return MODELS.find((m) => m.id === id) ?? runtimeModels.get(id);
|
|
2051
2124
|
}
|
|
2052
2125
|
function getModelsForProvider(provider) {
|
|
2053
|
-
return
|
|
2126
|
+
return getAllModels().filter((m) => m.provider === provider);
|
|
2054
2127
|
}
|
|
2055
2128
|
function getAuthStorageKeys(provider, modelId) {
|
|
2056
|
-
const model =
|
|
2129
|
+
const model = getAllModels().find((m) => m.id === modelId && m.provider === provider);
|
|
2057
2130
|
return model?.authStorageKeys ?? [provider];
|
|
2058
2131
|
}
|
|
2059
2132
|
function getAuthStorageKey(provider, modelId) {
|
|
@@ -2076,8 +2149,23 @@ function getDefaultModel(provider) {
|
|
|
2076
2149
|
if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
|
|
2077
2150
|
if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
|
|
2078
2151
|
if (provider === "xai") return MODELS.find((m) => m.id === "grok-4.5");
|
|
2152
|
+
if (provider === "local") {
|
|
2153
|
+
return getModelsForProvider("local")[0] ?? PLACEHOLDER_LOCAL_MODEL;
|
|
2154
|
+
}
|
|
2079
2155
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
2080
2156
|
}
|
|
2157
|
+
var PLACEHOLDER_LOCAL_MODEL = {
|
|
2158
|
+
id: "local/none/none",
|
|
2159
|
+
name: "No local model discovered",
|
|
2160
|
+
provider: "local",
|
|
2161
|
+
contextWindow: 8192,
|
|
2162
|
+
maxOutputTokens: 2048,
|
|
2163
|
+
supportsThinking: false,
|
|
2164
|
+
supportsImages: false,
|
|
2165
|
+
supportsVideo: false,
|
|
2166
|
+
costTier: "low",
|
|
2167
|
+
maxThinkingLevel: "high"
|
|
2168
|
+
};
|
|
2081
2169
|
function usesOpenAICodexTransport(options) {
|
|
2082
2170
|
return options?.provider === "openai" && Boolean(options.accountId);
|
|
2083
2171
|
}
|
|
@@ -2127,7 +2215,7 @@ var OPENAI_GPT_56_THINKING_LEVELS = [
|
|
|
2127
2215
|
];
|
|
2128
2216
|
var SAKANA_THINKING_LEVELS = ["high", "xhigh"];
|
|
2129
2217
|
var XAI_THINKING_LEVELS = ["low", "medium", "high"];
|
|
2130
|
-
var
|
|
2218
|
+
var ANTHROPIC_XHIGH_THINKING_LEVELS = [
|
|
2131
2219
|
"low",
|
|
2132
2220
|
"medium",
|
|
2133
2221
|
"high",
|
|
@@ -2141,6 +2229,7 @@ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
|
|
|
2141
2229
|
"max"
|
|
2142
2230
|
];
|
|
2143
2231
|
var MOONSHOT_K3_THINKING_LEVELS = ["low", "high", "max"];
|
|
2232
|
+
var LOCAL_THINKING_LEVELS = ["low", "medium", "high", "max"];
|
|
2144
2233
|
function isOpenAIGptModel(provider, model) {
|
|
2145
2234
|
return provider === "openai" && model.startsWith("gpt-");
|
|
2146
2235
|
}
|
|
@@ -2153,16 +2242,22 @@ function isXaiModel(provider) {
|
|
|
2153
2242
|
function isMoonshotK3Model(provider, model) {
|
|
2154
2243
|
return provider === "moonshot" && model === "kimi-k3";
|
|
2155
2244
|
}
|
|
2156
|
-
function
|
|
2157
|
-
return provider === "anthropic" && /opus-4-8|opus-4-7/.test(model);
|
|
2245
|
+
function isAnthropicXhighModel(provider, model) {
|
|
2246
|
+
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7/.test(model);
|
|
2158
2247
|
}
|
|
2159
2248
|
function isAnthropicAdaptiveModel(provider, model) {
|
|
2160
|
-
return provider === "anthropic" && /opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
2249
|
+
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
2161
2250
|
}
|
|
2162
2251
|
function getSupportedThinkingLevels(provider, model) {
|
|
2252
|
+
if (provider === "local") {
|
|
2253
|
+
const info = getModel(model);
|
|
2254
|
+
if (!info?.supportsThinking) return [];
|
|
2255
|
+
const maxIndex2 = LOCAL_THINKING_LEVELS.indexOf(info.maxThinkingLevel);
|
|
2256
|
+
return maxIndex2 === -1 ? LOCAL_THINKING_LEVELS.slice(0, 3) : LOCAL_THINKING_LEVELS.slice(0, maxIndex2 + 1);
|
|
2257
|
+
}
|
|
2163
2258
|
const maxLevel = getMaxThinkingLevel(model);
|
|
2164
2259
|
if (isAnthropicAdaptiveModel(provider, model)) {
|
|
2165
|
-
const levels2 =
|
|
2260
|
+
const levels2 = isAnthropicXhighModel(provider, model) ? ANTHROPIC_XHIGH_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
|
|
2166
2261
|
const maxIndex2 = levels2.indexOf(maxLevel);
|
|
2167
2262
|
if (maxIndex2 === -1) return ["low", "medium", "high"];
|
|
2168
2263
|
return levels2.slice(0, maxIndex2 + 1);
|
|
@@ -2189,7 +2284,11 @@ function isThinkingLevelSupported(provider, model, level) {
|
|
|
2189
2284
|
}
|
|
2190
2285
|
function getNextThinkingLevel(provider, model, current) {
|
|
2191
2286
|
const supportedLevels = getSupportedThinkingLevels(provider, model);
|
|
2192
|
-
const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model)
|
|
2287
|
+
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
|
|
2288
|
+
// low/medium/high on `reasoning_effort` (verified against 0.32) and the
|
|
2289
|
+
// other OpenAI-compatible servers use the same three. A model that can't
|
|
2290
|
+
// reason at all already has no supported levels, so it never gets here.
|
|
2291
|
+
provider === "local";
|
|
2193
2292
|
if (!shouldCycleLevels) {
|
|
2194
2293
|
return current ? void 0 : supportedLevels[0];
|
|
2195
2294
|
}
|
|
@@ -2199,6 +2298,253 @@ function getNextThinkingLevel(provider, model, current) {
|
|
|
2199
2298
|
return supportedLevels[index + 1];
|
|
2200
2299
|
}
|
|
2201
2300
|
|
|
2301
|
+
// src/local-models.ts
|
|
2302
|
+
var DEFAULT_LOCAL_ENDPOINTS = [
|
|
2303
|
+
{ id: "ollama", label: "Ollama", baseUrl: "http://127.0.0.1:11434/v1", kind: "ollama" },
|
|
2304
|
+
{ id: "lmstudio", label: "LM Studio", baseUrl: "http://127.0.0.1:1234/v1", kind: "lmstudio" },
|
|
2305
|
+
{ id: "llamacpp", label: "llama.cpp", baseUrl: "http://127.0.0.1:8080/v1", kind: "llamacpp" },
|
|
2306
|
+
{ id: "vllm", label: "vLLM", baseUrl: "http://127.0.0.1:8000/v1", kind: "vllm" }
|
|
2307
|
+
];
|
|
2308
|
+
var FALLBACK_CONTEXT_WINDOW = 8192;
|
|
2309
|
+
var LOCAL_API_KEY_PLACEHOLDER = "local";
|
|
2310
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 1200;
|
|
2311
|
+
var ENRICH_CONCURRENCY = 6;
|
|
2312
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
2313
|
+
var NON_CHAT_ID_PATTERN = /(?:^|[-_/])(?:embed|embedding|rerank|reranker|bge|nomic-embed)/i;
|
|
2314
|
+
var LOCAL_ID_PREFIX = "local/";
|
|
2315
|
+
function formatLocalModelId(endpointId, rawId) {
|
|
2316
|
+
return `${LOCAL_ID_PREFIX}${endpointId}/${rawId}`;
|
|
2317
|
+
}
|
|
2318
|
+
function parseLocalModelId(id) {
|
|
2319
|
+
if (!id.startsWith(LOCAL_ID_PREFIX)) return void 0;
|
|
2320
|
+
const rest = id.slice(LOCAL_ID_PREFIX.length);
|
|
2321
|
+
const slash = rest.indexOf("/");
|
|
2322
|
+
if (slash <= 0 || slash === rest.length - 1) return void 0;
|
|
2323
|
+
return { endpointId: rest.slice(0, slash), rawId: rest.slice(slash + 1) };
|
|
2324
|
+
}
|
|
2325
|
+
function isLocalModelId(id) {
|
|
2326
|
+
return parseLocalModelId(id) !== void 0;
|
|
2327
|
+
}
|
|
2328
|
+
function localAuthStorageKey(endpointId) {
|
|
2329
|
+
return `local:${endpointId}`;
|
|
2330
|
+
}
|
|
2331
|
+
function endpointRoot(baseUrl) {
|
|
2332
|
+
return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
2333
|
+
}
|
|
2334
|
+
function authHeaders(endpoint) {
|
|
2335
|
+
return {
|
|
2336
|
+
Authorization: `Bearer ${endpoint.apiKey ?? LOCAL_API_KEY_PLACEHOLDER}`,
|
|
2337
|
+
Accept: "application/json"
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
async function fetchJson(url, endpoint, options) {
|
|
2341
|
+
try {
|
|
2342
|
+
const res = await fetchJsonOrThrow(url, endpoint, options);
|
|
2343
|
+
return res;
|
|
2344
|
+
} catch {
|
|
2345
|
+
return void 0;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
async function fetchJsonOrThrow(url, endpoint, { timeoutMs, signal, method = "GET", body }) {
|
|
2349
|
+
const controller = new AbortController();
|
|
2350
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2351
|
+
const onAbort = () => controller.abort();
|
|
2352
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2353
|
+
try {
|
|
2354
|
+
const res = await fetch(url, {
|
|
2355
|
+
method,
|
|
2356
|
+
signal: controller.signal,
|
|
2357
|
+
headers: body ? { ...authHeaders(endpoint), "Content-Type": "application/json" } : authHeaders(endpoint),
|
|
2358
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
2359
|
+
});
|
|
2360
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2361
|
+
return await res.json();
|
|
2362
|
+
} finally {
|
|
2363
|
+
clearTimeout(timer);
|
|
2364
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
function unreachableReason(endpoint, err) {
|
|
2368
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2369
|
+
if (/abort/i.test(message)) return `No response from ${endpoint.baseUrl} (timed out)`;
|
|
2370
|
+
if (/HTTP 401|HTTP 403/.test(message)) {
|
|
2371
|
+
return `${endpoint.baseUrl} rejected the API key (HTTP ${message.includes("401") ? 401 : 403})`;
|
|
2372
|
+
}
|
|
2373
|
+
if (/HTTP \d+/.test(message)) return `${endpoint.baseUrl} returned ${message}`;
|
|
2374
|
+
return `Not running at ${endpoint.baseUrl}`;
|
|
2375
|
+
}
|
|
2376
|
+
async function probeEndpoint(endpoint, { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = {}) {
|
|
2377
|
+
const listUrl = `${endpoint.baseUrl.replace(/\/+$/, "")}/models`;
|
|
2378
|
+
let list;
|
|
2379
|
+
try {
|
|
2380
|
+
list = await fetchJsonOrThrow(listUrl, endpoint, { timeoutMs, signal });
|
|
2381
|
+
} catch (err) {
|
|
2382
|
+
return { endpoint, reachable: false, reason: unreachableReason(endpoint, err), models: [] };
|
|
2383
|
+
}
|
|
2384
|
+
const entries = (list.data ?? []).filter(
|
|
2385
|
+
(entry) => typeof entry.id === "string" && entry.id.length > 0 && !NON_CHAT_ID_PATTERN.test(entry.id)
|
|
2386
|
+
);
|
|
2387
|
+
const models = await enrich(endpoint, entries, { timeoutMs, signal });
|
|
2388
|
+
log("INFO", "local-models", `Probed ${endpoint.label}`, {
|
|
2389
|
+
baseUrl: endpoint.baseUrl,
|
|
2390
|
+
models: String(models.length)
|
|
2391
|
+
});
|
|
2392
|
+
return { endpoint, reachable: true, models };
|
|
2393
|
+
}
|
|
2394
|
+
async function enrich(endpoint, entries, options) {
|
|
2395
|
+
if (endpoint.kind === "lmstudio") return enrichLmStudio(endpoint, entries, options);
|
|
2396
|
+
if (endpoint.kind === "ollama") return enrichOllama(endpoint, entries, options);
|
|
2397
|
+
if (endpoint.kind === "llamacpp") return enrichLlamaCpp(endpoint, entries, options);
|
|
2398
|
+
return entries.map((entry) => genericModel(endpoint, entry));
|
|
2399
|
+
}
|
|
2400
|
+
function genericModel(endpoint, entry) {
|
|
2401
|
+
const declared = typeof entry.max_model_len === "number" ? entry.max_model_len : void 0;
|
|
2402
|
+
return {
|
|
2403
|
+
rawId: entry.id,
|
|
2404
|
+
endpointId: endpoint.id,
|
|
2405
|
+
contextWindow: declared ?? FALLBACK_CONTEXT_WINDOW,
|
|
2406
|
+
contextWindowKnown: declared !== void 0,
|
|
2407
|
+
supportsTools: true,
|
|
2408
|
+
supportsImages: false,
|
|
2409
|
+
supportsThinking: false
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
async function enrichOllama(endpoint, entries, options) {
|
|
2413
|
+
const showUrl = `${endpointRoot(endpoint.baseUrl)}/api/show`;
|
|
2414
|
+
const enriched = await mapLimited(entries, ENRICH_CONCURRENCY, async (entry) => {
|
|
2415
|
+
const show = await fetchJson(showUrl, endpoint, {
|
|
2416
|
+
...options,
|
|
2417
|
+
method: "POST",
|
|
2418
|
+
body: { model: entry.id }
|
|
2419
|
+
});
|
|
2420
|
+
if (!show) return genericModel(endpoint, entry);
|
|
2421
|
+
const caps = show.capabilities ?? [];
|
|
2422
|
+
if (caps.includes("embedding") && !caps.includes("completion")) return void 0;
|
|
2423
|
+
const ctx = ollamaContextLength(show.model_info);
|
|
2424
|
+
return {
|
|
2425
|
+
rawId: entry.id,
|
|
2426
|
+
endpointId: endpoint.id,
|
|
2427
|
+
contextWindow: ctx ?? FALLBACK_CONTEXT_WINDOW,
|
|
2428
|
+
contextWindowKnown: ctx !== void 0,
|
|
2429
|
+
// Ollama reports capabilities honestly, so trust it here rather than
|
|
2430
|
+
// using the optimistic generic default.
|
|
2431
|
+
supportsTools: caps.includes("tools"),
|
|
2432
|
+
supportsImages: caps.includes("vision"),
|
|
2433
|
+
supportsThinking: caps.includes("thinking")
|
|
2434
|
+
};
|
|
2435
|
+
});
|
|
2436
|
+
return enriched.filter((model) => model !== void 0);
|
|
2437
|
+
}
|
|
2438
|
+
function ollamaContextLength(info) {
|
|
2439
|
+
if (!info) return void 0;
|
|
2440
|
+
for (const [key, value] of Object.entries(info)) {
|
|
2441
|
+
if (key.endsWith(".context_length") && typeof value === "number" && value > 0) return value;
|
|
2442
|
+
}
|
|
2443
|
+
return void 0;
|
|
2444
|
+
}
|
|
2445
|
+
async function enrichLmStudio(endpoint, entries, options) {
|
|
2446
|
+
const detail = await fetchJson(
|
|
2447
|
+
`${endpointRoot(endpoint.baseUrl)}/api/v0/models`,
|
|
2448
|
+
endpoint,
|
|
2449
|
+
options
|
|
2450
|
+
);
|
|
2451
|
+
if (!detail?.data) return entries.map((entry) => genericModel(endpoint, entry));
|
|
2452
|
+
const byId = new Map(detail.data.filter((m) => m.id).map((m) => [m.id, m]));
|
|
2453
|
+
const models = [];
|
|
2454
|
+
for (const entry of entries) {
|
|
2455
|
+
const info = byId.get(entry.id);
|
|
2456
|
+
if (info && info.type !== "llm" && info.type !== "vlm") continue;
|
|
2457
|
+
const ctx = info?.max_context_length;
|
|
2458
|
+
models.push({
|
|
2459
|
+
rawId: entry.id,
|
|
2460
|
+
endpointId: endpoint.id,
|
|
2461
|
+
contextWindow: typeof ctx === "number" && ctx > 0 ? ctx : FALLBACK_CONTEXT_WINDOW,
|
|
2462
|
+
contextWindowKnown: typeof ctx === "number" && ctx > 0,
|
|
2463
|
+
// LM Studio doesn't report tool support; it gates per-model at request time.
|
|
2464
|
+
supportsTools: true,
|
|
2465
|
+
supportsImages: info?.type === "vlm",
|
|
2466
|
+
supportsThinking: false,
|
|
2467
|
+
loaded: info?.state === "loaded"
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
return models;
|
|
2471
|
+
}
|
|
2472
|
+
async function enrichLlamaCpp(endpoint, entries, options) {
|
|
2473
|
+
const props = await fetchJson(
|
|
2474
|
+
`${endpointRoot(endpoint.baseUrl)}/props`,
|
|
2475
|
+
endpoint,
|
|
2476
|
+
options
|
|
2477
|
+
);
|
|
2478
|
+
const nCtx = props?.default_generation_settings?.n_ctx;
|
|
2479
|
+
const known = typeof nCtx === "number" && nCtx > 0;
|
|
2480
|
+
return entries.map((entry) => ({
|
|
2481
|
+
...genericModel(endpoint, entry),
|
|
2482
|
+
contextWindow: known ? nCtx : FALLBACK_CONTEXT_WINDOW,
|
|
2483
|
+
contextWindowKnown: known
|
|
2484
|
+
}));
|
|
2485
|
+
}
|
|
2486
|
+
async function mapLimited(items, limit, fn) {
|
|
2487
|
+
const results = new Array(items.length);
|
|
2488
|
+
let next = 0;
|
|
2489
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
2490
|
+
while (next < items.length) {
|
|
2491
|
+
const index = next++;
|
|
2492
|
+
results[index] = await fn(items[index]);
|
|
2493
|
+
}
|
|
2494
|
+
});
|
|
2495
|
+
await Promise.all(workers);
|
|
2496
|
+
return results;
|
|
2497
|
+
}
|
|
2498
|
+
function maxThinkingLevelFor(endpoint) {
|
|
2499
|
+
return endpoint.kind === "ollama" ? "max" : "high";
|
|
2500
|
+
}
|
|
2501
|
+
function toModelInfo(model, endpoint) {
|
|
2502
|
+
return {
|
|
2503
|
+
id: formatLocalModelId(model.endpointId, model.rawId),
|
|
2504
|
+
name: `${model.rawId} (${endpoint.label})`,
|
|
2505
|
+
provider: "local",
|
|
2506
|
+
contextWindow: model.contextWindow,
|
|
2507
|
+
// Leave real headroom for the prompt on small local windows.
|
|
2508
|
+
maxOutputTokens: Math.max(512, Math.min(4096, Math.floor(model.contextWindow / 4))),
|
|
2509
|
+
supportsThinking: model.supportsThinking,
|
|
2510
|
+
supportsImages: model.supportsImages,
|
|
2511
|
+
supportsVideo: false,
|
|
2512
|
+
costTier: "low",
|
|
2513
|
+
maxThinkingLevel: maxThinkingLevelFor(endpoint),
|
|
2514
|
+
authStorageKeys: [localAuthStorageKey(model.endpointId)]
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
var cache;
|
|
2518
|
+
function cacheKey(endpoints) {
|
|
2519
|
+
return endpoints.map((e) => `${e.id}@${e.baseUrl}`).join("|");
|
|
2520
|
+
}
|
|
2521
|
+
async function discoverLocalModels(endpoints = DEFAULT_LOCAL_ENDPOINTS, options = {}) {
|
|
2522
|
+
const key = cacheKey(endpoints);
|
|
2523
|
+
if (!options.force && cache && cache.key === key && Date.now() - cache.at < CACHE_TTL_MS2) {
|
|
2524
|
+
return cache.result;
|
|
2525
|
+
}
|
|
2526
|
+
const probes = await Promise.all(endpoints.map((endpoint) => probeEndpoint(endpoint, options)));
|
|
2527
|
+
const models = probes.flatMap(
|
|
2528
|
+
(probe) => probe.models.map((model) => toModelInfo(model, probe.endpoint))
|
|
2529
|
+
);
|
|
2530
|
+
const result = { probes, models };
|
|
2531
|
+
cache = { key, at: Date.now(), result };
|
|
2532
|
+
return result;
|
|
2533
|
+
}
|
|
2534
|
+
function clearLocalDiscoveryCache() {
|
|
2535
|
+
cache = void 0;
|
|
2536
|
+
}
|
|
2537
|
+
function findProbedModel(probes, modelId) {
|
|
2538
|
+
const parsed = parseLocalModelId(modelId);
|
|
2539
|
+
if (!parsed) return void 0;
|
|
2540
|
+
for (const probe of probes) {
|
|
2541
|
+
if (probe.endpoint.id !== parsed.endpointId) continue;
|
|
2542
|
+
const model = probe.models.find((m) => m.rawId === parsed.rawId);
|
|
2543
|
+
if (model) return { model, endpoint: probe.endpoint };
|
|
2544
|
+
}
|
|
2545
|
+
return void 0;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2202
2548
|
// src/provider-usage.ts
|
|
2203
2549
|
var SubscriptionUsageError = class extends Error {
|
|
2204
2550
|
constructor(message, status, retryAfterMs2) {
|
|
@@ -2916,19 +3262,30 @@ function createAutoUpdater(config) {
|
|
|
2916
3262
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2917
3263
|
0 && (module.exports = {
|
|
2918
3264
|
AuthStorage,
|
|
3265
|
+
DEFAULT_LOCAL_ENDPOINTS,
|
|
2919
3266
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
3267
|
+
FALLBACK_CONTEXT_WINDOW,
|
|
3268
|
+
LOCAL_API_KEY_PLACEHOLDER,
|
|
3269
|
+
LOCAL_AUTH_KEY_PREFIX,
|
|
2920
3270
|
MODELS,
|
|
2921
3271
|
MOONSHOT_OAUTH_KEY,
|
|
2922
3272
|
NotLoggedInError,
|
|
2923
3273
|
SubscriptionUsageError,
|
|
2924
3274
|
TelegramBot,
|
|
2925
3275
|
XIAOMI_CREDITS_KEY,
|
|
3276
|
+
clearLocalDiscoveryCache,
|
|
3277
|
+
clearRuntimeModels,
|
|
2926
3278
|
closeLogger,
|
|
2927
3279
|
createAutoUpdater,
|
|
2928
3280
|
decodeOggOpus,
|
|
3281
|
+
discoverLocalModels,
|
|
2929
3282
|
downmixToMono,
|
|
3283
|
+
endpointRoot,
|
|
2930
3284
|
fetchSubscriptionUsage,
|
|
3285
|
+
findProbedModel,
|
|
3286
|
+
formatLocalModelId,
|
|
2931
3287
|
generatePKCE,
|
|
3288
|
+
getAllModels,
|
|
2932
3289
|
getAppPaths,
|
|
2933
3290
|
getAuthStorageKey,
|
|
2934
3291
|
getAuthStorageKeys,
|
|
@@ -2948,25 +3305,31 @@ function createAutoUpdater(config) {
|
|
|
2948
3305
|
getToolResultCharLimit,
|
|
2949
3306
|
getVideoByteLimit,
|
|
2950
3307
|
isKimiCodingEndpoint,
|
|
3308
|
+
isLocalModelId,
|
|
2951
3309
|
isLoggerOpen,
|
|
2952
3310
|
isModelLoaded,
|
|
2953
3311
|
isThinkingLevelSupported,
|
|
2954
3312
|
kimiCodeBaseUrl,
|
|
2955
3313
|
kimiCodingHeaders,
|
|
3314
|
+
localAuthStorageKey,
|
|
2956
3315
|
log,
|
|
2957
3316
|
loginAnthropic,
|
|
2958
3317
|
loginGemini,
|
|
2959
3318
|
loginKimi,
|
|
2960
3319
|
loginOpenAI,
|
|
2961
3320
|
openLog,
|
|
3321
|
+
parseLocalModelId,
|
|
3322
|
+
probeEndpoint,
|
|
2962
3323
|
readStoredBaseUrlSync,
|
|
2963
3324
|
refreshAnthropicToken,
|
|
2964
3325
|
refreshGeminiToken,
|
|
2965
3326
|
refreshKimiToken,
|
|
2966
3327
|
refreshOpenAIToken,
|
|
2967
3328
|
registerLogCleanup,
|
|
3329
|
+
registerRuntimeModels,
|
|
2968
3330
|
resample,
|
|
2969
3331
|
setProgressCallback,
|
|
3332
|
+
toModelInfo,
|
|
2970
3333
|
transcribeVoice,
|
|
2971
3334
|
usesOpenAICodexTransport,
|
|
2972
3335
|
withFileLock
|