Skip to content
Draft
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
64 changes: 56 additions & 8 deletions agentkit/toolkit/cli/cli_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""AgentKit CLI - Deploy command implementation."""

from pathlib import Path
from typing import Callable, Dict, Optional
from typing import Callable, Dict, List, Optional

import typer
from rich.console import Console
Expand Down Expand Up @@ -63,6 +63,27 @@ def deploy_command(
"--allowed-id",
help="Comma-separated allowed client IDs for OAuth2/JWT auth (harness deploy).",
),
runtime_network_mode: Optional[str] = typer.Option(
None,
"--runtime-network-mode",
help="Harness Runtime network mode: public, private, or both.",
),
runtime_vpc_id: Optional[str] = typer.Option(
None,
"--runtime-vpc-id",
help="Harness Runtime VPC ID (required for private/both).",
),
runtime_subnet_ids: Optional[List[str]] = typer.Option(
None,
"--runtime-subnet-id",
"--runtime-subnet-ids",
help="Harness Runtime subnet ID. Repeat for multiple subnets.",
),
runtime_enable_shared_internet_access: Optional[bool] = typer.Option(
None,
"--runtime-enable-shared-internet-access/--no-runtime-enable-shared-internet-access",
help="Enable shared internet egress for a private Harness Runtime.",
),
yes: bool = typer.Option(
False,
"--yes",
Expand All @@ -71,10 +92,6 @@ def deploy_command(
),
):
"""Deploy the Agent to target environment."""
from agentkit.toolkit.executors import DeployExecutor
from agentkit.toolkit.cli.console_reporter import ConsoleReporter
from agentkit.toolkit.context import ExecutionContext

if harness is not None:
_deploy_harness(
name=harness,
Expand All @@ -83,10 +100,33 @@ def deploy_command(
secret_key=volcengine_secret_key,
discovery_url=discovery_url,
allowed_id=allowed_id,
runtime_network_mode=runtime_network_mode,
runtime_vpc_id=runtime_vpc_id,
runtime_subnet_ids=runtime_subnet_ids,
runtime_enable_shared_internet_access=(
runtime_enable_shared_internet_access
),
assume_yes=yes,
)
return

if any(
value is not None
for value in (
runtime_network_mode,
runtime_vpc_id,
runtime_subnet_ids,
runtime_enable_shared_internet_access,
)
):
raise typer.BadParameter(
"Runtime network options require --harness.", param_hint="--harness"
)

from agentkit.toolkit.executors import DeployExecutor
from agentkit.toolkit.cli.console_reporter import ConsoleReporter
from agentkit.toolkit.context import ExecutionContext

console.print(f"[green]Deploying with {config_file}[/green]")

# Set execution context - CLI uses ConsoleReporter (with colored output and progress)
Expand Down Expand Up @@ -125,6 +165,10 @@ def _deploy_harness(
secret_key,
discovery_url,
allowed_id,
runtime_network_mode,
runtime_vpc_id,
runtime_subnet_ids,
runtime_enable_shared_internet_access,
assume_yes: bool = False,
):
"""Deploy a harness spec <name>.harness.json from the current directory."""
Expand Down Expand Up @@ -159,6 +203,12 @@ def _deploy_harness(
secret_key=secret_key,
discovery_url=discovery_url,
allowed_id=allowed_id,
runtime_network_mode=runtime_network_mode,
runtime_vpc_id=runtime_vpc_id,
runtime_subnet_ids=runtime_subnet_ids,
runtime_enable_shared_internet_access=(
runtime_enable_shared_internet_access
),
reporter=reporter,
on_conflict=on_conflict,
)
Expand All @@ -185,6 +235,4 @@ def _deploy_harness(
if meta.get("runtime_apikey"):
console.print(f"[green]API key: {meta['runtime_apikey']}[/green]")
if endpoint:
console.print(
f"[green]Recorded in {(Path.cwd() / 'harness.json')}[/green]"
)
console.print(f"[green]Recorded in {(Path.cwd() / 'harness.json')}[/green]")
6 changes: 6 additions & 0 deletions agentkit/toolkit/harness/config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def build_agentkit_config(
envs: Dict[str, str],
auth: Optional[Dict[str, Any]] = None,
runtime_id: str = "Auto",
runtime_network: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Build the cloud AgentKit launch config dict (auto-provision).

Expand All @@ -38,6 +39,9 @@ def build_agentkit_config(
When ``auth`` (a normalized ``{discovery_url, allowed_ids}`` block) is given,
the runtime is gated by OAuth2/JWT (``custom_jwt``); otherwise it keeps the
default API-key auth (``key_auth``).

``runtime_network`` is forwarded to the cloud runner for ``CreateRuntime``.
The runner converts it into the platform's public/private network fields.
"""
cloud: Dict[str, Any] = {
"region": region,
Expand All @@ -64,6 +68,8 @@ def build_agentkit_config(
cloud["runtime_apikey_name"] = "Auto"
cloud["runtime_apikey"] = "Auto"
cloud["runtime_jwt_allowed_clients"] = []
if runtime_network:
cloud["runtime_network"] = runtime_network
return {
"common": {
"agent_name": runtime_name,
Expand Down
27 changes: 27 additions & 0 deletions agentkit/toolkit/harness/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ def deploy_harness(
secret_key: Optional[str] = None,
discovery_url: Optional[str] = None,
allowed_id: Optional[str] = None,
runtime_network_mode: Optional[str] = None,
runtime_vpc_id: Optional[str] = None,
runtime_subnet_ids: Optional[list[str]] = None,
runtime_enable_shared_internet_access: Optional[bool] = None,
reporter: Optional[Reporter] = None,
on_conflict: Optional[Callable[[Dict[str, Any]], bool]] = None,
) -> LifecycleResult:
Expand Down Expand Up @@ -228,6 +232,12 @@ def deploy_harness(
region: AgentKit region (default ``cn-beijing`` or ``VOLCENGINE_REGION``).
access_key / secret_key: Volcengine credentials (default: ``VOLCENGINE_*`` env).
discovery_url / allowed_id: OAuth2/JWT overrides for the spec ``auth`` block.
runtime_network_mode: Runtime network mode: ``public``, ``private``, or
``both``. Network settings apply only when creating a Runtime.
runtime_vpc_id: VPC ID required by ``private`` / ``both`` modes.
runtime_subnet_ids: Optional subnet IDs for the Runtime VPC attachment.
runtime_enable_shared_internet_access: Enable shared internet egress for
a private network.
reporter: Progress reporter forwarded to the launch (default: silent).
on_conflict: Callback consulted when a single same-name harness exists;
returns True to update it, False to abort.
Expand Down Expand Up @@ -279,6 +289,17 @@ def deploy_harness(

resolved_region = region or os.getenv("VOLCENGINE_REGION") or "cn-beijing"

runtime_network = {
key: value
for key, value in {
"mode": runtime_network_mode,
"vpc_id": runtime_vpc_id,
"subnet_ids": runtime_subnet_ids,
"enable_shared_internet_access": runtime_enable_shared_internet_access,
}.items()
if value is not None
}

# Resolve a name collision into a deploy mode. The harness config defaults to
# `runtime_id: Auto` (create new); an existing same-name harness can instead
# be updated in place (new version) after confirmation.
Expand All @@ -300,6 +321,11 @@ def deploy_harness(
"Refusing to update it."
)
existing_id = match["runtime_id"]
if runtime_network:
raise ValueError(
"Runtime network configuration is supported only when creating a "
"Runtime. Delete the existing harness Runtime and deploy again."
)
version = _get_runtime_version(client, existing_id)
version_label = f"v{version}" if version is not None else "unknown"
if reporter:
Expand Down Expand Up @@ -328,6 +354,7 @@ def deploy_harness(
runtime_envs,
auth,
runtime_id=update_runtime_id or "Auto",
runtime_network=runtime_network,
)

# AgentKit's launch path exposes no hook for runtime tags, so tag the runtime
Expand Down
90 changes: 90 additions & 0 deletions tests/toolkit/cli/test_cli_deploy_harness_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for Harness Runtime network deployment options."""

from typer.testing import CliRunner

from agentkit.toolkit.cli import cli_deploy
from agentkit.toolkit.cli.cli import app
from agentkit.toolkit.harness.config_builder import build_agentkit_config


runner = CliRunner()


def test_harness_network_options_are_forwarded(monkeypatch):
captured = {}

def fake_deploy_harness(**kwargs):
captured.update(kwargs)

monkeypatch.setattr(cli_deploy, "_deploy_harness", fake_deploy_harness)

result = runner.invoke(
app,
[
"deploy",
"--harness",
"test-agent",
"--runtime-network-mode",
"private",
"--runtime-vpc-id",
"vpc-123",
"--runtime-subnet-id",
"subnet-a",
"--runtime-subnet-id",
"subnet-b",
"--runtime-enable-shared-internet-access",
],
)

assert result.exit_code == 0, result.output
assert captured["runtime_network_mode"] == "private"
assert captured["runtime_vpc_id"] == "vpc-123"
assert captured["runtime_subnet_ids"] == ["subnet-a", "subnet-b"]
assert captured["runtime_enable_shared_internet_access"] is True


def test_network_options_require_harness():
result = runner.invoke(app, ["deploy", "--runtime-network-mode", "private"])

assert result.exit_code != 0
assert "Runtime network options require --harness" in result.output


def test_config_builder_includes_runtime_network():
runtime_network = {
"mode": "private",
"vpc_id": "vpc-123",
"subnet_ids": ["subnet-a"],
"enable_shared_internet_access": True,
}

config = build_agentkit_config(
runtime_name="test-agent",
region="cn-shanghai",
envs={},
runtime_network=runtime_network,
)

assert config["launch_types"]["cloud"]["runtime_network"] == runtime_network


def test_config_builder_omits_empty_runtime_network():
config = build_agentkit_config(
runtime_name="test-agent", region="cn-shanghai", envs={}
)

assert "runtime_network" not in config["launch_types"]["cloud"]