소스 검색

feat: Add local OpenCode v2 model discovery plugin

- Created plugin directory in .opencode/plugins/opencode-openai-discovery
- Added all necessary plugin files (index.ts, discovery.ts, provider.ts, config.ts)
- Configured project-local opencode.jsonc to enable the plugin
Paul Klumpp 1 주 전
부모
커밋
019c237a2a

+ 18 - 0
.opencode/plugins/opencode-openai-discovery/package.json

@@ -0,0 +1,18 @@
+{
+  "name": "opencode-openai-discovery",
+  "version": "1.0.0",
+  "description": "Auto-discover models from OpenAI-compatible APIs in OpenCode v2",
+  "main": "dist/index.js",
+  "types": "dist/index.d.ts",
+  "engines": {
+    "opencode": "^2.0.0"
+  },
+  "scripts": {
+    "build": "tsc",
+    "watch": "tsc --watch"
+  },
+  "devDependencies": {
+    "@opencode/plugin": "^2.0.0",
+    "typescript": "^5.0.0"
+  }
+}

+ 23 - 0
.opencode/plugins/opencode-openai-discovery/src/config.ts

@@ -0,0 +1,23 @@
+export interface EndpointConfig {
+    name: string;
+    baseURL: string;
+    apiKey?: string;
+}
+
+export interface PluginConfig {
+    endpoints: EndpointConfig[];
+}
+
+export function validateConfig(config: any): PluginConfig {
+    if (!config?.endpoints || !Array.isArray(config.endpoints)) {
+        throw new Error("Missing or invalid 'endpoints' in plugin config");
+    }
+
+    for (const endpoint of config.endpoints) {
+        if (!endpoint.name || !endpoint.baseURL) {
+            throw new Error("Each endpoint must have 'name' and 'baseURL'");
+        }
+    }
+
+    return config;
+}

+ 32 - 0
.opencode/plugins/opencode-openai-discovery/src/discovery.ts

@@ -0,0 +1,32 @@
+import { Model } from "@opencode/plugin";
+
+export async function discoverModels(baseURL: string, apiKey?: string): Promise<Model.Info[]> {
+  const headers: Record<string, string> = {
+    "Content-Type": "application/json",
+  };
+
+  if (apiKey) {
+    headers["Authorization"] = `Bearer ${apiKey}`;
+  }
+
+  const response = await fetch(`${baseURL}/models`, { headers });
+
+  if (!response.ok) {
+    throw new Error(`Failed to fetch models: HTTP ${response.status} ${await response.text()}`);
+  }
+
+  const data = await response.json();
+
+  return data.data.map((model: any) => ({
+    id: model.id,
+    name: model.id,
+    capabilities: {
+      input: ["text"],
+      output: "text",
+    },
+    limits: {
+      context: model.context_length || 128_000,
+      output: model.max_tokens || 8_192,
+    },
+  }));
+}

+ 28 - 0
.opencode/plugins/opencode-openai-discovery/src/index.ts

@@ -0,0 +1,28 @@
+import { PluginContext } from "@opencode/plugin";
+import { validateConfig } from "./config";
+import { discoverModels } from "./discovery";
+import { registerProvider } from "./provider";
+
+export default async function (ctx: PluginContext) {
+    const config = validateConfig(ctx.config);
+
+    for (const endpoint of config.endpoints) {
+        try {
+            const models = await discoverModels(endpoint.baseURL, endpoint.apiKey);
+            await registerProvider(ctx, endpoint.name, endpoint.baseURL, models);
+            ctx.log.info(`Discovered ${models.length} models from ${endpoint.name}`);
+        } catch (error) {
+            ctx.log.error(`Failed to discover models from ${endpoint.name}: ${error}`);
+        }
+    }
+
+    // Command: /models refresh
+    ctx.commands.register({
+        id: "refresh-models",
+        description: "Refresh all discovered models",
+        async run() {
+            ctx.log.info("Refreshing models...");
+            await ctx.provider.reload();
+        },
+    });
+}

+ 21 - 0
.opencode/plugins/opencode-openai-discovery/src/provider.ts

@@ -0,0 +1,21 @@
+import { PluginContext, Provider } from "@opencode/plugin";
+
+export async function registerProvider(
+    ctx: PluginContext,
+    providerName: string,
+    baseURL: string,
+    models: Model.Info[]
+) {
+    await ctx.provider.transform((editor) => {
+        editor.add({
+            info: {
+                ...Provider.Info.empty(providerName),
+                name: providerName,
+                activation: "enabled",
+                package: "@opencode/ai/providers/openai-compatible",
+                settings: { baseURL },
+            },
+            models,
+        });
+    });
+}

+ 13 - 0
.opencode/plugins/opencode-openai-discovery/tsconfig.json

@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "module": "CommonJS",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "outDir": "./dist",
+    "rootDir": "./src"
+  },
+  "include": ["src/**/*"]
+}

+ 16 - 0
opencode.jsonc

@@ -0,0 +1,16 @@
+{
+  "plugins": [
+    {
+      "package": "./.opencode/plugins/opencode-openai-discovery",
+      "options": {
+        "endpoints": [
+          {
+            "name": "local-gateway",
+            "baseURL": "http://localhost:20128/v1",
+            "apiKey": "sk-e9ac58c0b78cd2bc-f17d3e-f6beea67"
+          }
+        ]
+      }
+    }
+  ]
+}