index.ts 4.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. /**
  2. * OpenCode v2 Plugin: Auto-discover models from OpenAI-compatible endpoints.
  3. *
  4. * Reads endpoint/key from opencode.jsonc (project or global) when available,
  5. * falling back to the built-in defaults. Creates the "OmniRoute" provider itself.
  6. */
  7. import { Plugin, Model } from "@opencode/plugin";
  8. function parseJsonc(text: string): any {
  9. try { return JSON.parse(text); } catch {}
  10. let s = text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
  11. s = s.replace(/,\s*([}\]])/g, "$1");
  12. return JSON.parse(s);
  13. }
  14. function readConfig(): { baseURL: string; apiKey: string } {
  15. const fs = require("node:fs");
  16. const path = require("node:path");
  17. const os = require("node:os");
  18. const candidates = [
  19. path.join(process.cwd(), "opencode.jsonc"),
  20. path.join(process.cwd(), "opencode.json"),
  21. path.join(os.homedir(), ".config", "opencode", "opencode.jsonc"),
  22. ];
  23. for (const c of candidates) {
  24. try {
  25. if (!fs.existsSync(c)) continue;
  26. const cfg = parseJsonc(fs.readFileSync(c, "utf8"));
  27. const provs = cfg?.providers || cfg?.provider;
  28. if (!provs || typeof provs !== "object") continue;
  29. for (const info of Object.values(provs) as any[]) {
  30. const p = info as Record<string, unknown>;
  31. if (typeof p.package === "string" && p.package.includes("openai-compatible")) {
  32. const settings = (p.settings && typeof p.settings === "object") ? (p.settings as Record<string, unknown>) : {};
  33. const baseURL = typeof settings.baseURL === "string" ? settings.baseURL : undefined;
  34. const apiKey = typeof settings.apiKey === "string" ? settings.apiKey : "";
  35. if (baseURL) return { baseURL, apiKey };
  36. }
  37. }
  38. } catch {}
  39. }
  40. return { baseURL: "http://localhost:20128/v1", apiKey: "sk-e9ac58c0b78cd2bc-f17d3e-f6beea67" };
  41. }
  42. const { baseURL: BASE_URL, apiKey: API_KEY } = readConfig();
  43. const PROVIDER_ID = "OmniRoute";
  44. const PROVIDER_NAME = "OmniRoute";
  45. export default Plugin.define({
  46. id: "opencode.openai-compatible-model-discovery",
  47. async setup(ctx) {
  48. try {
  49. const headers: Record<string, string> = { "Content-Type": "application/json" };
  50. if (API_KEY) headers["Authorization"] = `Bearer ${API_KEY}`;
  51. const res = await fetch(`${BASE_URL}/models`, { headers });
  52. if (!res.ok) throw new Error(`HTTP ${res.status} from ${BASE_URL}/models`);
  53. const json = await res.json();
  54. const rawModels = (json.data || []) as Array<Record<string, unknown>>;
  55. const models = rawModels.map((m) => {
  56. const modelId = typeof m.id === "string" ? m.id : String(m.id);
  57. const contextLength = typeof m.context_length === "number" ? m.context_length : 131072;
  58. const maxTokens = typeof m.max_tokens === "number" ? m.max_tokens : 32768;
  59. const inputMods = Array.isArray(m.input_modalities) ? (m.input_modalities as string[]) : ["text"];
  60. const outputMods = Array.isArray(m.output_modalities) ? (m.output_modalities as string[]) : ["text"];
  61. return {
  62. id: modelId, modelID: modelId, providerID: PROVIDER_ID, name: modelId,
  63. capabilities: { tools: true, input: inputMods, output: outputMods },
  64. variants: [], cost: [{ input: 0, output: 0, cache: { read: 0, write: 0 } }],
  65. limit: { context: contextLength, output: Math.min(maxTokens, contextLength) },
  66. time: { released: Math.floor(Date.now() / 1000) },
  67. status: "active" as const, enabled: true,
  68. } as unknown as Model.Info;
  69. });
  70. ctx.provider.transform((editor) => {
  71. const source = editor.get(PROVIDER_ID);
  72. if (source) {
  73. const existing = [...source.models.values()];
  74. const existingIDs = new Set(existing.map((model) => String(model.id)));
  75. const merged = [...existing, ...models.filter((m) => !existingIDs.has(String(m.id)))];
  76. editor.models.set(PROVIDER_ID, merged);
  77. } else {
  78. editor.add({ info: { id: PROVIDER_ID, name: PROVIDER_NAME, activation: "enabled" as const, package: "aisdk:@ai-sdk/openai-compatible", settings: { baseURL: BASE_URL, apiKey: API_KEY } } as never, models });
  79. }
  80. });
  81. console.log(`[openai-discovery] Registered ${models.length} models in provider "${PROVIDER_ID}"`);
  82. } catch (error) {
  83. console.error(`[openai-discovery] Failed to discover models: ${error instanceof Error ? error.message : String(error)}`);
  84. }
  85. },
  86. });