Add explicit source repo deploy tool
All checks were successful
build / build (push) Successful in 50s

This commit is contained in:
robert
2026-06-08 20:39:00 -03:00
parent 03f51fe27b
commit c5fdaba848
4 changed files with 132 additions and 3 deletions

View File

@@ -242,6 +242,11 @@ Your tools:
elsewhere; use force=True only after
the user explicitly accepts replacing
the current repo source.
- cp_deploy_source_repo(name) — deploy the current managed Gitea source
repo without uploading MinIO files. Use
only when source changes already live in
the platform repo; normal builder
workspaces should use cp_deploy_tarball.
- cp_compose_meta_agent(name, manifest_json, description="", version="0.1.0",
public=True, refresh_existing=False)
— create/deploy a manifest-backed
@@ -308,6 +313,8 @@ Discipline:
scaffold.
- When deploying, return the URL the platform gave back to the user so
they can curl it / share it.
- Source repo edits do not auto-deploy. If you edited the managed repo
directly, call cp_deploy_source_repo after the coherent edit set is done.
- If cp_deploy_tarball returns workspace_drift or workspace_untracked,
use sync_agent_workspace_from_repo when the user wants the builder to
continue from the current repo. Do not use force=True unless the user

View File

@@ -3,6 +3,7 @@
- workspace file CRUD through the caller's scoped workspace grant
- sandbox python (for `a2a card` / `a2a validate` round-trips)
- cp_deploy_tarball (call /v1/agents/from-tarball on the user's behalf)
- cp_deploy_source_repo (call /v1/agents/{name}/source/deploy)
- cp_compose_meta_agent (call /v1/agents/compose with a manifest)
"""
from __future__ import annotations
@@ -661,8 +662,10 @@ def build_tools(ctx: ToolContext) -> list[Any]:
f"{settings.sandbox_url}/v1/run_shell",
headers=headers,
json={
"bucket": bucket, "script": script,
"image": settings.image, "memory_mib": 512,
"bucket": bucket,
"script": script,
"image": settings.image,
"memory_mib": 512,
"timeout_seconds": settings.sandbox_timeout_s,
},
)
@@ -838,6 +841,58 @@ def build_tools(ctx: ToolContext) -> list[Any]:
out["last_seen_version"] = live_card.get("version")
return json.dumps(out)
@tool
async def cp_deploy_source_repo(name: str) -> str:
"""Deploy the current managed source repo for ``name``.
Use this when source changes already live in the platform-managed
Gitea repo. Do not use it for normal MinIO builder workspaces; for
those, run ``test_agent_in_sandbox`` and then ``cp_deploy_tarball``.
Returns JSON from the control plane, including ``deploy_id`` and a
serialized ``deployment`` when one is queued.
"""
try:
_validate_name(name)
except ValueError as exc:
return json.dumps({"error": str(exc)})
if not ctx.cp_jwt:
return json.dumps({
"error": "no CP JWT forwarded — agent declaration missing wants_cp_jwt=True",
})
try:
async with httpx.AsyncClient(timeout=60.0) as c:
r = await c.post(
f"{settings.cp_url}/v1/agents/{name}/source/deploy",
headers={"authorization": f"bearer {ctx.cp_jwt}"},
)
except httpx.HTTPError as exc:
return json.dumps({"error": f"cp unreachable: {exc}"})
if r.status_code >= 400:
return json.dumps({"error": f"cp {r.status_code}", "detail": r.text[:1000]})
payload = r.json()
body = payload if isinstance(payload, dict) else {}
deployment = body.get("deployment") if isinstance(body, dict) else None
url = None
if isinstance(deployment, dict):
url = deployment.get("agent_url")
out = {
"ok": True,
"name": body.get("agent_name") or name,
"status": body.get("status") or (deployment or {}).get("status"),
"url": url or _canonical_agent_url(name),
"head_sha": body.get("source_sha"),
"deployment_id": body.get("deploy_id"),
"deployment": deployment or {},
"already_deployed": bool(body.get("already_deployed")),
"skipped": bool(body.get("skipped")),
}
if body.get("summary"):
out["summary"] = body.get("summary")
if body.get("reason"):
out["reason"] = body.get("reason")
return json.dumps(out)
@tool
async def cp_compose_meta_agent(
name: str,
@@ -1121,7 +1176,7 @@ def build_tools(ctx: ToolContext) -> list[Any]:
return [
init_agent_template, list_agent_files, write_agent_file, read_agent_file,
write_agent_skill, test_agent_in_sandbox, cp_deploy_tarball,
cp_compose_meta_agent, cp_refresh_agent,
cp_deploy_source_repo, cp_compose_meta_agent, cp_refresh_agent,
sync_agent_workspace_from_repo,
list_a2a_pack, read_a2a_pack,
]
@@ -1457,6 +1512,8 @@ def _compile_agent_dsl_json(bundle: bytes) -> str:
entrypoint = str(cfg.get("entrypoint") or "").strip()
if not entrypoint:
raise ValueError("a2a.yaml entrypoint is required")
entrypoint_module = entrypoint.split(":", 1)[0].strip()
old_entrypoint_module = sys.modules.get(entrypoint_module)
old_dont_write_bytecode = os.environ.get("PYTHONDONTWRITEBYTECODE")
old_sys_dont_write_bytecode = sys.dont_write_bytecode
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
@@ -1483,6 +1540,10 @@ def _compile_agent_dsl_json(bundle: bytes) -> str:
else:
os.environ["PYTHONDONTWRITEBYTECODE"] = old_dont_write_bytecode
sys.dont_write_bytecode = old_sys_dont_write_bytecode
if old_entrypoint_module is None:
sys.modules.pop(entrypoint_module, None)
else:
sys.modules[entrypoint_module] = old_entrypoint_module
return dsl.model_dump_json()