@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.
- package/README.md +3 -1
- package/dist/index.js +36 -12
- 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
|
|
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
|
|
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
|
-
|
|
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
|
|
59
|
-
|
|
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 =
|
|
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 (!
|
|
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:
|
|
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,
|