@staticeng/opencode-litellm 0.1.0
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 +21 -0
- package/README.md +109 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +37 -0
- package/dist/litellm.d.ts +3 -0
- package/dist/litellm.js +33 -0
- package/dist/mapping.d.ts +5 -0
- package/dist/mapping.js +120 -0
- package/dist/merge.d.ts +1 -0
- package/dist/merge.js +11 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.js +20 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StaticDuo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# opencode-litellm
|
|
2
|
+
|
|
3
|
+
Runtime LiteLLM model autodiscovery for OpenCode. The plugin fetches LiteLLM `/model/info` at OpenCode startup and adds an in-memory provider named `LiteLLM` by default.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install the package by npm name, then add it to OpenCode `plugin` config:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @staticeng/opencode-litellm
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"$schema": "https://opencode.ai/config.json",
|
|
16
|
+
"plugin": [
|
|
17
|
+
[
|
|
18
|
+
"@staticeng/opencode-litellm",
|
|
19
|
+
{
|
|
20
|
+
"baseURL": "https://litellm.example.com/v1",
|
|
21
|
+
"apiKeyEnv": "LITELLM_API_KEY"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Set the API key in your environment before starting OpenCode:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
export LITELLM_API_KEY="..."
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
## Options
|
|
37
|
+
|
|
38
|
+
| Option | Default | Purpose |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `baseURL` | `LITELLM_BASE_URL` | LiteLLM proxy base URL. `/v1` URLs are supported. |
|
|
41
|
+
| `apiKey` | env value | Bearer token for LiteLLM. |
|
|
42
|
+
| `apiKeyEnv` | `LITELLM_API_KEY` | Environment variable used when `apiKey` is not set. |
|
|
43
|
+
| `providerKey` | `LiteLLM` | Key written to `cfg.provider[providerKey]`. |
|
|
44
|
+
| `providerName` | `LiteLLM` | Human provider name. |
|
|
45
|
+
| `overrides` | `{}` | Per-model deep-merge overrides. |
|
|
46
|
+
| `providerOverrides` | `{}` | Provider-level deep-merge overrides. |
|
|
47
|
+
| `includeModes` | `chat`, `responses` | LiteLLM modes to include. |
|
|
48
|
+
| `includeModels` | all included modes | Optional allowlist by model name. |
|
|
49
|
+
| `excludeModels` | none | Optional denylist by model name. |
|
|
50
|
+
| `timeoutMs` | `30000` | `/model/info` request timeout. |
|
|
51
|
+
| `strict` | `false` | Throw on fetch/mapping failures instead of warning. |
|
|
52
|
+
|
|
53
|
+
The LiteLLM key must have permission to call `GET /model/info`.
|
|
54
|
+
|
|
55
|
+
## Overrides
|
|
56
|
+
|
|
57
|
+
LiteLLM metadata does not expose every OpenCode model field. Use `overrides` to add variants, thinking controls, cache costs, interleaved reasoning settings, limits, modalities, timeouts, or provider-specific options.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"plugin": [
|
|
62
|
+
[
|
|
63
|
+
"@staticeng/opencode-litellm",
|
|
64
|
+
{
|
|
65
|
+
"baseURL": "https://litellm.example.com/v1",
|
|
66
|
+
"apiKeyEnv": "LITELLM_API_KEY",
|
|
67
|
+
"overrides": {
|
|
68
|
+
"chatgpt/gpt-5.5": {
|
|
69
|
+
"limit": { "context": 262144, "output": 128000 },
|
|
70
|
+
"variants": {
|
|
71
|
+
"high": { "options": { "reasoning_effort": "high" } }
|
|
72
|
+
},
|
|
73
|
+
"interleaved": { "field": "reasoning_details" }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"providerOverrides": {
|
|
77
|
+
"options": {
|
|
78
|
+
"timeout": 600000
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Overrides win over generated values through a recursive deep merge. Arrays and scalar values are replaced rather than concatenated.
|
|
88
|
+
|
|
89
|
+
## Runtime Behavior
|
|
90
|
+
|
|
91
|
+
- Fetches `${baseURL}/model/info` with `Authorization: Bearer <token>`.
|
|
92
|
+
- Handles base URLs with or without a trailing slash, including `/v1`.
|
|
93
|
+
- Adds or replaces `cfg.provider[providerKey]` during OpenCode's plugin config hook.
|
|
94
|
+
- Does not write `opencode.json`, cache model metadata, or persist generated provider data.
|
|
95
|
+
- Includes `chat` and `responses` modes by default; non-chat modes such as `embedding` and `rerank` are excluded unless explicitly included.
|
|
96
|
+
- No HuggingFace fallback is included in v1.
|
|
97
|
+
|
|
98
|
+
## Publish Notes
|
|
99
|
+
|
|
100
|
+
The package is ESM-only and publishes the compiled `dist/` directory.
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
npm install
|
|
104
|
+
npm run build
|
|
105
|
+
npm test
|
|
106
|
+
npm pack --dry-run
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Confirm package contents before publishing and keep secrets out of fixtures, docs, logs, and config examples.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Config, PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
import type { LiteLLMPluginOptions } from "./types.js";
|
|
3
|
+
export declare const LiteLLMPlugin: (ctx: PluginInput, options?: LiteLLMPluginOptions) => Promise<{
|
|
4
|
+
config: (cfg: Config) => Promise<void>;
|
|
5
|
+
}>;
|
|
6
|
+
export default LiteLLMPlugin;
|
|
7
|
+
export { buildProvider, mapModels } from "./mapping.js";
|
|
8
|
+
export { fetchModelInfo } from "./litellm.js";
|
|
9
|
+
export { deepMerge } from "./merge.js";
|
|
10
|
+
export type { LiteLLMPluginOptions, LiteLLMModelInfo, OpenCodeProvider, OpenCodeModel } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { fetchModelInfo } from "./litellm.js";
|
|
2
|
+
import { buildProvider } from "./mapping.js";
|
|
3
|
+
import { resolveOptions } from "./options.js";
|
|
4
|
+
async function log(ctx, level, message, extra = {}) {
|
|
5
|
+
await ctx.client?.app?.log?.({ body: { service: "opencode-litellm", level, message, extra } });
|
|
6
|
+
}
|
|
7
|
+
export const LiteLLMPlugin = async (ctx, options) => {
|
|
8
|
+
const resolved = resolveOptions(options);
|
|
9
|
+
return {
|
|
10
|
+
config: async (cfg) => {
|
|
11
|
+
try {
|
|
12
|
+
const models = await fetchModelInfo(resolved);
|
|
13
|
+
const provider = buildProvider(models, resolved);
|
|
14
|
+
cfg.provider = {
|
|
15
|
+
...(cfg.provider ?? {}),
|
|
16
|
+
[resolved.providerKey]: provider,
|
|
17
|
+
};
|
|
18
|
+
await log(ctx, "info", "loaded LiteLLM provider metadata", {
|
|
19
|
+
providerKey: resolved.providerKey,
|
|
20
|
+
modelCount: Object.keys(provider.models ?? {}).length,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
await log(ctx, resolved.strict ? "error" : "warn", "failed to load LiteLLM provider metadata", {
|
|
25
|
+
providerKey: resolved.providerKey,
|
|
26
|
+
error: error instanceof Error ? error.message : String(error),
|
|
27
|
+
});
|
|
28
|
+
if (resolved.strict)
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export default LiteLLMPlugin;
|
|
35
|
+
export { buildProvider, mapModels } from "./mapping.js";
|
|
36
|
+
export { fetchModelInfo } from "./litellm.js";
|
|
37
|
+
export { deepMerge } from "./merge.js";
|
package/dist/litellm.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { modelInfoURL } from "./options.js";
|
|
2
|
+
function isModelInfoArray(value) {
|
|
3
|
+
return Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export async function fetchModelInfo(options, fetchImpl = fetch) {
|
|
6
|
+
if (!options.baseURL)
|
|
7
|
+
throw new Error("opencode-litellm requires options.baseURL or LITELLM_BASE_URL");
|
|
8
|
+
if (!options.apiKey)
|
|
9
|
+
throw new Error("opencode-litellm requires options.apiKey or the configured apiKeyEnv");
|
|
10
|
+
const controller = new AbortController();
|
|
11
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetchImpl(modelInfoURL(options.baseURL), {
|
|
14
|
+
method: "GET",
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
17
|
+
Accept: "application/json",
|
|
18
|
+
},
|
|
19
|
+
signal: controller.signal,
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok)
|
|
22
|
+
throw new Error(`LiteLLM /model/info returned HTTP ${response.status}`);
|
|
23
|
+
const body = (await response.json());
|
|
24
|
+
if (isModelInfoArray(body))
|
|
25
|
+
return body;
|
|
26
|
+
if (isModelInfoArray(body.data))
|
|
27
|
+
return body.data;
|
|
28
|
+
throw new Error("LiteLLM /model/info response did not contain a data array");
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
clearTimeout(timeout);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LiteLLMModelInfo, LiteLLMPluginOptions, OpenCodeModel, OpenCodeProvider } from "./types.js";
|
|
2
|
+
export declare function mapModels(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): {
|
|
3
|
+
[k: string]: OpenCodeModel;
|
|
4
|
+
};
|
|
5
|
+
export declare function buildProvider(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): OpenCodeProvider;
|
package/dist/mapping.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { deepMerge } from "./merge.js";
|
|
2
|
+
const DEFAULT_MODES = new Set(["chat", "responses"]);
|
|
3
|
+
const NON_CHAT_MODES = new Set(["embedding", "rerank"]);
|
|
4
|
+
const TEXT_MODALITY = "text";
|
|
5
|
+
function asString(value) {
|
|
6
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
function asNumber(value) {
|
|
9
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
10
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
11
|
+
}
|
|
12
|
+
function perMillion(value) {
|
|
13
|
+
const parsed = asNumber(value);
|
|
14
|
+
return parsed === undefined ? undefined : Number((parsed * 1_000_000).toPrecision(12));
|
|
15
|
+
}
|
|
16
|
+
function unique(values) {
|
|
17
|
+
return Array.from(new Set(values));
|
|
18
|
+
}
|
|
19
|
+
function modelName(model) {
|
|
20
|
+
return asString(model.model_name) ?? asString(model.litellm_params?.model) ?? asString(model.model_info?.id);
|
|
21
|
+
}
|
|
22
|
+
function mode(model) {
|
|
23
|
+
return asString(model.model_info?.mode) ?? "chat";
|
|
24
|
+
}
|
|
25
|
+
function allowedByFilters(model, options) {
|
|
26
|
+
const name = modelName(model);
|
|
27
|
+
if (!name)
|
|
28
|
+
return false;
|
|
29
|
+
if (options.includeModels?.length && !options.includeModels.includes(name))
|
|
30
|
+
return false;
|
|
31
|
+
if (options.excludeModels?.includes(name))
|
|
32
|
+
return false;
|
|
33
|
+
const modelMode = mode(model);
|
|
34
|
+
if (options.includeModes?.length)
|
|
35
|
+
return options.includeModes.includes(modelMode);
|
|
36
|
+
return DEFAULT_MODES.has(modelMode) && !NON_CHAT_MODES.has(modelMode);
|
|
37
|
+
}
|
|
38
|
+
function cost(model) {
|
|
39
|
+
const input = perMillion(model.model_info?.input_cost_per_token);
|
|
40
|
+
const output = perMillion(model.model_info?.output_cost_per_token);
|
|
41
|
+
if (input === undefined || output === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
return {
|
|
44
|
+
input,
|
|
45
|
+
output,
|
|
46
|
+
...(perMillion(model.model_info?.cache_creation_input_token_cost) === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: { cache_write: perMillion(model.model_info?.cache_creation_input_token_cost) }),
|
|
49
|
+
...(perMillion(model.model_info?.cache_read_input_token_cost) === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { cache_read: perMillion(model.model_info?.cache_read_input_token_cost) }),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function limit(model) {
|
|
55
|
+
const context = asNumber(model.model_info?.max_input_tokens) ?? asNumber(model.model_info?.max_tokens);
|
|
56
|
+
const output = asNumber(model.model_info?.max_output_tokens) ?? asNumber(model.model_info?.max_tokens);
|
|
57
|
+
if (context === undefined || output === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
return {
|
|
60
|
+
context,
|
|
61
|
+
...(asNumber(model.model_info?.max_input_tokens) === undefined ? {} : { input: asNumber(model.model_info?.max_input_tokens) }),
|
|
62
|
+
output,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function modalities(model) {
|
|
66
|
+
return {
|
|
67
|
+
input: unique([
|
|
68
|
+
TEXT_MODALITY,
|
|
69
|
+
...(model.model_info?.supported_modalities ?? []),
|
|
70
|
+
...(model.model_info?.supports_vision ? ["image"] : []),
|
|
71
|
+
...(model.model_info?.supports_pdf_input ? ["pdf"] : []),
|
|
72
|
+
]),
|
|
73
|
+
output: unique([TEXT_MODALITY, ...(model.model_info?.output_modalities ?? [])]),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function supportsReasoning(model) {
|
|
77
|
+
return Boolean(model.model_info?.supports_reasoning ||
|
|
78
|
+
model.model_info?.supported_params?.includes("reasoning_effort") ||
|
|
79
|
+
model.model_info?.supported_params?.includes("thinking"));
|
|
80
|
+
}
|
|
81
|
+
function generatedModel(model) {
|
|
82
|
+
const name = modelName(model);
|
|
83
|
+
if (!name)
|
|
84
|
+
return undefined;
|
|
85
|
+
const mappedModalities = modalities(model);
|
|
86
|
+
return {
|
|
87
|
+
id: asString(model.model_info?.id) ?? name,
|
|
88
|
+
name,
|
|
89
|
+
...(asString(model.model_info?.base_model) ? { family: asString(model.model_info?.base_model) } : {}),
|
|
90
|
+
...(cost(model) ? { cost: cost(model) } : {}),
|
|
91
|
+
...(limit(model) ? { limit: limit(model) } : {}),
|
|
92
|
+
modalities: mappedModalities,
|
|
93
|
+
...(mappedModalities.input.includes("image") || mappedModalities.input.includes("pdf") ? { attachment: true } : {}),
|
|
94
|
+
...(supportsReasoning(model) ? { reasoning: true } : {}),
|
|
95
|
+
...(model.model_info?.supports_function_calling || model.model_info?.supports_tool_choice ? { tool_call: true } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function mapModels(models, options = {}) {
|
|
99
|
+
return Object.fromEntries(models
|
|
100
|
+
.filter((model) => allowedByFilters(model, options))
|
|
101
|
+
.map((model) => {
|
|
102
|
+
const name = modelName(model);
|
|
103
|
+
const generated = generatedModel(model);
|
|
104
|
+
return name && generated ? [name, deepMerge(generated, options.overrides?.[name])] : undefined;
|
|
105
|
+
})
|
|
106
|
+
.filter((entry) => Boolean(entry)));
|
|
107
|
+
}
|
|
108
|
+
export function buildProvider(models, options = {}) {
|
|
109
|
+
return deepMerge({
|
|
110
|
+
id: options.providerKey ?? "LiteLLM",
|
|
111
|
+
name: options.providerName ?? "LiteLLM",
|
|
112
|
+
npm: "@ai-sdk/openai-compatible",
|
|
113
|
+
api: "openai-compatible",
|
|
114
|
+
options: {
|
|
115
|
+
baseURL: options.baseURL,
|
|
116
|
+
apiKey: options.apiKey,
|
|
117
|
+
},
|
|
118
|
+
models: mapModels(models, options),
|
|
119
|
+
}, options.providerOverrides);
|
|
120
|
+
}
|
package/dist/merge.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepMerge<T extends Record<string, unknown>>(base: T, override?: Record<string, unknown>): T;
|
package/dist/merge.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function deepMerge(base, override) {
|
|
5
|
+
if (!override)
|
|
6
|
+
return { ...base };
|
|
7
|
+
return Object.entries(override).reduce((merged, [key, value]) => ({
|
|
8
|
+
...merged,
|
|
9
|
+
[key]: isPlainObject(merged[key]) && isPlainObject(value) ? deepMerge(merged[key], value) : value,
|
|
10
|
+
}), { ...base });
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LiteLLMPluginOptions } from "./types.js";
|
|
2
|
+
export type ResolvedOptions = Required<Pick<LiteLLMPluginOptions, "providerKey" | "providerName" | "timeoutMs" | "strict">> & Omit<LiteLLMPluginOptions, "providerKey" | "providerName" | "timeoutMs" | "strict">;
|
|
3
|
+
export declare function resolveOptions(options?: LiteLLMPluginOptions): ResolvedOptions;
|
|
4
|
+
export declare function modelInfoURL(baseURL: string): string;
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function readEnv(name) {
|
|
2
|
+
if (!name)
|
|
3
|
+
return undefined;
|
|
4
|
+
return typeof process === "undefined" ? undefined : process.env[name];
|
|
5
|
+
}
|
|
6
|
+
export function resolveOptions(options = {}) {
|
|
7
|
+
return {
|
|
8
|
+
...options,
|
|
9
|
+
baseURL: options.baseURL ?? readEnv("LITELLM_BASE_URL"),
|
|
10
|
+
apiKey: options.apiKey ?? readEnv(options.apiKeyEnv ?? "LITELLM_API_KEY"),
|
|
11
|
+
apiKeyEnv: options.apiKeyEnv ?? "LITELLM_API_KEY",
|
|
12
|
+
providerKey: options.providerKey ?? "LiteLLM",
|
|
13
|
+
providerName: options.providerName ?? "LiteLLM",
|
|
14
|
+
timeoutMs: options.timeoutMs ?? 30_000,
|
|
15
|
+
strict: options.strict ?? false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function modelInfoURL(baseURL) {
|
|
19
|
+
return `${baseURL.replace(/\/+$/, "")}/model/info`;
|
|
20
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type JsonObject = Record<string, unknown>;
|
|
2
|
+
export type OpenCodeModel = JsonObject & {
|
|
3
|
+
id?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
cost?: {
|
|
6
|
+
input: number;
|
|
7
|
+
output: number;
|
|
8
|
+
cache_read?: number;
|
|
9
|
+
cache_write?: number;
|
|
10
|
+
};
|
|
11
|
+
limit?: {
|
|
12
|
+
context: number;
|
|
13
|
+
input?: number;
|
|
14
|
+
output: number;
|
|
15
|
+
};
|
|
16
|
+
modalities?: {
|
|
17
|
+
input: string[];
|
|
18
|
+
output: string[];
|
|
19
|
+
};
|
|
20
|
+
attachment?: boolean;
|
|
21
|
+
reasoning?: boolean;
|
|
22
|
+
tool_call?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type OpenCodeProvider = JsonObject & {
|
|
25
|
+
id?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
npm?: string;
|
|
28
|
+
api?: string;
|
|
29
|
+
options?: JsonObject;
|
|
30
|
+
models: Record<string, OpenCodeModel>;
|
|
31
|
+
};
|
|
32
|
+
export type LiteLLMPluginOptions = {
|
|
33
|
+
baseURL?: string;
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
apiKeyEnv?: string;
|
|
36
|
+
providerKey?: string;
|
|
37
|
+
providerName?: string;
|
|
38
|
+
overrides?: Record<string, JsonObject>;
|
|
39
|
+
providerOverrides?: JsonObject;
|
|
40
|
+
includeModes?: string[];
|
|
41
|
+
excludeModels?: string[];
|
|
42
|
+
includeModels?: string[];
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
strict?: boolean;
|
|
45
|
+
};
|
|
46
|
+
export type LiteLLMModelInfo = JsonObject & {
|
|
47
|
+
model_name?: string;
|
|
48
|
+
litellm_params?: JsonObject & {
|
|
49
|
+
model?: string;
|
|
50
|
+
};
|
|
51
|
+
model_info?: JsonObject & {
|
|
52
|
+
id?: string;
|
|
53
|
+
base_model?: string;
|
|
54
|
+
mode?: string;
|
|
55
|
+
input_cost_per_token?: number | string;
|
|
56
|
+
output_cost_per_token?: number | string;
|
|
57
|
+
cache_creation_input_token_cost?: number | string;
|
|
58
|
+
cache_read_input_token_cost?: number | string;
|
|
59
|
+
max_tokens?: number | string;
|
|
60
|
+
max_input_tokens?: number | string;
|
|
61
|
+
max_output_tokens?: number | string;
|
|
62
|
+
supports_function_calling?: boolean;
|
|
63
|
+
supports_tool_choice?: boolean;
|
|
64
|
+
supports_vision?: boolean;
|
|
65
|
+
supports_pdf_input?: boolean;
|
|
66
|
+
supports_reasoning?: boolean;
|
|
67
|
+
supported_modalities?: string[];
|
|
68
|
+
output_modalities?: string[];
|
|
69
|
+
supported_params?: string[];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export type LiteLLMModelInfoResponse = {
|
|
73
|
+
data?: LiteLLMModelInfo[];
|
|
74
|
+
};
|
|
75
|
+
export type OpenCodeConfig = {
|
|
76
|
+
provider?: Record<string, OpenCodeProvider>;
|
|
77
|
+
};
|
|
78
|
+
export type PluginContext = {
|
|
79
|
+
client?: {
|
|
80
|
+
app?: {
|
|
81
|
+
log?: (input: {
|
|
82
|
+
body: {
|
|
83
|
+
service: string;
|
|
84
|
+
level: "info" | "warn" | "error" | "debug";
|
|
85
|
+
message: string;
|
|
86
|
+
extra?: JsonObject;
|
|
87
|
+
};
|
|
88
|
+
}) => Promise<unknown>;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@staticeng/opencode-litellm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode plugin that discovers LiteLLM models at runtime.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"test": "node --test test/*.test.mjs",
|
|
23
|
+
"prepack": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@opencode-ai/plugin": "*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@opencode-ai/plugin": "latest",
|
|
30
|
+
"@types/node": "^24.10.1",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"opencode",
|
|
38
|
+
"opencode-plugin",
|
|
39
|
+
"litellm",
|
|
40
|
+
"models"
|
|
41
|
+
]
|
|
42
|
+
}
|