显示思考内容
This commit is contained in:
+67
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user