feat(agent): add provider templates
test / unit (pull_request) Successful in 28s
test / integration (pull_request) Successful in 40s

Assisted-by: Codex
This commit is contained in:
2026-05-28 02:18:53 -04:00
parent e03d90962d
commit 500fd910c4
18 changed files with 510 additions and 119 deletions
+15 -10
View File
@@ -1,16 +1,11 @@
"""DockerBottle — concrete Bottle handle yielded by
DockerBottleBackend.launch.
Holds the container name plus the in-container prompt path so
exec_claude can transparently add --append-system-prompt-file when a
prompt was provisioned.
"""
"""DockerBottle — concrete Bottle handle yielded by DockerBottleBackend."""
from __future__ import annotations
import subprocess
from typing import Callable
from ...agent_provider import prompt_args
from .. import Bottle, ExecResult
@@ -22,22 +17,32 @@ class DockerBottle(Bottle):
container: str,
teardown: Callable[[], None],
prompt_path_in_container: str | None,
*,
agent_command: str = "claude",
agent_prompt_mode: str = "claude_append_file",
):
self.name = container
self._teardown = teardown
self._prompt_path = prompt_path_in_container
self._agent_command = agent_command
self._agent_prompt_mode = agent_prompt_mode
self.agent_command = agent_command
self.agent_provider_template = (
"codex" if agent_command == "codex" else "claude"
)
self._closed = False
def claude_argv(
self, argv: list[str], *, tty: bool = True,
) -> list[str]:
full_argv = list(argv)
if self._prompt_path:
full_argv.extend(["--append-system-prompt-file", self._prompt_path])
full_argv.extend(
prompt_args(self._agent_prompt_mode, self._prompt_path, argv=full_argv)
)
cmd = ["docker", "exec"]
if tty:
cmd.append("-it")
cmd.extend([self.name, "claude", *full_argv])
cmd.extend([self.name, self._agent_command, *full_argv])
return cmd
def exec_claude(self, argv: list[str], *, tty: bool = True) -> int:
+4 -1
View File
@@ -51,6 +51,9 @@ class DockerBottlePlan(BottlePlan):
# is opt-in via the manifest's bottle.supervise field.
supervise_plan: SupervisePlan | None
use_runsc: bool
agent_command: str = "claude"
agent_prompt_mode: str = "claude_append_file"
agent_provider_template: str = "claude"
def print(self, *, remote_control: bool) -> None:
"""Render the y/N preflight summary to stderr — compact form
@@ -73,6 +76,7 @@ class DockerBottlePlan(BottlePlan):
print(file=sys.stderr)
info(f"agent : {spec.agent_name}")
info(f"provider : {self.agent_provider_template}")
print_multi("env ", env_names)
print_multi("skills ", list(agent.skills))
info(f"bottle : {agent.bottle}")
@@ -91,4 +95,3 @@ class DockerBottlePlan(BottlePlan):
egress_lines.append(f"{r.host}{auth}")
print_multi(" egress ", egress_lines)
print(file=sys.stderr)
+7 -1
View File
@@ -207,6 +207,12 @@ def launch(
# Step 9: yield. exec_claude continues to use `docker exec -it`
# — the agent runs `sleep infinity` per the renderer's
# service spec.
yield DockerBottle(plan.container_name, teardown, prompt_path)
yield DockerBottle(
plan.container_name,
teardown,
prompt_path,
agent_command=plan.agent_command,
agent_prompt_mode=plan.agent_prompt_mode,
)
finally:
teardown()
+35 -8
View File
@@ -14,6 +14,7 @@ import os
from datetime import datetime, timezone
from pathlib import Path
from ...agent_provider import runtime_for
from ...egress import Egress
from ...env import ResolvedEnv, resolve_env
from ...git_gate import GitGate
@@ -58,6 +59,8 @@ def resolve_plan(
manifest = spec.manifest
agent = manifest.agents[spec.agent_name]
bottle = manifest.bottle_for(spec.agent_name)
provider = bottle.agent_provider
provider_runtime = runtime_for(provider.template)
# PRD 0016 follow-up: identity, not bare slug. A fresh `start`
# mints a random-suffixed identity (so parallel runs of the same
@@ -89,8 +92,14 @@ def resolve_plan(
if per_bottle_dockerfile(slug) is not None:
image_default = per_bottle_image_tag(slug)
dockerfile_path = str(per_bottle_dockerfile_path(slug))
elif provider.dockerfile:
image_default = f"claude-bottle:{provider.template}-{slug}"
dockerfile_path = _resolve_manifest_dockerfile(provider.dockerfile, spec)
elif provider_runtime.dockerfile:
image_default = provider_runtime.image
dockerfile_path = provider_runtime.dockerfile
else:
image_default = "claude-bottle:latest"
image_default = provider_runtime.image
image = os.environ.get("CLAUDE_BOTTLE_IMAGE", image_default)
derived_image = ""
runtime_image = image
@@ -171,8 +180,16 @@ def resolve_plan(
# PRD 0017 chunk 3 moved them behind the
# `list-egress-routes` MCP tool so the agent gets live
# state rather than a launch-time snapshot.)
dockerfile_path = Path(__file__).resolve().parent.parent.parent.parent / "Dockerfile"
dockerfile_content = dockerfile_path.read_text() if dockerfile_path.is_file() else ""
supervise_dockerfile_path = (
Path(dockerfile_path)
if dockerfile_path
else Path(__file__).resolve().parent.parent.parent.parent / "Dockerfile"
)
dockerfile_content = (
supervise_dockerfile_path.read_text()
if supervise_dockerfile_path.is_file()
else ""
)
supervise_dir = supervise_state_dir(slug)
supervise_dir.mkdir(parents=True, exist_ok=True)
supervise_plan = supervise.prepare(
@@ -192,12 +209,12 @@ def resolve_plan(
# placeholder. The placeholder isn't any real token value, so
# leaking it would tell an attacker only that egress is in
# front. Manifest validation enforces singleton on this role.
has_anthropic_auth = any(
"claude_code_oauth" in r.roles
for r in egress_plan.routes
has_provider_auth = any(
provider_runtime.auth_role in r.roles for r in egress_plan.routes
)
if has_anthropic_auth:
forwarded_env["CLAUDE_CODE_OAUTH_TOKEN"] = "egress-placeholder"
if has_provider_auth:
forwarded_env[provider_runtime.placeholder_env] = "egress-placeholder"
if provider.template == "claude" and has_provider_auth:
# Belt-and-braces: turn off telemetry endpoints (statsig,
# error reporting) that egress can't gate by auth.
forwarded_env.setdefault("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1")
@@ -225,6 +242,9 @@ def resolve_plan(
egress_plan=egress_plan,
supervise_plan=supervise_plan,
use_runsc=use_runsc,
agent_command=provider_runtime.command,
agent_prompt_mode=provider_runtime.prompt_mode,
agent_provider_template=provider.template,
)
@@ -243,3 +263,10 @@ def _write_env_file(resolved: ResolvedEnv, env_file: Path) -> None:
env_lines.append(f"{name}={value}")
env_file.write_text("\n".join(env_lines) + ("\n" if env_lines else ""))
env_file.chmod(0o600)
def _resolve_manifest_dockerfile(path_value: str, spec: BottleSpec) -> str:
path = Path(os.path.expanduser(path_value))
if not path.is_absolute():
path = Path(spec.user_cwd) / path
return str(path)