Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/ucode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2031,13 +2031,8 @@ def _launch_tool(
# provider). Skip model resolution, which would otherwise fail when
# the workspace has no matching Databricks models.
resolved_model = None
if tool == "claude" and relayed:
if model:
print_warning(
"This is a subscription-relay Model Provider Service; the gateway selects "
"the model, so --model is ignored."
)
elif tool == "claude" and (model or provider_models):
# Relayed services forward --model to Claude Code's own flag at launch (below), not env.
if tool == "claude" and not relayed and (model or provider_models):
route_root_model = resolve_provider_launch_model(model, provider_models or {})
else:
# A managed default_model is the model the admin wants sessions to start on, so it goes
Expand Down Expand Up @@ -2115,6 +2110,10 @@ def _launch_tool(
# per-family target pins.
custom_model=model if (tool == "claude" and not provider) else None,
)
# Relayed = a Claude subscription: forward --model to Claude Code's own flag, like `-- --model X`.
if tool == "claude" and provider and relayed and model and not forwarded_model:
ctx.args = ["--model", model, *ctx.args]
forwarded_model = model
print_section(_launch_title(tool))
if managed is not None:
print_kv("Config", "workspace-managed")
Expand Down
38 changes: 26 additions & 12 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ def test_v2_model_sets_transient_launch_override(self, monkeypatch):
@staticmethod
def _provider_launch(monkeypatch, argv, provider_models, relayed=False):
"""Invoke a provider launch with model discovery/config stubbed, returning the
configure_tool mock so tests can assert what was threaded to it."""
configure_tool and launch_agent mocks so tests can assert what was threaded to each."""
import ucode.cli as cli_mod

monkeypatch.setattr(cli_mod, "ensure_bootstrap_dependencies", lambda *a, **k: None)
Expand All @@ -623,19 +623,20 @@ def _provider_launch(monkeypatch, argv, provider_models, relayed=False):
monkeypatch.setattr(cli_mod, "configure_shared_state", lambda *a, **k: MINIMAL_STATE)
monkeypatch.setattr(cli_mod, "_fetch_managed_config", lambda s: (None, False))
monkeypatch.setattr(cli_mod, "_fetch_budget_recommendation", lambda s, m: None)
monkeypatch.setattr(cli_mod, "launch_agent", lambda *a, **k: None)
mock_launch = MagicMock()
monkeypatch.setattr(cli_mod, "launch_agent", mock_launch)
monkeypatch.setattr(
cli_mod, "resolve_provider_models", lambda t, s, p: (provider_models, None, relayed)
)
mock_configure = MagicMock(return_value=MINIMAL_STATE)
monkeypatch.setattr(cli_mod, "configure_tool", mock_configure)
result = runner.invoke(app, argv)
return result, mock_configure
return result, mock_configure, mock_launch

def test_model_and_provider_now_pin_the_launch_tier(self, monkeypatch):
# --model under a provider is no longer rejected: a family alias resolves to that tier's
# declared target and is threaded as route_root_model (ANTHROPIC_MODEL), not custom_model.
result, mock_configure = self._provider_launch(
result, mock_configure, _ = self._provider_launch(
monkeypatch,
["claude", "--model", "haiku", "--provider", "cat.schema.svc"],
{"sonnet": "claude-sonnet-5", "haiku": "claude-haiku-4-5"},
Expand All @@ -647,7 +648,7 @@ def test_model_and_provider_now_pin_the_launch_tier(self, monkeypatch):
def test_provider_without_opus_auto_picks_best_servable_tier(self, monkeypatch):
# No --model, and the service declares no opus target: launch on the most capable tier it
# does offer (sonnet) instead of dead-ending on Claude Code's opus default.
result, mock_configure = self._provider_launch(
result, mock_configure, _ = self._provider_launch(
monkeypatch,
["claude", "--provider", "cat.schema.svc"],
{"sonnet": "claude-sonnet-5", "haiku": "claude-haiku-4-5"},
Expand All @@ -658,7 +659,7 @@ def test_provider_without_opus_auto_picks_best_servable_tier(self, monkeypatch):
def test_provider_with_opus_keeps_claude_default(self, monkeypatch):
# Opus is offered, so Claude Code's own default already works — pin nothing (no ANTHROPIC_MODEL
# and no duplicate /model picker row).
result, mock_configure = self._provider_launch(
result, mock_configure, _ = self._provider_launch(
monkeypatch,
["claude", "--provider", "cat.schema.svc"],
{"opus": "claude-opus-4-8", "sonnet": "claude-sonnet-5"},
Expand All @@ -667,25 +668,38 @@ def test_provider_with_opus_keeps_claude_default(self, monkeypatch):
assert mock_configure.call_args.kwargs["route_root_model"] is None

def test_model_family_not_offered_by_provider_errors(self, monkeypatch):
result, _ = self._provider_launch(
result, _, _ = self._provider_launch(
monkeypatch,
["claude", "--model", "opus", "--provider", "cat.schema.svc"],
{"sonnet": "claude-sonnet-5", "haiku": "claude-haiku-4-5"},
)
assert result.exit_code == 1
assert "does not offer a 'opus' model" in result.output

def test_model_ignored_for_relayed_provider(self, monkeypatch):
# A relayed (subscription) service selects the model server-side; --model can't be honored.
result, mock_configure = self._provider_launch(
def test_model_forwarded_to_claude_for_relayed_provider(self, monkeypatch):
# Relayed = a subscription: --model rides Claude Code's own flag, not gateway env.
result, mock_configure, mock_launch = self._provider_launch(
monkeypatch,
["claude", "--model", "haiku", "--provider", "cat.schema.svc"],
["claude", "--model", "opus", "--provider", "cat.schema.svc"],
None,
relayed=True,
)
assert result.exit_code == 0, result.output
assert mock_launch.call_args.args[2] == ["--model", "opus"]
assert mock_configure.call_args.kwargs["route_root_model"] is None
assert "--model is ignored" in _strip_ansi(result.output)
assert mock_configure.call_args.kwargs["custom_model"] is None
assert "ignored" not in _strip_ansi(result.output)

def test_relayed_provider_without_model_forwards_nothing(self, monkeypatch):
# No --model on a relayed launch: nothing to forward.
result, _, mock_launch = self._provider_launch(
monkeypatch,
["claude", "--provider", "cat.schema.svc"],
None,
relayed=True,
)
assert result.exit_code == 0, result.output
assert mock_launch.call_args.args[2] == []

def test_provider_sets_transient_claude_launch_marker(self):
state = dict(MINIMAL_STATE)
Expand Down
Loading