Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions open-sse/translator/helpers/openaiHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export function filterToOpenAIFormat(body) {
if (!body.messages || !Array.isArray(body.messages)) return body;

body.messages = body.messages.map((msg) => {
// Normalize OpenAI Responses-style `developer` role to `system` — many
// OpenAI-compatible providers reject `developer` (ported from
// decolua/9router#1011).
if (msg.role === "developer") msg = { ...msg, role: "system" };
Comment on lines +38 to +41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes if msg is null, undefined, or not an object (e.g., if a client sends a malformed payload), we should add a defensive check at the beginning of the map function before accessing any properties like msg.role.

Suggested change
// Normalize OpenAI Responses-style `developer` role to `system` — many
// OpenAI-compatible providers reject `developer` (ported from
// decolua/9router#1011).
if (msg.role === "developer") msg = { ...msg, role: "system" };
if (!msg || typeof msg !== "object") return msg;
// Normalize OpenAI Responses-style developer role to system — many
// OpenAI-compatible providers reject developer (ported from
// decolua/9router#1011).
if (msg.role === "developer") msg = { ...msg, role: "system" };
References
  1. Enforce defensive programming by ensuring appropriate null/undefined checks exist before object property accesses.


// Keep tool messages as-is (OpenAI format)
if (msg.role === "tool") return msg;

Expand Down
38 changes: 38 additions & 0 deletions tests/unit/openai-developer-role-normalize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { test } from "node:test";
import assert from "node:assert/strict";

import { filterToOpenAIFormat } from "../../open-sse/translator/helpers/openaiHelper.ts";

// Ported from upstream decolua/9router#1011 — many OpenAI-compatible providers
// reject the `developer` role (introduced by OpenAI Responses API). Normalize to
// `system` so passthrough requests keep working downstream.
test("filterToOpenAIFormat normalizes developer role to system", () => {
const body = {
messages: [
{ role: "developer", content: "You are helpful" },
{ role: "user", content: "Hi" },
],
};

const out = filterToOpenAIFormat(body);

assert.equal(out.messages[0].role, "system");
assert.equal(out.messages[0].content, "You are helpful");
assert.equal(out.messages[1].role, "user");
});

test("filterToOpenAIFormat normalizes developer role with array content", () => {
const body = {
messages: [
{
role: "developer",
content: [{ type: "text", text: "Be concise" }],
},
],
};

const out = filterToOpenAIFormat(body);

assert.equal(out.messages[0].role, "system");
assert.deepEqual(out.messages[0].content, [{ type: "text", text: "Be concise" }]);
});
Comment on lines +1 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The repository uses Vitest as its primary test runner (as indicated by vitest.config.ts in the root and the repository style guide). For consistency, better integration with the test suite, and accurate coverage reporting, we should use Vitest instead of the Node.js built-in node:test runner.

import { test, expect } from "vitest";

import { filterToOpenAIFormat } from "../../open-sse/translator/helpers/openaiHelper.ts";

// Ported from upstream decolua/9router#1011 — many OpenAI-compatible providers
// reject the developer role (introduced by OpenAI Responses API). Normalize to
// system so passthrough requests keep working downstream.
test("filterToOpenAIFormat normalizes developer role to system", () => {
  const body = {
    messages: [
      { role: "developer", content: "You are helpful" },
      { role: "user", content: "Hi" },
    ],
  };

  const out = filterToOpenAIFormat(body);

  expect(out.messages[0].role).toBe("system");
  expect(out.messages[0].content).toBe("You are helpful");
  expect(out.messages[1].role).toBe("user");
});

test("filterToOpenAIFormat normalizes developer role with array content", () => {
  const body = {
    messages: [
      {
        role: "developer",
        content: [{ type: "text", text: "Be concise" }],
      },
    ],
  };

  const out = filterToOpenAIFormat(body);

  expect(out.messages[0].role).toBe("system");
  expect(out.messages[0].content).toEqual([{ type: "text", text: "Be concise" }]);
});
References
  1. The repository style guide specifies the use of Vitest files for testing. (link)

Loading