openclaw/src/agents/morpheus-models.ts
bowtiedbluefin fa4c1245a4 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)
2026-01-27 13:29:48 -05:00

311 lines
8.3 KiB
TypeScript

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);
}
}