Skip to content

Commit 195a597

Browse files
committed
feat: add Maple AI provider integration
Add Maple AI as a first-class provider with dynamic model discovery. - Provider dynamically fetches models from running Maple proxy - Supports API key authentication (stored in auth.json) - Configurable baseURL (default: http://127.0.0.1:8080/v1) - Helpful setup instructions in both CLI and TUI - Placed in 'Other' category without special hints - Gracefully handles proxy unavailability (no models shown) Users run /connect to add their Maple API key, then /models to see available models fetched directly from their Maple proxy.
1 parent e3c1861 commit 195a597

4 files changed

Lines changed: 122 additions & 1 deletion

File tree

packages/opencode/src/cli/cmd/auth.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ export const AuthLoginCommand = cmd({
265265
filtered[key] = value
266266
}
267267
}
268+
// Add Maple AI (not in models.dev, models discovered dynamically from proxy)
269+
if ((enabled ? enabled.has("maple") : true) && !disabled.has("maple")) {
270+
filtered["maple"] = {
271+
id: "maple",
272+
name: "Maple AI",
273+
env: ["MAPLE_API_KEY"],
274+
models: {},
275+
}
276+
}
268277
return filtered
269278
})
270279

@@ -358,6 +367,19 @@ export const AuthLoginCommand = cmd({
358367
)
359368
}
360369

370+
if (provider === "maple") {
371+
prompts.log.info(
372+
"Maple AI is a TEE-based private AI provider.\n\n" +
373+
"Setup:\n" +
374+
" 1. Start the Maple Proxy (desktop app or Docker)\n" +
375+
" 2. Generate an API key in the Maple app\n" +
376+
" 3. Enter your API key below\n\n" +
377+
"The default proxy URL is http://127.0.0.1:8080/v1\n" +
378+
"To use a different URL, add to opencode.json:\n" +
379+
' { "provider": { "maple": { "options": { "baseURL": "http://your-url/v1" } } } }',
380+
)
381+
}
382+
361383
const key = await prompts.password({
362384
message: "Enter your API key",
363385
validate: (x) => (x && x.length > 0 ? undefined : "Required"),

packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,19 @@ function ApiMethod(props: ApiMethodProps) {
236236
Go to <span style={{ fg: theme.primary }}>https://opencode.ai/zen</span> to get a key
237237
</text>
238238
</box>
239+
) : props.providerID === "maple" ? (
240+
<box gap={1}>
241+
<text fg={theme.textMuted}>
242+
Maple AI is a TEE-based private AI provider. Start the Maple Proxy (desktop app or Docker) and generate an
243+
API key in the Maple app.
244+
</text>
245+
<text fg={theme.text}>
246+
Default proxy URL: <span style={{ fg: theme.primary }}>http://127.0.0.1:8080/v1</span>
247+
</text>
248+
<text fg={theme.textMuted}>
249+
To use a different URL, add to opencode.json: provider.maple.options.baseURL
250+
</text>
251+
</box>
239252
) : undefined
240253
}
241254
onConfirm={async (value) => {

packages/opencode/src/provider/provider.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,69 @@ export namespace Provider {
504504
},
505505
}
506506
},
507+
maple: async (input) => {
508+
const config = await Config.get()
509+
const baseURL = config.provider?.["maple"]?.options?.baseURL ?? "http://127.0.0.1:8080/v1"
510+
511+
const auth = await Auth.get("maple")
512+
const apiKey = auth?.type === "api" ? auth.key : Env.get("MAPLE_API_KEY")
513+
514+
if (!apiKey) return { autoload: false }
515+
516+
// Dynamically fetch models from the Maple proxy
517+
try {
518+
const response = await fetch(`${baseURL}/models`, {
519+
headers: { Authorization: `Bearer ${apiKey}` },
520+
signal: AbortSignal.timeout(5000),
521+
})
522+
if (!response.ok) {
523+
log.warn("Failed to fetch Maple models", { status: response.status })
524+
return { autoload: false }
525+
}
526+
const data = (await response.json()) as { data?: Array<{ id: string }> }
527+
const models = data.data ?? []
528+
529+
for (const model of models) {
530+
input.models[model.id] = {
531+
id: model.id,
532+
providerID: "maple",
533+
name: model.id,
534+
api: {
535+
id: model.id,
536+
url: baseURL,
537+
npm: "@ai-sdk/openai-compatible",
538+
},
539+
status: "active",
540+
headers: {},
541+
options: {},
542+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
543+
limit: { context: 128000, output: 8192 },
544+
capabilities: {
545+
temperature: true,
546+
reasoning: false,
547+
attachment: false,
548+
toolcall: true,
549+
input: { text: true, audio: false, image: false, video: false, pdf: false },
550+
output: { text: true, audio: false, image: false, video: false, pdf: false },
551+
interleaved: false,
552+
},
553+
release_date: "",
554+
variants: {},
555+
}
556+
}
557+
} catch (e) {
558+
log.warn("Failed to connect to Maple proxy", { error: e })
559+
return { autoload: false }
560+
}
561+
562+
return {
563+
autoload: Object.keys(input.models).length > 0,
564+
options: {
565+
baseURL,
566+
apiKey,
567+
},
568+
}
569+
},
507570
}
508571

509572
export const Model = z
@@ -713,6 +776,16 @@ export namespace Provider {
713776
}
714777
}
715778

779+
// Add Maple AI provider (models are populated dynamically from the proxy)
780+
database["maple"] = {
781+
id: "maple",
782+
name: "Maple AI",
783+
source: "custom",
784+
env: ["MAPLE_API_KEY"],
785+
options: {},
786+
models: {},
787+
}
788+
716789
function mergeProvider(providerID: string, provider: Partial<Info>) {
717790
const existing = providers[providerID]
718791
if (existing) {

packages/opencode/src/server/routes/provider.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ export const ProviderRoutes = lazy(() =>
4646
filteredProviders[key] = value
4747
}
4848
}
49+
// Add Maple AI (not in models.dev, models discovered dynamically from proxy)
50+
if ((enabled ? enabled.has("maple") : true) && !disabled.has("maple")) {
51+
filteredProviders["maple"] = {
52+
id: "maple",
53+
name: "Maple AI",
54+
env: ["MAPLE_API_KEY"],
55+
models: {},
56+
}
57+
}
4958

5059
const connected = await Provider.list()
5160
const providers = Object.assign(
@@ -54,7 +63,11 @@ export const ProviderRoutes = lazy(() =>
5463
)
5564
return c.json({
5665
all: Object.values(providers),
57-
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
66+
default: mapValues(providers, (item) => {
67+
const models = Object.values(item.models)
68+
if (models.length === 0) return ""
69+
return Provider.sort(models)[0].id
70+
}),
5871
connected: Object.keys(connected),
5972
})
6073
},

0 commit comments

Comments
 (0)