Codex Responses Lite clients routed to a chat-native OpenAI-compatible provider lost tool use in three places: non-streaming Chat responses leaked the raw chat.completion envelope instead of Responses output items, internal reasoning continuity fields leaked into the outbound Chat body causing some upstreams to reject the request, and the Responses to Chat request translator ignored additional_tools, custom_tool_call, and custom_tool_call_output items entirely. Also fixes apiType (chat vs responses) for openai-compatible nodes being resolved from the immutable provider ID instead of the stored node config, so editing a node's API Type had no runtime effect.
77 lines
3.6 KiB
JavaScript
77 lines
3.6 KiB
JavaScript
// Locks openai-compatible apiType resolution: the stored apiType on the
|
|
// connection's providerSpecificData (kept in sync with the node) is
|
|
// authoritative, and the node-ID substring is only a legacy fallback.
|
|
//
|
|
// Regression for: editing a node's apiType to "responses" had no effect because
|
|
// runtime derived chat/responses from the immutable node ID string
|
|
// (`openai-compatible-<chat|responses>-<uuid>`) instead of the stored value.
|
|
import { describe, it, expect } from "vitest";
|
|
import { resolveOpenAICompatibleApiType, getTargetFormat } from "open-sse/services/provider.js";
|
|
import { DefaultExecutor } from "open-sse/executors/default.js";
|
|
import { BaseExecutor } from "open-sse/executors/base.js";
|
|
|
|
const CHAT_ID = "openai-compatible-chat-3d8d3de8-1206-47ee-a42f-22113a5f2387";
|
|
const RESPONSES_ID = "openai-compatible-responses-11111111-2222-3333-4444-555555555555";
|
|
const BASE = "https://api.ericding.io.vn/v1";
|
|
|
|
function creds(apiType) {
|
|
return { providerSpecificData: apiType === undefined ? { baseUrl: BASE } : { baseUrl: BASE, apiType } };
|
|
}
|
|
|
|
describe("resolveOpenAICompatibleApiType", () => {
|
|
it("prefers stored apiType over the ID substring (edited node on a legacy -chat- ID)", () => {
|
|
expect(resolveOpenAICompatibleApiType(CHAT_ID, creds("responses"))).toBe("responses");
|
|
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, creds("chat"))).toBe("chat");
|
|
});
|
|
|
|
it("falls back to the ID substring when apiType is absent", () => {
|
|
expect(resolveOpenAICompatibleApiType(CHAT_ID, creds(undefined))).toBe("chat");
|
|
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, creds(undefined))).toBe("responses");
|
|
expect(resolveOpenAICompatibleApiType(CHAT_ID, null)).toBe("chat");
|
|
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, null)).toBe("responses");
|
|
});
|
|
|
|
it("ignores an invalid stored apiType and falls back to the ID", () => {
|
|
expect(resolveOpenAICompatibleApiType(RESPONSES_ID, creds("bogus"))).toBe("responses");
|
|
expect(resolveOpenAICompatibleApiType(CHAT_ID, creds(""))).toBe("chat");
|
|
});
|
|
});
|
|
|
|
describe("getTargetFormat", () => {
|
|
it("selects openai-responses when the stored apiType is responses (even on a -chat- ID)", () => {
|
|
expect(getTargetFormat(CHAT_ID, creds("responses"))).toBe("openai-responses");
|
|
});
|
|
|
|
it("selects openai for chat, and honors stored chat over a -responses- ID", () => {
|
|
expect(getTargetFormat(CHAT_ID, creds(undefined))).toBe("openai");
|
|
expect(getTargetFormat(RESPONSES_ID, creds("chat"))).toBe("openai");
|
|
});
|
|
|
|
it("keeps the ID-based fallback when credentials are absent", () => {
|
|
expect(getTargetFormat(RESPONSES_ID)).toBe("openai-responses");
|
|
expect(getTargetFormat(CHAT_ID)).toBe("openai");
|
|
});
|
|
});
|
|
|
|
describe("executor buildUrl endpoint path", () => {
|
|
for (const [name, Ex] of [["DefaultExecutor", DefaultExecutor], ["BaseExecutor", BaseExecutor]]) {
|
|
describe(name, () => {
|
|
const ex = new Ex(CHAT_ID);
|
|
|
|
it("routes to /responses when stored apiType is responses, despite the -chat- ID", () => {
|
|
expect(ex.buildUrl("cx/gpt-5.6-sol", true, 0, creds("responses"))).toBe(`${BASE}/responses`);
|
|
});
|
|
|
|
it("routes to /chat/completions when apiType is chat", () => {
|
|
expect(ex.buildUrl("cx/gpt-5.6-sol", true, 0, creds("chat"))).toBe(`${BASE}/chat/completions`);
|
|
});
|
|
|
|
it("falls back to the ID substring (legacy) when apiType is absent", () => {
|
|
expect(ex.buildUrl("cx/gpt-5.6-sol", true, 0, creds(undefined))).toBe(`${BASE}/chat/completions`);
|
|
const exResp = new Ex(RESPONSES_ID);
|
|
expect(exResp.buildUrl("m", true, 0, creds(undefined))).toBe(`${BASE}/responses`);
|
|
});
|
|
});
|
|
}
|
|
});
|