@mcowger/oh-my-pi-plexus 1.3.7 → 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";
@@ -125453,8 +125565,10 @@ function ollamaCloudModelManagerOptions(config4) {
125453
125565
  };
125454
125566
  }
125455
125567
 
125568
+ // stub-pi-utils-logger:@oh-my-pi/pi-utils/logger
125569
+ function warn() {}
125570
+
125456
125571
  // ../../node_modules/.bun/@oh-my-pi+pi-catalog@17.1.2/node_modules/@oh-my-pi/pi-catalog/src/provider-models/openai-compat.ts
125457
- import * as logger from "@oh-my-pi/pi-utils/logger";
125458
125572
  init_effort();
125459
125573
  init_fireworks_model_id();
125460
125574
  init_family();
@@ -127677,7 +127791,7 @@ function warnLiteLLMMetadataFallback(managementBaseUrl, failure) {
127677
127791
  return;
127678
127792
  }
127679
127793
  warnedLiteLLMMetadataBases.add(managementBaseUrl);
127680
- logger.warn("LiteLLM rich model metadata unavailable; falling back to /v1/models", {
127794
+ warn("LiteLLM rich model metadata unavailable; falling back to /v1/models", {
127681
127795
  endpoint: `${managementBaseUrl}${failure.endpoint}`,
127682
127796
  status: failure.status ?? "unavailable",
127683
127797
  reason: failure.reason,
@@ -129291,8 +129405,9 @@ function getProviderApiKeyConfig() {
129291
129405
  var currentModels = [];
129292
129406
  function plexusExtension(pi) {
129293
129407
  const cached3 = readCachedModelsSync();
129408
+ const suppressPatterns = getSuppressedModels();
129294
129409
  const startupBaseUrl = getBaseUrl() ?? "http://localhost/v1";
129295
- const startupModels = cached3?.models.map(descriptorToOhMyPiModel) ?? [];
129410
+ const startupModels = (cached3?.models ?? []).filter((m) => !isModelSuppressed({ id: m.id, name: m.name }, suppressPatterns)).map(descriptorToOhMyPiModel);
129296
129411
  log("startup", {
129297
129412
  cachedModelCount: startupModels.length,
129298
129413
  startupBaseUrl
@@ -129415,7 +129530,8 @@ async function doRefresh(pi, apiKey, ctx) {
129415
129530
  }
129416
129531
  try {
129417
129532
  const { models: apiModels, raw } = await fetchPlexusModels(apiKey, modelsUrl);
129418
- const descriptors3 = convertDescriptors(apiModels, baseUrl);
129533
+ const suppressPatterns = getSuppressedModels();
129534
+ const descriptors3 = convertDescriptors(apiModels, baseUrl, suppressPatterns);
129419
129535
  const ohMyPiModels = descriptors3.map(descriptorToOhMyPiModel);
129420
129536
  await Promise.all([writeCachedModels(descriptors3), writeRawResponse(raw)]);
129421
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.7",
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": [