Merge pull request #17 from Molecule-AI/feat/xiaomi-mimo-anthropic-compat

feat: add Xiaomi MiMo support (testing — entrypoint-shell mapping)
This commit is contained in:
hongmingwang-moleculeai 2026-04-29 17:13:23 -07:00 committed by GitHub
commit 14f27b7886
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 163 additions and 12 deletions

View File

@ -17,6 +17,17 @@ github://Molecule-AI/template-claude-code-default
- `config.yaml` — workspace configuration (runtime, model, skills, etc.)
- `system-prompt.md` — agent system prompt (if present)
## Auth paths
| Path | Env var(s) | Where to get the key |
|---|---|---|
| OAuth (Claude Code subscription) | `CLAUDE_CODE_OAUTH_TOKEN` | `claude login` |
| Anthropic API (direct) | `ANTHROPIC_API_KEY` | console.anthropic.com |
| Third-party Anthropic-compat (e.g. Xiaomi MiMo pay-as-you-go) | `ANTHROPIC_API_KEY` (provider's key) | provider console |
| Xiaomi MiMo Token Plan | `ANTHROPIC_API_KEY` (Token Plan key), `ANTHROPIC_BASE_URL` (Token Plan endpoint) | token-plan dashboard |
For third-party providers, `entrypoint.sh` rewrites `ANTHROPIC_BASE_URL` based on the selected `MODEL` so the `claude` CLI routes there. Currently auto-routes `mimo-*` models to `https://api.xiaomimimo.com/anthropic` (pay-as-you-go). **Token Plan users** should set `ANTHROPIC_BASE_URL=https://token-plan-sgp.xiaomimimo.com/anthropic` as a workspace or org-level secret — the shell mapping is the fallback and operator-set values always win. Other Token Plan endpoints (e.g. `token-plan-hk.xiaomimimo.com`) can be used by setting the secret explicitly.
## Schema version
`template_schema_version: 1` — compatible with Molecule AI platform v1.x.

View File

@ -4,6 +4,7 @@ import json
import os
import logging
from pathlib import Path
from urllib.parse import urlparse
from molecule_runtime.adapters.base import BaseAdapter, AdapterConfig, RuntimeCapabilities
from a2a.server.agent_execution import AgentExecutor
@ -14,6 +15,47 @@ logger = logging.getLogger(__name__)
# the workspace by polling /transcript?limit=999999.
_TRANSCRIPT_MAX_LIMIT = 1000
# Auth-mode classification for a selected model id. The Claude Code CLI
# accepts three auth paths and the right env var differs per path; warning
# at boot about the wrong var (the pre-multi-provider behavior) misled
# operators who picked an API-key or third-party model. New third-party
# providers add a prefix → mode entry below + a model-prefix → base-URL
# mapping in entrypoint.sh until the data-driven `runtime_env` schema
# field lands platform-side.
_AUTH_MODE_OAUTH = "oauth"
_AUTH_MODE_ANTHROPIC_API = "anthropic_api"
_AUTH_MODE_THIRD_PARTY = "third_party_anthropic_compat"
_THIRD_PARTY_PREFIXES = ("mimo-",)
_OAUTH_ALIASES = frozenset({"sonnet", "opus", "haiku"})
def _detect_auth_mode(model: str) -> str:
"""Classify the picked model into one of three auth paths.
Used by setup() to validate the right env var is set so operators see
the misconfiguration at boot instead of on the first LLM call.
Unknown ids default to OAuth the historical default and the safest
fallback for the warning path.
"""
if not model:
return _AUTH_MODE_OAUTH
m = model.lower()
if any(m.startswith(p) for p in _THIRD_PARTY_PREFIXES):
return _AUTH_MODE_THIRD_PARTY
if m.startswith("claude-"):
return _AUTH_MODE_ANTHROPIC_API
if m in _OAUTH_ALIASES:
return _AUTH_MODE_OAUTH
return _AUTH_MODE_OAUTH
def _required_env_for_mode(mode: str) -> str:
"""The env var the claude CLI needs to authenticate for a given mode."""
if mode == _AUTH_MODE_OAUTH:
return "CLAUDE_CODE_OAUTH_TOKEN"
return "ANTHROPIC_API_KEY"
class ClaudeCodeAdapter(BaseAdapter):
@ -94,15 +136,60 @@ class ClaudeCodeAdapter(BaseAdapter):
``CLAUDE.md`` and ``/configs/skills/`` natively, and the default
:class:`AgentskillsAdaptor` writes to both.
"""
# KI-001 fix: warn immediately if CLAUDE_CODE_OAUTH_TOKEN is absent so
# operators see the problem at startup instead of a silent
# AuthenticationError on the first LLM call.
if not os.environ.get("CLAUDE_CODE_OAUTH_TOKEN"):
# KI-001 fix, generalized for the three auth paths the CLI supports:
# OAuth (CLAUDE_CODE_OAUTH_TOKEN), Anthropic API (ANTHROPIC_API_KEY),
# and third-party Anthropic-API-compat (ANTHROPIC_API_KEY + provider
# ANTHROPIC_BASE_URL). Detect the path from the picked model so the
# warning targets the *right* env var — the pre-multi-provider code
# always warned about CLAUDE_CODE_OAUTH_TOKEN even when the user had
# legitimately picked an API-key model and set ANTHROPIC_API_KEY.
rc = config.runtime_config
if isinstance(rc, dict):
picked_model = rc.get("model") or "sonnet"
else:
picked_model = getattr(rc, "model", None) or "sonnet"
auth_mode = _detect_auth_mode(picked_model)
required_var = _required_env_for_mode(auth_mode)
# Single-line startup banner — operators reading boot logs can see
# which provider path was selected and whether ANTHROPIC_BASE_URL
# (set by entrypoint.sh for third-party mimo-*) took effect. URL is
# logged as host-only; defensive against credential-shaped query
# strings even though base_url shouldn't carry one.
base_url = os.environ.get("ANTHROPIC_BASE_URL")
base_url_host = ""
if base_url:
try:
base_url_host = urlparse(base_url).netloc or "<unparseable>"
except Exception:
base_url_host = "<unparseable>"
logger.info(
"Claude Code adapter starting: model=%s auth_mode=%s required_env=%s%s",
picked_model, auth_mode, required_var,
f" base_url_host={base_url_host}" if base_url_host else "",
)
if not os.environ.get(required_var):
logger.warning(
"CLAUDE_CODE_OAUTH_TOKEN is not set — the adapter will fail on the "
"first LLM call with an AuthenticationError. Set the env var or "
"configure an API key in your platform workspace settings."
"%s is not set for model=%s (auth_mode=%s) — the adapter will fail "
"on the first LLM call with an AuthenticationError. Set the env "
"var or configure the key in your platform workspace settings.",
required_var, picked_model, auth_mode,
)
# Third-party paths additionally need ANTHROPIC_BASE_URL; entrypoint.sh
# sets it for known mimo-* prefixes. Surface the missing-base-URL
# case explicitly — the symptom otherwise is the CLI silently hitting
# api.anthropic.com with a third-party key, which 401s.
if auth_mode == _AUTH_MODE_THIRD_PARTY and not base_url:
logger.warning(
"model=%s is a third-party Anthropic-compat model but "
"ANTHROPIC_BASE_URL is unset — requests will land on the real "
"api.anthropic.com and fail with 401. Check entrypoint.sh's "
"model→base-URL mapping or set ANTHROPIC_BASE_URL via secrets.",
picked_model,
)
from molecule_runtime.plugins import load_plugins
workspace_plugins_dir = os.path.join(config.config_path, "plugins")
plugins = load_plugins(

View File

@ -1,11 +1,13 @@
name: Claude Code Agent
description: >-
General-purpose Claude Code workspace. Supports two auth paths:
General-purpose Claude Code workspace. Supports three auth paths:
(1) Claude Code subscription via OAuth token (CLAUDE_CODE_OAUTH_TOKEN,
obtained from `claude login`), or (2) Anthropic API key
(ANTHROPIC_API_KEY, pay-as-you-go via console.anthropic.com). The
`claude` CLI picks whichever is set; OAuth takes precedence when both
are present.
obtained from `claude login`), (2) Anthropic API key
(ANTHROPIC_API_KEY, pay-as-you-go via console.anthropic.com), or
(3) third-party Anthropic-API-compatible providers (e.g. Xiaomi MiMo)
via ANTHROPIC_API_KEY + provider-specific ANTHROPIC_BASE_URL routing.
The `claude` CLI picks whichever is set; OAuth takes precedence when
multiple are present.
version: 1.0.0
tier: 2
@ -43,6 +45,26 @@ runtime_config:
name: Claude Haiku 4.5 (API key / Anthropic Console)
required_env: [ANTHROPIC_API_KEY]
# --- Xiaomi MiMo (third-party, Anthropic-API-compatible) — set ANTHROPIC_API_KEY ---
# Routed through https://api.xiaomimimo.com/anthropic via ANTHROPIC_BASE_URL
# (the claude CLI honors the env var natively). Mapping lives in
# entrypoint.sh — when MODEL matches mimo-*, base URL is rewritten before
# the runtime starts. The user's ANTHROPIC_API_KEY here is a Xiaomi key,
# not an Anthropic Console key. Long-term, this should move to a
# data-driven `runtime_env` schema field; tracked separately.
- id: mimo-v2-flash
name: Xiaomi MiMo V2 Flash (third-party, Anthropic-API-compatible)
required_env: [ANTHROPIC_API_KEY]
- id: mimo-v2-pro
name: Xiaomi MiMo V2 Pro (third-party, Anthropic-API-compatible)
required_env: [ANTHROPIC_API_KEY]
- id: mimo-v2-omni
name: Xiaomi MiMo V2 Omni (third-party, Anthropic-API-compatible)
required_env: [ANTHROPIC_API_KEY]
- id: mimo-v2.5-pro
name: Xiaomi MiMo V2.5 Pro (third-party, Anthropic-API-compatible)
required_env: [ANTHROPIC_API_KEY]
# Default required_env — per-model entries above override this once a
# model is picked. Keep CLAUDE_CODE_OAUTH_TOKEN as the default so
# existing workspaces (which all use OAuth) keep working unchanged.

View File

@ -81,4 +81,35 @@ elif [ -n "${GH_TOKEN:-}" ]; then
echo "${GH_TOKEN}" | gh auth login --hostname github.com --with-token 2>/dev/null || true
fi
# Third-party Anthropic-API-compatible provider routing.
# The `claude` CLI honors ANTHROPIC_BASE_URL natively; we rewrite it
# based on MODEL so a Xiaomi MiMo selection lands on Xiaomi's endpoint
# without code changes inside the SDK. ANTHROPIC_API_KEY in this case
# is the third-party provider key, not an Anthropic Console key.
#
# Refuses to clobber an operator-set ANTHROPIC_BASE_URL — if the user
# provided one explicitly via secrets (e.g. a Xiaomi MiMo Token Plan
# endpoint such as https://token-plan-sgp.xiaomimimo.com/anthropic),
# that wins. The mapping below is only the fallback for known model
# prefixes.
#
# Supported Xiaomi MiMo endpoints:
# - Pay-as-you-go: https://api.xiaomimimo.com/anthropic
# - Token Plan SG: https://token-plan-sgp.xiaomimimo.com/anthropic
# - Token Plan HK: https://token-plan-hk.xiaomimimo.com/anthropic
# (Set ANTHROPIC_BASE_URL explicitly to use a specific endpoint.)
#
# Long-term this should move to a data-driven `runtime_env` field in
# config.yaml read by the platform provisioner; tracked separately.
case "${MODEL:-}" in
mimo-*)
if [ -z "${ANTHROPIC_BASE_URL:-}" ]; then
export ANTHROPIC_BASE_URL="https://api.xiaomimimo.com/anthropic"
echo "[entrypoint] MODEL=${MODEL} → ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL}" >&2
else
echo "[entrypoint] MODEL=${MODEL} but ANTHROPIC_BASE_URL already set; not overriding" >&2
fi
;;
esac
exec molecule-runtime "$@"