OpenAI Agents SDK (Python) integration - #2210
Conversation
…penAI Agents SDK - Configure `pyproject.toml` with project metadata and dependencies. - Include required `.python-version` file. - Add `README.md` with installation instructions. - Generate `uv.lock` file with dependency resolution.
…ing parts
- Change tool_result_id from random `toolresult_<hex>` to deterministic `{call_id}-result`,
linking result message visually and functionally to its tool call. Matches claude-agent-sdk pattern.
- Change reasoning part id separator from `/` to `-` (e.g., `rs_abc-1` not `rs_abc/1`).
Hyphen never appears in wire ids, so suffix clearly marks our derivations.
- new_tool_result_id(call_id) now takes call_id param; determinism aids dedup/replay safety.
- Reasoning fallback generation via new_reasoning_id() was already in place (rs_<hex> prefix).
All generated id shapes now mimic their native OpenAI counterparts; hyphen-suffixed
ids are transparently ours. Pass-through chain unchanged (call_id → fc_ → generated).
…ubpackage - Create public interfaces: AGUITranslator (streamed) and AGUINonStreamingTranslator (sync/async) - Move per-direction translation logic into engine/ subpackage for better separation of concerns - Flatten public module structure at package root to align with sibling integrations - Maintain backward compatibility: translator/ subpackage imports resolve via translator.py - Update README to serve as integration usage documentation This restructuring improves maintainability by isolating implementation details while keeping the public API simple and discoverable.
…and improve README with streaming examples
…te non-streaming translator
…e snapshot handling
…e events and append MESSAGES_SNAPSHOT
Add emit_state_snapshot (default True): after RUN_STARTED, echo run_input.state as a STATE_SNAPSHOT including empty state
… translator-first docs
…) with demo example
… and async sources
…ents, not factories returning them
…AGENTS_DISABLE_TRACING default
|
Hey @abdelrahmanzied, thanks for this. It's a clean, complete integration, and the checklist was met from the contributing guide. One thing to confirm: the new On the PyPI enrollment note, we'll handle that maintainer-side, nothing needed from you. Nice contribution. 🚀 |
Thanks for your kind feedback 😊 Yes, I can confirm that Dojo reaches the OpenAI Agents backend in the CI-style E2E setup using the existing Thanks for clarifying the PyPI enrollment as well! |
|
Great, @abdelrahmanzied, I've pinged engineering to get their final review. |
…-protocol#2210) (#1) * fix(openai-agents): CR round-1 fixes (correctness, robustness, tests) Engine: - translate(): read run_input.resume via getattr (defensive, matches parent_run_id and the documented contract) - outbound: reuse the real item id on lazy text-open instead of a generated one (honors the id-reuse invariant; avoids snapshot/stream id divergence) - outbound: buffer function-call args that stream before output_item.added instead of opening under a throwaway id, so the run-item commit no longer emits a duplicate TOOL_CALL_START/ARGS/END; finalize flushes orphans - outbound: surface REASONING_ENCRYPTED_VALUE on the run-item skip path so encrypted reasoning replay data is not dropped - helpers.to_string(): serialize pydantic models via model_dump() - helpers.__all__: add new_reasoning_id - translate_content(): drop empty/None content so empty turns are omitted Wrapper / endpoint: - OpenAIAgentsAgent: add context= injection point (falls back to AG-UI context); dedup client tools against each other - endpoint: log re-raised run errors instead of leaking an ASGI traceback; unique per-route name/operation_id so multiple agents can mount on one app Examples/dojo: - example DEFAULT_MODEL fallback -> gpt-5.5 - ApprovalCard dark-mode variants; consumerAgentId on docs_copilot & subagents - subagents e2e: scope delegation-log assertions to the log container (were matching the always-rendered team chips) Tests: covering tests for every behavioral change; snapshot test now guards against duplicate id emission. * fix(openai-agents): CR round-2 fixes (placeholder-id correlation, endpoint, ids) Confirmation round found a cluster of bugs the round-1 fixes exposed for non-native (placeholder / FAKE_RESPONSES_ID) backends, plus a few pre-existing: Outbound translator: - _is_real_id no longer misclassifies internal '__idx_' placeholder window keys as wire ids (add _is_placeholder_key guard): lazy text-open and the arg-buffer paths no longer leak placeholder keys onto the wire as message/tool_call ids - _window_key active-set now includes _pending_tool_args, so a placeholder window holding only buffered args is recognized and reused instead of split in two - run-item commit clears the correlated arg buffer (by item id, else oldest FIFO) so finalize no longer re-emits a phantom empty-name tool call - finalize drops orphaned (never-committed) arg buffers instead of emitting them - REASONING_ENCRYPTED_VALUE dedup keyed on the stable phase id (raw + skip paths) - reasoning delta-first reuses the real wire id (mirrors output_item.added) Wrapper / endpoint: - endpoint surfaces a terminal RUN_ERROR when setup fails before to_agui (was an empty 200); slug uniqueness guard so paths normalizing to one slug don't collide - translator honors an explicit empty run_error_message (only None -> str(exc)) - helpers.to_string logs a model_dump() failure instead of swallowing silently Examples/dojo: - run-dojo-everything.js OPENAI_CHAT_MODEL_ID default and commented model refs in agents.ts -> gpt-5.5 Tests: placeholder-backend coverage (arg flush/no-split, commit no-phantom, clean text id, reasoning id reuse), endpoint setup-error + slug-collision, empty run_error_message, and the text 'skip' branch now actually exercised in snapshot. * fix(openai-agents): CR round-3 fixes (SDK-floor compat, tool errors, buffer safety) Second confirmation round surfaced two regressions from the round-2 buffer fixes plus several pre-existing bugs: Outbound translator: - translate_tool_call_output_item reads call_id defensively (getattr + raw fallback): ToolCallOutputItem.call_id does not exist on the declared openai-agents>=0.8.4 floor, so a direct access turned every tool result into a RUN_ERROR on the minimum supported SDK - remove the blind FIFO buffer-drop in the tool-call 'new' branch: it could evict a *different* still-streaming call's buffered args; an uncorrelated placeholder buffer is harmlessly dropped by finalize instead Inbound translator: - translate_tool_message surfaces ToolMessage.error when content is empty (was silently sending a blank tool result the model can't recover from) - translate_document_content synthesizes a filename for base64 file_data (the Responses API requires one; mirrors _binary_as_file) Endpoint: - fallback now keys on RUN_STARTED: emits a well-formed RUN_STARTED+RUN_ERROR only on a true setup failure (run never started); stays silent (logs only) once the run started, so an explicit emit_run_error=False is honored and the terminal is never a lone RUN_ERROR without RUN_STARTED Dojo: OPENAI_AGENTS_PYTHON_URL wired into the dojo env blocks for parity. Tests: inbound message-type coverage (system/developer/assistant/reasoning/ tool-with-error/activity/context), defensive tool-output call_id, document filename, and caplog level consistency. * fix(openai-agents): CR round-4 fixes (filename synthesis, context/reasoning edges) Third confirmation round confirmed the round-3 fixes correct and surfaced a few small pre-existing edges plus one polish item on the round-3 endpoint change: - _binary_as_file synthesizes a filename for base64 file_data (mirrors translate_document_content); a filename-less binary file part was otherwise rejected by the Responses API. Comment on the document path corrected. - agent.py normalizes an empty ambient context to None (SDK default) instead of passing [], so agents branching on 'ctx.context is None' behave correctly. - endpoint setup-failure fallback RUN_STARTED now carries parent_run_id, matching the normal to_agui lifecycle path. - translate_context drops items missing either description or value (no more half-empty 'label: ' / ': value' lines). - translate_reasoning_message omits the summary entry entirely when content is empty (an empty summary_text is rejected by some backends). Tests updated/added for each; the prior test asserting the old (buggy) no-filename binary behavior now asserts the synthesized filename. * fix(openai-agents): CR round-5 fixes (SkipValidation on messages, refusal, mime) Fourth confirmation round confirmed the round-4 fixes correct; three reviewers independently traced two real bugs to one root cause -- TranslatedInput.messages being pydantic-validated against the Responses input union: - Audio input crashed to_openai(): input_audio is a valid Chat-Completions shape but not a Responses input-item member, so constructing TranslatedInput raised ValidationError and failed the WHOLE request (not just the audio part). - A reasoning item's summary (SDK-typed Iterable) was materialized as a one-shot pydantic ValidatorIterator that empties after a single read -- silent reasoning- replay data loss if messages is read more than once. Fix: mark TranslatedInput.messages SkipValidation (mirrors tools, same forward-ref rationale). The translator already guarantees well-formed shapes (e.g. images always get detail) and the SDK validates what it consumes at run time, so the removed strict validation was a redundant safety net that rejected otherwise- valid multimodal input. Audio now translates and reasoning summary stays a list. Also: - translate_message_output_item concatenates text + refusal into the snapshot (extract_text ignores refusals, so a fallback dropped a streamed refusal). - _audio_format_from_mime strips mime parameters ('audio/wav; codecs=1') before matching, so a parameterized-but-valid type isn't silently dropped. - endpoint comment clarifies the setup-failure terminal is emitted even when emit_run_error=False (a silent empty 200 is worse than a terminal error). Tests: audio-survives-to_openai, reasoning-summary-stable-list, text+refusal snapshot, mime-parameter parsing; the obsolete reject-image-without-detail test is replaced with a SkipValidation behavior test.
Fixes #167
Summary
Adds an OpenAI Agents SDK (Python) integration for the AG-UI protocol. It
translates AG-UI requests into OpenAI Agents SDK input and converts streamed SDK
output into ordered AG-UI events while preserving message, tool-call, reasoning,
thread, and run IDs.
What's included
Python package (
ag-ui-openai-agents)AGUITranslator, the recommended API for developers who need full controlover the OpenAI Agents SDK runner, transport, and orchestration. The
integration only translates between OpenAI Agents SDK data and AG-UI events.
OpenAIAgentsAgent, an optional wrapper for standard streamed runs.add_openai_agents_fastapi_endpoint, an optional FastAPI SSE endpoint wrapper.error event translation.
guardrails, backend approval flows, and supported multimodal input.
MESSAGES_SNAPSHOT.lifecycle behavior, IDs, examples, and troubleshooting.
Examples and Dojo
Adds a runnable FastAPI example server and OpenAI Agents (Python) Dojo support
for nine features:
Each listed Dojo feature has integration-specific Playwright coverage using
LLMock, so the E2E suite does not require live OpenAI calls or API credentials.
TypeScript client
Adds the repository-required TypeScript HTTP client package,
@ag-ui/openai-agents, which exposesOpenAIAgentsHttpAgentusing the baseHttpAgent. The package is private because it adds no integration-specificclient behavior; the integration runs in Python.
CI
Architecture
Test plan
134 passed20 passed2 passed11 passedacross all nine features with Node 22 and LLMockReviewer note
PyPI release enrollment is intentionally not included. Please confirm whether maintainers want to add
ag-ui-openai-agentsto the release configuration in this PR or in a maintainer-owned follow-up.