Skip to content
Merged
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
5 changes: 2 additions & 3 deletions agentkit/toolkit/cli/sandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
27 changes: 5 additions & 22 deletions agentkit/toolkit/cli/sandbox/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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)
Expand Down
43 changes: 28 additions & 15 deletions tests/toolkit/cli/test_cli_create_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,26 +819,36 @@ 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

_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):
Expand Down Expand Up @@ -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


Expand Down
95 changes: 80 additions & 15 deletions tests/toolkit/cli/test_cli_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down