@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 +68 -18
- package/dist/extension.js +71 -6
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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);
|