@staticeng/opencode-litellm 0.2.1 → 0.2.3

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
@@ -56,7 +56,7 @@ The LiteLLM key must have permission to call `GET /model/info` and root `GET /mo
56
56
 
57
57
  ## Overrides
58
58
 
59
- LiteLLM metadata does not expose every OpenCode model field. Version 0.2.0 includes exact built-in contracts for the currently approved GPT-5.4, GPT-5.5, GPT-5.6, GPT-5.3 Codex Spark, DeepSeek V4 Flash, and Qwen3.8 aliases. Use `overrides` to customize those defaults or to add metadata for unknown models.
59
+ LiteLLM metadata does not expose every OpenCode model field. The plugin includes exact built-in contracts for the currently approved GPT-5.4, GPT-5.5, GPT-5.6, DeepSeek V4 Flash, and Qwen3.8 aliases. Use `overrides` to customize those defaults or to add metadata for unknown models.
60
60
 
61
61
  When LiteLLM publishes `model_info.supported_reasoning_efforts`, the plugin maps that list to OpenCode variants automatically for unknown models. Known contracts use exact alias membership and win over absent, incomplete, or conflicting discovery metadata. No substring, suffix, provider namespace, or base-model inference is used.
62
62
 
@@ -97,13 +97,15 @@ The built-in mode matrix is:
97
97
  | GPT-5.4 / GPT-5.4 Mini | `none`, `low`, `medium`, `high`, `xhigh` | `none` |
98
98
  | GPT-5.5 | `none`, `low`, `medium`, `high`, `xhigh` | `medium` |
99
99
  | GPT-5.6 Luna / Sol / Terra | `none`, `low`, `medium`, `high`, `xhigh`, `max` | `medium` |
100
- | GPT-5.3 Codex Spark | `none`, `low`, `medium`, `high`, `xhigh` | `high` |
101
100
  | DeepSeek V4 Flash | `off`, `low`, `high`, `max` | `max` |
102
- | Qwen3.8 | `off`, `low`, `medium`, `xhigh` | `xhigh` |
101
+ | Qwen3.8 27B refusal dial | `off`, `low`, `medium`, `xhigh` | `xhigh` |
102
+ | Qwen3.8 Flash Next | `off`, `low` | `low` |
103
103
 
104
- DeepSeek `off` sends canonical `none`. Qwen `off` sends only `chat_template_kwargs.enable_thinking=false`, with no reasoning effort. `ultra` is not generated by a built-in contract. For requests without an explicit named variant, the plugin supplies the row's contract default through model options; an explicit variant is merged afterward and wins. Qwen's explicit `off` cleanup removes the default `xhigh` effort before serialization. The `variantDefault` compatibility marker is UI-only and is stripped before requests for exact built-in aliases.
104
+ DeepSeek `off` sends canonical `none`. Qwen `off` sends only `chat_template_kwargs.enable_thinking=false`, with no reasoning effort. `ultra` is not generated by a built-in contract. For requests without an explicit named variant, the plugin supplies the row's contract default through model options; an explicit variant is merged afterward and wins. Qwen's explicit `off` cleanup removes the model's default effort before serialization. The `variantDefault` compatibility marker is UI-only and is stripped before requests for exact built-in aliases.
105
105
 
106
- Normal GPT-5.3 Codex aliases are retired and filtered even if stale discovery metadata still reports them. GPT-5.3 Codex Spark is a separate exact contract and remains available whenever discovered. Unlisted aliases such as `defend/gpt-5.5` do not inherit a contract.
106
+ The exact dynamically discovered alias `qwen3.8-flash-next` also receives verified static metadata that overrides incomplete or conflicting LiteLLM group metadata: text and image input, text output, attachment, reasoning, tool calling, and a 262144-token context. Its output limit remains unknown and is represented by the package's `0` sentinel; pricing and other modalities are not supplied. The contract is still discovery-gated and never creates the alias when LiteLLM omits it.
107
+
108
+ Normal GPT-5.3 Codex and GPT-5.3 Codex Spark aliases are retired and filtered even if stale discovery metadata still reports them. Unlisted aliases such as `defend/gpt-5.5` do not inherit a contract.
107
109
 
108
110
  ## Runtime Behavior
109
111
 
@@ -113,7 +115,7 @@ Normal GPT-5.3 Codex aliases are retired and filtered even if stale discovery me
113
115
  - Keeps namespaced deployment IDs and augments them with exact bare model-group IDs; exact collisions are enriched deterministically.
114
116
  - Skips malformed group rows without a nonblank `model_group` and exposes reasoning capability on legacy and V2 model metadata.
115
117
  - Applies exact built-in model contracts as defaults, then applies explicit model and provider overrides last.
116
- - Filters retired normal GPT-5.3 Codex aliases without redirecting them; discovered Spark aliases remain independent.
118
+ - Filters retired normal and Spark GPT-5.3 Codex aliases without redirecting them.
117
119
  - Warns safely and keeps deployment models when group discovery fails in non-strict mode; strict mode mutates neither config surface on failure.
118
120
  - Does not write `opencode.json`, cache model metadata, or persist generated provider data.
119
121
  - Emits the required `limit.output` as `0` when LiteLLM reports a context/input limit but no output
package/dist/mapping.js CHANGED
@@ -274,12 +274,23 @@ function contractDefaults(name, tombstones = false) {
274
274
  const disabled = entry.family === "DeepSeek V4 Flash"
275
275
  ? ["medium", "xhigh"]
276
276
  : entry.family === "Qwen3.8"
277
- ? ["none", "minimal", "high", "max"]
277
+ ? ["none", "minimal", "low", "medium", "high", "xhigh", "max", ...(entry.metadata ? ["ultra"] : [])].filter((effort) => !entry.modes.some((mode) => mode.id === effort))
278
278
  : [];
279
279
  for (const effort of disabled)
280
280
  variants[effort] = { disabled: true };
281
281
  }
282
282
  return {
283
+ ...(entry.metadata
284
+ ? {
285
+ modalities: {
286
+ input: [...entry.metadata.modalities.input],
287
+ output: [...entry.metadata.modalities.output],
288
+ },
289
+ attachment: entry.metadata.attachment,
290
+ tool_call: entry.metadata.tool_call,
291
+ limit: { ...entry.metadata.limit },
292
+ }
293
+ : {}),
283
294
  reasoning: true,
284
295
  options: { ...defaultMode.legacy, variantDefault: entry.variantDefault },
285
296
  variants,
@@ -320,7 +331,9 @@ function generatedModel(model, tombstones = false) {
320
331
  ...(supportsReasoning(model) && reasoningVariants(model) ? { variants: reasoningVariants(model) } : {}),
321
332
  ...(model.model_info?.supports_function_calling || model.model_info?.supports_tool_choice ? { tool_call: true } : {}),
322
333
  };
323
- return deepMerge(generated, contractDefaults(name, tombstones));
334
+ const defaults = contractDefaults(name, tombstones);
335
+ const merged = deepMerge(generated, defaults);
336
+ return defaults?.limit ? { ...merged, limit: { ...defaults.limit } } : merged;
324
337
  }
325
338
  function finalizedModel(model, override) {
326
339
  const input = supportedModalities(model.modalities?.input ?? [TEXT_MODALITY]);
@@ -12,7 +12,7 @@ function effortModes(ids) {
12
12
  v2Body: freezeJson({ reasoning_effort: id }),
13
13
  })));
14
14
  }
15
- function contract(family, aliases, modes, defaultMode) {
15
+ function contract(family, aliases, modes, defaultMode, metadata) {
16
16
  if (!modes.some((mode) => mode.id === defaultMode))
17
17
  throw new Error(`Model contract ${family} has an invalid default`);
18
18
  return Object.freeze({
@@ -22,6 +22,7 @@ function contract(family, aliases, modes, defaultMode) {
22
22
  defaultMode,
23
23
  reasoning: true,
24
24
  variantDefault: false,
25
+ ...(metadata ? { metadata } : {}),
25
26
  });
26
27
  }
27
28
  const GPT_STANDARD_MODES = effortModes(["none", "low", "medium", "high", "xhigh"]);
@@ -43,6 +44,16 @@ const QWEN_MODES = Object.freeze([
43
44
  Object.freeze({ id: "medium", legacy: freezeJson({ reasoningEffort: "medium" }), v2Body: freezeJson({ reasoning_effort: "medium" }) }),
44
45
  Object.freeze({ id: "xhigh", legacy: freezeJson({ reasoningEffort: "xhigh" }), v2Body: freezeJson({ reasoning_effort: "xhigh" }) }),
45
46
  ]);
47
+ const QWEN_FLASH_NEXT_MODES = Object.freeze(QWEN_MODES.slice(0, 2));
48
+ const QWEN_FLASH_NEXT_METADATA = Object.freeze({
49
+ modalities: Object.freeze({
50
+ input: Object.freeze(["text", "image"]),
51
+ output: Object.freeze(["text"]),
52
+ }),
53
+ attachment: true,
54
+ tool_call: true,
55
+ limit: Object.freeze({ context: 262144, output: 0 }),
56
+ });
46
57
  const chatGptAliases = (model) => Object.freeze([
47
58
  model,
48
59
  `chatgpt/${model}`,
@@ -56,11 +67,14 @@ export const MODEL_CONTRACTS = Object.freeze([
56
67
  contract("GPT-5.6 Luna", chatGptAliases("gpt-5.6-luna"), GPT_MAX_MODES, "medium"),
57
68
  contract("GPT-5.6 Sol", chatGptAliases("gpt-5.6-sol"), GPT_MAX_MODES, "medium"),
58
69
  contract("GPT-5.6 Terra", chatGptAliases("gpt-5.6-terra"), GPT_MAX_MODES, "medium"),
59
- contract("GPT-5.3 Codex Spark", chatGptAliases("gpt-5.3-codex-spark"), GPT_STANDARD_MODES, "high"),
60
70
  contract("DeepSeek V4 Flash", ["deepseek-v4-flash-fp8-mtp", "deepseek-v4-flash-fp8-mtp-norefusal"], DEEPSEEK_MODES, "max"),
61
71
  contract("Qwen3.8", ["qwen3.8-27b-refusal-dial"], QWEN_MODES, "xhigh"),
72
+ contract("Qwen3.8", ["qwen3.8-flash-next"], QWEN_FLASH_NEXT_MODES, "low", QWEN_FLASH_NEXT_METADATA),
73
+ ]);
74
+ export const RETIRED_MODEL_ALIASES = Object.freeze([
75
+ ...chatGptAliases("gpt-5.3-codex"),
76
+ ...chatGptAliases("gpt-5.3-codex-spark"),
62
77
  ]);
63
- export const RETIRED_MODEL_ALIASES = Object.freeze(chatGptAliases("gpt-5.3-codex"));
64
78
  const contractsByAlias = new Map();
65
79
  for (const entry of MODEL_CONTRACTS) {
66
80
  for (const alias of entry.aliases) {
package/dist/types.d.ts CHANGED
@@ -4,6 +4,18 @@ export type ModelContractMode = Readonly<{
4
4
  legacy: Readonly<JsonObject>;
5
5
  v2Body: Readonly<JsonObject>;
6
6
  }>;
7
+ export type ModelContractMetadata = Readonly<{
8
+ modalities: Readonly<{
9
+ input: readonly string[];
10
+ output: readonly string[];
11
+ }>;
12
+ attachment: boolean;
13
+ tool_call: boolean;
14
+ limit: Readonly<{
15
+ context: number;
16
+ output: number;
17
+ }>;
18
+ }>;
7
19
  export type ModelContract = Readonly<{
8
20
  family: string;
9
21
  aliases: readonly string[];
@@ -11,6 +23,7 @@ export type ModelContract = Readonly<{
11
23
  defaultMode: string;
12
24
  reasoning: true;
13
25
  variantDefault: false;
26
+ metadata?: ModelContractMetadata;
14
27
  }>;
15
28
  export type OpenCodeModel = JsonObject & {
16
29
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staticeng/opencode-litellm",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "OpenCode plugin that discovers LiteLLM models at runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",