@staticeng/opencode-litellm 0.1.3 → 0.1.5

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 CHANGED
File without changes
package/README.md CHANGED
File without changes
package/dist/helpers.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { buildProvider, mapModels, normalizeModelListResponse, modelListResponseShape } from "./mapping.js";
1
+ export { buildProvider, buildProviderV2, mapModels, mapModelsV2 } from "./mapping.js";
2
2
  export { fetchModelInfo } from "./litellm.js";
3
3
  export { deepMerge } from "./merge.js";
4
- export type { LiteLLMPluginOptions, LiteLLMModelInfo, OpenCodeProvider, OpenCodeModel } from "./types.js";
4
+ export type { LiteLLMModelInfo, LiteLLMPluginOptions, OpenCodeModel, OpenCodeModelV2, OpenCodeProvider, OpenCodeProviderV2, } from "./types.js";
package/dist/helpers.js CHANGED
@@ -1,3 +1,3 @@
1
- export { buildProvider, mapModels, normalizeModelListResponse, modelListResponseShape } from "./mapping.js";
1
+ export { buildProvider, buildProviderV2, mapModels, mapModelsV2 } from "./mapping.js";
2
2
  export { fetchModelInfo } from "./litellm.js";
3
3
  export { deepMerge } from "./merge.js";
package/dist/index.d.ts CHANGED
@@ -4,4 +4,3 @@ declare const LiteLLMPlugin: (ctx: PluginInput, options?: LiteLLMPluginOptions)
4
4
  config: (cfg: Config) => Promise<void>;
5
5
  }>;
6
6
  export default LiteLLMPlugin;
7
- export type { LiteLLMPluginOptions, LiteLLMModelInfo, OpenCodeProvider, OpenCodeModel } from "./types.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { fetchModelInfo } from "./litellm.js";
2
- import { buildProvider } from "./mapping.js";
2
+ import { buildProvider, buildProviderV2 } 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 } });
@@ -11,21 +11,26 @@ const LiteLLMPlugin = async (ctx, options) => {
11
11
  try {
12
12
  const models = await fetchModelInfo(resolved);
13
13
  const provider = buildProvider(models, resolved);
14
+ const config = cfg;
15
+ const providerV2 = buildProviderV2(models, resolved);
14
16
  cfg.provider = {
15
17
  ...(cfg.provider ?? {}),
16
18
  [resolved.providerKey]: provider,
17
19
  };
20
+ config.providers = {
21
+ ...(config.providers ?? {}),
22
+ [resolved.providerKey]: providerV2,
23
+ };
18
24
  await log(ctx, "info", "loaded LiteLLM provider metadata", {
19
25
  providerKey: resolved.providerKey,
20
26
  modelCount: Object.keys(provider.models ?? {}).length,
27
+ configProviderV2: true,
21
28
  });
22
29
  }
23
30
  catch (error) {
24
- const errorShape = error instanceof Error && "responseShape" in error ? error.responseShape : undefined;
25
31
  await log(ctx, resolved.strict ? "error" : "warn", "failed to load LiteLLM provider metadata", {
26
32
  providerKey: resolved.providerKey,
27
33
  error: error instanceof Error ? error.message : String(error),
28
- ...(errorShape ? { responseShape: errorShape } : {}),
29
34
  });
30
35
  if (resolved.strict)
31
36
  throw error;
package/dist/litellm.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { type ResolvedOptions } from "./options.js";
2
- export declare function fetchModelInfo(options: ResolvedOptions, fetchImpl?: typeof fetch): Promise<import("./types.js").LiteLLMModelInfo[]>;
2
+ import type { LiteLLMModelInfo } from "./types.js";
3
+ export declare function fetchModelInfo(options: ResolvedOptions, fetchImpl?: typeof fetch): Promise<LiteLLMModelInfo[]>;
package/dist/litellm.js CHANGED
@@ -1,5 +1,7 @@
1
- import { normalizeModelListResponse } from "./mapping.js";
2
1
  import { modelInfoURL } from "./options.js";
2
+ function isModelInfoArray(value) {
3
+ return Array.isArray(value);
4
+ }
3
5
  export async function fetchModelInfo(options, fetchImpl = fetch) {
4
6
  if (!options.baseURL)
5
7
  throw new Error("opencode-litellm requires options.baseURL or LITELLM_BASE_URL");
@@ -18,7 +20,12 @@ export async function fetchModelInfo(options, fetchImpl = fetch) {
18
20
  });
19
21
  if (!response.ok)
20
22
  throw new Error(`LiteLLM /model/info returned HTTP ${response.status}`);
21
- return normalizeModelListResponse(await response.json());
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");
22
29
  }
23
30
  finally {
24
31
  clearTimeout(timeout);
package/dist/mapping.d.ts CHANGED
@@ -1,15 +1,9 @@
1
- import type { LiteLLMModelInfo, LiteLLMPluginOptions, OpenCodeModel, OpenCodeProvider } from "./types.js";
2
- export type ModelListResponseShape = {
3
- type: string;
4
- length?: number;
5
- keyCount?: number;
6
- arrayFields?: string[];
7
- objectValueCount?: number;
8
- scalarValueCount?: number;
9
- };
10
- export declare function modelListResponseShape(value: unknown): ModelListResponseShape;
11
- export declare function normalizeModelListResponse(response: unknown): LiteLLMModelInfo[];
12
- export declare function mapModels(response: unknown, options?: LiteLLMPluginOptions): {
1
+ import type { LiteLLMModelInfo, LiteLLMPluginOptions, OpenCodeModel, OpenCodeModelV2, OpenCodeProvider, OpenCodeProviderV2 } from "./types.js";
2
+ export declare function mapModels(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): {
13
3
  [k: string]: OpenCodeModel;
14
4
  };
15
- export declare function buildProvider(models: unknown, options?: LiteLLMPluginOptions): OpenCodeProvider;
5
+ export declare function buildProvider(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): OpenCodeProvider;
6
+ export declare function mapModelsV2(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): {
7
+ [k: string]: OpenCodeModelV2;
8
+ };
9
+ export declare function buildProviderV2(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): OpenCodeProviderV2;
package/dist/mapping.js CHANGED
@@ -3,51 +3,6 @@ const DEFAULT_MODES = new Set(["chat", "responses"]);
3
3
  const NON_CHAT_MODES = new Set(["embedding", "rerank"]);
4
4
  const TEXT_MODALITY = "text";
5
5
  const TEXT_ONLY_MODELS = new Set(["minimax-m2.7", "minimax_m2.7"]);
6
- const MODEL_LIST_RESPONSE_ERROR_MESSAGE = "LiteLLM model list response did not contain a model array";
7
- const MODEL_ARRAY_FIELDS = ["data", "models", "model_info"];
8
- const MODEL_INFO_FIELDS = new Set([
9
- "id",
10
- "base_model",
11
- "mode",
12
- "input_cost_per_token",
13
- "output_cost_per_token",
14
- "max_tokens",
15
- "max_input_tokens",
16
- "max_output_tokens",
17
- "supports_function_calling",
18
- "supports_tool_choice",
19
- "supports_vision",
20
- "supports_pdf_input",
21
- "supports_reasoning",
22
- "supported_modalities",
23
- "output_modalities",
24
- "supported_params",
25
- ]);
26
- function isRecord(value) {
27
- return typeof value === "object" && value !== null && !Array.isArray(value);
28
- }
29
- export function modelListResponseShape(value) {
30
- if (Array.isArray(value))
31
- return { type: "array", length: value.length };
32
- if (value === null)
33
- return { type: "null" };
34
- if (!isRecord(value))
35
- return { type: typeof value };
36
- const values = Object.values(value);
37
- return {
38
- type: "object",
39
- keyCount: Object.keys(value).length,
40
- arrayFields: MODEL_ARRAY_FIELDS.filter((field) => Array.isArray(value[field])),
41
- objectValueCount: values.filter(isRecord).length,
42
- scalarValueCount: values.filter((entry) => !isRecord(entry) && !Array.isArray(entry)).length,
43
- };
44
- }
45
- function modelListResponseError(response) {
46
- return Object.assign(new Error(MODEL_LIST_RESPONSE_ERROR_MESSAGE), {
47
- name: "LiteLLMModelListResponseError",
48
- responseShape: modelListResponseShape(response),
49
- });
50
- }
51
6
  function asString(value) {
52
7
  return typeof value === "string" && value.trim() ? value : undefined;
53
8
  }
@@ -65,44 +20,6 @@ function unique(values) {
65
20
  function modelName(model) {
66
21
  return asString(model.model_name) ?? asString(model.litellm_params?.model) ?? asString(model.model_info?.id);
67
22
  }
68
- function hasModelInfoFields(value) {
69
- return Object.keys(value).some((key) => MODEL_INFO_FIELDS.has(key));
70
- }
71
- function isPlausibleMappedModel(value) {
72
- if (isRecord(value.model_info) && hasModelInfoFields(value.model_info))
73
- return true;
74
- if (isRecord(value.litellm_params) && typeof value.litellm_params.model === "string")
75
- return true;
76
- return hasModelInfoFields(value);
77
- }
78
- function normalizeMappedModel(name, value) {
79
- if (!isRecord(value))
80
- return undefined;
81
- const model = value;
82
- if (modelName(model))
83
- return model;
84
- if (!isPlausibleMappedModel(value))
85
- return undefined;
86
- return { ...model, model_name: name };
87
- }
88
- export function normalizeModelListResponse(response) {
89
- if (Array.isArray(response))
90
- return response;
91
- if (isRecord(response)) {
92
- for (const field of MODEL_ARRAY_FIELDS) {
93
- if (Array.isArray(response[field]))
94
- return response[field];
95
- }
96
- if (MODEL_ARRAY_FIELDS.some((field) => field in response))
97
- throw modelListResponseError(response);
98
- const mappedModels = Object.entries(response)
99
- .map(([name, value]) => normalizeMappedModel(name, value))
100
- .filter((model) => Boolean(model));
101
- if (mappedModels.length > 0)
102
- return mappedModels;
103
- }
104
- throw modelListResponseError(response);
105
- }
106
23
  function mode(model) {
107
24
  return asString(model.model_info?.mode) ?? "chat";
108
25
  }
@@ -135,6 +52,22 @@ function cost(model) {
135
52
  : { cache_read: perMillion(model.model_info?.cache_read_input_token_cost) }),
136
53
  };
137
54
  }
55
+ function v2Cost(model) {
56
+ if (!model.cost)
57
+ return undefined;
58
+ return {
59
+ input: model.cost.input,
60
+ output: model.cost.output,
61
+ ...(!model.cost.cache_read && !model.cost.cache_write
62
+ ? {}
63
+ : {
64
+ cache: {
65
+ ...(model.cost.cache_read === undefined ? {} : { read: model.cost.cache_read }),
66
+ ...(model.cost.cache_write === undefined ? {} : { write: model.cost.cache_write }),
67
+ },
68
+ }),
69
+ };
70
+ }
138
71
  function limit(model) {
139
72
  const context = asNumber(model.model_info?.max_input_tokens) ?? asNumber(model.model_info?.max_tokens);
140
73
  const output = asNumber(model.model_info?.max_output_tokens) ?? asNumber(model.model_info?.max_tokens);
@@ -183,7 +116,7 @@ function generatedModel(model) {
183
116
  return undefined;
184
117
  const mappedModalities = modalities(model);
185
118
  return {
186
- id: asString(model.model_info?.id) ?? name,
119
+ id: name,
187
120
  name,
188
121
  ...(asString(model.model_info?.base_model) ? { family: asString(model.model_info?.base_model) } : {}),
189
122
  ...(cost(model) ? { cost: cost(model) } : {}),
@@ -194,8 +127,43 @@ function generatedModel(model) {
194
127
  ...(model.model_info?.supports_function_calling || model.model_info?.supports_tool_choice ? { tool_call: true } : {}),
195
128
  };
196
129
  }
197
- export function mapModels(response, options = {}) {
198
- const models = normalizeModelListResponse(response);
130
+ function v2Variants(model) {
131
+ const variants = model.variants;
132
+ if (!variants || typeof variants !== "object" || Array.isArray(variants))
133
+ return undefined;
134
+ return Object.entries(variants).map(([id, value]) => {
135
+ const raw = value && typeof value === "object" && !Array.isArray(value) ? value : {};
136
+ const body = raw.options && typeof raw.options === "object" && !Array.isArray(raw.options)
137
+ ? raw.options
138
+ : raw;
139
+ return { id, body };
140
+ });
141
+ }
142
+ function v2Model(model, baseURL) {
143
+ const id = asString(model.id) ?? asString(model.name);
144
+ const name = asString(model.name);
145
+ const family = asString(model.family);
146
+ return {
147
+ ...(family ? { family } : {}),
148
+ ...(name ? { name } : {}),
149
+ api: {
150
+ ...(id ? { id } : {}),
151
+ type: "aisdk",
152
+ package: "@ai-sdk/openai-compatible",
153
+ ...(baseURL ? { url: baseURL } : {}),
154
+ settings: {},
155
+ },
156
+ capabilities: {
157
+ tools: model.tool_call ?? false,
158
+ input: model.modalities?.input ?? [TEXT_MODALITY],
159
+ output: model.modalities?.output ?? [TEXT_MODALITY],
160
+ },
161
+ ...(v2Cost(model) ? { cost: v2Cost(model) } : {}),
162
+ ...(model.limit ? { limit: { ...model.limit } } : {}),
163
+ ...(v2Variants(model) ? { variants: v2Variants(model) } : {}),
164
+ };
165
+ }
166
+ export function mapModels(models, options = {}) {
199
167
  return Object.fromEntries(models
200
168
  .filter((model) => allowedByFilters(model, options))
201
169
  .map((model) => {
@@ -218,3 +186,24 @@ export function buildProvider(models, options = {}) {
218
186
  models: mapModels(models, options),
219
187
  }, options.providerOverrides);
220
188
  }
189
+ export function mapModelsV2(models, options = {}) {
190
+ const legacy = mapModels(models, options);
191
+ return Object.fromEntries(Object.entries(legacy).map(([name, model]) => [name, v2Model(model, options.baseURL)]));
192
+ }
193
+ export function buildProviderV2(models, options = {}) {
194
+ return {
195
+ name: options.providerName ?? "LiteLLM",
196
+ api: {
197
+ type: "aisdk",
198
+ package: "@ai-sdk/openai-compatible",
199
+ ...(options.baseURL ? { url: options.baseURL } : {}),
200
+ settings: {},
201
+ },
202
+ request: {
203
+ body: {
204
+ ...(options.apiKey ? { apiKey: options.apiKey } : {}),
205
+ },
206
+ },
207
+ models: mapModelsV2(models, options),
208
+ };
209
+ }
package/dist/merge.d.ts CHANGED
File without changes
package/dist/merge.js CHANGED
File without changes
package/dist/options.d.ts CHANGED
File without changes
package/dist/options.js CHANGED
File without changes
package/dist/types.d.ts CHANGED
@@ -21,6 +21,46 @@ export type OpenCodeModel = JsonObject & {
21
21
  reasoning?: boolean;
22
22
  tool_call?: boolean;
23
23
  };
24
+ export type OpenCodeModelV2 = JsonObject & {
25
+ family?: string;
26
+ name?: string;
27
+ api?: {
28
+ id?: string;
29
+ type: "aisdk";
30
+ package: string;
31
+ url?: string;
32
+ settings?: JsonObject;
33
+ };
34
+ capabilities?: {
35
+ tools: boolean;
36
+ input: string[];
37
+ output: string[];
38
+ };
39
+ request?: {
40
+ headers?: Record<string, string>;
41
+ body?: JsonObject;
42
+ variant?: string;
43
+ };
44
+ variants?: Array<{
45
+ id: string;
46
+ headers?: Record<string, string>;
47
+ body?: JsonObject;
48
+ }>;
49
+ cost?: {
50
+ input: number;
51
+ output: number;
52
+ cache?: {
53
+ read?: number;
54
+ write?: number;
55
+ };
56
+ };
57
+ disabled?: boolean;
58
+ limit?: {
59
+ context?: number;
60
+ input?: number;
61
+ output?: number;
62
+ };
63
+ };
24
64
  export type OpenCodeProvider = JsonObject & {
25
65
  id?: string;
26
66
  name?: string;
@@ -29,6 +69,20 @@ export type OpenCodeProvider = JsonObject & {
29
69
  options?: JsonObject;
30
70
  models: Record<string, OpenCodeModel>;
31
71
  };
72
+ export type OpenCodeProviderV2 = JsonObject & {
73
+ name?: string;
74
+ api: {
75
+ type: "aisdk";
76
+ package: string;
77
+ url?: string;
78
+ settings?: JsonObject;
79
+ };
80
+ request: {
81
+ headers?: Record<string, string>;
82
+ body: JsonObject;
83
+ };
84
+ models: Record<string, OpenCodeModelV2>;
85
+ };
32
86
  export type LiteLLMPluginOptions = {
33
87
  baseURL?: string;
34
88
  apiKey?: string;
@@ -69,13 +123,12 @@ export type LiteLLMModelInfo = JsonObject & {
69
123
  supported_params?: string[];
70
124
  };
71
125
  };
72
- export type LiteLLMModelInfoResponse = LiteLLMModelInfo[] | {
126
+ export type LiteLLMModelInfoResponse = {
73
127
  data?: LiteLLMModelInfo[];
74
- models?: LiteLLMModelInfo[];
75
- model_info?: LiteLLMModelInfo[];
76
128
  };
77
129
  export type OpenCodeConfig = {
78
130
  provider?: Record<string, OpenCodeProvider>;
131
+ providers?: Record<string, OpenCodeProviderV2>;
79
132
  };
80
133
  export type PluginContext = {
81
134
  client?: {
package/dist/types.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staticeng/opencode-litellm",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenCode plugin that discovers LiteLLM models at runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",