Skip to content

Commit 5d3f804

Browse files
committed
fix(proxy): forward reasoning_content sse delta
1 parent 8fd8b1c commit 5d3f804

2 files changed

Lines changed: 68 additions & 2 deletions

File tree

cli/openai-bridge.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,14 @@ function writeChatCompletionChunkAsResponsesSse(state, chunk) {
794794
const delta = choice && choice.delta && typeof choice.delta === 'object' ? choice.delta : null;
795795
if (!delta) continue;
796796

797+
const segments = [];
798+
if (typeof delta.reasoning_content === 'string' && delta.reasoning_content) {
799+
segments.push(delta.reasoning_content);
800+
}
797801
if (typeof delta.content === 'string' && delta.content) {
802+
segments.push(delta.content);
803+
}
804+
for (const seg of segments) {
798805
if (!state.messageItem) {
799806
state.messageItem = {
800807
id: `msg_${crypto.randomBytes(8).toString('hex')}`,
@@ -809,14 +816,14 @@ function writeChatCompletionChunkAsResponsesSse(state, chunk) {
809816
item: state.messageItem
810817
});
811818
}
812-
state.messageText += delta.content;
819+
state.messageText += seg;
813820
state.messageItem.content[0].text = state.messageText;
814821
writeSse(state.res, 'response.output_text.delta', {
815822
type: 'response.output_text.delta',
816823
item_id: state.messageItem.id,
817824
output_index: state.output.length - 1,
818825
content_index: 0,
819-
delta: delta.content,
826+
delta: seg,
820827
sequence_number: state.nextSeq()
821828
});
822829
}

tests/unit/openai-bridge-upstream-responses.test.mjs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,65 @@ test('openai-bridge streams chat/completions directly when Responses client requ
169169
await rm(tmpDir, { recursive: true, force: true });
170170
});
171171

172+
test('openai-bridge forwards upstream reasoning_content as output_text delta', async () => {
173+
const upstream = http.createServer((req, res) => {
174+
if (req.url === '/v1/chat/completions' && req.method === 'POST') {
175+
let body = '';
176+
req.on('data', (c) => (body += c));
177+
req.on('end', () => {
178+
res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8' });
179+
res.write('data: {"id":"r1","model":"deepseek-v4","choices":[{"delta":{"reasoning_content":"thinking-"}}]}\n\n');
180+
res.write('data: {"id":"r1","model":"deepseek-v4","choices":[{"delta":{"reasoning_content":"step"}}]}\n\n');
181+
res.write('data: {"id":"r1","model":"deepseek-v4","choices":[{"delta":{"content":"answer"}}]}\n\n');
182+
res.end('data: [DONE]\n\n');
183+
});
184+
return;
185+
}
186+
res.writeHead(404, { 'Content-Type': 'application/json' });
187+
res.end(JSON.stringify({ error: 'not found' }));
188+
});
189+
const { port: upstreamPort } = await listen(upstream);
190+
191+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'codexmate-bridge-test-'));
192+
const settingsFile = path.join(tmpDir, 'bridge.json');
193+
await writeFile(settingsFile, JSON.stringify({
194+
version: 1,
195+
providers: {
196+
test: { baseUrl: `http://127.0.0.1:${upstreamPort}/v1`, apiKey: 'sk-upstream' }
197+
}
198+
}), 'utf-8');
199+
200+
const handler = createOpenaiBridgeHttpHandler({ settingsFile, expectedToken: 'codexmate' });
201+
const bridge = http.createServer((req, res) => {
202+
if (!handler(req, res)) {
203+
res.statusCode = 404;
204+
res.end('not handled');
205+
}
206+
});
207+
const { port: bridgePort } = await listen(bridge);
208+
209+
const base = `http://127.0.0.1:${bridgePort}/bridge/openai/test/v1/responses`;
210+
const sse = await requestText(base, {
211+
method: 'POST',
212+
headers: {
213+
'Content-Type': 'application/json',
214+
'Accept': 'text/event-stream',
215+
'Authorization': 'Bearer codexmate'
216+
},
217+
body: { model: 'deepseek-v4', input: 'ping', stream: true }
218+
});
219+
assert.equal(sse.status, 200);
220+
assert.match(sse.text, /"delta":"thinking-"/);
221+
assert.match(sse.text, /"delta":"step"/);
222+
assert.match(sse.text, /"delta":"answer"/);
223+
assert.match(sse.text, /"text":"thinking-stepanswer"/);
224+
assert.match(sse.text, /data: \[DONE\]/);
225+
226+
await bridge.close();
227+
await upstream.close();
228+
await rm(tmpDir, { recursive: true, force: true });
229+
});
230+
172231
test('openai-bridge reports failed Responses SSE when upstream chat stream ends before DONE', async () => {
173232
const upstream = http.createServer((req, res) => {
174233
if (req.url === '/v1/chat/completions' && req.method === 'POST') {

0 commit comments

Comments
 (0)