opencode-provider-litellm 0.5.1 → 0.5.2

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
@@ -21,34 +21,39 @@ All models and MCP tools from your LiteLLM proxy appear in OpenCode automaticall
21
21
 
22
22
  ### Environment variables
23
23
 
24
- | Variable | Description |
25
- |----------|-------------|
26
- | `LITELLM_URL` | Your LiteLLM proxy base URL |
27
- | `LITELLM_KEY` | API key for the proxy |
28
- | `LITELLM_PROVIDER_ID` | Provider ID in OpenCode (defaults to `LiteLLM`) |
29
- | `LITELLM_GCLOUD_TOKEN_AUTH` | Set to `1` to use Google ADC for auth (makes `LITELLM_KEY` optional) |
30
- | `GOOGLE_APPLICATION_CREDENTIALS` | Path to a Google ADC JSON file (used when `LITELLM_GCLOUD_TOKEN_AUTH=1`) |
24
+ Environment variables take precedence over all other configuration. Use them to keep secrets out of checked-in files.
31
25
 
32
- ### Inline config
26
+ | Variable | Required | Description |
27
+ |----------|----------|-------------|
28
+ | `LITELLM_URL` | Yes | Your LiteLLM proxy base URL |
29
+ | `LITELLM_KEY` | Yes* | API key for the proxy |
30
+ | `LITELLM_PROVIDER_ID` | No | Provider name in OpenCode (default: `LiteLLM`) |
31
+ | `LITELLM_GCLOUD_TOKEN_AUTH` | No | Set to `1` to use Google ADC for auth — makes `LITELLM_KEY` optional |
32
+ | `GOOGLE_APPLICATION_CREDENTIALS` | No | Path to a Google ADC JSON file (used when `LITELLM_GCLOUD_TOKEN_AUTH=1`) |
33
33
 
34
- Alternatively, provide `url` and `apiKey` directly in your `opencode.json`:
34
+ *`LITELLM_KEY` is optional when `LITELLM_GCLOUD_TOKEN_AUTH=1`.
35
+
36
+ ### Plugin options
37
+
38
+ All env vars have an equivalent in the plugin options block in `opencode.json`. **Env vars take precedence** — use options for defaults that can be overridden per environment.
35
39
 
36
40
  ```jsonc
37
41
  {
38
42
  "plugin": [
39
43
  ["opencode-provider-litellm", {
40
- "url": "https://your-litellm-proxy.example.com",
41
- "apiKey": "sk-..."
44
+ "url": "https://your-litellm-proxy.example.com", // LITELLM_URL
45
+ "apiKey": "sk-...", // LITELLM_KEY
46
+ "providerName": "MyLiteLLM", // LITELLM_PROVIDER_ID
47
+ "gcloudTokenAuth": true // LITELLM_GCLOUD_TOKEN_AUTH=1
48
+ // apiKey can be omitted when gcloudTokenAuth is true
42
49
  }]
43
50
  ]
44
51
  }
45
52
  ```
46
53
 
47
- > **Tip:** Environment variables take precedence over inline config. Use env vars to keep secrets out of checked-in files.
48
-
49
54
  ### Google Vertex AI (gcloud token auth)
50
55
 
51
- When your LiteLLM proxy is backed by Google Vertex AI, you can skip `LITELLM_KEY` and let the plugin automatically fetch a gcloud OAuth token:
56
+ When your LiteLLM proxy is backed by Google Vertex AI, you can skip `LITELLM_KEY` and let the plugin automatically fetch a Google OAuth token:
52
57
 
53
58
  ```bash
54
59
  # 1. Authenticate with gcloud (creates an ADC JSON file)
@@ -62,9 +67,12 @@ export LITELLM_GCLOUD_TOKEN_AUTH=1
62
67
  opencode plugin opencode-provider-litellm
63
68
  ```
64
69
 
65
- The plugin reads your [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) JSON file and exchanges the refresh token for an access token before every LLM request. Tokens are cached for 50 minutes.
70
+ The plugin reads your [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) JSON file and exchanges the refresh token for an access token. Tokens are cached for 50 minutes and used for both model discovery at startup and every LLM request.
66
71
 
67
- To use a custom credentials file, set `GOOGLE_APPLICATION_CREDENTIALS` to its path.
72
+ **ADC file locations searched (in order):**
73
+ 1. `GOOGLE_APPLICATION_CREDENTIALS` env var (all platforms)
74
+ 2. `~/.config/gcloud/application_default_credentials.json` (Linux/macOS)
75
+ 3. `%APPDATA%/gcloud/application_default_credentials.json` (Windows)
68
76
 
69
77
  > **Note:** Only `authorized_user` credentials (from `gcloud auth application-default login`) are supported. Service account keys are not yet supported.
70
78
 
@@ -109,22 +117,23 @@ Skills appear in OpenCode's `/skills` menu and are loaded natively by the agent.
109
117
 
110
118
  ## How it works
111
119
 
112
- The plugin uses three OpenCode hooks:
120
+ The plugin uses these OpenCode hooks:
113
121
 
114
122
  | Hook | Purpose |
115
123
  |------|---------|
116
124
  | `config` | Discovers models from LiteLLM and injects them into OpenCode |
117
125
  | `auth` | Provides a `/connect` entry point for pasting an API key |
118
126
  | `tool` | Exposes discovered MCP tools as native OpenCode tools |
119
- | `chat.headers` | Injects `Authorization: Bearer <token>` when `LITELLM_GCLOUD_TOKEN_AUTH=1` |
127
+ | `chat.headers` | Injects `Authorization: Bearer <token>` when gcloud token auth is enabled |
120
128
 
121
129
  ## Troubleshooting
122
130
 
123
131
  | Problem | Solution |
124
132
  |---------|----------|
125
- | "Plugin config error" | Set `LITELLM_URL` and `LITELLM_KEY`, or add `url`/`apiKey` to plugin options |
133
+ | "Plugin config error" | Set `LITELLM_URL` and `LITELLM_KEY` (or `LITELLM_GCLOUD_TOKEN_AUTH=1`) |
126
134
  | "Access denied" (403) | Verify the API key has access to the LiteLLM proxy |
127
- | "No models discovered" | Check that the proxy is reachable and the `/health` endpoint responds |
135
+ | "No models discovered" | Check the proxy is reachable and `/health` responds |
136
+ | No models with gcloud auth | Verify `gcloud auth application-default login` has been run and the ADC file exists |
128
137
  | Skills not showing | Verify the proxy-sidecar is running and the skills URL is in `opencode.json` |
129
138
 
130
139
  ## Development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-provider-litellm",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "OpenCode plugin for any LiteLLM proxy — auto-discovers models, auth, and capabilities",
5
5
  "type": "module",
6
6
  "exports": {
@@ -83,7 +83,7 @@ describe('discoverModels', () => {
83
83
  tool_call: true,
84
84
  reasoning: false,
85
85
  limit: { context: 8192, output: 8192 },
86
- cost: { input: 0.0001, output: 0.0003 },
86
+ cost: { input: 100, output: 300 },
87
87
  modalities: { input: ['text'], output: ['text'] },
88
88
  },
89
89
  'qwen3-32b': {
@@ -91,7 +91,7 @@ describe('discoverModels', () => {
91
91
  tool_call: true,
92
92
  reasoning: true,
93
93
  limit: { context: 32768, output: 32768 },
94
- cost: { input: 0.00005, output: 0.00015 },
94
+ cost: { input: 50, output: 150 },
95
95
  modalities: { input: ['text'], output: ['text'] },
96
96
  },
97
97
  })
@@ -107,6 +107,51 @@ describe('discoverModels', () => {
107
107
  expect(getToken).toHaveBeenCalled()
108
108
  })
109
109
 
110
+ it('converts per-token cost to per-1M tokens with cache costs', async () => {
111
+ const mockFetch = vi.fn()
112
+ .mockResolvedValueOnce({
113
+ ok: true,
114
+ status: 200,
115
+ json: async () => ({
116
+ healthy_endpoints: [
117
+ { model: 'anthropic/claude-sonnet', model_id: 'uuid-1' },
118
+ ],
119
+ }),
120
+ })
121
+ .mockResolvedValueOnce({
122
+ ok: true,
123
+ status: 200,
124
+ json: async () => ({
125
+ data: [{
126
+ model_name: 'anthropic/claude-sonnet',
127
+ model_info: {
128
+ max_input_tokens: 1_000_000,
129
+ max_output_tokens: 64_000,
130
+ supports_function_calling: true,
131
+ supports_reasoning: true,
132
+ supports_vision: true,
133
+ supports_pdf_input: true,
134
+ // Per-token costs (LiteLLM format)
135
+ input_cost_per_token: 0.000005,
136
+ output_cost_per_token: 0.000025,
137
+ cache_read_input_token_cost: 0.0000005,
138
+ cache_creation_input_token_cost: 0.00000375,
139
+ },
140
+ }],
141
+ }),
142
+ })
143
+ vi.stubGlobal('fetch', mockFetch)
144
+
145
+ const result = await discoverModels(config, getToken)
146
+
147
+ expect(result['anthropic/claude-sonnet']?.cost).toEqual({
148
+ input: 5, // 0.000005 * 1M
149
+ output: 25, // 0.000025 * 1M
150
+ cache_read: 0.5, // 0.0000005 * 1M
151
+ cache_write: 3.75, // 0.00000375 * 1M
152
+ })
153
+ })
154
+
110
155
  it('returns empty object on timeout', async () => {
111
156
  vi.useFakeTimers()
112
157
 
package/src/discovery.ts CHANGED
@@ -117,16 +117,17 @@ export async function discoverModels(
117
117
  }
118
118
 
119
119
  // Add cost info if available
120
+ // LiteLLM returns cost per single token; opencode expects cost per 1M tokens
120
121
  if (info.input_cost_per_token != null && info.output_cost_per_token != null) {
121
122
  modelConfig.cost = {
122
- input: info.input_cost_per_token,
123
- output: info.output_cost_per_token,
123
+ input: info.input_cost_per_token * 1_000_000,
124
+ output: info.output_cost_per_token * 1_000_000,
124
125
  }
125
126
  if (info.cache_read_input_token_cost != null) {
126
- modelConfig.cost.cache_read = info.cache_read_input_token_cost
127
+ modelConfig.cost.cache_read = info.cache_read_input_token_cost * 1_000_000
127
128
  }
128
129
  if (info.cache_creation_input_token_cost != null) {
129
- modelConfig.cost.cache_write = info.cache_creation_input_token_cost
130
+ modelConfig.cost.cache_write = info.cache_creation_input_token_cost * 1_000_000
130
131
  }
131
132
  }
132
133