@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 +4 -0
- package/dist/cli/index.js +40 -10
- package/dist/cli/model-choices.js +17 -0
- package/dist/cli/provider-presets.js +24 -0
- package/dist/cli/tui.js +3 -1
- package/dist/core/provider.js +13 -2
- package/package.json +1 -1
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
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
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
|
|
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
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
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
|
|
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
|
|
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);
|
package/dist/core/provider.js
CHANGED
|
@@ -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 = [
|
|
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