显示思考内容

This commit is contained in:
Sirius
2026-08-05 20:04:59 +08:00
parent 98c6664701
commit 64469f9c1d
6 changed files with 76 additions and 5 deletions
+67 -1
View File
@@ -9,10 +9,13 @@ export class ChatCompletionSseWriter {
private readonly created = Math.floor(Date.now() / 1000);
private readonly accumulator = new FinalTurnAccumulator();
private outputText = "";
private thinkingOpen = false;
private thinkingBuffer = "";
constructor(
private readonly response: ServerResponse,
private readonly model: string,
private readonly streamThinking: boolean,
) {}
begin(): void {
@@ -21,12 +24,31 @@ export class ChatCompletionSseWriter {
commandEvent(event: Record<string, unknown>): void {
const chunks = this.accumulator.event(event);
if (this.streamThinking) {
if (event.type === "thinking_start") {
this.openThinking();
return;
}
if (event.type === "thinking_delta" && typeof event.delta === "string") {
this.openThinking();
this.addThinking(event.delta);
return;
}
if (event.type === "thinking_end") {
this.closeThinking();
return;
}
}
if (event.type === "text_delta" && typeof event.delta === "string") {
this.closeThinking();
this.addText([event.delta]);
return;
}
if (event.type !== "turn_end") return;
this.closeThinking();
if (event.hadToolCalls === true) {
this.outputText = "";
return;
@@ -35,6 +57,7 @@ export class ChatCompletionSseWriter {
}
finish(finalText: string, usage?: CommandUsage, includeUsage = false): void {
this.closeThinking();
this.ensureFinalText(finalText);
this.chunk({}, "stop");
@@ -56,10 +79,48 @@ export class ChatCompletionSseWriter {
}
error(error: object): void {
this.closeThinking();
this.write(error);
this.writeDone();
}
private openThinking(): void {
if (this.thinkingOpen) return;
this.thinkingOpen = true;
this.chunk({ content: "<think>" }, null);
}
private closeThinking(): void {
if (!this.thinkingOpen) return;
if (this.thinkingBuffer !== "") {
this.chunk({ content: escapeThinkingSources(this.thinkingBuffer) }, null);
this.thinkingBuffer = "";
}
this.thinkingOpen = false;
this.chunk({ content: "</think>\n\n" }, null);
}
private addThinking(delta: string): void {
this.thinkingBuffer += delta;
let content = "";
while (this.thinkingBuffer.length > 6) {
const match = /sources/i.exec(this.thinkingBuffer);
if (match) {
content += this.thinkingBuffer.slice(0, match.index);
content += escapeThinkingSources(match[0]);
this.thinkingBuffer = this.thinkingBuffer.slice(match.index + match[0].length);
continue;
}
const safeLength = this.thinkingBuffer.length - 6;
content += this.thinkingBuffer.slice(0, safeLength);
this.thinkingBuffer = this.thinkingBuffer.slice(safeLength);
}
if (content !== "") this.chunk({ content }, null);
}
private addText(chunks: string[]): void {
for (const delta of chunks) {
if (delta === "") continue;
@@ -104,9 +165,14 @@ export class ChatCompletionSseWriter {
}
}
function escapeThinkingSources(text: string): string {
return text.replace(/sources/gi, (word) => `${word.slice(0, 4)}\u200B${word.slice(4)}`);
}
export function startChatCompletionSse(
reply: FastifyReply,
model: string,
streamThinking: boolean,
): ChatCompletionSseWriter {
reply.hijack();
reply.raw.writeHead(200, {
@@ -116,7 +182,7 @@ export function startChatCompletionSse(
"X-Accel-Buffering": "no",
});
reply.raw.flushHeaders();
const writer = new ChatCompletionSseWriter(reply.raw, model);
const writer = new ChatCompletionSseWriter(reply.raw, model, streamThinking);
writer.begin();
return writer;
}
+1
View File
@@ -16,6 +16,7 @@ const configSchema = z.object({
timeout_seconds: z.number().int().positive().default(1800),
max_request_bytes: z.number().int().positive().default(20 * 1024 * 1024),
max_turns: z.number().int().positive().default(100),
stream_thinking: z.boolean().default(false),
response_store_directory: z.string().min(1).default(".command-code-openai-bridge/responses"),
permission_mode: z.enum(["default", "standard", "plan", "auto-accept", "dont-ask"]).default("auto-accept"),
dangerously_skip_permissions: z.boolean().default(false),
+1 -1
View File
@@ -173,7 +173,7 @@ export async function createServer(config: BridgeConfig): Promise<BridgeServer>
let writer: ChatCompletionSseWriter | undefined;
try {
if (parsed.stream) writer = startChatCompletionSse(reply, parsed.model);
if (parsed.stream) writer = startChatCompletionSse(reply, parsed.model, config.stream_thinking);
const result = await runCommandCode(
config,
model.cli_model,