@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 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)) {
@@ -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: "@ai-sdk/openai-compatible",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heventure/model-provider-x",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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",