@heventure/model-provider-x 0.2.1 → 0.2.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/README.md +12 -0
- package/dist/cli/args.js +16 -0
- package/dist/cli/index.js +78 -4
- package/dist/core/provider.js +15 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,18 @@ node dist/cli/index.js \
|
|
|
56
56
|
OpenCode provider entries include `options.setCacheKey=true` by default.
|
|
57
57
|
This lets OpenCode pass a stable cache key through the OpenAI-compatible provider path so relays or local providers that support prompt caching can route repeated context to the same cache.
|
|
58
58
|
|
|
59
|
+
When targeting OpenCode, choose the direct API type interactively or pass `--opencode-api`:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
node dist/cli/index.js setup --target opencode --provider lmstudio --direct --opencode-api responses
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Supported values are:
|
|
66
|
+
|
|
67
|
+
- `chat`: writes `npm: "@ai-sdk/openai-compatible"` for `/v1/chat/completions`.
|
|
68
|
+
- `responses`: writes `npm: "@ai-sdk/openai"` for `/v1/responses`.
|
|
69
|
+
- `messages`: writes `npm: "@ai-sdk/anthropic"` for Anthropic-compatible `/v1/messages`.
|
|
70
|
+
|
|
59
71
|
## Claude Code Setup
|
|
60
72
|
|
|
61
73
|
Create or update a provider profile and write Claude Code user settings:
|
package/dist/cli/args.js
CHANGED
|
@@ -35,6 +35,9 @@ export function parseCliArgs(argv) {
|
|
|
35
35
|
case "--provider":
|
|
36
36
|
options.providerPreset = next();
|
|
37
37
|
break;
|
|
38
|
+
case "--opencode-api":
|
|
39
|
+
options.opencodeApiType = parseOpenCodeApiType(next());
|
|
40
|
+
break;
|
|
38
41
|
case "--proxy":
|
|
39
42
|
options.proxy = true;
|
|
40
43
|
break;
|
|
@@ -100,6 +103,7 @@ Options:
|
|
|
100
103
|
--name <name> Provider display name.
|
|
101
104
|
--id <id> Provider id used under provider.<id>.
|
|
102
105
|
--provider <id> Use a built-in provider preset, for example lmstudio or openai.
|
|
106
|
+
--opencode-api <api> OpenCode API type: chat, responses, or messages.
|
|
103
107
|
--proxy Write agent config through the local compatibility proxy.
|
|
104
108
|
--direct Write agent config directly to the upstream provider.
|
|
105
109
|
--models <list> Comma-separated model ids. Skips interactive model selection.
|
|
@@ -111,6 +115,18 @@ Options:
|
|
|
111
115
|
}
|
|
112
116
|
export class HelpRequested extends Error {
|
|
113
117
|
}
|
|
118
|
+
function parseOpenCodeApiType(value) {
|
|
119
|
+
if (value === "chat" || value === "chat-completions" || value === "openai-compatible") {
|
|
120
|
+
return "chat";
|
|
121
|
+
}
|
|
122
|
+
if (value === "responses" || value === "openai-responses") {
|
|
123
|
+
return "responses";
|
|
124
|
+
}
|
|
125
|
+
if (value === "messages" || value === "anthropic-messages") {
|
|
126
|
+
return "messages";
|
|
127
|
+
}
|
|
128
|
+
throw new Error(`Unknown OpenCode API type: ${value}`);
|
|
129
|
+
}
|
|
114
130
|
function addModelByOneBasedIndex(selected, models, index) {
|
|
115
131
|
const model = models[index - 1];
|
|
116
132
|
if (!model) {
|
package/dist/cli/index.js
CHANGED
|
@@ -81,7 +81,8 @@ export async function runCli(options) {
|
|
|
81
81
|
providerName,
|
|
82
82
|
baseURL: fetched.baseURL,
|
|
83
83
|
apiKey,
|
|
84
|
-
models: selectedModels
|
|
84
|
+
models: selectedModels,
|
|
85
|
+
opencodeApiType: options.opencodeApiType ?? "chat"
|
|
85
86
|
});
|
|
86
87
|
const provider = fragment.provider[providerId];
|
|
87
88
|
const json = JSON.stringify(fragment, null, 2);
|
|
@@ -118,7 +119,7 @@ async function runSetup(command) {
|
|
|
118
119
|
const target = await resolveSetupTarget(rl, command.target);
|
|
119
120
|
const useProxy = await resolveProxyMode(rl, command.options, capabilities, target);
|
|
120
121
|
if (target === "opencode") {
|
|
121
|
-
await writeOpenCodeSetup(rl, command, selection, useProxy);
|
|
122
|
+
await writeOpenCodeSetup(rl, command, selection, useProxy, capabilities);
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
124
125
|
if (target === "codex") {
|
|
@@ -172,15 +173,17 @@ async function collectProviderSelection(rl, command, providerInput) {
|
|
|
172
173
|
toolConfigPath
|
|
173
174
|
};
|
|
174
175
|
}
|
|
175
|
-
async function writeOpenCodeSetup(rl, command, selection, useProxy) {
|
|
176
|
+
async function writeOpenCodeSetup(rl, command, selection, useProxy, capabilities) {
|
|
176
177
|
const baseURL = useProxy ? `http://${selection.config.proxy.host}:${selection.config.proxy.port}/v1` : selection.upstreamBaseURL;
|
|
177
178
|
const apiKey = useProxy ? selection.config.proxy.authToken : selection.apiKey;
|
|
179
|
+
const opencodeApiType = await resolveOpenCodeApiType(rl, command, capabilities, useProxy);
|
|
178
180
|
const fragment = buildProviderConfig({
|
|
179
181
|
providerId: selection.providerId,
|
|
180
182
|
providerName: selection.providerName,
|
|
181
183
|
baseURL,
|
|
182
184
|
apiKey,
|
|
183
|
-
models: selection.selectedModels
|
|
185
|
+
models: selection.selectedModels,
|
|
186
|
+
opencodeApiType
|
|
184
187
|
});
|
|
185
188
|
const provider = fragment.provider[selection.providerId];
|
|
186
189
|
const targetPath = command.options.configPath ?? (await chooseConfigPath(rl, selection.providerId, command.options.yes));
|
|
@@ -362,6 +365,77 @@ async function resolveProxyMode(rl, options, capabilities, target) {
|
|
|
362
365
|
const accepted = !answer.trim() || answer.trim().toLowerCase() === "y";
|
|
363
366
|
return recommendedProxy ? accepted : !accepted;
|
|
364
367
|
}
|
|
368
|
+
async function resolveOpenCodeApiType(rl, command, capabilities, useProxy) {
|
|
369
|
+
if (command.options.opencodeApiType) {
|
|
370
|
+
return command.options.opencodeApiType;
|
|
371
|
+
}
|
|
372
|
+
const availableApis = useProxy ? new Set(["openai-compatible", "openai-responses", "anthropic-messages"]) : new Set(capabilities.apis);
|
|
373
|
+
const recommended = recommendedOpenCodeApiType(availableApis);
|
|
374
|
+
if (command.options.yes) {
|
|
375
|
+
return recommended;
|
|
376
|
+
}
|
|
377
|
+
const choices = [
|
|
378
|
+
{
|
|
379
|
+
label: "Responses API",
|
|
380
|
+
value: "responses",
|
|
381
|
+
hint: choiceHint("openai-responses", recommended, "responses", availableApis)
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
label: "Chat Completions API",
|
|
385
|
+
value: "chat",
|
|
386
|
+
hint: choiceHint("openai-compatible", recommended, "chat", availableApis)
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
label: "Anthropic Messages API",
|
|
390
|
+
value: "messages",
|
|
391
|
+
hint: choiceHint("anthropic-messages", recommended, "messages", availableApis)
|
|
392
|
+
}
|
|
393
|
+
];
|
|
394
|
+
if (canUseTui()) {
|
|
395
|
+
return selectChoice("OpenCode API type", choices);
|
|
396
|
+
}
|
|
397
|
+
output.write("OpenCode API types:\n");
|
|
398
|
+
choices.forEach((choice, index) => {
|
|
399
|
+
output.write(`${index + 1}. ${choice.label}${choice.hint ? ` - ${choice.hint}` : ""}\n`);
|
|
400
|
+
});
|
|
401
|
+
const answer = await rl.question(`OpenCode API type [${recommended}]: `);
|
|
402
|
+
const value = answer.trim();
|
|
403
|
+
if (!value) {
|
|
404
|
+
return recommended;
|
|
405
|
+
}
|
|
406
|
+
if (value === "1" || value === "responses") {
|
|
407
|
+
return "responses";
|
|
408
|
+
}
|
|
409
|
+
if (value === "2" || value === "chat" || value === "chat-completions") {
|
|
410
|
+
return "chat";
|
|
411
|
+
}
|
|
412
|
+
if (value === "3" || value === "messages") {
|
|
413
|
+
return "messages";
|
|
414
|
+
}
|
|
415
|
+
throw new Error(`Unknown OpenCode API type: ${value}`);
|
|
416
|
+
}
|
|
417
|
+
function recommendedOpenCodeApiType(apis) {
|
|
418
|
+
if (apis.has("openai-responses")) {
|
|
419
|
+
return "responses";
|
|
420
|
+
}
|
|
421
|
+
if (apis.has("openai-compatible")) {
|
|
422
|
+
return "chat";
|
|
423
|
+
}
|
|
424
|
+
if (apis.has("anthropic-messages")) {
|
|
425
|
+
return "messages";
|
|
426
|
+
}
|
|
427
|
+
return "chat";
|
|
428
|
+
}
|
|
429
|
+
function choiceHint(api, recommended, value, availableApis) {
|
|
430
|
+
const hints = [];
|
|
431
|
+
if (value === recommended) {
|
|
432
|
+
hints.push("recommended");
|
|
433
|
+
}
|
|
434
|
+
if (!availableApis.has(api)) {
|
|
435
|
+
hints.push("not detected");
|
|
436
|
+
}
|
|
437
|
+
return hints.join(", ");
|
|
438
|
+
}
|
|
365
439
|
async function resolveDefaultModel(rl, command, selectedModels) {
|
|
366
440
|
if (command.defaultModel) {
|
|
367
441
|
if (!selectedModels.includes(command.defaultModel)) {
|
package/dist/core/provider.js
CHANGED
|
@@ -71,15 +71,18 @@ export function recommendProxyMode(capabilities, target) {
|
|
|
71
71
|
export function buildProviderConfig(input) {
|
|
72
72
|
const baseURL = normalizeBaseUrl(input.baseURL);
|
|
73
73
|
const apiKey = input.apiKey?.trim();
|
|
74
|
+
const opencodeApiType = input.opencodeApiType ?? "chat";
|
|
74
75
|
const provider = {
|
|
75
|
-
npm:
|
|
76
|
+
npm: npmPackageForOpenCodeApiType(opencodeApiType),
|
|
76
77
|
name: input.providerName.trim(),
|
|
77
78
|
options: {
|
|
78
|
-
baseURL
|
|
79
|
-
setCacheKey: true
|
|
79
|
+
baseURL
|
|
80
80
|
},
|
|
81
81
|
models: Object.fromEntries(input.models.map((model) => [model, { name: model }]))
|
|
82
82
|
};
|
|
83
|
+
if (opencodeApiType !== "messages") {
|
|
84
|
+
provider.options.setCacheKey = true;
|
|
85
|
+
}
|
|
83
86
|
if (apiKey) {
|
|
84
87
|
provider.options.apiKey = apiKey;
|
|
85
88
|
}
|
|
@@ -90,6 +93,15 @@ export function buildProviderConfig(input) {
|
|
|
90
93
|
}
|
|
91
94
|
};
|
|
92
95
|
}
|
|
96
|
+
export function npmPackageForOpenCodeApiType(apiType) {
|
|
97
|
+
if (apiType === "responses") {
|
|
98
|
+
return "@ai-sdk/openai";
|
|
99
|
+
}
|
|
100
|
+
if (apiType === "messages") {
|
|
101
|
+
return "@ai-sdk/anthropic";
|
|
102
|
+
}
|
|
103
|
+
return "@ai-sdk/openai-compatible";
|
|
104
|
+
}
|
|
93
105
|
function isModelListResponse(body) {
|
|
94
106
|
return (typeof body === "object" &&
|
|
95
107
|
body !== null &&
|
package/package.json
CHANGED