mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 11:52:26 +03:00
fix(sse): accept assistant refusal content parts in chatgpt-session
A `{ type: "refusal", refusal: "…" }` part is legal inside an assistant
message in the chat-completions spec, so a client replaying its own
conversation history sends it back. The unsupported-part guard rejected it
with a 400 and failed the whole request.
Fold a refusal part into the flattened content the way a text part is folded,
reading its `refusal` field. A refusal part whose payload is missing or is not
a string still falls through to the existing rejection — it is never dropped
in silence — and every other unrecognised part keeps throwing as before.
This commit is contained in:
@@ -42,6 +42,14 @@ function textFromContent(content: unknown): string {
|
||||
chunks.push(typed.text);
|
||||
continue;
|
||||
}
|
||||
// `{ type: "refusal", refusal: "…" }` is a legal ASSISTANT content part in the
|
||||
// chat-completions spec, and a client replaying its own conversation history sends it back
|
||||
// verbatim. It is text as far as the adapter is concerned. A `refusal` part whose payload is
|
||||
// missing or not a string falls through to the rejection below rather than being dropped.
|
||||
if (typed && typed.type === "refusal" && typeof typed.refusal === "string") {
|
||||
chunks.push(typed.refusal);
|
||||
continue;
|
||||
}
|
||||
if (typed && (typed.type === "image_url" || typed.type === "image")) {
|
||||
throw new ChatGptSessionInputError(
|
||||
"vision_unsupported",
|
||||
|
||||
@@ -134,3 +134,50 @@ test("rejects any content part that is neither text nor an image", () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("an assistant refusal part is folded into the mapped content as text", () => {
|
||||
const parsed = buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "refusal", refusal: "I can't help with that." },
|
||||
{ type: "text", text: "Here is something else." },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "three" },
|
||||
],
|
||||
stream: true,
|
||||
});
|
||||
assert.equal(parsed.context.messages.length, 3);
|
||||
assert.deepEqual(parsed.context.messages[1], {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "I can't help with that.\nHere is something else." }],
|
||||
timestamp: parsed.context.messages[1].timestamp,
|
||||
});
|
||||
});
|
||||
|
||||
test("a refusal part whose refusal field is not a string is still rejected", () => {
|
||||
for (const part of [
|
||||
{ type: "refusal" },
|
||||
{ type: "refusal", refusal: null },
|
||||
{ type: "refusal", refusal: { text: "nope" } },
|
||||
{ type: "refusal", text: "wrong field" },
|
||||
]) {
|
||||
assert.throws(
|
||||
() =>
|
||||
buildParsedRequest({
|
||||
route,
|
||||
messages: [
|
||||
{ role: "user", content: "one" },
|
||||
{ role: "assistant", content: [part] },
|
||||
],
|
||||
stream: false,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof ChatGptSessionInputError && error.code === "unsupported_content_part"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user