627 lines
20 KiB
TypeScript
627 lines
20 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { FORMATS } from "../../open-sse/translator/formats.ts";
|
|
import { getModelInfoCore } from "../../open-sse/services/model.ts";
|
|
import { detectFormat, detectFormatFromEndpoint } from "../../open-sse/services/provider.ts";
|
|
import { shouldUseNativeCodexPassthrough } from "../../open-sse/handlers/chatCore.ts";
|
|
import { translateRequest } from "../../open-sse/translator/index.ts";
|
|
import { GithubExecutor } from "../../open-sse/executors/github.ts";
|
|
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
|
|
import { CodexExecutor } from "../../open-sse/executors/codex.ts";
|
|
import { translateNonStreamingResponse } from "../../open-sse/handlers/responseTranslator.ts";
|
|
import { extractUsageFromResponse } from "../../open-sse/handlers/usageExtractor.ts";
|
|
import {
|
|
parseSSEToOpenAIResponse,
|
|
parseSSEToResponsesOutput,
|
|
} from "../../open-sse/handlers/sseParser.ts";
|
|
|
|
test("getModelInfoCore resolves unique non-openai unprefixed model", async () => {
|
|
const info = await getModelInfoCore("claude-sonnet-4-5-20250929", {});
|
|
assert.equal(info.provider, "claude");
|
|
assert.equal(info.model, "claude-sonnet-4-5-20250929");
|
|
});
|
|
|
|
test("getModelInfoCore keeps openai fallback for gpt-4o", async () => {
|
|
const info = await getModelInfoCore("gpt-4o", {});
|
|
assert.equal(info.provider, "openai");
|
|
assert.equal(info.model, "gpt-4o");
|
|
});
|
|
|
|
test("getModelInfoCore routes native codex-auto-review to codex", async () => {
|
|
const info = await getModelInfoCore("codex-auto-review", {});
|
|
assert.equal(info.provider, "codex");
|
|
assert.equal(info.model, "codex-auto-review");
|
|
});
|
|
|
|
test("getModelInfoCore keeps unprefixed gpt-5.5 on the OpenAI fallback", async () => {
|
|
const info = await getModelInfoCore("gpt-5.5", {});
|
|
assert.equal(info.provider, "openai");
|
|
assert.equal(info.model, "gpt-5.5");
|
|
});
|
|
|
|
test("getModelInfoCore keeps explicit cx/gpt-5.5-medium separate from gpt-5.5", async () => {
|
|
const info = await getModelInfoCore("cx/gpt-5.5-medium", {});
|
|
assert.equal(info.provider, "codex");
|
|
assert.equal(info.model, "gpt-5.5-medium");
|
|
});
|
|
|
|
test("getModelInfoCore resolves explicit gpt-5.5 Codex model", async () => {
|
|
const info = await getModelInfoCore("cx/gpt-5.5", {});
|
|
assert.equal(info.provider, "codex");
|
|
assert.equal(info.model, "gpt-5.5");
|
|
});
|
|
|
|
test("getModelInfoCore keeps duplicate unprefixed gpt-5.5 checks on OpenAI", async () => {
|
|
const info = await getModelInfoCore("gpt-5.5", {});
|
|
assert.equal(info.provider, "openai");
|
|
assert.equal(info.model, "gpt-5.5");
|
|
});
|
|
|
|
test("getModelInfoCore keeps repeated unprefixed gpt-5.5 checks on OpenAI", async () => {
|
|
const info = await getModelInfoCore("gpt-5.5", {});
|
|
assert.equal(info.provider, "openai");
|
|
assert.equal(info.model, "gpt-5.5");
|
|
});
|
|
|
|
test("getModelInfoCore resolves explicit gpt-5.5 Codex model", async () => {
|
|
const info = await getModelInfoCore("cx/gpt-5.5", {});
|
|
assert.equal(info.provider, "codex");
|
|
assert.equal(info.model, "gpt-5.5");
|
|
});
|
|
|
|
test("getModelInfoCore returns explicit ambiguity metadata for ambiguous unprefixed model", async () => {
|
|
const info = await getModelInfoCore("claude-haiku-4.5", {});
|
|
assert.equal(info.provider, null);
|
|
assert.equal((info as any).errorType, "ambiguous_model");
|
|
assert.match((info as any).errorMessage, /Ambiguous model/i);
|
|
assert.ok(Array.isArray((info as any).candidateProviders));
|
|
assert.ok((info as any).candidateProviders.length >= 2);
|
|
});
|
|
|
|
test("getModelInfoCore canonicalizes github legacy alias with explicit provider prefix", async () => {
|
|
const info = await getModelInfoCore("gh/claude-4.5-opus", {});
|
|
assert.equal(info.provider, "github");
|
|
assert.equal(info.model, "claude-opus-4-5-20251101");
|
|
});
|
|
|
|
test("GithubExecutor routes codex-family model to /responses", () => {
|
|
const executor = new GithubExecutor();
|
|
const url = executor.buildUrl("gpt-5.3-codex", true);
|
|
assert.match(url, /\/responses$/);
|
|
});
|
|
|
|
test("GithubExecutor keeps non-codex model on /chat/completions", () => {
|
|
const executor = new GithubExecutor();
|
|
const url = executor.buildUrl("gpt-5", true);
|
|
assert.match(url, /\/chat\/completions$/);
|
|
});
|
|
|
|
test("DefaultExecutor uses x-api-key for kimi-coding-apikey", () => {
|
|
const executor = new DefaultExecutor("kimi-coding-apikey");
|
|
const headers = executor.buildHeaders({ apiKey: "sk-kimi-test" }, true);
|
|
|
|
assert.equal((headers as any)["x-api-key"], "sk-kimi-test");
|
|
assert.equal((headers as any).Authorization, undefined);
|
|
});
|
|
|
|
test("DefaultExecutor execute honors connection-level custom User-Agent", async () => {
|
|
const executor = new DefaultExecutor("openai");
|
|
const originalFetch = globalThis.fetch;
|
|
let capturedHeaders = null;
|
|
|
|
globalThis.fetch = async (_url, init = {}) => {
|
|
capturedHeaders = (init as any).headers || null;
|
|
return new Response(JSON.stringify({ id: "chatcmpl-test" }), { status: 200 });
|
|
};
|
|
|
|
try {
|
|
await executor.execute({
|
|
model: "gpt-4o",
|
|
body: {
|
|
model: "gpt-4o",
|
|
messages: [{ role: "user", content: "hello" }],
|
|
},
|
|
stream: false,
|
|
credentials: {
|
|
apiKey: "sk-openai-test",
|
|
providerSpecificData: {
|
|
customUserAgent: "OmniRouteCustomUA/2.0",
|
|
},
|
|
},
|
|
});
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
|
|
assert.ok(capturedHeaders);
|
|
assert.equal(capturedHeaders.Authorization, "Bearer sk-openai-test");
|
|
assert.equal(capturedHeaders["User-Agent"], "OmniRouteCustomUA/2.0");
|
|
});
|
|
|
|
test("CodexExecutor forces stream=true for upstream compatibility", () => {
|
|
const executor = new CodexExecutor();
|
|
const transformed = executor.transformRequest(
|
|
"gpt-5.1-codex",
|
|
{ model: "gpt-5.1-codex", input: [], stream: false },
|
|
false,
|
|
{}
|
|
);
|
|
assert.equal(transformed.stream, true);
|
|
});
|
|
|
|
test("Claude native messages can be round-tripped through OpenAI into Claude OAuth format", () => {
|
|
const normalizeOptions = { normalizeToolCallId: false, preserveDeveloperRole: undefined };
|
|
const openaiBody = translateRequest(
|
|
FORMATS.CLAUDE,
|
|
FORMATS.OPENAI,
|
|
"claude-sonnet-4-6",
|
|
{
|
|
model: "claude-sonnet-4-6",
|
|
max_tokens: 32,
|
|
messages: [{ role: "user", content: "reply with OK only" }],
|
|
},
|
|
false,
|
|
null,
|
|
"claude",
|
|
null,
|
|
normalizeOptions
|
|
);
|
|
const translated = translateRequest(
|
|
FORMATS.OPENAI,
|
|
FORMATS.CLAUDE,
|
|
"claude-sonnet-4-6",
|
|
openaiBody,
|
|
false,
|
|
null,
|
|
"claude",
|
|
null,
|
|
normalizeOptions
|
|
);
|
|
|
|
assert.deepEqual(translated.messages, [
|
|
{
|
|
role: "user",
|
|
content: [{ type: "text", text: "reply with OK only" }],
|
|
},
|
|
]);
|
|
assert.equal(translated.system, undefined);
|
|
});
|
|
|
|
test("CodexExecutor maps fast service tier to priority", () => {
|
|
const executor = new CodexExecutor();
|
|
const transformed = executor.transformRequest(
|
|
"gpt-5.1-codex",
|
|
{ model: "gpt-5.1-codex", input: [], service_tier: "fast" },
|
|
true,
|
|
{}
|
|
);
|
|
assert.equal(transformed.service_tier, "priority");
|
|
});
|
|
|
|
test("shouldUseNativeCodexPassthrough only enables responses-native Codex requests", () => {
|
|
assert.equal(
|
|
shouldUseNativeCodexPassthrough({
|
|
provider: "codex",
|
|
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
|
endpointPath: "/v1/responses",
|
|
}),
|
|
true
|
|
);
|
|
|
|
assert.equal(
|
|
shouldUseNativeCodexPassthrough({
|
|
provider: "codex",
|
|
sourceFormat: FORMATS.OPENAI,
|
|
endpointPath: "/v1/responses",
|
|
}),
|
|
false
|
|
);
|
|
|
|
assert.equal(
|
|
shouldUseNativeCodexPassthrough({
|
|
provider: "openai",
|
|
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
|
endpointPath: "/v1/responses",
|
|
}),
|
|
false
|
|
);
|
|
|
|
assert.equal(
|
|
shouldUseNativeCodexPassthrough({
|
|
provider: "codex",
|
|
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
|
endpointPath: "/v1/responses/compact",
|
|
}),
|
|
true
|
|
);
|
|
|
|
assert.equal(
|
|
shouldUseNativeCodexPassthrough({
|
|
provider: "codex",
|
|
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
|
endpointPath: "/v1/responses/items/history",
|
|
}),
|
|
true
|
|
);
|
|
|
|
assert.equal(
|
|
shouldUseNativeCodexPassthrough({
|
|
provider: "codex",
|
|
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
|
endpointPath: "/v1/chat/completions",
|
|
}),
|
|
false
|
|
);
|
|
});
|
|
|
|
test("CodexExecutor can apply per-connection fast service tier defaults", () => {
|
|
const executor = new CodexExecutor();
|
|
const transformed = executor.transformRequest(
|
|
"gpt-5.1-codex",
|
|
{ model: "gpt-5.1-codex", input: [] },
|
|
true,
|
|
{
|
|
providerSpecificData: {
|
|
requestDefaults: { serviceTier: "priority" },
|
|
},
|
|
}
|
|
);
|
|
assert.equal(transformed.service_tier, "priority");
|
|
});
|
|
|
|
test("CodexExecutor always requests SSE accept header", () => {
|
|
const executor = new CodexExecutor();
|
|
const headers = executor.buildHeaders({ accessToken: "test-token" }, false);
|
|
assert.equal(headers.Accept, "text/event-stream");
|
|
});
|
|
|
|
test("CodexExecutor does not request SSE accept header for compact requests", () => {
|
|
const executor = new CodexExecutor();
|
|
const headers = executor.buildHeaders(
|
|
{
|
|
accessToken: "test-token",
|
|
requestEndpointPath: "/v1/responses/compact",
|
|
},
|
|
false
|
|
);
|
|
assert.equal(headers.Accept, "application/json");
|
|
});
|
|
|
|
test("CodexExecutor preserves native responses payloads for Codex passthrough", () => {
|
|
const executor = new CodexExecutor();
|
|
const transformed = executor.transformRequest(
|
|
"gpt-5.1-codex",
|
|
{
|
|
model: "gpt-5.1-codex",
|
|
input: "ship it",
|
|
instructions: "custom system prompt",
|
|
store: true,
|
|
metadata: { source: "codex-client" },
|
|
reasoning_effort: "high",
|
|
service_tier: "fast",
|
|
_nativeCodexPassthrough: true,
|
|
stream: false,
|
|
},
|
|
false,
|
|
{}
|
|
);
|
|
|
|
assert.equal(transformed.stream, true);
|
|
assert.equal(transformed.service_tier, "priority");
|
|
assert.equal(transformed.instructions, "custom system prompt");
|
|
assert.equal(transformed.store, false);
|
|
assert.deepEqual(transformed.metadata, { source: "codex-client" });
|
|
assert.equal(transformed.reasoning.effort, "high");
|
|
assert.equal(transformed.reasoning_effort, undefined);
|
|
assert.ok(!("_nativeCodexPassthrough" in transformed));
|
|
});
|
|
|
|
test("CodexExecutor gives model reasoning suffix precedence over client defaults", () => {
|
|
const executor = new CodexExecutor();
|
|
const transformed = executor.transformRequest(
|
|
"gpt-5.5-xhigh",
|
|
{
|
|
model: "gpt-5.5-xhigh",
|
|
input: [],
|
|
reasoning: { effort: "medium", summary: "auto" },
|
|
reasoning_effort: "low",
|
|
},
|
|
true,
|
|
{}
|
|
);
|
|
|
|
assert.equal(transformed.model, "gpt-5.5");
|
|
assert.deepEqual(transformed.reasoning, { effort: "xhigh", summary: "auto" });
|
|
assert.equal(transformed.reasoning_effort, undefined);
|
|
});
|
|
|
|
test("CodexExecutor strips streaming fields for compact passthrough", () => {
|
|
const executor = new CodexExecutor();
|
|
const transformed = executor.transformRequest(
|
|
"gpt-5.1-codex",
|
|
{
|
|
model: "gpt-5.1-codex",
|
|
input: "compact this session",
|
|
stream: false,
|
|
stream_options: { include_usage: true },
|
|
_nativeCodexPassthrough: true,
|
|
},
|
|
false,
|
|
{
|
|
requestEndpointPath: "/v1/responses/compact",
|
|
}
|
|
);
|
|
|
|
assert.equal("stream" in transformed, false);
|
|
assert.equal("stream_options" in transformed, false);
|
|
assert.ok(!("_nativeCodexPassthrough" in transformed));
|
|
});
|
|
|
|
test("CodexExecutor routes responses subpaths to matching upstream paths", () => {
|
|
const executor = new CodexExecutor();
|
|
const compactUrl = executor.buildUrl("gpt-5.1-codex", true, 0, {
|
|
requestEndpointPath: "/v1/responses/compact",
|
|
});
|
|
assert.match(compactUrl, /\/responses\/compact$/);
|
|
|
|
const genericSubpathUrl = executor.buildUrl("gpt-5.1-codex", true, 0, {
|
|
requestEndpointPath: "/v1/responses/items/history",
|
|
});
|
|
assert.match(genericSubpathUrl, /\/responses\/items\/history$/);
|
|
});
|
|
|
|
test("translateNonStreamingResponse converts Responses API payload to OpenAI chat.completion", () => {
|
|
const responseBody = {
|
|
id: "resp_123",
|
|
object: "response",
|
|
created_at: 1739370000,
|
|
model: "gpt-5.1-codex",
|
|
output: [
|
|
{
|
|
type: "message",
|
|
role: "assistant",
|
|
content: [{ type: "output_text", text: "Hello from responses API." }],
|
|
},
|
|
{
|
|
type: "function_call",
|
|
id: "fc_1",
|
|
call_id: "call_1",
|
|
name: "sum",
|
|
arguments: '{"a":1,"b":2}',
|
|
},
|
|
],
|
|
usage: {
|
|
input_tokens: 11,
|
|
output_tokens: 7,
|
|
},
|
|
};
|
|
|
|
const translated = translateNonStreamingResponse(
|
|
responseBody,
|
|
FORMATS.OPENAI_RESPONSES,
|
|
FORMATS.OPENAI
|
|
);
|
|
|
|
assert.equal((translated as any).object, "chat.completion");
|
|
assert.equal((translated as any).model, "gpt-5.1-codex");
|
|
(assert as any).equal((translated as any).choices[0].message.role, "assistant");
|
|
(assert as any).equal(
|
|
(translated as any).choices[0].message.content,
|
|
"Hello from responses API."
|
|
);
|
|
assert.equal((translated as any).choices[0].finish_reason, "tool_calls");
|
|
assert.equal(((translated as any).choices[0].message.tool_calls as any).length, 1);
|
|
assert.equal(((translated as any).usage as any).prompt_tokens, 11);
|
|
assert.equal((translated as any).usage.completion_tokens, 7);
|
|
assert.equal((translated as any).usage.total_tokens, 18);
|
|
});
|
|
|
|
test("extractUsageFromResponse reads usage from Responses API payload", () => {
|
|
const responseBody = {
|
|
object: "response",
|
|
usage: {
|
|
input_tokens: 20,
|
|
output_tokens: 9,
|
|
cache_read_input_tokens: 4,
|
|
reasoning_tokens: 3,
|
|
},
|
|
};
|
|
|
|
const usage = extractUsageFromResponse(responseBody, "github");
|
|
assert.equal(usage.prompt_tokens, 20);
|
|
assert.equal(usage.completion_tokens, 9);
|
|
assert.equal(usage.cached_tokens, 4);
|
|
assert.equal(usage.reasoning_tokens, 3);
|
|
});
|
|
|
|
test("detectFormat identifies OpenAI Responses when input is string", () => {
|
|
const format = detectFormat({
|
|
model: "gpt-5.1-codex",
|
|
input: "hello world",
|
|
stream: true,
|
|
});
|
|
assert.equal(format, FORMATS.OPENAI_RESPONSES);
|
|
});
|
|
|
|
test("detectFormat identifies OpenAI Responses by max_output_tokens without input array", () => {
|
|
const format = detectFormat({
|
|
model: "gpt-5.1-codex",
|
|
max_output_tokens: 256,
|
|
stream: false,
|
|
});
|
|
assert.equal(format, FORMATS.OPENAI_RESPONSES);
|
|
});
|
|
|
|
test("detectFormatFromEndpoint uses chat completions endpoint for OpenAI chat protocol", () => {
|
|
const format = detectFormatFromEndpoint(
|
|
{
|
|
model: "test-model",
|
|
messages: [{ role: "user", content: "hi" }],
|
|
input: "ignored for endpoint protocol detection",
|
|
max_output_tokens: 16,
|
|
stream: false,
|
|
},
|
|
"/v1/chat/completions"
|
|
);
|
|
assert.equal(format, FORMATS.OPENAI);
|
|
});
|
|
|
|
test("detectFormatFromEndpoint forces Claude for /v1/messages", () => {
|
|
const format = detectFormatFromEndpoint(
|
|
{
|
|
model: "claude-opus-4-6",
|
|
messages: [{ role: "user", content: "hi" }],
|
|
max_tokens: 16,
|
|
stream: false,
|
|
},
|
|
"/v1/messages"
|
|
);
|
|
assert.equal(format, FORMATS.CLAUDE);
|
|
});
|
|
|
|
test("translateRequest normalizes openai-responses input string into list payload", () => {
|
|
const translated = translateRequest(
|
|
FORMATS.OPENAI_RESPONSES,
|
|
FORMATS.OPENAI_RESPONSES,
|
|
"gpt-5.1-codex",
|
|
{
|
|
model: "gpt-5.1-codex",
|
|
input: "hello from responses",
|
|
stream: false,
|
|
},
|
|
false
|
|
);
|
|
|
|
assert.ok(Array.isArray(translated.input));
|
|
assert.equal(translated.input.length, 1);
|
|
assert.equal(translated.input[0].type, "message");
|
|
assert.equal(translated.input[0].role, "user");
|
|
assert.equal(translated.input[0].content[0].type, "input_text");
|
|
assert.equal(translated.input[0].content[0].text, "hello from responses");
|
|
});
|
|
|
|
test("translateRequest preserves service_tier when converting openai to openai-responses", () => {
|
|
const translated = translateRequest(
|
|
FORMATS.OPENAI,
|
|
FORMATS.OPENAI_RESPONSES,
|
|
"gpt-5.1-codex",
|
|
{
|
|
model: "gpt-5.1-codex",
|
|
messages: [{ role: "user", content: "hello from chat completions" }],
|
|
service_tier: "fast",
|
|
stream: false,
|
|
},
|
|
false
|
|
);
|
|
|
|
assert.equal(translated.service_tier, "fast");
|
|
assert.ok(Array.isArray(translated.input));
|
|
});
|
|
|
|
test("parseSSEToResponsesOutput parses completed response from SSE payload", () => {
|
|
const rawSSE = [
|
|
"event: response.created",
|
|
'data: {"type":"response.created","response":{"id":"resp_1","object":"response","model":"gpt-5.1-codex","status":"in_progress","output":[]}}',
|
|
"",
|
|
"event: response.completed",
|
|
'data: {"type":"response.completed","response":{"id":"resp_1","object":"response","model":"gpt-5.1-codex","status":"completed","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"ok"}]}],"usage":{"input_tokens":5,"output_tokens":3}}}',
|
|
"",
|
|
"data: [DONE]",
|
|
"",
|
|
].join("\n");
|
|
|
|
const parsed = parseSSEToResponsesOutput(rawSSE, "fallback-model");
|
|
assert.equal(parsed.object, "response");
|
|
assert.equal(parsed.id, "resp_1");
|
|
assert.equal(parsed.model, "gpt-5.1-codex");
|
|
assert.equal(parsed.status, "completed");
|
|
assert.equal(parsed.output[0].type, "message");
|
|
assert.equal(parsed.usage.input_tokens, 5);
|
|
assert.equal(parsed.usage.output_tokens, 3);
|
|
});
|
|
|
|
test("parseSSEToResponsesOutput returns null for invalid payload", () => {
|
|
const parsed = parseSSEToResponsesOutput("data: not-json\n\ndata: [DONE]\n", "fallback-model");
|
|
assert.equal(parsed, null);
|
|
});
|
|
|
|
test("parseSSEToOpenAIResponse merges split tool call chunks by id without duplication", () => {
|
|
const rawSSE = [
|
|
`data: ${JSON.stringify({
|
|
id: "chatcmpl_1",
|
|
object: "chat.completion.chunk",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
delta: {
|
|
tool_calls: [
|
|
{
|
|
id: "call_abc",
|
|
index: 0,
|
|
type: "function",
|
|
function: { name: "sum", arguments: '{"a":' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
})}`,
|
|
`data: ${JSON.stringify({
|
|
id: "chatcmpl_1",
|
|
object: "chat.completion.chunk",
|
|
choices: [
|
|
{
|
|
index: 0,
|
|
delta: {
|
|
tool_calls: [
|
|
{
|
|
id: "call_abc",
|
|
index: 0,
|
|
type: "function",
|
|
function: { arguments: "1}" },
|
|
},
|
|
],
|
|
},
|
|
finish_reason: "tool_calls",
|
|
},
|
|
],
|
|
})}`,
|
|
"data: [DONE]",
|
|
].join("\n");
|
|
|
|
const parsed = parseSSEToOpenAIResponse(rawSSE, "gpt-5.1-codex");
|
|
assert.ok(parsed);
|
|
assert.equal(parsed.choices[0].finish_reason, "tool_calls");
|
|
assert.equal(parsed.choices[0].message.tool_calls.length, 1);
|
|
assert.equal(parsed.choices[0].message.tool_calls[0].id, "call_abc");
|
|
assert.equal(parsed.choices[0].message.tool_calls[0].function.name, "sum");
|
|
assert.equal(parsed.choices[0].message.tool_calls[0].function.arguments, '{"a":1}');
|
|
});
|
|
|
|
test("parseSSEToOpenAIResponse normalizes delta.reasoning alias to reasoning_content", () => {
|
|
const rawSSE = [
|
|
`data: ${JSON.stringify({
|
|
id: "chatcmpl_2",
|
|
object: "chat.completion.chunk",
|
|
choices: [{ index: 0, delta: { reasoning: "Let me think..." } }],
|
|
})}`,
|
|
`data: ${JSON.stringify({
|
|
id: "chatcmpl_2",
|
|
object: "chat.completion.chunk",
|
|
choices: [{ index: 0, delta: { reasoning: " The answer is 4." } }],
|
|
})}`,
|
|
`data: ${JSON.stringify({
|
|
id: "chatcmpl_2",
|
|
object: "chat.completion.chunk",
|
|
choices: [{ index: 0, delta: { content: "2+2=4" }, finish_reason: "stop" }],
|
|
})}`,
|
|
"data: [DONE]",
|
|
].join("\n");
|
|
|
|
const parsed = parseSSEToOpenAIResponse(rawSSE, "moonshotai/kimi-k2.5");
|
|
assert.ok(parsed);
|
|
assert.equal(parsed.choices[0].message.reasoning_content, "Let me think... The answer is 4.");
|
|
assert.equal(parsed.choices[0].message.content, "2+2=4");
|
|
});
|