Merge 9d5f4da3d6 into 4583f88626
This commit is contained in:
commit
0e14383298
@ -46,6 +46,7 @@ See [Venice AI](/providers/venice).
|
||||
- [GLM models](/providers/glm)
|
||||
- [MiniMax](/providers/minimax)
|
||||
- [Venius (Venice AI, privacy-focused)](/providers/venice)
|
||||
- [Maple AI (TEE-based private inference)](/providers/maple)
|
||||
- [Ollama (local models)](/providers/ollama)
|
||||
|
||||
## Transcription providers
|
||||
|
||||
204
docs/providers/maple.md
Normal file
204
docs/providers/maple.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Maple AI Provider
|
||||
|
||||
Maple AI provides TEE-based (Trusted Execution Environment) private inference using Confidential Computing. All inference runs in secure enclaves with end-to-end encryption and cryptographic attestations, ensuring your prompts and responses remain private.
|
||||
|
||||
## How It Works
|
||||
|
||||
Maple AI runs as a local proxy (desktop app or Docker container) that connects to secure TEE enclaves. Your data is encrypted end-to-end and never visible to Maple or any third party. Maple runs the largest, state-of-the-art open models and does not share any data back to the model creators. Sign up at [trymaple.ai](https://trymaple.ai) to get started. Maple Proxy requires a paid account with API credits.
|
||||
|
||||
1. **Desktop App or Docker**: Run the Maple proxy locally
|
||||
2. **Local Proxy**: Default endpoint at `http://127.0.0.1:8080/v1`
|
||||
3. **TEE Backend**: Requests are forwarded to Maple's secure enclaves
|
||||
4. **Cryptographic Attestation**: Verify the enclave is running trusted code
|
||||
|
||||
## Features
|
||||
|
||||
- **End-to-end encryption**: Your prompts and responses are encrypted
|
||||
- **Cryptographic attestations**: Verify the secure enclave integrity
|
||||
- **Open-source verifiable code**: Audit the code running in the enclave
|
||||
- **OpenAI-compatible API**: Standard `/v1` endpoints for easy integration
|
||||
- **Streaming**: Required for all completions
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install Maple Proxy
|
||||
|
||||
**Desktop App (Recommended)**
|
||||
|
||||
Download and run the Maple desktop app from [trymaple.ai/downloads](https://trymaple.ai/downloads). The proxy runs automatically on `http://127.0.0.1:8080/v1`.
|
||||
|
||||
**Docker**
|
||||
|
||||
```bash
|
||||
docker run -p 8080:8080 \
|
||||
-e MAPLE_BACKEND_URL=https://enclave.trymaple.ai \
|
||||
-e MAPLE_ENABLE_CORS=true \
|
||||
trymaple/proxy
|
||||
```
|
||||
|
||||
### 2. Generate API Key
|
||||
|
||||
Open the Maple app and generate an API key. This key authenticates your requests to the local proxy.
|
||||
|
||||
### 3. Configure Moltbot
|
||||
|
||||
**Option A: Interactive Setup (Recommended)**
|
||||
|
||||
```bash
|
||||
moltbot onboard --auth-choice maple-api-key
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Prompt for your API key
|
||||
2. Ask for the proxy URL (defaults to `http://127.0.0.1:8080/v1`)
|
||||
3. Configure the provider automatically
|
||||
|
||||
**Option B: Environment Variable**
|
||||
|
||||
```bash
|
||||
export MAPLE_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
**Option C: Non-interactive**
|
||||
|
||||
```bash
|
||||
moltbot onboard --non-interactive \
|
||||
--auth-choice maple-api-key \
|
||||
--token "your-api-key" \
|
||||
--token-provider maple
|
||||
```
|
||||
|
||||
### 4. Verify Setup
|
||||
|
||||
```bash
|
||||
moltbot chat --model maple/llama-3.3-70b "Hello, are you working?"
|
||||
```
|
||||
|
||||
## Available Models
|
||||
|
||||
| Model ID | Name | Use Case | Pricing |
|
||||
|----------|------|----------|---------|
|
||||
| `kimi-k2-thinking` | Kimi K2 Thinking | Complex agentic workflows, multi-step coding, web research | $4/$4 per M tokens |
|
||||
| `gpt-oss-120b` | GPT OSS 120B | Creative writing, structured data | $4/$4 |
|
||||
| `deepseek-r1-0528` | DeepSeek R1 | Research, advanced math, coding | $4/$4 |
|
||||
| `qwen3-coder-480b` | Qwen3 Coder 480B | Agentic coding, large codebase analysis, browser automation | $4/$4 |
|
||||
| `qwen3-vl-30b` | Qwen3 VL 30B | Image and video analysis, screenshot-to-code, OCR, GUI automation | $4/$4 |
|
||||
| `llama-3.3-70b` | Llama 3.3 70B | General reasoning, conversation | $4/$4 |
|
||||
| `gemma-3-27b` | Gemma 3 27B | General purpose, efficient | $10/$10 |
|
||||
|
||||
## Model Selection
|
||||
|
||||
Change your default model anytime:
|
||||
|
||||
```bash
|
||||
moltbot models set maple/llama-3.3-70b
|
||||
moltbot models set maple/deepseek-r1-0528
|
||||
```
|
||||
|
||||
List available models:
|
||||
|
||||
```bash
|
||||
moltbot models list | grep maple
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Custom Proxy URL
|
||||
|
||||
If running the proxy on a different host or port:
|
||||
|
||||
```yaml
|
||||
# ~/.moltbot.yaml
|
||||
models:
|
||||
providers:
|
||||
maple:
|
||||
baseUrl: "http://192.168.1.100:8080/v1"
|
||||
api: "openai-completions"
|
||||
apiKey: "MAPLE_API_KEY"
|
||||
```
|
||||
|
||||
### Docker Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `MAPLE_BACKEND_URL` | TEE enclave URL | `https://enclave.trymaple.ai` |
|
||||
| `MAPLE_ENABLE_CORS` | Enable CORS headers | `false` |
|
||||
| `RUST_LOG` | Log level | `info` |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# General chat
|
||||
moltbot chat --model maple/llama-3.3-70b
|
||||
|
||||
# Advanced reasoning
|
||||
moltbot chat --model maple/kimi-k2-thinking
|
||||
|
||||
# Research and coding
|
||||
moltbot chat --model maple/deepseek-r1-0528
|
||||
|
||||
# Vision tasks
|
||||
moltbot chat --model maple/qwen3-vl-30b
|
||||
|
||||
# Coding tasks
|
||||
moltbot chat --model maple/qwen3-coder-480b
|
||||
```
|
||||
|
||||
## Privacy and Security
|
||||
|
||||
### Why TEE?
|
||||
|
||||
Trusted Execution Environments (TEEs) provide hardware-level isolation:
|
||||
|
||||
- **Memory encryption**: Data is encrypted in memory
|
||||
- **Attestation**: Cryptographic proof of what code is running
|
||||
- **Isolation**: Even the host system cannot access enclave data
|
||||
|
||||
### Security Proof Attestation
|
||||
|
||||
Maple provides cryptographic attestations that prove the integrity of the secure enclave. You can view the current attestations at [trymaple.ai/proof](https://trymaple.ai/proof).
|
||||
|
||||
The Maple Proxy automatically verifies these attestations before connecting to the backend. If the attestation is invalid or tampered with, the proxy refuses to connect, similar to how SSL/TLS certificates protect web connections. This ensures you're always communicating with a genuine, unmodified Maple enclave.
|
||||
|
||||
### Verification
|
||||
|
||||
You can verify the enclave attestation to ensure:
|
||||
|
||||
1. The code running matches the open-source release
|
||||
2. The TEE hardware is genuine
|
||||
3. No tampering has occurred
|
||||
|
||||
Visit [trymaple.ai/proof](https://trymaple.ai/proof) to inspect the current attestation details.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Proxy not running
|
||||
|
||||
Ensure the Maple app is running or Docker container is active:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8080/health
|
||||
```
|
||||
|
||||
### Connection refused
|
||||
|
||||
Check the proxy URL is correct and the service is running:
|
||||
|
||||
```bash
|
||||
# Test connectivity
|
||||
curl -H "Authorization: Bearer $MAPLE_API_KEY" \
|
||||
http://127.0.0.1:8080/v1/models
|
||||
```
|
||||
|
||||
### Model not available
|
||||
|
||||
The model list is fetched from the proxy. Ensure your Maple subscription includes the model you're trying to use.
|
||||
|
||||
## Links
|
||||
|
||||
- [Maple AI](https://trymaple.ai)
|
||||
- [Downloads](https://trymaple.ai/downloads)
|
||||
- [Security Proof](https://trymaple.ai/proof)
|
||||
- [Proxy Documentation](https://blog.trymaple.ai/maple-proxy-documentation/)
|
||||
- [Maple GitHub](https://github.com/OpenSecretCloud/Maple)
|
||||
- [Maple Proxy GitHub](https://github.com/OpenSecretCloud/maple-proxy)
|
||||
212
src/agents/maple-models.ts
Normal file
212
src/agents/maple-models.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import type { ModelDefinitionConfig } from "../config/types.js";
|
||||
|
||||
/**
|
||||
* Maple AI Provider
|
||||
*
|
||||
* Maple AI is a privacy-focused AI provider that uses Confidential Computing (TEEs)
|
||||
* to provide end-to-end encryption with cryptographic attestations. Users run the
|
||||
* Maple desktop app or Docker container, then point their tools at the local proxy.
|
||||
*
|
||||
* Default proxy URL: http://127.0.0.1:8080/v1
|
||||
*/
|
||||
|
||||
export const MAPLE_DEFAULT_BASE_URL = "http://127.0.0.1:8080/v1";
|
||||
export const MAPLE_DEFAULT_MODEL_ID = "kimi-k2-thinking";
|
||||
export const MAPLE_DEFAULT_MODEL_REF = `maple/${MAPLE_DEFAULT_MODEL_ID}`;
|
||||
|
||||
// Maple uses flat pricing per million tokens
|
||||
export const MAPLE_DEFAULT_COST = {
|
||||
input: 4,
|
||||
output: 4,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Static catalog of Maple AI models.
|
||||
*
|
||||
* All models run in TEE-based Confidential Computing environments,
|
||||
* providing end-to-end encryption and cryptographic attestations.
|
||||
*
|
||||
* This catalog serves as a fallback when the Maple API is unreachable.
|
||||
*/
|
||||
export const MAPLE_MODEL_CATALOG = [
|
||||
{
|
||||
id: "kimi-k2-thinking",
|
||||
name: "Kimi K2 Thinking",
|
||||
description: "Complex agentic workflows, multi-step coding, web research",
|
||||
reasoning: true,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 262144,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 4, output: 4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
id: "gpt-oss-120b",
|
||||
name: "GPT OSS 120B",
|
||||
description: "Creative writing, structured data",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 4, output: 4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
id: "deepseek-r1-0528",
|
||||
name: "DeepSeek R1",
|
||||
description: "Research, advanced math, coding",
|
||||
reasoning: true,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 4, output: 4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
id: "qwen3-coder-480b",
|
||||
name: "Qwen3 Coder 480B",
|
||||
description: "Agentic coding, large codebase analysis, browser automation",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 4, output: 4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
id: "qwen3-vl-30b",
|
||||
name: "Qwen3 VL 30B",
|
||||
description: "Image and video analysis, screenshot-to-code, OCR, GUI automation",
|
||||
reasoning: false,
|
||||
input: ["text", "image"] as const,
|
||||
contextWindow: 262144,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 4, output: 4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
id: "llama-3.3-70b",
|
||||
name: "Llama 3.3 70B",
|
||||
description: "General reasoning, conversation",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 4, output: 4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
{
|
||||
id: "gemma-3-27b",
|
||||
name: "Gemma 3 27B",
|
||||
description: "General purpose, efficient",
|
||||
reasoning: false,
|
||||
input: ["text"] as const,
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
cost: { input: 10, output: 10, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type MapleCatalogEntry = (typeof MAPLE_MODEL_CATALOG)[number];
|
||||
|
||||
/**
|
||||
* Build a ModelDefinitionConfig from a Maple catalog entry.
|
||||
*/
|
||||
export function buildMapleModelDefinition(entry: MapleCatalogEntry): ModelDefinitionConfig {
|
||||
return {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
reasoning: entry.reasoning,
|
||||
input: [...entry.input],
|
||||
cost: entry.cost,
|
||||
contextWindow: entry.contextWindow,
|
||||
maxTokens: entry.maxTokens,
|
||||
};
|
||||
}
|
||||
|
||||
// Maple API response types (OpenAI-compatible)
|
||||
interface MapleModel {
|
||||
id: string;
|
||||
object: string;
|
||||
owned_by?: string;
|
||||
}
|
||||
|
||||
interface MapleModelsResponse {
|
||||
object: string;
|
||||
data: MapleModel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover models from Maple API with fallback to static catalog.
|
||||
* Requires authentication (Bearer token).
|
||||
*/
|
||||
export async function discoverMapleModels(params?: {
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
}): Promise<ModelDefinitionConfig[]> {
|
||||
// Skip API discovery in test environment
|
||||
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
||||
return MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
}
|
||||
|
||||
const baseUrl = params?.baseUrl ?? MAPLE_DEFAULT_BASE_URL;
|
||||
const apiKey = params?.apiKey;
|
||||
|
||||
// If no API key, return static catalog
|
||||
if (!apiKey) {
|
||||
return MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/models`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(
|
||||
`[maple-models] Failed to discover models: HTTP ${response.status}, using static catalog`,
|
||||
);
|
||||
return MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as MapleModelsResponse;
|
||||
if (!Array.isArray(data.data) || data.data.length === 0) {
|
||||
console.warn("[maple-models] No models found from API, using static catalog");
|
||||
return MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
}
|
||||
|
||||
// Merge discovered models with catalog metadata
|
||||
const catalogById = new Map<string, MapleCatalogEntry>(
|
||||
MAPLE_MODEL_CATALOG.map((m) => [m.id, m]),
|
||||
);
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
|
||||
for (const apiModel of data.data) {
|
||||
const catalogEntry = catalogById.get(apiModel.id);
|
||||
if (catalogEntry) {
|
||||
// Use catalog metadata for known models
|
||||
models.push(buildMapleModelDefinition(catalogEntry));
|
||||
} else {
|
||||
// Create definition for newly discovered models not in catalog
|
||||
const isReasoning =
|
||||
apiModel.id.toLowerCase().includes("thinking") ||
|
||||
apiModel.id.toLowerCase().includes("reason") ||
|
||||
apiModel.id.toLowerCase().includes("r1");
|
||||
|
||||
models.push({
|
||||
id: apiModel.id,
|
||||
name: apiModel.id,
|
||||
reasoning: isReasoning,
|
||||
input: ["text"],
|
||||
cost: MAPLE_DEFAULT_COST,
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return models.length > 0 ? models : MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
} catch (error) {
|
||||
console.warn(`[maple-models] Discovery failed: ${String(error)}, using static catalog`);
|
||||
return MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
}
|
||||
}
|
||||
@ -284,6 +284,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
||||
xiaomi: "XIAOMI_API_KEY",
|
||||
synthetic: "SYNTHETIC_API_KEY",
|
||||
venice: "VENICE_API_KEY",
|
||||
maple: "MAPLE_API_KEY",
|
||||
mistral: "MISTRAL_API_KEY",
|
||||
opencode: "OPENCODE_API_KEY",
|
||||
};
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
SYNTHETIC_MODEL_CATALOG,
|
||||
} from "./synthetic-models.js";
|
||||
import { discoverVeniceModels, VENICE_BASE_URL } from "./venice-models.js";
|
||||
import { discoverMapleModels, MAPLE_DEFAULT_BASE_URL } from "./maple-models.js";
|
||||
|
||||
type ModelsConfig = NonNullable<MoltbotConfig["models"]>;
|
||||
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
|
||||
@ -379,6 +380,15 @@ async function buildVeniceProvider(): Promise<ProviderConfig> {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildMapleProvider(params?: { apiKey?: string }): Promise<ProviderConfig> {
|
||||
const models = await discoverMapleModels({ apiKey: params?.apiKey });
|
||||
return {
|
||||
baseUrl: MAPLE_DEFAULT_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models,
|
||||
};
|
||||
}
|
||||
|
||||
async function buildOllamaProvider(): Promise<ProviderConfig> {
|
||||
const models = await discoverOllamaModels();
|
||||
return {
|
||||
@ -431,6 +441,13 @@ export async function resolveImplicitProviders(params: {
|
||||
providers.venice = { ...(await buildVeniceProvider()), apiKey: veniceKey };
|
||||
}
|
||||
|
||||
const mapleKey =
|
||||
resolveEnvApiKeyVarName("maple") ??
|
||||
resolveApiKeyFromProfiles({ provider: "maple", store: authStore });
|
||||
if (mapleKey) {
|
||||
providers.maple = { ...(await buildMapleProvider({ apiKey: mapleKey })), apiKey: mapleKey };
|
||||
}
|
||||
|
||||
const qwenProfiles = listProfilesForProvider(authStore, "qwen-portal");
|
||||
if (qwenProfiles.length > 0) {
|
||||
providers["qwen-portal"] = {
|
||||
|
||||
@ -21,6 +21,7 @@ export type AuthChoiceGroupId =
|
||||
| "minimax"
|
||||
| "synthetic"
|
||||
| "venice"
|
||||
| "maple"
|
||||
| "qwen";
|
||||
|
||||
export type AuthChoiceGroup = {
|
||||
@ -72,6 +73,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
hint: "Privacy-focused (uncensored models)",
|
||||
choices: ["venice-api-key"],
|
||||
},
|
||||
{
|
||||
value: "maple",
|
||||
label: "Maple AI",
|
||||
hint: "TEE-based private inference",
|
||||
choices: ["maple-api-key"],
|
||||
},
|
||||
{
|
||||
value: "google",
|
||||
label: "Google",
|
||||
@ -154,6 +161,11 @@ export function buildAuthChoiceOptions(params: {
|
||||
label: "Venice AI API key",
|
||||
hint: "Privacy-focused inference (uncensored models)",
|
||||
});
|
||||
options.push({
|
||||
value: "maple-api-key",
|
||||
label: "Maple AI API key",
|
||||
hint: "TEE-based private inference (end-to-end encrypted)",
|
||||
});
|
||||
options.push({
|
||||
value: "github-copilot",
|
||||
label: "GitHub Copilot (GitHub device login)",
|
||||
|
||||
@ -25,6 +25,8 @@ import {
|
||||
applySyntheticProviderConfig,
|
||||
applyVeniceConfig,
|
||||
applyVeniceProviderConfig,
|
||||
applyMapleConfig,
|
||||
applyMapleProviderConfig,
|
||||
applyVercelAiGatewayConfig,
|
||||
applyVercelAiGatewayProviderConfig,
|
||||
applyXiaomiConfig,
|
||||
@ -35,6 +37,7 @@ import {
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||
VENICE_DEFAULT_MODEL_REF,
|
||||
MAPLE_DEFAULT_MODEL_REF,
|
||||
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
XIAOMI_DEFAULT_MODEL_REF,
|
||||
setGeminiApiKey,
|
||||
@ -44,6 +47,7 @@ import {
|
||||
setOpenrouterApiKey,
|
||||
setSyntheticApiKey,
|
||||
setVeniceApiKey,
|
||||
setMapleApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setXiaomiApiKey,
|
||||
setZaiApiKey,
|
||||
@ -89,6 +93,8 @@ export async function applyAuthChoiceApiProviders(
|
||||
authChoice = "synthetic-api-key";
|
||||
} else if (params.opts.tokenProvider === "venice") {
|
||||
authChoice = "venice-api-key";
|
||||
} else if (params.opts.tokenProvider === "maple") {
|
||||
authChoice = "maple-api-key";
|
||||
} else if (params.opts.tokenProvider === "opencode") {
|
||||
authChoice = "opencode-zen";
|
||||
}
|
||||
@ -576,6 +582,78 @@ export async function applyAuthChoiceApiProviders(
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (authChoice === "maple-api-key") {
|
||||
let hasCredential = false;
|
||||
let baseUrl: string | undefined;
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "maple") {
|
||||
await setMapleApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Maple AI provides TEE-based private inference with end-to-end encryption.",
|
||||
"Download the Maple desktop app at: https://trymaple.ai/downloads",
|
||||
"Run the app or Docker container, then configure the proxy URL.",
|
||||
"Default URL: http://127.0.0.1:8080/v1",
|
||||
"Generate your API key within the Maple app.",
|
||||
].join("\n"),
|
||||
"Maple AI",
|
||||
);
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("maple");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing MAPLE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await setMapleApiKey(envKey.apiKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter Maple AI API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
await setMapleApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||
}
|
||||
|
||||
// Ask for base URL (with default)
|
||||
const customUrl = await params.prompter.text({
|
||||
message: "Enter Maple proxy URL (press Enter for default)",
|
||||
placeholder: "http://127.0.0.1:8080/v1",
|
||||
});
|
||||
if (customUrl && String(customUrl).trim()) {
|
||||
baseUrl = String(customUrl).trim();
|
||||
}
|
||||
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "maple:default",
|
||||
provider: "maple",
|
||||
mode: "api_key",
|
||||
});
|
||||
{
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: MAPLE_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: (config) => applyMapleConfig(config, { baseUrl }),
|
||||
applyProviderConfig: (config) => applyMapleProviderConfig(config, { baseUrl }),
|
||||
noteDefault: MAPLE_DEFAULT_MODEL_REF,
|
||||
noteAgentModel,
|
||||
prompter: params.prompter,
|
||||
});
|
||||
nextConfig = applied.config;
|
||||
agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
|
||||
}
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (authChoice === "opencode-zen") {
|
||||
let hasCredential = false;
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "opencode") {
|
||||
|
||||
@ -21,6 +21,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
|
||||
"xiaomi-api-key": "xiaomi",
|
||||
"synthetic-api-key": "synthetic",
|
||||
"venice-api-key": "venice",
|
||||
"maple-api-key": "maple",
|
||||
"github-copilot": "github-copilot",
|
||||
"copilot-proxy": "copilot-proxy",
|
||||
"minimax-cloud": "minimax",
|
||||
|
||||
@ -11,6 +11,12 @@ import {
|
||||
VENICE_DEFAULT_MODEL_REF,
|
||||
VENICE_MODEL_CATALOG,
|
||||
} from "../agents/venice-models.js";
|
||||
import {
|
||||
buildMapleModelDefinition,
|
||||
MAPLE_DEFAULT_BASE_URL,
|
||||
MAPLE_DEFAULT_MODEL_REF,
|
||||
MAPLE_MODEL_CATALOG,
|
||||
} from "../agents/maple-models.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import {
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
@ -484,6 +490,85 @@ export function applyVeniceConfig(cfg: MoltbotConfig): MoltbotConfig {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Maple provider configuration without changing the default model.
|
||||
* Registers Maple models and sets up the provider, but preserves existing model selection.
|
||||
*/
|
||||
export function applyMapleProviderConfig(
|
||||
cfg: MoltbotConfig,
|
||||
params?: { baseUrl?: string },
|
||||
): MoltbotConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[MAPLE_DEFAULT_MODEL_REF] = {
|
||||
...models[MAPLE_DEFAULT_MODEL_REF],
|
||||
alias: models[MAPLE_DEFAULT_MODEL_REF]?.alias ?? "Kimi K2 Thinking",
|
||||
};
|
||||
|
||||
const providers = { ...cfg.models?.providers };
|
||||
const existingProvider = providers.maple;
|
||||
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||
const mapleModels = MAPLE_MODEL_CATALOG.map(buildMapleModelDefinition);
|
||||
const mergedModels = [
|
||||
...existingModels,
|
||||
...mapleModels.filter((model) => !existingModels.some((existing) => existing.id === model.id)),
|
||||
];
|
||||
const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
> as { apiKey?: string };
|
||||
const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
|
||||
const normalizedApiKey = resolvedApiKey?.trim();
|
||||
const baseUrl = params?.baseUrl ?? MAPLE_DEFAULT_BASE_URL;
|
||||
providers.maple = {
|
||||
...existingProviderRest,
|
||||
baseUrl,
|
||||
api: "openai-completions",
|
||||
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||
models: mergedModels.length > 0 ? mergedModels : mapleModels,
|
||||
};
|
||||
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: cfg.models?.mode ?? "merge",
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Maple provider configuration AND set Maple as the default model.
|
||||
* Use this when Maple is the primary provider choice during onboarding.
|
||||
*/
|
||||
export function applyMapleConfig(cfg: MoltbotConfig, params?: { baseUrl?: string }): MoltbotConfig {
|
||||
const next = applyMapleProviderConfig(cfg, params);
|
||||
const existingModel = next.agents?.defaults?.model;
|
||||
return {
|
||||
...next,
|
||||
agents: {
|
||||
...next.agents,
|
||||
defaults: {
|
||||
...next.agents?.defaults,
|
||||
model: {
|
||||
...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
|
||||
? {
|
||||
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
|
||||
}
|
||||
: undefined),
|
||||
primary: MAPLE_DEFAULT_MODEL_REF,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyAuthProfileConfig(
|
||||
cfg: MoltbotConfig,
|
||||
params: {
|
||||
|
||||
@ -112,6 +112,19 @@ export async function setVeniceApiKey(key: string, agentDir?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function setMapleApiKey(key: string, agentDir?: string) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "maple:default",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "maple",
|
||||
key,
|
||||
},
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
||||
export const XIAOMI_DEFAULT_MODEL_REF = "xiaomi/mimo-v2-flash";
|
||||
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
||||
|
||||
@ -3,6 +3,7 @@ export {
|
||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||
} from "../agents/synthetic-models.js";
|
||||
export { VENICE_DEFAULT_MODEL_ID, VENICE_DEFAULT_MODEL_REF } from "../agents/venice-models.js";
|
||||
export { MAPLE_DEFAULT_MODEL_ID, MAPLE_DEFAULT_MODEL_REF } from "../agents/maple-models.js";
|
||||
export {
|
||||
applyAuthProfileConfig,
|
||||
applyKimiCodeConfig,
|
||||
@ -15,6 +16,8 @@ export {
|
||||
applySyntheticProviderConfig,
|
||||
applyVeniceConfig,
|
||||
applyVeniceProviderConfig,
|
||||
applyMapleConfig,
|
||||
applyMapleProviderConfig,
|
||||
applyVercelAiGatewayConfig,
|
||||
applyVercelAiGatewayProviderConfig,
|
||||
applyXiaomiConfig,
|
||||
@ -45,6 +48,7 @@ export {
|
||||
setOpenrouterApiKey,
|
||||
setSyntheticApiKey,
|
||||
setVeniceApiKey,
|
||||
setMapleApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setXiaomiApiKey,
|
||||
setZaiApiKey,
|
||||
|
||||
@ -17,6 +17,7 @@ export type AuthChoice =
|
||||
| "kimi-code-api-key"
|
||||
| "synthetic-api-key"
|
||||
| "venice-api-key"
|
||||
| "maple-api-key"
|
||||
| "codex-cli"
|
||||
| "apiKey"
|
||||
| "gemini-api-key"
|
||||
@ -72,6 +73,8 @@ export type OnboardOptions = {
|
||||
minimaxApiKey?: string;
|
||||
syntheticApiKey?: string;
|
||||
veniceApiKey?: string;
|
||||
mapleApiKey?: string;
|
||||
mapleBaseUrl?: string;
|
||||
opencodeZenApiKey?: string;
|
||||
gatewayPort?: number;
|
||||
gatewayBind?: GatewayBind;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user