feat: AI SDK compatibility - Accept header & JSON markdown stripping

- Respect Accept: application/json header to return non-streaming JSON
  instead of SSE, fixing AI SDK generateObject/generateText compatibility
- Strip markdown code block markers (```json...```) from Claude
  non-streaming responses to prevent JSON parse errors

Cherry-picked and adapted from PR #290 by @rothnic
https://github.com/decolua/9router/pull/290

Made-with: Cursor
This commit is contained in:
Nick Roth
2026-03-13 10:00:47 +07:00
committed by decolua
parent 373b10ebb5
commit d12b14f411
2 changed files with 15 additions and 3 deletions

View File

@@ -39,7 +39,16 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
const clientRequestedStreaming = body.stream === true || sourceFormat === FORMATS.ANTIGRAVITY || sourceFormat === FORMATS.GEMINI || sourceFormat === FORMATS.GEMINI_CLI;
const providerRequiresStreaming = provider === "openai" || provider === "codex";
const stream = providerRequiresStreaming ? true : (body.stream !== false);
let stream = providerRequiresStreaming ? true : (body.stream !== false);
// Check client Accept header preference for non-streaming requests
// This fixes AI SDK compatibility where clients send Accept: application/json
const acceptHeader = clientRawRequest?.headers?.accept || "";
const clientPrefersJson = acceptHeader.includes("application/json");
const clientPrefersSSE = acceptHeader.includes("text/event-stream");
if (clientPrefersJson && !clientPrefersSSE && body.stream !== true) {
stream = false;
}
const reqLogger = await createRequestLogger(sourceFormat, targetFormat, model);
if (clientRawRequest) reqLogger.logClientRawRequest(clientRawRequest.endpoint, clientRawRequest.body, clientRawRequest.headers);

View File

@@ -77,8 +77,11 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
const toolCalls = [];
for (const block of responseBody.content) {
if (block.type === "text") textContent += block.text;
else if (block.type === "thinking") thinkingContent += block.thinking || "";
if (block.type === "text") {
// Strip markdown code block markers (e.g. kimi wraps JSON in ```json...```)
const text = block.text.replace(/^\s*```\s*json\s*\n?/i, "").replace(/\n?\s*```\s*$/i, "");
textContent += text;
} else if (block.type === "thinking") thinkingContent += block.thinking || "";
else if (block.type === "tool_use") {
toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input || {}) } });
}