diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts index 34e2269d0c1..23bb816538f 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/auth.ts @@ -265,6 +265,15 @@ export const AuthLoginCommand = cmd({ filtered[key] = value } } + // Add Maple AI (not in models.dev, models discovered dynamically from proxy) + if ((enabled ? enabled.has("maple") : true) && !disabled.has("maple")) { + filtered["maple"] = { + id: "maple", + name: "Maple AI", + env: ["MAPLE_API_KEY"], + models: {}, + } + } return filtered }) @@ -358,6 +367,19 @@ export const AuthLoginCommand = cmd({ ) } + if (provider === "maple") { + prompts.log.info( + "Maple AI is a TEE-based private AI provider.\n\n" + + "Setup:\n" + + " 1. Start the Maple Proxy (desktop app or Docker)\n" + + " 2. Generate an API key in the Maple app\n" + + " 3. Enter your API key below\n\n" + + "The default proxy URL is http://127.0.0.1:8080/v1\n" + + "To use a different URL, add to opencode.json:\n" + + ' { "provider": { "maple": { "options": { "baseURL": "http://your-url/v1" } } } }', + ) + } + const key = await prompts.password({ message: "Enter your API key", validate: (x) => (x && x.length > 0 ? undefined : "Required"), diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx index f8be5577b35..b224595c847 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx @@ -246,6 +246,19 @@ function ApiMethod(props: ApiMethodProps) { Go to https://opencode.ai/zen to get a key + ) : props.providerID === "maple" ? ( + + + Maple AI is a TEE-based private AI provider. Start the Maple Proxy (desktop app or Docker) and generate an + API key in the Maple app. + + + Default proxy URL: http://127.0.0.1:8080/v1 + + + To use a different URL, add to opencode.json: provider.maple.options.baseURL + + ) : undefined } onConfirm={async (value) => { diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 1cad3b3162a..92045a1dade 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -528,6 +528,69 @@ export namespace Provider { }, } }, + maple: async (input) => { + const config = await Config.get() + const baseURL = config.provider?.["maple"]?.options?.baseURL ?? "http://127.0.0.1:8080/v1" + + const auth = await Auth.get("maple") + const apiKey = auth?.type === "api" ? auth.key : Env.get("MAPLE_API_KEY") + + if (!apiKey) return { autoload: false } + + // Dynamically fetch models from the Maple proxy + try { + const response = await fetch(`${baseURL}/models`, { + headers: { Authorization: `Bearer ${apiKey}` }, + signal: AbortSignal.timeout(5000), + }) + if (!response.ok) { + log.warn("Failed to fetch Maple models", { status: response.status }) + return { autoload: false } + } + const data = (await response.json()) as { data?: Array<{ id: string }> } + const models = data.data ?? [] + + for (const model of models) { + input.models[model.id] = { + id: model.id, + providerID: "maple", + name: model.id, + api: { + id: model.id, + url: baseURL, + npm: "@ai-sdk/openai-compatible", + }, + status: "active", + headers: {}, + options: {}, + cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, + limit: { context: 128000, output: 8192 }, + capabilities: { + temperature: true, + reasoning: false, + attachment: false, + toolcall: true, + input: { text: true, audio: false, image: false, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + release_date: "", + variants: {}, + } + } + } catch (e) { + log.warn("Failed to connect to Maple proxy", { error: e }) + return { autoload: false } + } + + return { + autoload: Object.keys(input.models).length > 0, + options: { + baseURL, + apiKey, + }, + } + }, } export const Model = z @@ -734,6 +797,16 @@ export namespace Provider { } } + // Add Maple AI provider (models are populated dynamically from the proxy) + database["maple"] = { + id: "maple", + name: "Maple AI", + source: "custom", + env: ["MAPLE_API_KEY"], + options: {}, + models: {}, + } + function mergeProvider(providerID: string, provider: Partial) { const existing = providers[providerID] if (existing) { diff --git a/packages/opencode/src/server/routes/provider.ts b/packages/opencode/src/server/routes/provider.ts index 872b48be79d..bafe35a69fb 100644 --- a/packages/opencode/src/server/routes/provider.ts +++ b/packages/opencode/src/server/routes/provider.ts @@ -46,6 +46,15 @@ export const ProviderRoutes = lazy(() => filteredProviders[key] = value } } + // Add Maple AI (not in models.dev, models discovered dynamically from proxy) + if ((enabled ? enabled.has("maple") : true) && !disabled.has("maple")) { + filteredProviders["maple"] = { + id: "maple", + name: "Maple AI", + env: ["MAPLE_API_KEY"], + models: {}, + } + } const connected = await Provider.list() const providers = Object.assign( @@ -54,7 +63,11 @@ export const ProviderRoutes = lazy(() => ) return c.json({ all: Object.values(providers), - default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id), + default: mapValues(providers, (item) => { + const models = Object.values(item.models) + if (models.length === 0) return "" + return Provider.sort(models)[0].id + }), connected: Object.keys(connected), }) },