@mcowger/opencode-plexus 0.8.1 → 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 +83 -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
|
|
@@ -38,24 +39,39 @@ function mapPreferredApi(raw) {
|
|
|
38
39
|
function adjustBaseUrl(baseUrl, preferredApi) {
|
|
39
40
|
const stripped = baseUrl.replace(/\/+$/, "");
|
|
40
41
|
switch (preferredApi) {
|
|
42
|
+
case "anthropic-messages":
|
|
43
|
+
return stripped.endsWith("/v1") ? stripped.slice(0, -3) : stripped;
|
|
41
44
|
case "google-generative-ai":
|
|
42
45
|
return stripped.endsWith("/v1") ? `${stripped.slice(0, -3)}/v1beta` : stripped;
|
|
43
46
|
default:
|
|
44
47
|
return stripped;
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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}`);
|
|
52
64
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
56
74
|
}
|
|
57
|
-
const raw = await res.json();
|
|
58
|
-
return { models: raw.data ?? [], raw };
|
|
59
75
|
}
|
|
60
76
|
// src/cache.ts
|
|
61
77
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -84,24 +100,6 @@ async function getDir(client) {
|
|
|
84
100
|
resolvedDir = fallbackDir();
|
|
85
101
|
return resolvedDir;
|
|
86
102
|
}
|
|
87
|
-
function syncCachePath() {
|
|
88
|
-
return join(fallbackDir(), CACHE_FILE);
|
|
89
|
-
}
|
|
90
|
-
function readCachedModelsSync() {
|
|
91
|
-
try {
|
|
92
|
-
const path = syncCachePath();
|
|
93
|
-
if (!existsSync(path))
|
|
94
|
-
return null;
|
|
95
|
-
const raw = readFileSync(path, "utf8");
|
|
96
|
-
const parsed = JSON.parse(raw);
|
|
97
|
-
if (parsed && typeof parsed.models === "object" && !Array.isArray(parsed.models)) {
|
|
98
|
-
return parsed.models;
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
} catch {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
103
|
async function readCachedModels(client) {
|
|
106
104
|
try {
|
|
107
105
|
const dir = await getDir(client);
|
|
@@ -311,6 +309,19 @@ function buildModels(models, baseURL) {
|
|
|
311
309
|
|
|
312
310
|
// src/plugin.ts
|
|
313
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
|
+
}
|
|
314
325
|
function mergeModelMaps(base, overrides) {
|
|
315
326
|
if (!overrides)
|
|
316
327
|
return base;
|
|
@@ -346,22 +357,30 @@ function mergeModelMaps(base, overrides) {
|
|
|
346
357
|
}
|
|
347
358
|
return merged;
|
|
348
359
|
}
|
|
349
|
-
|
|
360
|
+
function refreshModels(client, baseURL, log, apiKey) {
|
|
350
361
|
if (lastRefresh && Date.now() - lastRefresh.at < REFRESH_TTL_MS) {
|
|
351
362
|
log.info(`Using in-memory plexus model cache (${Object.keys(lastRefresh.models).length} models)`);
|
|
352
|
-
return lastRefresh.models;
|
|
363
|
+
return Promise.resolve(lastRefresh.models);
|
|
353
364
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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;
|
|
365
384
|
}
|
|
366
385
|
var PlexusProviderPlugin = async (ctx) => {
|
|
367
386
|
const { client } = ctx;
|
|
@@ -377,9 +396,9 @@ var PlexusProviderPlugin = async (ctx) => {
|
|
|
377
396
|
if (typeof existingOptions["baseURL"] === "string") {
|
|
378
397
|
log.warn(`Ignoring legacy provider.options.baseURL=${String(existingOptions["baseURL"])}`);
|
|
379
398
|
}
|
|
380
|
-
const
|
|
381
|
-
if (
|
|
382
|
-
log.info(`Loaded
|
|
399
|
+
const cachedAsync = await readCachedModels(client);
|
|
400
|
+
if (cachedAsync) {
|
|
401
|
+
log.info(`Loaded plexus cache with ${Object.keys(cachedAsync).length} models`);
|
|
383
402
|
}
|
|
384
403
|
const merged = {
|
|
385
404
|
...existing,
|
|
@@ -390,7 +409,7 @@ var PlexusProviderPlugin = async (ctx) => {
|
|
|
390
409
|
...baseURL ? { [PLEXUS_BASE_URL_OPTION]: baseURL } : {},
|
|
391
410
|
...apiKey ? { apiKey } : {}
|
|
392
411
|
},
|
|
393
|
-
models: existingModels ??
|
|
412
|
+
models: existingModels ?? cachedAsync ?? {
|
|
394
413
|
[PLACEHOLDER_MODEL_ID]: {
|
|
395
414
|
id: PLACEHOLDER_MODEL_ID,
|
|
396
415
|
name: "Plexus (run /connect to configure)",
|
|
@@ -402,16 +421,24 @@ var PlexusProviderPlugin = async (ctx) => {
|
|
|
402
421
|
const mergedOptions = merged["options"];
|
|
403
422
|
delete mergedOptions["baseURL"];
|
|
404
423
|
if (baseURL) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (
|
|
413
|
-
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);
|
|
414
438
|
}
|
|
439
|
+
refreshPromise.catch((e) => {
|
|
440
|
+
log.warn(`Background plexus model refresh failed: ${String(e)}`);
|
|
441
|
+
});
|
|
415
442
|
}
|
|
416
443
|
} else {
|
|
417
444
|
log.info("Plexus baseURL not configured; skipping live refresh");
|
|
@@ -497,5 +524,6 @@ export {
|
|
|
497
524
|
OPENAI_COMPATIBLE_NPM,
|
|
498
525
|
MODELS_FETCH_TIMEOUT_MS,
|
|
499
526
|
ENV_BASE_URL,
|
|
500
|
-
ENV_API_KEY
|
|
527
|
+
ENV_API_KEY,
|
|
528
|
+
CONFIG_HOOK_REFRESH_BUDGET_MS
|
|
501
529
|
};
|