@kenkaiiii/gg-core 5.24.0 → 5.26.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-7OICWYUV.js → chunk-6OH2XAFL.js} +69 -5
- package/dist/chunk-6OH2XAFL.js.map +1 -0
- package/dist/index.cjs +357 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -2
- package/dist/index.d.ts +135 -2
- package/dist/index.js +281 -2
- package/dist/index.js.map +1 -1
- package/dist/model-registry.cjs +41 -3
- package/dist/model-registry.cjs.map +1 -1
- package/dist/model-registry.d.cts +10 -1
- package/dist/model-registry.d.ts +10 -1
- package/dist/model-registry.js +7 -1
- package/package.json +2 -2
- package/dist/chunk-7OICWYUV.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
|
|
@@ -1272,6 +1289,8 @@ function isAlive(pid) {
|
|
|
1272
1289
|
// src/auth-storage.ts
|
|
1273
1290
|
var MOONSHOT_OAUTH_KEY = "moonshot-oauth";
|
|
1274
1291
|
var XIAOMI_CREDITS_KEY = "xiaomi-credits";
|
|
1292
|
+
var LOCAL_AUTH_KEY_PREFIX = "local:";
|
|
1293
|
+
var LOCAL_CREDENTIAL_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1e3;
|
|
1275
1294
|
function activeBaseUrlEntry(data, provider) {
|
|
1276
1295
|
if (provider === "moonshot") {
|
|
1277
1296
|
const oauth = data[MOONSHOT_OAUTH_KEY];
|
|
@@ -1307,7 +1326,9 @@ var STATIC_API_KEY_PROVIDERS = /* @__PURE__ */ new Set([
|
|
|
1307
1326
|
"deepseek",
|
|
1308
1327
|
"openrouter",
|
|
1309
1328
|
"sakana",
|
|
1310
|
-
"xai"
|
|
1329
|
+
"xai",
|
|
1330
|
+
// Local endpoints: a fixed (usually placeholder) key, never refreshable.
|
|
1331
|
+
"local"
|
|
1311
1332
|
]);
|
|
1312
1333
|
var AuthStorage = class {
|
|
1313
1334
|
data = {};
|
|
@@ -1356,8 +1377,33 @@ var AuthStorage = class {
|
|
|
1356
1377
|
if (provider === "xiaomi") {
|
|
1357
1378
|
return Boolean(this.data["xiaomi"] || this.data[XIAOMI_CREDITS_KEY]);
|
|
1358
1379
|
}
|
|
1380
|
+
if (provider === "local") {
|
|
1381
|
+
return Object.keys(this.data).some((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX));
|
|
1382
|
+
}
|
|
1359
1383
|
return Boolean(this.data[provider]);
|
|
1360
1384
|
}
|
|
1385
|
+
/** Endpoint ids that currently have a `local:<id>` credential stored. */
|
|
1386
|
+
async listLocalEndpointIds() {
|
|
1387
|
+
await this.ensureLoaded();
|
|
1388
|
+
return Object.keys(this.data).filter((key) => key.startsWith(LOCAL_AUTH_KEY_PREFIX)).map((key) => key.slice(LOCAL_AUTH_KEY_PREFIX.length));
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Write (or refresh) the credential for one local endpoint. The `baseUrl` is
|
|
1392
|
+
* what `effectiveBaseUrl` later picks up, and `accessToken` is the endpoint's
|
|
1393
|
+
* key — a placeholder for the servers that ignore it.
|
|
1394
|
+
*/
|
|
1395
|
+
async setLocalEndpoint(endpointId, baseUrl, apiKey) {
|
|
1396
|
+
await this.setCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`, {
|
|
1397
|
+
accessToken: apiKey && apiKey.length > 0 ? apiKey : "local",
|
|
1398
|
+
refreshToken: "",
|
|
1399
|
+
expiresAt: Date.now() + LOCAL_CREDENTIAL_LIFETIME_MS,
|
|
1400
|
+
baseUrl
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
/** Remove one local endpoint's credential. No-op when it isn't stored. */
|
|
1404
|
+
async removeLocalEndpoint(endpointId) {
|
|
1405
|
+
await this.clearCredentials(`${LOCAL_AUTH_KEY_PREFIX}${endpointId}`);
|
|
1406
|
+
}
|
|
1361
1407
|
/**
|
|
1362
1408
|
* True if the active credential for `provider` is a static API key with no
|
|
1363
1409
|
* refresh mechanism. For `moonshot` this is only true when the Kimi OAuth
|
|
@@ -2047,14 +2093,30 @@ var MODELS = [
|
|
|
2047
2093
|
maxThinkingLevel: "high"
|
|
2048
2094
|
}
|
|
2049
2095
|
];
|
|
2096
|
+
var runtimeModels = /* @__PURE__ */ new Map();
|
|
2097
|
+
function registerRuntimeModels(models) {
|
|
2098
|
+
for (const model of models) runtimeModels.set(model.id, model);
|
|
2099
|
+
}
|
|
2100
|
+
function clearRuntimeModels(predicate) {
|
|
2101
|
+
if (!predicate) {
|
|
2102
|
+
runtimeModels.clear();
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
for (const [id, model] of runtimeModels) {
|
|
2106
|
+
if (predicate(model)) runtimeModels.delete(id);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
function getAllModels() {
|
|
2110
|
+
return [...MODELS, ...runtimeModels.values()];
|
|
2111
|
+
}
|
|
2050
2112
|
function getModel(id) {
|
|
2051
|
-
return MODELS.find((m) => m.id === id);
|
|
2113
|
+
return MODELS.find((m) => m.id === id) ?? runtimeModels.get(id);
|
|
2052
2114
|
}
|
|
2053
2115
|
function getModelsForProvider(provider) {
|
|
2054
|
-
return
|
|
2116
|
+
return getAllModels().filter((m) => m.provider === provider);
|
|
2055
2117
|
}
|
|
2056
2118
|
function getAuthStorageKeys(provider, modelId) {
|
|
2057
|
-
const model =
|
|
2119
|
+
const model = getAllModels().find((m) => m.id === modelId && m.provider === provider);
|
|
2058
2120
|
return model?.authStorageKeys ?? [provider];
|
|
2059
2121
|
}
|
|
2060
2122
|
function getAuthStorageKey(provider, modelId) {
|
|
@@ -2077,8 +2139,23 @@ function getDefaultModel(provider) {
|
|
|
2077
2139
|
if (provider === "openrouter") return MODELS.find((m) => m.id === "qwen/qwen3.6-plus");
|
|
2078
2140
|
if (provider === "sakana") return MODELS.find((m) => m.id === "fugu");
|
|
2079
2141
|
if (provider === "xai") return MODELS.find((m) => m.id === "grok-4.5");
|
|
2142
|
+
if (provider === "local") {
|
|
2143
|
+
return getModelsForProvider("local")[0] ?? PLACEHOLDER_LOCAL_MODEL;
|
|
2144
|
+
}
|
|
2080
2145
|
return MODELS.find((m) => m.id === "claude-sonnet-5");
|
|
2081
2146
|
}
|
|
2147
|
+
var PLACEHOLDER_LOCAL_MODEL = {
|
|
2148
|
+
id: "local/none/none",
|
|
2149
|
+
name: "No local model discovered",
|
|
2150
|
+
provider: "local",
|
|
2151
|
+
contextWindow: 8192,
|
|
2152
|
+
maxOutputTokens: 2048,
|
|
2153
|
+
supportsThinking: false,
|
|
2154
|
+
supportsImages: false,
|
|
2155
|
+
supportsVideo: false,
|
|
2156
|
+
costTier: "low",
|
|
2157
|
+
maxThinkingLevel: "high"
|
|
2158
|
+
};
|
|
2082
2159
|
function usesOpenAICodexTransport(options) {
|
|
2083
2160
|
return options?.provider === "openai" && Boolean(options.accountId);
|
|
2084
2161
|
}
|
|
@@ -2142,6 +2219,7 @@ var ANTHROPIC_ADAPTIVE_THINKING_LEVELS = [
|
|
|
2142
2219
|
"max"
|
|
2143
2220
|
];
|
|
2144
2221
|
var MOONSHOT_K3_THINKING_LEVELS = ["low", "high", "max"];
|
|
2222
|
+
var LOCAL_THINKING_LEVELS = ["low", "medium", "high", "max"];
|
|
2145
2223
|
function isOpenAIGptModel(provider, model) {
|
|
2146
2224
|
return provider === "openai" && model.startsWith("gpt-");
|
|
2147
2225
|
}
|
|
@@ -2161,6 +2239,12 @@ function isAnthropicAdaptiveModel(provider, model) {
|
|
|
2161
2239
|
return provider === "anthropic" && /opus-5|opus-4-8|opus-4-7|opus-4-6|sonnet-5|fable-5|mythos-5/.test(model);
|
|
2162
2240
|
}
|
|
2163
2241
|
function getSupportedThinkingLevels(provider, model) {
|
|
2242
|
+
if (provider === "local") {
|
|
2243
|
+
const info = getModel(model);
|
|
2244
|
+
if (!info?.supportsThinking) return [];
|
|
2245
|
+
const maxIndex2 = LOCAL_THINKING_LEVELS.indexOf(info.maxThinkingLevel);
|
|
2246
|
+
return maxIndex2 === -1 ? LOCAL_THINKING_LEVELS.slice(0, 3) : LOCAL_THINKING_LEVELS.slice(0, maxIndex2 + 1);
|
|
2247
|
+
}
|
|
2164
2248
|
const maxLevel = getMaxThinkingLevel(model);
|
|
2165
2249
|
if (isAnthropicAdaptiveModel(provider, model)) {
|
|
2166
2250
|
const levels2 = isAnthropicXhighModel(provider, model) ? ANTHROPIC_XHIGH_THINKING_LEVELS : ANTHROPIC_ADAPTIVE_THINKING_LEVELS;
|
|
@@ -2190,7 +2274,11 @@ function isThinkingLevelSupported(provider, model, level) {
|
|
|
2190
2274
|
}
|
|
2191
2275
|
function getNextThinkingLevel(provider, model, current) {
|
|
2192
2276
|
const supportedLevels = getSupportedThinkingLevels(provider, model);
|
|
2193
|
-
const shouldCycleLevels = isOpenAIGptModel(provider, model) || isAnthropicAdaptiveModel(provider, model) || isSakanaModel(provider) || isXaiModel(provider) || isMoonshotK3Model(provider, model)
|
|
2277
|
+
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
|
|
2278
|
+
// low/medium/high on `reasoning_effort` (verified against 0.32) and the
|
|
2279
|
+
// other OpenAI-compatible servers use the same three. A model that can't
|
|
2280
|
+
// reason at all already has no supported levels, so it never gets here.
|
|
2281
|
+
provider === "local";
|
|
2194
2282
|
if (!shouldCycleLevels) {
|
|
2195
2283
|
return current ? void 0 : supportedLevels[0];
|
|
2196
2284
|
}
|
|
@@ -2200,6 +2288,253 @@ function getNextThinkingLevel(provider, model, current) {
|
|
|
2200
2288
|
return supportedLevels[index + 1];
|
|
2201
2289
|
}
|
|
2202
2290
|
|
|
2291
|
+
// src/local-models.ts
|
|
2292
|
+
var DEFAULT_LOCAL_ENDPOINTS = [
|
|
2293
|
+
{ id: "ollama", label: "Ollama", baseUrl: "http://127.0.0.1:11434/v1", kind: "ollama" },
|
|
2294
|
+
{ id: "lmstudio", label: "LM Studio", baseUrl: "http://127.0.0.1:1234/v1", kind: "lmstudio" },
|
|
2295
|
+
{ id: "llamacpp", label: "llama.cpp", baseUrl: "http://127.0.0.1:8080/v1", kind: "llamacpp" },
|
|
2296
|
+
{ id: "vllm", label: "vLLM", baseUrl: "http://127.0.0.1:8000/v1", kind: "vllm" }
|
|
2297
|
+
];
|
|
2298
|
+
var FALLBACK_CONTEXT_WINDOW = 8192;
|
|
2299
|
+
var LOCAL_API_KEY_PLACEHOLDER = "local";
|
|
2300
|
+
var DEFAULT_PROBE_TIMEOUT_MS = 1200;
|
|
2301
|
+
var ENRICH_CONCURRENCY = 6;
|
|
2302
|
+
var CACHE_TTL_MS2 = 3e4;
|
|
2303
|
+
var NON_CHAT_ID_PATTERN = /(?:^|[-_/])(?:embed|embedding|rerank|reranker|bge|nomic-embed)/i;
|
|
2304
|
+
var LOCAL_ID_PREFIX = "local/";
|
|
2305
|
+
function formatLocalModelId(endpointId, rawId) {
|
|
2306
|
+
return `${LOCAL_ID_PREFIX}${endpointId}/${rawId}`;
|
|
2307
|
+
}
|
|
2308
|
+
function parseLocalModelId(id) {
|
|
2309
|
+
if (!id.startsWith(LOCAL_ID_PREFIX)) return void 0;
|
|
2310
|
+
const rest = id.slice(LOCAL_ID_PREFIX.length);
|
|
2311
|
+
const slash = rest.indexOf("/");
|
|
2312
|
+
if (slash <= 0 || slash === rest.length - 1) return void 0;
|
|
2313
|
+
return { endpointId: rest.slice(0, slash), rawId: rest.slice(slash + 1) };
|
|
2314
|
+
}
|
|
2315
|
+
function isLocalModelId(id) {
|
|
2316
|
+
return parseLocalModelId(id) !== void 0;
|
|
2317
|
+
}
|
|
2318
|
+
function localAuthStorageKey(endpointId) {
|
|
2319
|
+
return `local:${endpointId}`;
|
|
2320
|
+
}
|
|
2321
|
+
function endpointRoot(baseUrl) {
|
|
2322
|
+
return baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
2323
|
+
}
|
|
2324
|
+
function authHeaders(endpoint) {
|
|
2325
|
+
return {
|
|
2326
|
+
Authorization: `Bearer ${endpoint.apiKey ?? LOCAL_API_KEY_PLACEHOLDER}`,
|
|
2327
|
+
Accept: "application/json"
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
async function fetchJson(url, endpoint, options) {
|
|
2331
|
+
try {
|
|
2332
|
+
const res = await fetchJsonOrThrow(url, endpoint, options);
|
|
2333
|
+
return res;
|
|
2334
|
+
} catch {
|
|
2335
|
+
return void 0;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
async function fetchJsonOrThrow(url, endpoint, { timeoutMs, signal, method = "GET", body }) {
|
|
2339
|
+
const controller = new AbortController();
|
|
2340
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
2341
|
+
const onAbort = () => controller.abort();
|
|
2342
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2343
|
+
try {
|
|
2344
|
+
const res = await fetch(url, {
|
|
2345
|
+
method,
|
|
2346
|
+
signal: controller.signal,
|
|
2347
|
+
headers: body ? { ...authHeaders(endpoint), "Content-Type": "application/json" } : authHeaders(endpoint),
|
|
2348
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
2349
|
+
});
|
|
2350
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2351
|
+
return await res.json();
|
|
2352
|
+
} finally {
|
|
2353
|
+
clearTimeout(timer);
|
|
2354
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
function unreachableReason(endpoint, err) {
|
|
2358
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2359
|
+
if (/abort/i.test(message)) return `No response from ${endpoint.baseUrl} (timed out)`;
|
|
2360
|
+
if (/HTTP 401|HTTP 403/.test(message)) {
|
|
2361
|
+
return `${endpoint.baseUrl} rejected the API key (HTTP ${message.includes("401") ? 401 : 403})`;
|
|
2362
|
+
}
|
|
2363
|
+
if (/HTTP \d+/.test(message)) return `${endpoint.baseUrl} returned ${message}`;
|
|
2364
|
+
return `Not running at ${endpoint.baseUrl}`;
|
|
2365
|
+
}
|
|
2366
|
+
async function probeEndpoint(endpoint, { timeoutMs = DEFAULT_PROBE_TIMEOUT_MS, signal } = {}) {
|
|
2367
|
+
const listUrl = `${endpoint.baseUrl.replace(/\/+$/, "")}/models`;
|
|
2368
|
+
let list;
|
|
2369
|
+
try {
|
|
2370
|
+
list = await fetchJsonOrThrow(listUrl, endpoint, { timeoutMs, signal });
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
return { endpoint, reachable: false, reason: unreachableReason(endpoint, err), models: [] };
|
|
2373
|
+
}
|
|
2374
|
+
const entries = (list.data ?? []).filter(
|
|
2375
|
+
(entry) => typeof entry.id === "string" && entry.id.length > 0 && !NON_CHAT_ID_PATTERN.test(entry.id)
|
|
2376
|
+
);
|
|
2377
|
+
const models = await enrich(endpoint, entries, { timeoutMs, signal });
|
|
2378
|
+
log("INFO", "local-models", `Probed ${endpoint.label}`, {
|
|
2379
|
+
baseUrl: endpoint.baseUrl,
|
|
2380
|
+
models: String(models.length)
|
|
2381
|
+
});
|
|
2382
|
+
return { endpoint, reachable: true, models };
|
|
2383
|
+
}
|
|
2384
|
+
async function enrich(endpoint, entries, options) {
|
|
2385
|
+
if (endpoint.kind === "lmstudio") return enrichLmStudio(endpoint, entries, options);
|
|
2386
|
+
if (endpoint.kind === "ollama") return enrichOllama(endpoint, entries, options);
|
|
2387
|
+
if (endpoint.kind === "llamacpp") return enrichLlamaCpp(endpoint, entries, options);
|
|
2388
|
+
return entries.map((entry) => genericModel(endpoint, entry));
|
|
2389
|
+
}
|
|
2390
|
+
function genericModel(endpoint, entry) {
|
|
2391
|
+
const declared = typeof entry.max_model_len === "number" ? entry.max_model_len : void 0;
|
|
2392
|
+
return {
|
|
2393
|
+
rawId: entry.id,
|
|
2394
|
+
endpointId: endpoint.id,
|
|
2395
|
+
contextWindow: declared ?? FALLBACK_CONTEXT_WINDOW,
|
|
2396
|
+
contextWindowKnown: declared !== void 0,
|
|
2397
|
+
supportsTools: true,
|
|
2398
|
+
supportsImages: false,
|
|
2399
|
+
supportsThinking: false
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
async function enrichOllama(endpoint, entries, options) {
|
|
2403
|
+
const showUrl = `${endpointRoot(endpoint.baseUrl)}/api/show`;
|
|
2404
|
+
const enriched = await mapLimited(entries, ENRICH_CONCURRENCY, async (entry) => {
|
|
2405
|
+
const show = await fetchJson(showUrl, endpoint, {
|
|
2406
|
+
...options,
|
|
2407
|
+
method: "POST",
|
|
2408
|
+
body: { model: entry.id }
|
|
2409
|
+
});
|
|
2410
|
+
if (!show) return genericModel(endpoint, entry);
|
|
2411
|
+
const caps = show.capabilities ?? [];
|
|
2412
|
+
if (caps.includes("embedding") && !caps.includes("completion")) return void 0;
|
|
2413
|
+
const ctx = ollamaContextLength(show.model_info);
|
|
2414
|
+
return {
|
|
2415
|
+
rawId: entry.id,
|
|
2416
|
+
endpointId: endpoint.id,
|
|
2417
|
+
contextWindow: ctx ?? FALLBACK_CONTEXT_WINDOW,
|
|
2418
|
+
contextWindowKnown: ctx !== void 0,
|
|
2419
|
+
// Ollama reports capabilities honestly, so trust it here rather than
|
|
2420
|
+
// using the optimistic generic default.
|
|
2421
|
+
supportsTools: caps.includes("tools"),
|
|
2422
|
+
supportsImages: caps.includes("vision"),
|
|
2423
|
+
supportsThinking: caps.includes("thinking")
|
|
2424
|
+
};
|
|
2425
|
+
});
|
|
2426
|
+
return enriched.filter((model) => model !== void 0);
|
|
2427
|
+
}
|
|
2428
|
+
function ollamaContextLength(info) {
|
|
2429
|
+
if (!info) return void 0;
|
|
2430
|
+
for (const [key, value] of Object.entries(info)) {
|
|
2431
|
+
if (key.endsWith(".context_length") && typeof value === "number" && value > 0) return value;
|
|
2432
|
+
}
|
|
2433
|
+
return void 0;
|
|
2434
|
+
}
|
|
2435
|
+
async function enrichLmStudio(endpoint, entries, options) {
|
|
2436
|
+
const detail = await fetchJson(
|
|
2437
|
+
`${endpointRoot(endpoint.baseUrl)}/api/v0/models`,
|
|
2438
|
+
endpoint,
|
|
2439
|
+
options
|
|
2440
|
+
);
|
|
2441
|
+
if (!detail?.data) return entries.map((entry) => genericModel(endpoint, entry));
|
|
2442
|
+
const byId = new Map(detail.data.filter((m) => m.id).map((m) => [m.id, m]));
|
|
2443
|
+
const models = [];
|
|
2444
|
+
for (const entry of entries) {
|
|
2445
|
+
const info = byId.get(entry.id);
|
|
2446
|
+
if (info && info.type !== "llm" && info.type !== "vlm") continue;
|
|
2447
|
+
const ctx = info?.max_context_length;
|
|
2448
|
+
models.push({
|
|
2449
|
+
rawId: entry.id,
|
|
2450
|
+
endpointId: endpoint.id,
|
|
2451
|
+
contextWindow: typeof ctx === "number" && ctx > 0 ? ctx : FALLBACK_CONTEXT_WINDOW,
|
|
2452
|
+
contextWindowKnown: typeof ctx === "number" && ctx > 0,
|
|
2453
|
+
// LM Studio doesn't report tool support; it gates per-model at request time.
|
|
2454
|
+
supportsTools: true,
|
|
2455
|
+
supportsImages: info?.type === "vlm",
|
|
2456
|
+
supportsThinking: false,
|
|
2457
|
+
loaded: info?.state === "loaded"
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
return models;
|
|
2461
|
+
}
|
|
2462
|
+
async function enrichLlamaCpp(endpoint, entries, options) {
|
|
2463
|
+
const props = await fetchJson(
|
|
2464
|
+
`${endpointRoot(endpoint.baseUrl)}/props`,
|
|
2465
|
+
endpoint,
|
|
2466
|
+
options
|
|
2467
|
+
);
|
|
2468
|
+
const nCtx = props?.default_generation_settings?.n_ctx;
|
|
2469
|
+
const known = typeof nCtx === "number" && nCtx > 0;
|
|
2470
|
+
return entries.map((entry) => ({
|
|
2471
|
+
...genericModel(endpoint, entry),
|
|
2472
|
+
contextWindow: known ? nCtx : FALLBACK_CONTEXT_WINDOW,
|
|
2473
|
+
contextWindowKnown: known
|
|
2474
|
+
}));
|
|
2475
|
+
}
|
|
2476
|
+
async function mapLimited(items, limit, fn) {
|
|
2477
|
+
const results = new Array(items.length);
|
|
2478
|
+
let next = 0;
|
|
2479
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
2480
|
+
while (next < items.length) {
|
|
2481
|
+
const index = next++;
|
|
2482
|
+
results[index] = await fn(items[index]);
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
await Promise.all(workers);
|
|
2486
|
+
return results;
|
|
2487
|
+
}
|
|
2488
|
+
function maxThinkingLevelFor(endpoint) {
|
|
2489
|
+
return endpoint.kind === "ollama" ? "max" : "high";
|
|
2490
|
+
}
|
|
2491
|
+
function toModelInfo(model, endpoint) {
|
|
2492
|
+
return {
|
|
2493
|
+
id: formatLocalModelId(model.endpointId, model.rawId),
|
|
2494
|
+
name: `${model.rawId} (${endpoint.label})`,
|
|
2495
|
+
provider: "local",
|
|
2496
|
+
contextWindow: model.contextWindow,
|
|
2497
|
+
// Leave real headroom for the prompt on small local windows.
|
|
2498
|
+
maxOutputTokens: Math.max(512, Math.min(4096, Math.floor(model.contextWindow / 4))),
|
|
2499
|
+
supportsThinking: model.supportsThinking,
|
|
2500
|
+
supportsImages: model.supportsImages,
|
|
2501
|
+
supportsVideo: false,
|
|
2502
|
+
costTier: "low",
|
|
2503
|
+
maxThinkingLevel: maxThinkingLevelFor(endpoint),
|
|
2504
|
+
authStorageKeys: [localAuthStorageKey(model.endpointId)]
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
var cache;
|
|
2508
|
+
function cacheKey(endpoints) {
|
|
2509
|
+
return endpoints.map((e) => `${e.id}@${e.baseUrl}`).join("|");
|
|
2510
|
+
}
|
|
2511
|
+
async function discoverLocalModels(endpoints = DEFAULT_LOCAL_ENDPOINTS, options = {}) {
|
|
2512
|
+
const key = cacheKey(endpoints);
|
|
2513
|
+
if (!options.force && cache && cache.key === key && Date.now() - cache.at < CACHE_TTL_MS2) {
|
|
2514
|
+
return cache.result;
|
|
2515
|
+
}
|
|
2516
|
+
const probes = await Promise.all(endpoints.map((endpoint) => probeEndpoint(endpoint, options)));
|
|
2517
|
+
const models = probes.flatMap(
|
|
2518
|
+
(probe) => probe.models.map((model) => toModelInfo(model, probe.endpoint))
|
|
2519
|
+
);
|
|
2520
|
+
const result = { probes, models };
|
|
2521
|
+
cache = { key, at: Date.now(), result };
|
|
2522
|
+
return result;
|
|
2523
|
+
}
|
|
2524
|
+
function clearLocalDiscoveryCache() {
|
|
2525
|
+
cache = void 0;
|
|
2526
|
+
}
|
|
2527
|
+
function findProbedModel(probes, modelId) {
|
|
2528
|
+
const parsed = parseLocalModelId(modelId);
|
|
2529
|
+
if (!parsed) return void 0;
|
|
2530
|
+
for (const probe of probes) {
|
|
2531
|
+
if (probe.endpoint.id !== parsed.endpointId) continue;
|
|
2532
|
+
const model = probe.models.find((m) => m.rawId === parsed.rawId);
|
|
2533
|
+
if (model) return { model, endpoint: probe.endpoint };
|
|
2534
|
+
}
|
|
2535
|
+
return void 0;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2203
2538
|
// src/provider-usage.ts
|
|
2204
2539
|
var SubscriptionUsageError = class extends Error {
|
|
2205
2540
|
constructor(message, status, retryAfterMs2) {
|
|
@@ -2917,19 +3252,30 @@ function createAutoUpdater(config) {
|
|
|
2917
3252
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2918
3253
|
0 && (module.exports = {
|
|
2919
3254
|
AuthStorage,
|
|
3255
|
+
DEFAULT_LOCAL_ENDPOINTS,
|
|
2920
3256
|
DEFAULT_MAX_VIDEO_BYTES,
|
|
3257
|
+
FALLBACK_CONTEXT_WINDOW,
|
|
3258
|
+
LOCAL_API_KEY_PLACEHOLDER,
|
|
3259
|
+
LOCAL_AUTH_KEY_PREFIX,
|
|
2921
3260
|
MODELS,
|
|
2922
3261
|
MOONSHOT_OAUTH_KEY,
|
|
2923
3262
|
NotLoggedInError,
|
|
2924
3263
|
SubscriptionUsageError,
|
|
2925
3264
|
TelegramBot,
|
|
2926
3265
|
XIAOMI_CREDITS_KEY,
|
|
3266
|
+
clearLocalDiscoveryCache,
|
|
3267
|
+
clearRuntimeModels,
|
|
2927
3268
|
closeLogger,
|
|
2928
3269
|
createAutoUpdater,
|
|
2929
3270
|
decodeOggOpus,
|
|
3271
|
+
discoverLocalModels,
|
|
2930
3272
|
downmixToMono,
|
|
3273
|
+
endpointRoot,
|
|
2931
3274
|
fetchSubscriptionUsage,
|
|
3275
|
+
findProbedModel,
|
|
3276
|
+
formatLocalModelId,
|
|
2932
3277
|
generatePKCE,
|
|
3278
|
+
getAllModels,
|
|
2933
3279
|
getAppPaths,
|
|
2934
3280
|
getAuthStorageKey,
|
|
2935
3281
|
getAuthStorageKeys,
|
|
@@ -2949,25 +3295,31 @@ function createAutoUpdater(config) {
|
|
|
2949
3295
|
getToolResultCharLimit,
|
|
2950
3296
|
getVideoByteLimit,
|
|
2951
3297
|
isKimiCodingEndpoint,
|
|
3298
|
+
isLocalModelId,
|
|
2952
3299
|
isLoggerOpen,
|
|
2953
3300
|
isModelLoaded,
|
|
2954
3301
|
isThinkingLevelSupported,
|
|
2955
3302
|
kimiCodeBaseUrl,
|
|
2956
3303
|
kimiCodingHeaders,
|
|
3304
|
+
localAuthStorageKey,
|
|
2957
3305
|
log,
|
|
2958
3306
|
loginAnthropic,
|
|
2959
3307
|
loginGemini,
|
|
2960
3308
|
loginKimi,
|
|
2961
3309
|
loginOpenAI,
|
|
2962
3310
|
openLog,
|
|
3311
|
+
parseLocalModelId,
|
|
3312
|
+
probeEndpoint,
|
|
2963
3313
|
readStoredBaseUrlSync,
|
|
2964
3314
|
refreshAnthropicToken,
|
|
2965
3315
|
refreshGeminiToken,
|
|
2966
3316
|
refreshKimiToken,
|
|
2967
3317
|
refreshOpenAIToken,
|
|
2968
3318
|
registerLogCleanup,
|
|
3319
|
+
registerRuntimeModels,
|
|
2969
3320
|
resample,
|
|
2970
3321
|
setProgressCallback,
|
|
3322
|
+
toModelInfo,
|
|
2971
3323
|
transcribeVoice,
|
|
2972
3324
|
usesOpenAICodexTransport,
|
|
2973
3325
|
withFileLock
|