Feat/1479 OpenAI server#1507
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces an OpenAI-compatible server adapter for the BeeAI framework, enabling agents to be served via standard Chat Completions and Responses APIs with streaming support. Feedback focuses on improving server robustness and lifecycle management: wrapping the Express server startup in a Promise to handle errors, safely parsing authorization headers to prevent runtime crashes, cleaning up streaming event listeners on client disconnect to avoid memory leaks, and wrapping JSON parsing of tool arguments in try-catch blocks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Tomas2D
left a comment
There was a problem hiding this comment.
Thanks for porting the OpenAI server to TypeScript, @Veselin-Vasilev-IBM-1 — the overall structure (extending Server<…>, per-request agent/memory cloning, abort handling) follows our A2AServer/MCPServer pattern well. A few things need addressing before this can merge:
🔴 Security — reintroduces a vulnerability we just patched (blocking). OpenAIServerConfig.host defaults to "0.0.0.0" with apiKey undefined, so serve() binds an unauthenticated server to all interfaces by default — and examples/serve/openai_responses.ts even hard-codes host: "0.0.0.0". This is exactly the issue (#1516) that was just fixed on the Python side in beb08b3a (default to loopback 127.0.0.1, warn on non-loopback bind without an API key). Please mirror that fix here and drop the 0.0.0.0 from the example.
🔴 Correctness — streaming emits no content for the example agent (blocking). Both streaming handlers filter for event.name === "update", but ToolCallingAgent (used in both examples) only emits start/success — only ReActAgent emits update. So stream: true against the documented example yields an SSE envelope with zero text deltas. Please emit from the events the agent actually produces (or switch the example agent).
Other:
- The branch currently conflicts with
main(DIRTY) — please rebase/resolve. - The fork-gated Lint & Build & Test workflow hasn't run yet; it needs to pass.
- Minor:
sequence_numberin the Responses stream is stale/duplicated (built beforesendEventincrements), violating the monotonic-sequence contract; and the docs Examples section isn't updated.
Happy to help with the loopback-default port if useful.
Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
- Use .observe() + emitter.match() on the run instead of clonedAgent.emitter.on() to correctly subscribe to agent run events using the BeeAI pattern - Add try/catch around the streaming run() in ChatCompletionAPI so agent errors after SSE headers are sent produce a proper error chunk instead of an unhandled rejection - Pass SystemMessage through transformRequestMessages instead of silently dropping it, so callers' system prompts are respected - Remove cast to ReActAgentRunOutput in non-streaming paths; use the generic result.result.text which works for all agent types - Fix ResponsesStreamPartOutputText.type literal to "output_text" (was incorrectly "response.output_text.done" per OpenAI spec) - Remove double-mount at "/" in OpenAIServer.serve(); keep only "/v1" to match the OpenAI URL structure - Fix indentation inconsistency in utils.ts assistant tool-call loop Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
…rver adapter - Improve Authorization header check using regex - Wrap tool call arguments JSON parsing in try-catch to avoid unhandled exceptions - Add checks for `res.writableEnded` and `res.destroyed` to avoid unhandled stream write rejections - Fix eslint curly brace and unused variable warnings - Run prettier for formatting consistency Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
…nce fixes to TS Mirrors the Python-side security/correctness fixes (beb08b3) in the TypeScript OpenAI server adapter and addresses remaining code-review comments. Security (i-am-bee#1516): - Default OpenAIServerConfig.host to 127.0.0.1 (loopback); binding all interfaces (0.0.0.0) is now opt-in. - Warn when serving on a non-loopback host with no apiKey set. - Drop the hard-coded host: '0.0.0.0' from examples/serve/openai_responses.ts. Correctness (streaming): - ChatCompletionAPI and ResponsesAPI now emit text deltas from the events the agent actually produces. Previously both handlers filtered for event.name === 'update', but ToolCallingAgent only emits start/success, so stream: true against the documented example yielded an SSE envelope with zero text deltas. Now 'success' (state.result.text) is also emitted, keeping streaming working for both ReActAgent and ToolCallingAgent. Sequence number: - sendEvent() now increments sequence_number before assigning it to the event payload, instead of pre-building events with a stale/duplicated sequence_number, violating the monotonic-sequence contract. - Removed redundant sequence_number fields from event objects; the type is now optional and populated centrally by sendEvent. Docs: - Replace the 'coming soon' TypeScript placeholder in integrations/openai-api.mdx with the real openai_responses.ts example. Tests: - Add unit tests mirroring Python's test_openai_server_config.py: default host is loopback; warning fires for non-loopback without apiKey; no warning on loopback or when apiKey is set. Expose the module-level logger so tests can spy on warn(). Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
76a3f27 to
74f2e62
Compare
74f2e62 to
d9f6437
Compare
Replace 0.0.0.0 with 127.0.0.1 in the curl examples for the Responses and Chat Completion APIs, consistent with the security fix that defaults OpenAIServer to loopback (127.0.0.1) and warns on non-loopback binds without an API key. Signed-off-by: Veselin-Vasilev-IBM-1 <veselin.vasilev1@ibm.com>
|
Thanks @Tomas2D , 🔴 Security (loopback default) — 🔴 Streaming correctness — Both Other fixes:
Local validation:
Can you elaborate more on the "Lint & Build & Test" workflow - is this something I need to trigger ? |
Which issue(s) does this pull-request address?
Closes: #1479
Description
Adds an OpenAI-compatible server adapter for TypeScript, porting the functionality from the existing Python implementation (
python/beeai_framework/adapters/openai/serve).This exposes any BeeAI agent as a standard OpenAI
/v1/chat/completionsand/v1/responsesendpoint, enabling integration with tools like Watsonx Orchestrate that consume these APIs (ref: discussion #1008).What's included:
OpenAIServerclass following the same pattern as the existingA2AServerandMCPServeradapters, usingBaseAgentas the interim abstraction. Configurable viaapi: "chat-completion" | "responses".ChatCompletionAPI: Express-based router handling/v1/chat/completionswith both non-streaming and SSE streaming responses.ResponsesAPI: Express-based router handling the newer/v1/responsesendpoint, matching the Python implementation's object formats and streaming event sequences (response.created,response.in_progress, etc.).openaiMessageToBeeAIMessageandopenaiInputToBeeAIMessage) to translate between OpenAI and BeeAI message formats.examples/serve/openai.tsandexamples/serve/openai_responses.ts.No breaking changes.
Checklist
General
TypeScriptCode quality checks
mise check—yarn tsc --noEmitpasses cleanly (0 errors from this PR; 2 pre-existing errors inwatsonx/backend/chat.ts). Fullmise checkcould not run locally due to missingpipx/GitHub API auth in tool setup.Testing
A2AServer,MCPServer) which have no E2E tests. However, both endpoints have been manually verified end-to-end viacurlagainst a local Ollama instance (granite4:micro), confirming both string/array inputs and proper JSON output formats.Documentation