feat(cli): add default-openai-agents init template (async base)#434
Conversation
76361ef to
1c5dd14
Compare
cd6bb70 to
ab23d6d
Compare
|
@greptile review |
1c5dd14 to
86ce557
Compare
ab23d6d to
d3f1fb6
Compare
|
@greptile review |
|
@greptile review |
| agent = get_agent() | ||
| task_id = params.task.id | ||
| agent_id = params.agent.id | ||
| user_message = params.event.content.content |
There was a problem hiding this comment.
This handler assumes every incoming event is a text message by reading params.event.content.content and placing it directly into an OpenAI user input item. SendEventParams.event.content is optional and is the full TaskMessageContent union, so data, tool, or empty events can also reach event/send; for those messages .content may be a dict/list or absent in the expected shape, which creates invalid OpenAI Agents input or raises before the event is processed. Guard for TextContent and explicitly reject, ignore, or convert non-text content before appending to input_list.
Artifacts
Repro: generated handler runtime repro script
- Contains supporting evidence from the run (text/x-python; charset=utf-8).
Repro: runtime output showing missing and data event failures
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/cli/templates/default-openai-agents/project/acp.py.j2
Line: 115
Comment:
**Non-text events break**
This handler assumes every incoming event is a text message by reading `params.event.content.content` and placing it directly into an OpenAI user input item. `SendEventParams.event.content` is optional and is the full `TaskMessageContent` union, so data, tool, or empty events can also reach `event/send`; for those messages `.content` may be a dict/list or absent in the expected shape, which creates invalid OpenAI Agents input or raises before the event is processed. Guard for `TextContent` and explicitly reject, ignore, or convert non-text content before appending to `input_list`.
How can I resolve this? If you propose a fix, please make it concise.| result = Runner.run_streamed(starting_agent=agent, input=state.input_list) | ||
| turn = OpenAITurn(result=result, model=MODEL_NAME) | ||
| emitter = UnifiedEmitter( | ||
| task_id=task_id, | ||
| trace_id=task_id, | ||
| parent_span_id=turn_span.id if turn_span else None, | ||
| ) | ||
| turn_result = await emitter.auto_send_turn(turn) | ||
|
|
||
| # Persist the full conversation history (user + assistant + tool calls) | ||
| # so the next turn resumes with complete context. | ||
| state.input_list = result.to_input_list() | ||
| await adk.state.update( |
There was a problem hiding this comment.
The new user turn is only persisted after auto_send_turn fully exhausts the OpenAI stream. If the model, tool, or emitter stream raises after producing partial output, the handler exits before state.input_list = result.to_input_list() and adk.state.update(...), even though the user's message was already echoed to task history. The next retry or follow-up reloads the old state and re-runs without this accepted user turn, causing duplicate or lost context. Persist the appended user message before starting the run, or update state in a failure path with the best available history.
Artifacts
Repro: generated harness that executes the rendered ACP template with a failing partial stream
- Contains supporting evidence from the run (text/x-python; charset=utf-8).
Stack trace captured during the T-Rex run
- Keeps the raw stack trace available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/cli/templates/default-openai-agents/project/acp.py.j2
Line: 141-153
Comment:
**Failed streams lose input**
The new user turn is only persisted after `auto_send_turn` fully exhausts the OpenAI stream. If the model, tool, or emitter stream raises after producing partial output, the handler exits before `state.input_list = result.to_input_list()` and `adk.state.update(...)`, even though the user's message was already echoed to task history. The next retry or follow-up reloads the old state and re-runs without this accepted user turn, causing duplicate or lost context. Persist the appended user message before starting the run, or update state in a failure path with the best available history.
How can I resolve this? If you propose a fix, please make it concise.86ce557 to
5f81fc4
Compare
4310435 to
1638b8b
Compare
|
@greptile review |
5f81fc4 to
7f0d754
Compare
1638b8b to
3a13b08
Compare
7f0d754 to
b158aa2
Compare
3a13b08 to
e4db8d5
Compare
danielmillerp
left a comment
There was a problem hiding this comment.
thank you I know this was missing haha
e4db8d5 to
9fc5e23
Compare
b158aa2 to
561884d
Compare
9fc5e23 to
f55806c
Compare
561884d to
96141e0
Compare
| "# - ToolRequestContent: A message with a tool request, which contains a JSON-serializable request to call a tool\n", | ||
| "# - ToolResponseContent: A message with a tool response, which contains response object from a tool call in its content\n", | ||
| "\n", | ||
| "# When processing the message/send response, if you are expecting more than TextContent, such as DataContent, ToolRequestContent, or ToolResponseContent, you can process them as well\n", | ||
| "\n", | ||
| "rpc_response = client.agents.send_message(\n", | ||
| " agent_name=AGENT_NAME,\n", | ||
| " params={\n", | ||
| " \"content\": {\"type\": \"text\", \"author\": \"user\", \"content\": \"Hello what can you do?\"},\n", | ||
| " \"stream\": False\n", | ||
| " }\n", | ||
| ")\n", | ||
| "\n", | ||
| "if not rpc_response or not rpc_response.result:\n", | ||
| " raise ValueError(\"No result in response\")\n", | ||
| "\n", | ||
| "# Extract and print just the text content from the response\n", | ||
| "for task_message in rpc_response.result:\n", | ||
| " content = task_message.content\n", | ||
| " if isinstance(content, TextContent):\n", | ||
| " text = content.content\n", | ||
| " print(text)\n" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "79688331", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "# Test streaming response\n", | ||
| "from agentex.types.task_message_update import StreamTaskMessageDelta, StreamTaskMessageFull\n", | ||
| "from agentex.types.text_delta import TextDelta\n", | ||
| "\n", | ||
| "\n", | ||
| "# The result object of message/send will be a TaskMessageUpdate which is a union of the following types:\n", | ||
| "# - StreamTaskMessageStart: \n", | ||
| "# - An indicator that a streaming message was started, doesn't contain any useful content\n", | ||
| "# - StreamTaskMessageDelta: \n", | ||
| "# - A delta of a streaming message, contains the text delta to aggregate\n", | ||
| "# - StreamTaskMessageDone: \n", | ||
| "# - An indicator that a streaming message was done, doesn't contain any useful content\n", | ||
| "# - StreamTaskMessageFull: \n", | ||
| "# - A non-streaming message, there is nothing to aggregate, since this contains the full message, not deltas\n", | ||
| "\n", | ||
| "# Whenn processing StreamTaskMessageDelta, if you are expecting more than TextDeltas, such as DataDelta, ToolRequestDelta, or ToolResponseDelta, you can process them as well\n", | ||
| "# Whenn processing StreamTaskMessageFull, if you are expecting more than TextContent, such as DataContent, ToolRequestContent, or ToolResponseContent, you can process them as well\n", | ||
| "\n", | ||
| "for agent_rpc_response_chunk in client.agents.send_message_stream(\n", | ||
| " agent_name=AGENT_NAME,\n", | ||
| " params={\n", | ||
| " \"content\": {\"type\": \"text\", \"author\": \"user\", \"content\": \"Hello what can you do?\"},\n", | ||
| " \"stream\": True\n", | ||
| " }\n", | ||
| "):\n", | ||
| " # We know that the result of the message/send when stream is set to True will be a TaskMessageUpdate\n", | ||
| " task_message_update = agent_rpc_response_chunk.result\n", |
There was a problem hiding this comment.
This notebook uses the sync message/send helpers, but the generated agent is an async ACP template that only registers event/send through on_task_event_send. The async FastACP server returns an immediate {status: "processing"} acknowledgement for async methods and does not expose a message/send handler, so both client.agents.send_message(...) and send_message_stream(...) can either get Method message/send not found or never receive the agent output. The async notebook should send an event and then subscribe to the task messages, like the base async template.
Artifacts
Repro: notebook-equivalent JSON-RPC harness
- Contains supporting evidence from the run (text/x-python; charset=utf-8).
Repro: failing message/send and accepted event/send output
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/cli/templates/default-openai-agents/dev.ipynb.j2
Line: 64-121
Comment:
**Notebook calls wrong API**
This notebook uses the sync `message/send` helpers, but the generated agent is an async ACP template that only registers `event/send` through `on_task_event_send`. The async FastACP server returns an immediate `{status: "processing"}` acknowledgement for async methods and does not expose a `message/send` handler, so both `client.agents.send_message(...)` and `send_message_stream(...)` can either get `Method message/send not found` or never receive the agent output. The async notebook should send an event and then subscribe to the task messages, like the base async template.
How can I resolve this? If you propose a fix, please make it concise.96141e0 to
b30a90b
Compare
Add the missing async-base OpenAI Agents SDK template, wiring DEFAULT_OPENAI_AGENTS into the init flow (enum, project files, async prompt). The scaffolded acp.py imports OpenAITurn from the agentex.lib.adk facade, matching the other Turn-based templates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address Greptile review: the async base handler ran each turn with only the current user_message, so multi-turn context was dropped. Persist the OpenAI input list per task via adk.state and replay it into Runner.run_streamed, matching the sync and temporal openai-agents templates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f55806c to
9861b8b
Compare
Summary
Eighth slice of #425. Adds the missing async-base OpenAI Agents SDK init template.
default-openai-agentstemplate (all 11 scaffold files).DEFAULT_OPENAI_AGENTSintoinit.py(enum, project-files map, async prompt).acp.pyimportsOpenAITurnfrom theagentex.lib.adkfacade (added in refactor(harness): move OpenAI harness into adk/_modules + facade export #432), matching every other Turn-based template.Test plan
pytest tests/lib/cli/ -k "init or template"— 20 passed (template renders to valid Python)Notes
Stacked on #433. Retarget to
nextafter the chain merges.🤖 Generated with Claude Code
Greptile Summary
This PR adds the
default-openai-agentsasync template (11 scaffold files) and wires it into the CLI init command as the eighth slice of the template-expansion stack. Compared to the previous review round, several issues have been resolved: SGP tracing is now correctly gated onif _sgp_api_key and _sgp_account_id, the agent is rebuilt per-request sodatetime.now()in the instructions stays current, both Dockerfiles correctly use thenodejsapt package, and the notebook streaming cell consistently referencestask_message_update.init.py:DEFAULT_OPENAI_AGENTSis cleanly added to the enum, project-files map, and async template prompt choices.project/acp.py.j2: Async handler runs the OpenAI Agents SDK viaOpenAITurn+UnifiedEmitter.auto_send_turn, persists conversation history inStateModel, and streams output over Redis.StateModel.turn_numberis tracked but never read.Dockerfile.j2/Dockerfile-uv.j2: Pip and uv build variants; the uv variant's dependency on a pre-existinguv.lockis a known open item from the prior review.Confidence Score: 5/5
The new template is additive and gated behind a CLI prompt — no existing paths are modified. The fixes applied in this revision address the regressions identified earlier.
The changes are entirely new scaffold files plus clean enum/map wiring in init.py. The only newly identified issue in this pass is a dead turn_number field that accumulates unused state but does not affect correctness. No fresh correctness regressions were introduced in this revision.
The dev.ipynb.j2 notebook and Dockerfile-uv.j2 have the most outstanding open items from earlier review rounds.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Client participant FastACP as FastACP (async) participant Handler as handle_task_event_send participant ADK as adk.state / adk.messages participant Runner as OpenAI Agents Runner participant Emitter as UnifiedEmitter participant Redis Client->>FastACP: event/send (SendEventParams) FastACP->>Handler: dispatch Handler->>ADK: messages.create(user content) Handler->>ADK: state.get_by_task_and_agent() alt state is None Handler->>ADK: state.create(StateModel) end Handler->>Handler: append user turn to input_list Handler->>Runner: "Runner.run_streamed(input=input_list)" Handler->>Emitter: auto_send_turn(OpenAITurn) Emitter->>Runner: iterate streamed events Emitter->>Redis: "push StreamTaskMessage* events" Emitter-->>Handler: TurnResult Handler->>ADK: state.update(result.to_input_list()) FastACP-->>Client: 202 processing%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Client participant FastACP as FastACP (async) participant Handler as handle_task_event_send participant ADK as adk.state / adk.messages participant Runner as OpenAI Agents Runner participant Emitter as UnifiedEmitter participant Redis Client->>FastACP: event/send (SendEventParams) FastACP->>Handler: dispatch Handler->>ADK: messages.create(user content) Handler->>ADK: state.get_by_task_and_agent() alt state is None Handler->>ADK: state.create(StateModel) end Handler->>Handler: append user turn to input_list Handler->>Runner: "Runner.run_streamed(input=input_list)" Handler->>Emitter: auto_send_turn(OpenAITurn) Emitter->>Runner: iterate streamed events Emitter->>Redis: "push StreamTaskMessage* events" Emitter-->>Handler: TurnResult Handler->>ADK: state.update(result.to_input_list()) FastACP-->>Client: 202 processingComments Outside Diff (1)
General comment
TemplateType.DEFAULT_OPENAI_AGENTSis selectable and the async prompt label is present, but executingcreate_project_structure(..., TemplateType.DEFAULT_OPENAI_AGENTS, use_uv=True)generated only.dockerignore,.env.example,Dockerfile,README.md,dev.ipynb,environments.yaml,manifest.yaml,project/__init__.py,project/acp.py, andpyproject.toml. The validation expected the changed template artifact set to include bothDockerfile-uvandrequirements.txtas scaffold outputs; those were missing, and an extraproject/__init__.pywas generated relative to the requested exact scaffold assertion.src/agentex/lib/cli/commands/init.pyconditionally maps package-management templates: withuse_uv=True,Dockerfile-uv.j2is rendered toDockerfileand onlypyproject.tomlis emitted;requirements.txt.j2and a distinctDockerfile-uvfile are not emitted. The common project directory creation also touchesproject/__init__.py, so the output cannot match the requested exact 11-file scaffold set.create_project_structureto emitDockerfile-uv,Dockerfile,pyproject.toml, andrequirements.txtforDEFAULT_OPENAI_AGENTSas required, and decide whetherproject/__init__.pyshould be part of the documented expected set. If package-manager-specific output is intended instead, update the PR contract/tests to expect the conditional file set.Reviews (12): Last reviewed commit: "fix(cli): persist conversation history i..." | Re-trigger Greptile