# opencode-litellm

Runtime LiteLLM model autodiscovery for OpenCode. The plugin fetches LiteLLM deployment and model-group metadata at startup and adds an in-memory provider named `LiteLLM` by default.

## Install

Install the package by npm name, then add it to OpenCode `plugin` config:

```sh
npm install @staticeng/opencode-litellm
```

```json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    [
      "@staticeng/opencode-litellm",
      {
        "baseURL": "https://litellm.example.com/v1",
        "apiKeyEnv": "LITELLM_API_KEY"
      }
    ]
  ]
}
```

Set the API key in your environment before starting OpenCode:

```sh
export LITELLM_API_KEY="..."
```

Do not put real API keys in `opencode.json`. If you pass `apiKey` directly, the plugin uses it only in memory and never logs it, but environment variables are recommended.

Shared or synchronized OpenCode configuration should use the published package name as shown above. Do not use a local path or `file://` plugin reference for shared configuration.

## Options

| Option | Default | Purpose |
| --- | --- | --- |
| `baseURL` | `LITELLM_BASE_URL` | LiteLLM proxy base URL. `/v1` URLs are supported. |
| `apiKey` | env value | Bearer token for LiteLLM. |
| `apiKeyEnv` | `LITELLM_API_KEY` | Environment variable used when `apiKey` is not set. |
| `providerKey` | `LiteLLM` | Key written to legacy `cfg.provider` and V2 `cfg.providers`. |
| `providerName` | `LiteLLM` | Human provider name. |
| `overrides` | `{}` | Per-model deep-merge overrides. |
| `providerOverrides` | `{}` | Provider-level deep-merge overrides. |
| `includeModes` | `chat`, `responses` | LiteLLM modes to include. |
| `includeModels` | all included modes | Optional allowlist by model name. |
| `excludeModels` | none | Optional denylist by model name. |
| `timeoutMs` | `30000` | Per-request discovery timeout. |
| `strict` | `false` | Throw on fetch/mapping failures instead of warning. |

The LiteLLM key must have permission to call `GET /model/info` and root `GET /model_group/info`.

## Overrides

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.

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.

```json
{
  "plugin": [
    [
      "@staticeng/opencode-litellm",
      {
        "baseURL": "https://litellm.example.com/v1",
        "apiKeyEnv": "LITELLM_API_KEY",
        "overrides": {
          "chatgpt/gpt-5.5": {
            "limit": { "context": 262144, "output": 128000 },
            "variants": {
              "high": { "reasoningEffort": "high" }
            },
            "interleaved": { "field": "reasoning_details" }
          }
        },
        "providerOverrides": {
          "options": {
            "timeout": 600000
          }
        }
      }
    ]
  ]
}
```

Overrides win over built-in contracts and generated values through a recursive deep merge. Model overrides are applied first and provider overrides last. Arrays and scalar values are replaced rather than concatenated.

The built-in mode matrix is:

| Contract | Modes in order | Default |
| --- | --- | --- |
| GPT-5.4 / GPT-5.4 Mini | `none`, `low`, `medium`, `high`, `xhigh` | `none` |
| GPT-5.5 | `none`, `low`, `medium`, `high`, `xhigh` | `medium` |
| GPT-5.6 Luna / Sol / Terra | `none`, `low`, `medium`, `high`, `xhigh`, `max` | `medium` |
| GPT-5.3 Codex Spark | `none`, `low`, `medium`, `high`, `xhigh` | `high` |
| DeepSeek V4 Flash | `off`, `low`, `high`, `max` | `max` |
| Qwen3.8 | `off`, `low`, `medium`, `xhigh` | `xhigh` |

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.

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.

## Runtime Behavior

- Fetches `${baseURL}/model/info`, then root `/model_group/info`, with separate authenticated and timed requests.
- Preserves `/v1` for model-info discovery but resolves model-group discovery from the LiteLLM origin.
- Adds or replaces both legacy `cfg.provider[providerKey]` and V2 `cfg.providers[providerKey]` during the config hook.
- Keeps namespaced deployment IDs and augments them with exact bare model-group IDs; exact collisions are enriched deterministically.
- Skips malformed group rows without a nonblank `model_group` and exposes reasoning capability on legacy and V2 model metadata.
- Applies exact built-in model contracts as defaults, then applies explicit model and provider overrides last.
- Filters retired normal GPT-5.3 Codex aliases without redirecting them; discovered Spark aliases remain independent.
- Warns safely and keeps deployment models when group discovery fails in non-strict mode; strict mode mutates neither config surface on failure.
- Does not write `opencode.json`, cache model metadata, or persist generated provider data.
- Emits the required `limit.output` as `0` when LiteLLM reports a context/input limit but no output
  limit. The zero sentinel retains known metadata without inventing a positive output allowance.
- Includes `chat` and `responses` modes by default; non-chat modes such as `embedding` and `rerank` are excluded unless explicitly included.
- No HuggingFace fallback is included in v1.

## Publish Notes

The package is ESM-only and publishes the compiled `dist/` directory.

```sh
npm install
npm run build
npm test
npm pack --dry-run
```

Confirm package contents before publishing and keep secrets out of fixtures, docs, logs, and config examples.

Publishing uses npm trusted publishing from the `npm-production` GitHub environment. Provenance
requires this GitHub repository to remain public. Before a release, configure npm's trusted
publisher for package `@staticeng/opencode-litellm` with this repository, workflow
`publish.yml`, and environment `npm-production`; no npm token is used by the workflow.
