diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index a5491b1e..ae2a3038 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -16,7 +16,7 @@ permissions: jobs: test: - name: Release tests (Python ${{ matrix.python-version }}) + name: Security tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest strategy: fail-fast: false @@ -52,11 +52,7 @@ jobs: - name: Install package and test dependencies run: | python -m pip install --upgrade pip - python -m pip install ".[identity-runtime]" \ - -e packages/agentkit-harness-sidecar-integration \ - pytest \ - "setuptools>=77" \ - wheel + python -m pip install ".[identity-runtime]" pytest - name: Run identity and Agent Server security tests run: | @@ -66,14 +62,6 @@ jobs: tests/apps/test_agent_server_invoke.py \ tests/apps/test_agent_server_loader.py - - name: Run Harness Sidecar integration tests - run: | - python -m pytest -q \ - packages/agentkit-harness-sidecar-integration/tests \ - tests/test_optional_sidecar_packaging.py \ - tests/toolkit/cli/test_plugin_loader.py \ - tests/toolkit/test_harness_extension_hooks.py - publish: name: Build and publish needs: test @@ -165,17 +153,13 @@ jobs: PY - name: Build release artifacts - run: | - python -m build --outdir dist/sdk - python -m build \ - --outdir dist/integration \ - packages/agentkit-harness-sidecar-integration + run: python -m build --outdir dist - name: Verify artifacts shell: bash run: | set -euo pipefail - python -m twine check dist/sdk/* dist/integration/* + python -m twine check dist/* python - <<'PY' from __future__ import annotations @@ -185,17 +169,9 @@ jobs: from pathlib import Path expected = "${{ steps.version.outputs.version }}" - integration_version = "0.1.0" - sdk_dist = Path("dist/sdk") - integration_dist = Path("dist/integration") - wheel = next(sdk_dist.glob("agentkit_sdk_python-*.whl")) - sdist = next(sdk_dist.glob("agentkit_sdk_python-*.tar.gz")) - integration_wheel = next( - integration_dist.glob("agentkit_harness_sidecar_integration-*.whl") - ) - integration_sdist = next( - integration_dist.glob("agentkit_harness_sidecar_integration-*.tar.gz") - ) + dist = Path("dist") + wheel = next(dist.glob("agentkit_sdk_python-*.whl")) + sdist = next(dist.glob("agentkit_sdk_python-*.tar.gz")) with zipfile.ZipFile(wheel) as archive: names = archive.namelist() @@ -205,16 +181,6 @@ jobs: ) metadata = archive.read(metadata_name).decode() - with zipfile.ZipFile(integration_wheel) as archive: - integration_names = archive.namelist() - integration_metadata_name = next( - name for name in integration_names - if name.endswith(".dist-info/METADATA") - ) - integration_metadata = archive.read( - integration_metadata_name - ).decode() - with tarfile.open(sdist) as archive: root = archive.getnames()[0].split("/", 1)[0] pyproject = archive.extractfile(f"{root}/pyproject.toml").read().decode() @@ -222,44 +188,11 @@ jobs: archive.extractfile(f"{root}/agentkit/version.py").read().decode() ) - with tarfile.open(integration_sdist) as archive: - integration_root = archive.getnames()[0].split("/", 1)[0] - integration_pyproject = archive.extractfile( - f"{integration_root}/pyproject.toml" - ).read().decode() - - normalized_sdk_metadata = metadata.lower().replace("_", "-") - normalized_integration_metadata = ( - integration_metadata.lower().replace("_", "-") - ) - module_prefix = "agentkit/extensions/harness_sidecar/" - checks = { "wheel metadata": f"Version: {expected}" in metadata, "wheel version.py": f'VERSION = "{expected}"' in version_py, "sdist pyproject": f'version = "{expected}"' in pyproject, "sdist version.py": f'VERSION = "{expected}"' in sdist_version_py, - "SDK excludes optional integration modules": not any( - name.startswith(module_prefix) for name in names - ), - "SDK declares the Sidecar extra": ( - "provides-extra: harness-sidecar" in normalized_sdk_metadata - and "agentkit-harness-sidecar-integration" in normalized_sdk_metadata - ), - "integration wheel version": ( - f"version: {integration_version}" - in normalized_integration_metadata - ), - "integration depends on SDK 0.8.2": ( - "agentkit-sdk-python<0.9.0,>=0.8.2" - in normalized_integration_metadata - ), - "integration wheel contains public modules": any( - name.startswith(module_prefix) for name in integration_names - ), - "integration sdist version": ( - f'version = "{integration_version}"' in integration_pyproject - ), } failed = [name for name, ok in checks.items() if not ok] if failed: @@ -279,79 +212,29 @@ jobs: import venv from pathlib import Path - base_smoke = Path("/tmp/agentkit-base-wheel-smoke") - sidecar_smoke = Path("/tmp/agentkit-sidecar-wheel-smoke") - venv.EnvBuilder(with_pip=True, clear=True).create(base_smoke) - venv.EnvBuilder(with_pip=True, clear=True).create(sidecar_smoke) - base_python = base_smoke / "bin" / "python" - sidecar_python = sidecar_smoke / "bin" / "python" - sdk_dist = Path("dist/sdk") - integration_dist = Path("dist/integration") - wheel = next(sdk_dist.glob("agentkit_sdk_python-*.whl")) - + smoke = Path("/tmp/agentkit-wheel-smoke") + venv.EnvBuilder(with_pip=True, clear=True).create(smoke) + python = smoke / "bin" / "python" + wheel = next(Path("dist").glob("agentkit_sdk_python-*.whl")) subprocess.check_call( - [ - str(base_python), - "-m", - "pip", - "install", - f"{wheel}[identity-runtime]", - ] + [str(python), "-m", "pip", "install", f"{wheel}[identity-runtime]"] ) subprocess.check_call( [ - str(base_python), + str(python), "-I", "-c", ( - "import importlib.util; " "from agentkit.identity import IdentityRuntimeConfig, " "RuntimeIdentity; " - "from agentkit.apps import AgentkitAgentServerApp; " - "assert importlib.util.find_spec(" - "'agentkit.extensions.harness_sidecar') is None" - ), - ] - ) - - subprocess.check_call( - [ - str(sidecar_python), - "-m", - "pip", - "install", - "--find-links", - str(integration_dist), - f"{wheel}[identity-runtime,harness-sidecar]", - ] - ) - subprocess.check_call( - [ - str(sidecar_python), - "-I", - "-c", - ( - "from agentkit.identity import IdentityRuntimeConfig, " - "RuntimeIdentity; " - "from agentkit.apps import AgentkitAgentServerApp; " - "from agentkit.extensions.harness_sidecar import " - "HarnessSidecarConfig, resolve_harness_sidecar_selection" + "from agentkit.apps import AgentkitAgentServerApp" ), ] ) PY - - name: Publish Harness Sidecar integration to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_SIDECAR_INTEGRATION_API_TOKEN }} - packages-dir: dist/integration - skip-existing: true - - - name: Publish AgentKit SDK to PyPI + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} - packages-dir: dist/sdk diff --git a/agentkit/extensions/__init__.py b/agentkit/extensions/__init__.py deleted file mode 100644 index 258bafa3..00000000 --- a/agentkit/extensions/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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. - -"""Optional AgentKit extensions.""" - -__all__: list[str] = [] diff --git a/agentkit/toolkit/cli/cli.py b/agentkit/toolkit/cli/cli.py index a22b19ec..93c7df15 100644 --- a/agentkit/toolkit/cli/cli.py +++ b/agentkit/toolkit/cli/cli.py @@ -50,7 +50,6 @@ from agentkit.toolkit.cli.cli_list import list_app from agentkit.toolkit.cli.cli_delete import delete_app from agentkit.toolkit.cli.cli_logs import logs_command -from agentkit.toolkit.cli.plugin_loader import load_cli_plugins # Note: Avoid importing heavy packages at the top to keep CLI startup fast @@ -135,7 +134,6 @@ def main( app.add_typer(add_app, name="add") app.add_typer(list_app, name="list") app.add_typer(delete_app, name="delete") -load_cli_plugins(app) if __name__ == "__main__": diff --git a/agentkit/toolkit/cli/plugin_loader.py b/agentkit/toolkit/cli/plugin_loader.py deleted file mode 100644 index d5402054..00000000 --- a/agentkit/toolkit/cli/plugin_loader.py +++ /dev/null @@ -1,72 +0,0 @@ -# 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. - -"""Discover optional AgentKit CLI plugins without importing them from core.""" - -from __future__ import annotations - -from collections.abc import Iterable -from importlib.metadata import EntryPoint, entry_points -from typing import Any - -import typer - -CLI_PLUGIN_GROUP = "agentkit.cli_plugins" - - -def load_cli_plugins( - root: typer.Typer, - *, - plugins: Iterable[EntryPoint] | None = None, -) -> None: - """Register installed CLI plugins; an empty environment is a no-op.""" - - discovered = ( - entry_points(group=CLI_PLUGIN_GROUP) if plugins is None else list(plugins) - ) - for entry_point in sorted(discovered, key=lambda item: item.name): - try: - plugin: Any = entry_point.load() - except Exception as error: - _warn_plugin_failure(entry_point.name, "load", error) - continue - - try: - if isinstance(plugin, typer.Typer): - root.add_typer(plugin, name=entry_point.name) - continue - if callable(plugin): - plugin(root) - continue - typer.echo( - "Warning: ignoring AgentKit CLI plugin " - f"{entry_point.name!r}: expected a Typer or callable, " - f"got {type(plugin).__name__}.", - err=True, - ) - except Exception as error: - _warn_plugin_failure(entry_point.name, "registration", error) - - -def _warn_plugin_failure(name: str, stage: str, error: Exception) -> None: - """Report an isolated plugin failure without leaking exception details.""" - - typer.echo( - f"Warning: AgentKit CLI plugin {name!r} failed during {stage} " - f"({type(error).__name__}); continuing without it.", - err=True, - ) - - -__all__ = ["CLI_PLUGIN_GROUP", "load_cli_plugins"] diff --git a/agentkit/toolkit/harness/config_builder.py b/agentkit/toolkit/harness/config_builder.py index a40b6782..f457a559 100644 --- a/agentkit/toolkit/harness/config_builder.py +++ b/agentkit/toolkit/harness/config_builder.py @@ -23,7 +23,6 @@ def build_agentkit_config( envs: Dict[str, str], auth: Optional[Dict[str, Any]] = None, runtime_id: str = "Auto", - cloud_config_overrides: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Build the cloud AgentKit launch config dict (auto-provision). @@ -65,8 +64,6 @@ def build_agentkit_config( cloud["runtime_apikey_name"] = "Auto" cloud["runtime_apikey"] = "Auto" cloud["runtime_jwt_allowed_clients"] = [] - if cloud_config_overrides: - cloud.update(cloud_config_overrides) return { "common": { "agent_name": runtime_name, diff --git a/agentkit/toolkit/harness/deploy.py b/agentkit/toolkit/harness/deploy.py index 7c0fa906..9ad925fd 100644 --- a/agentkit/toolkit/harness/deploy.py +++ b/agentkit/toolkit/harness/deploy.py @@ -198,8 +198,6 @@ def deploy_harness( secret_key: Optional[str] = None, discovery_url: Optional[str] = None, allowed_id: Optional[str] = None, - runtime_env_builder: Optional[Callable[[Dict[str, Any]], Dict[str, str]]] = None, - cloud_config_overrides: Optional[Dict[str, Any]] = None, reporter: Optional[Reporter] = None, on_conflict: Optional[Callable[[Dict[str, Any]], bool]] = None, ) -> LifecycleResult: @@ -230,8 +228,6 @@ 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_env_builder: Optional environment builder used by extensions. - cloud_config_overrides: Optional cloud launch configuration overrides. 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. @@ -263,7 +259,7 @@ def deploy_harness( os.environ.setdefault(key, value) spec = _load_harness_spec(proj_dir / f"{name}.harness.json") - runtime_envs = (runtime_env_builder or to_runtime_env)(spec) + runtime_envs = to_runtime_env(spec) runtime_name = name auth = _resolve_auth(spec.get("auth"), discovery_url, allowed_id) @@ -332,7 +328,6 @@ def deploy_harness( runtime_envs, auth, runtime_id=update_runtime_id or "Auto", - cloud_config_overrides=cloud_config_overrides, ) # AgentKit's launch path exposes no hook for runtime tags, so tag the runtime diff --git a/packages/agentkit-harness-sidecar-integration/README.md b/packages/agentkit-harness-sidecar-integration/README.md deleted file mode 100644 index f4c84b0f..00000000 --- a/packages/agentkit-harness-sidecar-integration/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# AgentKit Harness Sidecar Integration - -Public integration for configuring and launching the private Harness Sidecar -Runtime. Projects that use the capability can declare the SDK extra: - -```bash -pip install "agentkit-sdk-python[harness-sidecar]" -``` - -This public integration is a separate optional distribution. A default -`agentkit-sdk-python` installation does not contain -`agentkit.extensions.harness_sidecar`, does not register the `agentkit harness` -CLI group, and does not add any Sidecar dependency. Install the integration only -through the SDK extra shown above. - -The private Runtime wheel is installed only while AgentKit builds a managed, -immutable Sidecar-enabled Runtime image in an authorized cloud environment. -Customer projects and deploy machines never resolve or download the private -wheel. The final application image retains the compiled Runtime but not the -wheel, private package index, source, or build cache. - -At Runtime startup, veADK's `HarnessExtension` owns the single Sidecar process. -For managed cloud delivery, `apig_runtime_port` sends model requests through the -Runtime Gateway with fixed `X-Faas-Proxy-Port` routing. It also rewrites the -configured managed-Toolset URL (for example, `TOOL_MCP_ROUTER_URL`) to a local -relay. That relay reaches the Sidecar MCP Gateway through APIG on fixed port -18788; the Sidecar replaces Runtime Gateway authorization with the separately -configured Toolset authorization before calling the upstream Toolset. Missing -Runtime or Toolset authorization fails closed. Missing private Runtime support -is an incompatible-image error: customers should select a Sidecar-enabled -managed image or disable the capability, not install a private package -themselves. diff --git a/packages/agentkit-harness-sidecar-integration/pyproject.toml b/packages/agentkit-harness-sidecar-integration/pyproject.toml deleted file mode 100644 index 1b16a7e7..00000000 --- a/packages/agentkit-harness-sidecar-integration/pyproject.toml +++ /dev/null @@ -1,31 +0,0 @@ -[build-system] -requires = ["setuptools>=77", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "agentkit-harness-sidecar-integration" -version = "0.1.0" -description = "Optional public AgentKit integration for managed Harness Sidecar runtimes" -readme = "README.md" -requires-python = ">=3.10" -license = "Apache-2.0" -dependencies = [ - "agentkit-sdk-python>=0.8.2,<0.9.0", - "pydantic>=2.11.9", - "typer", -] - -[project.entry-points."agentkit.cli_plugins"] -harness = "agentkit.extensions.harness_sidecar.cli:harness_app" - -[tool.setuptools.packages.find] -where = ["src"] -include = ["agentkit.extensions.harness_sidecar*"] -namespaces = true - -[tool.ruff] -line-length = 88 - -[tool.ruff.lint.per-file-ignores] -"__init__.py" = ["F401"] -"tests/*" = ["F401", "F811"] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/__init__.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/__init__.py deleted file mode 100644 index 690599ce..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/__init__.py +++ /dev/null @@ -1,93 +0,0 @@ -# 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. - -"""Optional AgentKit Harness Sidecar integration.""" - -from .component_catalog import ( - CATALOG_SCHEMA_VERSION, - CATALOG_VERSION, - PRODUCT_COMPONENT_ORDER, - ComponentAvailability, - HarnessComponentDefinition, - HarnessProfileDefinition, - HarnessSidecarCatalog, - get_harness_sidecar_catalog, -) -from .deploy import build_runtime_network, deploy_harness, to_runtime_env -from .runtime_components import ( - RUNTIME_COMPONENT_ORDER, - resolve_runtime_components, - runtime_flavor_for_components, -) -from .selection import ( - PLAN_SCHEMA_VERSION, - AutoAddedComponent, - HarnessActivationTargets, - HarnessSelectionIntent, - ResolvedHarnessPlan, - resolve_harness_sidecar_selection, -) -from .sidecar import ( - HarnessSidecarError, - HarnessSidecarRuntimeUnavailable, - SidecarBinding, - doctor_harness_sidecar, - export_sidecar_env, - run_with_harness_sidecar, - start_harness_sidecar, -) -from .sidecar_config import ( - HarnessSidecarConfig, - MCPGatewayConfig, - ModelProxyConfig, - SidecarBindingSpec, - resolve_sidecar_config, - sidecar_config_to_env, -) - -__all__ = [ - "AutoAddedComponent", - "CATALOG_SCHEMA_VERSION", - "CATALOG_VERSION", - "ComponentAvailability", - "HarnessActivationTargets", - "HarnessComponentDefinition", - "HarnessProfileDefinition", - "HarnessSelectionIntent", - "HarnessSidecarCatalog", - "HarnessSidecarConfig", - "HarnessSidecarError", - "HarnessSidecarRuntimeUnavailable", - "MCPGatewayConfig", - "ModelProxyConfig", - "PLAN_SCHEMA_VERSION", - "PRODUCT_COMPONENT_ORDER", - "RUNTIME_COMPONENT_ORDER", - "ResolvedHarnessPlan", - "SidecarBinding", - "SidecarBindingSpec", - "build_runtime_network", - "deploy_harness", - "doctor_harness_sidecar", - "export_sidecar_env", - "get_harness_sidecar_catalog", - "resolve_harness_sidecar_selection", - "resolve_runtime_components", - "resolve_sidecar_config", - "run_with_harness_sidecar", - "runtime_flavor_for_components", - "sidecar_config_to_env", - "start_harness_sidecar", - "to_runtime_env", -] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/cli.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/cli.py deleted file mode 100644 index 6e9f9c95..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/cli.py +++ /dev/null @@ -1,371 +0,0 @@ -"""Public ``agentkit harness sidecar`` commands.""" - -from __future__ import annotations - -import json -import shlex -import sys -from pathlib import Path -from typing import Annotated - -import typer - -from agentkit.toolkit.harness import HarnessDeployAborted - -from .component_catalog import ( - CATALOG_VERSION, - get_harness_sidecar_catalog, -) -from .deploy import deploy_harness -from .selection import resolve_harness_sidecar_selection -from .sidecar import ( - HarnessSidecarError, - doctor_harness_sidecar, - run_with_harness_sidecar, - start_harness_sidecar, -) -from .sidecar_config import ( - HarnessSidecarConfig, - sidecar_config_to_env, -) - - -harness_app = typer.Typer(help="AgentKit Harness capabilities.") -sidecar_app = typer.Typer(help="Configure and run zero-intrusion Harness Sidecar.") -harness_app.add_typer(sidecar_app, name="sidecar") - - -@sidecar_app.command("catalog") -def catalog( - profile: Annotated[str, typer.Option("--profile")] = "ops", -) -> None: - """Print the Runtime-independent Product Component Catalog.""" - - try: - value = get_harness_sidecar_catalog(profile) - except ValueError as error: - raise typer.BadParameter(str(error)) from error - typer.echo(value.model_dump_json(indent=2)) - - -@sidecar_app.command("resolve") -def resolve_selection( - profile: Annotated[str, typer.Option("--profile")] = "ops", - enabled: Annotated[bool, typer.Option("--enabled/--disabled")] = True, - component: Annotated[ - list[str] | None, - typer.Option( - "--component", help="Product component override as ID=true|false." - ), - ] = None, - catalog_version: Annotated[ - str, typer.Option("--catalog-version") - ] = CATALOG_VERSION, - runtime_version: Annotated[str | None, typer.Option("--runtime-version")] = None, -) -> None: - """Resolve Product Component overrides into a deterministic plan.""" - - overrides: dict[str, bool] = {} - for raw in component or []: - component_id, separator, selected = raw.partition("=") - normalized = selected.strip().lower() - if not separator or normalized not in {"true", "false"}: - raise typer.BadParameter( - f"invalid component override '{raw}'; expected ID=true|false" - ) - overrides[component_id.strip()] = normalized == "true" - plan = resolve_harness_sidecar_selection( - enabled=enabled, - profile=profile, - component_overrides=overrides, - catalog_version=catalog_version, - runtime_version=runtime_version, - ) - typer.echo(plan.model_dump_json(indent=2, exclude_none=True)) - if not plan.valid: - raise typer.Exit(1) - - -def _config( - *, - profile: str | None, - config_path: Path | None, - model_proxy: bool | None, - mcp_gateway: bool | None, - model_upstream_env: str | None, - compression_provider: str | None, - mcp_upstreams_env: str | None, - presets: list[str] | None, - readonly_segments: list[str] | None, -) -> HarnessSidecarConfig: - raw: dict = {} - if config_path is not None: - value = json.loads(config_path.read_text(encoding="utf-8")) - if not isinstance(value, dict): - raise typer.BadParameter("sidecar config must be a JSON object") - raw.update(value) - if profile is not None: - raw["profile"] = profile - else: - raw.setdefault("profile", "ops") - - model_overrides = {} - if model_proxy is not None: - model_overrides["enabled"] = model_proxy - if model_upstream_env is not None: - model_overrides["upstream_base_url_env"] = model_upstream_env - if compression_provider is not None: - model_overrides["compression_provider"] = compression_provider - if model_overrides: - raw.setdefault("model_proxy", {}).update(model_overrides) - - mcp_overrides = {} - if mcp_gateway is not None: - mcp_overrides["enabled"] = mcp_gateway - if mcp_upstreams_env is not None: - mcp_overrides["upstreams_env"] = mcp_upstreams_env - if presets is not None: - mcp_overrides["presets"] = presets - if readonly_segments is not None: - mcp_overrides["readonly_segments"] = readonly_segments - if mcp_overrides: - raw.setdefault("mcp_gateway", {}).update(mcp_overrides) - return HarnessSidecarConfig.model_validate(raw) - - -def _common_config( - profile: str | None, - config: Path | None, - model_proxy: bool | None, - mcp_gateway: bool | None, - model_upstream_env: str | None, - compression_provider: str | None, - mcp_upstreams_env: str | None, - preset: list[str] | None, - readonly_segment: list[str] | None, -) -> HarnessSidecarConfig: - return _config( - profile=profile, - config_path=config, - model_proxy=model_proxy, - mcp_gateway=mcp_gateway, - model_upstream_env=model_upstream_env, - compression_provider=compression_provider, - mcp_upstreams_env=mcp_upstreams_env, - presets=preset, - readonly_segments=readonly_segment, - ) - - -@sidecar_app.command("doctor") -def doctor( - profile: Annotated[str, typer.Option("--profile")] = "ops", -) -> None: - try: - report = doctor_harness_sidecar({"profile": profile}) - except HarnessSidecarError as error: - typer.echo(str(error), err=True) - raise typer.Exit(1) from error - typer.echo(json.dumps(report, ensure_ascii=False, indent=2)) - - -@sidecar_app.command("start") -def start( - profile: Annotated[str | None, typer.Option("--profile")] = None, - config: Annotated[Path | None, typer.Option("--config")] = None, - model_proxy: Annotated[ - bool | None, typer.Option("--model-proxy/--no-model-proxy") - ] = None, - mcp_gateway: Annotated[ - bool | None, typer.Option("--mcp-gateway/--no-mcp-gateway") - ] = None, - model_upstream_env: Annotated[ - str | None, typer.Option("--model-upstream-env") - ] = None, - compression_provider: Annotated[ - str | None, typer.Option("--compression-provider") - ] = None, - mcp_upstreams_env: Annotated[ - str | None, typer.Option("--mcp-upstreams-env") - ] = None, - preset: Annotated[list[str] | None, typer.Option("--preset")] = None, - readonly_segment: Annotated[ - list[str] | None, typer.Option("--readonly-segment") - ] = None, -) -> None: - resolved = _common_config( - profile, - config, - model_proxy, - mcp_gateway, - model_upstream_env, - compression_provider, - mcp_upstreams_env, - preset, - readonly_segment, - ) - try: - with start_harness_sidecar(resolved) as binding: - typer.echo(binding.spec.model_dump_json(indent=2)) - if binding.process is not None: - binding.process.wait() - except (HarnessSidecarError, KeyboardInterrupt) as error: - if isinstance(error, HarnessSidecarError): - typer.echo(str(error), err=True) - raise typer.Exit(1) from error - - -@sidecar_app.command( - "run", - context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, -) -def run( - ctx: typer.Context, - profile: Annotated[str | None, typer.Option("--profile")] = None, - config: Annotated[Path | None, typer.Option("--config")] = None, - model_proxy: Annotated[ - bool | None, typer.Option("--model-proxy/--no-model-proxy") - ] = None, - mcp_gateway: Annotated[ - bool | None, typer.Option("--mcp-gateway/--no-mcp-gateway") - ] = None, - model_upstream_env: Annotated[ - str | None, typer.Option("--model-upstream-env") - ] = None, - compression_provider: Annotated[ - str | None, typer.Option("--compression-provider") - ] = None, - mcp_upstreams_env: Annotated[ - str | None, typer.Option("--mcp-upstreams-env") - ] = None, - preset: Annotated[list[str] | None, typer.Option("--preset")] = None, - readonly_segment: Annotated[ - list[str] | None, typer.Option("--readonly-segment") - ] = None, -) -> None: - command = list(ctx.args) - if command and command[0] == "--": - command.pop(0) - if not command: - raise typer.BadParameter("provide a command after '--'") - resolved = _common_config( - profile, - config, - model_proxy, - mcp_gateway, - model_upstream_env, - compression_provider, - mcp_upstreams_env, - preset, - readonly_segment, - ) - try: - exit_code = run_with_harness_sidecar(resolved, command) - except HarnessSidecarError as error: - typer.echo(str(error), err=True) - raise typer.Exit(1) from error - raise typer.Exit(exit_code) - - -@sidecar_app.command("export-env") -def export_env( - profile: Annotated[str | None, typer.Option("--profile")] = None, - config: Annotated[Path | None, typer.Option("--config")] = None, - shell: Annotated[bool, typer.Option("--shell")] = False, -) -> None: - raw = {} - if config is not None: - raw = json.loads(config.read_text(encoding="utf-8")) - if profile is not None: - raw["profile"] = profile - else: - raw.setdefault("profile", "ops") - values = sidecar_config_to_env(raw) - if shell: - for key, value in values.items(): - typer.echo(f"export {key}={shlex.quote(value)}") - return - typer.echo(json.dumps(values, ensure_ascii=False, indent=2)) - - -def _prompt_harness_update(info: dict) -> bool: - version = info.get("version") - version_label = f"v{version}" if version is not None else "unknown" - return typer.confirm( - f"Harness '{info['name']}' already exists (current version " - f"{version_label}). Update it to a new version?", - default=False, - ) - - -@sidecar_app.command("deploy") -def deploy( - name: Annotated[str, typer.Argument(help="Harness spec name.")], - region: Annotated[str | None, typer.Option("--region")] = None, - access_key: Annotated[str | None, typer.Option("--volcengine-access-key")] = None, - secret_key: Annotated[str | None, typer.Option("--volcengine-secret-key")] = None, - discovery_url: Annotated[str | None, typer.Option("--discovery-url")] = None, - allowed_id: Annotated[str | None, typer.Option("--allowed-id")] = None, - runtime_network_mode: Annotated[ - str | None, typer.Option("--runtime-network-mode") - ] = None, - runtime_vpc_id: Annotated[str | None, typer.Option("--runtime-vpc-id")] = None, - runtime_subnet_ids: Annotated[ - list[str] | None, - typer.Option("--runtime-subnet-id", "--runtime-subnet-ids"), - ] = None, - runtime_enable_shared_internet_access: Annotated[ - bool | None, - typer.Option( - "--runtime-enable-shared-internet-access/--no-runtime-enable-shared-internet-access" - ), - ] = None, - yes: Annotated[bool, typer.Option("--yes", "-y")] = False, -) -> None: - """Deploy a Sidecar-enabled Harness with optional Runtime VPC settings.""" - - from agentkit.toolkit.cli.console_reporter import ConsoleReporter - from agentkit.toolkit.context import ExecutionContext - - reporter = ConsoleReporter() - ExecutionContext.set_reporter(reporter) - on_conflict = None - if yes: - on_conflict = lambda info: True # noqa: E731 - elif sys.stdin.isatty(): - on_conflict = _prompt_harness_update - - try: - result = deploy_harness( - name=name, - region=region, - access_key=access_key, - 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, - ) - except HarnessDeployAborted: - typer.echo("Harness deploy cancelled.") - return - except (ValueError, FileNotFoundError, NotADirectoryError) as error: - typer.echo(f"Harness deploy failed: {error}", err=True) - raise typer.Exit(1) from error - - if not result.success: - typer.echo(f"Harness deploy failed: {result.error}", err=True) - raise typer.Exit(1) - - deploy_result = result.deploy_result - metadata = (deploy_result and deploy_result.metadata) or {} - typer.echo(f"Harness Runtime deployed: {metadata.get('runtime_id', name)}") - - -__all__ = ["harness_app", "sidecar_app"] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/component_catalog.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/component_catalog.py deleted file mode 100644 index be7b3164..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/component_catalog.py +++ /dev/null @@ -1,232 +0,0 @@ -"""Stable Product Component Catalog for Harness Sidecar control planes.""" - -from __future__ import annotations - -from collections.abc import Mapping -from copy import deepcopy -from typing import Any - -from pydantic import BaseModel, ConfigDict, Field - -from .profiles import PROFILE_DEFAULTS, PROFILE_METADATA, profile_default_components - - -CATALOG_SCHEMA_VERSION = "agentkit.harness-sidecar.catalog/v1" -CATALOG_VERSION = "2026.07.1" -PRODUCT_COMPONENT_ORDER = ( - "context_engine", - "compressor", - "verifier", - "long_run_control", - "mcp_resilience", - "sql_readonly", - "browser", - "evaluation", - "shadow", -) - - -class CatalogModel(BaseModel): - model_config = ConfigDict(extra="forbid") - - -class ComponentAvailability(CatalogModel): - available: bool - status: str - reason: str | None = None - min_runtime_version: str | None = None - regions: list[str] = Field(default_factory=list) - - -class HarnessComponentDefinition(CatalogModel): - id: str - display_name: str - description: str - category: str - dependencies: list[str] = Field(default_factory=list) - risk_level: str = "standard" - status: str - settings_schema_ref: str | None = None - selected_by_profile: bool = False - availability: ComponentAvailability - - -class HarnessProfileDefinition(CatalogModel): - id: str - display_name: str - description: str - default_components: list[str] - profile_component_count: int - - -class HarnessSidecarCatalog(CatalogModel): - schema_version: str = CATALOG_SCHEMA_VERSION - catalog_version: str = CATALOG_VERSION - profile_count: int - profiles: list[HarnessProfileDefinition] - selected_profile: HarnessProfileDefinition - components: list[HarnessComponentDefinition] - total_component_count: int - selectable_component_count: int - - -_COMPONENT_REGISTRY: dict[str, dict[str, Any]] = { - "context_engine": { - "display_name": "上下文治理", - "description": "治理上下文组装、任务锚定和上下文预算。", - "category": "model", - }, - "compressor": { - "display_name": "上下文与结果压缩", - "description": "压缩长上下文和大型工具结果,降低 Token 成本。", - "category": "model", - }, - "verifier": { - "display_name": "回答校验与修复", - "description": "校验证据和回答,在失败时执行修复或告警。", - "category": "model", - }, - "long_run_control": { - "display_name": "长任务控制", - "description": "管理长任务进度、续跑和结束条件。", - "category": "model", - }, - "mcp_resilience": { - "display_name": "MCP 稳定性治理", - "description": "治理连接、超时、空结果、大返回、预算和结构化错误。", - "category": "tool", - }, - "sql_readonly": { - "display_name": "SQL 只读保护", - "description": "拦截写 SQL,并对所选 MCP 路由提供只读保护。", - "category": "security", - "dependencies": ["mcp_resilience"], - "risk_level": "high_when_disabled", - }, - "browser": { - "display_name": "浏览器任务增强", - "description": "增强浏览器动作、结果、grounding 和 trace。", - "category": "browser", - "availability": { - "available": False, - "status": "preview", - "reason": "当前 Runtime 尚未提供 Browser Controller", - }, - }, - "evaluation": { - "display_name": "在线评测", - "description": "提供评测采样、指标和回归数据。", - "category": "quality", - "dependencies": ["browser"], - "availability": { - "available": False, - "status": "preview", - "reason": "当前 Runtime 尚未提供 Evaluation Controller", - }, - }, - "shadow": { - "display_name": "Shadow 对照运行", - "description": "执行不影响主链路的影子运行和结果对照。", - "category": "quality", - "dependencies": ["evaluation"], - "availability": { - "available": False, - "status": "preview", - "reason": "当前 Runtime 尚未提供 Shadow Controller", - }, - }, -} - - -def get_harness_sidecar_catalog( - profile: str = "ops", - *, - availability_overrides: Mapping[str, ComponentAvailability | Mapping[str, Any]] - | None = None, -) -> HarnessSidecarCatalog: - """Return a Runtime-independent Product Component Catalog snapshot.""" - - selected_ids = set(profile_default_components(profile)) - overrides = dict(availability_overrides or {}) - unknown = sorted(set(overrides) - set(PRODUCT_COMPONENT_ORDER)) - if unknown: - raise ValueError( - "Unknown Harness Product Component availability override: " - + ", ".join(unknown) - ) - - profiles = [_profile_definition(profile_id) for profile_id in PROFILE_DEFAULTS] - selected_profile = next(item for item in profiles if item.id == profile) - components = [ - _component_definition( - component_id, - selected=component_id in selected_ids, - availability_override=overrides.get(component_id), - ) - for component_id in PRODUCT_COMPONENT_ORDER - ] - return HarnessSidecarCatalog( - profile_count=len(profiles), - profiles=profiles, - selected_profile=selected_profile, - components=components, - total_component_count=len(components), - selectable_component_count=sum( - component.availability.available for component in components - ), - ) - - -def _profile_definition(profile: str) -> HarnessProfileDefinition: - components = list(profile_default_components(profile)) - metadata = PROFILE_METADATA[profile] - return HarnessProfileDefinition( - id=profile, - display_name=metadata["display_name"], - description=metadata["description"], - default_components=components, - profile_component_count=len(components), - ) - - -def _component_definition( - component_id: str, - *, - selected: bool, - availability_override: ComponentAvailability | Mapping[str, Any] | None, -) -> HarnessComponentDefinition: - definition = deepcopy(_COMPONENT_REGISTRY[component_id]) - availability_value = definition.pop( - "availability", {"available": True, "status": "ga"} - ) - if availability_override is not None: - override = ( - availability_override.model_dump(exclude_unset=True) - if isinstance(availability_override, ComponentAvailability) - else dict(availability_override) - ) - availability_value.update(override) - if override.get("available") is True and "reason" not in override: - availability_value["reason"] = None - availability = ComponentAvailability.model_validate(availability_value) - return HarnessComponentDefinition( - id=component_id, - dependencies=list(definition.pop("dependencies", [])), - risk_level=definition.pop("risk_level", "standard"), - status=availability.status, - selected_by_profile=selected, - availability=availability, - **definition, - ) - - -__all__ = [ - "CATALOG_SCHEMA_VERSION", - "CATALOG_VERSION", - "PRODUCT_COMPONENT_ORDER", - "ComponentAvailability", - "HarnessComponentDefinition", - "HarnessProfileDefinition", - "HarnessSidecarCatalog", - "get_harness_sidecar_catalog", -] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/deploy.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/deploy.py deleted file mode 100644 index e5370163..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/deploy.py +++ /dev/null @@ -1,121 +0,0 @@ -# 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. - -"""Harness deploy integration, including opt-in Runtime VPC configuration.""" - -from __future__ import annotations - -from collections.abc import Callable, Mapping -from pathlib import Path -from typing import Any - -from agentkit.toolkit.harness.deploy import deploy_harness as _deploy_harness -from agentkit.toolkit.harness.env_mapping import to_runtime_env as _base_to_runtime_env -from agentkit.toolkit.models import LifecycleResult -from agentkit.toolkit.reporter import Reporter - -from .sidecar_config import sidecar_config_to_env - - -def to_runtime_env(spec: dict[str, Any]) -> dict[str, str]: - """Map a Harness spec without leaking Sidecar config into generic env keys.""" - - sanitized = dict(spec) - sidecar_section = sanitized.pop("sidecar", None) - sidecar_profile = sanitized.get("profile") - harness_section = sanitized.get("harness") - if isinstance(harness_section, Mapping): - if sidecar_section is None: - sidecar_section = harness_section.get("sidecar") - sidecar_profile = sidecar_profile or harness_section.get("profile") - remaining_harness = dict(harness_section) - remaining_harness.pop("sidecar", None) - if remaining_harness: - sanitized["harness"] = remaining_harness - else: - sanitized.pop("harness", None) - - env = _base_to_runtime_env(sanitized) - if isinstance(sidecar_section, (Mapping, bool)): - env.update( - sidecar_config_to_env( - sidecar_section, - profile=str(sidecar_profile or "default"), - ) - ) - return env - - -def build_runtime_network( - *, - mode: str | None = None, - vpc_id: str | None = None, - subnet_ids: list[str] | None = None, - enable_shared_internet_access: bool | None = None, -) -> dict[str, Any]: - """Build the cloud runner's Runtime network block when explicitly requested.""" - - network: dict[str, Any] = {} - if mode is not None: - network["mode"] = mode - if vpc_id is not None: - network["vpc_id"] = vpc_id - if subnet_ids: - network["subnet_ids"] = subnet_ids - if enable_shared_internet_access is not None: - network["enable_shared_internet_access"] = enable_shared_internet_access - return network - - -def deploy_harness( - name: str, - path: str | Path = ".", - *, - region: str | None = None, - access_key: str | None = None, - secret_key: str | None = None, - discovery_url: str | None = None, - allowed_id: str | None = None, - runtime_network_mode: str | None = None, - runtime_vpc_id: str | None = None, - runtime_subnet_ids: list[str] | None = None, - runtime_enable_shared_internet_access: bool | None = None, - reporter: Reporter | None = None, - on_conflict: Callable[[dict[str, Any]], bool] | None = None, -) -> LifecycleResult: - """Deploy a Sidecar-enabled Harness with optional Runtime networking.""" - - runtime_network = build_runtime_network( - mode=runtime_network_mode, - vpc_id=runtime_vpc_id, - subnet_ids=runtime_subnet_ids, - enable_shared_internet_access=runtime_enable_shared_internet_access, - ) - cloud_overrides = {"runtime_network": runtime_network} if runtime_network else None - return _deploy_harness( - name=name, - path=path, - region=region, - access_key=access_key, - secret_key=secret_key, - discovery_url=discovery_url, - allowed_id=allowed_id, - runtime_env_builder=to_runtime_env, - cloud_config_overrides=cloud_overrides, - reporter=reporter, - on_conflict=on_conflict, - ) - - -__all__ = ["build_runtime_network", "deploy_harness", "to_runtime_env"] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/failover_proxy.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/failover_proxy.py deleted file mode 100644 index 8a0e29e4..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/failover_proxy.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Stable localhost HTTP relay for Sidecar-to-direct-path failover.""" - -from __future__ import annotations - -import json -import threading -import urllib.error -import urllib.parse -import urllib.request -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer - -_HOP_BY_HOP_HEADERS = { - "connection", - "keep-alive", - "proxy-authenticate", - "proxy-authorization", - "te", - "trailer", - "transfer-encoding", - "upgrade", -} - - -class StableHttpRelay: - """Expose one stable URL and atomically switch its upstream target.""" - - def __init__(self, active_url: str, fallback_url: str) -> None: - self._active = _validated_base_url(active_url) - self._fallback = _validated_base_url(fallback_url) - self._source_path = urllib.parse.urlsplit(self._active).path.rstrip("/") - self._target = self._active - self._target_lock = threading.Lock() - self._server = ThreadingHTTPServer(("127.0.0.1", 0), self._handler()) - self._server.daemon_threads = True - self._thread = threading.Thread( - target=self._server.serve_forever, - name="agentkit-harness-direct-failover", - daemon=True, - ) - self._thread.start() - - @property - def url(self) -> str: - host, port = self._server.server_address[:2] - return f"http://{host}:{port}{self._source_path}" - - def activate_fallback(self) -> None: - with self._target_lock: - self._target = self._fallback - - def close(self) -> None: - self._server.shutdown() - self._server.server_close() - if threading.current_thread() is not self._thread: - self._thread.join(timeout=5) - - def _target_url(self, request_path: str) -> str: - incoming = urllib.parse.urlsplit(request_path) - request_base = self._source_path - if incoming.path == request_base: - suffix = "" - elif request_base and incoming.path.startswith(f"{request_base}/"): - suffix = incoming.path[len(request_base) :] - else: - suffix = incoming.path - with self._target_lock: - target = urllib.parse.urlsplit(self._target) - target_path = target.path.rstrip("/") + suffix - query = "&".join(value for value in (target.query, incoming.query) if value) - return urllib.parse.urlunsplit( - (target.scheme, target.netloc, target_path or "/", query, "") - ) - - def _handler(self) -> type[BaseHTTPRequestHandler]: - relay = self - - class RelayHandler(BaseHTTPRequestHandler): - protocol_version = "HTTP/1.1" - - def log_message(self, *_args: object) -> None: - return - - def do_GET(self) -> None: # noqa: N802 - self._forward() - - def do_POST(self) -> None: # noqa: N802 - self._forward() - - def do_PUT(self) -> None: # noqa: N802 - self._forward() - - def do_PATCH(self) -> None: # noqa: N802 - self._forward() - - def do_DELETE(self) -> None: # noqa: N802 - self._forward() - - def do_OPTIONS(self) -> None: # noqa: N802 - self._forward() - - def do_HEAD(self) -> None: # noqa: N802 - self._forward() - - def _forward(self) -> None: - content_length = int(self.headers.get("Content-Length", "0") or 0) - body = self.rfile.read(content_length) if content_length else None - headers = { - name: value - for name, value in self.headers.items() - if name.lower() not in _HOP_BY_HOP_HEADERS - and name.lower() not in {"host", "content-length"} - } - request = urllib.request.Request( - relay._target_url(self.path), - data=body, - headers=headers, - method=self.command, - ) - try: - response = urllib.request.urlopen(request, timeout=60) - except urllib.error.HTTPError as error: - response = error - except (OSError, urllib.error.URLError): - payload = json.dumps( - {"status": "error", "error": "direct_upstream_unavailable"} - ).encode("utf-8") - self.send_response(502) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(payload))) - self.send_header("Connection", "close") - self.end_headers() - if self.command != "HEAD": - self.wfile.write(payload) - self.close_connection = True - return - - with response: - self.send_response(response.getcode()) - for name, value in response.headers.items(): - if name.lower() not in _HOP_BY_HOP_HEADERS: - self.send_header(name, value) - self.send_header("Connection", "close") - self.end_headers() - if self.command != "HEAD": - while chunk := response.read(64 * 1024): - self.wfile.write(chunk) - self.wfile.flush() - self.close_connection = True - - return RelayHandler - - -def _validated_base_url(value: str) -> str: - parsed = urllib.parse.urlsplit(str(value).strip()) - if parsed.scheme not in {"http", "https"} or not parsed.netloc: - raise ValueError("failover upstream must be an HTTP(S) URL") - return urllib.parse.urlunsplit( - (parsed.scheme, parsed.netloc, parsed.path.rstrip("/"), parsed.query, "") - ) - - -__all__ = ["StableHttpRelay"] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/profiles.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/profiles.py deleted file mode 100644 index caa0f7bd..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/profiles.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Customer-facing Harness Sidecar product profiles.""" - -from __future__ import annotations - -from copy import deepcopy -from typing import Any, Mapping - - -PROFILE_DEFAULTS: dict[str, tuple[str, ...]] = { - "default": ( - "context_engine", - "compressor", - "verifier", - "long_run_control", - ), - "ops": ( - "context_engine", - "compressor", - "verifier", - "long_run_control", - "mcp_resilience", - "sql_readonly", - ), -} - -PROFILE_METADATA: dict[str, dict[str, str]] = { - "default": { - "display_name": "通用增强", - "description": "适用于通用对话、知识问答和常规 Agent 的默认增强能力。", - }, - "ops": { - "display_name": "运维增强", - "description": "面向运维诊断、数据库、日志和监控 MCP 的安全增强能力。", - }, -} - -_TRUE_VALUES = frozenset({"1", "true", "yes", "on"}) -_FALSE_VALUES = frozenset({"0", "false", "no", "off", ""}) - - -def profile_default_components(profile: str) -> tuple[str, ...]: - try: - return PROFILE_DEFAULTS[profile] - except KeyError as error: - raise ValueError( - f"Unknown Harness Sidecar profile '{profile}'. " - f"Known profiles: {', '.join(sorted(PROFILE_DEFAULTS))}" - ) from error - - -def expand_sidecar_profile( - profile: str, overrides: Mapping[str, Any] | None = None -) -> dict[str, Any]: - """Expand product selections into the legacy technical config contract.""" - - raw = deepcopy(dict(overrides or {})) - enabled = _parse_enabled(raw.get("enabled", True)) - component_overrides = raw.get("component_overrides") or {} - - # Import lazily so the Product Catalog can import the profile registry without - # introducing a module cycle. - from .selection import resolve_harness_sidecar_selection - - plan = resolve_harness_sidecar_selection( - enabled=enabled, - profile=profile, - component_overrides=component_overrides, - catalog_version=raw.get("catalog_version"), - runtime_version=raw.get("runtime_version"), - ) - if plan.errors: - raise ValueError("; ".join(plan.errors)) - - selected = set(plan.effective_components) - model_components = [ - component for component in PROFILE_DEFAULTS["default"] if component in selected - ] - mcp_enabled = bool({"mcp_resilience", "sql_readonly"} & selected) - defaults: dict[str, Any] = { - "enabled": enabled, - "model_proxy": { - "enabled": bool(model_components), - "components": model_components, - "compression_provider": "noop", - "fail_open": True, - }, - "mcp_gateway": { - "enabled": mcp_enabled, - "fail_open": True, - "readonly_segments": ["*"] if "sql_readonly" in selected else [], - "presets": ["sql_readonly"] if "sql_readonly" in selected else [], - "policy": _mcp_policy() if "mcp_resilience" in selected else {}, - }, - } - _deep_merge(defaults, raw) - defaults["profile"] = profile - return defaults - - -def _parse_enabled(value: Any) -> bool: - if isinstance(value, bool): - return value - if isinstance(value, str): - normalized = value.strip().lower() - if normalized in _TRUE_VALUES: - return True - if normalized in _FALSE_VALUES: - return False - if isinstance(value, int) and value in {0, 1}: - return bool(value) - raise ValueError("Harness Sidecar enabled must be a boolean value") - - -def _mcp_policy() -> dict[str, Any]: - return { - "result_quality": { - "empty_is_unhealthy": True, - "max_consecutive_empty": 2, - }, - "large_result": {"max_bytes": 50_000}, - "budget": { - "max_calls_per_session": 70, - "session_idle_reset_seconds": 300.0, - }, - } - - -def _deep_merge(target: dict[str, Any], overrides: Mapping[str, Any]) -> None: - for key, value in overrides.items(): - current = target.get(key) - if isinstance(current, dict) and isinstance(value, Mapping): - _deep_merge(current, value) - else: - target[key] = deepcopy(value) - - -__all__ = [ - "PROFILE_DEFAULTS", - "PROFILE_METADATA", - "expand_sidecar_profile", - "profile_default_components", -] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/runtime_components.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/runtime_components.py deleted file mode 100644 index 0b4badff..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/runtime_components.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Resolve public Sidecar settings to private Runtime module requirements.""" - -from __future__ import annotations - -from collections.abc import Iterable - -# Internal artifact inventory. Product Catalog responses must never expose these -# module names as customer-selectable components. -RUNTIME_COMPONENT_ORDER = ( - "harness_core", - "ops", - "goal_runtime", - "model_proxy", - "mcp_gateway", - "browser_runtime", - "eval_runtime", - "shadow_runtime", -) -RUNTIME_COMPONENT_DEPENDENCIES = { - "harness_core": (), - "ops": ("harness_core",), - "goal_runtime": ("harness_core",), - "model_proxy": ("harness_core",), - "mcp_gateway": ("harness_core",), - "browser_runtime": ("harness_core",), - "eval_runtime": ("harness_core",), - "shadow_runtime": ("harness_core",), -} -RUNTIME_COMPONENT_ALIASES = { - "core": "harness_core", - "harness": "harness_core", - "harness_core": "harness_core", - "runtime": "harness_core", - "runtime_core": "harness_core", - "model": "model_proxy", - "model_proxy": "model_proxy", - "mcp": "mcp_gateway", - "mcp_gateway": "mcp_gateway", - "ops": "ops", - "ops_kernel": "ops", - "goal": "goal_runtime", - "goal_loop": "goal_runtime", - "goal_runtime": "goal_runtime", - "browser": "browser_runtime", - "browser_runtime": "browser_runtime", - "eval": "eval_runtime", - "evaluation": "eval_runtime", - "eval_runtime": "eval_runtime", - "shadow": "shadow_runtime", - "shadow_runtime": "shadow_runtime", -} -RUNTIME_FLAVOR = "harness-sidecar" -RUNTIME_FLAVOR_CORE = RUNTIME_FLAVOR -RUNTIME_FLAVOR_OPS = RUNTIME_FLAVOR -RUNTIME_FLAVOR_FULL = RUNTIME_FLAVOR -RUNTIME_FLAVOR_CUSTOM = RUNTIME_FLAVOR - - -def normalize_runtime_component(value: str) -> str: - name = value.strip().lower().replace("-", "_") - try: - return RUNTIME_COMPONENT_ALIASES[name] - except KeyError as error: - known = ", ".join(RUNTIME_COMPONENT_ORDER) - raise ValueError( - f"Unknown Harness Runtime component '{value}'. Known components: {known}" - ) from error - - -def resolve_runtime_components( - *, - model_proxy_enabled: bool, - mcp_gateway_enabled: bool, - components: Iterable[str] = (), -) -> list[str]: - requested = ["harness_core"] - if model_proxy_enabled: - requested.append("model_proxy") - if mcp_gateway_enabled: - requested.append("mcp_gateway") - requested.extend(normalize_runtime_component(item) for item in components) - selected: set[str] = set() - - def include(component: str) -> None: - if component in selected: - return - for dependency in RUNTIME_COMPONENT_DEPENDENCIES[component]: - include(dependency) - selected.add(component) - - for component in requested: - include(component) - return [component for component in RUNTIME_COMPONENT_ORDER if component in selected] - - -def runtime_flavor_for_components(components: Iterable[str]) -> str: - return RUNTIME_FLAVOR - - -__all__ = [ - "RUNTIME_COMPONENT_DEPENDENCIES", - "RUNTIME_COMPONENT_ORDER", - "RUNTIME_FLAVOR", - "RUNTIME_FLAVOR_CORE", - "RUNTIME_FLAVOR_CUSTOM", - "RUNTIME_FLAVOR_FULL", - "RUNTIME_FLAVOR_OPS", - "normalize_runtime_component", - "resolve_runtime_components", - "runtime_flavor_for_components", -] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/runtime_gateway_proxy.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/runtime_gateway_proxy.py deleted file mode 100644 index d94cce05..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/runtime_gateway_proxy.py +++ /dev/null @@ -1,200 +0,0 @@ -"""Local transport relay that forces Sidecar traffic through Runtime Gateway.""" - -from __future__ import annotations - -import json -import threading -import urllib.error -import urllib.parse -import urllib.request -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer - -_HOP_BY_HOP_HEADERS = { - "connection", - "keep-alive", - "proxy-authenticate", - "proxy-authorization", - "te", - "trailer", - "transfer-encoding", - "upgrade", -} - - -class RuntimeGatewayHttpRelay: - """Expose a localhost URL while sending every request through APIG.""" - - def __init__( - self, - gateway_url: str, - *, - source_path: str, - api_key: str, - target_port: int, - timeout_seconds: float, - ) -> None: - self._gateway = _validated_gateway_url(gateway_url) - self._source_path = _normalized_path(source_path) - self._api_key = str(api_key).strip() - if not self._api_key: - raise ValueError("Runtime Gateway API key is required") - if not 1 <= int(target_port) <= 65535: - raise ValueError("Runtime Gateway target port is invalid") - if timeout_seconds <= 0: - raise ValueError("Runtime Gateway timeout must be positive") - self._target_port = int(target_port) - self._timeout_seconds = float(timeout_seconds) - self._server = ThreadingHTTPServer(("127.0.0.1", 0), self._handler()) - self._server.daemon_threads = True - self._thread = threading.Thread( - target=self._server.serve_forever, - name="agentkit-harness-runtime-gateway", - daemon=True, - ) - self._thread.start() - - @property - def url(self) -> str: - host, port = self._server.server_address[:2] - return f"http://{host}:{port}{self._source_path}" - - def activate_fallback(self) -> None: - """Keep the APIG route fail-closed; no direct Sidecar fallback exists.""" - - def close(self) -> None: - self._server.shutdown() - self._server.server_close() - if threading.current_thread() is not self._thread: - self._thread.join(timeout=5) - - def _target_url(self, request_path: str) -> str: - incoming = urllib.parse.urlsplit(request_path) - gateway = urllib.parse.urlsplit(self._gateway) - path = "/".join( - part.strip("/") for part in (gateway.path, incoming.path) if part.strip("/") - ) - return urllib.parse.urlunsplit( - ( - gateway.scheme, - gateway.netloc, - f"/{path}" if path else "/", - incoming.query, - "", - ) - ) - - def _handler(self) -> type[BaseHTTPRequestHandler]: - relay = self - - class RelayHandler(BaseHTTPRequestHandler): - protocol_version = "HTTP/1.1" - - def log_message(self, *_args: object) -> None: - return - - def do_GET(self) -> None: - self._forward() - - def do_POST(self) -> None: - self._forward() - - def do_PUT(self) -> None: - self._forward() - - def do_PATCH(self) -> None: - self._forward() - - def do_DELETE(self) -> None: - self._forward() - - def do_OPTIONS(self) -> None: - self._forward() - - def do_HEAD(self) -> None: - self._forward() - - def _forward(self) -> None: - content_length = int(self.headers.get("Content-Length", "0") or 0) - body = self.rfile.read(content_length) if content_length else None - headers = { - str(name): str(value) - for name, value in self.headers.items() - if str(name).lower() not in _HOP_BY_HOP_HEADERS - and str(name).lower() - not in { - "authorization", - "content-length", - "host", - "x-faas-proxy-port", - } - } - headers["Authorization"] = f"Bearer {relay._api_key}" - headers["X-Faas-Proxy-Port"] = str(relay._target_port) - request = urllib.request.Request( - relay._target_url(self.path), - data=body, - headers=headers, - method=self.command, - ) - try: - response = urllib.request.urlopen( - request, timeout=relay._timeout_seconds - ) - except urllib.error.HTTPError as error: - response = error - except (OSError, urllib.error.URLError): - payload = json.dumps( - {"status": "error", "error": "runtime_gateway_unavailable"}, - separators=(",", ":"), - ).encode("utf-8") - self.send_response(502) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(payload))) - self.send_header("Connection", "close") - self.end_headers() - if self.command != "HEAD": - self.wfile.write(payload) - self.close_connection = True - return - - with response: - self.send_response(response.getcode()) - for name, value in response.headers.items(): - if name.lower() not in _HOP_BY_HOP_HEADERS: - self.send_header(name, value) - self.send_header("Connection", "close") - self.end_headers() - if self.command != "HEAD": - while chunk := response.read(64 * 1024): - self.wfile.write(chunk) - self.wfile.flush() - self.close_connection = True - - return RelayHandler - - -def _validated_gateway_url(value: str) -> str: - parsed = urllib.parse.urlsplit(str(value).strip()) - if ( - parsed.scheme not in {"http", "https"} - or not parsed.netloc - or parsed.username - or parsed.password - or parsed.query - or parsed.fragment - ): - raise ValueError( - "Runtime Gateway endpoint must be a credential-free HTTP(S) URL" - ) - return urllib.parse.urlunsplit( - (parsed.scheme, parsed.netloc, parsed.path.rstrip("/"), "", "") - ) - - -def _normalized_path(value: str) -> str: - parsed = urllib.parse.urlsplit(str(value).strip()) - path = parsed.path.rstrip("/") - return path if path.startswith("/") else f"/{path}" - - -__all__ = ["RuntimeGatewayHttpRelay"] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/selection.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/selection.py deleted file mode 100644 index a719d9d2..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/selection.py +++ /dev/null @@ -1,293 +0,0 @@ -"""Deterministic Harness Sidecar Product Component selection resolver.""" - -from __future__ import annotations - -import hashlib -import json -from collections.abc import Mapping -from typing import Any - -from pydantic import BaseModel, ConfigDict, Field - -from .component_catalog import ( - CATALOG_VERSION, - PRODUCT_COMPONENT_ORDER, - ComponentAvailability, - get_harness_sidecar_catalog, -) -from .profiles import PROFILE_DEFAULTS -from .runtime_components import resolve_runtime_components - - -PLAN_SCHEMA_VERSION = "agentkit.harness-sidecar.plan/v1" -_MODEL_COMPONENTS = ( - "context_engine", - "compressor", - "verifier", - "long_run_control", -) -_VEADK_PLUGIN_TARGETS = { - "context_engine": "invocation_context", - "compressor": "compactor", - "verifier": "response_verification", - "long_run_control": "long_run_control", -} -_OPTIONAL_RUNTIME_TARGETS = { - "long_run_control": "goal_runtime", - "browser": "browser_runtime", - "evaluation": "eval_runtime", - "shadow": "shadow_runtime", -} - - -class SelectionModel(BaseModel): - model_config = ConfigDict(extra="forbid") - - -class HarnessSelectionIntent(SelectionModel): - enabled: bool = True - profile: str = "ops" - component_overrides: dict[str, bool] = Field(default_factory=dict) - catalog_version: str | None = None - runtime_version: str | None = None - - -class AutoAddedComponent(SelectionModel): - id: str - required_by: list[str] - - -class ModelProxyActivation(SelectionModel): - enabled: bool - components: list[str] = Field(default_factory=list) - - -class MCPGatewayActivation(SelectionModel): - enabled: bool - presets: list[str] = Field(default_factory=list) - readonly_segments: list[str] = Field(default_factory=list) - - -class HarnessActivationTargets(SelectionModel): - veadk_plugins: list[str] = Field(default_factory=list) - model_proxy: ModelProxyActivation - mcp_gateway: MCPGatewayActivation - runtime_components: list[str] = Field(default_factory=list) - - -class ResolvedHarnessPlan(SelectionModel): - schema_version: str = PLAN_SCHEMA_VERSION - valid: bool - enabled: bool - profile: str - requested_components: list[str] = Field(default_factory=list) - effective_components: list[str] = Field(default_factory=list) - auto_added_components: list[AutoAddedComponent] = Field(default_factory=list) - profile_component_count: int - total_component_count: int - selectable_component_count: int - effective_component_count: int - activation_targets: HarnessActivationTargets - warnings: list[str] = Field(default_factory=list) - errors: list[str] = Field(default_factory=list) - catalog_version: str = CATALOG_VERSION - runtime_version: str | None = None - plan_hash: str - - -def resolve_harness_sidecar_selection( - *, - enabled: bool = True, - profile: str = "ops", - component_overrides: Mapping[str, bool] | None = None, - catalog_version: str | None = None, - runtime_version: str | None = None, - availability_overrides: Mapping[str, ComponentAvailability | Mapping[str, Any]] - | None = None, -) -> ResolvedHarnessPlan: - """Resolve a profile and explicit overrides into an immutable plan snapshot.""" - - errors: list[str] = [] - warnings: list[str] = [] - overrides = dict(component_overrides or {}) - unknown_components = sorted(set(overrides) - set(PRODUCT_COMPONENT_ORDER)) - if unknown_components: - errors.append( - "Unknown Harness Product Component override: " - + ", ".join(unknown_components) - ) - invalid_values = sorted( - component_id - for component_id, value in overrides.items() - if not isinstance(value, bool) - ) - if invalid_values: - errors.append( - "Harness Product Component overrides must be boolean: " - + ", ".join(invalid_values) - ) - if catalog_version is not None and catalog_version != CATALOG_VERSION: - errors.append( - f"Unsupported Harness Catalog version '{catalog_version}'; " - f"expected '{CATALOG_VERSION}'" - ) - - if profile in PROFILE_DEFAULTS: - catalog = get_harness_sidecar_catalog( - profile, availability_overrides=availability_overrides - ) - profile_defaults = set(catalog.selected_profile.default_components) - profile_component_count = catalog.selected_profile.profile_component_count - else: - errors.append( - f"Unknown Harness Sidecar profile '{profile}'. " - f"Known profiles: {', '.join(sorted(PROFILE_DEFAULTS))}" - ) - catalog = get_harness_sidecar_catalog( - "default", availability_overrides=availability_overrides - ) - profile_defaults = set() - profile_component_count = 0 - - requested: set[str] = set() - if enabled: - requested.update(profile_defaults) - for component_id, selected in overrides.items(): - if component_id not in PRODUCT_COMPONENT_ORDER or not isinstance( - selected, bool - ): - continue - if selected: - requested.add(component_id) - else: - requested.discard(component_id) - - effective = set(requested) - required_by: dict[str, set[str]] = {} - definitions = {component.id: component for component in catalog.components} - - def include_dependencies(component_id: str) -> None: - for dependency in definitions[component_id].dependencies: - required_by.setdefault(dependency, set()).add(component_id) - if dependency not in effective: - effective.add(dependency) - include_dependencies(dependency) - - for component_id in _ordered(requested): - include_dependencies(component_id) - - if enabled and not effective: - errors.append("Enabled Harness Sidecar must resolve at least one component") - - for component_id in _ordered(effective): - availability = definitions[component_id].availability - if not availability.available: - detail = f": {availability.reason}" if availability.reason else "" - errors.append( - f"Harness Product Component '{component_id}' is unavailable{detail}" - ) - - if enabled and profile == "ops" and overrides.get("sql_readonly") is False: - warnings.append("SQL readonly protection is disabled for the ops profile") - - requested_ordered = _ordered(requested) - effective_ordered = _ordered(effective) - auto_added = [ - AutoAddedComponent( - id=component_id, - required_by=_ordered(required_by.get(component_id, set())), - ) - for component_id in effective_ordered - if component_id not in requested - ] - activation_targets = _activation_targets(enabled, profile, effective_ordered) - plan_values: dict[str, Any] = { - "valid": not errors, - "enabled": enabled, - "profile": profile, - "requested_components": requested_ordered, - "effective_components": effective_ordered, - "auto_added_components": auto_added, - "profile_component_count": profile_component_count, - "total_component_count": catalog.total_component_count, - "selectable_component_count": catalog.selectable_component_count, - "effective_component_count": len(effective_ordered), - "activation_targets": activation_targets, - "warnings": warnings, - "errors": errors, - "catalog_version": CATALOG_VERSION, - "runtime_version": runtime_version, - } - plan_values["plan_hash"] = _plan_hash(plan_values) - return ResolvedHarnessPlan.model_validate(plan_values) - - -def _activation_targets( - enabled: bool, profile: str, effective_components: list[str] -) -> HarnessActivationTargets: - selected = set(effective_components) if enabled else set() - model_components = [item for item in _MODEL_COMPONENTS if item in selected] - mcp_enabled = bool({"mcp_resilience", "sql_readonly"} & selected) - optional_runtime = (["ops"] if profile == "ops" and selected else []) + [ - runtime_component - for product_component, runtime_component in _OPTIONAL_RUNTIME_TARGETS.items() - if product_component in selected - ] - runtime_components = ( - resolve_runtime_components( - model_proxy_enabled=bool(model_components), - mcp_gateway_enabled=mcp_enabled, - components=optional_runtime, - ) - if selected - else [] - ) - return HarnessActivationTargets( - veadk_plugins=[ - _VEADK_PLUGIN_TARGETS[item] - for item in _MODEL_COMPONENTS - if item in selected - ], - model_proxy=ModelProxyActivation( - enabled=bool(model_components), components=model_components - ), - mcp_gateway=MCPGatewayActivation( - enabled=mcp_enabled, - presets=["sql_readonly"] if "sql_readonly" in selected else [], - readonly_segments=["*"] if "sql_readonly" in selected else [], - ), - runtime_components=runtime_components, - ) - - -def _ordered(values: set[str]) -> list[str]: - return [item for item in PRODUCT_COMPONENT_ORDER if item in values] - - -def _plan_hash(values: Mapping[str, Any]) -> str: - payload = json.dumps( - dict(values), - default=_json_default, - ensure_ascii=False, - sort_keys=True, - separators=(",", ":"), - ).encode("utf-8") - return f"sha256:{hashlib.sha256(payload).hexdigest()}" - - -def _json_default(value: Any) -> Any: - if isinstance(value, BaseModel): - return value.model_dump(mode="json") - raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable") - - -__all__ = [ - "PLAN_SCHEMA_VERSION", - "AutoAddedComponent", - "HarnessActivationTargets", - "HarnessSelectionIntent", - "MCPGatewayActivation", - "ModelProxyActivation", - "ResolvedHarnessPlan", - "resolve_harness_sidecar_selection", -] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/sidecar.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/sidecar.py deleted file mode 100644 index cfcc5939..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/sidecar.py +++ /dev/null @@ -1,504 +0,0 @@ -"""Public lifecycle wrapper for the private Harness Sidecar runtime artifact.""" - -from __future__ import annotations - -import atexit -import json -import os -import queue -import shlex -import shutil -import subprocess -import tempfile -import threading -import urllib.parse -import warnings -from collections.abc import Mapping, MutableMapping, Sequence -from dataclasses import dataclass, field -from pathlib import Path -from typing import Any - -from .failover_proxy import StableHttpRelay -from .runtime_gateway_proxy import RuntimeGatewayHttpRelay -from .sidecar_config import ( - HarnessSidecarConfig, - SidecarBindingSpec, - resolve_sidecar_config, -) - -RUNTIME_EXECUTABLE = "agentkit-harness-sidecar-runtime" -INSTALL_HINT = "run inside an AgentKit managed cloud runtime" - - -class HarnessSidecarError(RuntimeError): - pass - - -class HarnessSidecarRuntimeUnavailable(HarnessSidecarError): - pass - - -@dataclass -class SidecarBinding: - config: HarnessSidecarConfig - spec: SidecarBindingSpec - process: subprocess.Popen[str] | None = None - config_path: Path | None = None - stderr_lines: list[str] = field(default_factory=list) - _environ: MutableMapping[str, str] | None = field(default=None, repr=False) - _previous_env: dict[str, str | None] = field(default_factory=dict, repr=False) - _relays: list[StableHttpRelay | RuntimeGatewayHttpRelay] = field( - default_factory=list, repr=False - ) - _relay_env_keys: set[str] = field(default_factory=set, repr=False) - _state_lock: threading.RLock = field(default_factory=threading.RLock, repr=False) - _monitor_thread: threading.Thread | None = field(default=None, repr=False) - _runtime_exit_code: int | None = field(default=None, init=False, repr=False) - _stopped: bool = field(default=False, init=False, repr=False) - - @property - def env(self) -> dict[str, str]: - return dict(self.spec.env) - - def apply_env( - self, environ: MutableMapping[str, str] | None = None - ) -> dict[str, str]: - target = environ if environ is not None else os.environ - if self._environ is None: - self._environ = target - self._previous_env = {key: target.get(key) for key in self.spec.env} - for key, value in self.spec.env.items(): - target[key] = value - return self.env - - def stop(self) -> None: - with self._state_lock: - if self._stopped: - return - self._stopped = True - if self.process is not None and self.process.poll() is None: - self.process.terminate() - try: - self.process.wait(timeout=5) - except subprocess.TimeoutExpired: - self.process.kill() - self.process.wait(timeout=5) - if self.config_path is not None: - self.config_path.unlink(missing_ok=True) - for relay in self._relays: - relay.close() - self._relays.clear() - if self._environ is not None: - for key, previous in self._previous_env.items(): - if self._environ.get(key) != self.spec.env.get(key): - continue - if previous is None: - self._environ.pop(key, None) - else: - self._environ[key] = previous - self._environ = None - - def _configure_failover_relays(self, runtime_env: Mapping[str, str]) -> None: - if self.config.transport == "apig_runtime_port": - self._configure_runtime_gateway_relay(runtime_env) - return - try: - model_upstream = ( - self.config.model_proxy.upstream_base_url - or runtime_env.get(self.config.model_proxy.upstream_base_url_env) - ) - if self.spec.model_proxy_url and model_upstream: - relay = StableHttpRelay(self.spec.model_proxy_url, model_upstream) - self._relays.append(relay) - self.spec.model_proxy_url = relay.url - if "MODEL_AGENT_API_BASE" in self.spec.env: - self.spec.env["MODEL_AGENT_API_BASE"] = relay.url - self._relay_env_keys.add("MODEL_AGENT_API_BASE") - - mcp_upstreams = list(self.config.mcp_gateway.upstreams) - if not mcp_upstreams: - mcp_upstreams = _csv_urls( - runtime_env.get(self.config.mcp_gateway.upstreams_env) - ) - if self.spec.mcp_urls and len(self.spec.mcp_urls) == len(mcp_upstreams): - relay_urls: list[str] = [] - for sidecar_url, direct_url in zip( - self.spec.mcp_urls, mcp_upstreams, strict=True - ): - relay = StableHttpRelay(sidecar_url, direct_url) - self._relays.append(relay) - relay_urls.append(relay.url) - self.spec.mcp_urls = relay_urls - if "MCP_URLS" in self.spec.env: - self.spec.env["MCP_URLS"] = ",".join(relay_urls) - self._relay_env_keys.add("MCP_URLS") - except Exception: - for relay in self._relays: - relay.close() - self._relays.clear() - self._relay_env_keys.clear() - raise - - def _configure_runtime_gateway_relay(self, runtime_env: Mapping[str, str]) -> None: - endpoint = str( - runtime_env.get(self.config.runtime_gateway_endpoint_env) or "" - ).strip() - api_key = str( - runtime_env.get(self.config.runtime_gateway_api_key_env) or "" - ).strip() - if not endpoint: - raise HarnessSidecarError("Runtime Gateway endpoint is not configured") - if not api_key: - raise HarnessSidecarError("Runtime Gateway API key is not configured") - if not self.spec.model_proxy_url: - raise HarnessSidecarError("Sidecar model proxy discovery is missing") - source_path = urllib.parse.urlsplit(self.spec.model_proxy_url).path - relay = RuntimeGatewayHttpRelay( - endpoint, - source_path=source_path, - api_key=api_key, - target_port=self.config.model_proxy.port, - timeout_seconds=self.config.runtime_gateway_timeout_seconds, - ) - self._relays.append(relay) - self.spec.model_proxy_url = relay.url - if "MODEL_AGENT_API_BASE" in self.spec.env: - self.spec.env["MODEL_AGENT_API_BASE"] = relay.url - self._relay_env_keys.add("MODEL_AGENT_API_BASE") - - if not self.config.mcp_gateway.enabled: - return - if not self.spec.mcp_urls: - raise HarnessSidecarError("Sidecar MCP gateway discovery is missing") - relay_urls: list[str] = [] - for sidecar_url in self.spec.mcp_urls: - source_path = urllib.parse.urlsplit(sidecar_url).path - mcp_relay = RuntimeGatewayHttpRelay( - endpoint, - source_path=source_path, - api_key=api_key, - target_port=self.config.mcp_gateway.port, - timeout_seconds=self.config.runtime_gateway_timeout_seconds, - ) - self._relays.append(mcp_relay) - relay_urls.append(mcp_relay.url) - self.spec.mcp_urls = relay_urls - upstreams_env = self.config.mcp_gateway.upstreams_env - self.spec.env[upstreams_env] = ",".join(relay_urls) - self._relay_env_keys.add(upstreams_env) - - def _start_runtime_monitor(self) -> None: - if self.process is None: - return - - def monitor() -> None: - exit_code = self.process.wait() - with self._state_lock: - if self._stopped: - return - self._runtime_exit_code = exit_code - for relay in self._relays: - relay.activate_fallback() - self.spec.status = "degraded" - message = ( - "Sidecar exited; Runtime Gateway transport remains fail closed" - if self.config.transport == "apig_runtime_port" - else "Sidecar exited; direct upstream fallback is active" - ) - self.spec.diagnostics.append( - { - "component": "harness_sidecar", - "status": "degraded", - "message": message, - } - ) - self._restore_nonrelay_env() - if self.config_path is not None: - self.config_path.unlink(missing_ok=True) - - self._monitor_thread = threading.Thread( - target=monitor, - name="harness-sidecar-runtime-monitor", - daemon=True, - ) - self._monitor_thread.start() - - def _restore_nonrelay_env(self) -> None: - for key, previous in self._previous_env.items(): - if key in self._relay_env_keys: - continue - current = self.spec.env.get(key) - if self._environ is not None and self._environ.get(key) == current: - if previous is None: - self._environ.pop(key, None) - else: - self._environ[key] = previous - if previous is None: - self.spec.env.pop(key, None) - else: - self.spec.env[key] = previous - - def __enter__(self) -> "SidecarBinding": - return self - - def __exit__(self, *_args: Any) -> None: - self.stop() - - -def start_harness_sidecar( - config: HarnessSidecarConfig | Mapping[str, Any] | bool | None = None, - *, - profile: str | None = None, - apply_env: bool = False, - environ: MutableMapping[str, str] | None = None, - process_env: Mapping[str, str] | None = None, -) -> SidecarBinding: - resolved = resolve_sidecar_config(config, profile=profile) - if not resolved.enabled: - return SidecarBinding( - config=resolved, - spec=SidecarBindingSpec(status="disabled", profile=resolved.profile), - ) - runtime_env = dict( - process_env - if process_env is not None - else (environ if environ is not None else os.environ) - ) - stderr_lines: list[str] = [] - config_path: Path | None = None - process: subprocess.Popen[str] | None = None - binding: SidecarBinding | None = None - try: - command = _resolve_runtime_command(resolved, runtime_env) - config_path = _write_runtime_config(resolved) - process = subprocess.Popen( - [*command, "serve", "--config", str(config_path)], - stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=runtime_env, - bufsize=1, - ) - _drain_stream(process.stderr, stderr_lines) - line = _read_startup_line(process, resolved.startup_timeout_seconds) - discovery = json.loads(line) - if not isinstance(discovery, dict): - raise HarnessSidecarError("runtime discovery must be a JSON object") - spec = SidecarBindingSpec.from_discovery(discovery) - if spec.status == "error" or ( - spec.status == "degraded" and not resolved.fail_open - ): - detail = discovery.get("error") or discovery.get("diagnostics") or line - raise HarnessSidecarError(f"Harness Sidecar failed to start: {detail}") - binding = SidecarBinding( - config=resolved, - spec=spec, - process=process, - config_path=config_path, - stderr_lines=stderr_lines, - ) - binding._configure_failover_relays(runtime_env) - if apply_env: - binding.apply_env(environ) - binding._start_runtime_monitor() - atexit.register(binding.stop) - return binding - except Exception as error: - if binding is not None: - binding.stop() - else: - if config_path is not None: - config_path.unlink(missing_ok=True) - if process is not None: - _terminate_process(process) - if isinstance(error, HarnessSidecarError): - raise - raise HarnessSidecarError(f"Harness Sidecar startup failed: {error}") from error - - -def export_sidecar_env( - binding: SidecarBinding, - base_env: Mapping[str, str] | None = None, -) -> dict[str, str]: - result = dict(base_env or {}) - result.update(binding.spec.env) - return result - - -def run_with_harness_sidecar( - config: HarnessSidecarConfig | Mapping[str, Any] | bool | None, - command: Sequence[str], - *, - profile: str | None = None, - env: Mapping[str, str] | None = None, - cwd: str | os.PathLike[str] | None = None, -) -> int: - if not command: - raise ValueError("a child command is required") - base_env = dict(env if env is not None else os.environ) - resolved = resolve_sidecar_config(config, profile=profile) - try: - binding = start_harness_sidecar(resolved, environ=base_env) - except HarnessSidecarError as error: - if not resolved.fail_open: - raise - warnings.warn( - f"Harness Sidecar unavailable; running command directly: {error}", - RuntimeWarning, - stacklevel=2, - ) - binding = SidecarBinding( - config=resolved, - spec=SidecarBindingSpec(status="degraded", profile=resolved.profile), - ) - with binding: - child_env = export_sidecar_env(binding, base_env) - try: - completed = subprocess.run(list(command), env=child_env, cwd=cwd) - except KeyboardInterrupt: - return 130 - return int(completed.returncode) - - -def doctor_harness_sidecar( - config: HarnessSidecarConfig | Mapping[str, Any] | bool | None = None, - *, - env: Mapping[str, str] | None = None, -) -> dict[str, Any]: - resolved = resolve_sidecar_config(config) - process_env = dict(env if env is not None else os.environ) - command = _resolve_runtime_command(resolved, process_env) - try: - completed = subprocess.run( - [ - *command, - "doctor", - "--json", - "--components", - ",".join(resolved.required_runtime_components), - ], - text=True, - capture_output=True, - env=process_env, - timeout=max(0.1, resolved.startup_timeout_seconds), - ) - except subprocess.TimeoutExpired as error: - raise HarnessSidecarError( - "Harness Sidecar doctor timed out after " - f"{resolved.startup_timeout_seconds:g}s" - ) from error - except OSError as error: - raise HarnessSidecarError( - f"Harness Sidecar doctor could not start: {error}" - ) from error - try: - report = json.loads(completed.stdout.strip() or "{}") - except ValueError as error: - raise HarnessSidecarError( - f"invalid doctor response: {completed.stdout or completed.stderr}" - ) from error - if completed.returncode != 0: - raise HarnessSidecarError( - str(report.get("error") or completed.stderr or "runtime doctor failed") - ) - return report - - -def _resolve_runtime_command( - config: HarnessSidecarConfig, env: Mapping[str, str] -) -> list[str]: - if config.runtime_command: - return list(config.runtime_command) - configured = env.get("AGENTKIT_HARNESS_RUNTIME_COMMAND") - if configured: - return shlex.split(configured) - executable = shutil.which(RUNTIME_EXECUTABLE) - if executable: - return [executable] - raise HarnessSidecarRuntimeUnavailable( - "AgentKit Harness Sidecar Runtime is not available in this environment. " - f"The private Runtime is cloud-only; {_install_hint(config)} or disable " - "harness.sidecar for local debugging." - ) - - -def _install_hint(_config: HarnessSidecarConfig) -> str: - return INSTALL_HINT - - -def _csv_urls(value: str | None) -> list[str]: - return [item.strip() for item in (value or "").split(",") if item.strip()] - - -def _write_runtime_config(config: HarnessSidecarConfig) -> Path: - descriptor, name = tempfile.mkstemp( - prefix="agentkit-harness-sidecar-", suffix=".json" - ) - path = Path(name) - try: - os.fchmod(descriptor, 0o600) - with os.fdopen(descriptor, "w", encoding="utf-8") as stream: - json.dump(config.runtime_payload(), stream, ensure_ascii=False) - except Exception: - path.unlink(missing_ok=True) - raise - return path - - -def _drain_stream(stream, target: list[str]) -> None: - if stream is None: - return - - def drain() -> None: - for line in stream: - target.append(line.rstrip()) - - threading.Thread(target=drain, name="harness-sidecar-stderr", daemon=True).start() - - -def _terminate_process(process: subprocess.Popen[str]) -> None: - if process.poll() is not None: - return - process.terminate() - try: - process.wait(timeout=3) - except subprocess.TimeoutExpired: - process.kill() - try: - process.wait(timeout=3) - except subprocess.TimeoutExpired: - pass - - -def _read_startup_line(process: subprocess.Popen[str], timeout: float) -> str: - if process.stdout is None: - raise HarnessSidecarError("runtime stdout is unavailable") - lines: queue.Queue[str] = queue.Queue(maxsize=1) - - def read() -> None: - lines.put(process.stdout.readline()) - - threading.Thread(target=read, name="harness-sidecar-discovery", daemon=True).start() - try: - line = lines.get(timeout=max(0.1, timeout)).strip() - except queue.Empty as error: - raise HarnessSidecarError( - f"Harness Sidecar did not become ready within {timeout:g}s" - ) from error - if not line: - raise HarnessSidecarError( - f"Harness Sidecar exited before discovery (exit={process.poll()})" - ) - return line - - -__all__ = [ - "HarnessSidecarError", - "HarnessSidecarRuntimeUnavailable", - "SidecarBinding", - "doctor_harness_sidecar", - "export_sidecar_env", - "run_with_harness_sidecar", - "start_harness_sidecar", -] diff --git a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/sidecar_config.py b/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/sidecar_config.py deleted file mode 100644 index 573b6f3a..00000000 --- a/packages/agentkit-harness-sidecar-integration/src/agentkit/extensions/harness_sidecar/sidecar_config.py +++ /dev/null @@ -1,423 +0,0 @@ -"""Public configuration contract for AgentKit Harness Sidecar.""" - -from __future__ import annotations - -import json -import os -import shlex -from collections.abc import Mapping -from typing import Any, Literal - -from pydantic import BaseModel, ConfigDict, Field, model_validator - -from .component_catalog import CATALOG_VERSION -from .profiles import expand_sidecar_profile -from .runtime_components import ( - resolve_runtime_components, - runtime_flavor_for_components, -) -from .selection import ResolvedHarnessPlan, resolve_harness_sidecar_selection - - -class SidecarConfigModel(BaseModel): - model_config = ConfigDict(extra="forbid") - - -class ModelProxyConfig(SidecarConfigModel): - enabled: bool = True - host: str = "127.0.0.1" - port: int = 0 - upstream_base_url: str | None = None - upstream_base_url_env: str = "MODEL_AGENT_API_BASE" - upstream_api_key_env: str = "MODEL_AGENT_API_KEY" - prefer_configured_upstream_api_key: bool = False - components: list[str] = Field(default_factory=list) - compression_provider: str = "noop" - headroom_base_url_env: str = "HARNESS_HEADROOM_BASE_URL" - fail_open: bool = True - trace_dir: str = ".harness-service/traces" - state_path: str = ".harness-service/state.sqlite3" - - -class MCPGatewayConfig(SidecarConfigModel): - enabled: bool = False - host: str = "127.0.0.1" - port: int = 0 - upstreams: list[str] = Field(default_factory=list) - upstreams_env: str = "MCP_URLS" - upstream_api_key_env: str = "MCP_API_KEY" - prefer_configured_upstream_api_key: bool = False - readonly_segments: list[str] = Field(default_factory=list) - presets: list[str] = Field(default_factory=list) - fail_open: bool = True - policy: dict[str, Any] = Field(default_factory=dict) - - -class HarnessSidecarConfig(SidecarConfigModel): - enabled: bool = True - profile: str = "default" - catalog_version: str = CATALOG_VERSION - runtime_version: str | None = None - component_overrides: dict[str, bool] = Field(default_factory=dict) - fail_open: bool = True - startup_timeout_seconds: float = 20.0 - runtime_command: list[str] | None = None - transport: Literal["local", "apig_runtime_port"] = "local" - runtime_gateway_endpoint_env: str = "HARNESS_SIDECAR_APIG_ENDPOINT" - runtime_gateway_api_key_env: str = "HARNESS_SIDECAR_APIG_API_KEY" - runtime_gateway_timeout_seconds: float = 300.0 - components: list[str] = Field(default_factory=list) - model_proxy: ModelProxyConfig = Field(default_factory=ModelProxyConfig) - mcp_gateway: MCPGatewayConfig = Field(default_factory=MCPGatewayConfig) - - @model_validator(mode="before") - @classmethod - def _expand_profile(cls, value: Any) -> Any: - if isinstance(value, cls): - return value - raw = dict(value or {}) - profile = str(raw.get("profile") or "default") - return expand_sidecar_profile(profile, raw) - - @model_validator(mode="after") - def _validate_transport(self) -> "HarnessSidecarConfig": - if self.transport != "apig_runtime_port": - return self - if self.fail_open: - raise ValueError("apig_runtime_port requires fail_open=false") - if not self.model_proxy.enabled: - raise ValueError("apig_runtime_port requires model_proxy.enabled=true") - if self.model_proxy.host not in {"0.0.0.0", "::"}: - raise ValueError( - "apig_runtime_port requires an externally reachable model proxy host" - ) - if not 1 <= self.model_proxy.port <= 65535: - raise ValueError("apig_runtime_port requires a fixed model proxy port") - if not self.model_proxy.prefer_configured_upstream_api_key: - raise ValueError( - "apig_runtime_port requires configured model-upstream authorization" - ) - if self.mcp_gateway.enabled: - if self.mcp_gateway.host not in {"0.0.0.0", "::"}: - raise ValueError( - "apig_runtime_port requires an externally reachable MCP gateway host" - ) - if not 1 <= self.mcp_gateway.port <= 65535: - raise ValueError("apig_runtime_port requires a fixed MCP gateway port") - if self.mcp_gateway.fail_open: - raise ValueError("apig_runtime_port requires MCP fail_open=false") - if not self.mcp_gateway.prefer_configured_upstream_api_key: - raise ValueError( - "apig_runtime_port requires configured MCP-upstream authorization" - ) - if self.runtime_gateway_timeout_seconds <= 0: - raise ValueError("runtime gateway timeout must be positive") - return self - - def runtime_payload(self) -> dict[str, Any]: - plan = self.resolved_plan - if not plan.valid: - raise ValueError("; ".join(plan.errors)) - return { - "schema_version": "1", - "enabled": self.enabled, - "profile": self.profile, - "runtime_components": self.required_runtime_components, - "resolved_plan": plan.model_dump(mode="json", exclude_none=True), - "model_proxy": self.model_proxy.model_dump(exclude_none=True), - "mcp_gateway": self.mcp_gateway.model_dump(exclude_none=True), - } - - @property - def resolved_plan(self) -> ResolvedHarnessPlan: - return resolve_harness_sidecar_selection( - enabled=self.enabled, - profile=self.profile, - component_overrides=self.component_overrides, - catalog_version=self.catalog_version, - runtime_version=self.runtime_version, - ) - - @property - def required_runtime_components(self) -> list[str]: - if not self.enabled: - return [] - vertical_components = list(self.components) - if self.profile == "ops": - vertical_components.append("ops") - if ( - self.model_proxy.enabled - and "long_run_control" in self.model_proxy.components - ): - vertical_components.append("goal_runtime") - return resolve_runtime_components( - model_proxy_enabled=self.model_proxy.enabled, - mcp_gateway_enabled=self.mcp_gateway.enabled, - components=vertical_components, - ) - - @property - def runtime_flavor(self) -> str: - return runtime_flavor_for_components(self.required_runtime_components) - - @classmethod - def from_env(cls, env: Mapping[str, str] | None = None) -> "HarnessSidecarConfig": - values = dict(env if env is not None else os.environ) - profile = values.get("HARNESS_PROFILE") or "default" - model_enabled = _env_bool(values.get("HARNESS_MODEL_PROXY_ENABLED"), True) - mcp_enabled = _env_bool( - values.get("HARNESS_MCP_GATEWAY_ENABLED"), profile == "ops" - ) - runtime_command = values.get("AGENTKIT_HARNESS_RUNTIME_COMMAND") - component_overrides = _env_json_object( - values.get("HARNESS_SIDECAR_COMPONENT_OVERRIDES") - ) - mcp_upstreams_env = values.get("HARNESS_MCP_UPSTREAMS_ENV", "MCP_URLS") - mcp_gateway: dict[str, Any] = { - "enabled": mcp_enabled, - "host": values.get("HARNESS_MCP_GATEWAY_HOST", "127.0.0.1"), - "port": int(values.get("HARNESS_MCP_GATEWAY_PORT", "0")), - "upstreams": _csv(values.get(mcp_upstreams_env)), - "upstreams_env": mcp_upstreams_env, - "upstream_api_key_env": values.get( - "HARNESS_MCP_UPSTREAM_API_KEY_ENV", "MCP_API_KEY" - ), - "prefer_configured_upstream_api_key": _env_bool( - values.get("HARNESS_MCP_PREFER_CONFIGURED_UPSTREAM_API_KEY"), - False, - ), - "fail_open": _env_bool(values.get("HARNESS_MCP_FAIL_OPEN"), True), - } - if "HARNESS_MCP_READONLY_SEGMENTS" in values: - mcp_gateway["readonly_segments"] = _csv( - values.get("HARNESS_MCP_READONLY_SEGMENTS") - ) - if "HARNESS_MCP_PRESETS" in values: - mcp_gateway["presets"] = _csv(values.get("HARNESS_MCP_PRESETS")) - return cls.model_validate( - { - "enabled": _env_bool(values.get("HARNESS_SIDECAR_ENABLED"), False), - "profile": profile, - "catalog_version": values.get( - "HARNESS_SIDECAR_CATALOG_VERSION", CATALOG_VERSION - ), - "runtime_version": values.get("HARNESS_SIDECAR_RUNTIME_VERSION"), - "component_overrides": component_overrides, - "fail_open": _env_bool(values.get("HARNESS_SIDECAR_FAIL_OPEN"), True), - "transport": values.get("HARNESS_SIDECAR_TRANSPORT", "local"), - "runtime_gateway_endpoint_env": values.get( - "HARNESS_SIDECAR_APIG_ENDPOINT_ENV", - "HARNESS_SIDECAR_APIG_ENDPOINT", - ), - "runtime_gateway_api_key_env": values.get( - "HARNESS_SIDECAR_APIG_API_KEY_ENV", - "HARNESS_SIDECAR_APIG_API_KEY", - ), - "runtime_gateway_timeout_seconds": float( - values.get("HARNESS_SIDECAR_APIG_TIMEOUT_SECONDS", "300") - ), - "components": _csv(values.get("HARNESS_RUNTIME_COMPONENTS")), - "runtime_command": shlex.split(runtime_command) - if runtime_command - else None, - "model_proxy": { - "enabled": model_enabled, - "host": values.get("HARNESS_MODEL_PROXY_HOST", "127.0.0.1"), - "port": int(values.get("HARNESS_MODEL_PROXY_PORT", "0")), - "upstream_base_url_env": values.get( - "HARNESS_MODEL_UPSTREAM_BASE_URL_ENV", - "MODEL_AGENT_API_BASE", - ), - "upstream_api_key_env": values.get( - "HARNESS_MODEL_UPSTREAM_API_KEY_ENV", - "MODEL_AGENT_API_KEY", - ), - "prefer_configured_upstream_api_key": _env_bool( - values.get("HARNESS_MODEL_PREFER_CONFIGURED_UPSTREAM_API_KEY"), - False, - ), - "compression_provider": values.get( - "HARNESS_MODEL_COMPRESSION_PROVIDER", "noop" - ), - }, - "mcp_gateway": mcp_gateway, - } - ) - - -class SidecarBindingSpec(SidecarConfigModel): - schema_version: str = "1" - status: str - profile: str - env: dict[str, str] = Field(default_factory=dict) - model_proxy_url: str | None = None - mcp_urls: list[str] = Field(default_factory=list) - runtime_flavor: str = "unknown" - runtime_components: list[str] = Field(default_factory=list) - requested_components: list[str] = Field(default_factory=list) - effective_components: list[str] = Field(default_factory=list) - active_components: list[str] = Field(default_factory=list) - failed_components: list[str] = Field(default_factory=list) - plan_hash: str | None = None - diagnostics: list[dict[str, Any]] = Field(default_factory=list) - - @classmethod - def from_discovery(cls, discovery: Mapping[str, Any]) -> "SidecarBindingSpec": - model_proxy = dict(discovery.get("model_proxy") or {}) - mcp_gateway = dict(discovery.get("mcp_gateway") or {}) - endpoints = dict(discovery.get("endpoints") or {}) - runtime = dict(discovery.get("runtime") or {}) - return cls( - schema_version=str(discovery.get("schema_version") or "1"), - status=str(discovery.get("status") or "error"), - profile=str(discovery.get("profile") or "default"), - env={ - str(key): str(value) - for key, value in dict(discovery.get("env") or {}).items() - }, - model_proxy_url=model_proxy.get("url") or endpoints.get("model_proxy_url"), - mcp_urls=[ - str(item) - for item in (mcp_gateway.get("urls") or endpoints.get("mcp_urls") or []) - ], - runtime_flavor=str(runtime.get("flavor") or "unknown"), - runtime_components=[ - str(item) - for item in ( - runtime.get("installed_internal_components") - or runtime.get("installed_components") - or [] - ) - ], - requested_components=[ - str(item) for item in discovery.get("requested_components") or [] - ], - effective_components=[ - str(item) for item in discovery.get("effective_components") or [] - ], - active_components=[ - str(item) for item in discovery.get("active_components") or [] - ], - failed_components=[ - str(item) for item in discovery.get("failed_components") or [] - ], - plan_hash=discovery.get("plan_hash"), - diagnostics=[dict(item) for item in discovery.get("diagnostics") or []], - ) - - -def resolve_sidecar_config( - value: HarnessSidecarConfig | Mapping[str, Any] | bool | None = None, - *, - profile: str | None = None, -) -> HarnessSidecarConfig: - if isinstance(value, HarnessSidecarConfig): - if profile is None or value.profile == profile: - return value - raw = value.model_dump(exclude_unset=True) - raw["profile"] = profile - return HarnessSidecarConfig.model_validate(raw) - if isinstance(value, bool): - raw: dict[str, Any] = {"enabled": value} - else: - raw = dict(value or {}) - if profile is not None: - raw["profile"] = profile - return HarnessSidecarConfig.model_validate(raw) - - -def sidecar_config_to_env( - value: HarnessSidecarConfig | Mapping[str, Any] | bool | None, - *, - profile: str | None = None, -) -> dict[str, str]: - config = resolve_sidecar_config(value, profile=profile) - plan = config.resolved_plan - if not plan.valid: - raise ValueError("; ".join(plan.errors)) - env = { - "HARNESS_SIDECAR_ENABLED": _bool_string(config.enabled), - "HARNESS_SIDECAR_FAIL_OPEN": _bool_string(config.fail_open), - "HARNESS_SIDECAR_TRANSPORT": config.transport, - "HARNESS_SIDECAR_APIG_ENDPOINT_ENV": config.runtime_gateway_endpoint_env, - "HARNESS_SIDECAR_APIG_API_KEY_ENV": config.runtime_gateway_api_key_env, - "HARNESS_SIDECAR_APIG_TIMEOUT_SECONDS": str( - config.runtime_gateway_timeout_seconds - ), - "HARNESS_PROFILE": config.profile, - "HARNESS_SIDECAR_CATALOG_VERSION": config.catalog_version, - "HARNESS_SIDECAR_COMPONENT_OVERRIDES": json.dumps( - config.component_overrides, - ensure_ascii=False, - sort_keys=True, - separators=(",", ":"), - ), - "HARNESS_SIDECAR_PLAN": json.dumps( - plan.model_dump(mode="json", exclude_none=True), - ensure_ascii=False, - sort_keys=True, - separators=(",", ":"), - ), - "HARNESS_RUNTIME_COMPONENTS": ",".join(config.components), - "HARNESS_MODEL_PROXY_ENABLED": _bool_string(config.model_proxy.enabled), - "HARNESS_MODEL_PROXY_HOST": config.model_proxy.host, - "HARNESS_MODEL_PROXY_PORT": str(config.model_proxy.port), - "HARNESS_MODEL_UPSTREAM_BASE_URL_ENV": ( - config.model_proxy.upstream_base_url_env - ), - "HARNESS_MODEL_UPSTREAM_API_KEY_ENV": config.model_proxy.upstream_api_key_env, - "HARNESS_MODEL_PREFER_CONFIGURED_UPSTREAM_API_KEY": _bool_string( - config.model_proxy.prefer_configured_upstream_api_key - ), - "HARNESS_MODEL_COMPRESSION_PROVIDER": (config.model_proxy.compression_provider), - "HARNESS_MCP_GATEWAY_ENABLED": _bool_string(config.mcp_gateway.enabled), - "HARNESS_MCP_GATEWAY_HOST": config.mcp_gateway.host, - "HARNESS_MCP_GATEWAY_PORT": str(config.mcp_gateway.port), - "HARNESS_MCP_UPSTREAMS_ENV": config.mcp_gateway.upstreams_env, - "HARNESS_MCP_UPSTREAM_API_KEY_ENV": (config.mcp_gateway.upstream_api_key_env), - "HARNESS_MCP_PREFER_CONFIGURED_UPSTREAM_API_KEY": _bool_string( - config.mcp_gateway.prefer_configured_upstream_api_key - ), - "HARNESS_MCP_FAIL_OPEN": _bool_string(config.mcp_gateway.fail_open), - "HARNESS_MCP_READONLY_SEGMENTS": ",".join(config.mcp_gateway.readonly_segments), - "HARNESS_MCP_PRESETS": ",".join(config.mcp_gateway.presets), - } - if config.runtime_version is not None: - env["HARNESS_SIDECAR_RUNTIME_VERSION"] = config.runtime_version - return env - - -def _csv(value: str | None) -> list[str]: - return [item.strip() for item in (value or "").split(",") if item.strip()] - - -def _env_bool(value: str | None, default: bool) -> bool: - if value is None: - return default - return value.strip().lower() in {"1", "true", "yes", "on"} - - -def _env_json_object(value: str | None) -> dict[str, Any]: - if not value: - return {} - try: - parsed = json.loads(value) - except ValueError as error: - raise ValueError("HARNESS_SIDECAR_COMPONENT_OVERRIDES must be JSON") from error - if not isinstance(parsed, dict): - raise ValueError("HARNESS_SIDECAR_COMPONENT_OVERRIDES must be a JSON object") - return {str(key): item for key, item in parsed.items()} - - -def _bool_string(value: bool) -> str: - return "true" if value else "false" - - -__all__ = [ - "HarnessSidecarConfig", - "MCPGatewayConfig", - "ModelProxyConfig", - "SidecarBindingSpec", - "resolve_sidecar_config", - "sidecar_config_to_env", -] diff --git a/packages/agentkit-harness-sidecar-integration/tests/conftest.py b/packages/agentkit-harness-sidecar-integration/tests/conftest.py deleted file mode 100644 index ebce2fa2..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import agentkit.extensions - - -PLUGIN_EXTENSIONS = ( - Path(__file__).resolve().parents[1] / "src" / "agentkit" / "extensions" -) -agentkit.extensions.__path__.append(str(PLUGIN_EXTENSIONS)) diff --git a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/cli/test_cli.py b/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/cli/test_cli.py deleted file mode 100644 index 8ba45721..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/cli/test_cli.py +++ /dev/null @@ -1,164 +0,0 @@ -from __future__ import annotations - -import sys -from pathlib import Path -from types import SimpleNamespace - -import typer -from typer.testing import CliRunner - -from agentkit.extensions.harness_sidecar.cli import _config, harness_app - - -runner = CliRunner() -app = typer.Typer() -app.add_typer(harness_app, name="harness") - - -def test_harness_sidecar_export_env_uses_ops_product_profile() -> None: - result = runner.invoke( - app, - [ - "harness", - "sidecar", - "export-env", - "--profile", - "ops", - "--shell", - ], - ) - - assert result.exit_code == 0, result.output - assert "export HARNESS_SIDECAR_ENABLED=true" in result.output - assert "export HARNESS_PROFILE=ops" in result.output - assert "export HARNESS_MCP_GATEWAY_ENABLED=true" in result.output - assert "export HARNESS_MCP_READONLY_SEGMENTS='*'" in result.output - assert "export HARNESS_MCP_PRESETS=sql_readonly" in result.output - - -def test_harness_sidecar_catalog_and_resolve_commands() -> None: - catalog = runner.invoke(app, ["harness", "sidecar", "catalog", "--profile", "ops"]) - resolved = runner.invoke( - app, - [ - "harness", - "sidecar", - "resolve", - "--profile", - "ops", - "--component", - "verifier=false", - ], - ) - - assert catalog.exit_code == 0, catalog.output - assert '"total_component_count": 9' in catalog.output - assert resolved.exit_code == 0, resolved.output - assert '"effective_component_count": 5' in resolved.output - - -def test_harness_sidecar_doctor_uses_runtime_command(tmp_path: Path) -> None: - runtime = tmp_path / "doctor.py" - runtime.write_text( - "import json; print(json.dumps({'status': 'ok', 'version': 'test'}))", - encoding="utf-8", - ) - result = runner.invoke( - app, - ["harness", "sidecar", "doctor"], - env={"AGENTKIT_HARNESS_RUNTIME_COMMAND": f"{sys.executable} {runtime}"}, - ) - - assert result.exit_code == 0, result.output - assert '"status": "ok"' in result.output - - -def test_config_file_values_are_preserved_without_explicit_cli_overrides( - tmp_path: Path, -) -> None: - config_path = tmp_path / "sidecar.json" - config_path.write_text( - '{"profile":"default","model_proxy":{"enabled":false},' - '"mcp_gateway":{"enabled":true,"presets":["sql_readonly"]}}', - encoding="utf-8", - ) - - config = _config( - profile=None, - config_path=config_path, - model_proxy=None, - mcp_gateway=None, - model_upstream_env=None, - compression_provider=None, - mcp_upstreams_env=None, - presets=None, - readonly_segments=None, - ) - - assert config.profile == "default" - assert config.model_proxy.enabled is False - assert config.mcp_gateway.enabled is True - assert config.mcp_gateway.presets == ["sql_readonly"] - - -def test_export_env_preserves_profile_from_config_file(tmp_path: Path) -> None: - config_path = tmp_path / "sidecar.json" - config_path.write_text( - '{"profile":"default","mcp_gateway":{"enabled":false}}', - encoding="utf-8", - ) - - result = runner.invoke( - app, - ["harness", "sidecar", "export-env", "--config", str(config_path)], - ) - - assert result.exit_code == 0, result.output - assert '"HARNESS_PROFILE": "default"' in result.output - assert '"HARNESS_MCP_GATEWAY_ENABLED": "false"' in result.output - - -def test_sidecar_deploy_forwards_vpc_options(monkeypatch) -> None: - import agentkit.extensions.harness_sidecar.cli as cli_module - - captured = {} - - def fake_deploy_harness(**kwargs): - captured.update(kwargs) - return SimpleNamespace( - success=True, - deploy_result=SimpleNamespace(metadata={"runtime_id": "runtime-test"}), - ) - - monkeypatch.setattr(cli_module, "deploy_harness", fake_deploy_harness) - - result = runner.invoke( - app, - [ - "harness", - "sidecar", - "deploy", - "private-harness", - "--runtime-vpc-id", - "vpc-example", - "--runtime-subnet-id", - "subnet-example", - "--no-runtime-enable-shared-internet-access", - "--yes", - ], - ) - - assert result.exit_code == 0, result.output - assert captured["runtime_vpc_id"] == "vpc-example" - assert captured["runtime_subnet_ids"] == ["subnet-example"] - assert captured["runtime_enable_shared_internet_access"] is False - - -def test_core_deploy_command_does_not_expose_sidecar_vpc_flags() -> None: - import inspect - - from agentkit.toolkit.cli.cli_deploy import deploy_command - - parameters = inspect.signature(deploy_command).parameters - assert "runtime_vpc_id" not in parameters - assert "runtime_subnet_id" not in parameters diff --git a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_component_catalog.py b/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_component_catalog.py deleted file mode 100644 index a4f1ecd5..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_component_catalog.py +++ /dev/null @@ -1,186 +0,0 @@ -from __future__ import annotations - -import json - -from agentkit.extensions.harness_sidecar import ( - CATALOG_VERSION, - PRODUCT_COMPONENT_ORDER, - ComponentAvailability, - get_harness_sidecar_catalog, - resolve_harness_sidecar_selection, -) - - -def test_catalog_exposes_nine_product_components_and_two_profiles() -> None: - catalog = get_harness_sidecar_catalog(profile="ops") - - assert catalog.catalog_version == CATALOG_VERSION - assert catalog.profile_count == 2 - assert catalog.total_component_count == 9 - assert catalog.selectable_component_count == 6 - assert catalog.selected_profile.profile_component_count == 6 - assert catalog.selected_profile.default_components == list( - PRODUCT_COMPONENT_ORDER[:6] - ) - assert [component.id for component in catalog.components] == list( - PRODUCT_COMPONENT_ORDER - ) - - -def test_catalog_hides_internal_runtime_module_names() -> None: - payload = get_harness_sidecar_catalog(profile="ops").model_dump_json() - - for internal_name in ( - "harness_core", - "runtime_core", - "goal_runtime", - "model_proxy", - "mcp_gateway", - "browser_runtime", - ): - assert internal_name not in payload - - -def test_preview_components_are_visible_with_unavailable_reasons() -> None: - catalog = get_harness_sidecar_catalog(profile="default") - components = {component.id: component for component in catalog.components} - - for component_id in ("browser", "evaluation", "shadow"): - availability = components[component_id].availability - assert availability.available is False - assert availability.status == "preview" - assert availability.reason - - -def test_catalog_dependencies_exist_and_are_acyclic() -> None: - catalog = get_harness_sidecar_catalog(profile="ops") - dependencies = { - component.id: component.dependencies for component in catalog.components - } - - def visit(component_id: str, path: tuple[str, ...] = ()) -> None: - assert component_id not in path - for dependency in dependencies[component_id]: - assert dependency in dependencies - visit(dependency, (*path, component_id)) - - for component_id in dependencies: - visit(component_id) - - -def test_catalog_accepts_control_plane_availability_overrides() -> None: - catalog = get_harness_sidecar_catalog( - profile="default", - availability_overrides={ - "browser": ComponentAvailability( - available=True, - status="ga", - min_runtime_version="0.2.0", - regions=["cn-beijing"], - ) - }, - ) - - browser = next(item for item in catalog.components if item.id == "browser") - assert browser.availability.available is True - assert browser.availability.reason is None - assert browser.availability.min_runtime_version == "0.2.0" - assert catalog.selectable_component_count == 7 - - -def test_default_and_ops_resolve_exact_product_defaults() -> None: - default = resolve_harness_sidecar_selection(profile="default") - ops = resolve_harness_sidecar_selection(profile="ops") - - assert default.valid is True - assert default.effective_components == list(PRODUCT_COMPONENT_ORDER[:4]) - assert default.effective_component_count == 4 - assert ops.valid is True - assert ops.effective_components == list(PRODUCT_COMPONENT_ORDER[:6]) - assert ops.effective_component_count == 6 - assert ops.activation_targets.mcp_gateway.presets == ["sql_readonly"] - assert ops.activation_targets.mcp_gateway.readonly_segments == ["*"] - - -def test_resolver_applies_overrides_and_product_dependencies() -> None: - plan = resolve_harness_sidecar_selection( - profile="default", - component_overrides={"sql_readonly": True, "verifier": False}, - ) - - assert plan.valid is True - assert plan.requested_components == [ - "context_engine", - "compressor", - "long_run_control", - "sql_readonly", - ] - assert plan.effective_components == [ - "context_engine", - "compressor", - "long_run_control", - "mcp_resilience", - "sql_readonly", - ] - assert [item.model_dump() for item in plan.auto_added_components] == [ - {"id": "mcp_resilience", "required_by": ["sql_readonly"]} - ] - - -def test_disabled_selection_resolves_to_zero_components() -> None: - plan = resolve_harness_sidecar_selection( - enabled=False, - profile="ops", - component_overrides={"browser": True}, - ) - - assert plan.valid is True - assert plan.requested_components == [] - assert plan.effective_components == [] - assert plan.effective_component_count == 0 - assert plan.activation_targets.runtime_components == [] - - -def test_unavailable_dependency_chain_returns_actionable_errors() -> None: - plan = resolve_harness_sidecar_selection( - profile="default", - component_overrides={"shadow": True}, - ) - - assert plan.valid is False - assert plan.effective_components[-3:] == ["browser", "evaluation", "shadow"] - assert {item.id for item in plan.auto_added_components} == { - "browser", - "evaluation", - } - assert len(plan.errors) == 3 - assert all("unavailable" in error for error in plan.errors) - - -def test_plan_hash_is_stable_across_mapping_order_and_json_round_trip() -> None: - first = resolve_harness_sidecar_selection( - profile="ops", - component_overrides={"verifier": False, "sql_readonly": True}, - runtime_version="0.1.0", - ) - second = resolve_harness_sidecar_selection( - profile="ops", - component_overrides={"sql_readonly": True, "verifier": False}, - runtime_version="0.1.0", - ) - - assert first.plan_hash == second.plan_hash - assert first.plan_hash.startswith("sha256:") - assert json.loads(first.model_dump_json()) == json.loads(second.model_dump_json()) - - -def test_unknown_component_and_catalog_version_return_invalid_plan() -> None: - plan = resolve_harness_sidecar_selection( - profile="ops", - component_overrides={"not_a_component": True}, - catalog_version="old", - ) - - assert plan.valid is False - assert any("not_a_component" in error for error in plan.errors) - assert any("Catalog version" in error for error in plan.errors) diff --git a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_deploy.py b/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_deploy.py deleted file mode 100644 index cdb974b3..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_deploy.py +++ /dev/null @@ -1,96 +0,0 @@ -# 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. - -"""Runtime networking and core-extension contracts.""" - -from inspect import Parameter, signature - -from agentkit.extensions.harness_sidecar import deploy as deploy_module -from agentkit.extensions.harness_sidecar.deploy import ( - build_runtime_network, - deploy_harness, - to_runtime_env, -) - - -def test_runtime_network_is_empty_without_explicit_options(): - assert build_runtime_network() == {} - assert build_runtime_network(subnet_ids=[]) == {} - - -def test_runtime_network_preserves_explicit_false(): - assert build_runtime_network( - mode="private", - vpc_id="vpc-example", - subnet_ids=["subnet-example"], - enable_shared_internet_access=False, - ) == { - "mode": "private", - "vpc_id": "vpc-example", - "subnet_ids": ["subnet-example"], - "enable_shared_internet_access": False, - } - - -def test_deploy_network_options_are_optional_keyword_only_parameters(): - parameters = signature(deploy_harness).parameters - - for name in ( - "runtime_network_mode", - "runtime_vpc_id", - "runtime_subnet_ids", - "runtime_enable_shared_internet_access", - ): - assert parameters[name].kind is Parameter.KEYWORD_ONLY - assert parameters[name].default is None - - -def test_deploy_forwards_vpc_through_generic_core_hooks(monkeypatch): - captured = {} - expected = object() - - def fake_core_deploy(**kwargs): - captured.update(kwargs) - return expected - - monkeypatch.setattr(deploy_module, "_deploy_harness", fake_core_deploy) - - result = deploy_harness( - "private-harness", - runtime_vpc_id="vpc-example", - runtime_subnet_ids=["subnet-example"], - ) - - assert result is expected - assert captured["runtime_env_builder"] is to_runtime_env - assert captured["cloud_config_overrides"] == { - "runtime_network": { - "vpc_id": "vpc-example", - "subnet_ids": ["subnet-example"], - } - } - - -def test_deploy_without_vpc_does_not_override_cloud_config(monkeypatch): - captured = {} - - def fake_core_deploy(**kwargs): - captured.update(kwargs) - return object() - - monkeypatch.setattr(deploy_module, "_deploy_harness", fake_core_deploy) - - deploy_harness("legacy-harness") - - assert captured["cloud_config_overrides"] is None diff --git a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_failover_proxy.py b/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_failover_proxy.py deleted file mode 100644 index 07499173..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_failover_proxy.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -import json -import threading -import urllib.request -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer - -from agentkit.extensions.harness_sidecar.failover_proxy import StableHttpRelay - - -def _upstream(label: str): - requests: list[dict[str, object]] = [] - - class Handler(BaseHTTPRequestHandler): - def log_message(self, *_args: object) -> None: - return - - def do_POST(self) -> None: # noqa: N802 - length = int(self.headers.get("Content-Length", "0")) - body = self.rfile.read(length) - requests.append({"path": self.path, "body": body.decode("utf-8")}) - payload = json.dumps({"upstream": label}).encode("utf-8") - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(payload))) - self.end_headers() - self.wfile.write(payload) - - server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) - thread = threading.Thread(target=server.serve_forever, daemon=True) - thread.start() - host, port = server.server_address[:2] - return server, thread, f"http://{host}:{port}", requests - - -def test_stable_relay_switches_to_direct_upstream_without_changing_url() -> None: - sidecar, sidecar_thread, sidecar_url, sidecar_requests = _upstream("sidecar") - direct, direct_thread, direct_url, direct_requests = _upstream("direct") - relay = StableHttpRelay(f"{sidecar_url}/api/v3", f"{direct_url}/direct/v3") - stable_url = relay.url - request_url = f"{stable_url}/chat/completions?trace=test" - - try: - request = urllib.request.Request( - request_url, data=b'{"messages":[]}', method="POST" - ) - with urllib.request.urlopen(request, timeout=5) as response: - assert json.loads(response.read()) == {"upstream": "sidecar"} - - relay.activate_fallback() - request = urllib.request.Request( - request_url, data=b'{"messages":[]}', method="POST" - ) - with urllib.request.urlopen(request, timeout=5) as response: - assert json.loads(response.read()) == {"upstream": "direct"} - finally: - relay.close() - for server, thread in ( - (sidecar, sidecar_thread), - (direct, direct_thread), - ): - server.shutdown() - server.server_close() - thread.join(timeout=5) - - assert relay.url == stable_url - assert sidecar_requests == [ - {"path": "/api/v3/chat/completions?trace=test", "body": '{"messages":[]}'} - ] - assert direct_requests == [ - {"path": "/direct/v3/chat/completions?trace=test", "body": '{"messages":[]}'} - ] diff --git a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_sidecar.py b/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_sidecar.py deleted file mode 100644 index 2a22c9db..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_sidecar.py +++ /dev/null @@ -1,550 +0,0 @@ -from __future__ import annotations - -import json -import os -import stat -import sys -import textwrap -import threading -import time -import urllib.request -from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path -from secrets import token_urlsafe - -import pytest - -from agentkit.extensions.harness_sidecar.sidecar import ( - HarnessSidecarError, - HarnessSidecarRuntimeUnavailable, - doctor_harness_sidecar, - run_with_harness_sidecar, - start_harness_sidecar, -) -from agentkit.extensions.harness_sidecar.sidecar_config import ( - HarnessSidecarConfig, - SidecarBindingSpec, -) - - -@pytest.fixture -def fake_runtime(tmp_path: Path) -> Path: - path = tmp_path / "fake_runtime.py" - path.write_text( - textwrap.dedent( - """ - import json - import signal - import sys - import time - - if sys.argv[1] == "doctor": - print(json.dumps({"status": "ok", "internal_kernel": True})) - raise SystemExit(0) - - config_path = sys.argv[sys.argv.index("--config") + 1] - config = json.load(open(config_path, encoding="utf-8")) - capture = __import__("os").environ.get("FAKE_RUNTIME_CAPTURE") - if capture: - open(capture, "w", encoding="utf-8").write(json.dumps(config)) - discovery = { - "schema_version": "1", - "status": "ok", - "profile": config["profile"], - "model_proxy": {"url": "http://127.0.0.1:18787/api/v3"}, - "mcp_gateway": {"urls": ["http://127.0.0.1:18899/metrics"]}, - "env": { - "MODEL_AGENT_API_BASE": "http://127.0.0.1:18787/api/v3", - "MCP_URLS": "http://127.0.0.1:18899/metrics", - "HARNESS_SIDECAR_ENABLED": "true", - "HARNESS_PROFILE": config["profile"], - }, - "diagnostics": [], - } - print(json.dumps(discovery), flush=True) - if __import__("os").environ.get("FAKE_RUNTIME_EXIT_AFTER_DISCOVERY"): - time.sleep(0.1) - raise SystemExit(23) - running = True - def stop(*_args): - global running - running = False - signal.signal(signal.SIGTERM, stop) - signal.signal(signal.SIGINT, stop) - while running: - time.sleep(0.05) - """ - ), - encoding="utf-8", - ) - return path - - -def _config(fake_runtime: Path) -> HarnessSidecarConfig: - return HarnessSidecarConfig( - profile="ops", runtime_command=[sys.executable, str(fake_runtime)] - ) - - -def test_start_applies_and_restores_binding_env( - fake_runtime: Path, tmp_path: Path -) -> None: - capture = tmp_path / "runtime-config.json" - environ = { - "MODEL_AGENT_API_BASE": "https://real-model/api/v3", - "MCP_URLS": "https://real-mcp/metrics", - "FAKE_RUNTIME_CAPTURE": str(capture), - } - binding = start_harness_sidecar( - _config(fake_runtime), apply_env=True, environ=environ - ) - try: - assert binding.process is not None and binding.process.poll() is None - assert environ["MODEL_AGENT_API_BASE"].startswith("http://127.0.0.1") - assert environ["MCP_URLS"].endswith("/metrics") - assert stat.S_IMODE(binding.config_path.stat().st_mode) == 0o600 - runtime_config = json.loads(capture.read_text(encoding="utf-8")) - assert runtime_config["profile"] == "ops" - assert "runtime_command" not in runtime_config - finally: - binding.stop() - assert environ["MODEL_AGENT_API_BASE"] == "https://real-model/api/v3" - assert environ["MCP_URLS"] == "https://real-mcp/metrics" - assert binding.config_path.exists() is False - - -def test_runtime_process_env_is_separate_from_binding_target( - fake_runtime: Path, -) -> None: - target_env = {"ORIGINAL": "target"} - runtime_env = {"ORIGINAL": "runtime"} - - binding = start_harness_sidecar( - _config(fake_runtime), - apply_env=True, - environ=target_env, - process_env=runtime_env, - ) - try: - assert target_env["ORIGINAL"] == "target" - assert target_env["MODEL_AGENT_API_BASE"].startswith("http://127.0.0.1") - finally: - binding.stop() - - -def test_apig_runtime_port_relay_replaces_gateway_authorization( - fake_runtime: Path, -) -> None: - observed: dict[str, str] = {} - - class GatewayHandler(BaseHTTPRequestHandler): - def log_message(self, *_args: object) -> None: - return - - def do_POST(self) -> None: - length = int(self.headers.get("Content-Length", "0") or 0) - if length: - self.rfile.read(length) - observed["authorization"] = self.headers.get("Authorization", "") - observed["port"] = self.headers.get("X-Faas-Proxy-Port", "") - observed["path"] = self.path - payload = b'{"ok":true}' - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(payload))) - self.end_headers() - self.wfile.write(payload) - - gateway = ThreadingHTTPServer(("127.0.0.1", 0), GatewayHandler) - gateway_thread = threading.Thread(target=gateway.serve_forever, daemon=True) - gateway_thread.start() - host, port = gateway.server_address[:2] - gateway_url = f"http://{host}:{port}" - original_model_url = "https://model.example/api/v3" - gateway_marker = token_urlsafe(24) - environ = { - "MODEL_AGENT_API_BASE": original_model_url, - "HARNESS_SIDECAR_APIG_ENDPOINT": gateway_url, - "HARNESS_SIDECAR_APIG_API_KEY": gateway_marker, - } - config = HarnessSidecarConfig( - profile="default", - fail_open=False, - transport="apig_runtime_port", - runtime_command=[sys.executable, str(fake_runtime)], - model_proxy={ - "enabled": True, - "host": "0.0.0.0", - "port": 18787, - "upstream_base_url": original_model_url, - "prefer_configured_upstream_api_key": True, - }, - mcp_gateway={"enabled": False}, - ) - binding = start_harness_sidecar( - config, - apply_env=True, - environ=environ, - process_env=environ, - ) - try: - request = urllib.request.Request( - f"{environ['MODEL_AGENT_API_BASE']}/chat/completions", - data=b"{}", - headers={"Authorization": "Bearer model-upstream-sentinel"}, - method="POST", - ) - with urllib.request.urlopen(request, timeout=5) as response: - assert response.status == 200 - assert observed == { - "authorization": f"Bearer {gateway_marker}", - "port": "18787", - "path": "/api/v3/chat/completions", - } - finally: - binding.stop() - gateway.shutdown() - gateway.server_close() - gateway_thread.join(timeout=5) - - assert environ["MODEL_AGENT_API_BASE"] == original_model_url - - -def test_apig_runtime_port_rewrites_managed_toolset_through_mcp_gateway( - fake_runtime: Path, -) -> None: - observed: dict[str, str] = {} - - class GatewayHandler(BaseHTTPRequestHandler): - def log_message(self, *_args: object) -> None: - return - - def do_POST(self) -> None: - length = int(self.headers.get("Content-Length", "0") or 0) - if length: - self.rfile.read(length) - observed["authorization"] = self.headers.get("Authorization", "") - observed["port"] = self.headers.get("X-Faas-Proxy-Port", "") - observed["path"] = self.path - payload = b'{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}' - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(payload))) - self.end_headers() - self.wfile.write(payload) - - gateway = ThreadingHTTPServer(("127.0.0.1", 0), GatewayHandler) - gateway_thread = threading.Thread(target=gateway.serve_forever, daemon=True) - gateway_thread.start() - host, port = gateway.server_address[:2] - original_toolset_url = "https://toolset.example/metrics" - toolset_marker = token_urlsafe(24) - gateway_marker = token_urlsafe(24) - environ = { - "MODEL_AGENT_API_BASE": "https://model.example/api/v3", - "TOOL_MCP_ROUTER_URL": original_toolset_url, - "TOOL_MCP_ROUTER_API_KEY": toolset_marker, - "HARNESS_SIDECAR_APIG_ENDPOINT": f"http://{host}:{port}", - "HARNESS_SIDECAR_APIG_API_KEY": gateway_marker, - } - config = HarnessSidecarConfig( - profile="default", - fail_open=False, - transport="apig_runtime_port", - runtime_command=[sys.executable, str(fake_runtime)], - model_proxy={ - "enabled": True, - "host": "0.0.0.0", - "port": 18787, - "upstream_base_url": environ["MODEL_AGENT_API_BASE"], - "prefer_configured_upstream_api_key": True, - }, - mcp_gateway={ - "enabled": True, - "host": "0.0.0.0", - "port": 18788, - "upstreams_env": "TOOL_MCP_ROUTER_URL", - "upstream_api_key_env": "TOOL_MCP_ROUTER_API_KEY", - "prefer_configured_upstream_api_key": True, - "fail_open": False, - }, - ) - binding = start_harness_sidecar( - config, - apply_env=True, - environ=environ, - process_env=environ, - ) - try: - relay_url = environ["TOOL_MCP_ROUTER_URL"] - assert relay_url.startswith("http://127.0.0.1:") - assert relay_url.endswith("/metrics") - request = urllib.request.Request( - relay_url, - data=b'{"jsonrpc":"2.0","id":1,"method":"tools/list"}', - headers={"Authorization": "Bearer toolset-upstream-sentinel"}, - method="POST", - ) - with urllib.request.urlopen(request, timeout=5) as response: - assert response.status == 200 - assert observed == { - "authorization": f"Bearer {gateway_marker}", - "port": "18788", - "path": "/metrics", - } - finally: - binding.stop() - gateway.shutdown() - gateway.server_close() - gateway_thread.join(timeout=5) - - assert environ["TOOL_MCP_ROUTER_URL"] == original_toolset_url - - -def test_runtime_exit_switches_stable_model_url_to_direct_upstream( - fake_runtime: Path, -) -> None: - requests: list[str] = [] - - class DirectHandler(BaseHTTPRequestHandler): - def log_message(self, *_args: object) -> None: - return - - def do_GET(self) -> None: # noqa: N802 - requests.append(self.path) - payload = b"direct-ok" - self.send_response(200) - self.send_header("Content-Length", str(len(payload))) - self.end_headers() - self.wfile.write(payload) - - direct = ThreadingHTTPServer(("127.0.0.1", 0), DirectHandler) - direct_thread = threading.Thread(target=direct.serve_forever, daemon=True) - direct_thread.start() - host, port = direct.server_address[:2] - direct_url = f"http://{host}:{port}/api/v3" - environ = {"MODEL_AGENT_API_BASE": direct_url} - config = HarnessSidecarConfig( - profile="default", - runtime_command=[sys.executable, str(fake_runtime)], - model_proxy={"enabled": True, "upstream_base_url": direct_url}, - mcp_gateway={"enabled": False}, - ) - binding = start_harness_sidecar( - config, - apply_env=True, - environ=environ, - process_env={"FAKE_RUNTIME_EXIT_AFTER_DISCOVERY": "1"}, - ) - stable_url = environ["MODEL_AGENT_API_BASE"] - - try: - deadline = time.monotonic() + 5 - while binding.spec.status != "degraded" and time.monotonic() < deadline: - time.sleep(0.02) - assert binding.spec.status == "degraded" - assert stable_url.startswith("http://127.0.0.1:") - assert stable_url != direct_url - assert environ["MODEL_AGENT_API_BASE"] == stable_url - assert "HARNESS_SIDECAR_ENABLED" not in environ - with urllib.request.urlopen( - f"{stable_url}/chat/completions", timeout=5 - ) as response: - assert response.read() == b"direct-ok" - assert binding.spec.diagnostics[-1]["status"] == "degraded" - finally: - binding.stop() - direct.shutdown() - direct.server_close() - direct_thread.join(timeout=5) - - assert environ["MODEL_AGENT_API_BASE"] == direct_url - assert requests == ["/api/v3/chat/completions"] - - -def test_runtime_exit_switches_stable_mcp_url_to_env_upstream( - fake_runtime: Path, -) -> None: - requests: list[str] = [] - - class DirectHandler(BaseHTTPRequestHandler): - def log_message(self, *_args: object) -> None: - return - - def do_POST(self) -> None: # noqa: N802 - requests.append(self.path) - payload = b"direct-mcp-ok" - self.send_response(200) - self.send_header("Content-Length", str(len(payload))) - self.end_headers() - self.wfile.write(payload) - - direct = ThreadingHTTPServer(("127.0.0.1", 0), DirectHandler) - direct_thread = threading.Thread(target=direct.serve_forever, daemon=True) - direct_thread.start() - host, port = direct.server_address[:2] - direct_url = f"http://{host}:{port}/metrics" - environ = {"MCP_URLS": direct_url} - config = HarnessSidecarConfig( - profile="default", - runtime_command=[sys.executable, str(fake_runtime)], - model_proxy={"enabled": False}, - mcp_gateway={"enabled": True, "upstreams_env": "MCP_URLS"}, - ) - binding = start_harness_sidecar( - config, - apply_env=True, - environ=environ, - process_env={ - "FAKE_RUNTIME_EXIT_AFTER_DISCOVERY": "1", - "MCP_URLS": direct_url, - }, - ) - stable_url = environ["MCP_URLS"] - - try: - deadline = time.monotonic() + 5 - while binding.spec.status != "degraded" and time.monotonic() < deadline: - time.sleep(0.02) - assert binding.spec.status == "degraded" - assert stable_url.startswith("http://127.0.0.1:") - assert stable_url != direct_url - assert environ["MCP_URLS"] == stable_url - request = urllib.request.Request( - f"{stable_url}/tools/call", data=b"{}", method="POST" - ) - with urllib.request.urlopen(request, timeout=5) as response: - assert response.read() == b"direct-mcp-ok" - finally: - binding.stop() - direct.shutdown() - direct.server_close() - direct_thread.join(timeout=5) - - assert environ["MCP_URLS"] == direct_url - assert requests == ["/metrics/tools/call"] - - -def test_run_wraps_child_with_sidecar_environment( - fake_runtime: Path, tmp_path: Path -) -> None: - output = tmp_path / "child-env.json" - child = tmp_path / "child.py" - child.write_text( - "import json, os, sys; " - "json.dump({'model': os.getenv('MODEL_AGENT_API_BASE'), " - "'mcp': os.getenv('MCP_URLS')}, open(sys.argv[1], 'w'))", - encoding="utf-8", - ) - - exit_code = run_with_harness_sidecar( - _config(fake_runtime), [sys.executable, str(child), str(output)] - ) - - assert exit_code == 0 - values = json.loads(output.read_text(encoding="utf-8")) - assert values["model"] == "http://127.0.0.1:18787/api/v3" - assert values["mcp"] == "http://127.0.0.1:18899/metrics" - - -def test_doctor_uses_product_runtime_entrypoint(fake_runtime: Path) -> None: - report = doctor_harness_sidecar(_config(fake_runtime)) - - assert report == {"status": "ok", "internal_kernel": True} - - -def test_missing_runtime_has_customer_facing_install_hint(monkeypatch) -> None: - monkeypatch.delenv("AGENTKIT_HARNESS_RUNTIME_COMMAND", raising=False) - monkeypatch.setattr("shutil.which", lambda _name: None) - - with pytest.raises( - HarnessSidecarRuntimeUnavailable, - match="private Runtime is cloud-only", - ): - start_harness_sidecar({"profile": "ops"}) - - -def test_run_fails_open_to_direct_child( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path -) -> None: - output = tmp_path / "direct.txt" - monkeypatch.delenv("AGENTKIT_HARNESS_RUNTIME_COMMAND", raising=False) - monkeypatch.setattr("shutil.which", lambda _name: None) - - with pytest.warns(RuntimeWarning, match="running command directly"): - exit_code = run_with_harness_sidecar( - {"profile": "ops", "fail_open": True}, - [sys.executable, "-c", f"open({str(output)!r}, 'w').write('ok')"], - ) - - assert exit_code == 0 - assert output.read_text(encoding="utf-8") == "ok" - - -def test_run_fails_open_when_runtime_discovery_is_invalid(tmp_path: Path) -> None: - runtime = tmp_path / "invalid-runtime.py" - runtime.write_text("print('not-json', flush=True)", encoding="utf-8") - output = tmp_path / "direct-after-invalid-runtime.txt" - - with pytest.warns(RuntimeWarning, match="running command directly"): - exit_code = run_with_harness_sidecar( - { - "profile": "ops", - "fail_open": True, - "runtime_command": [sys.executable, str(runtime)], - }, - [sys.executable, "-c", f"open({str(output)!r}, 'w').write('ok')"], - ) - - assert exit_code == 0 - assert output.read_text(encoding="utf-8") == "ok" - - -def test_doctor_has_a_bounded_timeout(tmp_path: Path) -> None: - runtime = tmp_path / "slow-runtime.py" - runtime.write_text("import time; time.sleep(30)", encoding="utf-8") - - with pytest.raises(HarnessSidecarError, match="doctor timed out"): - doctor_harness_sidecar( - { - "runtime_command": [sys.executable, str(runtime)], - "startup_timeout_seconds": 0.1, - } - ) - - -def test_discovery_v2_reports_product_activation_state() -> None: - spec = SidecarBindingSpec.from_discovery( - { - "schema_version": "agentkit.harness-sidecar.discovery/v2", - "status": "degraded", - "profile": "ops", - "requested_components": ["context_engine", "verifier"], - "effective_components": ["context_engine", "verifier"], - "active_components": ["context_engine"], - "failed_components": ["verifier"], - "endpoints": {"model_proxy_url": "http://127.0.0.1:18787/api/v3"}, - "runtime": { - "installed_internal_components": [ - "runtime_core", - "ops", - "goal_runtime", - "model_proxy", - ] - }, - "plan_hash": "sha256:test", - } - ) - - assert spec.model_proxy_url == "http://127.0.0.1:18787/api/v3" - assert spec.requested_components == ["context_engine", "verifier"] - assert spec.effective_components == ["context_engine", "verifier"] - assert spec.active_components == ["context_engine"] - assert spec.failed_components == ["verifier"] - assert spec.runtime_components == [ - "runtime_core", - "ops", - "goal_runtime", - "model_proxy", - ] - assert spec.plan_hash == "sha256:test" diff --git a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_sidecar_config.py b/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_sidecar_config.py deleted file mode 100644 index 54a7fa70..00000000 --- a/packages/agentkit-harness-sidecar-integration/tests/harness_sidecar/test_sidecar_config.py +++ /dev/null @@ -1,441 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path -from secrets import token_urlsafe - -import pytest - -try: - import tomllib -except ModuleNotFoundError: # pragma: no cover - Python 3.10 compatibility - import tomli as tomllib - -from agentkit.extensions.harness_sidecar.deploy import to_runtime_env -from agentkit.extensions.harness_sidecar.runtime_components import ( - RUNTIME_COMPONENT_DEPENDENCIES, - RUNTIME_COMPONENT_ORDER, -) -from agentkit.extensions.harness_sidecar.sidecar_config import ( - HarnessSidecarConfig, - resolve_sidecar_config, - sidecar_config_to_env, -) - - -def test_internal_runtime_inventory_matches_single_wheel_contract() -> None: - assert RUNTIME_COMPONENT_ORDER == ( - "harness_core", - "ops", - "goal_runtime", - "model_proxy", - "mcp_gateway", - "browser_runtime", - "eval_runtime", - "shadow_runtime", - ) - - -def test_vertical_runtime_components_depend_only_on_harness_core() -> None: - assert RUNTIME_COMPONENT_DEPENDENCIES["harness_core"] == () - assert { - component: dependencies - for component, dependencies in RUNTIME_COMPONENT_DEPENDENCIES.items() - if component != "harness_core" - } == { - component: ("harness_core",) - for component in RUNTIME_COMPONENT_ORDER - if component != "harness_core" - } - - -def test_public_distributions_never_install_the_private_runtime_wheel() -> None: - plugin_root = Path(__file__).resolve().parents[2] - repository_root = Path(__file__).resolve().parents[4] - plugin_project = tomllib.loads( - (plugin_root / "pyproject.toml").read_text(encoding="utf-8") - ) - sdk_project = tomllib.loads( - (repository_root / "pyproject.toml").read_text(encoding="utf-8") - ) - - runtime_requirement = "bytedance.agentkit_harness_sidecar" - all_requirements = [ - *plugin_project["project"].get("dependencies", []), - *sdk_project["project"].get("dependencies", []), - *( - requirement - for extra in sdk_project["project"]["optional-dependencies"].values() - for requirement in extra - ), - ] - - assert not any(runtime_requirement in item for item in all_requirements) - assert sdk_project["project"]["optional-dependencies"]["harness-sidecar"] == [ - "agentkit-harness-sidecar-integration>=0.1.0,<0.2.0" - ] - - -def test_core_source_excludes_sidecar_implementation() -> None: - repository_root = Path(__file__).resolve().parents[4] - pyproject = tomllib.loads( - (repository_root / "pyproject.toml").read_text(encoding="utf-8") - ) - package_finder = pyproject["tool"]["setuptools"]["packages"]["find"] - core_extension = repository_root / "agentkit" / "extensions" / "harness_sidecar" - plugin_extension = ( - repository_root - / "packages" - / "agentkit-harness-sidecar-integration" - / "src" - / "agentkit" - / "extensions" - / "harness_sidecar" - ) - - assert package_finder["exclude"] == ["packages", "packages.*"] - assert not core_extension.exists() - assert (plugin_extension / "__init__.py").is_file() - - -def test_ops_profile_expands_product_defaults() -> None: - config = HarnessSidecarConfig(profile="ops") - - assert config.model_proxy.enabled is True - assert config.mcp_gateway.enabled is True - assert config.mcp_gateway.presets == ["sql_readonly"] - assert config.mcp_gateway.readonly_segments == ["*"] - assert config.mcp_gateway.policy["result_quality"]["empty_is_unhealthy"] is True - assert config.mcp_gateway.policy["budget"]["max_calls_per_session"] == 70 - assert config.runtime_flavor == "harness-sidecar" - assert config.required_runtime_components == [ - "harness_core", - "ops", - "goal_runtime", - "model_proxy", - "mcp_gateway", - ] - - -def test_explicit_values_override_profile_defaults() -> None: - config = resolve_sidecar_config( - { - "profile": "ops", - "model_proxy": {"enabled": False}, - "mcp_gateway": {"policy": {"large_result": {"max_bytes": 65_536}}}, - } - ) - - assert config.model_proxy.enabled is False - assert config.mcp_gateway.policy["large_result"]["max_bytes"] == 65_536 - assert config.mcp_gateway.policy["budget"]["max_calls_per_session"] == 70 - - -def test_unknown_profile_fails_fast() -> None: - with pytest.raises(ValueError, match="Unknown Harness Sidecar profile"): - HarnessSidecarConfig(profile="unknown") - - -def test_runtime_payload_excludes_public_launcher_details() -> None: - config = HarnessSidecarConfig( - profile="ops", runtime_command=["python", "fake-runtime.py"] - ) - - payload = config.runtime_payload() - - assert "runtime_command" not in payload - assert "startup_timeout_seconds" not in payload - assert payload["profile"] == "ops" - assert payload["runtime_components"] == config.required_runtime_components - - -def test_apig_runtime_port_env_round_trip_and_runtime_payload() -> None: - env = sidecar_config_to_env( - { - "profile": "ops", - "fail_open": False, - "transport": "apig_runtime_port", - "model_proxy": { - "host": "0.0.0.0", - "port": 18787, - "prefer_configured_upstream_api_key": True, - }, - "mcp_gateway": { - "enabled": True, - "host": "0.0.0.0", - "port": 18788, - "upstreams_env": "TOOL_MCP_ROUTER_URL", - "upstream_api_key_env": "TOOL_MCP_ROUTER_API_KEY", - "prefer_configured_upstream_api_key": True, - "fail_open": False, - }, - } - ) - env["TOOL_MCP_ROUTER_URL"] = "https://toolset.example/mcp" - env["TOOL_MCP_ROUTER_API_KEY"] = token_urlsafe(24) - - config = HarnessSidecarConfig.from_env(env) - payload = config.runtime_payload() - - assert config.transport == "apig_runtime_port" - assert config.model_proxy.host == "0.0.0.0" - assert config.model_proxy.port == 18787 - assert config.model_proxy.prefer_configured_upstream_api_key is True - assert env["HARNESS_SIDECAR_TRANSPORT"] == "apig_runtime_port" - assert env["HARNESS_MODEL_PROXY_PORT"] == "18787" - assert payload["model_proxy"]["host"] == "0.0.0.0" - assert payload["model_proxy"]["port"] == 18787 - assert payload["model_proxy"]["prefer_configured_upstream_api_key"] is True - assert config.mcp_gateway.host == "0.0.0.0" - assert config.mcp_gateway.port == 18788 - assert config.mcp_gateway.upstreams == ["https://toolset.example/mcp"] - assert config.mcp_gateway.upstream_api_key_env == "TOOL_MCP_ROUTER_API_KEY" - assert config.mcp_gateway.prefer_configured_upstream_api_key is True - assert config.mcp_gateway.fail_open is False - assert env["HARNESS_MCP_GATEWAY_PORT"] == "18788" - assert payload["runtime_components"] == ( - config.resolved_plan.activation_targets.runtime_components - ) - assert payload["mcp_gateway"]["enabled"] is True - assert payload["mcp_gateway"]["port"] == 18788 - assert payload["mcp_gateway"]["prefer_configured_upstream_api_key"] is True - assert "runtime_gateway_endpoint_env" not in payload - assert "runtime_gateway_api_key_env" not in payload - - -@pytest.mark.parametrize( - ("override", "message"), - [ - ({"fail_open": True}, "fail_open=false"), - ({"model_proxy": {"host": "127.0.0.1"}}, "externally reachable"), - ({"model_proxy": {"port": 0}}, "fixed model proxy port"), - ({"mcp_gateway": {"host": "127.0.0.1"}}, "MCP gateway host"), - ({"mcp_gateway": {"port": 0}}, "fixed MCP gateway port"), - ({"mcp_gateway": {"fail_open": True}}, "MCP fail_open=false"), - ( - {"mcp_gateway": {"prefer_configured_upstream_api_key": False}}, - "configured MCP-upstream authorization", - ), - ], -) -def test_apig_runtime_port_rejects_unsafe_config( - override: dict[str, object], message: str -) -> None: - values: dict[str, object] = { - "profile": "default", - "fail_open": False, - "transport": "apig_runtime_port", - "model_proxy": { - "host": "0.0.0.0", - "port": 18787, - "prefer_configured_upstream_api_key": True, - }, - "mcp_gateway": { - "enabled": True, - "host": "0.0.0.0", - "port": 18788, - "upstreams": ["https://toolset.example/mcp"], - "upstreams_env": "TOOL_MCP_ROUTER_URL", - "upstream_api_key_env": "TOOL_MCP_ROUTER_API_KEY", - "prefer_configured_upstream_api_key": True, - "fail_open": False, - }, - } - for key, value in override.items(): - if key in {"model_proxy", "mcp_gateway"} and isinstance(value, dict): - values[key] = {**dict(values[key]), **value} - else: - values[key] = value - - with pytest.raises(ValueError, match=message): - HarnessSidecarConfig.model_validate(values) - - -def test_explicit_browser_component_resolves_dependency_closure() -> None: - config = HarnessSidecarConfig( - profile="default", - components=["browser"], - model_proxy={"enabled": False}, - ) - - assert config.runtime_flavor == "harness-sidecar" - assert config.required_runtime_components == [ - "harness_core", - "browser_runtime", - ] - - -@pytest.mark.parametrize("legacy_alias", ["ops", "ops_kernel"]) -def test_legacy_ops_component_alias_resolves_internal_dependency( - legacy_alias: str, -) -> None: - config = HarnessSidecarConfig( - profile="default", - components=[legacy_alias], - model_proxy={"enabled": False}, - ) - - assert config.required_runtime_components == ["harness_core", "ops"] - - -def test_full_optional_component_set_resolves_full_flavor() -> None: - config = HarnessSidecarConfig( - profile="ops", - components=["browser", "eval", "shadow"], - ) - - assert config.runtime_flavor == "harness-sidecar" - assert config.required_runtime_components[-3:] == [ - "browser_runtime", - "eval_runtime", - "shadow_runtime", - ] - - -def test_env_round_trip_keeps_product_semantics() -> None: - env = sidecar_config_to_env( - { - "profile": "ops", - "mcp_gateway": { - "presets": ["sql_readonly"], - "readonly_segments": ["bqmcp"], - }, - "components": ["browser"], - } - ) - config = HarnessSidecarConfig.from_env(env) - - assert config.enabled is True - assert config.profile == "ops" - assert config.mcp_gateway.presets == ["sql_readonly"] - assert config.mcp_gateway.readonly_segments == ["bqmcp"] - assert config.components == ["browser"] - - -def test_default_env_profile_does_not_enable_mcp_gateway_implicitly() -> None: - config = HarnessSidecarConfig.from_env({"HARNESS_SIDECAR_ENABLED": "true"}) - - assert config.profile == "default" - assert config.model_proxy.enabled is True - assert config.mcp_gateway.enabled is False - assert config.required_runtime_components == [ - "harness_core", - "goal_runtime", - "model_proxy", - ] - - -def test_ops_env_profile_keeps_profile_readonly_defaults() -> None: - config = HarnessSidecarConfig.from_env( - {"HARNESS_SIDECAR_ENABLED": "true", "HARNESS_PROFILE": "ops"} - ) - - assert config.mcp_gateway.enabled is True - assert config.mcp_gateway.presets == ["sql_readonly"] - assert config.mcp_gateway.readonly_segments == ["*"] - - -def test_mcp_upstreams_are_materialized_from_configured_env() -> None: - config = HarnessSidecarConfig.from_env( - { - "HARNESS_SIDECAR_ENABLED": "true", - "HARNESS_PROFILE": "ops", - "HARNESS_MCP_UPSTREAMS_ENV": "YUMC_MCP_URLS", - "YUMC_MCP_URLS": "http://mcp-a.example/mcp, http://mcp-b.example/mcp", - } - ) - - assert config.mcp_gateway.upstreams_env == "YUMC_MCP_URLS" - assert config.mcp_gateway.upstreams == [ - "http://mcp-a.example/mcp", - "http://mcp-b.example/mcp", - ] - - -def test_deploy_env_mapping_supports_nested_harness_sidecar() -> None: - env = to_runtime_env( - { - "description": "Yum China operations agent", - "harness": { - "enabled": True, - "profile": "ops", - "sidecar": { - "enabled": True, - "model_proxy": {"enabled": True}, - "mcp_gateway": {"enabled": True}, - }, - }, - } - ) - - assert env["HARNESS_SIDECAR_ENABLED"] == "true" - assert env["HARNESS_PROFILE"] == "ops" - assert env["HARNESS_MCP_PRESETS"] == "sql_readonly" - assert env["HARNESS_MCP_READONLY_SEGMENTS"] == "*" - assert env["HARNESS_ENABLED"] == "true" - assert env["DESCRIPTION"] == "Yum China operations agent" - assert not any(key.startswith("HARNESS_SIDECAR_MCP_GATEWAY_") for key in env) - - -def test_deploy_env_mapping_supports_boolean_sidecar_shorthand() -> None: - env = to_runtime_env({"harness": {"profile": "ops", "sidecar": True}}) - - assert env["HARNESS_SIDECAR_ENABLED"] == "true" - assert env["HARNESS_PROFILE"] == "ops" - assert env["HARNESS_MCP_GATEWAY_ENABLED"] == "true" - assert env["HARNESS_MCP_PRESETS"] == "sql_readonly" - assert env["HARNESS_MCP_READONLY_SEGMENTS"] == "*" - - -def test_product_component_overrides_drive_technical_config_and_plan_env() -> None: - env = sidecar_config_to_env( - { - "profile": "ops", - "component_overrides": { - "verifier": False, - "sql_readonly": False, - }, - } - ) - - plan = json.loads(env["HARNESS_SIDECAR_PLAN"]) - assert "verifier" not in plan["effective_components"] - assert "sql_readonly" not in plan["effective_components"] - assert env["HARNESS_MCP_PRESETS"] == "" - assert env["HARNESS_MCP_READONLY_SEGMENTS"] == "" - - -def test_disabled_sidecar_has_no_runtime_or_product_components() -> None: - config = HarnessSidecarConfig(profile="ops", enabled=False) - - assert config.required_runtime_components == [] - assert config.resolved_plan.effective_components == [] - - -@pytest.mark.parametrize( - ("value", "expected"), - [ - (False, False), - ("false", False), - ("0", False), - ("no", False), - ("off", False), - (True, True), - ("true", True), - ("1", True), - ("yes", True), - ("on", True), - ], -) -def test_profile_enabled_uses_type_aware_boolean_parsing( - value: bool | str, expected: bool -) -> None: - config = resolve_sidecar_config({"profile": "ops", "enabled": value}) - - assert config.enabled is expected - assert bool(config.required_runtime_components) is expected - - -def test_profile_enabled_rejects_ambiguous_values() -> None: - with pytest.raises(ValueError, match="enabled must be a boolean"): - resolve_sidecar_config({"enabled": "sometimes"}) diff --git a/pyproject.toml b/pyproject.toml index 7493f4f4..a9b2c642 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,6 @@ agentkit = "agentkit.toolkit.cli.cli:app" [tool.setuptools.packages.find] where = ["."] include = ["agentkit", "agentkit.*", "agentkit_identity", "agentkit_identity.*"] -exclude = ["packages", "packages.*"] [tool.setuptools] include-package-data = true @@ -88,16 +87,6 @@ langgraph = ["langgraph"] strands = ["strands-agents"] agentcore = ["bedrock-agentcore"] frameworks = ["langchain", "langgraph", "strands-agents", "bedrock-agentcore"] -harness-sidecar = [ - "agentkit-harness-sidecar-integration>=0.1.0,<0.2.0", -] - -[tool.uv.workspace] -members = ["packages/agentkit-harness-sidecar-integration"] - -[tool.uv.sources] -agentkit-harness-sidecar-integration = { workspace = true } -agentkit-sdk-python = { workspace = true } [tool.ruff.lint] diff --git a/tests/test_optional_sidecar_packaging.py b/tests/test_optional_sidecar_packaging.py deleted file mode 100644 index b59d7981..00000000 --- a/tests/test_optional_sidecar_packaging.py +++ /dev/null @@ -1,116 +0,0 @@ -from __future__ import annotations - -import os -import shutil -import subprocess -import sys -from pathlib import Path -from zipfile import ZipFile - - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -PLUGIN_ROOT = REPOSITORY_ROOT / "packages" / "agentkit-harness-sidecar-integration" - - -def _build_wheel(source: Path, destination: Path) -> Path: - destination.mkdir() - command = ( - "from setuptools.build_meta import build_wheel; " - "import sys; print(build_wheel(sys.argv[1]))" - ) - completed = subprocess.run( - [sys.executable, "-c", command, str(destination)], - cwd=source, - text=True, - capture_output=True, - check=False, - ) - assert completed.returncode == 0, completed.stderr[-4000:] - wheels = list(destination.glob("*.whl")) - assert len(wheels) == 1 - return wheels[0] - - -def _copy_sdk_source(destination: Path) -> None: - destination.mkdir() - shutil.copytree(REPOSITORY_ROOT / "agentkit", destination / "agentkit") - for filename in ("LICENSE", "MANIFEST.in", "README.md", "pyproject.toml"): - shutil.copy2(REPOSITORY_ROOT / filename, destination / filename) - - -def _metadata(wheel: Path) -> tuple[list[str], str, str]: - with ZipFile(wheel) as archive: - names = archive.namelist() - metadata_name = next( - name for name in names if name.endswith(".dist-info/METADATA") - ) - entry_points_name = next( - (name for name in names if name.endswith(".dist-info/entry_points.txt")), - None, - ) - metadata = archive.read(metadata_name).decode("utf-8") - entry_points = ( - archive.read(entry_points_name).decode("utf-8") if entry_points_name else "" - ) - return names, metadata, entry_points - - -def _probe_installation(site_packages: Path, *, plugin_expected: bool) -> None: - script = """ -import importlib.util - -spec = importlib.util.find_spec("agentkit.extensions.harness_sidecar") -assert (spec is not None) is PLUGIN_EXPECTED -""" - env = dict(os.environ) - env["PYTHONPATH"] = str(site_packages) - completed = subprocess.run( - [ - sys.executable, - "-S", - "-c", - f"PLUGIN_EXPECTED = {plugin_expected!r}\n{script}", - ], - cwd=site_packages, - env=env, - text=True, - capture_output=True, - check=False, - ) - assert completed.returncode == 0, (completed.stdout + completed.stderr)[-4000:] - - -def test_built_wheels_preserve_optional_sidecar_boundary(tmp_path: Path) -> None: - sdk_source = tmp_path / "sdk-source" - plugin_source = tmp_path / "plugin-source" - _copy_sdk_source(sdk_source) - shutil.copytree(PLUGIN_ROOT, plugin_source) - - sdk_wheel = _build_wheel(sdk_source, tmp_path / "sdk-dist") - plugin_wheel = _build_wheel(plugin_source, tmp_path / "plugin-dist") - sdk_names, sdk_metadata, sdk_entry_points = _metadata(sdk_wheel) - plugin_names, plugin_metadata, plugin_entry_points = _metadata(plugin_wheel) - - module_prefix = "agentkit/extensions/harness_sidecar/" - assert not any(name.startswith(module_prefix) for name in sdk_names) - assert any(name.startswith(module_prefix) for name in plugin_names) - assert "agentkit-harness-sidecar-integration" in sdk_metadata - assert "agentkit.cli_plugins" not in sdk_entry_points - assert "[agentkit.cli_plugins]" in plugin_entry_points - assert "harness = agentkit.extensions.harness_sidecar.cli:harness_app" in ( - plugin_entry_points - ) - public_metadata = f"{sdk_metadata}\n{plugin_metadata}".lower().replace("_", "-") - assert "bytedance-agentkit-harness-sidecar" not in public_metadata - - base_site = tmp_path / "base-site" - base_site.mkdir() - with ZipFile(sdk_wheel) as archive: - archive.extractall(base_site) - _probe_installation(base_site, plugin_expected=False) - - extra_site = tmp_path / "extra-site" - shutil.copytree(base_site, extra_site) - with ZipFile(plugin_wheel) as archive: - archive.extractall(extra_site) - _probe_installation(extra_site, plugin_expected=True) diff --git a/tests/toolkit/cli/test_plugin_loader.py b/tests/toolkit/cli/test_plugin_loader.py deleted file mode 100644 index 10d932d4..00000000 --- a/tests/toolkit/cli/test_plugin_loader.py +++ /dev/null @@ -1,129 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -import pytest -import typer -from typer.testing import CliRunner - -from agentkit.toolkit.cli.plugin_loader import load_cli_plugins - - -@dataclass(frozen=True) -class _EntryPoint: - name: str - value: Any - load_error: Exception | None = None - - def load(self) -> Any: - if self.load_error is not None: - raise self.load_error - return self.value - - -def test_empty_plugin_set_does_not_change_core_cli() -> None: - root = typer.Typer() - - @root.command("core") - def core() -> None: - typer.echo("core-ok") - - load_cli_plugins(root, plugins=[]) - - result = CliRunner().invoke(root, ["--help"]) - assert result.exit_code == 0 - assert "harness" not in result.output - - -def test_installed_typer_plugin_is_registered_by_entry_point_name() -> None: - root = typer.Typer() - plugin = typer.Typer() - - @plugin.command("status") - def status() -> None: - typer.echo("plugin-ok") - - load_cli_plugins(root, plugins=[_EntryPoint("harness", plugin)]) - - result = CliRunner().invoke(root, ["harness", "status"]) - assert result.exit_code == 0 - assert result.output.strip() == "plugin-ok" - - -def test_plugin_load_failure_warns_and_continues( - capsys: pytest.CaptureFixture[str], -) -> None: - root = typer.Typer() - plugin = typer.Typer() - - @plugin.command("status") - def status() -> None: - typer.echo("healthy-ok") - - load_cli_plugins( - root, - plugins=[ - _EntryPoint("broken", None, RuntimeError("private details")), - _EntryPoint("healthy", plugin), - ], - ) - - warning = capsys.readouterr().err - assert "plugin 'broken' failed during load (RuntimeError)" in warning - assert "private details" not in warning - result = CliRunner().invoke(root, ["healthy", "status"]) - assert result.exit_code == 0 - assert result.output.strip() == "healthy-ok" - - -def test_plugin_registration_failure_warns_and_continues( - capsys: pytest.CaptureFixture[str], -) -> None: - root = typer.Typer() - plugin = typer.Typer() - - def broken_registration(_root: typer.Typer) -> None: - raise ValueError("private details") - - @plugin.command("status") - def status() -> None: - typer.echo("healthy-ok") - - load_cli_plugins( - root, - plugins=[ - _EntryPoint("broken", broken_registration), - _EntryPoint("healthy", plugin), - ], - ) - - warning = capsys.readouterr().err - assert "plugin 'broken' failed during registration (ValueError)" in warning - assert "private details" not in warning - result = CliRunner().invoke(root, ["healthy", "status"]) - assert result.exit_code == 0 - assert result.output.strip() == "healthy-ok" - - -def test_invalid_plugin_contract_warns_without_breaking_core_cli( - capsys: pytest.CaptureFixture[str], -) -> None: - root = typer.Typer() - - @root.command("core") - def core() -> None: - typer.echo("core-ok") - - @root.command("other") - def other() -> None: - typer.echo("other-ok") - - load_cli_plugins(root, plugins=[_EntryPoint("invalid", object())]) - - warning = capsys.readouterr().err - assert "plugin 'invalid'" in warning - assert "expected a Typer or callable, got object" in warning - result = CliRunner().invoke(root, ["core"]) - assert result.exit_code == 0 - assert result.output.strip() == "core-ok" diff --git a/tests/toolkit/test_harness_extension_hooks.py b/tests/toolkit/test_harness_extension_hooks.py deleted file mode 100644 index 8c6d2d08..00000000 --- a/tests/toolkit/test_harness_extension_hooks.py +++ /dev/null @@ -1,53 +0,0 @@ -# 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. - -"""Backward-compatible extension hooks used by optional Harness capabilities.""" - -from inspect import Parameter, signature - -from agentkit.toolkit.harness.config_builder import build_agentkit_config -from agentkit.toolkit.harness.deploy import deploy_harness - - -def test_legacy_config_builder_call_is_unchanged(): - config = build_agentkit_config( - "legacy-harness", - "cn-beijing", - {"EXISTING_ENV": "value"}, - None, - "Auto", - ) - - cloud = config["launch_types"]["cloud"] - assert cloud["runtime_id"] == "Auto" - assert "runtime_network" not in cloud - - -def test_cloud_config_overrides_are_opt_in(): - config = build_agentkit_config( - "extended-harness", - "cn-beijing", - {}, - cloud_config_overrides={"extension_value": "enabled"}, - ) - - assert config["launch_types"]["cloud"]["extension_value"] == "enabled" - - -def test_new_deploy_hooks_are_optional_keyword_only_parameters(): - parameters = signature(deploy_harness).parameters - - for name in ("runtime_env_builder", "cloud_config_overrides"): - assert parameters[name].kind is Parameter.KEYWORD_ONLY - assert parameters[name].default is None