diff --git a/agentkit/toolkit/cli/sandbox/README.md b/agentkit/toolkit/cli/sandbox/README.md index 39eac10..088bbdb 100644 --- a/agentkit/toolkit/cli/sandbox/README.md +++ b/agentkit/toolkit/cli/sandbox/README.md @@ -65,8 +65,7 @@ Options: `AGENTKIT_SANDBOX_MODEL_PROVIDER`; defaults to `model_square`. The built-in providers `model_square`, `coding_plan`, and `agent_plan` also provide base URLs, default models, and Codex model catalog entries. Other provider strings - are passed through without built-in URL or catalog handling and require - `--model-base-url`. + are passed through without built-in URL or catalog handling. - `--model-name`: optional. Injected into the tool as `OPENCODE_MODEL`, `CODEX_MODEL`, and `ANTHROPIC_MODEL`. If omitted for a built-in provider, that provider's default model is used. Custom model names are allowed and are @@ -472,7 +471,7 @@ Options: `agent_plan` also provide default models, base URL envs, and `CODEX_CONFIG_TOML` / `CODEX_MODEL_CATALOG_JSON` updates for `CodeEnv` sessions. Other provider strings are passed through without built-in URL or - catalog handling and require `--model-base-url`. + catalog handling. - `--model-base-url`: optional. When creating a sandbox session, injects the value into `OPENCODE_BASE_URL`, `CODEX_BASE_URL`, `MODEL_BASE_URL`, and `ANTHROPIC_BASE_URL`. When provided, it takes precedence over provider base diff --git a/agentkit/toolkit/cli/sandbox/model_config.py b/agentkit/toolkit/cli/sandbox/model_config.py index 75c236f..e54865b 100644 --- a/agentkit/toolkit/cli/sandbox/model_config.py +++ b/agentkit/toolkit/cli/sandbox/model_config.py @@ -209,16 +209,6 @@ def validate_model_provider_base_url( else model_base_url_was_provided ) - if ( - provider_was_provided - and resolved_model_provider - and resolved_model_provider not in MODEL_PROVIDER_CONFIGS - and not resolved_model_base_url - ): - raise ValueError( - "--model-provider requires --model-base-url for custom providers" - ) - if ( base_url_was_provided and resolved_model_base_url @@ -269,7 +259,7 @@ def resolve_model_name( return resolved_model_name if config: return config.default_model_name - return "" + return DEFAULT_MODEL_NAME def resolve_model_base_urls( @@ -292,12 +282,7 @@ def should_emit_codex_model_config( model_provider: str | ModelProviderType | None, model_base_url: Optional[str] = None, ) -> bool: - resolved_provider = normalize_optional_model_provider(model_provider) - if resolved_provider is None: - return normalize_model_base_url(model_base_url) is None - return resolved_provider in MODEL_PROVIDER_CONFIGS or bool( - normalize_model_base_url(model_base_url) - ) + return True def should_emit_codex_model_catalog( @@ -356,12 +341,10 @@ def build_codex_config_toml( config = get_model_provider_config_if_known(resolved_provider) resolved_model_base_url = normalize_model_base_url(model_base_url) provider_base_url = resolved_model_base_url or ( - config.model_base_url if config else None + config.model_base_url + if config + else MODEL_PROVIDER_CONFIGS[DEFAULT_MODEL_PROVIDER].model_base_url ) - if not provider_base_url: - raise ValueError( - f"--model-provider has no built-in configuration: {resolved_provider}" - ) resolved_model_name = resolve_model_name(model_name, resolved_provider) resolved_codex_provider = codex_model_provider_id(resolved_provider) quoted_model = _toml_quote(resolved_model_name) diff --git a/tests/toolkit/cli/test_cli_create_tool.py b/tests/toolkit/cli/test_cli_create_tool.py index 464f316..883b387 100644 --- a/tests/toolkit/cli/test_cli_create_tool.py +++ b/tests/toolkit/cli/test_cli_create_tool.py @@ -819,7 +819,7 @@ def test_build_create_tool_request_renames_reserved_codex_provider(monkeypatch): assert "CODEX_MODEL_CATALOG_JSON" not in envs -def test_build_create_tool_request_rejects_arbitrary_model_provider_without_base_url( +def test_build_create_tool_request_allows_arbitrary_model_provider_without_base_url( monkeypatch, ): from agentkit.toolkit.cli.sandbox import cli_create @@ -827,18 +827,28 @@ def test_build_create_tool_request_rejects_arbitrary_model_provider_without_base _reset_fake_tools_client() monkeypatch.setattr(cli_create, "TOSService", _FakeTOSService) - with pytest.raises( - ValueError, - match="--model-provider requires --model-base-url for custom providers", - ): - cli_create._build_create_tool_request( - tool_type="CodeEnv", - name="demo-tool", - tos_bucket="my-bucket", - tos_region="cn-beijing", - model_provider="model-square-experimental", - model_name="custom-model", - ) + request = cli_create._build_create_tool_request( + tool_type="CodeEnv", + name="demo-tool", + tos_bucket="my-bucket", + tos_region="cn-beijing", + model_provider="model-square-experimental", + model_name="custom-model", + ) + + envs = {item.key: item.value for item in request.envs} + assert envs["AGENTKIT_SANDBOX_MODEL_PROVIDER"] == "model-square-experimental" + assert envs["CODEX_MODEL"] == "custom-model" + assert "CODEX_BASE_URL" not in envs + assert "ANTHROPIC_BASE_URL" not in envs + assert 'model_provider = "model-square-experimental"' in envs["CODEX_CONFIG_TOML"] + assert 'model = "custom-model"' in envs["CODEX_CONFIG_TOML"] + assert ( + 'base_url = "https://ark.cn-beijing.volces.com/api/v3"' + in envs["CODEX_CONFIG_TOML"] + ) + assert "model_catalog_json" not in envs["CODEX_CONFIG_TOML"] + assert "CODEX_MODEL_CATALOG_JSON" not in envs def test_build_create_tool_request_allows_custom_model_name(monkeypatch): @@ -963,8 +973,11 @@ def test_create_command_accepts_model_base_url_without_model_name(monkeypatch): envs = {item.key: item.value for item in _FakeToolsClient.last_request.envs} assert envs["AGENTKIT_SANDBOX_MODEL_PROVIDER"] == "custom_provider" assert envs["CODEX_BASE_URL"] == "https://models.example.com" - assert "CODEX_MODEL" not in envs - assert "CODEX_CONFIG_TOML" not in envs + assert envs["CODEX_MODEL"] == "deepseek-v4-flash-260425" + assert 'model_provider = "custom_provider"' in envs["CODEX_CONFIG_TOML"] + assert 'model = "deepseek-v4-flash-260425"' in envs["CODEX_CONFIG_TOML"] + assert 'base_url = "https://models.example.com"' in envs["CODEX_CONFIG_TOML"] + assert "model_catalog_json" not in envs["CODEX_CONFIG_TOML"] assert "CODEX_MODEL_CATALOG_JSON" not in envs diff --git a/tests/toolkit/cli/test_cli_sandbox.py b/tests/toolkit/cli/test_cli_sandbox.py index 08c3b6d..a1ac201 100644 --- a/tests/toolkit/cli/test_cli_sandbox.py +++ b/tests/toolkit/cli/test_cli_sandbox.py @@ -830,17 +830,33 @@ def test_build_model_envs_renames_reserved_codex_provider(monkeypatch) -> None: assert "CODEX_MODEL_CATALOG_JSON" not in env_map -def test_build_model_envs_requires_base_url_with_arbitrary_model_provider() -> None: +def test_build_model_envs_allows_arbitrary_model_provider_without_base_url( + monkeypatch, +) -> None: import agentkit.toolkit.cli.sandbox.session_create as session_create - with pytest.raises( - ValueError, - match="--model-provider requires --model-base-url for custom providers", - ): - session_create.build_model_envs( - model_name="custom-model", - model_provider="agent_plan_experimental", - ) + monkeypatch.delenv("MODEL_API_KEY", raising=False) + + envs = session_create.build_model_envs( + model_name="custom-model", + model_provider="agent_plan_experimental", + include_codex_config=True, + ) + + assert [(item.key, item.value) for item in envs] == [ + ("AGENTKIT_SANDBOX_MODEL_PROVIDER", "agent_plan_experimental"), + ("OPENCODE_MODEL", "custom-model"), + ("CODEX_MODEL", "custom-model"), + ("ANTHROPIC_MODEL", "custom-model"), + ( + "CODEX_CONFIG_TOML", + envs[4].value, + ), + ] + assert 'model_provider = "agent_plan_experimental"' in envs[4].value + assert 'model = "custom-model"' in envs[4].value + assert 'base_url = "https://ark.cn-beijing.volces.com/api/v3"' in envs[4].value + assert "model_catalog_json" not in envs[4].value def test_build_model_envs_allows_model_base_url_without_model_name(monkeypatch) -> None: @@ -856,11 +872,22 @@ def test_build_model_envs_allows_model_base_url_without_model_name(monkeypatch) assert [(item.key, item.value) for item in envs] == [ ("AGENTKIT_SANDBOX_MODEL_PROVIDER", "custom_provider"), + ("OPENCODE_MODEL", "deepseek-v4-flash-260425"), + ("CODEX_MODEL", "deepseek-v4-flash-260425"), + ("ANTHROPIC_MODEL", "deepseek-v4-flash-260425"), ("OPENCODE_BASE_URL", "https://models.example.com/v1"), ("CODEX_BASE_URL", "https://models.example.com/v1"), ("MODEL_BASE_URL", "https://models.example.com/v1"), ("ANTHROPIC_BASE_URL", "https://models.example.com/v1"), + ( + "CODEX_CONFIG_TOML", + envs[8].value, + ), ] + assert 'model_provider = "custom_provider"' in envs[8].value + assert 'model = "deepseek-v4-flash-260425"' in envs[8].value + assert 'base_url = "https://models.example.com/v1"' in envs[8].value + assert "model_catalog_json" not in envs[8].value def test_build_model_envs_infers_provider_from_builtin_model_base_url( @@ -4632,8 +4659,37 @@ def test_cli_exec_rejects_non_ark_model_base_url_without_model_provider() -> Non ) -def test_cli_exec_rejects_arbitrary_model_provider_without_base_url() -> None: +def test_cli_exec_allows_arbitrary_model_provider_without_base_url( + monkeypatch, + tmp_path, +) -> None: from agentkit.toolkit.cli.cli import app + import agentkit.toolkit.cli.sandbox.cli_exec as cli_exec + + monkeypatch.delenv("MODEL_API_KEY", raising=False) + store_path = _patch_store_path(monkeypatch, tmp_path) + stored_session = { + "session_id": "user-1", + "tool_id": "tool-1", + "instance_id": "session-1", + "endpoint": "https://sandbox.example.com/?token=abc", + } + store_path.write_text( + json.dumps({"user-1": stored_session}), + encoding="utf-8", + ) + captured_session = {} + _patch_exec_session( + monkeypatch, + cli_exec, + stored_session, + capture=captured_session, + ) + monkeypatch.setattr( + cli_exec, + "_connect_terminal", + lambda *_args, **_kwargs: None, + ) result = runner.invoke( app, @@ -4644,16 +4700,25 @@ def test_cli_exec_rejects_arbitrary_model_provider_without_base_url() -> None: "custom_provider", "--model-name", "custom-model", - "--command", - "echo should-not-run; exit", + "--session-id", + "user-1", ], ) - assert result.exit_code != 0 + assert result.exit_code == 0 + envs = {item.key: item.value for item in captured_session["envs"]} + assert envs["AGENTKIT_SANDBOX_MODEL_PROVIDER"] == "custom_provider" + assert envs["CODEX_MODEL"] == "custom-model" + assert "CODEX_BASE_URL" not in envs + assert "ANTHROPIC_BASE_URL" not in envs + assert 'model_provider = "custom_provider"' in envs["CODEX_CONFIG_TOML"] + assert 'model = "custom-model"' in envs["CODEX_CONFIG_TOML"] assert ( - "--model-provider requires --model-base-url for custom providers" - in result.output + 'base_url = "https://ark.cn-beijing.volces.com/api/v3"' + in envs["CODEX_CONFIG_TOML"] ) + assert "model_catalog_json" not in envs["CODEX_CONFIG_TOML"] + assert "CODEX_MODEL_CATALOG_JSON" not in envs def test_cli_exec_custom_provider_base_url_emits_codex_config_without_catalog(