fix(api-ai): support responses style openai payloads

This commit is contained in:
2026-04-07 23:20:50 +08:00
parent 1f8b539b68
commit 929b838e0f
2 changed files with 161 additions and 14 deletions
@@ -122,6 +122,20 @@ export class OpenAiCompatibleProvider implements AiChannelExecutor {
}
private extractAssistantText(payload: unknown): string {
const chatCompletionText = this.extractChatCompletionText(payload);
if (chatCompletionText) {
return chatCompletionText;
}
const responsesText = this.extractResponsesApiText(payload);
if (responsesText) {
return responsesText;
}
return "";
}
private extractChatCompletionText(payload: unknown): string {
if (!this.isRecord(payload)) {
return "";
}
@@ -137,11 +151,49 @@ export class OpenAiCompatibleProvider implements AiChannelExecutor {
}
const message = firstChoice["message"];
if (!this.isRecord(message)) {
if (this.isRecord(message)) {
const messageContent = this.extractMessageContent(message["content"]);
if (messageContent) {
return messageContent;
}
}
if (typeof firstChoice["text"] === "string") {
return firstChoice["text"];
}
return "";
}
return this.extractMessageContent(message["content"]);
private extractResponsesApiText(payload: unknown): string {
if (!this.isRecord(payload)) {
return "";
}
if (typeof payload["output_text"] === "string") {
return payload["output_text"];
}
const output = payload["output"];
if (!Array.isArray(output)) {
return "";
}
return output
.map((item) => {
if (!this.isRecord(item)) {
return "";
}
if (typeof item["text"] === "string") {
return item["text"];
}
return this.extractMessageContent(item["content"]);
})
.filter((item) => item.length > 0)
.join("\n")
.trim();
}
private extractMessageContent(content: unknown): string {
@@ -154,7 +206,13 @@ export class OpenAiCompatibleProvider implements AiChannelExecutor {
}
return content
.map((item) => {
.map((item) => this.extractContentPartText(item))
.filter((item) => item.length > 0)
.join("\n")
.trim();
}
private extractContentPartText(item: unknown): string {
if (!this.isRecord(item)) {
return "";
}
@@ -163,10 +221,19 @@ export class OpenAiCompatibleProvider implements AiChannelExecutor {
return item["text"];
}
if (this.isRecord(item["text"]) && typeof item["text"]["value"] === "string") {
return item["text"]["value"];
}
if (typeof item["content"] === "string") {
return item["content"];
}
if (this.isRecord(item["content"]) && typeof item["content"]["text"] === "string") {
return item["content"]["text"];
}
return "";
})
.filter((item) => item.length > 0)
.join("\n");
}
private extractModel(payload: unknown): string | null {
@@ -0,0 +1,80 @@
import { AiChannel } from "../generated/prisma/client";
import { OpenAiCompatibleProvider } from "../src/ai/providers/openai-compatible.provider";
describe("OpenAiCompatibleProvider", () => {
const originalFetch = global.fetch;
afterEach(() => {
global.fetch = originalFetch;
jest.restoreAllMocks();
});
it("should read text from responses style payload when chat content is empty", async () => {
const provider = new OpenAiCompatibleProvider();
const fetchMock = jest.fn(async (_input: unknown, init?: RequestInit) => {
expect(init?.method).toBe("POST");
return new Response(
JSON.stringify({
id: "resp_123",
object: "response",
model: "gpt-5.4",
output: [
{
id: "msg_123",
type: "message",
role: "assistant",
content: [
{
type: "output_text",
text: "今天优先先完成截止时间最近的任务。"
}
]
}
],
usage: {
prompt_tokens: 15,
completion_tokens: 9,
total_tokens: 24
}
}),
{
status: 200,
headers: {
"content-type": "application/json"
}
}
);
});
global.fetch = fetchMock as typeof global.fetch;
const result = await provider.execute(
{
channel: AiChannel.USER_KEY,
source: "binding",
sourceId: "binding_user_key_1",
providerName: "airouter",
model: "gpt-5.4",
configId: null,
configName: null,
endpoint: "https://api.airouter.io/v1",
apiKey: "sk_test"
},
{
userId: "user_1",
message: "帮我安排今天的任务",
sessionId: null
}
);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(result.content).toBe("今天优先先完成截止时间最近的任务。");
expect(result.model).toBe("gpt-5.4");
expect(result.usage).toEqual({
promptTokens: 15,
completionTokens: 9,
totalTokens: 24
});
});
});