diff --git a/.npmrc b/.npmrc index 07aebe1..3ec916e 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} \ No newline at end of file +# npmrc - remove token for security diff --git a/package.json b/package.json index cb6a59f..65d6d37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "opencode-mem", - "version": "2.12.0", + "name": "@xiaoxiamimengfb/my-opencode-mem", + "version": "2.12.1", "description": "OpenCode plugin that gives coding agents persistent memory using local vector database", "type": "module", "main": "dist/plugin.js", @@ -42,6 +42,7 @@ "franc-min": "^6.2.0", "iso-639-3": "^3.0.1", "usearch": "^2.21.4", + "vercel-minimax-ai-provider": "^0.0.2", "zod": "^4.3.6" }, "devDependencies": { diff --git a/src/services/ai/opencode-provider.ts b/src/services/ai/opencode-provider.ts index 4db3b3e..910807c 100644 --- a/src/services/ai/opencode-provider.ts +++ b/src/services/ai/opencode-provider.ts @@ -1,8 +1,10 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join, dirname } from "node:path"; +import os from "node:os"; import { generateText, Output } from "ai"; import { createAnthropic } from "@ai-sdk/anthropic"; import { createOpenAI } from "@ai-sdk/openai"; +import { createMinimax, createMinimaxOpenAI } from "vercel-minimax-ai-provider"; import type { ZodType } from "zod"; type OAuthAuth = { type: "oauth"; refresh: string; access: string; expires: number }; @@ -24,20 +26,75 @@ export function getStatePath(): string { return _statePath; } +// Provider name aliases mapping +const PROVIDER_ALIASES: Record = { + "MiniMax Coding Plan (minimaxi.com)": "minimax-cn-coding-plan", + "MiniMax (minimaxi.com)": "minimax", + "MiniMax Coding Plan (minimax.io)": "minimax-coding-plan", +}; +// Reverse mapping: internal name -> opencode display name +const ALIASES_REVERSE: Record = { + "minimax-cn-coding-plan": "MiniMax Coding Plan (minimaxi.com)", + "minimax": "MiniMax (minimaxi.com)", + "minimax-coding-plan": "MiniMax Coding Plan (minimax.io)", +}; + export function setConnectedProviders(providers: string[]): void { - _connectedProviders = providers; + let validProviders: string[] = []; + if (Array.isArray(providers)) { + validProviders = providers.filter(p => typeof p === "string"); + } else if (typeof providers === "string") { + try { + const parsed = JSON.parse(providers); + validProviders = Array.isArray(parsed) ? parsed.filter(p => typeof p === "string") : []; + } catch { + validProviders = []; + } + } + _connectedProviders = validProviders; } export function isProviderConnected(providerName: string): boolean { - return _connectedProviders.includes(providerName); + if (providerName.includes("minimax") || providerName.includes("MiniMax")) { + const homeDir = os.homedir(); + const authPath = join(homeDir, ".local", "share", "opencode", "auth.json"); + try { + if (existsSync(authPath)) { + const authContent = readFileSync(authPath, "utf-8"); + const auth = JSON.parse(authContent); + const keys = Object.keys(auth); + const hasMinimax = keys.some(k => k.toLowerCase().includes("minimax")); + return hasMinimax; + } + } catch {} + } + if (_connectedProviders.includes(providerName)) { + return true; + } + for (const connected of _connectedProviders) { + if (PROVIDER_ALIASES[connected] === providerName) { + return true; + } + } + const reverseMatch = ALIASES_REVERSE[providerName]; + if (reverseMatch && _connectedProviders.includes(reverseMatch)) { + return true; + } + return false; } // --- Auth --- function findAuthJsonPath(statePath: string): string | undefined { + const homeDir = os.homedir(); const candidates = [ join(statePath, "auth.json"), - join(dirname(statePath), "share", "opencode", "auth.json"), + join(dirname(dirname(statePath)), "share", "opencode", "auth.json"), join(statePath.replace("/state/", "/share/"), "auth.json"), + join(statePath.replace("\\state\\", "\\share\\"), "auth.json"), + // 全局 auth.json 路径 (Linux/macOS: ~/.local/share/opencode/auth.json, Windows: %USERPROFILE%/.local/share/opencode/auth.json) + join(homeDir, ".local", "share", "opencode", "auth.json"), + // Windows 可能的全局路径 + join(homeDir, ".config", "opencode", "auth.json"), ]; return candidates.find(existsSync); } @@ -61,7 +118,21 @@ export function readOpencodeAuth(statePath: string, providerName: string): Auth } catch { throw new Error(`Failed to read opencode auth.json: invalid JSON`); } - const auth = parsed[providerName]; + let auth = parsed[providerName]; + if (!auth) { + const reverseKey = ALIASES_REVERSE[providerName]; + if (reverseKey) { + auth = parsed[reverseKey]; + } + } + if (!auth) { + for (const [authKey, internalName] of Object.entries(PROVIDER_ALIASES)) { + if (internalName === providerName && parsed[authKey]) { + auth = parsed[authKey]; + break; + } + } + } if (!auth) { const connected = Object.keys(parsed).join(", ") || "none"; throw new Error( @@ -246,8 +317,29 @@ export function createOpencodeAIProvider(providerName: string, auth: Auth, state } return createOpenAI({ apiKey: auth.key }); } + if (providerName === "minimax") { + if (auth.type === "oauth") { + throw new Error("Minimax does not support OAuth authentication. Use an API key instead."); + } + return createMinimaxOpenAI({ apiKey: auth.key }); + } + if (providerName === "minimax-cn-coding-plan") { + if (auth.type === "oauth") { + throw new Error("Minimax does not support OAuth authentication. Use an API key instead."); + } + return createMinimax({ + apiKey: auth.key, + baseURL: "https://api.minimaxi.com/anthropic/v1" + }); + } + if (providerName === "minimax-cn") { + if (auth.type === "oauth") { + throw new Error("Minimax does not support OAuth authentication. Use an API key instead."); + } + return createMinimaxOpenAI({ apiKey: auth.key }); + } throw new Error( - `Unsupported opencode provider: '${providerName}'. Supported providers: anthropic, openai` + `Unsupported opencode provider: '${providerName}'. Supported providers: anthropic, openai, minimax, minimax-cn-coding-plan, minimax-cn` ); }