@staticeng/opencode-litellm 0.3.2 → 0.3.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/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -188,3 +188,7 @@ publisher for package `@staticeng/opencode-litellm` with this repository, workfl
188
188
  The exact discovered aliases `5090-local-model` and `5090-local-model-codex` use the Qwen3.8 Flash Next Strata contract: text/image input, text output, reasoning, tools, and 262144 tokens of shared context. The output ceiling is 262144 within that shared window; it is not an additional output budget. Pricing continues to come from LiteLLM metadata.
189
189
 
190
190
  Both aliases preserve their routable IDs and use Chat Completions in OpenCode. Their public variants are `off`, `low`, `medium`, and `xhigh` (default `xhigh`): `off` sends `reasoning_effort: "none"`; the other variants send their named effort. Strata accepts `high` and `max` as aliases of native `xhigh`; they are not distinct variants. This differs from the cloud Qwen contract and does not alter it. The `-codex` alias remains available to Codex via LiteLLM's separate Responses adapter. No alias is fabricated when discovery omits it; explicit model/provider overrides still win.
191
+
192
+ ## Native image models (OpenCode 2.2+)
193
+
194
+ Current V2 discovery includes LiteLLM `image_generation` deployments as `type: "image"` using the native OpenAI-compatible Images driver. They are available to OpenCode image generation and OpenChamber image model settings, separately from conversation models. Explicit `includeModes` and model filters still apply. Legacy and transitional catalogs omit image deployments. Generation uses `/images/generations`; reference images use `/images/edits`.
package/dist/helpers.d.ts CHANGED
File without changes
package/dist/helpers.js CHANGED
File without changes
package/dist/index.d.ts CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
package/dist/litellm.d.ts CHANGED
File without changes
package/dist/litellm.js CHANGED
File without changes
package/dist/mapping.d.ts CHANGED
@@ -18,7 +18,7 @@ export declare function mapModels(response: unknown, options?: LiteLLMPluginOpti
18
18
  [k: string]: OpenCodeModel;
19
19
  };
20
20
  export declare function buildProvider(models: unknown, options?: LiteLLMPluginOptions): OpenCodeProvider;
21
- export declare function mapModelsV2(models: unknown, options?: LiteLLMPluginOptions): {
21
+ export declare function mapModelsV2(models: unknown, options?: LiteLLMPluginOptions, nativeImages?: boolean): {
22
22
  [k: string]: OpenCodeModelV2 | {
23
23
  variants: {
24
24
  id: string;
@@ -26,6 +26,7 @@ export declare function mapModelsV2(models: unknown, options?: LiteLLMPluginOpti
26
26
  [x: string]: unknown;
27
27
  };
28
28
  }[];
29
+ type?: "image";
29
30
  family?: string;
30
31
  name?: string;
31
32
  api?: {
@@ -63,4 +64,4 @@ export declare function mapModelsV2(models: unknown, options?: LiteLLMPluginOpti
63
64
  };
64
65
  };
65
66
  };
66
- export declare function buildProviderV2(models: unknown, options?: LiteLLMPluginOptions): OpenCodeProviderV2;
67
+ export declare function buildProviderV2(models: unknown, options?: LiteLLMPluginOptions, nativeImages?: boolean): OpenCodeProviderV2;
package/dist/mapping.js CHANGED
@@ -167,7 +167,7 @@ export function normalizeModelListResponse(response) {
167
167
  function mode(model) {
168
168
  return asString(model.model_info?.mode) ?? "chat";
169
169
  }
170
- function allowedByFilters(model, options) {
170
+ function allowedByFilters(model, options, nativeImages = false) {
171
171
  const name = modelName(model);
172
172
  if (!name)
173
173
  return false;
@@ -186,8 +186,13 @@ function allowedByFilters(model, options) {
186
186
  if (options.excludeModels?.includes(name))
187
187
  return false;
188
188
  const modelMode = mode(model);
189
+ // Images require the typed catalog and media resolver introduced in current V2.
190
+ if (modelMode === "image_generation" && !nativeImages)
191
+ return false;
189
192
  if (options.includeModes?.length)
190
193
  return options.includeModes.includes(modelMode);
194
+ if (modelMode === "image_generation")
195
+ return nativeImages;
191
196
  return DEFAULT_MODES.has(modelMode) && !NON_CHAT_MODES.has(modelMode);
192
197
  }
193
198
  function cost(model) {
@@ -330,6 +335,15 @@ function generatedModel(model, tombstones = false) {
330
335
  const name = modelName(model);
331
336
  if (!name)
332
337
  return undefined;
338
+ if (mode(model) === "image_generation")
339
+ return {
340
+ id: name,
341
+ name,
342
+ type: "image",
343
+ provider: { npm: "@opencode/ai/providers/openai-compatible/images" },
344
+ modalities: { input: supportedModalities(["text", ...(model.model_info?.supported_modalities ?? [])]), output: ["image"] },
345
+ tool_call: false,
346
+ };
333
347
  const mappedModalities = modalities(model);
334
348
  const discoveredVariants = supportsReasoning(model) ? reasoningVariants(model) : undefined;
335
349
  const suppressDiscoveredVariants = modelContract(name)?.suppressDiscoveredVariants === true;
@@ -403,6 +417,7 @@ function v2Model(model, baseURL) {
403
417
  const name = asString(model.name);
404
418
  const family = asString(model.family);
405
419
  return {
420
+ ...(model.type === "image" ? { type: "image" } : {}),
406
421
  ...(family ? { family } : {}),
407
422
  ...(name ? { name } : {}),
408
423
  api: {
@@ -425,10 +440,10 @@ function v2Model(model, baseURL) {
425
440
  ...(v2Variants(model) ? { variants: v2Variants(model) } : {}),
426
441
  };
427
442
  }
428
- function mapModelsInternal(response, options, tombstones) {
443
+ function mapModelsInternal(response, options, tombstones, nativeImages = false) {
429
444
  const models = normalizeModelListResponse(response);
430
445
  return Object.fromEntries(models
431
- .filter((model) => allowedByFilters(model, options))
446
+ .filter((model) => allowedByFilters(model, options, nativeImages))
432
447
  .map((model) => {
433
448
  const name = modelName(model);
434
449
  const generated = generatedModel(model, tombstones);
@@ -458,8 +473,8 @@ export function buildProvider(models, options = {}) {
458
473
  models: mapModelsInternal(models, options, true),
459
474
  }, options.providerOverrides);
460
475
  }
461
- export function mapModelsV2(models, options = {}) {
462
- const legacy = mapModels(models, options);
476
+ export function mapModelsV2(models, options = {}, nativeImages = false) {
477
+ const legacy = mapModelsInternal(models, options, false, nativeImages);
463
478
  return Object.fromEntries(Object.entries(legacy).map(([name, model]) => {
464
479
  const mapped = v2Model(model, options.baseURL);
465
480
  const variants = contractV2Variants(name, model, options.overrides?.[name]);
@@ -504,7 +519,7 @@ function responsesBody(options) {
504
519
  }
505
520
  return body;
506
521
  }
507
- export function buildProviderV2(models, options = {}) {
522
+ export function buildProviderV2(models, options = {}, nativeImages = false) {
508
523
  return deepMerge({
509
524
  name: options.providerName ?? "LiteLLM",
510
525
  api: {
@@ -518,6 +533,6 @@ export function buildProviderV2(models, options = {}) {
518
533
  ...(options.apiKey ? { apiKey: options.apiKey } : {}),
519
534
  },
520
535
  },
521
- models: mapModelsV2(models, options),
536
+ models: mapModelsV2(models, options, nativeImages),
522
537
  }, options.providerOverrides);
523
538
  }
package/dist/merge.d.ts CHANGED
File without changes
package/dist/merge.js CHANGED
File without changes
File without changes
File without changes
package/dist/options.d.ts CHANGED
File without changes
package/dist/options.js CHANGED
File without changes
package/dist/types.d.ts CHANGED
@@ -27,6 +27,7 @@ export type ModelContract = Readonly<{
27
27
  metadata?: ModelContractMetadata;
28
28
  }>;
29
29
  export type OpenCodeModel = JsonObject & {
30
+ type?: "image";
30
31
  provider?: {
31
32
  npm?: string;
32
33
  api?: string;
@@ -54,6 +55,7 @@ export type OpenCodeModel = JsonObject & {
54
55
  variants?: Record<string, JsonObject>;
55
56
  };
56
57
  export type OpenCodeModelV2 = JsonObject & {
58
+ type?: "image";
57
59
  family?: string;
58
60
  name?: string;
59
61
  api?: {
package/dist/types.js CHANGED
File without changes
package/dist/v2.d.ts CHANGED
File without changes
package/dist/v2.js CHANGED
@@ -5,6 +5,13 @@ import { resolveOptions } from "./options.js";
5
5
  const CHAT_PACKAGE = "@opencode/ai/providers/openai-compatible";
6
6
  // The 2.0.16 binary bundles this driver, but not openai-compatible/responses.
7
7
  const RESPONSES_PACKAGE = "@opencode/ai/providers/openai/responses";
8
+ /** Image catalog types and their resolver are available in OpenCode 2.2+. */
9
+ function supportsNativeImages(ctx) {
10
+ const version = ctx.app?.version?.match(/^v?(\d+)\.(\d+)\.(\d+)(?:$|[-+])/);
11
+ if (!version)
12
+ return false;
13
+ return Number(version[1]) > 2 || (Number(version[1]) === 2 && Number(version[2]) >= 2);
14
+ }
8
15
  function packageName(name) {
9
16
  if (name === "@ai-sdk/openai" || name === "@opencode/ai/providers/openai-compatible/responses")
10
17
  return RESPONSES_PACKAGE;
@@ -49,6 +56,7 @@ function currentModel(providerID, id, config, providerPackage) {
49
56
  }
50
57
  return {
51
58
  id,
59
+ ...(config.type === "image" ? { type: "image" } : {}),
52
60
  modelID: config.api?.id ?? id,
53
61
  providerID,
54
62
  name: config.name ?? id,
@@ -250,7 +258,7 @@ const LiteLLMPluginV2 = {
250
258
  // Opaque Responses aliases need the same transport as GPT-named models.
251
259
  overrides[id] = deepMerge({ provider: { npm: "@ai-sdk/openai" } }, overrides[id]);
252
260
  }
253
- provider = buildProviderV2(models, { ...options, overrides });
261
+ provider = buildProviderV2(models, { ...options, overrides }, supportsNativeImages(ctx));
254
262
  for (const model of models) {
255
263
  if (model.model_info?.use_responses_lite !== true)
256
264
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staticeng/opencode-litellm",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "OpenCode plugin that discovers LiteLLM models at runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",