@mcowger/oh-my-pi-plexus 1.3.8 → 1.3.10
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/README.md +42 -0
- package/dist/extension.js +138 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -244,6 +244,47 @@ Both adapters also accept pi-style environment interpolation in configured strin
|
|
|
244
244
|
|
|
245
245
|
---
|
|
246
246
|
|
|
247
|
+
## Model suppression
|
|
248
|
+
|
|
249
|
+
All plugin packages support suppressing models by name or pattern so undesired models do not appear in model selectors or cache restored lists.
|
|
250
|
+
|
|
251
|
+
### Pattern matching syntax
|
|
252
|
+
|
|
253
|
+
- **Exact match** (case-insensitive): Matches model `id`, `name`, or short ID (suffix after `/` or `:`). Example: `"gpt-4o"`, `"Claude 3.5 Sonnet"`.
|
|
254
|
+
- **Glob wildcards**: Supports `*` (0+ characters) and `?` (1 character). Example: `"gpt-3.5*"`, `"*deprecated*"`, `"anthropic/*"`.
|
|
255
|
+
- **Regex patterns**: Prefix with `regex:`. Example: `"regex:^gpt-[34]"`.
|
|
256
|
+
|
|
257
|
+
### Configuration methods
|
|
258
|
+
|
|
259
|
+
- **Environment variables**:
|
|
260
|
+
```sh
|
|
261
|
+
export PLEXUS_SUPPRESS_MODELS="gpt-3.5*, *deprecated*, whisper"
|
|
262
|
+
```
|
|
263
|
+
`PLEXUS_EXCLUDE_MODELS` is also accepted. Values can be comma-, semicolon-, or newline-separated.
|
|
264
|
+
|
|
265
|
+
- **pi / Oh My Pi config**: Add `suppressModels` (or `suppress`) to `config.json`:
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"baseUrl": "https://plexus.example.com",
|
|
269
|
+
"suppressModels": ["gpt-3.5*", "*deprecated*"]
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- **OpenCode config**: Add `suppressModels` (or `suppress`) under `provider.plexus.options` in `opencode.json`:
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"provider": {
|
|
277
|
+
"plexus": {
|
|
278
|
+
"options": {
|
|
279
|
+
"suppressModels": ["gpt-3.5*", "claude-2*"]
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
247
288
|
## Package layout
|
|
248
289
|
|
|
249
290
|
```
|
|
@@ -252,6 +293,7 @@ packages/
|
|
|
252
293
|
src/
|
|
253
294
|
types.ts # wire types (PlexusApiModel, PlexusModelDescriptor, etc.)
|
|
254
295
|
convert.ts # model fetching, conversion, compat detection
|
|
296
|
+
suppress.ts # model pattern suppression / exclusion matching
|
|
255
297
|
index.ts # barrel export
|
|
256
298
|
plexus-pi/ # pi host adapter
|
|
257
299
|
src/
|
package/dist/extension.js
CHANGED
|
@@ -109228,6 +109228,55 @@ var init_devin = __esm(() => {
|
|
|
109228
109228
|
NO_REASONING_LABEL_PATTERN = /\bno thinking\b/i;
|
|
109229
109229
|
});
|
|
109230
109230
|
|
|
109231
|
+
// ../plexus-models/src/suppress.ts
|
|
109232
|
+
function parseSuppressionPatterns(raw) {
|
|
109233
|
+
if (!raw)
|
|
109234
|
+
return [];
|
|
109235
|
+
const items = Array.isArray(raw) ? raw : raw.split(/[\n,;]+/);
|
|
109236
|
+
return items.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
109237
|
+
}
|
|
109238
|
+
function getEnvSuppressedModels() {
|
|
109239
|
+
const env = typeof process !== "undefined" && process?.env ? process.env : {};
|
|
109240
|
+
const raw = env.PLEXUS_SUPPRESS_MODELS ?? env.PLEXUS_EXCLUDE_MODELS;
|
|
109241
|
+
return parseSuppressionPatterns(raw);
|
|
109242
|
+
}
|
|
109243
|
+
function isModelSuppressed(model, patterns) {
|
|
109244
|
+
const envPatterns = getEnvSuppressedModels();
|
|
109245
|
+
const explicitPatterns = parseSuppressionPatterns(patterns);
|
|
109246
|
+
const allPatterns = [...envPatterns, ...explicitPatterns];
|
|
109247
|
+
if (allPatterns.length === 0)
|
|
109248
|
+
return false;
|
|
109249
|
+
const id = model.id.toLowerCase();
|
|
109250
|
+
const name = (model.name ?? "").toLowerCase();
|
|
109251
|
+
const shortId = id.includes("/") ? id.split("/").pop() : id.includes(":") ? id.split(":").pop() : id;
|
|
109252
|
+
for (const pattern of allPatterns) {
|
|
109253
|
+
if (matchesPattern(id, name, shortId, pattern)) {
|
|
109254
|
+
return true;
|
|
109255
|
+
}
|
|
109256
|
+
}
|
|
109257
|
+
return false;
|
|
109258
|
+
}
|
|
109259
|
+
function matchesPattern(id, name, shortId, pattern) {
|
|
109260
|
+
const p = pattern.toLowerCase();
|
|
109261
|
+
if (p.startsWith("regex:")) {
|
|
109262
|
+
try {
|
|
109263
|
+
const re = new RegExp(pattern.slice(6), "i");
|
|
109264
|
+
return re.test(id) || re.test(name) || re.test(shortId);
|
|
109265
|
+
} catch {
|
|
109266
|
+
return false;
|
|
109267
|
+
}
|
|
109268
|
+
}
|
|
109269
|
+
if (p.includes("*") || p.includes("?")) {
|
|
109270
|
+
const regexStr = "^" + p.replace(/([.+^${}()|[\]\\])/g, "\\$1").replace(/\*/g, ".*").replace(/\?/g, ".") + "$";
|
|
109271
|
+
try {
|
|
109272
|
+
const re = new RegExp(regexStr, "i");
|
|
109273
|
+
return re.test(id) || re.test(name) || re.test(shortId);
|
|
109274
|
+
} catch {
|
|
109275
|
+
return false;
|
|
109276
|
+
}
|
|
109277
|
+
}
|
|
109278
|
+
return id === p || name === p || shortId === p;
|
|
109279
|
+
}
|
|
109231
109280
|
// ../plexus-models/src/convert.ts
|
|
109232
109281
|
var REASONING_PARAMS = new Set(["reasoning", "include_reasoning", "reasoning_effort"]);
|
|
109233
109282
|
var NON_CHAT_PATTERN = /(?:^|[\W_])(?:embed(?:ding|dings)?|transcri(?:be[ds]?|ptions?)|whisper|speech[\W_]*to[\W_]*text|stt|text[\W_]*to[\W_]*speech|tts|image[\W_]*(?:gen(?:eration)?|\d+)|diffusion|dall[\W_]*e|stable[\W_]*diffusion|sdxl|dream)(?:$|[\W_])/i;
|
|
@@ -109361,11 +109410,68 @@ function isChatModel(model) {
|
|
|
109361
109410
|
const apiHints = Array.isArray(model.preferred_api) ? model.preferred_api.join(" ") : model.preferred_api ?? "";
|
|
109362
109411
|
return !NON_CHAT_PATTERN.test(`${model.id} ${model.name ?? ""} ${apiHints}`);
|
|
109363
109412
|
}
|
|
109364
|
-
function
|
|
109413
|
+
function parseSuppressionPatterns2(input) {
|
|
109414
|
+
if (!input)
|
|
109415
|
+
return [];
|
|
109416
|
+
const rawItems = Array.isArray(input) ? input : [input];
|
|
109417
|
+
const patterns = [];
|
|
109418
|
+
for (const item of rawItems) {
|
|
109419
|
+
if (!item || typeof item !== "string")
|
|
109420
|
+
continue;
|
|
109421
|
+
const parts = item.split(/[\n,]/);
|
|
109422
|
+
for (const part of parts) {
|
|
109423
|
+
const trimmed = part.trim();
|
|
109424
|
+
if (trimmed.length > 0) {
|
|
109425
|
+
patterns.push(trimmed);
|
|
109426
|
+
}
|
|
109427
|
+
}
|
|
109428
|
+
}
|
|
109429
|
+
return patterns;
|
|
109430
|
+
}
|
|
109431
|
+
function isSuppressedModel(model, suppress) {
|
|
109432
|
+
if (!model || !model.id)
|
|
109433
|
+
return false;
|
|
109434
|
+
const patterns = parseSuppressionPatterns2(suppress);
|
|
109435
|
+
if (patterns.length === 0)
|
|
109436
|
+
return false;
|
|
109437
|
+
const idLower = model.id.toLowerCase();
|
|
109438
|
+
const nameLower = (model.name ?? "").toLowerCase();
|
|
109439
|
+
for (const pattern of patterns) {
|
|
109440
|
+
const patternLower = pattern.toLowerCase();
|
|
109441
|
+
if (idLower === patternLower || nameLower && nameLower === patternLower) {
|
|
109442
|
+
return true;
|
|
109443
|
+
}
|
|
109444
|
+
if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
|
|
109445
|
+
const lastSlash = pattern.lastIndexOf("/");
|
|
109446
|
+
const regexBody = pattern.slice(1, lastSlash);
|
|
109447
|
+
const regexFlags = pattern.slice(lastSlash + 1) || "i";
|
|
109448
|
+
try {
|
|
109449
|
+
const re = new RegExp(regexBody, regexFlags);
|
|
109450
|
+
if (re.test(model.id) || model.name && re.test(model.name)) {
|
|
109451
|
+
return true;
|
|
109452
|
+
}
|
|
109453
|
+
} catch {}
|
|
109454
|
+
}
|
|
109455
|
+
if (pattern.includes("*") || pattern.includes("?")) {
|
|
109456
|
+
try {
|
|
109457
|
+
const escaped = patternLower.replace(/[.+^$()|[{}]\\]/g, "\\$&");
|
|
109458
|
+
const regexStr = "^" + escaped.replace(/\*/g, ".*").replace(/\?/g, ".") + "$";
|
|
109459
|
+
const globRe = new RegExp(regexStr, "i");
|
|
109460
|
+
if (globRe.test(model.id) || model.name && globRe.test(model.name)) {
|
|
109461
|
+
return true;
|
|
109462
|
+
}
|
|
109463
|
+
} catch {}
|
|
109464
|
+
}
|
|
109465
|
+
}
|
|
109466
|
+
return false;
|
|
109467
|
+
}
|
|
109468
|
+
function convertDescriptors(models, baseUrl, suppress) {
|
|
109365
109469
|
const result = [];
|
|
109366
109470
|
for (const m of models) {
|
|
109367
109471
|
if (!isChatModel(m))
|
|
109368
109472
|
continue;
|
|
109473
|
+
if (isSuppressedModel(m, suppress))
|
|
109474
|
+
continue;
|
|
109369
109475
|
result.push(convertToDescriptor(m, baseUrl));
|
|
109370
109476
|
}
|
|
109371
109477
|
return result;
|
|
@@ -109430,22 +109536,28 @@ function detectOpenAICompletionsCompat(providerName, baseUrl) {
|
|
|
109430
109536
|
return compat;
|
|
109431
109537
|
}
|
|
109432
109538
|
var DEFAULT_MODELS_FETCH_TIMEOUT_MS = 1e4;
|
|
109433
|
-
async function fetchPlexusModels(apiKey, modelsUrl, timeoutMs = DEFAULT_MODELS_FETCH_TIMEOUT_MS) {
|
|
109539
|
+
async function fetchPlexusModels(apiKey, modelsUrl, timeoutMs = DEFAULT_MODELS_FETCH_TIMEOUT_MS, etag) {
|
|
109434
109540
|
const controller = new AbortController;
|
|
109435
109541
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
109436
109542
|
try {
|
|
109437
109543
|
const headers = { Accept: "application/json" };
|
|
109438
109544
|
if (apiKey)
|
|
109439
109545
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
109546
|
+
if (etag)
|
|
109547
|
+
headers["If-None-Match"] = etag;
|
|
109440
109548
|
const res = await fetch(modelsUrl, {
|
|
109441
109549
|
headers,
|
|
109442
109550
|
signal: controller.signal
|
|
109443
109551
|
});
|
|
109552
|
+
if (res.status === 304) {
|
|
109553
|
+
return { models: [], notModified: true };
|
|
109554
|
+
}
|
|
109444
109555
|
if (!res.ok) {
|
|
109445
109556
|
throw new Error(`Plexus models fetch failed: ${res.status} ${res.statusText}`);
|
|
109446
109557
|
}
|
|
109447
109558
|
const raw = await res.json();
|
|
109448
|
-
|
|
109559
|
+
const responseEtag = res.headers.get("etag") ?? undefined;
|
|
109560
|
+
return { models: raw.data ?? [], raw, etag: responseEtag };
|
|
109449
109561
|
} catch (err) {
|
|
109450
109562
|
if (err instanceof Error && err.name === "AbortError") {
|
|
109451
109563
|
throw new Error(`Plexus models fetch timed out after ${timeoutMs}ms`);
|
|
@@ -109579,6 +109691,12 @@ function getBaseUrl() {
|
|
|
109579
109691
|
const raw = getRawBaseUrl();
|
|
109580
109692
|
return raw ? normalizeApiBase(raw) : null;
|
|
109581
109693
|
}
|
|
109694
|
+
function getSuppressedModels() {
|
|
109695
|
+
const config = getConfigSync();
|
|
109696
|
+
const envSuppressed = getEnvSuppressedModels();
|
|
109697
|
+
const configSuppressed = parseSuppressionPatterns(config.suppressModels ?? config.suppress);
|
|
109698
|
+
return [...envSuppressed, ...configSuppressed];
|
|
109699
|
+
}
|
|
109582
109700
|
|
|
109583
109701
|
// src/cache.ts
|
|
109584
109702
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
@@ -109598,7 +109716,8 @@ function parseCacheData(raw) {
|
|
|
109598
109716
|
return null;
|
|
109599
109717
|
return {
|
|
109600
109718
|
models: obj["models"],
|
|
109601
|
-
timestamp: typeof obj["timestamp"] === "number" ? obj["timestamp"] : 0
|
|
109719
|
+
timestamp: typeof obj["timestamp"] === "number" ? obj["timestamp"] : 0,
|
|
109720
|
+
etag: typeof obj["etag"] === "string" ? obj["etag"] : undefined
|
|
109602
109721
|
};
|
|
109603
109722
|
} catch {
|
|
109604
109723
|
return null;
|
|
@@ -109614,9 +109733,9 @@ function readCachedModelsSync() {
|
|
|
109614
109733
|
return null;
|
|
109615
109734
|
}
|
|
109616
109735
|
}
|
|
109617
|
-
async function writeCachedModels(models) {
|
|
109736
|
+
async function writeCachedModels(models, etag) {
|
|
109618
109737
|
await mkdir2(getCacheDir(), { recursive: true });
|
|
109619
|
-
const payload = { models, timestamp: Date.now() };
|
|
109738
|
+
const payload = { models, timestamp: Date.now(), etag };
|
|
109620
109739
|
await writeFile2(getModelsCachePath(), `${JSON.stringify(payload, null, 2)}
|
|
109621
109740
|
`, "utf8");
|
|
109622
109741
|
}
|
|
@@ -129293,8 +129412,9 @@ function getProviderApiKeyConfig() {
|
|
|
129293
129412
|
var currentModels = [];
|
|
129294
129413
|
function plexusExtension(pi) {
|
|
129295
129414
|
const cached3 = readCachedModelsSync();
|
|
129415
|
+
const suppressPatterns = getSuppressedModels();
|
|
129296
129416
|
const startupBaseUrl = getBaseUrl() ?? "http://localhost/v1";
|
|
129297
|
-
const startupModels = cached3?.models.map(descriptorToOhMyPiModel)
|
|
129417
|
+
const startupModels = (cached3?.models ?? []).filter((m) => !isModelSuppressed({ id: m.id, name: m.name }, suppressPatterns)).map(descriptorToOhMyPiModel);
|
|
129298
129418
|
log("startup", {
|
|
129299
129419
|
cachedModelCount: startupModels.length,
|
|
129300
129420
|
startupBaseUrl
|
|
@@ -129416,10 +129536,18 @@ async function doRefresh(pi, apiKey, ctx) {
|
|
|
129416
129536
|
return;
|
|
129417
129537
|
}
|
|
129418
129538
|
try {
|
|
129419
|
-
const
|
|
129420
|
-
const
|
|
129539
|
+
const cached3 = readCachedModelsSync();
|
|
129540
|
+
const { models: apiModels, raw, etag, notModified } = await fetchPlexusModels(apiKey, modelsUrl, undefined, cached3?.etag);
|
|
129541
|
+
if (notModified) {
|
|
129542
|
+
log("doRefresh: not modified", { etag: cached3?.etag });
|
|
129543
|
+
if (ctx)
|
|
129544
|
+
ctx.ui.notify(`Refreshed ${currentModels.length} Plexus models (not modified)`, "info");
|
|
129545
|
+
return;
|
|
129546
|
+
}
|
|
129547
|
+
const suppressPatterns = getSuppressedModels();
|
|
129548
|
+
const descriptors3 = convertDescriptors(apiModels, baseUrl, suppressPatterns);
|
|
129421
129549
|
const ohMyPiModels = descriptors3.map(descriptorToOhMyPiModel);
|
|
129422
|
-
await Promise.all([writeCachedModels(descriptors3), writeRawResponse(raw)]);
|
|
129550
|
+
await Promise.all([writeCachedModels(descriptors3, etag), raw ? writeRawResponse(raw) : Promise.resolve()]);
|
|
129423
129551
|
currentModels = ohMyPiModels;
|
|
129424
129552
|
pi.registerProvider(PROVIDER_NAME, {
|
|
129425
129553
|
api: "openai-completions",
|