Skip to content

Commit d421ab4

Browse files
committed
feat: add Maple AI provider integration
1 parent 5e9d5c7 commit d421ab4

4 files changed

Lines changed: 113 additions & 0 deletions

File tree

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,15 @@ export const ProvidersLoginCommand = cmd({
338338
filtered[key] = value
339339
}
340340
}
341+
// Add Maple AI (not in models.dev, models discovered dynamically from proxy)
342+
if ((enabled ? enabled.has("maple") : true) && !disabled.has("maple")) {
343+
filtered["maple"] = {
344+
id: "maple",
345+
name: "Maple AI",
346+
env: ["MAPLE_API_KEY"],
347+
models: {},
348+
}
349+
}
341350
return filtered
342351
})
343352
const hooks = await AppRuntime.runPromise(
@@ -463,6 +472,19 @@ export const ProvidersLoginCommand = cmd({
463472
)
464473
}
465474

475+
if (provider === "maple") {
476+
prompts.log.info(
477+
"Maple AI is a TEE-based private AI provider.\n\n" +
478+
"Setup:\n" +
479+
" 1. Start the Maple Proxy (desktop app or Docker)\n" +
480+
" 2. Generate an API key in the Maple app\n" +
481+
" 3. Enter your API key below\n\n" +
482+
"The default proxy URL is http://127.0.0.1:8080/v1\n" +
483+
"To use a different URL, add to opencode.json:\n" +
484+
' { "provider": { "maple": { "options": { "baseURL": "http://your-url/v1" } } } }',
485+
)
486+
}
487+
466488
const key = await prompts.password({
467489
message: "Enter your API key",
468490
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
@@ -289,6 +289,20 @@ function ApiMethod(props: ApiMethodProps) {
289289
</text>
290290
</box>
291291
),
292+
maple: (
293+
<box gap={1}>
294+
<text fg={theme.textMuted}>
295+
Maple AI is a TEE-based private AI provider. Start the Maple Proxy (desktop app or Docker) and generate
296+
an API key in the Maple app.
297+
</text>
298+
<text fg={theme.text}>
299+
Default proxy URL: <span style={{ fg: theme.primary }}>http://127.0.0.1:8080/v1</span>
300+
</text>
301+
<text fg={theme.textMuted}>
302+
To use a different URL, add to opencode.json: provider.maple.options.baseURL
303+
</text>
304+
</box>
305+
),
292306
}[props.providerID] ?? undefined
293307
}
294308
onConfirm={async (value) => {

packages/opencode/src/provider/models-snapshot.ts

Lines changed: 2 additions & 0 deletions
Large diffs are not rendered by default.

packages/opencode/src/provider/provider.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,71 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
803803
},
804804
},
805805
}),
806+
maple: Effect.fnUntraced(function* (input: Info) {
807+
const cfg = yield* dep.config()
808+
const env = yield* dep.env()
809+
const baseURL = cfg.provider?.["maple"]?.options?.baseURL ?? "http://127.0.0.1:8080/v1"
810+
811+
const auth = yield* dep.auth("maple")
812+
const apiKey = auth?.type === "api" ? auth.key : env["MAPLE_API_KEY"]
813+
814+
if (!apiKey) return { autoload: false }
815+
816+
try {
817+
const response = yield* Effect.promise(() =>
818+
fetch(`${baseURL}/models`, {
819+
headers: { Authorization: `Bearer ${apiKey}` },
820+
signal: AbortSignal.timeout(5000),
821+
}),
822+
)
823+
if (!response.ok) {
824+
log.warn("Failed to fetch Maple models", { status: response.status })
825+
return { autoload: false }
826+
}
827+
const data = yield* Effect.promise(() => response.json() as Promise<{ data?: Array<{ id: string }> }>)
828+
const models = data.data ?? []
829+
830+
for (const model of models) {
831+
input.models[model.id] = {
832+
id: ModelID.make(model.id),
833+
providerID: ProviderID.make("maple"),
834+
name: model.id,
835+
api: {
836+
id: model.id,
837+
url: baseURL,
838+
npm: "@ai-sdk/openai-compatible",
839+
},
840+
status: "active",
841+
headers: {},
842+
options: {},
843+
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
844+
limit: { context: 128000, output: 8192 },
845+
capabilities: {
846+
temperature: true,
847+
reasoning: false,
848+
attachment: false,
849+
toolcall: true,
850+
input: { text: true, audio: false, image: false, video: false, pdf: false },
851+
output: { text: true, audio: false, image: false, video: false, pdf: false },
852+
interleaved: false,
853+
},
854+
release_date: "",
855+
variants: {},
856+
}
857+
}
858+
} catch (e) {
859+
log.warn("Failed to connect to Maple proxy", { error: e })
860+
return { autoload: false }
861+
}
862+
863+
return {
864+
autoload: Object.keys(input.models).length > 0,
865+
options: {
866+
baseURL,
867+
apiKey,
868+
},
869+
}
870+
}),
806871
}
807872
}
808873

@@ -1082,6 +1147,16 @@ const layer: Layer.Layer<
10821147
get: (key: string) => env.get(key),
10831148
}
10841149

1150+
// Add Maple AI provider (models are populated dynamically from the proxy)
1151+
database["maple"] = {
1152+
id: ProviderID.make("maple"),
1153+
name: "Maple AI",
1154+
source: "custom",
1155+
env: ["MAPLE_API_KEY"],
1156+
options: {},
1157+
models: {},
1158+
}
1159+
10851160
log.info("init")
10861161

10871162
function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {

0 commit comments

Comments
 (0)