@heventure/model-provider-x 0.1.0 → 0.1.1

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/cli/args.js CHANGED
@@ -32,6 +32,9 @@ export function parseCliArgs(argv) {
32
32
  case "--name":
33
33
  options.providerName = next();
34
34
  break;
35
+ case "--provider":
36
+ options.providerPreset = next();
37
+ break;
35
38
  case "--print":
36
39
  options.print = true;
37
40
  break;
@@ -89,6 +92,7 @@ Options:
89
92
  --api-key <key> Optional API key. Written into config when provided.
90
93
  --name <name> Provider display name.
91
94
  --id <id> Provider id used under provider.<id>.
95
+ --provider <id> Use a built-in provider preset, for example lmstudio or openai.
92
96
  --models <list> Comma-separated model ids. Skips interactive model selection.
93
97
  --config <path> OpenCode config path to write.
94
98
  --print Print generated JSON and do not write config.
package/dist/cli/index.js CHANGED
@@ -8,6 +8,8 @@ import { startProxyServer } from "../proxy/server.js";
8
8
  import { getDefaultClaudeSettingsPath, writeClaudeCodeSettings } from "../targets/claude-code.js";
9
9
  import { HelpRequested, parseModelSelection } from "./args.js";
10
10
  import { commandUsage, parseCommand } from "./commands.js";
11
+ import { createModelChoices } from "./model-choices.js";
12
+ import { createProviderChoices, getProviderPreset } from "./provider-presets.js";
11
13
  import { canUseTui, multiSelectChoices, renderIntro, selectChoice } from "./tui.js";
12
14
  async function main() {
13
15
  try {
@@ -62,15 +64,16 @@ export async function runCli(options) {
62
64
  const rl = createInterface({ input, output });
63
65
  try {
64
66
  output.write(canUseTui() ? renderIntro() : "model-provider-x\n\n");
65
- const providerName = await requiredOption(rl, options.providerName, "Provider name");
66
- const providerId = await requiredOption(rl, options.providerId ?? slugify(providerName), "Provider id");
67
- const baseURL = await requiredOption(rl, options.baseURL, "API base URL");
68
- const apiKey = options.apiKey ?? (await rl.question("API key (optional): "));
67
+ const providerDefaults = await resolveProviderDefaults(rl, options);
68
+ const providerName = await requiredOption(rl, options.providerName ?? providerDefaults.name, "Provider name");
69
+ const providerId = await requiredOption(rl, options.providerId ?? providerDefaults.id ?? slugify(providerName), "Provider id");
70
+ const baseURL = await requiredOption(rl, options.baseURL ?? providerDefaults.baseURL, "API base URL");
71
+ const apiKey = await resolveApiKey(rl, options.apiKey, providerDefaults.preset);
69
72
  output.write("Fetching models...\n");
70
73
  const fetched = await validateAndFetchModels({ baseURL, apiKey });
71
74
  const selectedModels = options.models ??
72
75
  (canUseTui()
73
- ? await multiSelectChoices("Select models", fetched.models.map((model) => ({ label: model, value: model })))
76
+ ? await multiSelectChoices("Select models", createModelChoices(fetched.models))
74
77
  : parseModelSelection(await rl.question(formatModelPrompt(fetched.models)), fetched.models));
75
78
  const fragment = buildProviderConfig({
76
79
  providerId,
@@ -114,15 +117,16 @@ async function runClaudeCodeSetup(command) {
114
117
  const rl = createInterface({ input, output });
115
118
  try {
116
119
  output.write(canUseTui() ? renderIntro() : "model-provider-x\n\n");
117
- const providerName = await requiredOption(rl, command.options.providerName, "Provider name");
118
- const providerId = await requiredOption(rl, command.profileId ?? command.options.providerId ?? slugify(providerName), "Provider id");
119
- const baseURL = await requiredOption(rl, command.options.baseURL, "API base URL");
120
- const apiKey = command.options.apiKey ?? (await rl.question("Upstream API key (optional): "));
120
+ const providerDefaults = await resolveProviderDefaults(rl, command.options);
121
+ const providerName = await requiredOption(rl, command.options.providerName ?? providerDefaults.name, "Provider name");
122
+ const providerId = await requiredOption(rl, command.profileId ?? command.options.providerId ?? providerDefaults.id ?? slugify(providerName), "Provider id");
123
+ const baseURL = await requiredOption(rl, command.options.baseURL ?? providerDefaults.baseURL, "API base URL");
124
+ const apiKey = await resolveApiKey(rl, command.options.apiKey, providerDefaults.preset, "Upstream API key");
121
125
  output.write("Fetching models...\n");
122
126
  const fetched = await validateAndFetchModels({ baseURL, apiKey });
123
127
  const selectedModels = command.options.models ??
124
128
  (canUseTui()
125
- ? await multiSelectChoices("Select Claude Code gateway models", fetched.models.map((model) => ({ label: model, value: model })))
129
+ ? await multiSelectChoices("Select Claude Code gateway models", createModelChoices(fetched.models))
126
130
  : parseModelSelection(await rl.question(formatModelPrompt(fetched.models)), fetched.models));
127
131
  const toolConfigPath = getDefaultToolConfigPath();
128
132
  const config = await upsertProviderProfile(toolConfigPath, {
@@ -163,6 +167,32 @@ async function requiredOption(rl, value, label) {
163
167
  }
164
168
  return answer.trim();
165
169
  }
170
+ async function resolveProviderDefaults(rl, options) {
171
+ if (options.baseURL) {
172
+ return {};
173
+ }
174
+ const presetId = options.providerPreset ?? (canUseTui() ? await selectChoice("Choose provider", createProviderChoices()) : undefined);
175
+ if (!presetId || presetId === "custom") {
176
+ return {};
177
+ }
178
+ const preset = getProviderPreset(presetId);
179
+ if (!preset) {
180
+ throw new Error(`Unknown provider preset: ${presetId}`);
181
+ }
182
+ return {
183
+ id: preset.id,
184
+ name: preset.name,
185
+ baseURL: preset.baseURL,
186
+ preset
187
+ };
188
+ }
189
+ async function resolveApiKey(rl, apiKey, preset, label = "API key") {
190
+ if (apiKey !== undefined) {
191
+ return apiKey;
192
+ }
193
+ const suffix = preset?.apiKeyRequired ? "" : " (optional)";
194
+ return rl.question(`${label}${suffix}: `);
195
+ }
166
196
  async function chooseConfigPath(rl, providerId, yes) {
167
197
  const configs = await discoverOpenCodeConfigs({ providerId });
168
198
  if (configs.length === 0) {
@@ -0,0 +1,17 @@
1
+ const likelyUnsupportedModelName = /\b(?:embed|embedding|embeddings|bge|e5|nomic-embed)\b/i;
2
+ export function createModelChoices(models) {
3
+ return models.map((model) => {
4
+ if (!isLikelyUnsupportedModelName(model)) {
5
+ return { label: model, value: model, selected: true };
6
+ }
7
+ return {
8
+ label: model,
9
+ value: model,
10
+ hint: "suspected unsupported model",
11
+ selected: false
12
+ };
13
+ });
14
+ }
15
+ export function isLikelyUnsupportedModelName(model) {
16
+ return likelyUnsupportedModelName.test(model);
17
+ }
@@ -0,0 +1,24 @@
1
+ const providerPresets = [
2
+ { id: "unsloth", name: "Unsloth Local", baseURL: "http://localhost:8888/v1", apiKeyRequired: false },
3
+ { id: "lmstudio", name: "LM Studio", baseURL: "http://localhost:1234/v1", apiKeyRequired: false },
4
+ { id: "ollama", name: "Ollama", baseURL: "http://localhost:11434/v1", apiKeyRequired: false },
5
+ { id: "vllm", name: "vLLM", baseURL: "http://localhost:8000/v1", apiKeyRequired: false },
6
+ { id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", apiKeyRequired: true },
7
+ { id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1", apiKeyRequired: true }
8
+ ];
9
+ export function getProviderPresets() {
10
+ return [...providerPresets];
11
+ }
12
+ export function getProviderPreset(id) {
13
+ return providerPresets.find((preset) => preset.id === id);
14
+ }
15
+ export function createProviderChoices() {
16
+ return [
17
+ ...providerPresets.map((preset) => ({
18
+ label: preset.name,
19
+ value: preset.id,
20
+ hint: preset.baseURL
21
+ })),
22
+ { label: "Custom provider", value: "custom", hint: "enter URL and API key manually" }
23
+ ];
24
+ }
package/dist/cli/tui.js CHANGED
@@ -45,7 +45,9 @@ export async function selectChoice(title, choices, streams = { input: process.st
45
45
  return selected.value;
46
46
  }
47
47
  export async function multiSelectChoices(title, choices, streams = { input: process.stdin, output: process.stdout }) {
48
- let selected = new Set(choices.map((choice, index) => (choice.disabled ? -1 : index)).filter((index) => index >= 0));
48
+ let selected = new Set(choices
49
+ .map((choice, index) => (choice.disabled || choice.selected === false ? -1 : index))
50
+ .filter((index) => index >= 0));
49
51
  const choice = await runKeyMenu((cursor) => renderMultiSelect(title, choices, cursor, selected), choices, streams, (key, cursor) => {
50
52
  if (key.name === "space") {
51
53
  selected = toggleSelectedIndex(selected, cursor);
@@ -26,9 +26,14 @@ export async function validateAndFetchModels(input, fetchImpl = globalThis.fetch
26
26
  if (!isModelListResponse(body)) {
27
27
  throw new Error("Expected /models to return an object with a data array");
28
28
  }
29
- const models = [...new Set(body.data.map((model) => model.id.trim()).filter(Boolean))];
29
+ const models = [
30
+ ...new Set(body.data
31
+ .filter(isOpenCodeCompatibleModel)
32
+ .map((model) => model.id.trim())
33
+ .filter(Boolean))
34
+ ];
30
35
  if (models.length === 0) {
31
- throw new Error("Provider returned no model ids");
36
+ throw new Error("Provider returned no OpenCode-compatible model ids");
32
37
  }
33
38
  return { baseURL, models };
34
39
  }
@@ -59,3 +64,9 @@ function isModelListResponse(body) {
59
64
  Array.isArray(body.data) &&
60
65
  body.data.every((model) => typeof model === "object" && model !== null && typeof model.id === "string"));
61
66
  }
67
+ function isOpenCodeCompatibleModel(model) {
68
+ if (typeof model.type !== "string") {
69
+ return true;
70
+ }
71
+ return ["llm", "chat", "completion", "text-generation"].includes(model.type.trim().toLowerCase());
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heventure/model-provider-x",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "TUI configurator and local API proxy for wiring custom model providers into OpenCode and Claude Code.",
5
5
  "private": false,
6
6
  "license": "MIT",