@staticeng/opencode-litellm 0.1.0 → 0.1.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/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export declare const LiteLLMPlugin: (ctx: PluginInput, options?: LiteLLMPluginOp
4
4
  config: (cfg: Config) => Promise<void>;
5
5
  }>;
6
6
  export default LiteLLMPlugin;
7
- export { buildProvider, mapModels } from "./mapping.js";
7
+ export { buildProvider, mapModels, normalizeModelListResponse, modelListResponseShape } from "./mapping.js";
8
8
  export { fetchModelInfo } from "./litellm.js";
9
9
  export { deepMerge } from "./merge.js";
10
10
  export type { LiteLLMPluginOptions, LiteLLMModelInfo, OpenCodeProvider, OpenCodeModel } from "./types.js";
package/dist/index.js CHANGED
@@ -21,9 +21,11 @@ export const LiteLLMPlugin = async (ctx, options) => {
21
21
  });
22
22
  }
23
23
  catch (error) {
24
+ const errorShape = error instanceof Error && "responseShape" in error ? error.responseShape : undefined;
24
25
  await log(ctx, resolved.strict ? "error" : "warn", "failed to load LiteLLM provider metadata", {
25
26
  providerKey: resolved.providerKey,
26
27
  error: error instanceof Error ? error.message : String(error),
28
+ ...(errorShape ? { responseShape: errorShape } : {}),
27
29
  });
28
30
  if (resolved.strict)
29
31
  throw error;
@@ -32,6 +34,6 @@ export const LiteLLMPlugin = async (ctx, options) => {
32
34
  };
33
35
  };
34
36
  export default LiteLLMPlugin;
35
- export { buildProvider, mapModels } from "./mapping.js";
37
+ export { buildProvider, mapModels, normalizeModelListResponse, modelListResponseShape } from "./mapping.js";
36
38
  export { fetchModelInfo } from "./litellm.js";
37
39
  export { deepMerge } from "./merge.js";
package/dist/litellm.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  import { type ResolvedOptions } from "./options.js";
2
- import type { LiteLLMModelInfo } from "./types.js";
3
- export declare function fetchModelInfo(options: ResolvedOptions, fetchImpl?: typeof fetch): Promise<LiteLLMModelInfo[]>;
2
+ export declare function fetchModelInfo(options: ResolvedOptions, fetchImpl?: typeof fetch): Promise<import("./types.js").LiteLLMModelInfo[]>;
package/dist/litellm.js CHANGED
@@ -1,7 +1,5 @@
1
+ import { normalizeModelListResponse } from "./mapping.js";
1
2
  import { modelInfoURL } from "./options.js";
2
- function isModelInfoArray(value) {
3
- return Array.isArray(value);
4
- }
5
3
  export async function fetchModelInfo(options, fetchImpl = fetch) {
6
4
  if (!options.baseURL)
7
5
  throw new Error("opencode-litellm requires options.baseURL or LITELLM_BASE_URL");
@@ -20,12 +18,7 @@ export async function fetchModelInfo(options, fetchImpl = fetch) {
20
18
  });
21
19
  if (!response.ok)
22
20
  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");
21
+ return normalizeModelListResponse(await response.json());
29
22
  }
30
23
  finally {
31
24
  clearTimeout(timeout);
package/dist/mapping.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import type { LiteLLMModelInfo, LiteLLMPluginOptions, OpenCodeModel, OpenCodeProvider } from "./types.js";
2
- export declare function mapModels(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): {
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): {
3
13
  [k: string]: OpenCodeModel;
4
14
  };
5
- export declare function buildProvider(models: LiteLLMModelInfo[], options?: LiteLLMPluginOptions): OpenCodeProvider;
15
+ export declare function buildProvider(models: unknown, options?: LiteLLMPluginOptions): OpenCodeProvider;
package/dist/mapping.js CHANGED
@@ -2,6 +2,52 @@ 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 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
+ }
5
51
  function asString(value) {
6
52
  return typeof value === "string" && value.trim() ? value : undefined;
7
53
  }
@@ -19,6 +65,44 @@ function unique(values) {
19
65
  function modelName(model) {
20
66
  return asString(model.model_name) ?? asString(model.litellm_params?.model) ?? asString(model.model_info?.id);
21
67
  }
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
+ }
22
106
  function mode(model) {
23
107
  return asString(model.model_info?.mode) ?? "chat";
24
108
  }
@@ -62,7 +146,22 @@ function limit(model) {
62
146
  output,
63
147
  };
64
148
  }
149
+ function canonicalModelName(model) {
150
+ const name = modelName(model);
151
+ const base = asString(model.model_info?.base_model);
152
+ const id = asString(model.model_info?.id);
153
+ return [name, base, id].filter((value) => Boolean(value)).map((value) => value.toLowerCase());
154
+ }
155
+ function isTextOnlyModel(model) {
156
+ return canonicalModelName(model).some((name) => TEXT_ONLY_MODELS.has(name));
157
+ }
65
158
  function modalities(model) {
159
+ if (isTextOnlyModel(model)) {
160
+ return {
161
+ input: [TEXT_MODALITY],
162
+ output: [TEXT_MODALITY],
163
+ };
164
+ }
66
165
  return {
67
166
  input: unique([
68
167
  TEXT_MODALITY,
@@ -95,7 +194,8 @@ function generatedModel(model) {
95
194
  ...(model.model_info?.supports_function_calling || model.model_info?.supports_tool_choice ? { tool_call: true } : {}),
96
195
  };
97
196
  }
98
- export function mapModels(models, options = {}) {
197
+ export function mapModels(response, options = {}) {
198
+ const models = normalizeModelListResponse(response);
99
199
  return Object.fromEntries(models
100
200
  .filter((model) => allowedByFilters(model, options))
101
201
  .map((model) => {
package/dist/types.d.ts CHANGED
@@ -69,8 +69,10 @@ export type LiteLLMModelInfo = JsonObject & {
69
69
  supported_params?: string[];
70
70
  };
71
71
  };
72
- export type LiteLLMModelInfoResponse = {
72
+ export type LiteLLMModelInfoResponse = LiteLLMModelInfo[] | {
73
73
  data?: LiteLLMModelInfo[];
74
+ models?: LiteLLMModelInfo[];
75
+ model_info?: LiteLLMModelInfo[];
74
76
  };
75
77
  export type OpenCodeConfig = {
76
78
  provider?: Record<string, OpenCodeProvider>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staticeng/opencode-litellm",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "OpenCode plugin that discovers LiteLLM models at runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",