@staticeng/opencode-litellm 0.1.8 → 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 +127 -8
- package/dist/types.d.ts +6 -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
|
@@ -4,6 +4,37 @@ const NON_CHAT_MODES = new Set(["embedding", "rerank"]);
|
|
|
4
4
|
const TEXT_MODALITY = "text";
|
|
5
5
|
const OPENCODE_MODALITIES = new Set(["text", "audio", "image", "video", "pdf"]);
|
|
6
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);
|
|
7
38
|
const NUMERIC_MODEL_INFO_FIELDS = new Set([
|
|
8
39
|
"input_cost_per_token",
|
|
9
40
|
"output_cost_per_token",
|
|
@@ -96,6 +127,9 @@ function enrichMetadata(existing, incoming, key) {
|
|
|
96
127
|
}
|
|
97
128
|
return merged;
|
|
98
129
|
}
|
|
130
|
+
if (key === "supports_reasoning" && typeof existing === "boolean" && typeof incoming === "boolean") {
|
|
131
|
+
return existing && incoming;
|
|
132
|
+
}
|
|
99
133
|
if (typeof existing === "boolean" && typeof incoming === "boolean")
|
|
100
134
|
return existing || incoming;
|
|
101
135
|
if (isValidMetadata(existing, key))
|
|
@@ -205,12 +239,12 @@ function v2Cost(model) {
|
|
|
205
239
|
function limit(model) {
|
|
206
240
|
const context = asNumber(model.model_info?.max_input_tokens) ?? asNumber(model.model_info?.max_tokens);
|
|
207
241
|
const output = asNumber(model.model_info?.max_output_tokens) ?? asNumber(model.model_info?.max_tokens);
|
|
208
|
-
if (context === undefined
|
|
242
|
+
if (context === undefined)
|
|
209
243
|
return undefined;
|
|
210
244
|
return {
|
|
211
245
|
context,
|
|
212
246
|
...(asNumber(model.model_info?.max_input_tokens) === undefined ? {} : { input: asNumber(model.model_info?.max_input_tokens) }),
|
|
213
|
-
output,
|
|
247
|
+
output: output ?? 0,
|
|
214
248
|
};
|
|
215
249
|
}
|
|
216
250
|
function canonicalModelName(model) {
|
|
@@ -240,10 +274,70 @@ function modalities(model) {
|
|
|
240
274
|
};
|
|
241
275
|
}
|
|
242
276
|
function supportsReasoning(model) {
|
|
243
|
-
|
|
244
|
-
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") ||
|
|
245
280
|
model.model_info?.supported_params?.includes("thinking"));
|
|
246
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
|
+
}
|
|
247
341
|
function generatedModel(model) {
|
|
248
342
|
const name = modelName(model);
|
|
249
343
|
if (!name)
|
|
@@ -258,6 +352,7 @@ function generatedModel(model) {
|
|
|
258
352
|
modalities: mappedModalities,
|
|
259
353
|
...(mappedModalities.input.includes("image") || mappedModalities.input.includes("pdf") ? { attachment: true } : {}),
|
|
260
354
|
...(supportsReasoning(model) ? { reasoning: true } : {}),
|
|
355
|
+
...(supportsReasoning(model) && reasoningVariants(model) ? { variants: reasoningVariants(model) } : {}),
|
|
261
356
|
...(model.model_info?.supports_function_calling || model.model_info?.supports_tool_choice ? { tool_call: true } : {}),
|
|
262
357
|
};
|
|
263
358
|
}
|
|
@@ -306,6 +401,7 @@ function v2Model(model, baseURL) {
|
|
|
306
401
|
input: supportedModalities(model.modalities?.input ?? [TEXT_MODALITY]),
|
|
307
402
|
output: supportedModalities(model.modalities?.output ?? [TEXT_MODALITY]),
|
|
308
403
|
},
|
|
404
|
+
...(isRecord(model.options) ? { options: { ...model.options } } : {}),
|
|
309
405
|
...(v2Cost(model) ? { cost: v2Cost(model) } : {}),
|
|
310
406
|
...(model.limit ? { limit: { ...model.limit } } : {}),
|
|
311
407
|
...(v2Variants(model) ? { variants: v2Variants(model) } : {}),
|
|
@@ -319,13 +415,16 @@ export function mapModels(response, options = {}) {
|
|
|
319
415
|
const name = modelName(model);
|
|
320
416
|
const generated = generatedModel(model);
|
|
321
417
|
return name && generated
|
|
322
|
-
? [
|
|
418
|
+
? [
|
|
419
|
+
name,
|
|
420
|
+
enforceReasoningVariants(finalizedModel(deepMerge(generated, options.overrides?.[name]), options.overrides?.[name])),
|
|
421
|
+
]
|
|
323
422
|
: undefined;
|
|
324
423
|
})
|
|
325
424
|
.filter((entry) => Boolean(entry)));
|
|
326
425
|
}
|
|
327
426
|
export function buildProvider(models, options = {}) {
|
|
328
|
-
return deepMerge({
|
|
427
|
+
return enforceReasoningProviderModels(deepMerge({
|
|
329
428
|
id: options.providerKey ?? "LiteLLM",
|
|
330
429
|
name: options.providerName ?? "LiteLLM",
|
|
331
430
|
npm: "@ai-sdk/openai-compatible",
|
|
@@ -336,14 +435,14 @@ export function buildProvider(models, options = {}) {
|
|
|
336
435
|
...(options.apiKey ? { body: { apiKey: options.apiKey } } : {}),
|
|
337
436
|
},
|
|
338
437
|
models: mapModels(models, options),
|
|
339
|
-
}, options.providerOverrides);
|
|
438
|
+
}, options.providerOverrides));
|
|
340
439
|
}
|
|
341
440
|
export function mapModelsV2(models, options = {}) {
|
|
342
441
|
const legacy = mapModels(models, options);
|
|
343
442
|
return Object.fromEntries(Object.entries(legacy).map(([name, model]) => [name, v2Model(model, options.baseURL)]));
|
|
344
443
|
}
|
|
345
444
|
export function buildProviderV2(models, options = {}) {
|
|
346
|
-
|
|
445
|
+
const provider = deepMerge({
|
|
347
446
|
name: options.providerName ?? "LiteLLM",
|
|
348
447
|
api: {
|
|
349
448
|
type: "aisdk",
|
|
@@ -358,4 +457,24 @@ export function buildProviderV2(models, options = {}) {
|
|
|
358
457
|
},
|
|
359
458
|
models: mapModelsV2(models, options),
|
|
360
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
|
+
};
|
|
361
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;
|
|
@@ -120,6 +121,10 @@ export type LiteLLMModelInfo = JsonObject & {
|
|
|
120
121
|
supports_vision?: boolean;
|
|
121
122
|
supports_pdf_input?: boolean;
|
|
122
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;
|
|
123
128
|
supported_modalities?: string[];
|
|
124
129
|
output_modalities?: string[];
|
|
125
130
|
supported_params?: string[];
|
|
@@ -145,6 +150,7 @@ export type LiteLLMModelGroup = JsonObject & {
|
|
|
145
150
|
supports_vision?: boolean;
|
|
146
151
|
supports_pdf_input?: boolean;
|
|
147
152
|
supports_reasoning?: boolean;
|
|
153
|
+
supported_reasoning_efforts?: string[];
|
|
148
154
|
supported_modalities?: string[];
|
|
149
155
|
output_modalities?: string[];
|
|
150
156
|
supported_openai_params?: string[];
|