mirror of
https://github.com/usestrix/strix.git
synced 2026-08-23 03:12:37 +02:00
auth: make STRIX_LLM=openai/subscription the single switch
Replace the separate STRIX_AUTH_MODE flag with a sentinel model value: STRIX_LLM=openai/subscription selects the authenticated ChatGPT subscription, and any other value is a normal API-key model. The env vars that already run Strix are now the single source of truth — no second mode to keep in sync. Encapsulate the behavior instead of branching everywhere: - StrixProvider.get_model routes the sentinel to a _CodexResponsesModel backed by a cached OAuth client (no global default-client mutation, no per-call client churn). - _CodexResponsesModel self-enforces the backend's requirements — streaming, store=false, encrypted reasoning, and the configured reasoning effort — so the runner, warm-up, and make_model_settings no longer special-case subscription. Remove now-unneeded machinery: STRIX_AUTH_MODE/AuthMode, the "incompatible model" warning, the non-OpenAI model coercion, the make_model_settings codex flag, and the global set_default_openai_client wiring. run.json still records a derived auth_mode so the viewer/telemetry/cost display are unchanged. Switching modes is now just editing STRIX_LLM. Sentinel-only (no per-model override): a subscription run uses gpt-5.4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9f54b2f144
commit
30390628da
@@ -97,7 +97,7 @@ def test_login_accepts_provider_aliases(provider: str, monkeypatch: pytest.Monke
|
||||
|
||||
monkeypatch.setattr(auth_cli, "_run_oauth_flow", _fake_flow)
|
||||
monkeypatch.setattr(codex, "save_record", lambda _record: None)
|
||||
monkeypatch.setattr(auth_cli, "_persist_subscription_config", lambda _model: None)
|
||||
monkeypatch.setattr(auth_cli, "_persist_subscription_config", lambda: None)
|
||||
|
||||
assert auth_cli.run_auth(["login", provider]) == 0
|
||||
assert reached["flow"] is True
|
||||
|
||||
+14
-25
@@ -69,36 +69,25 @@ def test_parse_redirect_input(value: str, expected: tuple[str | None, str | None
|
||||
@pytest.mark.parametrize(
|
||||
("model", "expected"),
|
||||
[
|
||||
("openai/gpt-5.5", "gpt-5.5"),
|
||||
("gpt-5.4", "gpt-5.4"),
|
||||
# A configured-but-unlisted OpenAI/bare name is passed through; the backend validates.
|
||||
("openai/gpt-5.6", "gpt-5.6"),
|
||||
# Another provider can't be served by a ChatGPT subscription → coerced to default.
|
||||
("anthropic/claude-opus-4-8", codex.DEFAULT_CODEX_MODEL),
|
||||
("deepseek/deepseek-v4-pro", codex.DEFAULT_CODEX_MODEL),
|
||||
("vertex_ai/gemini-3-pro", codex.DEFAULT_CODEX_MODEL),
|
||||
(None, codex.DEFAULT_CODEX_MODEL),
|
||||
("", codex.DEFAULT_CODEX_MODEL),
|
||||
],
|
||||
)
|
||||
def test_normalize_model(model: str | None, expected: str) -> None:
|
||||
assert codex.normalize_model(model) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "compatible"),
|
||||
[
|
||||
("openai/gpt-5.5", True),
|
||||
("gpt-5.4", True),
|
||||
("gpt-5.1-codex", True), # bare name: backend is the authority
|
||||
("openai/subscription", True),
|
||||
("OpenAI/Subscription", True), # case-insensitive
|
||||
(" openai/subscription ", True), # surrounding whitespace
|
||||
("openai/gpt-5.4", False),
|
||||
("anthropic/claude-opus-4-8", False),
|
||||
("deepseek/deepseek-v4-pro", False),
|
||||
("openai/subscription/gpt-5.5", False), # only the bare sentinel selects it
|
||||
("", False),
|
||||
(None, False),
|
||||
],
|
||||
)
|
||||
def test_is_backend_compatible(model: str | None, compatible: bool) -> None:
|
||||
assert codex.is_backend_compatible(model) is compatible
|
||||
def test_is_subscription(model: str | None, expected: bool) -> None:
|
||||
assert codex.is_subscription(model) is expected
|
||||
|
||||
|
||||
def test_resolve_and_label() -> None:
|
||||
assert codex.resolve_subscription_model() == codex.DEFAULT_CODEX_MODEL
|
||||
assert codex.auth_mode_label("openai/subscription") == "subscription"
|
||||
assert codex.auth_mode_label("openai/gpt-5.4") == "api_key"
|
||||
assert codex.auth_mode_label(None) == "api_key"
|
||||
|
||||
|
||||
def test_account_id_from_jwt() -> None:
|
||||
|
||||
@@ -63,6 +63,9 @@ def _response_payload() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
_CAPTURED: dict[str, Any] = {}
|
||||
|
||||
|
||||
class _Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *args: Any) -> None:
|
||||
pass
|
||||
@@ -70,6 +73,8 @@ class _Handler(BaseHTTPRequestHandler):
|
||||
def do_POST(self) -> None:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
_CAPTURED.clear()
|
||||
_CAPTURED.update(body)
|
||||
if not body.get("stream"):
|
||||
payload = json.dumps({"detail": "Stream must be set to true"}).encode()
|
||||
self.send_response(400)
|
||||
@@ -136,3 +141,21 @@ async def test_codex_model_streams_and_aggregates(backend_url: str) -> None:
|
||||
response = await model.get_response(**_call_kwargs())
|
||||
assert response.output[0].content[0].text == "OK"
|
||||
assert response.usage.total_tokens == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_codex_model_self_enforces_backend_requirements(backend_url: str) -> None:
|
||||
# The caller passes ordinary settings; the model must impose the backend's
|
||||
# requirements (stream, store=false, encrypted reasoning) and the configured
|
||||
# reasoning effort itself.
|
||||
model = _CodexResponsesModel(
|
||||
model="gpt-5.4", openai_client=_client(backend_url), reasoning_effort="high"
|
||||
)
|
||||
kwargs = _call_kwargs()
|
||||
kwargs["model_settings"] = ModelSettings() # nothing special from the caller
|
||||
await model.get_response(**kwargs)
|
||||
|
||||
assert _CAPTURED["stream"] is True
|
||||
assert _CAPTURED["store"] is False
|
||||
assert _CAPTURED["include"] == ["reasoning.encrypted_content"]
|
||||
assert _CAPTURED["reasoning"] == {"effort": "high"}
|
||||
|
||||
@@ -35,7 +35,6 @@ async def test_persistent_rate_limit_stops_gracefully(
|
||||
settings = types.SimpleNamespace(
|
||||
llm=types.SimpleNamespace(
|
||||
model="openai/gpt-4o",
|
||||
auth_mode="api_key",
|
||||
reasoning_effort="high",
|
||||
force_required_tool_choice=False,
|
||||
timeout=300,
|
||||
|
||||
@@ -43,7 +43,6 @@ def _patch_engine_scaffold(
|
||||
settings = types.SimpleNamespace(
|
||||
llm=types.SimpleNamespace(
|
||||
model="openai/gpt-4o",
|
||||
auth_mode="api_key",
|
||||
reasoning_effort="high",
|
||||
force_required_tool_choice=False,
|
||||
timeout=300,
|
||||
|
||||
Reference in New Issue
Block a user