Skip to content

Commit e928801

Browse files
danielmillerpclaude
andcommitted
feat(cli): add sync-openai-agents-local-sandbox init template + fix 050 test helper
- New 'agentex init' template 'sync-openai-agents-local-sandbox' (registered in init.py: TemplateType enum + project_files + menu) that scaffolds an OpenAI Agents SDK agent on the local (unix_local) sandbox, based on the 050 tutorial. - Fix 050 sync test's _response_text helper to dig through TaskMessage->TextContent (it previously dropped non-str .content; verified locally the agent returns the correct answer). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3ef8794 commit e928801

16 files changed

Lines changed: 1212 additions & 6 deletions

File tree

examples/tutorials/00_sync/050_openai_agents_local_sandbox/tests/test_agent.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,25 @@ def agent_id(client, agent_name):
5050

5151

5252
def _response_text(result) -> str:
53-
"""Flatten a send_message result into a single string for assertions."""
54-
parts = []
55-
for content in result:
56-
text = getattr(content, "content", None)
57-
if isinstance(text, str):
58-
parts.append(text)
53+
"""Flatten a send_message result into a single string for assertions.
54+
55+
Result items may be a bare string, a ``TextContent`` (``.content`` is the
56+
string), or a ``TaskMessage`` wrapping a ``TextContent`` (``.content`` is the
57+
``TextContent``, whose ``.content`` is the string). Dig through ``.content``
58+
until we reach a string.
59+
"""
60+
61+
def _text_of(obj, _depth: int = 0) -> str:
62+
if isinstance(obj, str):
63+
return obj
64+
if _depth > 5:
65+
return ""
66+
inner = getattr(obj, "content", None)
67+
if inner is None:
68+
return ""
69+
return _text_of(inner, _depth + 1)
70+
71+
parts = [t for t in (_text_of(item) for item in result) if t]
5972
return "\n".join(parts)
6073

6174

src/agentex/lib/cli/commands/init.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class TemplateType(str, Enum):
3030
DEFAULT_PYDANTIC_AI = "default-pydantic-ai"
3131
SYNC = "sync"
3232
SYNC_OPENAI_AGENTS = "sync-openai-agents"
33+
SYNC_OPENAI_AGENTS_LOCAL_SANDBOX = "sync-openai-agents-local-sandbox"
3334
SYNC_LANGGRAPH = "sync-langgraph"
3435
SYNC_PYDANTIC_AI = "sync-pydantic-ai"
3536

@@ -68,6 +69,7 @@ def create_project_structure(
6869
TemplateType.DEFAULT_PYDANTIC_AI: ["acp.py", "agent.py", "tools.py"],
6970
TemplateType.SYNC: ["acp.py"],
7071
TemplateType.SYNC_OPENAI_AGENTS: ["acp.py"],
72+
TemplateType.SYNC_OPENAI_AGENTS_LOCAL_SANDBOX: ["acp.py", "agent.py", "tools.py"],
7173
TemplateType.SYNC_LANGGRAPH: ["acp.py", "graph.py", "tools.py"],
7274
TemplateType.SYNC_PYDANTIC_AI: ["acp.py", "agent.py", "tools.py"],
7375
}[template_type]
@@ -203,6 +205,7 @@ def validate_agent_name(text: str) -> bool | str:
203205
choices=[
204206
{"name": "Basic Sync ACP", "value": TemplateType.SYNC},
205207
{"name": "Sync ACP + OpenAI Agents SDK (Recommended)", "value": TemplateType.SYNC_OPENAI_AGENTS},
208+
{"name": "Sync ACP + OpenAI Agents SDK + Local Sandbox", "value": TemplateType.SYNC_OPENAI_AGENTS_LOCAL_SANDBOX},
206209
{"name": "Sync ACP + LangGraph", "value": TemplateType.SYNC_LANGGRAPH},
207210
{"name": "Sync ACP + Pydantic AI", "value": TemplateType.SYNC_PYDANTIC_AI},
208211
],
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Environments
24+
.env**
25+
.venv
26+
env/
27+
venv/
28+
ENV/
29+
env.bak/
30+
venv.bak/
31+
32+
# IDE
33+
.idea/
34+
.vscode/
35+
*.swp
36+
*.swo
37+
38+
# Git
39+
.git
40+
.gitignore
41+
42+
# Misc
43+
.DS_Store
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# {{ agent_name }} - Environment Variables
2+
# Copy this file to .env and fill in the values
3+
4+
# API key for your LLM provider
5+
LITELLM_API_KEY=
6+
7+
# LLM base URL (optional - override to use a different provider)
8+
# OPENAI_BASE_URL=
9+
10+
# SGP Configuration (optional - for tracing)
11+
# SGP_API_KEY=
12+
# SGP_ACCOUNT_ID=
13+
# SGP_CLIENT_BASE_URL=
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# syntax=docker/dockerfile:1.3
2+
FROM python:3.12-slim
3+
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/
4+
5+
# Install system dependencies
6+
RUN apt-get update && apt-get install -y \
7+
htop \
8+
vim \
9+
curl \
10+
tar \
11+
python3-dev \
12+
postgresql-client \
13+
build-essential \
14+
libpq-dev \
15+
gcc \
16+
cmake \
17+
netcat-openbsd \
18+
nodejs \
19+
npm \
20+
&& apt-get clean \
21+
&& rm -rf /var/lib/apt/lists/**
22+
23+
ENV UV_COMPILE_BYTECODE=1
24+
ENV UV_LINK_MODE=copy
25+
ENV UV_HTTP_TIMEOUT=1000
26+
27+
WORKDIR /app/{{ project_path_from_build_root }}
28+
29+
# Copy dependency files for layer caching
30+
COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./
31+
32+
# Install dependencies (without project itself, for layer caching)
33+
RUN --mount=type=cache,target=/root/.cache/uv \
34+
uv sync --locked --no-install-project --no-dev
35+
36+
# Copy the project code
37+
COPY {{ project_path_from_build_root }}/project ./project
38+
39+
# Install the project
40+
RUN --mount=type=cache,target=/root/.cache/uv \
41+
uv sync --locked --no-dev
42+
43+
ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH"
44+
ENV PYTHONPATH=/app
45+
46+
# Run the agent using uvicorn
47+
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# syntax=docker/dockerfile:1.3
2+
FROM python:3.12-slim
3+
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/
4+
5+
# Install system dependencies
6+
RUN apt-get update && apt-get install -y \
7+
htop \
8+
vim \
9+
curl \
10+
tar \
11+
python3-dev \
12+
postgresql-client \
13+
build-essential \
14+
libpq-dev \
15+
gcc \
16+
cmake \
17+
netcat-openbsd \
18+
node \
19+
npm \
20+
&& apt-get clean \
21+
&& rm -rf /var/lib/apt/lists/*
22+
23+
RUN uv pip install --system --upgrade pip setuptools wheel
24+
25+
ENV UV_HTTP_TIMEOUT=1000
26+
27+
# Copy just the requirements file to optimize caching
28+
COPY {{ project_path_from_build_root }}/requirements.txt /app/{{ project_path_from_build_root }}/requirements.txt
29+
30+
WORKDIR /app/{{ project_path_from_build_root }}
31+
32+
# Install the required Python packages
33+
RUN uv pip install --system -r requirements.txt
34+
35+
# Copy the project code
36+
COPY {{ project_path_from_build_root }}/project /app/{{ project_path_from_build_root }}/project
37+
38+
39+
# Set environment variables
40+
ENV PYTHONPATH=/app
41+
42+
# Run the agent using uvicorn
43+
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]

0 commit comments

Comments
 (0)