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
8 changes: 5 additions & 3 deletions frontend/server/studio_update_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
SnapshotKind = Literal["codex", "openclaw", "hermes"]

_SNAPSHOT_ENVIRONMENTS: tuple[tuple[str, SnapshotKind, str], ...] = (
("SANDBOX_CHAT_CODEX_SNAPSHOT", "codex", "chat"),
("SANDBOX_CHAT_CODEX_SNAPSHOT", "codex", "codex"),
("SANDBOX_CHAT_OPENCLAW_SNAPSHOT", "openclaw", "openclaw"),
("SANDBOX_CHAT_HERMES_SNAPSHOT", "hermes", "hermes"),
)
Expand Down Expand Up @@ -69,18 +69,20 @@ def _provision_snapshot_tool(
ensure_studio_code_env_tool,
studio_sandbox_agent_model_name,
studio_sandbox_model_base_url,
studio_sandbox_tool_name,
studio_sandbox_tool_name_candidates,
)

tool_name = studio_sandbox_tool_name(
tool_names = studio_sandbox_tool_name_candidates(
application_id,
purpose,
snapshot=True,
)
tool_name = tool_names[0]
model_name = studio_sandbox_agent_model_name(provider)
if kind == "codex":
tool_id = ensure_studio_code_env_tool(
name=tool_name,
legacy_names=tool_names[1:],
enable_snapshot=True,
region=region,
access_key=access_key,
Expand Down
5 changes: 5 additions & 0 deletions tests/cli/test_studio_deploy_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,11 @@ def _ensure_tool(**kwargs: object) -> str:
creation_barrier.wait(timeout=5)
assert kwargs["create_min_interval"] == 0.5
snapshot = bool(kwargs["enable_snapshot"])
assert "-codex-" in str(kwargs["name"])
legacy_names = kwargs["legacy_names"]
assert isinstance(legacy_names, tuple)
assert len(legacy_names) == 1
assert "-chat-" in str(legacy_names[0])
with created_kinds_lock:
created_kinds.append("codex_snapshot" if snapshot else "codex")
if snapshot:
Expand Down
58 changes: 57 additions & 1 deletion tests/cli/test_studio_sandbox_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import os
from types import SimpleNamespace
from typing import cast
from typing import Any, cast
from unittest.mock import patch

import pytest
Expand All @@ -32,6 +32,7 @@
studio_sandbox_agent_model_name,
studio_sandbox_model_base_url,
studio_sandbox_tool_name,
studio_sandbox_tool_name_candidates,
)


Expand Down Expand Up @@ -64,6 +65,46 @@ def test_ensure_studio_code_env_tool_reuses_ready_exact_name() -> None:
)


def test_ensure_studio_code_env_tool_reuses_legacy_name() -> None:
requested_names: list[str] = []

def _list_tools(request: object) -> SimpleNamespace:
filters = cast(Any, request).filters
requested_name = str(filters[0].values[0])
requested_names.append(requested_name)
tools = []
if requested_name == "studio-demo-chat-123456":
tools.append(
SimpleNamespace(
name=requested_name,
project_name="default",
tool_type="CodeEnv",
tool_id="legacy-tool",
)
)
return SimpleNamespace(tools=tools, next_token=None)

client = SimpleNamespace(
list_tools=_list_tools,
get_tool=lambda _: SimpleNamespace(status="Ready"),
create_tool=lambda _: pytest.fail("the legacy Tool must be reused"),
)

assert (
ensure_studio_code_env_tool(
name="studio-demo-codex-123456",
legacy_names=("studio-demo-chat-123456",),
client=client,
timeout_seconds=0,
)
== "legacy-tool"
)
assert requested_names == [
"studio-demo-codex-123456",
"studio-demo-chat-123456",
]


def test_ensure_studio_code_env_tool_creates_ready_code_env() -> None:
requests: list[object] = []

Expand Down Expand Up @@ -391,6 +432,21 @@ def test_studio_sandbox_tool_name_uses_short_studio_format() -> None:
)


def test_codex_tool_name_candidates_prefer_codex_and_fall_back_to_chat() -> None:
assert studio_sandbox_tool_name_candidates("Studio App", "codex") == (
"studio-studio-app-codex-1d66ce",
"studio-studio-app-chat-1d66ce",
)
assert studio_sandbox_tool_name_candidates(
"Studio App",
"codex",
snapshot=True,
) == (
"studio-studio-app-codex-1d66ce_snapshot",
"studio-studio-app-chat-1d66ce_snapshot",
)


def test_studio_sandbox_tool_name_normalizes_invalid_characters() -> None:
assert studio_sandbox_tool_name(" My_App@2026! ", "skill") == (
"studio-my-app-2026-skill-6c7faf"
Expand Down
3 changes: 3 additions & 0 deletions tests/cli/test_studio_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,9 @@ def update_application_code_bundle(self, **kwargs: object) -> str:
assert result.exit_code == 0, result.output
assert len(code_tools) == 1
assert code_tools[0]["enable_snapshot"] is True
assert "-codex-" in str(code_tools[0]["name"])
assert len(code_tools[0]["legacy_names"]) == 1
assert "-chat-" in str(code_tools[0]["legacy_names"][0])
assert str(code_tools[0]["name"]).endswith("_snapshot")
if dev_tool_id is None:
assert len(dev_tools) == 1
Expand Down
17 changes: 12 additions & 5 deletions tests/frontend/server/test_studio_update_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ def _tool(**kwargs: Any) -> str:
]
assert {call["kind"] for call in tool_calls} == {"codex", "openclaw", "hermes"}
assert all(call["application_id"] == "application-id" for call in tool_calls)
assert (
next(call for call in tool_calls if call["kind"] == "codex")["purpose"]
== "codex"
)


def test_reconcile_studio_update_resources_only_repairs_missing_items(
Expand Down Expand Up @@ -228,7 +232,7 @@ def test_provision_codex_snapshot_tool_binds_model_credential(
) -> None:
captured: dict[str, Any] = {}
monkeypatch.setattr(
"veadk.cli.studio_sandbox_tools.studio_sandbox_tool_name",
"veadk.cli.studio_sandbox_tools.studio_sandbox_tool_name_candidates",
lambda application_id, purpose, *, snapshot: (
captured.update(
{
Expand All @@ -237,7 +241,7 @@ def test_provision_codex_snapshot_tool_binds_model_credential(
"snapshot": snapshot,
}
)
or "snapshot-tool-name"
or ("snapshot-tool-name", "legacy-snapshot-tool-name")
),
)
monkeypatch.setattr(
Expand All @@ -255,7 +259,7 @@ def test_provision_codex_snapshot_tool_binds_model_credential(

tool_id = _provision_snapshot_tool(
kind="codex",
purpose="chat",
purpose="codex",
provider="byteplus",
region="ap-southeast-1",
application_id="application-id",
Expand All @@ -265,7 +269,10 @@ def test_provision_codex_snapshot_tool_binds_model_credential(
)

assert tool_id == "tool-id"
assert captured["purpose"] == "codex"
assert captured["snapshot"] is True
assert captured["tool"]["name"] == "snapshot-tool-name"
assert captured["tool"]["legacy_names"] == ("legacy-snapshot-tool-name",)
assert captured["tool"]["enable_snapshot"] is True
assert captured["credential"]["tool_id"] == "tool-id"
assert captured["credential"]["provider"] == "byteplus"
Expand All @@ -277,8 +284,8 @@ def test_provision_agent_snapshot_tool_binds_provider_model(
) -> None:
captured: dict[str, Any] = {}
monkeypatch.setattr(
"veadk.cli.studio_sandbox_tools.studio_sandbox_tool_name",
lambda *_args, **_kwargs: "snapshot-tool-name",
"veadk.cli.studio_sandbox_tools.studio_sandbox_tool_name_candidates",
lambda *_args, **_kwargs: ("snapshot-tool-name",),
)
monkeypatch.setattr(
"veadk.cli.studio_sandbox_tools.studio_sandbox_agent_model_name",
Expand Down
44 changes: 25 additions & 19 deletions veadk/cli/cli_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -8237,8 +8237,8 @@ def frontend_deploy(
"dev": "Dev Sandbox",
}
sandbox_tool_purposes = {
"codex": "chat",
"codex_snapshot": "chat",
"codex": "codex",
"codex_snapshot": "codex",
"openclaw": "openclaw",
"openclaw_snapshot": "openclaw",
"hermes": "hermes",
Expand All @@ -8252,38 +8252,40 @@ def frontend_deploy(
ensure_studio_dev_env_tool,
studio_sandbox_agent_model_name,
studio_sandbox_model_base_url,
studio_sandbox_tool_name,
studio_sandbox_tool_name_candidates,
)

sandbox_agent_model_name = studio_sandbox_agent_model_name(provider_id)
sandbox_model_base_url = studio_sandbox_model_base_url(provider_id)

missing_sandbox_tools: dict[str, str] = {}
missing_sandbox_tools: dict[str, tuple[str, ...]] = {}
for kind, tool_id in sandbox_tool_ids.items():
label = sandbox_tool_labels[kind]
if tool_id:
click.echo(f"Using configured AgentKit {label} Tool '{tool_id}'.")
continue
tool_name = studio_sandbox_tool_name(
tool_names = studio_sandbox_tool_name_candidates(
vefaas_app_name,
sandbox_tool_purposes[kind],
snapshot=kind.endswith("_snapshot"),
)
click.echo(f"Creating AgentKit {label} Tool '{tool_name}'…")
missing_sandbox_tools[kind] = tool_name
click.echo(f"Creating AgentKit {label} Tool '{tool_names[0]}'…")
missing_sandbox_tools[kind] = tool_names

if missing_sandbox_tools:
with ThreadPoolExecutor(max_workers=len(missing_sandbox_tools)) as executor:
tool_futures = {}
for kind, tool_name in missing_sandbox_tools.items():
for kind, tool_names in missing_sandbox_tools.items():
if tool_futures:
sleep(_SANDBOX_TOOL_CREATE_STAGGER_SECONDS)
tool_name = tool_names[0]
base_kind = kind.removesuffix("_snapshot")
enable_snapshot = kind.endswith("_snapshot")
if base_kind == "codex":
future = executor.submit(
ensure_studio_code_env_tool,
name=tool_name,
legacy_names=tool_names[1:],
enable_snapshot=enable_snapshot,
create_min_interval=_SANDBOX_TOOL_CREATE_STAGGER_SECONDS,
region=region,
Expand Down Expand Up @@ -8948,7 +8950,7 @@ def frontend_update(
ensure_studio_code_env_tool,
studio_sandbox_agent_model_name,
studio_sandbox_model_base_url,
studio_sandbox_tool_name,
studio_sandbox_tool_name_candidates,
)

snapshot_labels = {
Expand All @@ -8957,14 +8959,14 @@ def frontend_update(
"hermes_snapshot": "Hermes Snapshot",
}
snapshot_purposes = {
"codex_snapshot": "chat",
"codex_snapshot": "codex",
"openclaw_snapshot": "openclaw",
"hermes_snapshot": "hermes",
}
sandbox_agent_model_name = studio_sandbox_agent_model_name(provider_id)
sandbox_model_base_url = studio_sandbox_model_base_url(provider_id)
missing_snapshot_tools = {
kind: studio_sandbox_tool_name(
kind: studio_sandbox_tool_name_candidates(
vefaas_app_name,
snapshot_purposes[kind],
snapshot=True,
Expand All @@ -8977,16 +8979,18 @@ def frontend_update(
max_workers=len(missing_snapshot_tools)
) as executor:
tool_futures = {}
for kind, tool_name in missing_snapshot_tools.items():
for kind, tool_names in missing_snapshot_tools.items():
if tool_futures:
sleep(_SANDBOX_TOOL_CREATE_STAGGER_SECONDS)
tool_name = tool_names[0]
label = snapshot_labels[kind]
click.echo(f"Creating AgentKit {label} Tool '{tool_name}'…")
base_kind = kind.removesuffix("_snapshot")
if base_kind == "codex":
future = executor.submit(
ensure_studio_code_env_tool,
name=tool_name,
legacy_names=tool_names[1:],
enable_snapshot=True,
create_min_interval=(
_SANDBOX_TOOL_CREATE_STAGGER_SECONDS
Expand Down Expand Up @@ -9126,7 +9130,7 @@ def frontend_update(
}
byteplus_sandbox_purposes = {
"dev": "dev",
"codex": "chat",
"codex": "codex",
"openclaw": "openclaw",
"hermes": "hermes",
}
Expand All @@ -9141,39 +9145,41 @@ def frontend_update(
ensure_studio_dev_env_tool,
studio_sandbox_agent_model_name,
studio_sandbox_model_base_url,
studio_sandbox_tool_name,
studio_sandbox_tool_name_candidates,
)

sandbox_agent_model_name = studio_sandbox_agent_model_name(provider_id)
sandbox_model_base_url = studio_sandbox_model_base_url(provider_id)
missing_sandbox_tools: dict[str, str] = {}
missing_sandbox_tools: dict[str, tuple[str, ...]] = {}
for kind, tool_id in byteplus_sandbox_tool_ids.items():
label = byteplus_sandbox_labels[kind]
if str(tool_id or "").strip():
click.echo(f"Using AgentKit {label} Tool '{tool_id}'.")
continue
tool_name = studio_sandbox_tool_name(
tool_names = studio_sandbox_tool_name_candidates(
vefaas_app_name,
byteplus_sandbox_purposes[kind],
snapshot=kind.endswith("_snapshot"),
)
click.echo(f"Creating AgentKit {label} Tool '{tool_name}'…")
missing_sandbox_tools[kind] = tool_name
click.echo(f"Creating AgentKit {label} Tool '{tool_names[0]}'…")
missing_sandbox_tools[kind] = tool_names

if missing_sandbox_tools:
with ThreadPoolExecutor(
max_workers=len(missing_sandbox_tools)
) as ex:
tool_futures = {}
for kind, tool_name in missing_sandbox_tools.items():
for kind, tool_names in missing_sandbox_tools.items():
if tool_futures:
sleep(_SANDBOX_TOOL_CREATE_STAGGER_SECONDS)
tool_name = tool_names[0]
base_kind = kind.removesuffix("_snapshot")
enable_snapshot = kind.endswith("_snapshot")
if base_kind == "codex":
future = ex.submit(
ensure_studio_code_env_tool,
name=tool_name,
legacy_names=tool_names[1:],
enable_snapshot=enable_snapshot,
create_min_interval=(
_SANDBOX_TOOL_CREATE_STAGGER_SECONDS
Expand Down
Loading
Loading