@mcowger/opencode-plexus 1.0.4 → 1.1.1

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +36 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -227,8 +227,10 @@ Models with a falsy `id` are skipped. Missing metadata falls back to safe defaul
227
227
 
228
228
  ## Adapter behavior
229
229
 
230
- - **pi** refreshes on startup, extension reload, and through `/plexus refresh`. It accepts either root URLs or URLs ending in `/v1` and normalizes them before calling Plexus.
230
+ - **pi** refreshes on session start and through `/plexus refresh`. It accepts either root URLs or URLs ending in `/v1` and normalizes them before calling Plexus.
231
231
  - **OpenCode** seeds the provider from cache or a placeholder model during config loading, then performs live discovery through the `provider.models` hook. If Plexus is slow or unavailable, OpenCode uses the cache and lets the refresh continue in the background.
232
+ - OpenCode models retain their upstream model ID, SDK dialect, release date, and reasoning metadata so OpenCode can generate its native GPT, Claude, Gemini, and OpenAI-compatible variants and apply its current request transforms. DeepSeek models also preserve `reasoning_content` across tool-call turns.
233
+ - OpenCode uses a 250K-token context window when Plexus supplies no context metadata; its output fallback remains 20% of that window.
232
234
  - Both adapters convert Plexus's per-token base and tier rates to the per-million-token units expected by their host.
233
235
 
234
236
  ## Development
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ var PLACEHOLDER_MODEL_ID = "plexus-unconfigured";
16
16
 
17
17
  // ../plexus-models/src/convert.ts
18
18
  var REASONING_PARAMS = new Set(["reasoning", "include_reasoning", "reasoning_effort"]);
19
- var NON_CHAT_ID_PATTERN = /embedding|embed|tts|whisper|image-[0-9]|image\b.*gen|diffusion|dall-e|stable-diff|sdxl|dream/i;
19
+ 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;
20
20
  var API_DIALECT_MAP = {
21
21
  chat_completions: "openai-completions",
22
22
  "openai-completions": "openai-completions",
@@ -50,13 +50,19 @@ function adjustBaseUrl(baseUrl, preferredApi) {
50
50
  }
51
51
  }
52
52
  function isChatModel(model) {
53
- const inputModalities = model.architecture?.input_modalities;
54
- if (inputModalities !== undefined && !inputModalities.includes("text"))
53
+ if (!model.id)
55
54
  return false;
56
55
  const outputModalities = model.architecture?.output_modalities;
57
- if (outputModalities !== undefined)
58
- return outputModalities.includes("text");
59
- return !NON_CHAT_ID_PATTERN.test(model.id);
56
+ if (outputModalities !== undefined && !outputModalities.includes("text"))
57
+ return false;
58
+ const modality = model.architecture?.modality;
59
+ if (modality?.includes("->")) {
60
+ const output = modality.split("->").at(-1) ?? "";
61
+ if (!output.toLowerCase().includes("text"))
62
+ return false;
63
+ }
64
+ const apiHints = Array.isArray(model.preferred_api) ? model.preferred_api.join(" ") : model.preferred_api ?? "";
65
+ return !NON_CHAT_PATTERN.test(`${model.id} ${model.name ?? ""} ${apiHints}`);
60
66
  }
61
67
  var DEFAULT_MODELS_FETCH_TIMEOUT_MS = 1e4;
62
68
  async function fetchPlexusModels(apiKey, modelsUrl, timeoutMs = DEFAULT_MODELS_FETCH_TIMEOUT_MS) {
@@ -231,7 +237,7 @@ function createLogger(client) {
231
237
 
232
238
  // src/mapper.ts
233
239
  var REASONING_PARAMS2 = new Set(["reasoning", "include_reasoning", "reasoning_effort"]);
234
- var DEFAULT_CONTEXT = 8192;
240
+ var DEFAULT_CONTEXT = 250000;
235
241
  var PER_TOKEN_TO_PER_MILLION = 1e6;
236
242
  function resolveModelProvider(model, baseURL) {
237
243
  const preferredApi = mapPreferredApi(model.preferred_api);
@@ -289,6 +295,18 @@ function mapModality(m) {
289
295
  return null;
290
296
  }
291
297
  }
298
+ function releaseDate(created) {
299
+ if (typeof created !== "number" || !Number.isFinite(created) || created <= 0)
300
+ return;
301
+ const date = new Date(created * 1000);
302
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString().slice(0, 10);
303
+ }
304
+ function interleavedReasoning(model, preferredApi) {
305
+ if (preferredApi === "openai-completions" && model.id.toLowerCase().includes("deepseek")) {
306
+ return { field: "reasoning_content" };
307
+ }
308
+ return;
309
+ }
292
310
  function buildInputModalities(model) {
293
311
  const raw = model.architecture?.input_modalities ?? [];
294
312
  const mapped = raw.map(mapModality).filter((m) => m !== null);
@@ -307,7 +325,7 @@ function buildOutputModalities(model) {
307
325
  function buildModels(models, baseURL) {
308
326
  const result = {};
309
327
  for (const m of models) {
310
- if (!m.id || !isChatModel(m))
328
+ if (!isChatModel(m))
311
329
  continue;
312
330
  const outputModalities = buildOutputModalities(m);
313
331
  if (outputModalities === null)
@@ -323,7 +341,10 @@ function buildModels(models, baseURL) {
323
341
  const hasCachePricing = cacheReadPrice > 0 || cacheWritePrice > 0;
324
342
  const pricingTiers = buildPricingTiers(m);
325
343
  const hasNonTextInput = inputModalities.some((mod) => mod !== "text");
344
+ const preferredApi = mapPreferredApi(m.preferred_api);
326
345
  const provider = resolveModelProvider(m, baseURL);
346
+ const created = releaseDate(m.created);
347
+ const interleaved = interleavedReasoning(m, preferredApi);
327
348
  const entry = {
328
349
  id: m.id,
329
350
  name: m.name ?? m.id,
@@ -347,7 +368,9 @@ function buildModels(models, baseURL) {
347
368
  ...params.some((p) => REASONING_PARAMS2.has(p)) ? { reasoning: true } : {},
348
369
  ...params.includes("temperature") ? { temperature: true } : {},
349
370
  ...hasNonTextInput ? { attachment: true } : {},
350
- ...pricingTiers ? { pricingTiers } : {}
371
+ ...pricingTiers ? { pricingTiers } : {},
372
+ ...created ? { release_date: created } : {},
373
+ ...interleaved ? { interleaved } : {}
351
374
  };
352
375
  result[m.id] = entry;
353
376
  }
@@ -379,7 +402,7 @@ function toRuntimeCapabilities(model) {
379
402
  video: output.has("video"),
380
403
  pdf: output.has("pdf")
381
404
  },
382
- interleaved: false
405
+ interleaved: model.interleaved ?? false
383
406
  };
384
407
  }
385
408
  function toRuntimeModels(models, provider) {
@@ -391,7 +414,7 @@ function toRuntimeModels(models, provider) {
391
414
  id: model.id,
392
415
  providerID: provider.id,
393
416
  api: {
394
- id: provider.id,
417
+ id: model.id,
395
418
  url: model.provider?.api ?? providerApi,
396
419
  npm: model.provider?.npm ?? providerNpm
397
420
  },
@@ -417,7 +440,7 @@ function toRuntimeModels(models, provider) {
417
440
  status: "active",
418
441
  options: {},
419
442
  headers: {},
420
- release_date: ""
443
+ release_date: model.release_date ?? ""
421
444
  };
422
445
  }
423
446
  return result;
@@ -609,6 +632,7 @@ var plugin2 = {
609
632
  };
610
633
  var src_default = plugin2;
611
634
  export {
635
+ toRuntimeModels,
612
636
  src_default as default,
613
637
  buildModels,
614
638
  REFRESH_TTL_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcowger/opencode-plexus",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "description": "OpenCode plugin: Plexus provider with dynamic model discovery",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",