Skip to content

Add OpenAI Responses API adapter and developer role support - #15

Merged
callingmedic911 merged 9 commits into
mainfrom
aditya/openai-responses-api
Mar 10, 2026
Merged

Add OpenAI Responses API adapter and developer role support#15
callingmedic911 merged 9 commits into
mainfrom
aditya/openai-responses-api

Conversation

@callingmedic911

@callingmedic911 callingmedic911 commented Mar 9, 2026

Copy link
Copy Markdown
Member

Summary

  • New openai_responses flavor with OpenAIResponsesAdapter — strips system messages, wraps messages in {type: "message", ...} format, and formats tool schemas in the flat Responses API style
  • Role coercion system via prepareMessages()developer role passes through for openai_responses, coerces to system for openai_chat, throws for unsupported flavors
  • tool role support for OpenAI adapters (both OpenAILLMAdapter and OpenAIResponsesAdapter)
  • allMessages() now accepts arrays to support Responses API completion.output
  • Output schema support for openai_responses
  • New example in examples/ts-cjs/openaiResponsesApi.ts
  • Version bump to 0.6.0

Mirrors Python SDK changes from freeplayai/freeplay-python#17 and freeplayai/freeplay-python#18.

Note

Add OpenAI Responses API adapter and developer role support across all flavors

  • Introduces OpenAIResponsesAdapter (new openai_responses flavor) in src/model.ts that drops system-role messages and wraps remaining messages as {type: 'message', ...} for the OpenAI Responses API.
  • Adds RoleSupport policies per adapter to validate, coerce, or reject unsupported roles via a new prepareMessages function; developer role is coerced to system for standard OpenAI and added to StrictChatMessage.
  • Updates BoundPrompt.format in src/resources/prompts.ts to apply role coercion before calling toLLMSyntax, and formatToolSchema/formatOutputSchema to handle the openai_responses format.
  • FormattedPrompt.allMessages now accepts either a single message or an array of messages.
  • Adds a usage example in examples/ts-cjs/openaiResponsesApi.ts and a test suite in test/openaiResponses.test.ts.

Changes since #15 opened

  • Added flavorName parameter to prepareMessages function and updated unsupported role error messages to include the specific flavor name with guidance directing users to update their prompt template in Freeplay to use a flavor supporting the unsupported role [945466e]
  • Updated BoundPrompt.formatPrompt method to pass flavor name argument when calling prepareMessages [945466e]
  • Updated test assertions in openaiResponses.test.ts to pass flavor name argument to prepareMessages function calls [945466e]

Macroscope summarized aa6f90c.

Summary by CodeRabbit

  • New Features

    • openai_responses flavor added to produce Responses-style prompts/outputs.
    • Developer message role added with cross-provider handling and coercion rules.
    • Example script demonstrating Responses API integration and recording flow.
  • Tests

    • Expanded tests for Responses formatting, role coercion, tool/schema handling, and end-to-end Responses flows.
  • Chores

    • Version bumped to 0.6.0 and example run script added.

@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds OpenAI Responses API flavor and adapter, expands message roles to include "developer" with role coercion via prepareMessages(), updates prompt formatting and tests, adds an example script, and bumps package version to 0.6.0.

Changes

Cohort / File(s) Summary
Release & Versioning
CHANGELOG.md, package.json
Bump version to 0.6.0 and add release notes for OpenAI Responses API support and developer-role handling.
Core Model & Adapters
src/model.ts
Add developer to message roles; introduce RoleSupport and constants; add prepareMessages() and roleSupport to ILLMAdapter; add OpenAIResponsesAdapter and wire openai_responses flavor; set roleSupport defaults on adapters; override toLLMSyntax for Responses API.
Prompt Formatting
src/resources/prompts.ts
Import and use prepareMessages(); include developer in template message types; handle openai_responses when formatting tool/output schemas; use effectivePromptInfo; change FormattedPrompt.allMessages to accept unknown and return ProviderMessage[].
Examples
examples/ts-cjs/openaiResponsesApi.ts, examples/ts-cjs/package.json
Add example showing Responses API flow and recording; add openai-responses npm script.
Tests
test/openaiResponses.test.ts, test/sdk.test.ts
Add comprehensive tests for OpenAIResponsesAdapter and prepareMessages coercion rules; add flat tool schema test and small timing adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant FP as Freeplay Client
    participant PromptAPI as Prompts API
    participant OpenAI as OpenAI Responses API
    participant Records as Recordings API

    User->>FP: invoke example / run flow
    FP->>PromptAPI: getFormatted(projectId, template, env, vars)
    PromptAPI-->>FP: FormattedPrompt (messages, tools, promptInfo)

    FP->>FP: prepareMessages(messages, roleSupport) -> toLLMSyntax / build params
    FP->>OpenAI: responses.create(messages, model, params)
    OpenAI-->>FP: completion/result

    FP->>FP: FormattedPrompt.allMessages(completion.output)
    FP->>Records: recordings.create(metadata, callInfo)
    Records-->>FP: recorded
    FP-->>User: log success / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • gemini api chat flavor #14 — Adds a new LLM flavor/adapter and updates flavor wiring and prompt formatting; closely related changes to adapter factory and prompt handling.

Poem

🐰 I hopped through code with twitching nose,

Responses found a brand-new lane,
Developer roles now skip and pose,
Prompts and tests snug on the train,
0.6.0 — a carrot for the brain 🥕

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary changes: adding OpenAI Responses API adapter and developer role support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch aditya/openai-responses-api

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/openaiResponses.test.ts (1)

145-160: Consider verifying the warning message content.

The test mocks console.warn but doesn't verify the warning message. Consider adding an assertion to ensure users see the expected coercion warning.

💡 Suggested improvement
     const warnSpy = jest.spyOn(console, "warn").mockImplementation();
     const result = prepareMessages(messages, adapter.roleSupport);
-    warnSpy.mockRestore();
+    expect(warnSpy).toHaveBeenCalledWith(
+      "Role 'developer' is not natively supported by this flavor. Coercing to 'system'."
+    );
+    warnSpy.mockRestore();

     expect(result).toEqual([
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/openaiResponses.test.ts` around lines 145 - 160, The test "developer
coerced to system for openai_chat" currently mocks console.warn but doesn't
assert the warning content; update the test that constructs OpenAILLMAdapter and
calls prepareMessages to assert the mocked warnSpy was called with the expected
coercion message (e.g., using expect(warnSpy).toHaveBeenCalledWith(...) or a
regex) after prepareMessages runs, keeping warnSpy.mockRestore() afterward;
reference the OpenAILLMAdapter instantiation and the call to
prepareMessages(messages, adapter.roleSupport) so you assert the precise warning
emitted when a "developer" role is coerced to "system".
examples/ts-cjs/openaiResponsesApi.ts (1)

39-48: Consider making the schema name configurable.

The schema name "COTReasoning" is hardcoded. If the prompt template defines an output schema, the name might need to come from the schema itself or be configurable.

💡 Suggested improvement
   if (formattedPrompt.outputSchema) {
+    const schemaName = formattedPrompt.outputSchema.json_schema?.name || "ResponseSchema";
     responseParams.text = {
       format: {
         type: "json_schema",
         strict: true,
         schema: formattedPrompt.outputSchema,
-        name: "COTReasoning",
+        name: schemaName,
       },
     };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ts-cjs/openaiResponsesApi.ts` around lines 39 - 48, The hardcoded
schema name "COTReasoning" in the responseParams.text block should be made
configurable: change the code that sets responseParams.text.format.name to
derive the name from the provided formattedPrompt.outputSchema (e.g., use
formattedPrompt.outputSchema.name if present) or from a new configuration
parameter passed into the function that builds responseParams; ensure you fall
back to a sensible default (e.g., "COTReasoning") when neither is provided so
existing behavior remains stable. Locate the assignment around
responseParams.text and modify it to prefer formattedPrompt.outputSchema.name or
a supplied config value before using the hardcoded string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@examples/ts-cjs/openaiResponsesApi.ts`:
- Around line 39-48: The hardcoded schema name "COTReasoning" in the
responseParams.text block should be made configurable: change the code that sets
responseParams.text.format.name to derive the name from the provided
formattedPrompt.outputSchema (e.g., use formattedPrompt.outputSchema.name if
present) or from a new configuration parameter passed into the function that
builds responseParams; ensure you fall back to a sensible default (e.g.,
"COTReasoning") when neither is provided so existing behavior remains stable.
Locate the assignment around responseParams.text and modify it to prefer
formattedPrompt.outputSchema.name or a supplied config value before using the
hardcoded string.

In `@test/openaiResponses.test.ts`:
- Around line 145-160: The test "developer coerced to system for openai_chat"
currently mocks console.warn but doesn't assert the warning content; update the
test that constructs OpenAILLMAdapter and calls prepareMessages to assert the
mocked warnSpy was called with the expected coercion message (e.g., using
expect(warnSpy).toHaveBeenCalledWith(...) or a regex) after prepareMessages
runs, keeping warnSpy.mockRestore() afterward; reference the OpenAILLMAdapter
instantiation and the call to prepareMessages(messages, adapter.roleSupport) so
you assert the precise warning emitted when a "developer" role is coerced to
"system".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63bf4975-0e7b-40ea-afcb-05b4c60fdccd

📥 Commits

Reviewing files that changed from the base of the PR and between d08b950 and 182448a.

⛔ Files ignored due to path filters (1)
  • examples/ts-cjs/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • CHANGELOG.md
  • examples/ts-cjs/openaiResponsesApi.ts
  • examples/ts-cjs/package.json
  • package.json
  • src/model.ts
  • src/resources/prompts.ts
  • test/openaiResponses.test.ts
  • test/sdk.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/ts-cjs/openaiResponsesApi.ts`:
- Around line 17-22: The example calls fpClient.prompts.getFormatted(...) but
doesn't force the Responses formatter, so if the stored template flavor is e.g.
openai_chat the returned llmPrompt and toolSchema will be shaped for the wrong
API and openaiClient.responses.create(...) will fail; update the getFormatted
call (fpClient.prompts.getFormatted) to explicitly request the Responses
formatter/flavor (pass the option that forces "responses" or equivalent
formatter) so the returned formattedPrompt, llmPrompt and toolSchema are
compatible with openaiClient.responses.create.

In `@src/resources/prompts.ts`:
- Around line 472-477: effectivePromptInfo currently only overrides flavorName
so promptInfo.provider can remain out of sync with the selected flavor; update
effectivePromptInfo to also set provider to the provider for finalFlavor (e.g.,
derive it from LLMAdapters.adapterForFlavor(finalFlavor) or a flavor->provider
mapping) so that subsequent calls like getCallInfo() and recording use the
correct provider; ensure this change is made before prepareMessages/toLLMSyntax
and any recording logic that reads promptInfo.provider.
- Around line 542-544: The allMessages(newMessage: unknown) method currently
appends raw OpenAI Responses API items which may include non-message types and
items without a role; update allMessages to only return normalized
ProviderMessage entries by filtering the incoming newMessage(s) to include only
items that represent actual messages (e.g., item.type === 'message' or items
that have a defined role) and mapping those items to the ProviderMessage shape
(ensure role, content/text, and optional name are set) before concatenating with
this.messages so downstream functions like bind() and prepareMessages() can
safely access .role without runtime errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9fa9437-85dd-4643-a113-ac8d14444c49

📥 Commits

Reviewing files that changed from the base of the PR and between 182448a and 76315da.

📒 Files selected for processing (5)
  • examples/ts-cjs/openaiResponsesApi.ts
  • src/model.ts
  • src/resources/prompts.ts
  • test/openaiResponses.test.ts
  • test/sdk.test.ts
✅ Files skipped from review due to trivial changes (1)
  • test/openaiResponses.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/sdk.test.ts

Comment thread examples/ts-cjs/openaiResponsesApi.ts
Comment thread src/resources/prompts.ts
Comment thread src/resources/prompts.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/resources/prompts.ts (1)

546-548: ⚠️ Potential issue | 🟠 Major

Only append normalized message items here.

The Responses API output is an Items array, not a ProviderMessage[]; it can contain non-message items such as function_call, function_call_output, and reasoning-related items, and the response docs explicitly warn callers not to assume output entries are assistant messages. Appending that array verbatim here means the next prepareMessages() pass can hit message.role === undefined and either mis-handle history or throw. Filter to actual message items and normalize them into ProviderMessage before concatenating. (platform.openai.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/resources/prompts.ts` around lines 546 - 548, The allMessages method
currently appends newMessage items verbatim which can include non-message
entries from the Responses API; update allMessages (and the normalization it
uses) to first filter newMessage items to only real message items (e.g., entries
with a role or type indicating assistant/user/system) and map/normalize each
into a ProviderMessage shape before concatenating to this.messages, so that
prepareMessages() will never receive items missing message.role; reference the
allMessages function and the ProviderMessage type for where to apply the
filter+normalize.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/resources/prompts.ts`:
- Line 262: The FormattedPrompt currently stores the original template messages
(this.messages) instead of the processed ProviderMessage array returned by
prepareMessages(), causing allMessages() and systemContent to reflect messages
never actually sent; update FormattedPrompt to store the prepared messages (or
widen its internal messages field to ProviderMessage[]), so after calling
prepareMessages() you replace this.messages with the returned ProviderMessage[];
ensure functions like allMessages(), systemContent and any history concatenation
use the stored ProviderMessage[] so role rewrites (e.g., developer -> system)
persist through turns.

---

Duplicate comments:
In `@src/resources/prompts.ts`:
- Around line 546-548: The allMessages method currently appends newMessage items
verbatim which can include non-message entries from the Responses API; update
allMessages (and the normalization it uses) to first filter newMessage items to
only real message items (e.g., entries with a role or type indicating
assistant/user/system) and map/normalize each into a ProviderMessage shape
before concatenating to this.messages, so that prepareMessages() will never
receive items missing message.role; reference the allMessages function and the
ProviderMessage type for where to apply the filter+normalize.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: be982a9b-3f29-433f-b490-540f989c0efd

📥 Commits

Reviewing files that changed from the base of the PR and between 76315da and 8085cda.

📒 Files selected for processing (2)
  • src/resources/prompts.ts
  • test/openaiResponses.test.ts
✅ Files skipped from review due to trivial changes (1)
  • test/openaiResponses.test.ts

Comment thread src/resources/prompts.ts
Comment thread src/model.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/resources/prompts.ts (1)

550-552: ⚠️ Potential issue | 🟠 Major

allMessages() still can’t safely round-trip Responses output.

response.output can include function_call, function_call_output, and reasoning items, and reasoning/tool-loop turns are meant to feed those items back on later requests. This method returns them as ProviderMessage[], but the next bind()/prepareMessages() path assumes every entry has .role, so stateless Responses history will either fail or force callers to drop required items. Use a separate Responses-item history type here, or reject non-message items instead of typing them as ProviderMessage[]. (platform.openai.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/resources/prompts.ts` around lines 550 - 552, allMessages(newMessage:
unknown) incorrectly treats arbitrary response.output items as
ProviderMessage[]; that breaks later bind()/prepareMessages() which expect a
.role on every entry. Fix by changing allMessages to either (a) filter/validate
and only return proper ProviderMessage objects (reject or throw if an item lacks
.role), or (b) introduce a separate ResponsesHistory type for function_call /
function_call_output /reasoning items and return a union (e.g., ProviderMessage
| ResponseItem) so callers can handle non-message entries explicitly; update
callers of allMessages, bind(), and prepareMessages() to handle the new type or
to only accept entries with .role. Ensure references to response.output,
function_call, function_call_output, and reasoning are preserved in the new
ResponsesHistory type if you choose the union approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/model.ts`:
- Around line 159-162: OPENAI_RESPONSES_ROLE_SUPPORT incorrectly includes
"system" and "tool" and OpenAIResponsesAdapter.toLLMSyntax() currently strips
system messages and misserializes tool outputs; update
OPENAI_RESPONSES_ROLE_SUPPORT to remove "system" and "tool" from the supported
set, modify OpenAIResponsesAdapter.toLLMSyntax() to preserve system messages in
the output (do not filter them out), and either implement proper serialization
of tool messages as Responses API function_call_output items or make the adapter
reject/translate the "tool" role until that serialization is added; apply the
same change to the other role-support constant referenced similarly elsewhere in
the file.

---

Duplicate comments:
In `@src/resources/prompts.ts`:
- Around line 550-552: allMessages(newMessage: unknown) incorrectly treats
arbitrary response.output items as ProviderMessage[]; that breaks later
bind()/prepareMessages() which expect a .role on every entry. Fix by changing
allMessages to either (a) filter/validate and only return proper ProviderMessage
objects (reject or throw if an item lacks .role), or (b) introduce a separate
ResponsesHistory type for function_call / function_call_output /reasoning items
and return a union (e.g., ProviderMessage | ResponseItem) so callers can handle
non-message entries explicitly; update callers of allMessages, bind(), and
prepareMessages() to handle the new type or to only accept entries with .role.
Ensure references to response.output, function_call, function_call_output, and
reasoning are preserved in the new ResponsesHistory type if you choose the union
approach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84e29d8b-39d4-4ec4-872e-02f2ea8ea2e8

📥 Commits

Reviewing files that changed from the base of the PR and between aa6f90c and 945466e.

📒 Files selected for processing (3)
  • src/model.ts
  • src/resources/prompts.ts
  • test/openaiResponses.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/openaiResponses.test.ts

Comment thread src/model.ts
@callingmedic911
callingmedic911 merged commit 737e1d5 into main Mar 10, 2026
5 checks passed
@callingmedic911
callingmedic911 deleted the aditya/openai-responses-api branch March 10, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants