@darknesskiller/opencode-cliproxyapi 0.2.0 → 0.3.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to this project are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
  and this project follows [Semantic Versioning](https://semver.org/).
7
7
 
8
+ ## [0.3.0] - Unreleased
9
+
10
+ ### Added
11
+
12
+ - OpenCode v2 support. The package now default-exports a definition object with
13
+ `id`, `setup`, and `server`: OpenCode v2 loads it through `setup(ctx)` and
14
+ registers the provider with `ctx.provider.transform` (discovered catalog,
15
+ `@opencode/ai/providers/*` driver package, `baseURL`/`apiKey` settings),
16
+ while OpenCode v1 (1.18.29 and newer) keeps calling `server(ctx)`.
17
+ Anthropic-endpoint models are routed through
18
+ `@opencode/ai/providers/anthropic` via the per-model `package` field, and
19
+ v2 variants list exactly the effort levels the server reported.
20
+ - `index.ts` at the repository root so a local checkout can be used as the
21
+ plugin path without a build step.
22
+
23
+ ## [0.2.1] - 2026-09-09
24
+
25
+ ### Fixed
26
+
27
+ - Stop OpenCode from adding its own default reasoning efforts on top of the
28
+ server-reported ones. OpenCode merges per-model default variants
29
+ (e.g. `medium` for OpenAI-compatible models) with the plugin's variants, so
30
+ models configured on CLIProxyAPI with only `low`/`high`/`max` used to show
31
+ `low`/`medium`/`high`/`max` in the picker. Unreported default efforts are
32
+ now disabled and the picker shows exactly the server-supported set.
33
+
8
34
  ## [0.2.0] - 2026-09-09
9
35
 
10
36
  ### Added
@@ -52,7 +78,8 @@ and this project follows [Semantic Versioning](https://semver.org/).
52
78
  - Automatic model names and capability hints in OpenCode's model picker.
53
79
  - Local plugin and npm package installation flows.
54
80
 
55
- [Unreleased]: https://github.com/DarknessKiller/opencode-cliproxyapi/compare/v0.2.0...HEAD
81
+ [Unreleased]: https://github.com/DarknessKiller/opencode-cliproxyapi/compare/v0.2.1...HEAD
82
+ [0.2.1]: https://github.com/DarknessKiller/opencode-cliproxyapi/compare/v0.2.0...v0.2.1
56
83
  [0.2.0]: https://github.com/DarknessKiller/opencode-cliproxyapi/compare/v0.1.2...v0.2.0
57
84
  [0.1.2]: https://github.com/DarknessKiller/opencode-cliproxyapi/compare/v0.1.1...v0.1.2
58
85
  [0.1.1]: https://github.com/DarknessKiller/opencode-cliproxyapi/compare/v0.1.0...v0.1.1
package/README.md CHANGED
@@ -173,6 +173,50 @@ can be customized:
173
173
  }
174
174
  ```
175
175
 
176
+ ### OpenCode v2
177
+
178
+ OpenCode v2 replaced the `plugin` array with `plugins` and no longer runs v1
179
+ plugin code. This package ships a dual entrypoint: v2 calls `setup(ctx)` and
180
+ v1 (1.18.29 and newer) calls `server(ctx)`, so one configuration works on both.
181
+
182
+ ```jsonc
183
+ // OpenCode v2 (~/.config/opencode/opencode.jsonc)
184
+ {
185
+ "$schema": "https://opencode.ai/config.json",
186
+ "plugins": [
187
+ {
188
+ "package": "@darknesskiller/opencode-cliproxyapi",
189
+ "options": {
190
+ "baseURL": "http://your-server:8317",
191
+ "apiKey": "your-cli-proxy-api-key"
192
+ }
193
+ }
194
+ ]
195
+ }
196
+ ```
197
+
198
+ On v2 the plugin registers the provider itself: it publishes the discovered
199
+ catalog through `ctx.provider.transform`, selects
200
+ `@opencode/ai/providers/openai-compatible` (or `@opencode/ai/providers/openai`
201
+ with `protocol: "responses"`), and routes Anthropic-endpoint models through
202
+ `@opencode/ai/providers/anthropic`. Every option in the table above works
203
+ unchanged inside the plugin entry's `options`.
204
+
205
+ To run a checkout instead of the published package, point `package` at the
206
+ repository directory (OpenCode resolves its root `index.ts`) and use the local
207
+ path:
208
+
209
+ ```jsonc
210
+ {
211
+ "plugins": [
212
+ {
213
+ "package": "/path/to/opencode-cliproxyapi",
214
+ "options": { "baseURL": "http://your-server:8317" }
215
+ }
216
+ ]
217
+ }
218
+ ```
219
+
176
220
  ## Troubleshooting
177
221
 
178
222
  ### `Missing API key`
package/dist/index.d.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
+ import { setup } from "./plugin-v2.js";
2
3
  export declare const CLIProxyAPIPlugin: Plugin;
4
+ /**
5
+ * Dual V1/V2 entry point. OpenCode V2 calls `setup(ctx)`; V1 (1.18.29 and
6
+ * newer) calls `server(ctx)` for the classic hooks object.
7
+ */
3
8
  declare const _default: {
4
9
  id: string;
5
10
  server: Plugin;
11
+ setup: typeof setup;
6
12
  };
7
13
  export default _default;
8
14
  export { discoverModelProtocols, discoverModels, discoverReasoningEfforts, normalizeBaseURL, parseCatalog, parseModelProtocolCatalog, parseReasoningEffortCatalog, } from "./catalog.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAA+B,MAAM,qBAAqB,CAAA;AAkCtF,eAAO,MAAM,iBAAiB,EAAE,MA8E/B,CAAA;;;;;AAED,wBAGwB;AACxB,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EAChB,YAAY,EACZ,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,EAAgB,MAAM,qBAAqB,CAAA;AAuBvE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAWtC,eAAO,MAAM,iBAAiB,EAAE,MAiE/B,CAAA;AAED;;;GAGG;;;;;;AACH,wBAIkD;AAClD,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,wBAAwB,EACxB,gBAAgB,EAChB,YAAY,EACZ,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA"}
package/dist/index.js CHANGED
@@ -103,12 +103,177 @@ function isRecord(value) {
103
103
  return typeof value === "object" && value !== null && !Array.isArray(value);
104
104
  }
105
105
 
106
- // src/index.ts
106
+ // src/shared.ts
107
107
  var DEFAULT_BASE_URL = "http://localhost:8317/v1";
108
108
  var DEFAULT_PROVIDER_ID = "cliproxyapi";
109
109
  var DEFAULT_PROVIDER_NAME = "CLIProxyAPI";
110
110
  var DEFAULT_DISCOVERY_TIMEOUT_MS = 1e4;
111
111
  var DEFAULT_MODEL_METADATA_URL = "https://models.dev/api.json";
112
+ var V1_CHAT_PACKAGE = "@ai-sdk/openai-compatible";
113
+ var V1_RESPONSES_PACKAGE = "@ai-sdk/openai";
114
+ var V1_ANTHROPIC_PACKAGE = "@ai-sdk/anthropic";
115
+ var V2_CHAT_PACKAGE = "@opencode/ai/providers/openai-compatible";
116
+ var V2_RESPONSES_PACKAGE = "@opencode/ai/providers/openai";
117
+ var V2_ANTHROPIC_PACKAGE = "@opencode/ai/providers/anthropic";
118
+ function readOptions(input) {
119
+ if (!input)
120
+ return {};
121
+ return {
122
+ baseURL: stringOption(input.baseURL),
123
+ apiKey: stringOption(input.apiKey),
124
+ providerID: stringOption(input.providerID),
125
+ providerName: stringOption(input.providerName),
126
+ protocol: input.protocol === "responses" ? "responses" : input.protocol === "chat" ? "chat" : undefined,
127
+ modelMetadataURL: input.modelMetadataURL === false ? false : stringOption(input.modelMetadataURL),
128
+ discoveryTimeoutMs: typeof input.discoveryTimeoutMs === "number" && input.discoveryTimeoutMs > 0 ? input.discoveryTimeoutMs : undefined
129
+ };
130
+ }
131
+ function stringOption(value) {
132
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
133
+ }
134
+ function displayName(modelID) {
135
+ return modelID.split("-").map((part) => {
136
+ const lower = part.toLowerCase();
137
+ if (lower === "gpt")
138
+ return "GPT";
139
+ if (lower === "oss")
140
+ return "OSS";
141
+ if (lower === "codex")
142
+ return "Codex";
143
+ return part.charAt(0).toUpperCase() + part.slice(1);
144
+ }).join(" ");
145
+ }
146
+ function isImageModel(modelID) {
147
+ return /(?:^|-)image(?:-|$)/i.test(modelID);
148
+ }
149
+ function isReasoningModel(modelID, efforts) {
150
+ if (efforts?.levels.length)
151
+ return efforts.levels.some((level) => level !== "none");
152
+ return /thinking|reasoning|codex|gpt-(?:5|oss)/i.test(modelID);
153
+ }
154
+ function supportsAttachments(modelID) {
155
+ return /^(?:claude|gemini|gpt)/i.test(modelID);
156
+ }
157
+ function resolveModelNpm(catalog, providerID, modelID) {
158
+ const provider = catalog[providerID];
159
+ return provider?.models[modelID] ?? provider?.npm;
160
+ }
161
+ function isAnthropicRouted(model, protocolCatalog) {
162
+ return model.ownedBy !== undefined && resolveModelNpm(protocolCatalog, model.ownedBy, model.id) === V1_ANTHROPIC_PACKAGE;
163
+ }
164
+ function supportedEffortLevels(model, input) {
165
+ const efforts = input.effortCatalog[model.id];
166
+ if (!efforts)
167
+ return;
168
+ if (isAnthropicRouted(model, input.protocolCatalog))
169
+ return;
170
+ const levels = efforts.levels.filter((level) => level !== "none");
171
+ return levels.length > 0 ? levels : undefined;
172
+ }
173
+ async function discoverCatalog(input) {
174
+ const [catalog, protocolDiscovery, effortDiscovery] = await Promise.all([
175
+ discoverModels({ baseURL: input.baseURL, apiKey: input.apiKey, timeoutMs: input.timeoutMs }),
176
+ discoverProtocols({ url: input.modelMetadataURL, timeoutMs: input.timeoutMs }),
177
+ discoverEffortLevels({ baseURL: input.baseURL, apiKey: input.apiKey, timeoutMs: input.timeoutMs })
178
+ ]);
179
+ return {
180
+ catalog,
181
+ protocolCatalog: protocolDiscovery.catalog,
182
+ effortCatalog: effortDiscovery.catalog,
183
+ errors: [protocolDiscovery.error, effortDiscovery.error].filter((error) => error !== undefined)
184
+ };
185
+ }
186
+ function discoverProtocols(input) {
187
+ if (input.url === false)
188
+ return Promise.resolve({ catalog: {} });
189
+ return discoverModelProtocols({
190
+ url: input.url,
191
+ timeoutMs: input.timeoutMs
192
+ }).then((catalog) => ({ catalog })).catch((error) => ({
193
+ catalog: {},
194
+ error: error instanceof Error ? error.message : String(error)
195
+ }));
196
+ }
197
+ function discoverEffortLevels(input) {
198
+ return discoverReasoningEfforts(input).then((catalog) => ({ catalog })).catch((error) => ({
199
+ catalog: {},
200
+ error: error instanceof Error ? error.message : String(error)
201
+ }));
202
+ }
203
+
204
+ // src/plugin-v2.ts
205
+ var DEFAULT_CONTEXT_LIMIT = 128000;
206
+ var DEFAULT_OUTPUT_LIMIT = 4096;
207
+ function toV2Model(model, input) {
208
+ const image = isImageModel(model.id);
209
+ const levels = supportedEffortLevels(model, input.discovery);
210
+ const anthropic = isAnthropicRouted(model, input.discovery.protocolCatalog);
211
+ return {
212
+ id: model.id,
213
+ modelID: model.id,
214
+ providerID: input.providerID,
215
+ name: displayName(model.id),
216
+ ...anthropic ? { package: V2_ANTHROPIC_PACKAGE } : {},
217
+ capabilities: {
218
+ tools: !image,
219
+ input: image ? ["text", "image"] : ["text"],
220
+ output: image ? ["image"] : ["text"]
221
+ },
222
+ variants: (levels ?? []).map((level) => ({
223
+ id: level,
224
+ settings: { reasoningEffort: level }
225
+ })),
226
+ time: { released: 0 },
227
+ cost: [{ input: 0, output: 0, cache: { read: 0, write: 0 } }],
228
+ status: "active",
229
+ enabled: true,
230
+ limit: { context: DEFAULT_CONTEXT_LIMIT, output: DEFAULT_OUTPUT_LIMIT }
231
+ };
232
+ }
233
+ function toV2Models(input) {
234
+ return input.discovery.catalog.map((model) => toV2Model(model, { providerID: input.providerID, discovery: input.discovery }));
235
+ }
236
+ async function setup(ctx) {
237
+ const options = readOptions(ctx.options);
238
+ const providerID = options.providerID ?? DEFAULT_PROVIDER_ID;
239
+ const baseURL = normalizeBaseURL(options.baseURL ?? process.env.CLIPROXY_BASE_URL ?? DEFAULT_BASE_URL);
240
+ const apiKey = options.apiKey ?? process.env.CLIPROXY_API_KEY;
241
+ const timeoutMs = options.discoveryTimeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;
242
+ const modelMetadataURL = options.modelMetadataURL ?? DEFAULT_MODEL_METADATA_URL;
243
+ const discovery = await discoverCatalog({ baseURL, apiKey, modelMetadataURL, timeoutMs });
244
+ for (const error of discovery.errors) {
245
+ console.warn(`[cliproxyapi] ${error}`);
246
+ }
247
+ const models = toV2Models({ providerID, discovery });
248
+ const providerPackage = options.protocol === "responses" ? V2_RESPONSES_PACKAGE : V2_CHAT_PACKAGE;
249
+ await ctx.provider.transform((editor) => {
250
+ const settings = { baseURL };
251
+ if (apiKey)
252
+ settings.apiKey = apiKey;
253
+ const existing = editor.get(providerID);
254
+ if (existing) {
255
+ editor.update(providerID, (provider) => {
256
+ provider.name = provider.name || options.providerName || DEFAULT_PROVIDER_NAME;
257
+ provider.activation = "enabled";
258
+ provider.package = providerPackage;
259
+ provider.settings = { ...provider.settings, ...settings };
260
+ });
261
+ editor.models.set(providerID, models);
262
+ return;
263
+ }
264
+ const info = {
265
+ id: providerID,
266
+ name: options.providerName ?? DEFAULT_PROVIDER_NAME,
267
+ activation: "enabled",
268
+ package: providerPackage,
269
+ settings
270
+ };
271
+ editor.add({ info, models });
272
+ });
273
+ return async () => {};
274
+ }
275
+
276
+ // src/index.ts
112
277
  var CLIProxyAPIPlugin = async ({ client }, rawOptions) => {
113
278
  const options = readOptions(rawOptions);
114
279
  return {
@@ -119,24 +284,13 @@ var CLIProxyAPIPlugin = async ({ client }, rawOptions) => {
119
284
  const apiKey = options.apiKey ?? process.env.CLIPROXY_API_KEY ?? stringOption(existing?.options?.apiKey);
120
285
  try {
121
286
  const timeoutMs = options.discoveryTimeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;
122
- const [catalog, protocolDiscovery, effortDiscovery] = await Promise.all([
123
- discoverModels({
124
- baseURL,
125
- apiKey,
126
- timeoutMs
127
- }),
128
- discoverProtocols({
129
- url: options.modelMetadataURL ?? DEFAULT_MODEL_METADATA_URL,
130
- timeoutMs
131
- }),
132
- discoverEffortLevels({
133
- baseURL,
134
- apiKey,
135
- timeoutMs
136
- })
137
- ]);
138
- const discoveryErrors = [protocolDiscovery.error, effortDiscovery.error].filter((error) => error !== undefined);
139
- for (const message of discoveryErrors) {
287
+ const discovery = await discoverCatalog({
288
+ baseURL,
289
+ apiKey,
290
+ modelMetadataURL: options.modelMetadataURL ?? DEFAULT_MODEL_METADATA_URL,
291
+ timeoutMs
292
+ });
293
+ for (const message of discovery.errors) {
140
294
  await client.app.log({
141
295
  body: {
142
296
  service: "opencode-cliproxyapi",
@@ -151,15 +305,15 @@ var CLIProxyAPIPlugin = async ({ client }, rawOptions) => {
151
305
  baseURL,
152
306
  apiKey,
153
307
  protocol: options.protocol ?? "chat",
154
- catalog,
155
- protocolCatalog: protocolDiscovery.catalog,
156
- effortCatalog: effortDiscovery.catalog
308
+ catalog: discovery.catalog,
309
+ protocolCatalog: discovery.protocolCatalog,
310
+ effortCatalog: discovery.effortCatalog
157
311
  });
158
312
  await client.app.log({
159
313
  body: {
160
314
  service: "opencode-cliproxyapi",
161
315
  level: "info",
162
- message: `Discovered ${catalog.length} CLIProxyAPI models from ${baseURL}`
316
+ message: `Discovered ${discovery.catalog.length} CLIProxyAPI models from ${baseURL}`
163
317
  }
164
318
  });
165
319
  } catch (error) {
@@ -177,7 +331,8 @@ var CLIProxyAPIPlugin = async ({ client }, rawOptions) => {
177
331
  };
178
332
  var src_default = {
179
333
  id: "opencode-cliproxyapi",
180
- server: CLIProxyAPIPlugin
334
+ server: CLIProxyAPIPlugin,
335
+ setup
181
336
  };
182
337
  function addProvider(config, input) {
183
338
  const existing = config.provider?.[input.providerID];
@@ -188,9 +343,9 @@ function addProvider(config, input) {
188
343
  tool_call: !isImageModel(model.id),
189
344
  reasoning: isReasoningModel(model.id, input.effortCatalog[model.id]),
190
345
  attachment: supportsAttachments(model.id),
191
- ...model.ownedBy && resolveModelNpm(input.protocolCatalog, model.ownedBy, model.id) === "@ai-sdk/anthropic" ? {
346
+ ...model.ownedBy && resolveModelNpm(input.protocolCatalog, model.ownedBy, model.id) === V1_ANTHROPIC_PACKAGE ? {
192
347
  provider: {
193
- npm: "@ai-sdk/anthropic"
348
+ npm: V1_ANTHROPIC_PACKAGE
194
349
  }
195
350
  } : {},
196
351
  ...isImageModel(model.id) ? {
@@ -207,7 +362,7 @@ function addProvider(config, input) {
207
362
  [input.providerID]: {
208
363
  ...existing,
209
364
  name: existing?.name ?? input.providerName,
210
- npm: existing?.npm ?? (input.protocol === "responses" ? "@ai-sdk/openai" : "@ai-sdk/openai-compatible"),
365
+ npm: existing?.npm ?? (input.protocol === "responses" ? V1_RESPONSES_PACKAGE : V1_CHAT_PACKAGE),
211
366
  env: [...new Set([...existing?.env ?? [], "CLIPROXY_API_KEY"])],
212
367
  options: {
213
368
  ...existing?.options,
@@ -222,21 +377,25 @@ function reasoningEffortConfig(model, input) {
222
377
  const efforts = input.effortCatalog[model.id];
223
378
  if (!efforts)
224
379
  return {};
225
- const anthropicRouted = model.ownedBy !== undefined && resolveModelNpm(input.protocolCatalog, model.ownedBy, model.id) === "@ai-sdk/anthropic";
226
- if (anthropicRouted)
227
- return {};
228
- const levels = efforts.levels.filter((level) => level !== "none");
229
- if (levels.length === 0)
380
+ const levels = supportedEffortLevels(model, input);
381
+ if (!levels)
230
382
  return {};
231
383
  const defaultLevel = efforts.defaultLevel !== undefined && efforts.levels.includes(efforts.defaultLevel) ? efforts.defaultLevel : undefined;
232
384
  return {
233
385
  ...defaultLevel ? { options: { reasoningEffort: defaultLevel } } : {},
234
- variants: Object.fromEntries(levels.map((level) => [level, { reasoningEffort: level }]))
386
+ variants: {
387
+ ...Object.fromEntries(efforts.levels.map((level) => [level, { reasoningEffort: level }])),
388
+ ...disabledVariants(efforts.levels)
389
+ }
235
390
  };
236
391
  }
237
- function resolveModelNpm(catalog, providerID, modelID) {
238
- const provider = catalog[providerID];
239
- return provider?.models[modelID] ?? provider?.npm;
392
+ var OPENCODE_DEFAULT_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
393
+ function disabledVariants(supported) {
394
+ const supportedSet = new Set(supported);
395
+ return Object.fromEntries(OPENCODE_DEFAULT_EFFORTS.filter((effort) => !supportedSet.has(effort)).map((effort) => [
396
+ effort,
397
+ { disabled: true }
398
+ ]));
240
399
  }
241
400
  function mergeModels(discovered, existing) {
242
401
  const merged = { ...discovered };
@@ -251,70 +410,14 @@ function mergeModels(discovered, existing) {
251
410
  }
252
411
  return merged;
253
412
  }
254
- function readOptions(input) {
255
- if (!input)
256
- return {};
257
- return {
258
- baseURL: stringOption(input.baseURL),
259
- apiKey: stringOption(input.apiKey),
260
- providerID: stringOption(input.providerID),
261
- providerName: stringOption(input.providerName),
262
- protocol: input.protocol === "responses" ? "responses" : input.protocol === "chat" ? "chat" : undefined,
263
- modelMetadataURL: input.modelMetadataURL === false ? false : stringOption(input.modelMetadataURL),
264
- discoveryTimeoutMs: typeof input.discoveryTimeoutMs === "number" && input.discoveryTimeoutMs > 0 ? input.discoveryTimeoutMs : undefined
265
- };
266
- }
267
- function discoverProtocols(input) {
268
- if (input.url === false)
269
- return Promise.resolve({ catalog: {} });
270
- return discoverModelProtocols({
271
- url: input.url,
272
- timeoutMs: input.timeoutMs
273
- }).then((catalog) => ({ catalog })).catch((error) => ({
274
- catalog: {},
275
- error: error instanceof Error ? error.message : String(error)
276
- }));
277
- }
278
- function discoverEffortLevels(input) {
279
- return discoverReasoningEfforts(input).then((catalog) => ({ catalog })).catch((error) => ({
280
- catalog: {},
281
- error: error instanceof Error ? error.message : String(error)
282
- }));
283
- }
284
- function stringOption(value) {
285
- return typeof value === "string" && value.trim() !== "" ? value : undefined;
286
- }
287
- function displayName(modelID) {
288
- return modelID.split("-").map((part) => {
289
- const lower = part.toLowerCase();
290
- if (lower === "gpt")
291
- return "GPT";
292
- if (lower === "oss")
293
- return "OSS";
294
- if (lower === "codex")
295
- return "Codex";
296
- return part.charAt(0).toUpperCase() + part.slice(1);
297
- }).join(" ");
298
- }
299
- function isImageModel(modelID) {
300
- return /(?:^|-)image(?:-|$)/i.test(modelID);
301
- }
302
- function isReasoningModel(modelID, efforts) {
303
- if (efforts?.levels.length)
304
- return efforts.levels.some((level) => level !== "none");
305
- return /thinking|reasoning|codex|gpt-(?:5|oss)/i.test(modelID);
306
- }
307
- function supportsAttachments(modelID) {
308
- return /^(?:claude|gemini|gpt)/i.test(modelID);
309
- }
310
413
  export {
311
- CLIProxyAPIPlugin,
312
- src_default as default,
313
- discoverModelProtocols,
314
- discoverModels,
315
- discoverReasoningEfforts,
316
- normalizeBaseURL,
317
- parseCatalog,
414
+ parseReasoningEffortCatalog,
318
415
  parseModelProtocolCatalog,
319
- parseReasoningEffortCatalog
416
+ parseCatalog,
417
+ normalizeBaseURL,
418
+ discoverReasoningEfforts,
419
+ discoverModels,
420
+ discoverModelProtocols,
421
+ src_default as default,
422
+ CLIProxyAPIPlugin
320
423
  };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * OpenCode V2 entrypoint for CLIProxyAPI.
3
+ *
4
+ * V2 plugins default-export a definition with an `id` and a `setup(ctx)`
5
+ * function; V1 plugin functions do not run on V2. This module ports the
6
+ * discovery pipeline to the V2 provider API: it resolves the connector
7
+ * options, runs the same catalog/protocol/effort discovery as V1, and
8
+ * publishes the resulting provider through `ctx.provider.transform`.
9
+ *
10
+ * All `@opencode/plugin` imports are type-only, so the built plugin has no
11
+ * runtime dependency on the V2 SDK.
12
+ */
13
+ import type { Plugin } from "@opencode/plugin";
14
+ import { type CatalogDiscovery } from "./shared.js";
15
+ type Context = Plugin.Context;
16
+ export interface V2ModelVariant {
17
+ id: string;
18
+ settings?: Record<string, unknown>;
19
+ }
20
+ export interface V2ModelInfo {
21
+ id: string;
22
+ modelID: string;
23
+ providerID: string;
24
+ name: string;
25
+ package?: string;
26
+ capabilities: {
27
+ tools: boolean;
28
+ input: string[];
29
+ output: string[];
30
+ };
31
+ variants: V2ModelVariant[];
32
+ time: {
33
+ released: number;
34
+ };
35
+ cost: Array<{
36
+ input: number;
37
+ output: number;
38
+ cache: {
39
+ read: number;
40
+ write: number;
41
+ };
42
+ }>;
43
+ status: "active";
44
+ enabled: boolean;
45
+ limit: {
46
+ context: number;
47
+ output: number;
48
+ };
49
+ }
50
+ export declare function toV2Model(model: CatalogDiscovery["catalog"][number], input: {
51
+ providerID: string;
52
+ discovery: CatalogDiscovery;
53
+ }): V2ModelInfo;
54
+ export declare function toV2Models(input: {
55
+ providerID: string;
56
+ discovery: CatalogDiscovery;
57
+ }): V2ModelInfo[];
58
+ /**
59
+ * V2 setup. Registers the provider from the discovered catalog and returns a
60
+ * no-op cleanup (V2 keeps no timers or subscriptions for this plugin).
61
+ */
62
+ export declare function setup(ctx: Context): Promise<() => Promise<void>>;
63
+ export declare const v2Definition: {
64
+ id: string;
65
+ setup: typeof setup;
66
+ };
67
+ export {};
68
+ //# sourceMappingURL=plugin-v2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-v2.d.ts","sourceRoot":"","sources":["../src/plugin-v2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAS,MAAM,EAAY,MAAM,kBAAkB,CAAA;AAE/D,OAAO,EAeL,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAA;AAEpB,KAAK,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;AAK7B,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,MAAM,EAAE,MAAM,EAAE,CAAA;KACjB,CAAA;IACD,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,IAAI,EAAE,KAAK,CAAC;QACV,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;QACd,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KACvC,CAAC,CAAA;IACF,MAAM,EAAE,QAAQ,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC1C,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,gBAAgB,CAAA;CAAE,GACzD,WAAW,CA4Bb;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,gBAAgB,CAAA;CAC5B,GAAG,WAAW,EAAE,CAIhB;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CA8CtE;AAED,eAAO,MAAM,YAAY;;;CAGxB,CAAA"}
@@ -0,0 +1,50 @@
1
+ import type { PluginOptions } from "@opencode-ai/plugin";
2
+ import { type CatalogModel, type ModelProtocolCatalog, type ReasoningEffortCatalog } from "./catalog.js";
3
+ export declare const DEFAULT_BASE_URL = "http://localhost:8317/v1";
4
+ export declare const DEFAULT_PROVIDER_ID = "cliproxyapi";
5
+ export declare const DEFAULT_PROVIDER_NAME = "CLIProxyAPI";
6
+ export declare const DEFAULT_DISCOVERY_TIMEOUT_MS = 10000;
7
+ export declare const DEFAULT_MODEL_METADATA_URL = "https://models.dev/api.json";
8
+ export declare const V1_CHAT_PACKAGE = "@ai-sdk/openai-compatible";
9
+ export declare const V1_RESPONSES_PACKAGE = "@ai-sdk/openai";
10
+ export declare const V1_ANTHROPIC_PACKAGE = "@ai-sdk/anthropic";
11
+ export declare const V2_CHAT_PACKAGE = "@opencode/ai/providers/openai-compatible";
12
+ export declare const V2_RESPONSES_PACKAGE = "@opencode/ai/providers/openai";
13
+ export declare const V2_ANTHROPIC_PACKAGE = "@opencode/ai/providers/anthropic";
14
+ export type ConnectorOptions = {
15
+ baseURL?: string;
16
+ apiKey?: string;
17
+ providerID?: string;
18
+ providerName?: string;
19
+ protocol?: "chat" | "responses";
20
+ modelMetadataURL?: string | false;
21
+ discoveryTimeoutMs?: number;
22
+ };
23
+ export type CatalogDiscovery = {
24
+ catalog: CatalogModel[];
25
+ protocolCatalog: ModelProtocolCatalog;
26
+ effortCatalog: ReasoningEffortCatalog;
27
+ errors: string[];
28
+ };
29
+ export declare function readOptions(input?: PluginOptions): ConnectorOptions;
30
+ export declare function stringOption(value: unknown): string | undefined;
31
+ export declare function displayName(modelID: string): string;
32
+ export declare function isImageModel(modelID: string): boolean;
33
+ export declare function isReasoningModel(modelID: string, efforts?: {
34
+ levels: string[];
35
+ defaultLevel?: string;
36
+ }): boolean;
37
+ export declare function supportsAttachments(modelID: string): boolean;
38
+ export declare function resolveModelNpm(catalog: ModelProtocolCatalog, providerID: string, modelID: string): string | undefined;
39
+ export declare function isAnthropicRouted(model: CatalogModel, protocolCatalog: ModelProtocolCatalog): boolean;
40
+ export declare function supportedEffortLevels(model: CatalogModel, input: {
41
+ protocolCatalog: ModelProtocolCatalog;
42
+ effortCatalog: ReasoningEffortCatalog;
43
+ }): string[] | undefined;
44
+ export declare function discoverCatalog(input: {
45
+ baseURL: string;
46
+ apiKey?: string;
47
+ modelMetadataURL: string | false;
48
+ timeoutMs: number;
49
+ }): Promise<CatalogDiscovery>;
50
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAA;AAErB,eAAO,MAAM,gBAAgB,6BAA6B,CAAA;AAC1D,eAAO,MAAM,mBAAmB,gBAAgB,CAAA;AAChD,eAAO,MAAM,qBAAqB,gBAAgB,CAAA;AAClD,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAClD,eAAO,MAAM,0BAA0B,gCAAgC,CAAA;AAGvE,eAAO,MAAM,eAAe,8BAA8B,CAAA;AAC1D,eAAO,MAAM,oBAAoB,mBAAmB,CAAA;AACpD,eAAO,MAAM,oBAAoB,sBAAsB,CAAA;AACvD,eAAO,MAAM,eAAe,6CAA6C,CAAA;AACzE,eAAO,MAAM,oBAAoB,kCAAkC,CAAA;AACnE,eAAO,MAAM,oBAAoB,qCAAqC,CAAA;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,eAAe,EAAE,oBAAoB,CAAA;IACrC,aAAa,EAAE,sBAAsB,CAAA;IACrC,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,gBAAgB,CAiBnE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,sBAE1C;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,UAW1C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,WAE3C;AAID,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,WAItD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,WAElD;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,oBAAoB,EAC7B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,eAAe,EAAE,oBAAoB,GACpC,OAAO,CAKT;AAMD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE;IAAE,eAAe,EAAE,oBAAoB,CAAC;IAAC,aAAa,EAAE,sBAAsB,CAAA;CAAE,GACtF,MAAM,EAAE,GAAG,SAAS,CAOtB;AAKD,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gBAAgB,EAAE,MAAM,GAAG,KAAK,CAAA;IAChC,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe5B"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@darknesskiller/opencode-cliproxyapi",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Fork of opencode-cliproxyapi that discovers CLIProxyAPI models and auto-configures reasoning-effort variants in OpenCode.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -29,7 +29,7 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "scripts": {
32
- "build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm --external @opencode-ai/plugin && tsc --emitDeclarationOnly",
32
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun --format esm --external @opencode-ai/plugin --external @opencode/plugin && tsc --emitDeclarationOnly",
33
33
  "typecheck": "tsc --noEmit",
34
34
  "test": "bun test ./src",
35
35
  "check": "bun run typecheck && bun run test && bun run build",
@@ -43,6 +43,7 @@
43
43
  ],
44
44
  "devDependencies": {
45
45
  "@opencode-ai/plugin": "1.18.29",
46
+ "@opencode/plugin": "^2.0.18",
46
47
  "@types/bun": "^1.3.5",
47
48
  "typescript": "^5.9.3"
48
49
  },