@mcowger/oh-my-pi-plexus 1.3.8 → 1.3.9

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 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 convertDescriptors(models, baseUrl) {
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;
@@ -109579,6 +109685,12 @@ function getBaseUrl() {
109579
109685
  const raw = getRawBaseUrl();
109580
109686
  return raw ? normalizeApiBase(raw) : null;
109581
109687
  }
109688
+ function getSuppressedModels() {
109689
+ const config = getConfigSync();
109690
+ const envSuppressed = getEnvSuppressedModels();
109691
+ const configSuppressed = parseSuppressionPatterns(config.suppressModels ?? config.suppress);
109692
+ return [...envSuppressed, ...configSuppressed];
109693
+ }
109582
109694
 
109583
109695
  // src/cache.ts
109584
109696
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
@@ -129293,8 +129405,9 @@ function getProviderApiKeyConfig() {
129293
129405
  var currentModels = [];
129294
129406
  function plexusExtension(pi) {
129295
129407
  const cached3 = readCachedModelsSync();
129408
+ const suppressPatterns = getSuppressedModels();
129296
129409
  const startupBaseUrl = getBaseUrl() ?? "http://localhost/v1";
129297
- const startupModels = cached3?.models.map(descriptorToOhMyPiModel) ?? [];
129410
+ const startupModels = (cached3?.models ?? []).filter((m) => !isModelSuppressed({ id: m.id, name: m.name }, suppressPatterns)).map(descriptorToOhMyPiModel);
129298
129411
  log("startup", {
129299
129412
  cachedModelCount: startupModels.length,
129300
129413
  startupBaseUrl
@@ -129417,7 +129530,8 @@ async function doRefresh(pi, apiKey, ctx) {
129417
129530
  }
129418
129531
  try {
129419
129532
  const { models: apiModels, raw } = await fetchPlexusModels(apiKey, modelsUrl);
129420
- const descriptors3 = convertDescriptors(apiModels, baseUrl);
129533
+ const suppressPatterns = getSuppressedModels();
129534
+ const descriptors3 = convertDescriptors(apiModels, baseUrl, suppressPatterns);
129421
129535
  const ohMyPiModels = descriptors3.map(descriptorToOhMyPiModel);
129422
129536
  await Promise.all([writeCachedModels(descriptors3), writeRawResponse(raw)]);
129423
129537
  currentModels = ohMyPiModels;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcowger/oh-my-pi-plexus",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "Plexus AI proxy plugin for the Oh My Pi coding agent",
5
5
  "type": "module",
6
6
  "files": [