@mcowger/opencode-plexus 1.0.3 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +27 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -229,6 +229,8 @@ Models with a falsy `id` are skipped. Missing metadata falls back to safe defaul
229
229
 
230
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,6 @@ 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;
20
19
  var API_DIALECT_MAP = {
21
20
  chat_completions: "openai-completions",
22
21
  "openai-completions": "openai-completions",
@@ -49,15 +48,6 @@ function adjustBaseUrl(baseUrl, preferredApi) {
49
48
  return stripped;
50
49
  }
51
50
  }
52
- function isChatModel(model) {
53
- const inputModalities = model.architecture?.input_modalities;
54
- if (inputModalities !== undefined && !inputModalities.includes("text"))
55
- return false;
56
- const outputModalities = model.architecture?.output_modalities;
57
- if (outputModalities !== undefined)
58
- return outputModalities.includes("text");
59
- return !NON_CHAT_ID_PATTERN.test(model.id);
60
- }
61
51
  var DEFAULT_MODELS_FETCH_TIMEOUT_MS = 1e4;
62
52
  async function fetchPlexusModels(apiKey, modelsUrl, timeoutMs = DEFAULT_MODELS_FETCH_TIMEOUT_MS) {
63
53
  const controller = new AbortController;
@@ -231,7 +221,7 @@ function createLogger(client) {
231
221
 
232
222
  // src/mapper.ts
233
223
  var REASONING_PARAMS2 = new Set(["reasoning", "include_reasoning", "reasoning_effort"]);
234
- var DEFAULT_CONTEXT = 8192;
224
+ var DEFAULT_CONTEXT = 250000;
235
225
  var PER_TOKEN_TO_PER_MILLION = 1e6;
236
226
  function resolveModelProvider(model, baseURL) {
237
227
  const preferredApi = mapPreferredApi(model.preferred_api);
@@ -289,11 +279,24 @@ function mapModality(m) {
289
279
  return null;
290
280
  }
291
281
  }
282
+ function releaseDate(created) {
283
+ if (typeof created !== "number" || !Number.isFinite(created) || created <= 0)
284
+ return;
285
+ const date = new Date(created * 1000);
286
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString().slice(0, 10);
287
+ }
288
+ function interleavedReasoning(model, preferredApi) {
289
+ if (preferredApi === "openai-completions" && model.id.toLowerCase().includes("deepseek")) {
290
+ return { field: "reasoning_content" };
291
+ }
292
+ return;
293
+ }
292
294
  function buildInputModalities(model) {
293
295
  const raw = model.architecture?.input_modalities ?? [];
294
296
  const mapped = raw.map(mapModality).filter((m) => m !== null);
295
297
  return mapped.length > 0 ? [...new Set(mapped)] : ["text"];
296
298
  }
299
+ var NON_CHAT_ID_PATTERN = /embedding|embed|tts|whisper|image-[0-9]|image\b.*gen|diffusion|dall-e|stable-diff|sdxl|dream/i;
297
300
  function buildOutputModalities(model) {
298
301
  const raw = model.architecture?.output_modalities;
299
302
  if (raw !== undefined) {
@@ -302,12 +305,14 @@ function buildOutputModalities(model) {
302
305
  const mapped = raw.map(mapModality).filter((m) => m !== null);
303
306
  return mapped.length > 0 ? [...new Set(mapped)] : ["text"];
304
307
  }
308
+ if (NON_CHAT_ID_PATTERN.test(model.id))
309
+ return null;
305
310
  return ["text"];
306
311
  }
307
312
  function buildModels(models, baseURL) {
308
313
  const result = {};
309
314
  for (const m of models) {
310
- if (!m.id || !isChatModel(m))
315
+ if (!m.id)
311
316
  continue;
312
317
  const outputModalities = buildOutputModalities(m);
313
318
  if (outputModalities === null)
@@ -323,7 +328,10 @@ function buildModels(models, baseURL) {
323
328
  const hasCachePricing = cacheReadPrice > 0 || cacheWritePrice > 0;
324
329
  const pricingTiers = buildPricingTiers(m);
325
330
  const hasNonTextInput = inputModalities.some((mod) => mod !== "text");
331
+ const preferredApi = mapPreferredApi(m.preferred_api);
326
332
  const provider = resolveModelProvider(m, baseURL);
333
+ const created = releaseDate(m.created);
334
+ const interleaved = interleavedReasoning(m, preferredApi);
327
335
  const entry = {
328
336
  id: m.id,
329
337
  name: m.name ?? m.id,
@@ -347,7 +355,9 @@ function buildModels(models, baseURL) {
347
355
  ...params.some((p) => REASONING_PARAMS2.has(p)) ? { reasoning: true } : {},
348
356
  ...params.includes("temperature") ? { temperature: true } : {},
349
357
  ...hasNonTextInput ? { attachment: true } : {},
350
- ...pricingTiers ? { pricingTiers } : {}
358
+ ...pricingTiers ? { pricingTiers } : {},
359
+ ...created ? { release_date: created } : {},
360
+ ...interleaved ? { interleaved } : {}
351
361
  };
352
362
  result[m.id] = entry;
353
363
  }
@@ -379,7 +389,7 @@ function toRuntimeCapabilities(model) {
379
389
  video: output.has("video"),
380
390
  pdf: output.has("pdf")
381
391
  },
382
- interleaved: false
392
+ interleaved: model.interleaved ?? false
383
393
  };
384
394
  }
385
395
  function toRuntimeModels(models, provider) {
@@ -391,7 +401,7 @@ function toRuntimeModels(models, provider) {
391
401
  id: model.id,
392
402
  providerID: provider.id,
393
403
  api: {
394
- id: provider.id,
404
+ id: model.id,
395
405
  url: model.provider?.api ?? providerApi,
396
406
  npm: model.provider?.npm ?? providerNpm
397
407
  },
@@ -417,7 +427,7 @@ function toRuntimeModels(models, provider) {
417
427
  status: "active",
418
428
  options: {},
419
429
  headers: {},
420
- release_date: ""
430
+ release_date: model.release_date ?? ""
421
431
  };
422
432
  }
423
433
  return result;
@@ -609,6 +619,7 @@ var plugin2 = {
609
619
  };
610
620
  var src_default = plugin2;
611
621
  export {
622
+ toRuntimeModels,
612
623
  src_default as default,
613
624
  buildModels,
614
625
  REFRESH_TTL_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcowger/opencode-plexus",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "OpenCode plugin: Plexus provider with dynamic model discovery",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",