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:
@@ -6,6 +6,7 @@
|
||||
import { handleChatCore } from "./chatCore.js";
|
||||
import { convertResponsesApiFormat } from "../translator/helpers/responsesApiHelper.js";
|
||||
import { createResponsesApiTransformStream } from "../transformer/responsesTransformer.js";
|
||||
import { convertResponsesStreamToJson } from "../transformer/streamToJsonConverter.js";
|
||||
|
||||
/**
|
||||
* Handle /v1/responses request
|
||||
@@ -24,8 +25,12 @@ export async function handleResponsesCore({ body, modelInfo, credentials, log, o
|
||||
// Convert Responses API format to Chat Completions format
|
||||
const convertedBody = convertResponsesApiFormat(body);
|
||||
|
||||
// Ensure stream is enabled
|
||||
convertedBody.stream = true;
|
||||
// Preserve client's stream preference (matches OpenClaw behavior)
|
||||
// Default to false if omitted: Boolean(undefined) = false
|
||||
const clientRequestedStreaming = convertedBody.stream === true;
|
||||
if (convertedBody.stream === undefined) {
|
||||
convertedBody.stream = false;
|
||||
}
|
||||
|
||||
// Call chat core handler
|
||||
const result = await handleChatCore({
|
||||
@@ -46,26 +51,52 @@ export async function handleResponsesCore({ body, modelInfo, credentials, log, o
|
||||
const response = result.response;
|
||||
const contentType = response.headers.get("Content-Type") || "";
|
||||
|
||||
// If not SSE or error, return as-is
|
||||
if (!contentType.includes("text/event-stream") || response.status !== 200) {
|
||||
return result;
|
||||
// Case 1: Client wants non-streaming, but got SSE (provider forced it, e.g., Codex)
|
||||
if (!clientRequestedStreaming && contentType.includes("text/event-stream")) {
|
||||
try {
|
||||
const jsonResponse = await convertResponsesStreamToJson(response.body);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
response: new Response(JSON.stringify(jsonResponse), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
})
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[Responses API] Stream-to-JSON conversion failed:", error);
|
||||
return {
|
||||
success: false,
|
||||
status: 500,
|
||||
error: "Failed to convert streaming response to JSON"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Transform SSE stream to Responses API format (no logging in worker)
|
||||
const transformStream = createResponsesApiTransformStream(null);
|
||||
const transformedBody = response.body.pipeThrough(transformStream);
|
||||
// Case 2: Client wants streaming, got SSE - transform it
|
||||
if (clientRequestedStreaming && contentType.includes("text/event-stream")) {
|
||||
const transformStream = createResponsesApiTransformStream(null);
|
||||
const transformedBody = response.body.pipeThrough(transformStream);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
response: new Response(transformedBody, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
})
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
response: new Response(transformedBody, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Case 3: Non-SSE response (error or non-streaming from provider) - return as-is
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user