@darknesskiller/opencode-cliproxyapi 0.2.1 → 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 +15 -0
- package/README.md +44 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +192 -100
- package/dist/plugin-v2.d.ts +68 -0
- package/dist/plugin-v2.d.ts.map +1 -0
- package/dist/shared.d.ts +50 -0
- package/dist/shared.d.ts.map +1 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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
|
+
|
|
8
23
|
## [0.2.1] - 2026-09-09
|
|
9
24
|
|
|
10
25
|
### Fixed
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,MAAM,
|
|
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/
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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:
|
|
156
|
-
effortCatalog:
|
|
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) ===
|
|
346
|
+
...model.ownedBy && resolveModelNpm(input.protocolCatalog, model.ownedBy, model.id) === V1_ANTHROPIC_PACKAGE ? {
|
|
192
347
|
provider: {
|
|
193
|
-
npm:
|
|
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" ?
|
|
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,11 +377,8 @@ function reasoningEffortConfig(model, input) {
|
|
|
222
377
|
const efforts = input.effortCatalog[model.id];
|
|
223
378
|
if (!efforts)
|
|
224
379
|
return {};
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
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 {
|
|
@@ -245,10 +397,6 @@ function disabledVariants(supported) {
|
|
|
245
397
|
{ disabled: true }
|
|
246
398
|
]));
|
|
247
399
|
}
|
|
248
|
-
function resolveModelNpm(catalog, providerID, modelID) {
|
|
249
|
-
const provider = catalog[providerID];
|
|
250
|
-
return provider?.models[modelID] ?? provider?.npm;
|
|
251
|
-
}
|
|
252
400
|
function mergeModels(discovered, existing) {
|
|
253
401
|
const merged = { ...discovered };
|
|
254
402
|
for (const [modelID, model] of Object.entries(existing ?? {})) {
|
|
@@ -262,70 +410,14 @@ function mergeModels(discovered, existing) {
|
|
|
262
410
|
}
|
|
263
411
|
return merged;
|
|
264
412
|
}
|
|
265
|
-
function readOptions(input) {
|
|
266
|
-
if (!input)
|
|
267
|
-
return {};
|
|
268
|
-
return {
|
|
269
|
-
baseURL: stringOption(input.baseURL),
|
|
270
|
-
apiKey: stringOption(input.apiKey),
|
|
271
|
-
providerID: stringOption(input.providerID),
|
|
272
|
-
providerName: stringOption(input.providerName),
|
|
273
|
-
protocol: input.protocol === "responses" ? "responses" : input.protocol === "chat" ? "chat" : undefined,
|
|
274
|
-
modelMetadataURL: input.modelMetadataURL === false ? false : stringOption(input.modelMetadataURL),
|
|
275
|
-
discoveryTimeoutMs: typeof input.discoveryTimeoutMs === "number" && input.discoveryTimeoutMs > 0 ? input.discoveryTimeoutMs : undefined
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
function discoverProtocols(input) {
|
|
279
|
-
if (input.url === false)
|
|
280
|
-
return Promise.resolve({ catalog: {} });
|
|
281
|
-
return discoverModelProtocols({
|
|
282
|
-
url: input.url,
|
|
283
|
-
timeoutMs: input.timeoutMs
|
|
284
|
-
}).then((catalog) => ({ catalog })).catch((error) => ({
|
|
285
|
-
catalog: {},
|
|
286
|
-
error: error instanceof Error ? error.message : String(error)
|
|
287
|
-
}));
|
|
288
|
-
}
|
|
289
|
-
function discoverEffortLevels(input) {
|
|
290
|
-
return discoverReasoningEfforts(input).then((catalog) => ({ catalog })).catch((error) => ({
|
|
291
|
-
catalog: {},
|
|
292
|
-
error: error instanceof Error ? error.message : String(error)
|
|
293
|
-
}));
|
|
294
|
-
}
|
|
295
|
-
function stringOption(value) {
|
|
296
|
-
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
297
|
-
}
|
|
298
|
-
function displayName(modelID) {
|
|
299
|
-
return modelID.split("-").map((part) => {
|
|
300
|
-
const lower = part.toLowerCase();
|
|
301
|
-
if (lower === "gpt")
|
|
302
|
-
return "GPT";
|
|
303
|
-
if (lower === "oss")
|
|
304
|
-
return "OSS";
|
|
305
|
-
if (lower === "codex")
|
|
306
|
-
return "Codex";
|
|
307
|
-
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
308
|
-
}).join(" ");
|
|
309
|
-
}
|
|
310
|
-
function isImageModel(modelID) {
|
|
311
|
-
return /(?:^|-)image(?:-|$)/i.test(modelID);
|
|
312
|
-
}
|
|
313
|
-
function isReasoningModel(modelID, efforts) {
|
|
314
|
-
if (efforts?.levels.length)
|
|
315
|
-
return efforts.levels.some((level) => level !== "none");
|
|
316
|
-
return /thinking|reasoning|codex|gpt-(?:5|oss)/i.test(modelID);
|
|
317
|
-
}
|
|
318
|
-
function supportsAttachments(modelID) {
|
|
319
|
-
return /^(?:claude|gemini|gpt)/i.test(modelID);
|
|
320
|
-
}
|
|
321
413
|
export {
|
|
322
|
-
|
|
323
|
-
src_default as default,
|
|
324
|
-
discoverModelProtocols,
|
|
325
|
-
discoverModels,
|
|
326
|
-
discoverReasoningEfforts,
|
|
327
|
-
normalizeBaseURL,
|
|
328
|
-
parseCatalog,
|
|
414
|
+
parseReasoningEffortCatalog,
|
|
329
415
|
parseModelProtocolCatalog,
|
|
330
|
-
|
|
416
|
+
parseCatalog,
|
|
417
|
+
normalizeBaseURL,
|
|
418
|
+
discoverReasoningEfforts,
|
|
419
|
+
discoverModels,
|
|
420
|
+
discoverModelProtocols,
|
|
421
|
+
src_default as default,
|
|
422
|
+
CLIProxyAPIPlugin
|
|
331
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"}
|
package/dist/shared.d.ts
ADDED
|
@@ -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.
|
|
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
|
},
|