@mcowger/opencode-plexus 0.8.2 → 0.8.3
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/index.js +81 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ var ENV_BASE_URL = "PLEXUS_BASE_URL";
|
|
|
10
10
|
var ENV_API_KEY = "PLEXUS_API_KEY";
|
|
11
11
|
var MODELS_FETCH_TIMEOUT_MS = 1e4;
|
|
12
12
|
var REFRESH_TTL_MS = 60000;
|
|
13
|
+
var CONFIG_HOOK_REFRESH_BUDGET_MS = 3000;
|
|
13
14
|
var PLACEHOLDER_MODEL_ID = "plexus-unconfigured";
|
|
14
15
|
|
|
15
16
|
// ../plexus-models/src/convert.ts
|
|
@@ -46,18 +47,31 @@ function adjustBaseUrl(baseUrl, preferredApi) {
|
|
|
46
47
|
return stripped;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
var DEFAULT_MODELS_FETCH_TIMEOUT_MS = 1e4;
|
|
51
|
+
async function fetchPlexusModels(apiKey, modelsUrl, timeoutMs = DEFAULT_MODELS_FETCH_TIMEOUT_MS) {
|
|
52
|
+
const controller = new AbortController;
|
|
53
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(modelsUrl, {
|
|
56
|
+
headers: {
|
|
57
|
+
Authorization: `Bearer ${apiKey}`,
|
|
58
|
+
Accept: "application/json"
|
|
59
|
+
},
|
|
60
|
+
signal: controller.signal
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
throw new Error(`Plexus models fetch failed: ${res.status} ${res.statusText}`);
|
|
54
64
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
const raw = await res.json();
|
|
66
|
+
return { models: raw.data ?? [], raw };
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
69
|
+
throw new Error(`Plexus models fetch timed out after ${timeoutMs}ms`);
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
} finally {
|
|
73
|
+
clearTimeout(timer);
|
|
58
74
|
}
|
|
59
|
-
const raw = await res.json();
|
|
60
|
-
return { models: raw.data ?? [], raw };
|
|
61
75
|
}
|
|
62
76
|
// src/cache.ts
|
|
63
77
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -86,24 +100,6 @@ async function getDir(client) {
|
|
|
86
100
|
resolvedDir = fallbackDir();
|
|
87
101
|
return resolvedDir;
|
|
88
102
|
}
|
|
89
|
-
function syncCachePath() {
|
|
90
|
-
return join(fallbackDir(), CACHE_FILE);
|
|
91
|
-
}
|
|
92
|
-
function readCachedModelsSync() {
|
|
93
|
-
try {
|
|
94
|
-
const path = syncCachePath();
|
|
95
|
-
if (!existsSync(path))
|
|
96
|
-
return null;
|
|
97
|
-
const raw = readFileSync(path, "utf8");
|
|
98
|
-
const parsed = JSON.parse(raw);
|
|
99
|
-
if (parsed && typeof parsed.models === "object" && !Array.isArray(parsed.models)) {
|
|
100
|
-
return parsed.models;
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
} catch {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
103
|
async function readCachedModels(client) {
|
|
108
104
|
try {
|
|
109
105
|
const dir = await getDir(client);
|
|
@@ -313,6 +309,19 @@ function buildModels(models, baseURL) {
|
|
|
313
309
|
|
|
314
310
|
// src/plugin.ts
|
|
315
311
|
var lastRefresh = null;
|
|
312
|
+
var inFlightRefresh = null;
|
|
313
|
+
function raceWithTimeout(promise, timeoutMs) {
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
const timer = setTimeout(() => resolve({ status: "timed-out" }), timeoutMs);
|
|
316
|
+
promise.then((value) => {
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
resolve({ status: "resolved", value });
|
|
319
|
+
}, (error) => {
|
|
320
|
+
clearTimeout(timer);
|
|
321
|
+
resolve({ status: "rejected", error });
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
316
325
|
function mergeModelMaps(base, overrides) {
|
|
317
326
|
if (!overrides)
|
|
318
327
|
return base;
|
|
@@ -348,22 +357,30 @@ function mergeModelMaps(base, overrides) {
|
|
|
348
357
|
}
|
|
349
358
|
return merged;
|
|
350
359
|
}
|
|
351
|
-
|
|
360
|
+
function refreshModels(client, baseURL, log, apiKey) {
|
|
352
361
|
if (lastRefresh && Date.now() - lastRefresh.at < REFRESH_TTL_MS) {
|
|
353
362
|
log.info(`Using in-memory plexus model cache (${Object.keys(lastRefresh.models).length} models)`);
|
|
354
|
-
return lastRefresh.models;
|
|
363
|
+
return Promise.resolve(lastRefresh.models);
|
|
355
364
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
if (inFlightRefresh)
|
|
366
|
+
return inFlightRefresh;
|
|
367
|
+
const run = async () => {
|
|
368
|
+
const url = modelsUrl(baseURL);
|
|
369
|
+
const { models: apiModels, raw } = await fetchPlexusModels(apiKey ?? "", url);
|
|
370
|
+
const built = buildModels(apiModels, apiBase(baseURL));
|
|
371
|
+
for (const [id, model] of Object.entries(built)) {
|
|
372
|
+
const providerNpm = model.provider?.npm ?? OPENAI_COMPATIBLE_NPM;
|
|
373
|
+
const providerApi = model.provider?.api ?? "(missing)";
|
|
374
|
+
log.info(`Model mapping ${id}: npm=${providerNpm} api=${providerApi}`);
|
|
375
|
+
}
|
|
376
|
+
lastRefresh = { at: Date.now(), models: built };
|
|
377
|
+
writeCache(client, built, raw).catch(() => {});
|
|
378
|
+
return built;
|
|
379
|
+
};
|
|
380
|
+
inFlightRefresh = run().finally(() => {
|
|
381
|
+
inFlightRefresh = null;
|
|
382
|
+
});
|
|
383
|
+
return inFlightRefresh;
|
|
367
384
|
}
|
|
368
385
|
var PlexusProviderPlugin = async (ctx) => {
|
|
369
386
|
const { client } = ctx;
|
|
@@ -379,9 +396,9 @@ var PlexusProviderPlugin = async (ctx) => {
|
|
|
379
396
|
if (typeof existingOptions["baseURL"] === "string") {
|
|
380
397
|
log.warn(`Ignoring legacy provider.options.baseURL=${String(existingOptions["baseURL"])}`);
|
|
381
398
|
}
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
log.info(`Loaded
|
|
399
|
+
const cachedAsync = await readCachedModels(client);
|
|
400
|
+
if (cachedAsync) {
|
|
401
|
+
log.info(`Loaded plexus cache with ${Object.keys(cachedAsync).length} models`);
|
|
385
402
|
}
|
|
386
403
|
const merged = {
|
|
387
404
|
...existing,
|
|
@@ -392,7 +409,7 @@ var PlexusProviderPlugin = async (ctx) => {
|
|
|
392
409
|
...baseURL ? { [PLEXUS_BASE_URL_OPTION]: baseURL } : {},
|
|
393
410
|
...apiKey ? { apiKey } : {}
|
|
394
411
|
},
|
|
395
|
-
models: existingModels ??
|
|
412
|
+
models: existingModels ?? cachedAsync ?? {
|
|
396
413
|
[PLACEHOLDER_MODEL_ID]: {
|
|
397
414
|
id: PLACEHOLDER_MODEL_ID,
|
|
398
415
|
name: "Plexus (run /connect to configure)",
|
|
@@ -404,16 +421,24 @@ var PlexusProviderPlugin = async (ctx) => {
|
|
|
404
421
|
const mergedOptions = merged["options"];
|
|
405
422
|
delete mergedOptions["baseURL"];
|
|
406
423
|
if (baseURL) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (
|
|
415
|
-
merged["models"] = mergeModelMaps(
|
|
424
|
+
const refreshPromise = refreshModels(client, baseURL, log, apiKey);
|
|
425
|
+
const race = await raceWithTimeout(refreshPromise, CONFIG_HOOK_REFRESH_BUDGET_MS);
|
|
426
|
+
if (race.status === "resolved") {
|
|
427
|
+
merged["models"] = mergeModelMaps(race.value, existingModels);
|
|
428
|
+
log.info(`Loaded ${Object.keys(race.value).length} plexus models from ${baseURL}`);
|
|
429
|
+
} else if (race.status === "rejected") {
|
|
430
|
+
log.warn(`Live model refresh failed, using cache: ${String(race.error)}`);
|
|
431
|
+
if (cachedAsync) {
|
|
432
|
+
merged["models"] = mergeModelMaps(cachedAsync, existingModels);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
log.info(`Live model refresh still pending after ${CONFIG_HOOK_REFRESH_BUDGET_MS}ms; using cache and continuing in background`);
|
|
436
|
+
if (cachedAsync) {
|
|
437
|
+
merged["models"] = mergeModelMaps(cachedAsync, existingModels);
|
|
416
438
|
}
|
|
439
|
+
refreshPromise.catch((e) => {
|
|
440
|
+
log.warn(`Background plexus model refresh failed: ${String(e)}`);
|
|
441
|
+
});
|
|
417
442
|
}
|
|
418
443
|
} else {
|
|
419
444
|
log.info("Plexus baseURL not configured; skipping live refresh");
|
|
@@ -499,5 +524,6 @@ export {
|
|
|
499
524
|
OPENAI_COMPATIBLE_NPM,
|
|
500
525
|
MODELS_FETCH_TIMEOUT_MS,
|
|
501
526
|
ENV_BASE_URL,
|
|
502
|
-
ENV_API_KEY
|
|
527
|
+
ENV_API_KEY,
|
|
528
|
+
CONFIG_HOOK_REFRESH_BUDGET_MS
|
|
503
529
|
};
|