feat: add Morpheus Inference API provider integration
Morpheus is a decentralized inference marketplace that provides FREE access to AI models during Open Beta. The API is fully OpenAI-compatible. This integration adds: - Complete model catalog with 14 models: - Flagship: Qwen3 Coder 480B, Hermes 3 Llama 405B, GPT OSS 120B - Reasoning: Kimi K2 Thinking, GLM 4.7 Thinking, Qwen3 235B - Mid-size: Llama 3.3 70B (default), Qwen3 Next 80B, Mistral 31 24B - Fast: Llama 3.2 3B, Qwen3 4B - Auto-discovery from Morpheus API with fallback to static catalog - MORPHEUS_API_KEY environment variable support - Interactive onboarding via 'morpheus-api-key' auth choice - Provider auto-registration when API key is detected - Comprehensive documentation covering: - All models with context windows and features - Streaming and function calling support - Security guidelines - Troubleshooting Default model: morpheus/llama-3.3-70b (reliable, balanced performance) Morpheus API: https://api.mor.org/api/v1 (OpenAI-compatible)
This commit is contained in:
parent
640c8d1554
commit
fa4c1245a4
@ -45,6 +45,7 @@ See [Venice AI](/providers/venice).
|
||||
- [GLM models](/providers/glm)
|
||||
- [MiniMax](/providers/minimax)
|
||||
- [Venius (Venice AI, privacy-focused)](/providers/venice)
|
||||
- [Morpheus (decentralized inference, FREE beta)](/providers/morpheus)
|
||||
- [Ollama (local models)](/providers/ollama)
|
||||
|
||||
## Transcription providers
|
||||
|
||||
222
docs/providers/morpheus.md
Normal file
222
docs/providers/morpheus.md
Normal file
@ -0,0 +1,222 @@
|
||||
---
|
||||
summary: "Use Morpheus decentralized inference in Clawdbot"
|
||||
read_when:
|
||||
- You want decentralized AI inference in Clawdbot
|
||||
- You want Morpheus API setup guidance
|
||||
---
|
||||
# Morpheus Inference API
|
||||
|
||||
**Morpheus** provides decentralized AI inference via the Morpheus Network, offering FREE access to open-source models during Open Beta.
|
||||
|
||||
The Morpheus Inference API is a simple, OpenAI-compatible gateway providing users access to the Morpheus Inference Marketplace. Providers host hardware and offer inference, while the API abstracts these efforts for a seamless experience.
|
||||
|
||||
## Why Morpheus in Clawdbot
|
||||
|
||||
- **Decentralized inference** from the Morpheus Inference Marketplace
|
||||
- **FREE during Open Beta** (until 1/31/26)
|
||||
- **20+ models** including Llama, Qwen, DeepSeek, GLM, Kimi, and more
|
||||
- OpenAI-compatible `/v1` endpoints
|
||||
|
||||
## Features
|
||||
|
||||
- **OpenAI-compatible API**: Standard `/v1` endpoints for easy integration
|
||||
- **Streaming**: Supported on all models
|
||||
- **Function calling**: Supported on most models
|
||||
- **Vision**: Supported on select models (e.g., `mistral-31-24b`)
|
||||
- **Web search**: Add `:web` suffix to any model for web search capabilities
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Get API Key
|
||||
|
||||
1. Create an account at [app.mor.org](https://app.mor.org)
|
||||
2. Click **Create API Key** and copy it immediately
|
||||
3. Your API key format: `sk-xxxxxxxxxxxxx`
|
||||
|
||||
### 2. Configure Clawdbot
|
||||
|
||||
**Option A: Environment Variable**
|
||||
|
||||
```bash
|
||||
export MORPHEUS_API_KEY="sk-xxxxxxxxxxxxx"
|
||||
```
|
||||
|
||||
**Option B: Interactive Setup (Recommended)**
|
||||
|
||||
```bash
|
||||
clawdbot onboard --auth-choice morpheus-api-key
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Prompt for your API key (or use existing `MORPHEUS_API_KEY`)
|
||||
2. Show all available Morpheus models
|
||||
3. Let you pick your default model
|
||||
4. Configure the provider automatically
|
||||
|
||||
**Option C: Non-interactive**
|
||||
|
||||
```bash
|
||||
clawdbot onboard --non-interactive \
|
||||
--auth-choice morpheus-api-key \
|
||||
--morpheus-api-key "sk-xxxxxxxxxxxxx"
|
||||
```
|
||||
|
||||
### 3. Verify Setup
|
||||
|
||||
```bash
|
||||
clawdbot chat --model morpheus/llama-3.3-70b "Hello, are you working?"
|
||||
```
|
||||
|
||||
## Model Selection
|
||||
|
||||
After setup, Clawdbot shows all available Morpheus models. Pick based on your needs:
|
||||
|
||||
- **Default (our pick)**: `morpheus/llama-3.3-70b` for reliable, balanced performance
|
||||
- **Best for coding**: `morpheus/qwen3-coder-480b-a35b-instruct` with 256K context
|
||||
- **Best for reasoning**: `morpheus/kimi-k2-thinking` for deep analysis
|
||||
- **Fastest**: `morpheus/llama-3.2-3b` for low-latency responses
|
||||
|
||||
Change your default model anytime:
|
||||
|
||||
```bash
|
||||
clawdbot models set morpheus/llama-3.3-70b
|
||||
clawdbot models set morpheus/kimi-k2-thinking
|
||||
```
|
||||
|
||||
List all available models:
|
||||
|
||||
```bash
|
||||
clawdbot models list | grep morpheus
|
||||
```
|
||||
|
||||
## Available Models
|
||||
|
||||
### Flagship Models
|
||||
|
||||
| Model ID | Name | Context | Best For |
|
||||
|----------|------|---------|----------|
|
||||
| `qwen3-coder-480b-a35b-instruct` | Qwen3 Coder 480B | 256K | Code generation |
|
||||
| `hermes-3-llama-3.1-405b` | Hermes 3 Llama 405B | 128K | General purpose |
|
||||
| `gpt-oss-120b` | GPT OSS 120B | 128K | GPT-style responses |
|
||||
|
||||
### Reasoning Models
|
||||
|
||||
| Model ID | Name | Context | Best For |
|
||||
|----------|------|---------|----------|
|
||||
| `kimi-k2-thinking` | Kimi K2 Thinking | 256K | Deep reasoning, math, coding |
|
||||
| `glm-4.7-thinking` | GLM 4.7 Thinking | 198K | Extended thinking |
|
||||
| `glm-4.7` | GLM 4.7 | 198K | Reasoning, multilingual |
|
||||
| `qwen3-235b` | Qwen3 235B | 128K | Complex reasoning |
|
||||
|
||||
### Mid-Size Models
|
||||
|
||||
| Model ID | Name | Context | Best For |
|
||||
|----------|------|---------|----------|
|
||||
| `llama-3.3-70b` | Llama 3.3 70B | 128K | General purpose |
|
||||
| `qwen3-next-80b` | Qwen3 Next 80B | 256K | Long context |
|
||||
| `mistral-31-24b` | Mistral 31 24B | 128K | Fast, vision |
|
||||
| `venice-uncensored` | Venice Uncensored | 32K | Uncensored, creative |
|
||||
| `hermes-4-14b` | Hermes 4 14B | 128K | Efficient |
|
||||
|
||||
### Fast Models
|
||||
|
||||
| Model ID | Name | Context | Best For |
|
||||
|----------|------|---------|----------|
|
||||
| `llama-3.2-3b` | Llama 3.2 3B | 128K | Fastest responses |
|
||||
| `qwen3-4b` | Qwen3 4B | 32K | Lightweight, reasoning |
|
||||
|
||||
### Web-Enabled Models
|
||||
|
||||
Add `:web` suffix to any model for web search:
|
||||
- `llama-3.3-70b:web`
|
||||
- `kimi-k2-thinking:web`
|
||||
- `qwen3-coder-480b-a35b-instruct:web`
|
||||
|
||||
## Model Discovery
|
||||
|
||||
Clawdbot automatically discovers models from the Morpheus API when `MORPHEUS_API_KEY` is set. If the API is unreachable, it falls back to a static catalog.
|
||||
|
||||
## Streaming & Tool Support
|
||||
|
||||
| Feature | Support |
|
||||
|---------|---------|
|
||||
| **Streaming** | All models |
|
||||
| **Function calling** | Most models |
|
||||
| **Vision/Images** | `mistral-31-24b` |
|
||||
| **JSON mode** | Supported via `response_format` |
|
||||
|
||||
## Pricing
|
||||
|
||||
Morpheus is **FREE during Open Beta** (until 1/31/26). Billing infrastructure will be implemented soon.
|
||||
|
||||
## Config File Example
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { MORPHEUS_API_KEY: "sk-..." },
|
||||
agents: { defaults: { model: { primary: "morpheus/llama-3.3-70b" } } },
|
||||
models: {
|
||||
mode: "merge",
|
||||
providers: {
|
||||
morpheus: {
|
||||
baseUrl: "https://api.mor.org/api/v1",
|
||||
apiKey: "${MORPHEUS_API_KEY}",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: "llama-3.3-70b",
|
||||
name: "Llama 3.3 70B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```bash
|
||||
# Use default model
|
||||
clawdbot chat --model morpheus/llama-3.3-70b
|
||||
|
||||
# Use coding model
|
||||
clawdbot chat --model morpheus/qwen3-coder-480b-a35b-instruct
|
||||
|
||||
# Use reasoning model
|
||||
clawdbot chat --model morpheus/kimi-k2-thinking
|
||||
|
||||
# Use with web search
|
||||
clawdbot chat --model morpheus/llama-3.3-70b:web
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### API key not recognized
|
||||
|
||||
```bash
|
||||
echo $MORPHEUS_API_KEY
|
||||
clawdbot models list | grep morpheus
|
||||
```
|
||||
|
||||
Ensure the key starts with `sk-`.
|
||||
|
||||
### Model not available
|
||||
|
||||
Model availability depends on active providers in the Morpheus marketplace. Run `clawdbot models list` to see currently available models.
|
||||
|
||||
### Connection issues
|
||||
|
||||
Morpheus API is at `https://api.mor.org/api/v1`. Ensure your network allows HTTPS connections.
|
||||
|
||||
## Links
|
||||
|
||||
- [Morpheus API Docs](https://apidocs.mor.org)
|
||||
- [Morpheus App](https://app.mor.org)
|
||||
- [Morpheus Discord](https://discord.gg/kyVaxTHnvB)
|
||||
- [Morpheus Twitter](https://x.com/morpheusais)
|
||||
@ -283,6 +283,7 @@ export function resolveEnvApiKey(provider: string): EnvApiKeyResult | null {
|
||||
minimax: "MINIMAX_API_KEY",
|
||||
synthetic: "SYNTHETIC_API_KEY",
|
||||
venice: "VENICE_API_KEY",
|
||||
morpheus: "MORPHEUS_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 { discoverMorpheusModels, MORPHEUS_BASE_URL } from "./morpheus-models.js";
|
||||
|
||||
type ModelsConfig = NonNullable<MoltbotConfig["models"]>;
|
||||
export type ProviderConfig = NonNullable<ModelsConfig["providers"]>[string];
|
||||
@ -350,6 +351,15 @@ async function buildVeniceProvider(): Promise<ProviderConfig> {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildMorpheusProvider(apiKey?: string): Promise<ProviderConfig> {
|
||||
const models = await discoverMorpheusModels(apiKey);
|
||||
return {
|
||||
baseUrl: MORPHEUS_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models,
|
||||
};
|
||||
}
|
||||
|
||||
async function buildOllamaProvider(): Promise<ProviderConfig> {
|
||||
const models = await discoverOllamaModels();
|
||||
return {
|
||||
@ -402,6 +412,13 @@ export async function resolveImplicitProviders(params: {
|
||||
providers.venice = { ...(await buildVeniceProvider()), apiKey: veniceKey };
|
||||
}
|
||||
|
||||
const morpheusKey =
|
||||
resolveEnvApiKeyVarName("morpheus") ??
|
||||
resolveApiKeyFromProfiles({ provider: "morpheus", store: authStore });
|
||||
if (morpheusKey) {
|
||||
providers.morpheus = { ...(await buildMorpheusProvider(morpheusKey)), apiKey: morpheusKey };
|
||||
}
|
||||
|
||||
const qwenProfiles = listProfilesForProvider(authStore, "qwen-portal");
|
||||
if (qwenProfiles.length > 0) {
|
||||
providers["qwen-portal"] = {
|
||||
|
||||
310
src/agents/morpheus-models.ts
Normal file
310
src/agents/morpheus-models.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import type { ModelDefinitionConfig } from "../config/types.js";
|
||||
|
||||
export const MORPHEUS_BASE_URL = "https://api.mor.org/api/v1";
|
||||
export const MORPHEUS_DEFAULT_MODEL_ID = "llama-3.3-70b";
|
||||
export const MORPHEUS_DEFAULT_MODEL_REF = `morpheus/${MORPHEUS_DEFAULT_MODEL_ID}`;
|
||||
|
||||
// Morpheus is currently FREE during Open Beta (until 1/31/26).
|
||||
// Set costs to 0 as pricing will be implemented later.
|
||||
export const MORPHEUS_DEFAULT_COST = {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete catalog of Morpheus Inference API models.
|
||||
*
|
||||
* Morpheus is a decentralized inference marketplace that provides access to
|
||||
* open-source AI models. The API is fully OpenAI-compatible.
|
||||
*
|
||||
* Model availability depends on active providers in the marketplace.
|
||||
* This catalog serves as a fallback when the Morpheus API is unreachable.
|
||||
*
|
||||
* Models with the `:web` suffix have web search capabilities enabled.
|
||||
*/
|
||||
export const MORPHEUS_MODEL_CATALOG = [
|
||||
// ============================================
|
||||
// FLAGSHIP MODELS
|
||||
// ============================================
|
||||
{
|
||||
id: "qwen3-coder-480b-a35b-instruct",
|
||||
name: "Qwen3 Coder 480B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 256000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Code"],
|
||||
},
|
||||
{
|
||||
id: "hermes-3-llama-3.1-405b",
|
||||
name: "Hermes 3 Llama 3.1 405B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["General"],
|
||||
},
|
||||
{
|
||||
id: "gpt-oss-120b",
|
||||
name: "GPT OSS 120B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["General"],
|
||||
},
|
||||
|
||||
// ============================================
|
||||
// REASONING MODELS
|
||||
// ============================================
|
||||
{
|
||||
id: "kimi-k2-thinking",
|
||||
name: "Kimi K2 Thinking",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 256000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Reasoning", "Code"],
|
||||
},
|
||||
{
|
||||
id: "glm-4.7-thinking",
|
||||
name: "GLM 4.7 Thinking",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 198000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Reasoning"],
|
||||
},
|
||||
{
|
||||
id: "glm-4.7",
|
||||
name: "GLM 4.7",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 198000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Reasoning"],
|
||||
},
|
||||
{
|
||||
id: "qwen3-235b",
|
||||
name: "Qwen3 235B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Reasoning"],
|
||||
},
|
||||
|
||||
// ============================================
|
||||
// MID-SIZE MODELS
|
||||
// ============================================
|
||||
{
|
||||
id: "llama-3.3-70b",
|
||||
name: "Llama 3.3 70B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["General"],
|
||||
},
|
||||
{
|
||||
id: "qwen3-next-80b",
|
||||
name: "Qwen3 Next 80B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 256000,
|
||||
maxTokens: 8192,
|
||||
tags: ["General"],
|
||||
},
|
||||
{
|
||||
id: "mistral-31-24b",
|
||||
name: "Mistral 31 24B",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Vision"],
|
||||
},
|
||||
{
|
||||
id: "venice-uncensored",
|
||||
name: "Venice Uncensored",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 32000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Uncensored"],
|
||||
},
|
||||
{
|
||||
id: "hermes-4-14b",
|
||||
name: "Hermes 4 14B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["General"],
|
||||
},
|
||||
|
||||
// ============================================
|
||||
// FAST MODELS
|
||||
// ============================================
|
||||
{
|
||||
id: "llama-3.2-3b",
|
||||
name: "Llama 3.2 3B",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Fast"],
|
||||
},
|
||||
{
|
||||
id: "qwen3-4b",
|
||||
name: "Qwen3 4B",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 32000,
|
||||
maxTokens: 8192,
|
||||
tags: ["Fast", "Reasoning"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type MorpheusCatalogEntry = (typeof MORPHEUS_MODEL_CATALOG)[number];
|
||||
|
||||
/**
|
||||
* Build a ModelDefinitionConfig from a Morpheus catalog entry.
|
||||
*/
|
||||
export function buildMorpheusModelDefinition(entry: MorpheusCatalogEntry): ModelDefinitionConfig {
|
||||
return {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
reasoning: entry.reasoning,
|
||||
input: [...entry.input],
|
||||
cost: MORPHEUS_DEFAULT_COST,
|
||||
contextWindow: entry.contextWindow,
|
||||
maxTokens: entry.maxTokens,
|
||||
};
|
||||
}
|
||||
|
||||
// Morpheus API response types
|
||||
interface MorpheusModel {
|
||||
id: string;
|
||||
blockchainID: string;
|
||||
created: number;
|
||||
tags: string[];
|
||||
modelType: "LLM" | "TTS" | "STT" | "EMBEDDING";
|
||||
}
|
||||
|
||||
interface MorpheusModelsResponse {
|
||||
object: string;
|
||||
data: MorpheusModel[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer model properties from Morpheus API model data.
|
||||
*/
|
||||
function inferModelProperties(model: MorpheusModel): {
|
||||
reasoning: boolean;
|
||||
input: string[];
|
||||
contextWindow: number;
|
||||
} {
|
||||
const id = model.id.toLowerCase();
|
||||
const tags = model.tags.map((t) => t.toLowerCase());
|
||||
|
||||
// Infer reasoning from model name or tags
|
||||
const reasoning =
|
||||
id.includes("thinking") ||
|
||||
id.includes("reason") ||
|
||||
id.includes("r1") ||
|
||||
tags.includes("reasoning");
|
||||
|
||||
// Infer vision support from tags
|
||||
const hasVision = id.includes("vision") || id.includes("vl-") || id.includes("-vl");
|
||||
const input = hasVision ? ["text", "image"] : ["text"];
|
||||
|
||||
// Infer context window from size tag or model name
|
||||
let contextWindow = 128000;
|
||||
if (id.includes("qwen3-coder") || id.includes("kimi-k2") || id.includes("qwen3-next")) {
|
||||
contextWindow = 256000;
|
||||
} else if (id.includes("glm-4.7")) {
|
||||
contextWindow = 198000;
|
||||
} else if (id.includes("venice-uncensored") || id.includes("qwen3-4b")) {
|
||||
contextWindow = 32000;
|
||||
}
|
||||
|
||||
return { reasoning, input, contextWindow };
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover models from Morpheus API with fallback to static catalog.
|
||||
* The /models endpoint requires authentication.
|
||||
*/
|
||||
export async function discoverMorpheusModels(apiKey?: string): Promise<ModelDefinitionConfig[]> {
|
||||
// Skip API discovery in test environment
|
||||
if (process.env.NODE_ENV === "test" || process.env.VITEST) {
|
||||
return MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
}
|
||||
|
||||
// If no API key provided, use static catalog
|
||||
if (!apiKey?.trim()) {
|
||||
return MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${MORPHEUS_BASE_URL}/models`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(
|
||||
`[morpheus-models] Failed to discover models: HTTP ${response.status}, using static catalog`,
|
||||
);
|
||||
return MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as MorpheusModelsResponse;
|
||||
if (!Array.isArray(data.data) || data.data.length === 0) {
|
||||
console.warn("[morpheus-models] No models found from API, using static catalog");
|
||||
return MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
}
|
||||
|
||||
// Filter to LLM models only and merge with catalog metadata
|
||||
const llmModels = data.data.filter((m) => m.modelType === "LLM");
|
||||
const catalogById = new Map<string, MorpheusCatalogEntry>(
|
||||
MORPHEUS_MODEL_CATALOG.map((m) => [m.id, m]),
|
||||
);
|
||||
const models: ModelDefinitionConfig[] = [];
|
||||
|
||||
for (const apiModel of llmModels) {
|
||||
const catalogEntry = catalogById.get(apiModel.id);
|
||||
if (catalogEntry) {
|
||||
// Use catalog metadata for known models
|
||||
models.push(buildMorpheusModelDefinition(catalogEntry));
|
||||
} else {
|
||||
// Create definition for newly discovered models not in catalog
|
||||
const inferred = inferModelProperties(apiModel);
|
||||
const displayName = apiModel.id
|
||||
.split("-")
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(" ");
|
||||
|
||||
models.push({
|
||||
id: apiModel.id,
|
||||
name: displayName,
|
||||
reasoning: inferred.reasoning,
|
||||
input: inferred.input as ("text" | "image")[],
|
||||
cost: MORPHEUS_DEFAULT_COST,
|
||||
contextWindow: inferred.contextWindow,
|
||||
maxTokens: 8192,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return models.length > 0 ? models : MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
} catch (error) {
|
||||
console.warn(`[morpheus-models] Discovery failed: ${String(error)}, using static catalog`);
|
||||
return MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ export type AuthChoiceGroupId =
|
||||
| "minimax"
|
||||
| "synthetic"
|
||||
| "venice"
|
||||
| "morpheus"
|
||||
| "qwen";
|
||||
|
||||
export type AuthChoiceGroup = {
|
||||
@ -71,6 +72,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
|
||||
hint: "Privacy-focused (uncensored models)",
|
||||
choices: ["venice-api-key"],
|
||||
},
|
||||
{
|
||||
value: "morpheus",
|
||||
label: "Morpheus",
|
||||
hint: "Decentralized inference (FREE beta)",
|
||||
choices: ["morpheus-api-key"],
|
||||
},
|
||||
{
|
||||
value: "google",
|
||||
label: "Google",
|
||||
@ -147,6 +154,11 @@ export function buildAuthChoiceOptions(params: {
|
||||
label: "Venice AI API key",
|
||||
hint: "Privacy-focused inference (uncensored models)",
|
||||
});
|
||||
options.push({
|
||||
value: "morpheus-api-key",
|
||||
label: "Morpheus API key",
|
||||
hint: "Decentralized inference (FREE during beta)",
|
||||
});
|
||||
options.push({
|
||||
value: "github-copilot",
|
||||
label: "GitHub Copilot (GitHub device login)",
|
||||
|
||||
@ -25,6 +25,8 @@ import {
|
||||
applySyntheticProviderConfig,
|
||||
applyVeniceConfig,
|
||||
applyVeniceProviderConfig,
|
||||
applyMorpheusConfig,
|
||||
applyMorpheusProviderConfig,
|
||||
applyVercelAiGatewayConfig,
|
||||
applyVercelAiGatewayProviderConfig,
|
||||
applyZaiConfig,
|
||||
@ -33,6 +35,7 @@ import {
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
SYNTHETIC_DEFAULT_MODEL_REF,
|
||||
VENICE_DEFAULT_MODEL_REF,
|
||||
MORPHEUS_DEFAULT_MODEL_REF,
|
||||
VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF,
|
||||
setGeminiApiKey,
|
||||
setKimiCodeApiKey,
|
||||
@ -41,6 +44,7 @@ import {
|
||||
setOpenrouterApiKey,
|
||||
setSyntheticApiKey,
|
||||
setVeniceApiKey,
|
||||
setMorpheusApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setZaiApiKey,
|
||||
ZAI_DEFAULT_MODEL_REF,
|
||||
@ -83,6 +87,8 @@ export async function applyAuthChoiceApiProviders(
|
||||
authChoice = "synthetic-api-key";
|
||||
} else if (params.opts.tokenProvider === "venice") {
|
||||
authChoice = "venice-api-key";
|
||||
} else if (params.opts.tokenProvider === "morpheus") {
|
||||
authChoice = "morpheus-api-key";
|
||||
} else if (params.opts.tokenProvider === "opencode") {
|
||||
authChoice = "opencode-zen";
|
||||
}
|
||||
@ -522,6 +528,65 @@ export async function applyAuthChoiceApiProviders(
|
||||
return { config: nextConfig, agentModelOverride };
|
||||
}
|
||||
|
||||
if (authChoice === "morpheus-api-key") {
|
||||
let hasCredential = false;
|
||||
|
||||
if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "morpheus") {
|
||||
await setMorpheusApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
|
||||
if (!hasCredential) {
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Morpheus provides decentralized AI inference via the Morpheus Network.",
|
||||
"Get your API key at: https://app.mor.org",
|
||||
"Currently FREE during Open Beta (until 1/31/26).",
|
||||
].join("\n"),
|
||||
"Morpheus Inference",
|
||||
);
|
||||
}
|
||||
|
||||
const envKey = resolveEnvApiKey("morpheus");
|
||||
if (envKey) {
|
||||
const useExisting = await params.prompter.confirm({
|
||||
message: `Use existing MORPHEUS_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
|
||||
initialValue: true,
|
||||
});
|
||||
if (useExisting) {
|
||||
await setMorpheusApiKey(envKey.apiKey, params.agentDir);
|
||||
hasCredential = true;
|
||||
}
|
||||
}
|
||||
if (!hasCredential) {
|
||||
const key = await params.prompter.text({
|
||||
message: "Enter Morpheus API key",
|
||||
validate: validateApiKeyInput,
|
||||
});
|
||||
await setMorpheusApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
|
||||
}
|
||||
nextConfig = applyAuthProfileConfig(nextConfig, {
|
||||
profileId: "morpheus:default",
|
||||
provider: "morpheus",
|
||||
mode: "api_key",
|
||||
});
|
||||
{
|
||||
const applied = await applyDefaultModelChoice({
|
||||
config: nextConfig,
|
||||
setDefaultModel: params.setDefaultModel,
|
||||
defaultModel: MORPHEUS_DEFAULT_MODEL_REF,
|
||||
applyDefaultConfig: applyMorpheusConfig,
|
||||
applyProviderConfig: applyMorpheusProviderConfig,
|
||||
noteDefault: MORPHEUS_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") {
|
||||
|
||||
@ -10,6 +10,12 @@ import {
|
||||
VENICE_DEFAULT_MODEL_REF,
|
||||
VENICE_MODEL_CATALOG,
|
||||
} from "../agents/venice-models.js";
|
||||
import {
|
||||
buildMorpheusModelDefinition,
|
||||
MORPHEUS_BASE_URL,
|
||||
MORPHEUS_DEFAULT_MODEL_REF,
|
||||
MORPHEUS_MODEL_CATALOG,
|
||||
} from "../agents/morpheus-models.js";
|
||||
import type { MoltbotConfig } from "../config/config.js";
|
||||
import {
|
||||
OPENROUTER_DEFAULT_MODEL_REF,
|
||||
@ -411,6 +417,75 @@ export function applyVeniceConfig(cfg: MoltbotConfig): MoltbotConfig {
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMorpheusProviderConfig(cfg: MoltbotConfig): MoltbotConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[MORPHEUS_DEFAULT_MODEL_REF] = {
|
||||
...models[MORPHEUS_DEFAULT_MODEL_REF],
|
||||
alias: models[MORPHEUS_DEFAULT_MODEL_REF]?.alias ?? "Llama 3.3 70B",
|
||||
};
|
||||
|
||||
const providers = { ...cfg.models?.providers };
|
||||
const existingProvider = providers.morpheus;
|
||||
const existingModels = Array.isArray(existingProvider?.models) ? existingProvider.models : [];
|
||||
const morpheusModels = MORPHEUS_MODEL_CATALOG.map(buildMorpheusModelDefinition);
|
||||
const mergedModels = [
|
||||
...existingModels,
|
||||
...morpheusModels.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();
|
||||
providers.morpheus = {
|
||||
...existingProviderRest,
|
||||
baseUrl: MORPHEUS_BASE_URL,
|
||||
api: "openai-completions",
|
||||
...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
|
||||
models: mergedModels.length > 0 ? mergedModels : morpheusModels,
|
||||
};
|
||||
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
},
|
||||
},
|
||||
models: {
|
||||
mode: cfg.models?.mode ?? "merge",
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyMorpheusConfig(cfg: MoltbotConfig): MoltbotConfig {
|
||||
const next = applyMorpheusProviderConfig(cfg);
|
||||
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: MORPHEUS_DEFAULT_MODEL_REF,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applyAuthProfileConfig(
|
||||
cfg: MoltbotConfig,
|
||||
params: {
|
||||
|
||||
@ -100,7 +100,6 @@ export async function setSyntheticApiKey(key: string, agentDir?: string) {
|
||||
}
|
||||
|
||||
export async function setVeniceApiKey(key: string, agentDir?: string) {
|
||||
// Write to resolved agent dir so gateway finds credentials on startup.
|
||||
upsertAuthProfile({
|
||||
profileId: "venice:default",
|
||||
credential: {
|
||||
@ -112,6 +111,20 @@ export async function setVeniceApiKey(key: string, agentDir?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function setMorpheusApiKey(key: string, agentDir?: string) {
|
||||
upsertAuthProfile({
|
||||
profileId: "morpheus:default",
|
||||
credential: {
|
||||
type: "api_key",
|
||||
provider: "morpheus",
|
||||
key,
|
||||
},
|
||||
agentDir: resolveAuthAgentDir(agentDir),
|
||||
});
|
||||
}
|
||||
|
||||
export const MORPHEUS_DEFAULT_MODEL_REF = "morpheus/llama-3.3-70b";
|
||||
|
||||
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
||||
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
|
||||
export const VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF = "vercel-ai-gateway/anthropic/claude-opus-4.5";
|
||||
|
||||
@ -3,6 +3,10 @@ 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 {
|
||||
MORPHEUS_DEFAULT_MODEL_ID,
|
||||
MORPHEUS_DEFAULT_MODEL_REF,
|
||||
} from "../agents/morpheus-models.js";
|
||||
export {
|
||||
applyAuthProfileConfig,
|
||||
applyKimiCodeConfig,
|
||||
@ -15,6 +19,8 @@ export {
|
||||
applySyntheticProviderConfig,
|
||||
applyVeniceConfig,
|
||||
applyVeniceProviderConfig,
|
||||
applyMorpheusConfig,
|
||||
applyMorpheusProviderConfig,
|
||||
applyVercelAiGatewayConfig,
|
||||
applyVercelAiGatewayProviderConfig,
|
||||
applyZaiConfig,
|
||||
@ -43,6 +49,7 @@ export {
|
||||
setOpenrouterApiKey,
|
||||
setSyntheticApiKey,
|
||||
setVeniceApiKey,
|
||||
setMorpheusApiKey,
|
||||
setVercelAiGatewayApiKey,
|
||||
setZaiApiKey,
|
||||
writeOAuthCredentials,
|
||||
|
||||
@ -17,6 +17,7 @@ export type AuthChoice =
|
||||
| "kimi-code-api-key"
|
||||
| "synthetic-api-key"
|
||||
| "venice-api-key"
|
||||
| "morpheus-api-key"
|
||||
| "codex-cli"
|
||||
| "apiKey"
|
||||
| "gemini-api-key"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user