File size: 6,173 Bytes
a37e1b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""CommandCode provider profile.

CommandCode provides a unified API that fronts 20+ models from DeepSeek, Qwen,
Kimi, GLM, MiniMax, StepFun, Xiaomi Mimo, Google Gemini, and OpenAI GPT β€” all
accessible through either OpenAI-compatible chat completions or Anthropic
Messages endpoints from a single base URL and API key.

Two provider profiles are registered:

``commandcode``
    ``api_mode=chat_completions`` β€” standard OpenAI-compatible endpoint.
    Model prefix: ``deepseek/deepseek-v4-pro``, ``Qwen/Qwen3.7-Max``, etc.

``commandcode-anthropic``
    ``api_mode=anthropic_messages`` β€” Anthropic Messages API-compatible.
    Model names: ``claude-sonnet-4-6``, ``claude-opus-4-7``,
    ``claude-haiku-4-5-20251001``.

Both use the same ``COMMANDCODE_API_KEY`` env var and
``https://api.commandcode.ai/provider/v1`` base URL.  The
``commandcode-anthropic`` profile relies on ``agent/anthropic_adapter.py``
recognizing the ``api.commandcode.ai`` hostname for Bearer auth (the
CommandCode /anthropic endpoint uses ``Authorization: Bearer``, not
Anthropic's native ``x-api-key`` header).
"""

from __future__ import annotations

import json
import logging
import urllib.request

from providers import register_provider
from providers.base import ProviderProfile, _profile_user_agent

logger = logging.getLogger(__name__)

# ── Shared constants ──────────────────────────────────────────────────────────
_COMMANDCODE_BASE = "https://api.commandcode.ai/provider/v1"
_COMMANDCODE_MODELS_URL = f"{_COMMANDCODE_BASE}/models"
# Both profiles authenticate with the same key; each carries its own base-URL
# override var so each renders its own card on the desktop Keys tab (rows are
# keyed by env var, and the shared API key attributes to the first profile).
_COMMANDCODE_ENV = ("COMMANDCODE_API_KEY", "COMMANDCODE_BASE_URL")
_COMMANDCODE_ANTHROPIC_ENV = ("COMMANDCODE_API_KEY", "COMMANDCODE_ANTHROPIC_BASE_URL")


def _fetch_commandcode_models(
    timeout: float = 10.0,
) -> list[str] | None:
    """Fetch the live model list from the CommandCode /models endpoint.

    Returns a flat list of model IDs or None on failure.
    No auth required β€” the public models endpoint is open.
    """
    try:
        req = urllib.request.Request(_COMMANDCODE_MODELS_URL)
        req.add_header("Accept", "application/json")
        req.add_header("User-Agent", _profile_user_agent())
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            data = json.loads(resp.read().decode())
        # Response shape: {"object": "list", "data": [{"id": "..."}, ...]}
        return [
            m["id"]
            for m in data.get("data", [])
            if isinstance(m, dict) and "id" in m
        ]
    except Exception as exc:
        logger.debug("fetch_models(commandcode): %s", exc)
        return None


# ── Chat Completions profile ──────────────────────────────────────────────────

class CommandCodeProfile(ProviderProfile):
    """CommandCode β€” OpenAI-compatible chat completions endpoint."""

    def fetch_models(
        self,
        *,
        api_key: str | None = None,
        timeout: float = 8.0,
    ) -> list[str] | None:
        """Fetch from the public CommandCode /models endpoint."""
        return _fetch_commandcode_models(timeout=timeout)


commandcode = CommandCodeProfile(
    name="commandcode",
    aliases=("commandcode-chat",),
    api_mode="chat_completions",
    env_vars=_COMMANDCODE_ENV,
    display_name="CommandCode",
    description="CommandCode β€” 20+ models via OpenAI-compatible API",
    signup_url="https://commandcode.ai/",
    base_url=_COMMANDCODE_BASE,
    models_url=_COMMANDCODE_MODELS_URL,
    fallback_models=(
        "deepseek/deepseek-v4-pro",
        "deepseek/deepseek-v4-flash",
        "Qwen/Qwen3.7-Max",
        "Qwen/Qwen3.6-Plus",
        "moonshotai/Kimi-K2.6",
        "zai-org/GLM-5.1",
        "MiniMaxAI/MiniMax-M2.7",
        "stepfun/Step-3.5-Flash",
        "xiaomi/mimo-v2.5-pro",
        "google/gemini-3.5-flash",
        "gpt-5.5",
    ),
    default_aux_model="deepseek/deepseek-v4-flash",
)


# ── Anthropic Messages profile ────────────────────────────────────────────────

class CommandCodeAnthropicProfile(ProviderProfile):
    """CommandCode β€” Anthropic Messages API-compatible endpoint.

    Uses Bearer auth (same API key), not Anthropic's native x-api-key header.
    ``agent/anthropic_adapter.py`` must recognize ``api.commandcode.ai``
    as a Bearer-auth domain for this to work.
    """

    def fetch_models(
        self,
        *,
        api_key: str | None = None,
        timeout: float = 8.0,
    ) -> list[str] | None:
        """Fetch from the public CommandCode /models endpoint.

        Filter to Anthropic-family models only (claude-*).
        """
        all_models = _fetch_commandcode_models(timeout=timeout)
        if all_models is None:
            return None
        return [m for m in all_models if m.startswith("claude-")]


commandcode_anthropic = CommandCodeAnthropicProfile(
    name="commandcode-anthropic",
    aliases=("commandcode-claude",),
    api_mode="anthropic_messages",
    env_vars=_COMMANDCODE_ANTHROPIC_ENV,
    display_name="CommandCode (Anthropic)",
    description="CommandCode β€” Claude models via Anthropic Messages API",
    signup_url="https://commandcode.ai/",
    base_url=_COMMANDCODE_BASE,
    models_url=_COMMANDCODE_MODELS_URL,
    fallback_models=(
        "claude-sonnet-4-6",
        "claude-opus-4-7",
        "claude-haiku-4-5-20251001",
    ),
    default_aux_model="claude-haiku-4-5-20251001",
)


# ── Registration ──────────────────────────────────────────────────────────────
register_provider(commandcode)
register_provider(commandcode_anthropic)