Exclude cache artifacts from builder source packages
Some checks failed
build / build (push) Failing after 2s
Some checks failed
build / build (push) Failing after 2s
This commit is contained in:
@@ -12,6 +12,8 @@ import ast
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tarfile
|
||||
import tempfile
|
||||
@@ -31,7 +33,8 @@ if TYPE_CHECKING:
|
||||
from .config import Settings
|
||||
|
||||
|
||||
A2A_PACK_MIN_VERSION = "0.1.79"
|
||||
A2A_PACK_MIN_VERSION = "0.1.80"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -213,6 +216,17 @@ _SKILL_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$")
|
||||
_BUILDER_STATE_FILE = ".a2a-builder-state.json"
|
||||
_BUILDER_INTERNAL_PREFIX = ".agent-builder/"
|
||||
_DERIVED_PRICING_FIELDS = frozenset({"compute", "total_usd"})
|
||||
_EXCLUDED_SOURCE_PARTS = frozenset({
|
||||
"__pycache__",
|
||||
".git",
|
||||
".pytest_cache",
|
||||
".mypy_cache",
|
||||
".ruff_cache",
|
||||
".venv",
|
||||
"node_modules",
|
||||
})
|
||||
_EXCLUDED_SOURCE_SUFFIXES = (".pyc", ".pyo", ".pyd")
|
||||
_EXCLUDED_SOURCE_PART_SUFFIXES = (".egg-info",)
|
||||
|
||||
|
||||
def _validate_name(name: str) -> None:
|
||||
@@ -239,6 +253,24 @@ def _builder_state_key(name: str) -> str:
|
||||
return _agent_prefix(name) + _BUILDER_STATE_FILE
|
||||
|
||||
|
||||
def _should_include_source_path(path: str) -> bool:
|
||||
normalized = path.replace("\\", "/").strip("/")
|
||||
if not normalized:
|
||||
return False
|
||||
parts = normalized.split("/")
|
||||
if any(part in _EXCLUDED_SOURCE_PARTS for part in parts):
|
||||
return False
|
||||
if any(part.endswith(_EXCLUDED_SOURCE_PART_SUFFIXES) for part in parts):
|
||||
return False
|
||||
return not normalized.endswith(_EXCLUDED_SOURCE_SUFFIXES)
|
||||
|
||||
|
||||
def _should_include_agent_rel_path(rel: str) -> bool:
|
||||
if rel == _BUILDER_STATE_FILE or rel.startswith(_BUILDER_INTERNAL_PREFIX):
|
||||
return False
|
||||
return _should_include_source_path(rel)
|
||||
|
||||
|
||||
def build_tools(ctx: ToolContext) -> list[Any]:
|
||||
bucket = ctx.bucket
|
||||
settings = ctx.settings
|
||||
@@ -306,9 +338,14 @@ def build_tools(ctx: ToolContext) -> list[Any]:
|
||||
out: list[dict[str, Any]] = []
|
||||
for key in store.iter_keys(prefix):
|
||||
rel = key[len(prefix):]
|
||||
if rel == _BUILDER_STATE_FILE or rel.startswith(_BUILDER_INTERNAL_PREFIX):
|
||||
if not _should_include_agent_rel_path(rel):
|
||||
continue
|
||||
out.append({"path": rel, "size": len(store.get(key))})
|
||||
try:
|
||||
size = len(store.get(key))
|
||||
except FileNotFoundError:
|
||||
logger.warning("Skipping disappeared source file during list: %s", key)
|
||||
continue
|
||||
out.append({"path": rel, "size": size})
|
||||
return json.dumps({"agent": name, "files": out})
|
||||
|
||||
@tool
|
||||
@@ -323,7 +360,7 @@ def build_tools(ctx: ToolContext) -> list[Any]:
|
||||
except ValueError as exc:
|
||||
return json.dumps({"error": str(exc)})
|
||||
rel = path.lstrip("/")
|
||||
if rel == _BUILDER_STATE_FILE or rel.startswith(_BUILDER_INTERNAL_PREFIX):
|
||||
if not _should_include_agent_rel_path(rel):
|
||||
return json.dumps({"error": f"{rel} is managed by agent-builder"})
|
||||
sanitized_fields: tuple[str, ...] = ()
|
||||
if rel == "agent.py":
|
||||
@@ -349,7 +386,7 @@ def build_tools(ctx: ToolContext) -> list[Any]:
|
||||
except ValueError as exc:
|
||||
return json.dumps({"error": str(exc)})
|
||||
rel = path.lstrip("/")
|
||||
if rel == _BUILDER_STATE_FILE or rel.startswith(_BUILDER_INTERNAL_PREFIX):
|
||||
if not _should_include_agent_rel_path(rel):
|
||||
return json.dumps({"error": f"{rel} is managed by agent-builder"})
|
||||
key = prefix + rel
|
||||
try:
|
||||
@@ -430,6 +467,7 @@ def build_tools(ctx: ToolContext) -> list[Any]:
|
||||
b64 = base64.b64encode(bundle_bytes).decode("ascii")
|
||||
script = (
|
||||
"set -e\n"
|
||||
"export PYTHONDONTWRITEBYTECODE=1\n"
|
||||
f"pip install --quiet 'a2a-pack>={A2A_PACK_MIN_VERSION}' >/dev/null\n"
|
||||
"mkdir -p /tmp/agent\n"
|
||||
f"echo '{b64}' | base64 -d | tar -xzf - -C /tmp/agent\n"
|
||||
@@ -949,11 +987,13 @@ def _tarball_workspace_dir(
|
||||
with tarfile.open(fileobj=buf, mode="w:gz") as tf:
|
||||
for key in store.iter_keys(prefix):
|
||||
rel = key[len(prefix):]
|
||||
if not rel:
|
||||
if not _should_include_agent_rel_path(rel):
|
||||
continue
|
||||
if rel == _BUILDER_STATE_FILE or rel.startswith(_BUILDER_INTERNAL_PREFIX):
|
||||
try:
|
||||
body = store.get(key)
|
||||
except FileNotFoundError:
|
||||
logger.warning("Skipping disappeared source file during tarball: %s", key)
|
||||
continue
|
||||
body = store.get(key)
|
||||
info = tarfile.TarInfo(name=rel)
|
||||
info.size = len(body)
|
||||
info.mode = 0o644
|
||||
@@ -1165,7 +1205,7 @@ def _files_from_tarball(bundle: bytes) -> dict[str, bytes]:
|
||||
rel = rel[2:]
|
||||
if not rel or rel.startswith("/") or ".." in rel.split("/"):
|
||||
raise ValueError(f"unsafe member path: {member.name}")
|
||||
if rel == _BUILDER_STATE_FILE:
|
||||
if not _should_include_agent_rel_path(rel):
|
||||
continue
|
||||
extracted = tf.extractfile(member)
|
||||
if extracted is None:
|
||||
@@ -1177,6 +1217,7 @@ def _files_from_tarball(bundle: bytes) -> dict[str, bytes]:
|
||||
|
||||
|
||||
def _compile_agent_dsl_json(bundle: bytes) -> str:
|
||||
import sys
|
||||
import yaml
|
||||
from a2a_pack import apply_project_manifest, compile_agent_to_dsl
|
||||
from a2a_pack.cli.loader import load_agent_class
|
||||
@@ -1197,21 +1238,32 @@ 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")
|
||||
cls = load_agent_class(entrypoint, project_dir=root)
|
||||
apply_project_manifest(cls, cfg)
|
||||
dsl = compile_agent_to_dsl(
|
||||
cls,
|
||||
language="python",
|
||||
entrypoint=entrypoint,
|
||||
metadata={
|
||||
"source": "agent-builder",
|
||||
"project_manifest": {
|
||||
key: value
|
||||
for key, value in cfg.items()
|
||||
if key in {"name", "version", "entrypoint", "frontend"}
|
||||
old_dont_write_bytecode = os.environ.get("PYTHONDONTWRITEBYTECODE")
|
||||
old_sys_dont_write_bytecode = sys.dont_write_bytecode
|
||||
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
|
||||
sys.dont_write_bytecode = True
|
||||
try:
|
||||
cls = load_agent_class(entrypoint, project_dir=root)
|
||||
apply_project_manifest(cls, cfg)
|
||||
dsl = compile_agent_to_dsl(
|
||||
cls,
|
||||
language="python",
|
||||
entrypoint=entrypoint,
|
||||
metadata={
|
||||
"source": "agent-builder",
|
||||
"project_manifest": {
|
||||
key: value
|
||||
for key, value in cfg.items()
|
||||
if key in {"name", "version", "entrypoint", "frontend"}
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
finally:
|
||||
if old_dont_write_bytecode is None:
|
||||
os.environ.pop("PYTHONDONTWRITEBYTECODE", None)
|
||||
else:
|
||||
os.environ["PYTHONDONTWRITEBYTECODE"] = old_dont_write_bytecode
|
||||
sys.dont_write_bytecode = old_sys_dont_write_bytecode
|
||||
return dsl.model_dump_json()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user