@mcowger/oh-my-pi-plexus 1.9.0 → 1.11.0

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
@@ -139,19 +139,6 @@ Inspect the effective URL, authentication availability, and catalog source witho
139
139
  /plexus status
140
140
  ```
141
141
 
142
- Choose the per-session service tier for eligible Plexus models:
143
-
144
- ```
145
- /service-tier fast
146
- ```
147
-
148
- Tiers are `default` (aliases `off`, `none`), `fast` (alias `priority`), `flex`, and `ultrafast`; `/service-tier status` reports the active tier and whether the current model supports it. The tier is applied per API:
149
-
150
- - **OpenAI Responses** models get a `service_tier` field. `priority` covers OpenAI's Fast-mode set (GPT-5 and newer, GPT-4.1+, GPT-4o, and o3/o4-mini); `flex` and `ultrafast` are limited to their documented models.
151
- - **Anthropic Messages** models use Claude Fast mode (`speed: "fast"` plus the `anthropic-beta: fast-mode-2026-02-01` flag) for `priority`, on `claude-opus-5` and `claude-opus-4-8`; `flex` and `ultrafast` are unsupported. This is a first-party research-preview feature, so it only works when the upstream allows it.
152
-
153
- The tier resets to `default` on every session start. `/plexus-service-tier` is an alias for the same command.
154
-
155
142
  Select a Plexus model in `/model`. Save its startup default with the host's normal model-picker action; Plexus does not maintain a separate default-model setting.
156
143
 
157
144
  Use `/login plexus` for setup and `/logout plexus` to remove stored credentials.
@@ -176,21 +163,21 @@ To force a model refresh:
176
163
  /plexus-refresh
177
164
  ```
178
165
 
179
- `PLEXUS_API_URL` / `PLEXUS_BASE_URL` / `PLEXUS_API_KEY` env vars and plugin options (`plexusBaseURL`, `suppressModels`) are also honored. Until a URL is configured, the provider shows a single `plexus-unconfigured` placeholder. Plugin logs are written to `~/.local/share/opencode/plugins/plexus/plugin.log` (the OpenCode service discards plugin stdout).
166
+ `PLEXUS_API_URL` / `PLEXUS_BASE_URL` / `PLEXUS_API_KEY` env vars and plugin options (`plexusBaseURL`, `apiKey`, `apiKeyEnv`, `suppressModels`) are also honored. Until a URL is configured, the provider shows a single `plexus-unconfigured` placeholder. Plugin logs are written to `~/.local/share/opencode/plugins/plexus/plugin.log` (the OpenCode service discards plugin stdout).
180
167
 
181
168
  ## Configuration files
182
169
 
183
170
  ### Connection state
184
171
 
185
- Each adapter stores only non-secret Plexus settings in its own `config.json`:
172
+ Pi and OMP store only non-secret Plexus settings in their own `config.json`:
186
173
 
187
174
  ```text
188
- <agent-dir>/extensions/plexus/config.json # base URL and optional model suppression
175
+ <agent-dir>/extensions/plexus/config.json # base URL, apiKeyEnv, model suppression
189
176
  ```
190
177
 
191
178
  Credentials stay in the host credential store (`auth.json` for Pi, `agent.db` for OMP). Model catalogs stay in the host model registry/store. The extension no longer writes a second model cache, ETag file, raw API response, or default-model preference.
192
179
 
193
- At runtime, configuration resolves as follows:
180
+ With `apiKeyEnv` omitted, Pi resolves configuration as follows:
194
181
 
195
182
  ```text
196
183
  base URL: PLEXUS_API_URL → PLEXUS_BASE_URL → saved Plexus base URL
@@ -199,10 +186,73 @@ catalog: host model store → live Plexus refresh
199
186
  startup model: host model-picker preference
200
187
  ```
201
188
 
202
- `PLEXUS_API_URL` and `PLEXUS_BASE_URL` are process-only URL overrides; `PLEXUS_API_KEY` is a process-only credential fallback. None are persisted. `/plexus status` reports the effective URL source, whether auth is available, and catalog state without exposing credentials.
189
+ OMP uses the same URL precedence, but its registered `PLEXUS_API_KEY` environment reference is a native config-key override, ahead of saved credentials. If that variable isn't set, OMP uses its host credential resolver. OpenCode uses `PLEXUS_API_KEY` before the saved `/connect` credential and plugin `apiKey` option.
190
+
191
+ `PLEXUS_API_URL` and `PLEXUS_BASE_URL` are process-only URL overrides; environment API keys aren't persisted. `/plexus status` reports the effective URL source, whether auth is available, and catalog state without exposing credentials.
203
192
 
204
193
  Plexus accepts a root URL or a `/v1` API URL. The adapter normalizes that only for the Plexus discovery request. Each model then receives its own API-specific base URL: OpenAI stays on `/v1`, Anthropic uses the root, and Google uses `/v1beta`.
205
194
 
195
+ ### Separate API-key environment variables
196
+
197
+ Set `apiKeyEnv` to the **name** of an environment variable, not its value or a `$VAR` template. The default is `PLEXUS_API_KEY`; no client-specific name is built in.
198
+
199
+ For Pi, edit `~/.pi/agent/extensions/plexus/config.json` (or `<agent-dir>/extensions/plexus/config.json` when using a custom agent directory):
200
+
201
+ ```json
202
+ {
203
+ "baseUrl": "https://plexus.example.com",
204
+ "apiKeyEnv": "AIHOME_PI_API_KEY"
205
+ }
206
+ ```
207
+
208
+ For OMP, edit `~/.omp/agent/extensions/plexus/config.json` (under the active profile's agent directory when using profiles):
209
+
210
+ ```json
211
+ {
212
+ "baseUrl": "https://plexus.example.com",
213
+ "apiKeyEnv": "AIHOME_OMP_API_KEY"
214
+ }
215
+ ```
216
+
217
+ For OpenCode V2, put `apiKeyEnv` in the plugin's `options` in `~/.config/opencode/opencode.jsonc` (or your project `opencode.jsonc`):
218
+
219
+ ```jsonc
220
+ {
221
+ "plugins": [
222
+ {
223
+ "package": "@mcowger/opencode-plexus",
224
+ "options": {
225
+ "plexusBaseURL": "https://plexus.example.com",
226
+ "apiKeyEnv": "AIHOME_OPENCODE_API_KEY"
227
+ }
228
+ }
229
+ ]
230
+ }
231
+ ```
232
+
233
+ Set the named variable in the process environment that launches the client. Restart Pi/OMP after editing their config; restart the OpenCode service after editing plugin options or changing its environment.
234
+
235
+ Credential precedence:
236
+
237
+ | Client | `apiKeyEnv` omitted | `apiKeyEnv` explicitly set |
238
+ |---|---|---|
239
+ | Pi | Saved host credential, then `PLEXUS_API_KEY` fallback | Only the named environment variable, ahead of saved credentials |
240
+ | OMP | Registered `PLEXUS_API_KEY` env override, then host credentials | Only the named environment variable, ahead of saved credentials |
241
+ | OpenCode V2 | `PLEXUS_API_KEY`, then saved `/connect` credential, then plugin `apiKey` option | Only the named environment variable, ahead of both saved credentials and `apiKey` |
242
+
243
+ An explicit setting is authoritative even when it names `PLEXUS_API_KEY`. If the selected variable is missing, empty, or whitespace-only, the plugin reports an error naming the variable without showing its value. It does not fall back to a saved key, OpenCode's `apiKey` option, or the shared `PLEXUS_API_KEY`. Saved credentials remain stored; remove `apiKeyEnv` to restore the default precedence.
244
+
245
+ ### Native login with `apiKeyEnv`
246
+
247
+ `apiKeyEnv` changes which key authenticates requests, not the native login UI:
248
+
249
+ - Pi and OMP: `/login plexus` still prompts for the base URL and API key, and saves both through the normal configuration and credential stores.
250
+ - OpenCode V2: `/connect` still stores the Plexus base URL and API key normally.
251
+
252
+ With an explicit `apiKeyEnv`, discovery and model requests use the named environment variable rather than the key entered during login. OMP's immediate post-login refresh also uses the environment key. Login remains useful for configuring the base URL, but entering a different key won't override the environment setting.
253
+
254
+ If the named variable is missing or empty at startup, the plugin fails to load. Native login cannot repair that setting: set the variable in the launching process environment or remove `apiKeyEnv`, then restart. Removing the setting restores the default precedence above without deleting saved credentials.
255
+
206
256
  ---
207
257
 
208
258
  ## Model suppression
package/dist/extension.js CHANGED
@@ -16414,6 +16414,29 @@ var toPlexusApiBase = (raw) => {
16414
16414
  const root = normalizeConfigBaseUrl(raw);
16415
16415
  return root ? `${root}/v1` : "";
16416
16416
  };
16417
+ function resolveApiKeyEnvSetting() {
16418
+ const raw = getConfigSync().apiKeyEnv;
16419
+ if (raw === undefined || raw === null)
16420
+ return { name: ENV_API_KEY, explicit: false };
16421
+ const name = typeof raw === "string" ? raw.trim() : "";
16422
+ if (!ENV_VAR_NAME_RE.test(name)) {
16423
+ throw new Error("Invalid apiKeyEnv: expected an environment variable name");
16424
+ }
16425
+ return { name, explicit: true };
16426
+ }
16427
+ function getApiKeyEnvName() {
16428
+ return resolveApiKeyEnvSetting().name;
16429
+ }
16430
+ function resolveExplicitApiKey() {
16431
+ const { name, explicit } = resolveApiKeyEnvSetting();
16432
+ if (!explicit)
16433
+ return;
16434
+ const value = process.env[name]?.trim();
16435
+ if (!value) {
16436
+ throw new Error(`apiKeyEnv environment variable "${name}" is missing or empty`);
16437
+ }
16438
+ return value;
16439
+ }
16417
16440
  var cachedConfig = null;
16418
16441
  function getConfigSync() {
16419
16442
  if (cachedConfig)
@@ -16452,6 +16475,12 @@ function getBaseUrlResolution() {
16452
16475
  function getEnvApiKey() {
16453
16476
  return resolveStringOption(process.env[ENV_API_KEY]) ?? null;
16454
16477
  }
16478
+ function resolveApiKey(storedKey) {
16479
+ const explicit = resolveExplicitApiKey();
16480
+ if (explicit)
16481
+ return explicit;
16482
+ return storedKey ?? getEnvApiKey();
16483
+ }
16455
16484
  function getModelsUrl() {
16456
16485
  const { baseUrl } = getBaseUrlResolution();
16457
16486
  return baseUrl ? `${toPlexusApiBase(baseUrl)}/models` : null;
@@ -23193,7 +23222,10 @@ function normalizeProviderConnectionClosed(message, providerName) {
23193
23222
  // src/extension.ts
23194
23223
  var PROVIDER_NAME = "plexus";
23195
23224
  function getProviderApiKeyConfig() {
23196
- return Bun.env[ENV_API_KEY]?.trim() ? { apiKey: ENV_API_KEY, authHeader: true } : {};
23225
+ const explicitApiKey = resolveExplicitApiKey();
23226
+ const envName = getApiKeyEnvName();
23227
+ const envValue = explicitApiKey ?? getEnvApiKey();
23228
+ return envValue ? { apiKey: envName, authHeader: true } : {};
23197
23229
  }
23198
23230
  var currentModels = [];
23199
23231
  var catalogSource = "none";
@@ -23211,7 +23243,15 @@ function plexusExtension(pi) {
23211
23243
  normalizeProviderConnectionClosed(event.message, PROVIDER_NAME);
23212
23244
  });
23213
23245
  pi.on("session_start", async (_event, ctx) => {
23214
- const apiKey = await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME) ?? getEnvApiKey();
23246
+ let apiKey;
23247
+ try {
23248
+ apiKey = resolveApiKey(await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME));
23249
+ } catch (error) {
23250
+ const message = error instanceof Error ? error.message : String(error);
23251
+ log("session_start: api key resolution failed", { error: message });
23252
+ ctx.ui.notify(message, "error");
23253
+ return;
23254
+ }
23215
23255
  const baseUrl = getBaseUrl();
23216
23256
  log("session_start", { hasApiKey: !!apiKey, baseUrl });
23217
23257
  if (!apiKey || !baseUrl) {
@@ -23260,7 +23300,13 @@ function createPlexusLoginProvider(pi) {
23260
23300
  };
23261
23301
  }
23262
23302
  async function handleRefresh(pi, ctx) {
23263
- const apiKey = await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME) ?? getEnvApiKey();
23303
+ let apiKey;
23304
+ try {
23305
+ apiKey = resolveApiKey(await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME));
23306
+ } catch (error) {
23307
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
23308
+ return;
23309
+ }
23264
23310
  if (!apiKey) {
23265
23311
  ctx.ui.notify("No Plexus API key configured. Run /login plexus first.", "error");
23266
23312
  return;
@@ -23273,14 +23319,26 @@ async function handleStatus(ctx) {
23273
23319
  const apiKey = await ctx.modelRegistry.authStorage.getApiKey(PROVIDER_NAME);
23274
23320
  ctx.ui.notify([
23275
23321
  `Plexus base URL: ${baseUrl.baseUrl ?? "not configured"} (${baseUrl.source})`,
23276
- `API key: ${apiKey ? getEnvApiKey() ? "host credential or PLEXUS_API_KEY fallback" : "host credential" : "not configured"}`,
23322
+ `API key: ${describeApiKey(apiKey)}`,
23277
23323
  `Catalog: ${currentModels.length} models (${catalogSource})`,
23278
23324
  "Default model: managed by OMP. Use /model or /models to save the selection there."
23279
23325
  ].join(`
23280
23326
  `), "info");
23281
23327
  }
23328
+ function describeApiKey(savedApiKey) {
23329
+ try {
23330
+ const envName = getApiKeyEnvName();
23331
+ if (resolveExplicitApiKey() !== undefined)
23332
+ return `${envName} (apiKeyEnv)`;
23333
+ if (getEnvApiKey())
23334
+ return `${envName} (overrides host credential)`;
23335
+ return savedApiKey ? "host credential" : "not configured";
23336
+ } catch (error) {
23337
+ return `invalid apiKeyEnv configuration (${error instanceof Error ? error.message : String(error)})`;
23338
+ }
23339
+ }
23282
23340
  async function fetchPlexusModelConfigs(apiKey) {
23283
- const key = apiKey ?? getEnvApiKey();
23341
+ const key = resolveApiKey(apiKey);
23284
23342
  const modelsUrl = getModelsUrl();
23285
23343
  const baseUrl = getBaseUrl();
23286
23344
  if (!key || !modelsUrl || !baseUrl)
@@ -23302,7 +23360,14 @@ async function doRefresh(pi, apiKey, ctx) {
23302
23360
  return;
23303
23361
  }
23304
23362
  try {
23305
- const { models: apiModels } = await fetchPlexusModels(apiKey, modelsUrl);
23363
+ const resolvedApiKey = resolveApiKey(apiKey);
23364
+ if (!resolvedApiKey) {
23365
+ if (ctx)
23366
+ ctx.ui.notify("No Plexus API key configured. Run /login plexus first.", "warning");
23367
+ log("doRefresh: no API key configured");
23368
+ return;
23369
+ }
23370
+ const { models: apiModels } = await fetchPlexusModels(resolvedApiKey, modelsUrl);
23306
23371
  const suppressPatterns = getSuppressedModels();
23307
23372
  const descriptors = convertDescriptors(apiModels, baseUrl, suppressPatterns);
23308
23373
  const ohMyPiModels = descriptors.map(descriptorToOhMyPiModel);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcowger/oh-my-pi-plexus",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Plexus AI proxy plugin for the Oh My Pi coding agent",
5
5
  "type": "module",
6
6
  "files": [