mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-21 14:22:14 +03:00
Kimi Coding (claude-format upstream) never engaged reasoning replay: requiresReasoningReplay() had no kimi-coding/kimi-coding-apikey provider entry and only matched /kimi-k2/i model ids, so thinking was neither captured nor re-injected on multi-turn requests. Additionally, streamed Claude thinking_delta chunks were accumulated into content instead of accumulatedReasoning in createSSEStream, so the reconstructed completion body carried no reasoning_content for the cache to capture. - reasoningCache: add kimi-coding/kimi-coding-apikey providers; broaden model pattern to /kimi[-/]k\d/i (covers k2.6/k2.7 incl. namespaced ids, excludes kimi-latest and non-thinking aliases) - stream: accumulate Claude delta.thinking into accumulatedReasoning so the completion body exposes reasoning_content for replay capture - tests: provider/model predicate cases + a reconstructed-stream-body regression test separating thinking from visible text - docs: sync REASONING_REPLAY provider/pattern lists Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { createSSEStream } from "../../open-sse/utils/stream.ts";
|
|
import { FORMATS } from "../../open-sse/translator/formats.ts";
|
|
|
|
async function processClaudeStream(events: Record<string, unknown>[]) {
|
|
let responseBody: unknown;
|
|
const transform = createSSEStream({
|
|
sourceFormat: FORMATS.OPENAI,
|
|
targetFormat: FORMATS.CLAUDE,
|
|
model: "kimi-for-coding",
|
|
onComplete: (result) => {
|
|
responseBody = result.responseBody;
|
|
},
|
|
}) as TransformStream<Uint8Array, Uint8Array>;
|
|
|
|
const writer = transform.writable.getWriter();
|
|
const reader = transform.readable.getReader();
|
|
const encoder = new TextEncoder();
|
|
const drain = (async () => {
|
|
while (!(await reader.read()).done) {}
|
|
})();
|
|
|
|
for (const event of events) {
|
|
await writer.write(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
|
|
}
|
|
await writer.close();
|
|
await drain;
|
|
|
|
return responseBody as {
|
|
choices: Array<{
|
|
message: {
|
|
content: string | null;
|
|
reasoning_content?: string;
|
|
tool_calls?: unknown[];
|
|
};
|
|
}>;
|
|
};
|
|
}
|
|
|
|
test("reconstructed completion separates Claude thinking from visible text", async () => {
|
|
const responseBody = await processClaudeStream([
|
|
{
|
|
type: "message_start",
|
|
message: { id: "msg_test", type: "message", role: "assistant", content: [] },
|
|
},
|
|
{
|
|
type: "content_block_start",
|
|
index: 0,
|
|
content_block: { type: "thinking", thinking: "" },
|
|
},
|
|
{ type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "plan " } },
|
|
{
|
|
type: "content_block_delta",
|
|
index: 0,
|
|
delta: { type: "thinking_delta", thinking: "carefully" },
|
|
},
|
|
{ type: "content_block_stop", index: 0 },
|
|
{ type: "content_block_start", index: 1, content_block: { type: "text", text: "" } },
|
|
{
|
|
type: "content_block_delta",
|
|
index: 1,
|
|
delta: { type: "text_delta", text: "Visible answer" },
|
|
},
|
|
{ type: "content_block_stop", index: 1 },
|
|
{
|
|
type: "content_block_start",
|
|
index: 2,
|
|
content_block: { type: "tool_use", id: "tool_1", name: "lookup", input: {} },
|
|
},
|
|
{
|
|
type: "content_block_delta",
|
|
index: 2,
|
|
delta: { type: "input_json_delta", partial_json: '{"query":"test"}' },
|
|
},
|
|
{ type: "content_block_stop", index: 2 },
|
|
{ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { output_tokens: 12 } },
|
|
{ type: "message_stop" },
|
|
]);
|
|
|
|
const message = responseBody.choices[0].message;
|
|
assert.equal(message.reasoning_content, "plan carefully");
|
|
assert.equal(message.content, "Visible answer");
|
|
assert.equal(message.tool_calls?.length, 1);
|
|
});
|