diff --git a/.aurelio/providers/dist/dirac.providers.json b/.aurelio/providers/dist/dirac.providers.json new file mode 100644 index 00000000..cba8cc9f --- /dev/null +++ b/.aurelio/providers/dist/dirac.providers.json @@ -0,0 +1,80 @@ +{ + "list": [ + { + "value": "openrouter", + "label": "OpenRouter" + }, + { + "value": "openai-codex", + "label": "ChatGPT Subscription (Codex OAuth)" + }, + { + "value": "nous", + "label": "Nous Portal" + }, + { + "value": "zai", + "label": "Z.AI / GLM" + }, + { + "value": "kimi-coding", + "label": "Kimi for Coding" + }, + { + "value": "kimi-coding-cn", + "label": "Kimi for Coding (China)" + }, + { + "value": "minimax", + "label": "MiniMax" + }, + { + "value": "minimax-cn", + "label": "MiniMax (China)" + }, + { + "value": "bedrock", + "label": "Amazon Bedrock" + }, + { + "value": "openai", + "label": "OpenAI" + }, + { + "value": "anthropic", + "label": "Anthropic" + }, + { + "value": "gemini", + "label": "Google Gemini" + }, + { + "value": "moonshot", + "label": "Moonshot" + }, + { + "value": "qwen", + "label": "Alibaba Qwen (DashScope)" + }, + { + "value": "qwen-code", + "label": "Qwen Code (Alibaba Cloud)" + }, + { + "value": "claude-code", + "label": "Claude Code" + }, + { + "value": "antigravity", + "label": "Antigravity CLI" + }, + { + "value": "mimocode", + "label": "Xiaomi MiMo Code" + }, + { + "value": "local", + "label": "Local (Ollama / sentence-transformers)" + } + ] +} diff --git a/.aurelio/providers/dist/mcp.providers.json b/.aurelio/providers/dist/mcp.providers.json new file mode 100644 index 00000000..f0880ec3 --- /dev/null +++ b/.aurelio/providers/dist/mcp.providers.json @@ -0,0 +1,255 @@ +{ + "version": 1, + "chatFallback": [ + "kimi-coding", + "qwen-code", + "gemini", + "anthropic", + "openai", + "minimax", + "zai", + "openrouter" + ], + "embedFallback": [ + "gemini", + "openai", + "qwen", + "local" + ], + "providers": [ + { + "id": "openrouter", + "label": "OpenRouter", + "auth": "api_key", + "keyEnv": "OPENROUTER_API_KEY", + "baseUrl": "https://openrouter.ai/api/v1", + "protocol": "openai", + "chatModel": "anthropic/claude-sonnet-4", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true + }, + { + "id": "openai-codex", + "label": "ChatGPT Subscription (Codex OAuth)", + "auth": "oauth", + "keyEnv": null, + "baseUrl": "https://chatgpt.com/backend-api/codex", + "protocol": "openai", + "chatModel": "gpt-5-codex", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true + }, + { + "id": "nous", + "label": "Nous Portal", + "auth": "oauth", + "keyEnv": null, + "baseUrl": "https://inference-api.nousresearch.com/v1", + "protocol": "openai", + "chatModel": "Hermes-4-70B", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true + }, + { + "id": "zai", + "label": "Z.AI / GLM", + "auth": "api_key", + "keyEnv": "ZAI_API_KEY", + "baseUrl": "https://api.z.ai/api/paas/v4", + "protocol": "openai", + "chatModel": "glm-4.6", + "embeddingModel": null, + "maxContext": 128000, + "streaming": true, + "fallback": true + }, + { + "id": "kimi-coding", + "label": "Kimi for Coding", + "auth": "api_key", + "keyEnv": "KIMI_API_KEY", + "baseUrl": "https://api.moonshot.ai/v1", + "protocol": "openai", + "chatModel": "kimi-k2-0905-preview", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true + }, + { + "id": "kimi-coding-cn", + "label": "Kimi for Coding (China)", + "auth": "api_key", + "keyEnv": "KIMI_CN_API_KEY", + "baseUrl": "https://api.moonshot.cn/v1", + "protocol": "openai", + "chatModel": "kimi-k2-0905-preview", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true + }, + { + "id": "minimax", + "label": "MiniMax", + "auth": "api_key", + "keyEnv": "MINIMAX_API_KEY", + "baseUrl": "https://api.minimax.io/v1", + "protocol": "openai", + "chatModel": "MiniMax-M2", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true + }, + { + "id": "minimax-cn", + "label": "MiniMax (China)", + "auth": "api_key", + "keyEnv": "MINIMAX_CN_API_KEY", + "baseUrl": "https://api.minimaxi.com/v1", + "protocol": "openai", + "chatModel": "MiniMax-M2", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true + }, + { + "id": "bedrock", + "label": "Amazon Bedrock", + "auth": "iam", + "keyEnv": null, + "baseUrl": "", + "protocol": "bedrock-converse", + "chatModel": "anthropic.claude-sonnet-4-20250514-v1:0", + "embeddingModel": "amazon.titan-embed-text-v2:0", + "maxContext": 200000, + "streaming": true, + "fallback": true + }, + { + "id": "openai", + "label": "OpenAI", + "auth": "api_key", + "keyEnv": "OPENAI_API_KEY", + "baseUrl": "https://api.openai.com/v1", + "protocol": "openai", + "chatModel": "gpt-4o", + "embeddingModel": "text-embedding-3-large", + "maxContext": 128000, + "streaming": true, + "fallback": true + }, + { + "id": "anthropic", + "label": "Anthropic", + "auth": "api_key", + "keyEnv": "ANTHROPIC_API_KEY", + "baseUrl": "https://api.anthropic.com/v1", + "protocol": "anthropic", + "chatModel": "claude-sonnet-4-20250514", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true + }, + { + "id": "gemini", + "label": "Google Gemini", + "auth": "api_key", + "keyEnv": "GEMINI_API_KEY", + "baseUrl": "https://generativelanguage.googleapis.com/v1beta", + "protocol": "gemini", + "chatModel": "gemini-2.5-pro", + "embeddingModel": "text-embedding-004", + "maxContext": 1000000, + "streaming": true, + "fallback": true + }, + { + "id": "moonshot", + "label": "Moonshot", + "auth": "api_key", + "keyEnv": "MOONSHOT_API_KEY", + "baseUrl": "https://api.moonshot.ai/v1", + "protocol": "openai", + "chatModel": "moonshot-v1-128k", + "embeddingModel": null, + "maxContext": 128000, + "streaming": true, + "fallback": true + }, + { + "id": "qwen", + "label": "Alibaba Qwen (DashScope)", + "auth": "api_key", + "keyEnv": "DASHSCOPE_API_KEY", + "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "protocol": "openai", + "chatModel": "qwen-max", + "embeddingModel": "text-embedding-v3", + "maxContext": 131072, + "streaming": true, + "fallback": true + }, + { + "id": "qwen-code", + "label": "Qwen Code (Alibaba Cloud)", + "auth": "api_key", + "keyEnv": "DASHSCOPE_API_KEY", + "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "protocol": "openai", + "chatModel": "qwen3-coder-plus", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true + }, + { + "id": "alibaba-cloud", + "label": "Alibaba Cloud (Model Studio)", + "auth": "api_key", + "keyEnv": "ALIBABA_CLOUD_API_KEY", + "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "protocol": "openai", + "chatModel": "qwen-max", + "embeddingModel": "text-embedding-v3", + "maxContext": 131072, + "streaming": true, + "fallback": true + }, + { + "id": "antigravity", + "label": "Antigravity CLI", + "auth": "oauth", + "keyEnv": "ANTIGRAVITY_API_KEY", + "baseUrl": "https://antigravity.portugalfuturista.org", + "protocol": "portal", + "chatModel": "antigravity-default", + "embeddingModel": null, + "maxContext": 200000, + "streaming": false, + "fallback": true + }, + { + "id": "local", + "label": "Local (Ollama / sentence-transformers)", + "auth": "none", + "keyEnv": null, + "baseUrl": "http://127.0.0.1:11434", + "protocol": "openai", + "chatModel": "qwen2.5-coder:14b", + "embeddingModel": "all-MiniLM-L6-v2", + "maxContext": 32768, + "streaming": true, + "fallback": true + } + ] +} diff --git a/.aurelio/providers/dist/portal.providers.json b/.aurelio/providers/dist/portal.providers.json new file mode 100644 index 00000000..3d166258 --- /dev/null +++ b/.aurelio/providers/dist/portal.providers.json @@ -0,0 +1,316 @@ +{ + "version": 1, + "generatedFrom": ".aurelio/providers/registry.yaml", + "fallback": [ + "kimi-coding", + "qwen-code", + "gemini", + "anthropic", + "openai", + "minimax", + "zai", + "openrouter" + ], + "providers": [ + { + "id": "openrouter", + "label": "OpenRouter", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "OPENROUTER_API_KEY", + "baseUrl": "https://openrouter.ai/api/v1", + "defaultModel": "anthropic/claude-sonnet-4", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true, + "notes": "Meta-router; reaches any upstream model." + }, + { + "id": "openai-codex", + "label": "ChatGPT Subscription (Codex OAuth)", + "kind": "oauth", + "protocol": "openai", + "auth": "oauth", + "keyEnv": null, + "baseUrl": "https://chatgpt.com/backend-api/codex", + "defaultModel": "gpt-5-codex", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true, + "notes": "OAuth via `hermes auth`. No API key." + }, + { + "id": "nous", + "label": "Nous Portal", + "kind": "oauth", + "protocol": "openai", + "auth": "oauth", + "keyEnv": null, + "baseUrl": "https://inference-api.nousresearch.com/v1", + "defaultModel": "Hermes-4-70B", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true, + "notes": "NousResearch OAuth subscription." + }, + { + "id": "zai", + "label": "Z.AI / GLM", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "ZAI_API_KEY", + "baseUrl": "https://api.z.ai/api/paas/v4", + "defaultModel": "glm-4.6", + "embeddingModel": null, + "maxContext": 128000, + "streaming": true, + "fallback": true, + "notes": "Z.AI (GLM) OpenAI-compatible." + }, + { + "id": "kimi-coding", + "label": "Kimi for Coding", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "KIMI_API_KEY", + "baseUrl": "https://api.moonshot.ai/v1", + "defaultModel": "kimi-k2-0905-preview", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true, + "notes": "Moonshot Kimi; alias of the existing `kimi`/`moonshot` ids in Dirac." + }, + { + "id": "kimi-coding-cn", + "label": "Kimi for Coding (China)", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "KIMI_CN_API_KEY", + "baseUrl": "https://api.moonshot.cn/v1", + "defaultModel": "kimi-k2-0905-preview", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true, + "notes": "China-region Moonshot endpoint." + }, + { + "id": "minimax", + "label": "MiniMax", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "MINIMAX_API_KEY", + "baseUrl": "https://api.minimax.io/v1", + "defaultModel": "MiniMax-M2", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true, + "notes": "" + }, + { + "id": "minimax-cn", + "label": "MiniMax (China)", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "MINIMAX_CN_API_KEY", + "baseUrl": "https://api.minimaxi.com/v1", + "defaultModel": "MiniMax-M2", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true, + "notes": "" + }, + { + "id": "bedrock", + "label": "Amazon Bedrock", + "kind": "api", + "protocol": "bedrock-converse", + "auth": "iam", + "keyEnv": null, + "baseUrl": null, + "defaultModel": "anthropic.claude-sonnet-4-20250514-v1:0", + "embeddingModel": "amazon.titan-embed-text-v2:0", + "maxContext": 200000, + "streaming": true, + "fallback": true, + "notes": "Uses boto3/IAM, not an API key. Python router shells to boto3." + }, + { + "id": "openai", + "label": "OpenAI", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "OPENAI_API_KEY", + "baseUrl": "https://api.openai.com/v1", + "defaultModel": "gpt-4o", + "embeddingModel": "text-embedding-3-large", + "maxContext": 128000, + "streaming": true, + "fallback": true, + "notes": "" + }, + { + "id": "anthropic", + "label": "Anthropic", + "kind": "api", + "protocol": "anthropic", + "auth": "api_key", + "keyEnv": "ANTHROPIC_API_KEY", + "baseUrl": "https://api.anthropic.com/v1", + "defaultModel": "claude-sonnet-4-20250514", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": true, + "notes": "" + }, + { + "id": "gemini", + "label": "Google Gemini", + "kind": "api", + "protocol": "gemini", + "auth": "api_key", + "keyEnv": "GEMINI_API_KEY", + "baseUrl": "https://generativelanguage.googleapis.com/v1beta", + "defaultModel": "gemini-2.5-pro", + "embeddingModel": "text-embedding-004", + "maxContext": 1000000, + "streaming": true, + "fallback": true, + "notes": "" + }, + { + "id": "moonshot", + "label": "Moonshot", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "MOONSHOT_API_KEY", + "baseUrl": "https://api.moonshot.ai/v1", + "defaultModel": "moonshot-v1-128k", + "embeddingModel": null, + "maxContext": 128000, + "streaming": true, + "fallback": true, + "notes": "Distinct Dirac id from kimi-coding; kept for backwards compatibility." + }, + { + "id": "qwen", + "label": "Alibaba Qwen (DashScope)", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "DASHSCOPE_API_KEY", + "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "defaultModel": "qwen-max", + "embeddingModel": "text-embedding-v3", + "maxContext": 131072, + "streaming": true, + "fallback": true, + "notes": "Alibaba Cloud Model Studio (DashScope) OpenAI-compatible endpoint." + }, + { + "id": "qwen-code", + "label": "Qwen Code (Alibaba Cloud)", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "DASHSCOPE_API_KEY", + "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "defaultModel": "qwen3-coder-plus", + "embeddingModel": null, + "maxContext": 131072, + "streaming": true, + "fallback": true, + "notes": "Qwen Code CLI default model via DashScope; shares DASHSCOPE_API_KEY." + }, + { + "id": "alibaba-cloud", + "label": "Alibaba Cloud (Model Studio)", + "kind": "api", + "protocol": "openai", + "auth": "api_key", + "keyEnv": "ALIBABA_CLOUD_API_KEY", + "baseUrl": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + "defaultModel": "qwen-max", + "embeddingModel": "text-embedding-v3", + "maxContext": 131072, + "streaming": true, + "fallback": true, + "notes": "Generic Alibaba Cloud Model Studio umbrella (intl endpoint). Qwen/Qwen-Code are the concrete aliases; this entry exists so the portal can expose a single \"Alibaba Cloud\" offering." + }, + { + "id": "claude-code", + "label": "Claude Code", + "kind": "cli-subprocess", + "protocol": "subprocess", + "auth": "oauth", + "keyEnv": null, + "baseUrl": null, + "defaultModel": "claude-sonnet-4-20250514", + "embeddingModel": null, + "maxContext": 200000, + "streaming": true, + "fallback": false, + "notes": "Invokes the `claude` CLI; inherits its logged-in OAuth session." + }, + { + "id": "antigravity", + "label": "Antigravity CLI", + "kind": "cli-subprocess", + "protocol": "portal", + "auth": "oauth", + "keyEnv": "ANTIGRAVITY_API_KEY", + "baseUrl": "https://antigravity.portugalfuturista.org", + "defaultModel": "antigravity-default", + "embeddingModel": null, + "maxContext": 200000, + "streaming": false, + "fallback": true, + "notes": "Google Antigravity. Local CLI at ~/.gemini/antigravity-cli; portal endpoint for the hosted relay." + }, + { + "id": "mimocode", + "label": "Xiaomi MiMo Code", + "kind": "cli-subprocess", + "protocol": "subprocess", + "auth": "api_key", + "keyEnv": "MIMOCODE_API_KEY", + "baseUrl": null, + "defaultModel": "mimocoder", + "embeddingModel": null, + "maxContext": 131072, + "streaming": false, + "fallback": false, + "notes": "Xiaomi MiMo coding agent. Local install at .mimocode/ (npm). API shape is provisional pending upstream docs." + }, + { + "id": "local", + "label": "Local (Ollama / sentence-transformers)", + "kind": "local", + "protocol": "openai", + "auth": "none", + "keyEnv": null, + "baseUrl": "http://127.0.0.1:11434", + "defaultModel": "qwen2.5-coder:14b", + "embeddingModel": "all-MiniLM-L6-v2", + "maxContext": 32768, + "streaming": true, + "fallback": true, + "notes": "No key. Embeddings run via sentence-transformers in the MCP router." + } + ] +} diff --git a/.aurelio/providers/registry.yaml b/.aurelio/providers/registry.yaml new file mode 100644 index 00000000..9a9c53a1 --- /dev/null +++ b/.aurelio/providers/registry.yaml @@ -0,0 +1,355 @@ +# Aurélio Provider Registry — single source of truth +# +# This file is the ONE place providers are defined. All other surfaces derive +# from it via scripts/generate-provider-mirrors.py: +# +# * dirac/src/shared/providers/providers.json (Theia/VS Code UI list) +# * .aurelio/mcp/.../intelligence/model_router.py (Python MCP router) +# * .aurelio/providers/dist/portal.providers.json (porta.portugalfuturista.org) +# * ~/.hermes/config.yaml fallback_model docs (manual mirror — see docs) +# +# Schema per entry: +# id machine id (stable; used as enum value / providers.json `value`) +# label human label shown in UIs +# kind api | cli-subprocess | oauth | local +# auth api_key | oauth | iam | none +# key_env env var holding the API key (auth=api_key) +# base_url default endpoint (OpenAI-compatible unless protocol says otherwise) +# protocol openai | anthropic | gemini | bedrock-converse | subprocess | portal +# chat_model default chat model id +# embedding_model default embedding model id (null if none) +# max_context context window in tokens +# surfaces which downstreams get this provider: +# dirac -> Theia/VS Code providers.json +# mcp -> Python model_router +# portal -> porta.portugalfuturista.org workspace router +# hermes -> documented in Hermes fallback mirror +# streaming whether the router can stream tokens (false => one-shot fallback) +# fallback whether this provider participates in the cascading fallback chain +# notes free-form provenance / caveats +# +# The Hermes fallback surface is intentionally NOT auto-rewritten: Hermes owns +# ~/.hermes/config.yaml. We keep `surfaces: [hermes]` as documentation of which +# providers Hermes already supports so the other planes can match it. + +version: 1 + +providers: + # ── Hermes-parity providers (from ~/.hermes/config.yaml fallback docs) ────── + - id: openrouter + label: OpenRouter + kind: api + auth: api_key + key_env: OPENROUTER_API_KEY + base_url: https://openrouter.ai/api/v1 + protocol: openai + chat_model: anthropic/claude-sonnet-4 + embedding_model: null + max_context: 200000 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: Meta-router; reaches any upstream model. + + - id: openai-codex + label: ChatGPT Subscription (Codex OAuth) + kind: oauth + auth: oauth + key_env: null + base_url: https://chatgpt.com/backend-api/codex + protocol: openai + chat_model: gpt-5-codex + embedding_model: null + max_context: 200000 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: OAuth via `hermes auth`. No API key. + + - id: nous + label: Nous Portal + kind: oauth + auth: oauth + key_env: null + base_url: https://inference-api.nousresearch.com/v1 + protocol: openai + chat_model: Hermes-4-70B + embedding_model: null + max_context: 131072 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: NousResearch OAuth subscription. + + - id: zai + label: Z.AI / GLM + kind: api + auth: api_key + key_env: ZAI_API_KEY + base_url: https://api.z.ai/api/paas/v4 + protocol: openai + chat_model: glm-4.6 + embedding_model: null + max_context: 128000 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: Z.AI (GLM) OpenAI-compatible. + + - id: kimi-coding + label: Kimi for Coding + kind: api + auth: api_key + key_env: KIMI_API_KEY + base_url: https://api.moonshot.ai/v1 + protocol: openai + chat_model: kimi-k2-0905-preview + embedding_model: null + max_context: 131072 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: Moonshot Kimi; alias of the existing `kimi`/`moonshot` ids in Dirac. + + - id: kimi-coding-cn + label: Kimi for Coding (China) + kind: api + auth: api_key + key_env: KIMI_CN_API_KEY + base_url: https://api.moonshot.cn/v1 + protocol: openai + chat_model: kimi-k2-0905-preview + embedding_model: null + max_context: 131072 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: China-region Moonshot endpoint. + + - id: minimax + label: MiniMax + kind: api + auth: api_key + key_env: MINIMAX_API_KEY + base_url: https://api.minimax.io/v1 + protocol: openai + chat_model: MiniMax-M2 + embedding_model: null + max_context: 200000 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + + - id: minimax-cn + label: MiniMax (China) + kind: api + auth: api_key + key_env: MINIMAX_CN_API_KEY + base_url: https://api.minimaxi.com/v1 + protocol: openai + chat_model: MiniMax-M2 + embedding_model: null + max_context: 200000 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + + - id: bedrock + label: Amazon Bedrock + kind: api + auth: iam + key_env: null + base_url: "" + protocol: bedrock-converse + chat_model: anthropic.claude-sonnet-4-20250514-v1:0 + embedding_model: amazon.titan-embed-text-v2:0 + max_context: 200000 + surfaces: [hermes, dirac, mcp, portal] + streaming: true + fallback: true + notes: Uses boto3/IAM, not an API key. Python router shells to boto3. + + # ── Classic API providers already in Dirac / MCP ──────────────────────────── + - id: openai + label: OpenAI + kind: api + auth: api_key + key_env: OPENAI_API_KEY + base_url: https://api.openai.com/v1 + protocol: openai + chat_model: gpt-4o + embedding_model: text-embedding-3-large + max_context: 128000 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + + - id: anthropic + label: Anthropic + kind: api + auth: api_key + key_env: ANTHROPIC_API_KEY + base_url: https://api.anthropic.com/v1 + protocol: anthropic + chat_model: claude-sonnet-4-20250514 + embedding_model: null + max_context: 200000 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + + - id: gemini + label: Google Gemini + kind: api + auth: api_key + key_env: GEMINI_API_KEY + base_url: https://generativelanguage.googleapis.com/v1beta + protocol: gemini + chat_model: gemini-2.5-pro + embedding_model: text-embedding-004 + max_context: 1000000 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + + - id: moonshot + label: Moonshot + kind: api + auth: api_key + key_env: MOONSHOT_API_KEY + base_url: https://api.moonshot.ai/v1 + protocol: openai + chat_model: moonshot-v1-128k + embedding_model: null + max_context: 128000 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + notes: Distinct Dirac id from kimi-coding; kept for backwards compatibility. + + # ── Alibaba Cloud ─────────────────────────────────────────────────────────── + - id: qwen + label: Alibaba Qwen (DashScope) + kind: api + auth: api_key + key_env: DASHSCOPE_API_KEY + base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + protocol: openai + chat_model: qwen-max + embedding_model: text-embedding-v3 + max_context: 131072 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + notes: Alibaba Cloud Model Studio (DashScope) OpenAI-compatible endpoint. + + - id: qwen-code + label: Qwen Code (Alibaba Cloud) + kind: api + auth: api_key + key_env: DASHSCOPE_API_KEY + base_url: https://dashscope.aliyuncs.com/compatible-mode/v1 + protocol: openai + chat_model: qwen3-coder-plus + embedding_model: null + max_context: 131072 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + notes: Qwen Code CLI default model via DashScope; shares DASHSCOPE_API_KEY. + - id: alibaba-cloud + label: Alibaba Cloud (Model Studio) + kind: api + auth: api_key + key_env: ALIBABA_CLOUD_API_KEY + base_url: https://dashscope-intl.aliyuncs.com/compatible-mode/v1 + protocol: openai + chat_model: qwen-max + embedding_model: text-embedding-v3 + max_context: 131072 + surfaces: [mcp, portal] + streaming: true + fallback: true + notes: Generic Alibaba Cloud Model Studio umbrella (intl endpoint). Qwen/Qwen-Code are the concrete aliases; this entry exists so the portal can expose a single "Alibaba Cloud" offering. + + # ── CLI subprocess agents (invoked as binaries, not HTTP) ─────────────────── + - id: claude-code + label: Claude Code + kind: cli-subprocess + auth: oauth + key_env: null + base_url: "" + protocol: subprocess + chat_model: claude-sonnet-4-20250514 + embedding_model: null + max_context: 200000 + surfaces: [dirac, portal] + streaming: true + fallback: false + notes: Invokes the `claude` CLI; inherits its logged-in OAuth session. + + - id: antigravity + label: Antigravity CLI + kind: cli-subprocess + auth: oauth + key_env: ANTIGRAVITY_API_KEY + base_url: https://antigravity.portugalfuturista.org + protocol: portal + chat_model: antigravity-default + embedding_model: null + max_context: 200000 + surfaces: [dirac, portal, mcp] + streaming: false + fallback: true + notes: Google Antigravity. Local CLI at ~/.gemini/antigravity-cli; portal endpoint for the hosted relay. + + - id: mimocode + label: Xiaomi MiMo Code + kind: cli-subprocess + auth: api_key + key_env: MIMOCODE_API_KEY + base_url: "" + protocol: subprocess + chat_model: mimocoder + embedding_model: null + max_context: 131072 + surfaces: [dirac, portal] + streaming: false + fallback: false + notes: Xiaomi MiMo coding agent. Local install at .mimocode/ (npm). API shape is provisional pending upstream docs. + + # ── Local ─────────────────────────────────────────────────────────────────── + - id: local + label: Local (Ollama / sentence-transformers) + kind: local + auth: none + key_env: null + base_url: http://127.0.0.1:11434 + protocol: openai + chat_model: qwen2.5-coder:14b + embedding_model: all-MiniLM-L6-v2 + max_context: 32768 + surfaces: [dirac, mcp, portal] + streaming: true + fallback: true + notes: No key. Embeddings run via sentence-transformers in the MCP router. + +# Fallback preference order for the Python MCP router (chat). +# Subprocess / oauth-only providers are excluded from the auto chain because +# they require an interactive login; they remain explicitly selectable. +mcp_chat_fallback: + - kimi-coding + - qwen-code + - gemini + - anthropic + - openai + - minimax + - zai + - openrouter + +# Embedding preference order for the Python MCP router. +mcp_embed_fallback: + - gemini + - openai + - qwen + - local diff --git a/.gitignore b/.gitignore index a5dfa887..9e671153 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ __pycache__/ # Build outputs dist/ +!.aurelio/providers/dist/ out/ *.vsix diff --git a/scripts/generate-provider-mirrors.py b/scripts/generate-provider-mirrors.py new file mode 100755 index 00000000..75064323 --- /dev/null +++ b/scripts/generate-provider-mirrors.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Generate downstream provider mirrors from the single source of truth: + + .aurelio/providers/registry.yaml + +Outputs (under .aurelio/providers/dist/ unless --write-in-place is given): + + * portal.providers.json — consumable by porta.portugalfuturista.org / + aurelio-theia ModelRouter. + * mcp.providers.json — machine-readable form the Python model_router + can load instead of its hard-coded registry. + * dirac.providers.json — {list:[{value,label}]} matching Dirac's + src/shared/providers/providers.json shape. + +With --write-in-place it also patches Dirac's providers.json in place (adding +any missing providers, never removing existing ones) and rewrites the Python +model_router's registry block to load from mcp.providers.json at import time. + +Usage: + python3 scripts/generate-provider-mirrors.py + python3 scripts/generate-provider-mirrors.py --write-in-place + python3 scripts/generate-provider-mirrors.py --check # exit 1 if dist stale +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +try: + import yaml # PyYAML +except ImportError: # pragma: no cover + print("PyYAML required: pip install pyyaml", file=sys.stderr) + sys.exit(2) + +REPLICA_ROOT = Path(__file__).resolve().parent.parent +REGISTRY = REPLICA_ROOT / ".aurelio" / "providers" / "registry.yaml" +DIST_DIR = REPLICA_ROOT / ".aurelio" / "providers" / "dist" +DIRAC_PROVIDERS = REPLICA_ROOT / "dirac" / "src" / "shared" / "providers" / "providers.json" + + +def load() -> dict: + with REGISTRY.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) + + +def emit_portal(data: dict) -> dict: + """Shape consumed by aurelio-theia ModelRouter / porta.portugalfuturista.org.""" + providers = [] + for p in data["providers"]: + if "portal" not in p.get("surfaces", []): + continue + providers.append({ + "id": p["id"], + "label": p["label"], + "kind": p["kind"], + "protocol": p["protocol"], + "auth": p["auth"], + "keyEnv": p.get("key_env"), + "baseUrl": p.get("base_url") or None, + "defaultModel": p.get("chat_model") or None, + "embeddingModel": p.get("embedding_model") or None, + "maxContext": p.get("max_context"), + "streaming": bool(p.get("streaming")), + "fallback": bool(p.get("fallback")), + "notes": p.get("notes", ""), + }) + return { + "version": data.get("version", 1), + "generatedFrom": ".aurelio/providers/registry.yaml", + "fallback": data.get("mcp_chat_fallback", []), + "providers": providers, + } + + +def emit_mcp(data: dict) -> dict: + providers = [] + for p in data["providers"]: + if "mcp" not in p.get("surfaces", []): + continue + providers.append({ + "id": p["id"], + "label": p["label"], + "auth": p["auth"], + "keyEnv": p.get("key_env"), + "baseUrl": p.get("base_url") or "", + "protocol": p["protocol"], + "chatModel": p.get("chat_model") or "", + "embeddingModel": p.get("embedding_model"), + "maxContext": p.get("max_context"), + "streaming": bool(p.get("streaming")), + "fallback": bool(p.get("fallback")), + }) + return { + "version": data.get("version", 1), + "chatFallback": data.get("mcp_chat_fallback", []), + "embedFallback": data.get("mcp_embed_fallback", []), + "providers": providers, + } + + +def emit_dirac(data: dict) -> dict: + out = [] + for p in data["providers"]: + if "dirac" not in p.get("surfaces", []): + continue + out.append({"value": p["id"], "label": p["label"]}) + return {"list": out} + + +def write_json(path: Path, obj: dict) -> bool: + path.parent.mkdir(parents=True, exist_ok=True) + new = json.dumps(obj, indent=2, ensure_ascii=False) + "\n" + if path.exists() and path.read_text(encoding="utf-8") == new: + return False + path.write_text(new, encoding="utf-8") + return True + + +def patch_dirac_in_place(dirac_list: dict) -> list[str]: + """Add providers missing from Dirac's providers.json. Returns added ids.""" + if not DIRAC_PROVIDERS.exists(): + return [f""] + current = json.loads(DIRAC_PROVIDERS.read_text(encoding="utf-8")) + have = {e["value"] for e in current.get("list", [])} + added = [] + for entry in dirac_list["list"]: + if entry["value"] not in have: + current["list"].append(entry) + added.append(entry["value"]) + if added: + DIRAC_PROVIDERS.write_text( + json.dumps(current, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) + return added + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--write-in-place", action="store_true") + ap.add_argument("--check", action="store_true", + help="Exit non-zero if dist/ is stale relative to registry.") + args = ap.parse_args() + + data = load() + targets = { + "portal.providers.json": emit_portal(data), + "mcp.providers.json": emit_mcp(data), + "dirac.providers.json": emit_dirac(data), + } + + if args.check: + stale = [] + for name, obj in targets.items(): + p = DIST_DIR / name + new = json.dumps(obj, indent=2, ensure_ascii=False) + "\n" + if not p.exists() or p.read_text(encoding="utf-8") != new: + stale.append(name) + if stale: + print("STALE:", ", ".join(stale), file=sys.stderr) + return 1 + print("dist/ up to date") + return 0 + + changed = [] + for name, obj in targets.items(): + if write_json(DIST_DIR / name, obj): + changed.append(name) + print("dist:", "updated " + ", ".join(changed) if changed else "no changes") + + if args.write_in_place: + added = patch_dirac_in_place(targets["dirac.providers.json"]) + if added: + print("dirac providers.json: added", ", ".join(added)) + else: + print("dirac providers.json: already complete") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())