feat(responses): respect client streaming preference + string input support (#121)

- Remove forced stream=true from responsesHandler
- Add stream-to-JSON converter for non-streaming clients (Codex)
- Accept string input in Responses API (normalize to array)
- Codex SSE header fallback for missing Content-Type
- Refactor: extract shared normalizeResponsesInput()

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
apeltekci
2026-02-15 11:47:55 +07:00
committed by decolua
co-authored by Claude Sonnet 4.5 Cursor
parent 202fee714b
commit ac7cedd27e
7 changed files with 263 additions and 26 deletions
@@ -1,3 +1,18 @@
/**
* Normalize Responses API input to array format.
* Accepts string or array, returns array of message items.
* @param {string|Array} input - raw input from Responses API body
* @returns {Array|null} normalized array or null if invalid
*/
export function normalizeResponsesInput(input) {
if (typeof input === "string") {
const text = input.trim() === "" ? "..." : input;
return [{ type: "message", role: "user", content: [{ type: "input_text", text }] }];
}
if (Array.isArray(input)) return input;
return null;
}
/**
* Convert OpenAI Responses API format to standard chat completions format
* Responses API uses: { input: [...], instructions: "..." }
@@ -19,7 +34,10 @@ export function convertResponsesApiFormat(body) {
let pendingToolCalls = [];
let pendingToolResults = [];
for (const item of body.input) {
const inputItems = normalizeResponsesInput(body.input);
if (!inputItems) return body;
for (const item of inputItems) {
// Determine item type - Droid CLI sends role-based items without 'type' field
// Fallback: if no type but has role property, treat as message
const itemType = item.type || (item.role ? "message" : null);
@@ -6,6 +6,7 @@
*/
import { register } from "../index.js";
import { FORMATS } from "../formats.js";
import { normalizeResponsesInput } from "../helpers/responsesApiHelper.js";
/**
* Convert OpenAI Responses API request to OpenAI Chat Completions format
@@ -25,7 +26,10 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
let currentAssistantMsg = null;
let pendingToolResults = [];
for (const item of body.input) {
const inputItems = normalizeResponsesInput(body.input);
if (!inputItems) return body;
for (const item of inputItems) {
// Determine item type - Droid CLI sends role-based items without 'type' field
// Fallback: if no type but has role property, treat as message
const itemType = item.type || (item.role ? "message" : null);
@@ -234,4 +238,3 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials)
// Register both directions
register(FORMATS.OPENAI_RESPONSES, FORMATS.OPENAI, openaiResponsesToOpenAIRequest, null);
register(FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES, openaiToOpenAIResponsesRequest, null);