@staticeng/opencode-litellm 0.1.7 → 0.1.9
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 +20 -2
- package/dist/helpers.d.ts +1 -1
- package/dist/helpers.js +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +10 -2
- package/dist/mapping.d.ts +3 -0
- package/dist/mapping.js +148 -12
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ npm install @staticeng/opencode-litellm
|
|
|
15
15
|
"$schema": "https://opencode.ai/config.json",
|
|
16
16
|
"plugin": [
|
|
17
17
|
[
|
|
18
|
-
"@staticeng/opencode-litellm",
|
|
18
|
+
"@staticeng/opencode-litellm@0.1.9",
|
|
19
19
|
{
|
|
20
20
|
"baseURL": "https://litellm.example.com/v1",
|
|
21
21
|
"apiKeyEnv": "LITELLM_API_KEY"
|
|
@@ -33,6 +33,8 @@ export LITELLM_API_KEY="..."
|
|
|
33
33
|
|
|
34
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
35
|
|
|
36
|
+
Shared or synchronized OpenCode configuration must use the published, pinned npm package as shown above. Do not use a local path or `file://` plugin reference for shared configuration.
|
|
37
|
+
|
|
36
38
|
## Options
|
|
37
39
|
|
|
38
40
|
| Option | Default | Purpose |
|
|
@@ -56,6 +58,10 @@ The LiteLLM key must have permission to call `GET /model/info` and root `GET /mo
|
|
|
56
58
|
|
|
57
59
|
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
60
|
|
|
61
|
+
When LiteLLM publishes `model_info.supported_reasoning_efforts`, the plugin maps
|
|
62
|
+
that list to OpenCode variants automatically. It does not guess effort levels
|
|
63
|
+
from model names; the LiteLLM catalog remains the source of truth.
|
|
64
|
+
|
|
59
65
|
```json
|
|
60
66
|
{
|
|
61
67
|
"plugin": [
|
|
@@ -68,7 +74,7 @@ LiteLLM metadata does not expose every OpenCode model field. Use `overrides` to
|
|
|
68
74
|
"chatgpt/gpt-5.5": {
|
|
69
75
|
"limit": { "context": 262144, "output": 128000 },
|
|
70
76
|
"variants": {
|
|
71
|
-
"high": { "
|
|
77
|
+
"high": { "reasoningEffort": "high" }
|
|
72
78
|
},
|
|
73
79
|
"interleaved": { "field": "reasoning_details" }
|
|
74
80
|
}
|
|
@@ -86,6 +92,10 @@ LiteLLM metadata does not expose every OpenCode model field. Use `overrides` to
|
|
|
86
92
|
|
|
87
93
|
Overrides win over generated values through a recursive deep merge. Arrays and scalar values are replaced rather than concatenated.
|
|
88
94
|
|
|
95
|
+
The exact model groups `deepseek-v4-flash-fp8-mtp` and `deepseek-v4-flash-fp8-mtp-norefusal` are an exception: the plugin exposes only `off`, `low`, `high`, and `max`. Semantic `off` emits canonical `none`; the other values pass through unchanged. At the OpenCode config boundary, disabled `medium` and `xhigh` tombstones remove generic generated variants, while the model-scoped `variantDefault: false` capability hides the UI's default sentinel on compatible OpenCode builds. The plugin strips that UI-only marker in `chat.params` only when both the LiteLLM provider and an exact target alias match. Target model and provider overrides cannot change these controls. No manual model entry in `opencode.json` is needed.
|
|
96
|
+
|
|
97
|
+
The exact model group `qwen3.8-27b-refusal-dial` similarly exposes only `off`, `low`, `medium`, and `xhigh`. Its semantic `off` sends `chat_template_kwargs.enable_thinking=false` without a `reasoning_effort`; the native effort modes pass through unchanged. Disabled `none`, `minimal`, `high`, and `max` tombstones suppress OpenCode's unsupported generated variants. Official OpenCode 1.18.23 still owns an intrinsic default selection (`Predeterminado`) outside model variants; plugin configuration cannot remove it, and leaving it selected omits reasoning controls and preserves the model's deployed default.
|
|
98
|
+
|
|
89
99
|
## Runtime Behavior
|
|
90
100
|
|
|
91
101
|
- Fetches `${baseURL}/model/info`, then root `/model_group/info`, with separate authenticated and timed requests.
|
|
@@ -93,8 +103,11 @@ Overrides win over generated values through a recursive deep merge. Arrays and s
|
|
|
93
103
|
- Adds or replaces both legacy `cfg.provider[providerKey]` and V2 `cfg.providers[providerKey]` during the config hook.
|
|
94
104
|
- Keeps namespaced deployment IDs and augments them with exact bare model-group IDs; exact collisions are enriched deterministically.
|
|
95
105
|
- Skips malformed group rows without a nonblank `model_group` and exposes reasoning capability on legacy and V2 model metadata.
|
|
106
|
+
- Generates exact DeepSeek V4 and Qwen3.8 reasoning variants for the supported aliases and sanitizes conflicting target overrides after merging.
|
|
96
107
|
- Warns safely and keeps deployment models when group discovery fails in non-strict mode; strict mode mutates neither config surface on failure.
|
|
97
108
|
- Does not write `opencode.json`, cache model metadata, or persist generated provider data.
|
|
109
|
+
- Emits the required `limit.output` as `0` when LiteLLM reports a context/input limit but no output
|
|
110
|
+
limit. The zero sentinel retains known metadata without inventing a positive output allowance.
|
|
98
111
|
- Includes `chat` and `responses` modes by default; non-chat modes such as `embedding` and `rerank` are excluded unless explicitly included.
|
|
99
112
|
- No HuggingFace fallback is included in v1.
|
|
100
113
|
|
|
@@ -110,3 +123,8 @@ npm pack --dry-run
|
|
|
110
123
|
```
|
|
111
124
|
|
|
112
125
|
Confirm package contents before publishing and keep secrets out of fixtures, docs, logs, and config examples.
|
|
126
|
+
|
|
127
|
+
Publishing uses npm trusted publishing from the `npm-production` GitHub environment. Provenance
|
|
128
|
+
requires this GitHub repository to remain public. Before a release, configure npm's trusted
|
|
129
|
+
publisher for package `@staticeng/opencode-litellm` with this repository, workflow
|
|
130
|
+
`publish.yml`, and environment `npm-production`; no npm token is used by the workflow.
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildProvider, buildProviderV2, mapModels, mapModelsV2, mergeModelInfo, normalizeModelListResponse, modelListResponseShape, } from "./mapping.js";
|
|
1
|
+
export { addOpenCodeVariantTombstones, buildProvider, buildProviderV2, isDeepSeekV4ReasoningModel, mapModels, mapModelsV2, mergeModelInfo, normalizeModelListResponse, modelListResponseShape, } from "./mapping.js";
|
|
2
2
|
export { fetchModelGroupInfo, fetchModelInfo, normalizeModelGroupResponse } from "./litellm.js";
|
|
3
3
|
export { modelGroupInfoURL, modelInfoURL } from "./options.js";
|
|
4
4
|
export { deepMerge } from "./merge.js";
|
package/dist/helpers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildProvider, buildProviderV2, mapModels, mapModelsV2, mergeModelInfo, normalizeModelListResponse, modelListResponseShape, } from "./mapping.js";
|
|
1
|
+
export { addOpenCodeVariantTombstones, buildProvider, buildProviderV2, isDeepSeekV4ReasoningModel, mapModels, mapModelsV2, mergeModelInfo, normalizeModelListResponse, modelListResponseShape, } from "./mapping.js";
|
|
2
2
|
export { fetchModelGroupInfo, fetchModelInfo, normalizeModelGroupResponse } from "./litellm.js";
|
|
3
3
|
export { modelGroupInfoURL, modelInfoURL } from "./options.js";
|
|
4
4
|
export { deepMerge } from "./merge.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,18 @@ import type { Config, PluginInput } from "@opencode-ai/plugin";
|
|
|
2
2
|
import type { LiteLLMPluginOptions } from "./types.js";
|
|
3
3
|
declare const LiteLLMPlugin: (ctx: PluginInput, options?: LiteLLMPluginOptions) => Promise<{
|
|
4
4
|
config: (cfg: Config) => Promise<void>;
|
|
5
|
+
"chat.params": (input: {
|
|
6
|
+
sessionID: string;
|
|
7
|
+
agent: string;
|
|
8
|
+
model: import("@opencode-ai/sdk").Model;
|
|
9
|
+
provider: import("@opencode-ai/plugin").ProviderContext;
|
|
10
|
+
message: import("@opencode-ai/sdk").UserMessage;
|
|
11
|
+
}, output: {
|
|
12
|
+
temperature: number;
|
|
13
|
+
topP: number;
|
|
14
|
+
topK: number;
|
|
15
|
+
maxOutputTokens: number | undefined;
|
|
16
|
+
options: Record<string, any>;
|
|
17
|
+
}) => Promise<void>;
|
|
5
18
|
}>;
|
|
6
19
|
export default LiteLLMPlugin;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fetchModelGroupInfo, fetchModelInfo } from "./litellm.js";
|
|
2
|
-
import { buildProvider, buildProviderV2, mergeModelInfo } from "./mapping.js";
|
|
2
|
+
import { addOpenCodeVariantTombstones, buildProvider, buildProviderV2, isContractReasoningModel, mergeModelInfo, } from "./mapping.js";
|
|
3
3
|
import { resolveOptions } from "./options.js";
|
|
4
4
|
async function log(ctx, level, message, extra = {}) {
|
|
5
5
|
await ctx.client?.app?.log?.({ body: { service: "opencode-litellm", level, message, extra } });
|
|
@@ -25,7 +25,7 @@ const LiteLLMPlugin = async (ctx, options) => {
|
|
|
25
25
|
throw error;
|
|
26
26
|
}
|
|
27
27
|
const models = mergeModelInfo(deployments, groups);
|
|
28
|
-
const provider = buildProvider(models, resolved);
|
|
28
|
+
const provider = addOpenCodeVariantTombstones(buildProvider(models, resolved));
|
|
29
29
|
const config = cfg;
|
|
30
30
|
const providerV2 = buildProviderV2(models, resolved);
|
|
31
31
|
cfg.provider = {
|
|
@@ -53,6 +53,14 @@ const LiteLLMPlugin = async (ctx, options) => {
|
|
|
53
53
|
throw error;
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
|
+
"chat.params": (async (input, output) => {
|
|
57
|
+
const provider = input.provider;
|
|
58
|
+
if ((provider.info?.id ?? provider.id) !== resolved.providerKey)
|
|
59
|
+
return;
|
|
60
|
+
if (!isContractReasoningModel(input.model.id))
|
|
61
|
+
return;
|
|
62
|
+
delete output.options.variantDefault;
|
|
63
|
+
}),
|
|
56
64
|
};
|
|
57
65
|
};
|
|
58
66
|
export default LiteLLMPlugin;
|
package/dist/mapping.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export type ModelListResponseShape = {
|
|
|
10
10
|
export declare function modelListResponseShape(value: unknown): ModelListResponseShape;
|
|
11
11
|
export declare function mergeModelInfo(deployments: LiteLLMModelInfo[], groups: LiteLLMModelInfo[]): LiteLLMModelInfo[];
|
|
12
12
|
export declare function normalizeModelListResponse(response: unknown): LiteLLMModelInfo[];
|
|
13
|
+
export declare function isDeepSeekV4ReasoningModel(name: string | undefined): boolean;
|
|
14
|
+
export declare function isContractReasoningModel(name: string | undefined): boolean;
|
|
15
|
+
export declare function addOpenCodeVariantTombstones(provider: OpenCodeProvider): OpenCodeProvider;
|
|
13
16
|
export declare function mapModels(response: unknown, options?: LiteLLMPluginOptions): {
|
|
14
17
|
[k: string]: OpenCodeModel;
|
|
15
18
|
};
|
package/dist/mapping.js
CHANGED
|
@@ -2,7 +2,39 @@ import { deepMerge } from "./merge.js";
|
|
|
2
2
|
const DEFAULT_MODES = new Set(["chat", "responses"]);
|
|
3
3
|
const NON_CHAT_MODES = new Set(["embedding", "rerank"]);
|
|
4
4
|
const TEXT_MODALITY = "text";
|
|
5
|
+
const OPENCODE_MODALITIES = new Set(["text", "audio", "image", "video", "pdf"]);
|
|
5
6
|
const TEXT_ONLY_MODELS = new Set(["minimax-m2.7", "minimax_m2.7"]);
|
|
7
|
+
const DEEPSEEK_V4_REASONING_MODELS = new Set([
|
|
8
|
+
"deepseek-v4-flash-fp8-mtp",
|
|
9
|
+
"deepseek-v4-flash-fp8-mtp-norefusal",
|
|
10
|
+
]);
|
|
11
|
+
const QWEN_38_REASONING_MODELS = new Set(["qwen3.8-27b-refusal-dial"]);
|
|
12
|
+
const DEEPSEEK_V4_VARIANTS = {
|
|
13
|
+
off: { reasoningEffort: "none" },
|
|
14
|
+
low: { reasoningEffort: "low" },
|
|
15
|
+
high: { reasoningEffort: "high" },
|
|
16
|
+
max: { reasoningEffort: "max" },
|
|
17
|
+
};
|
|
18
|
+
const DEEPSEEK_V4_VARIANTS_V2 = [
|
|
19
|
+
{ id: "off", body: { reasoning_effort: "none" } },
|
|
20
|
+
{ id: "low", body: { reasoning_effort: "low" } },
|
|
21
|
+
{ id: "high", body: { reasoning_effort: "high" } },
|
|
22
|
+
{ id: "max", body: { reasoning_effort: "max" } },
|
|
23
|
+
];
|
|
24
|
+
const QWEN_38_VARIANTS = {
|
|
25
|
+
off: { chat_template_kwargs: { enable_thinking: false } },
|
|
26
|
+
low: { reasoningEffort: "low" },
|
|
27
|
+
medium: { reasoningEffort: "medium" },
|
|
28
|
+
xhigh: { reasoningEffort: "xhigh" },
|
|
29
|
+
};
|
|
30
|
+
const QWEN_38_VARIANTS_V2 = [
|
|
31
|
+
{ id: "off", body: { chat_template_kwargs: { enable_thinking: false } } },
|
|
32
|
+
{ id: "low", body: { reasoning_effort: "low" } },
|
|
33
|
+
{ id: "medium", body: { reasoning_effort: "medium" } },
|
|
34
|
+
{ id: "xhigh", body: { reasoning_effort: "xhigh" } },
|
|
35
|
+
];
|
|
36
|
+
const REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
37
|
+
const REASONING_EFFORT_SET = new Set(REASONING_EFFORTS);
|
|
6
38
|
const NUMERIC_MODEL_INFO_FIELDS = new Set([
|
|
7
39
|
"input_cost_per_token",
|
|
8
40
|
"output_cost_per_token",
|
|
@@ -32,6 +64,9 @@ const MODEL_INFO_FIELDS = new Set([
|
|
|
32
64
|
"output_modalities",
|
|
33
65
|
"supported_params",
|
|
34
66
|
]);
|
|
67
|
+
function supportedModalities(values) {
|
|
68
|
+
return unique(values).filter((modality) => OPENCODE_MODALITIES.has(modality));
|
|
69
|
+
}
|
|
35
70
|
function isRecord(value) {
|
|
36
71
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
72
|
}
|
|
@@ -92,6 +127,9 @@ function enrichMetadata(existing, incoming, key) {
|
|
|
92
127
|
}
|
|
93
128
|
return merged;
|
|
94
129
|
}
|
|
130
|
+
if (key === "supports_reasoning" && typeof existing === "boolean" && typeof incoming === "boolean") {
|
|
131
|
+
return existing && incoming;
|
|
132
|
+
}
|
|
95
133
|
if (typeof existing === "boolean" && typeof incoming === "boolean")
|
|
96
134
|
return existing || incoming;
|
|
97
135
|
if (isValidMetadata(existing, key))
|
|
@@ -201,12 +239,12 @@ function v2Cost(model) {
|
|
|
201
239
|
function limit(model) {
|
|
202
240
|
const context = asNumber(model.model_info?.max_input_tokens) ?? asNumber(model.model_info?.max_tokens);
|
|
203
241
|
const output = asNumber(model.model_info?.max_output_tokens) ?? asNumber(model.model_info?.max_tokens);
|
|
204
|
-
if (context === undefined
|
|
242
|
+
if (context === undefined)
|
|
205
243
|
return undefined;
|
|
206
244
|
return {
|
|
207
245
|
context,
|
|
208
246
|
...(asNumber(model.model_info?.max_input_tokens) === undefined ? {} : { input: asNumber(model.model_info?.max_input_tokens) }),
|
|
209
|
-
output,
|
|
247
|
+
output: output ?? 0,
|
|
210
248
|
};
|
|
211
249
|
}
|
|
212
250
|
function canonicalModelName(model) {
|
|
@@ -226,20 +264,80 @@ function modalities(model) {
|
|
|
226
264
|
};
|
|
227
265
|
}
|
|
228
266
|
return {
|
|
229
|
-
input:
|
|
267
|
+
input: supportedModalities([
|
|
230
268
|
TEXT_MODALITY,
|
|
231
269
|
...(model.model_info?.supported_modalities ?? []),
|
|
232
270
|
...(model.model_info?.supports_vision ? ["image"] : []),
|
|
233
271
|
...(model.model_info?.supports_pdf_input ? ["pdf"] : []),
|
|
234
272
|
]),
|
|
235
|
-
output:
|
|
273
|
+
output: supportedModalities([TEXT_MODALITY, ...(model.model_info?.output_modalities ?? [])]),
|
|
236
274
|
};
|
|
237
275
|
}
|
|
238
276
|
function supportsReasoning(model) {
|
|
239
|
-
|
|
240
|
-
model.model_info
|
|
277
|
+
if (typeof model.model_info?.supports_reasoning === "boolean")
|
|
278
|
+
return model.model_info.supports_reasoning;
|
|
279
|
+
return Boolean(model.model_info?.supported_params?.includes("reasoning_effort") ||
|
|
241
280
|
model.model_info?.supported_params?.includes("thinking"));
|
|
242
281
|
}
|
|
282
|
+
export function isDeepSeekV4ReasoningModel(name) {
|
|
283
|
+
return name !== undefined && DEEPSEEK_V4_REASONING_MODELS.has(name);
|
|
284
|
+
}
|
|
285
|
+
export function isContractReasoningModel(name) {
|
|
286
|
+
return isDeepSeekV4ReasoningModel(name) || (name !== undefined && QWEN_38_REASONING_MODELS.has(name));
|
|
287
|
+
}
|
|
288
|
+
function enforceReasoningVariants(model) {
|
|
289
|
+
const name = asString(model.id) ?? asString(model.name);
|
|
290
|
+
if (!isContractReasoningModel(name))
|
|
291
|
+
return model;
|
|
292
|
+
return {
|
|
293
|
+
...model,
|
|
294
|
+
reasoning: true,
|
|
295
|
+
options: { ...(isRecord(model.options) ? model.options : {}), variantDefault: false },
|
|
296
|
+
variants: isDeepSeekV4ReasoningModel(name) ? DEEPSEEK_V4_VARIANTS : QWEN_38_VARIANTS,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function enforceReasoningProviderModels(provider) {
|
|
300
|
+
return {
|
|
301
|
+
...provider,
|
|
302
|
+
models: Object.fromEntries(Object.entries(provider.models).map(([name, model]) => [
|
|
303
|
+
name,
|
|
304
|
+
isContractReasoningModel(name) ? enforceReasoningVariants({ ...model, id: name }) : model,
|
|
305
|
+
])),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
export function addOpenCodeVariantTombstones(provider) {
|
|
309
|
+
return {
|
|
310
|
+
...provider,
|
|
311
|
+
models: Object.fromEntries(Object.entries(provider.models).map(([name, model]) => [
|
|
312
|
+
name,
|
|
313
|
+
isContractReasoningModel(name)
|
|
314
|
+
? {
|
|
315
|
+
...model,
|
|
316
|
+
variants: {
|
|
317
|
+
...(isDeepSeekV4ReasoningModel(name) ? DEEPSEEK_V4_VARIANTS : QWEN_38_VARIANTS),
|
|
318
|
+
...(isDeepSeekV4ReasoningModel(name)
|
|
319
|
+
? { medium: { disabled: true }, xhigh: { disabled: true } }
|
|
320
|
+
: {
|
|
321
|
+
none: { disabled: true },
|
|
322
|
+
minimal: { disabled: true },
|
|
323
|
+
high: { disabled: true },
|
|
324
|
+
max: { disabled: true },
|
|
325
|
+
}),
|
|
326
|
+
},
|
|
327
|
+
}
|
|
328
|
+
: model,
|
|
329
|
+
])),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function reasoningVariants(model) {
|
|
333
|
+
const reported = new Set((model.model_info?.supported_reasoning_efforts ?? [])
|
|
334
|
+
.map((effort) => asString(effort)?.toLowerCase())
|
|
335
|
+
.filter((effort) => effort !== undefined && REASONING_EFFORT_SET.has(effort)));
|
|
336
|
+
const efforts = REASONING_EFFORTS.filter((effort) => reported.has(effort));
|
|
337
|
+
if (!efforts.length)
|
|
338
|
+
return undefined;
|
|
339
|
+
return Object.fromEntries(efforts.map((effort) => [effort, { reasoningEffort: effort }]));
|
|
340
|
+
}
|
|
243
341
|
function generatedModel(model) {
|
|
244
342
|
const name = modelName(model);
|
|
245
343
|
if (!name)
|
|
@@ -254,9 +352,20 @@ function generatedModel(model) {
|
|
|
254
352
|
modalities: mappedModalities,
|
|
255
353
|
...(mappedModalities.input.includes("image") || mappedModalities.input.includes("pdf") ? { attachment: true } : {}),
|
|
256
354
|
...(supportsReasoning(model) ? { reasoning: true } : {}),
|
|
355
|
+
...(supportsReasoning(model) && reasoningVariants(model) ? { variants: reasoningVariants(model) } : {}),
|
|
257
356
|
...(model.model_info?.supports_function_calling || model.model_info?.supports_tool_choice ? { tool_call: true } : {}),
|
|
258
357
|
};
|
|
259
358
|
}
|
|
359
|
+
function finalizedModel(model, override) {
|
|
360
|
+
const input = supportedModalities(model.modalities?.input ?? [TEXT_MODALITY]);
|
|
361
|
+
const output = supportedModalities(model.modalities?.output ?? [TEXT_MODALITY]);
|
|
362
|
+
const attachmentOverride = typeof override?.attachment === "boolean" ? override.attachment : undefined;
|
|
363
|
+
return {
|
|
364
|
+
...model,
|
|
365
|
+
modalities: { input, output },
|
|
366
|
+
attachment: attachmentOverride ?? input.some((modality) => modality === "image" || modality === "pdf"),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
260
369
|
function v2Variants(model) {
|
|
261
370
|
const variants = model.variants;
|
|
262
371
|
if (!isRecord(variants))
|
|
@@ -288,9 +397,11 @@ function v2Model(model, baseURL) {
|
|
|
288
397
|
capabilities: {
|
|
289
398
|
tools: model.tool_call ?? false,
|
|
290
399
|
reasoning: model.reasoning ?? false,
|
|
291
|
-
|
|
292
|
-
|
|
400
|
+
attachment: model.attachment ?? false,
|
|
401
|
+
input: supportedModalities(model.modalities?.input ?? [TEXT_MODALITY]),
|
|
402
|
+
output: supportedModalities(model.modalities?.output ?? [TEXT_MODALITY]),
|
|
293
403
|
},
|
|
404
|
+
...(isRecord(model.options) ? { options: { ...model.options } } : {}),
|
|
294
405
|
...(v2Cost(model) ? { cost: v2Cost(model) } : {}),
|
|
295
406
|
...(model.limit ? { limit: { ...model.limit } } : {}),
|
|
296
407
|
...(v2Variants(model) ? { variants: v2Variants(model) } : {}),
|
|
@@ -303,12 +414,17 @@ export function mapModels(response, options = {}) {
|
|
|
303
414
|
.map((model) => {
|
|
304
415
|
const name = modelName(model);
|
|
305
416
|
const generated = generatedModel(model);
|
|
306
|
-
return name && generated
|
|
417
|
+
return name && generated
|
|
418
|
+
? [
|
|
419
|
+
name,
|
|
420
|
+
enforceReasoningVariants(finalizedModel(deepMerge(generated, options.overrides?.[name]), options.overrides?.[name])),
|
|
421
|
+
]
|
|
422
|
+
: undefined;
|
|
307
423
|
})
|
|
308
424
|
.filter((entry) => Boolean(entry)));
|
|
309
425
|
}
|
|
310
426
|
export function buildProvider(models, options = {}) {
|
|
311
|
-
return deepMerge({
|
|
427
|
+
return enforceReasoningProviderModels(deepMerge({
|
|
312
428
|
id: options.providerKey ?? "LiteLLM",
|
|
313
429
|
name: options.providerName ?? "LiteLLM",
|
|
314
430
|
npm: "@ai-sdk/openai-compatible",
|
|
@@ -319,14 +435,14 @@ export function buildProvider(models, options = {}) {
|
|
|
319
435
|
...(options.apiKey ? { body: { apiKey: options.apiKey } } : {}),
|
|
320
436
|
},
|
|
321
437
|
models: mapModels(models, options),
|
|
322
|
-
}, options.providerOverrides);
|
|
438
|
+
}, options.providerOverrides));
|
|
323
439
|
}
|
|
324
440
|
export function mapModelsV2(models, options = {}) {
|
|
325
441
|
const legacy = mapModels(models, options);
|
|
326
442
|
return Object.fromEntries(Object.entries(legacy).map(([name, model]) => [name, v2Model(model, options.baseURL)]));
|
|
327
443
|
}
|
|
328
444
|
export function buildProviderV2(models, options = {}) {
|
|
329
|
-
|
|
445
|
+
const provider = deepMerge({
|
|
330
446
|
name: options.providerName ?? "LiteLLM",
|
|
331
447
|
api: {
|
|
332
448
|
type: "aisdk",
|
|
@@ -341,4 +457,24 @@ export function buildProviderV2(models, options = {}) {
|
|
|
341
457
|
},
|
|
342
458
|
models: mapModelsV2(models, options),
|
|
343
459
|
}, options.providerOverrides);
|
|
460
|
+
return {
|
|
461
|
+
...provider,
|
|
462
|
+
models: Object.fromEntries(Object.entries(provider.models).map(([name, model]) => [
|
|
463
|
+
name,
|
|
464
|
+
isContractReasoningModel(name)
|
|
465
|
+
? {
|
|
466
|
+
...model,
|
|
467
|
+
capabilities: {
|
|
468
|
+
tools: model.capabilities?.tools ?? false,
|
|
469
|
+
reasoning: true,
|
|
470
|
+
attachment: model.capabilities?.attachment ?? false,
|
|
471
|
+
input: model.capabilities?.input ?? [TEXT_MODALITY],
|
|
472
|
+
output: model.capabilities?.output ?? [TEXT_MODALITY],
|
|
473
|
+
},
|
|
474
|
+
options: { ...(isRecord(model.options) ? model.options : {}), variantDefault: false },
|
|
475
|
+
variants: isDeepSeekV4ReasoningModel(name) ? DEEPSEEK_V4_VARIANTS_V2 : QWEN_38_VARIANTS_V2,
|
|
476
|
+
}
|
|
477
|
+
: model,
|
|
478
|
+
])),
|
|
479
|
+
};
|
|
344
480
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type OpenCodeModel = JsonObject & {
|
|
|
20
20
|
attachment?: boolean;
|
|
21
21
|
reasoning?: boolean;
|
|
22
22
|
tool_call?: boolean;
|
|
23
|
+
variants?: Record<string, JsonObject>;
|
|
23
24
|
};
|
|
24
25
|
export type OpenCodeModelV2 = JsonObject & {
|
|
25
26
|
family?: string;
|
|
@@ -34,6 +35,7 @@ export type OpenCodeModelV2 = JsonObject & {
|
|
|
34
35
|
capabilities?: {
|
|
35
36
|
tools: boolean;
|
|
36
37
|
reasoning: boolean;
|
|
38
|
+
attachment: boolean;
|
|
37
39
|
input: string[];
|
|
38
40
|
output: string[];
|
|
39
41
|
};
|
|
@@ -119,6 +121,10 @@ export type LiteLLMModelInfo = JsonObject & {
|
|
|
119
121
|
supports_vision?: boolean;
|
|
120
122
|
supports_pdf_input?: boolean;
|
|
121
123
|
supports_reasoning?: boolean;
|
|
124
|
+
supported_reasoning_efforts?: string[];
|
|
125
|
+
supports_low_reasoning_effort?: boolean;
|
|
126
|
+
supports_xhigh_reasoning_effort?: boolean;
|
|
127
|
+
supports_max_reasoning_effort?: boolean;
|
|
122
128
|
supported_modalities?: string[];
|
|
123
129
|
output_modalities?: string[];
|
|
124
130
|
supported_params?: string[];
|
|
@@ -144,6 +150,7 @@ export type LiteLLMModelGroup = JsonObject & {
|
|
|
144
150
|
supports_vision?: boolean;
|
|
145
151
|
supports_pdf_input?: boolean;
|
|
146
152
|
supports_reasoning?: boolean;
|
|
153
|
+
supported_reasoning_efforts?: string[];
|
|
147
154
|
supported_modalities?: string[];
|
|
148
155
|
output_modalities?: string[];
|
|
149
156
|
supported_openai_params?: string[];
|