Skip to content

Commit 945aa1a

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 d500a84 commit 945aa1a

4 files changed

Lines changed: 118 additions & 7 deletions

File tree

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,15 @@ export const ProvidersLoginCommand = cmd({
317317
filtered[key] = value
318318
}
319319
}
320+
// Add Maple AI (not in models.dev, models discovered dynamically from proxy)
321+
if ((enabled ? enabled.has("maple") : true) && !disabled.has("maple")) {
322+
filtered["maple"] = {
323+
id: "maple",
324+
name: "Maple AI",
325+
env: ["MAPLE_API_KEY"],
326+
models: {},
327+
}
328+
}
320329
return filtered
321330
})
322331

@@ -436,6 +445,19 @@ export const ProvidersLoginCommand = cmd({
436445
)
437446
}
438447

448+
if (provider === "maple") {
449+
prompts.log.info(
450+
"Maple AI is a TEE-based private AI provider.\n\n" +
451+
"Setup:\n" +
452+
" 1. Start the Maple Proxy (desktop app or Docker)\n" +
453+
" 2. Generate an API key in the Maple app\n" +
454+
" 3. Enter your API key below\n\n" +
455+
"The default proxy URL is http://127.0.0.1:8080/v1\n" +
456+
"To use a different URL, add to opencode.json:\n" +
457+
' { "provider": { "maple": { "options": { "baseURL": "http://your-url/v1" } } } }',
458+
)
459+
}
460+
439461
const key = await prompts.password({
440462
message: "Enter your API key",
441463
validate: (x) => (x && x.length > 0 ? undefined : "Required"),

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,20 @@ function ApiMethod(props: ApiMethodProps) {
259259
</text>
260260
</box>
261261
),
262+
maple: (
263+
<box gap={1}>
264+
<text fg={theme.textMuted}>
265+
Maple AI is a TEE-based private AI provider. Start the Maple Proxy (desktop app or Docker) and generate
266+
an API key in the Maple app.
267+
</text>
268+
<text fg={theme.text}>
269+
Default proxy URL: <span style={{ fg: theme.primary }}>http://127.0.0.1:8080/v1</span>
270+
</text>
271+
<text fg={theme.textMuted}>
272+
To use a different URL, add to opencode.json: provider.maple.options.baseURL
273+
</text>
274+
</box>
275+
),
262276
}[props.providerID] ?? undefined
263277
}
264278
onConfirm={async (value) => {

packages/opencode/src/provider/provider.ts

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -737,14 +737,66 @@ export namespace Provider {
737737
},
738738
}
739739
},
740-
kilo: async () => {
740+
maple: async (input) => {
741+
const config = await Config.get()
742+
const baseURL = config.provider?.["maple"]?.options?.baseURL ?? "http://127.0.0.1:8080/v1"
743+
744+
const auth = await Auth.get("maple")
745+
const apiKey = auth?.type === "api" ? auth.key : Env.get("MAPLE_API_KEY")
746+
747+
if (!apiKey) return { autoload: false }
748+
749+
// Dynamically fetch models from the Maple proxy
750+
try {
751+
const response = await fetch(`${baseURL}/models`, {
752+
headers: { Authorization: `Bearer ${apiKey}` },
753+
signal: AbortSignal.timeout(5000),
754+
})
755+
if (!response.ok) {
756+
log.warn("Failed to fetch Maple models", { status: response.status })
757+
return { autoload: false }
758+
}
759+
const data = (await response.json()) as { data?: Array<{ id: string }> }
760+
const models = data.data ?? []
761+
762+
for (const model of models) {
763+
input.models[model.id] = {
764+
id: model.id,
765+
providerID: "maple",
766+
name: model.id,
767+
api: {
768+
id: model.id,
769+
url: baseURL,
770+
npm: "@ai-sdk/openai-compatible",
771+
},
772+
status: "active",
773+
headers: {},
774+
options: {},
775+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
776+
limit: { context: 128000, output: 8192 },
777+
capabilities: {
778+
temperature: true,
779+
reasoning: false,
780+
attachment: false,
781+
toolcall: true,
782+
input: { text: true, audio: false, image: false, video: false, pdf: false },
783+
output: { text: true, audio: false, image: false, video: false, pdf: false },
784+
interleaved: false,
785+
},
786+
release_date: "",
787+
variants: {},
788+
}
789+
}
790+
} catch (e) {
791+
log.warn("Failed to connect to Maple proxy", { error: e })
792+
return { autoload: false }
793+
}
794+
741795
return {
742-
autoload: false,
796+
autoload: Object.keys(input.models).length > 0,
743797
options: {
744-
headers: {
745-
"HTTP-Referer": "https://opencode.ai/",
746-
"X-Title": "opencode",
747-
},
798+
baseURL,
799+
apiKey,
748800
},
749801
}
750802
},
@@ -946,6 +998,16 @@ export namespace Provider {
946998

947999
const configProviders = Object.entries(config.provider ?? {})
9481000

1001+
// Add Maple AI provider (models are populated dynamically from the proxy)
1002+
database["maple"] = {
1003+
id: "maple",
1004+
name: "Maple AI",
1005+
source: "custom",
1006+
env: ["MAPLE_API_KEY"],
1007+
options: {},
1008+
models: {},
1009+
}
1010+
9491011
function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
9501012
const existing = providers[providerID]
9511013
if (existing) {

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

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

5463
const connected = await Provider.list()
5564
const providers = Object.assign(
@@ -58,7 +67,11 @@ export const ProviderRoutes = lazy(() =>
5867
)
5968
return c.json({
6069
all: Object.values(providers),
61-
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
70+
default: mapValues(providers, (item) => {
71+
const models = Object.values(item.models)
72+
if (models.length === 0) return ""
73+
return Provider.sort(models)[0].id
74+
}),
6275
connected: Object.keys(connected),
6376
})
6477
},

0 commit comments

Comments
 (0)