diegosouzapw--omniroute
136 行
4.9 KiB
TypeScript
136 行
4.9 KiB
TypeScript
/**
|
|
* chatCore non-streaming response parsing/classification (Quality Gate v2 / Fase 9 — chatCore
|
|
* god-file decomposition, #3501 — response-handling slice of executeProviderRequest).
|
|
*
|
|
* Extracted from handleChatCore's `if (!stream)` block: reads the upstream non-streaming body,
|
|
* detects whether it is an event stream (SSE / NDJSON) buffered for a non-streaming client, and
|
|
* parses it into a JSON response body. Returns a discriminated union describing the outcome
|
|
* (`ok` | `invalid_sse` | `invalid_json`) and leaves every persistence side-effect
|
|
* (appendRequestLog / persistAttemptLogs / persistFailureUsage / trackPendingRequest /
|
|
* createErrorResult) to the handler, so behaviour is observably identical to the previous inline
|
|
* block. Pure with respect to handler state (only buffering debug/warn logs as a side effect).
|
|
*/
|
|
|
|
import { normalizePayloadForLog } from "@/lib/logPayloads";
|
|
import { extractSSEErrorMessage } from "../sseParser.ts";
|
|
import { readNonStreamingResponseBody } from "./nonStreamingResponseBody.ts";
|
|
import {
|
|
normalizeNonStreamingEventPayload,
|
|
parseNonStreamingSSEPayload,
|
|
shouldTreatBufferedEventResponseAsExpected,
|
|
} from "./nonStreamingSse.ts";
|
|
|
|
type LoggerLike =
|
|
| {
|
|
debug?: (...args: unknown[]) => void;
|
|
warn?: (...args: unknown[]) => void;
|
|
}
|
|
| null
|
|
| undefined;
|
|
|
|
export type NonStreamingParseResult =
|
|
| {
|
|
kind: "ok";
|
|
responseBody: unknown;
|
|
responsePayloadFormat: string;
|
|
looksLikeSSE: boolean;
|
|
normalizedProviderPayload: unknown;
|
|
}
|
|
| {
|
|
kind: "invalid_sse";
|
|
message: string;
|
|
looksLikeSSE: true;
|
|
normalizedProviderPayload: unknown;
|
|
}
|
|
| {
|
|
kind: "invalid_json";
|
|
message: string;
|
|
detailedError: string;
|
|
looksLikeSSE: false;
|
|
normalizedProviderPayload: unknown;
|
|
};
|
|
|
|
export async function parseNonStreamingResponseBody(opts: {
|
|
providerResponse: Response;
|
|
upstreamStream: boolean;
|
|
providerHeaders: Record<string, unknown> | Headers | null | undefined;
|
|
finalBody: unknown;
|
|
targetFormat: string;
|
|
model: string;
|
|
log?: LoggerLike;
|
|
}): Promise<NonStreamingParseResult> {
|
|
const { providerResponse, upstreamStream, providerHeaders, finalBody, targetFormat, model, log } =
|
|
opts;
|
|
|
|
const contentType = (providerResponse.headers.get("content-type") || "").toLowerCase();
|
|
const rawBody = await readNonStreamingResponseBody(providerResponse, contentType, upstreamStream);
|
|
const normalizedProviderPayload = normalizePayloadForLog(rawBody);
|
|
const looksLikeSSE =
|
|
contentType.includes("text/event-stream") ||
|
|
contentType.includes("application/x-ndjson") ||
|
|
/(^|\n)\s*(event|data):/m.test(rawBody);
|
|
|
|
if (looksLikeSSE) {
|
|
const streamPayload = normalizeNonStreamingEventPayload(rawBody, contentType);
|
|
const streamKind = contentType.includes("application/x-ndjson") ? "NDJSON" : "SSE";
|
|
if (shouldTreatBufferedEventResponseAsExpected(upstreamStream, providerHeaders, finalBody)) {
|
|
log?.debug?.(
|
|
"STREAM",
|
|
`Buffering upstream ${streamKind} response for non-streaming client request`
|
|
);
|
|
} else {
|
|
log?.warn?.(
|
|
"STREAM",
|
|
`Unexpected ${streamKind} response for non-streaming request — buffering`
|
|
);
|
|
}
|
|
// Upstream returned an event stream for a non-streaming client; convert best-effort to JSON.
|
|
const parsedFromSSE = parseNonStreamingSSEPayload(streamPayload, targetFormat, model);
|
|
|
|
if (!parsedFromSSE) {
|
|
// Some executors (e.g. the Devin/Windsurf CLI) always emit text/event-stream, signalling
|
|
// failure with an error-only chunk (`data: {"error":{"message":"Devin CLI not found..."}}`)
|
|
// that carries no `choices`. Surface that real, sanitized message instead of the generic 502
|
|
// so the actionable error is not swallowed (#3324).
|
|
const surfacedSseError = extractSSEErrorMessage(streamPayload);
|
|
const invalidSseMessage =
|
|
surfacedSseError || "Invalid SSE response for non-streaming request";
|
|
return {
|
|
kind: "invalid_sse",
|
|
message: invalidSseMessage,
|
|
looksLikeSSE: true,
|
|
normalizedProviderPayload,
|
|
};
|
|
}
|
|
|
|
return {
|
|
kind: "ok",
|
|
responseBody: parsedFromSSE.body,
|
|
responsePayloadFormat: parsedFromSSE.format,
|
|
looksLikeSSE: true,
|
|
normalizedProviderPayload,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const responseBody = rawBody ? JSON.parse(rawBody) : {};
|
|
return {
|
|
kind: "ok",
|
|
responseBody,
|
|
responsePayloadFormat: targetFormat,
|
|
looksLikeSSE: false,
|
|
normalizedProviderPayload,
|
|
};
|
|
} catch (err) {
|
|
const detailedError = `Invalid JSON response from provider (error: ${err instanceof Error ? err.message : String(err)}): ${rawBody.substring(0, 1000)}`;
|
|
const invalidJsonMessage = "Invalid JSON response from provider";
|
|
return {
|
|
kind: "invalid_json",
|
|
message: invalidJsonMessage,
|
|
detailedError,
|
|
looksLikeSSE: false,
|
|
normalizedProviderPayload,
|
|
};
|
|
}
|
|
}
|