fix(model-bridge): handle SDK context hints and Responses metadata

This commit is contained in:
xiaohan.yuan committed 2026-10-21 20:27:50 +08:00
1 parent 194853ac01
commit 2cfd53b923
4 files changed
+98 -5

No files matched your search

+18 -3
View File
@@ -34,6 +34,15 @@ for (const api of ['openai-completions', 'openai-responses'] as const) {
}
assert.throws(() => convertRequest({ ...request, messages: [{ role: 'user', content: [{ type: 'document' }] }] }, model(api), context()), /Unsupported/); checks++
assert.throws(() => convertRequest({ ...request, messages: [{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'x', content: [{ type: 'document' }] }] }] }, model(api), context()), /Only text and image tool results/); checks++
for (const context_management of [null, {}, { edits: [] }, { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] }, { edits: [{ type: 'clear_thinking_20251015', keep: { type: 'all' } }] }]) {
assert.deepEqual(convertRequest({ ...request, context_management }, model(api), context()), value); checks++
}
for (const context_management of [false, { unknown: true }, { edits: null }, { edits: {} }, { edits: [null] }, { edits: [{ type: 'clear_thinking_20251015' }] }, { edits: [{ type: 'clear_thinking_20251015', keep: { type: 'thinking_turns', value: 1 } }] }, { edits: [{ type: 'clear_tool_uses_20250919' }] }, { edits: [{ type: 'compact_20260112' }] }, { edits: [{ type: 'clear_thinking_20251015', keep: 'all', unknown: true }] }]) {
assert.throws(() => convertRequest({ ...request, context_management }, model(api), context()), /context_management/); checks++
}
const environment = convertRequest({ ...request, messages: [...request.messages, { role: 'system', content: [{ type: 'text', text: 'SDK environment', cache_control: { type: 'ephemeral' } }] }] }, model(api), context())
check((environment.input ?? environment.messages).at(-1).role === 'system' && (environment.input ?? environment.messages).at(-1).content[0].text === 'SDK environment', `${api}: SDK in-history system instruction preserved in order`)
assert.throws(() => convertRequest({ ...request, messages: [{ role: 'system', content: [{ type: 'image', source: { type: 'url', url: 'https://invalid.example/image.png' } }] }] }, model(api), context()), /Only text system blocks/); checks++
}
const chat = convertResponse({ id: 'chat_1', choices: [{ message: { content: 'ok', reasoning_content: 'reason', tool_calls: [{ id: 'call_1', function: { name: 'echo', arguments: '{"v":1}' } }] }, finish_reason: 'tool_calls' }], usage: { prompt_tokens: 20, completion_tokens: 8, prompt_tokens_details: { cached_tokens: 5 } } }, 'm', false, context())
check(chat.content[0].thinking === 'reason' && !chat.content[0].signature && chat.stop_reason === 'tool_use', 'Chat reasoning and tools')
@@ -54,7 +63,7 @@ for (const api of ['openai-completions', 'openai-responses'] as const) {
}
const rc = context(), responseData = { id: 'r1', status: 'completed', output: [{ type: 'reasoning', id: 'rs1', encrypted_content: 'opaque', summary: [{ type: 'summary_text', text: 'reason' }] }, { type: 'function_call', id: 'fc1', call_id: 'call_1', name: 'echo', arguments: '{"v":1}' }], usage: { input_tokens: 7, output_tokens: 4 } }
const converted = convertResponse(responseData, 'm', true, rc)
const replay = convertRequest({ max_tokens: 30, messages: [{ role: 'assistant', content: converted.content }, { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'ok' }] }] }, model('openai-responses'), rc)
const replay = convertRequest({ max_tokens: 30, context_management: { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] }, messages: [{ role: 'assistant', content: converted.content }, { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'ok' }] }] }, model('openai-responses'), rc)
check(replay.input[0].encrypted_content === 'opaque' && replay.input[1].call_id === 'call_1', 'Responses opaque reasoning exact-history replay')
assert.throws(() => convertRequest({ max_tokens: 30, messages: [{ role: 'assistant', content: converted.content }] }, model('openai-responses'), context()), /history is not available/); checks++
rc.responsePrefix = undefined
@@ -113,6 +122,10 @@ const upstream = createServer(async (req, res) => {
{ type: 'response.reasoning_summary_text.delta', output_index: 2, summary_index: 0, delta: 'one' } as any,
{ type: 'response.reasoning_summary_text.delta', output_index: 2, summary_index: 1, delta: 'two' } as any,
{ type: 'response.output_item.done', output_index: 2, item: completion.output[2] } as any)
if (responses) events.splice(events.length - 1, 0, {
type: 'response.metadata', sequence_number: 27, response_id: 'r2',
metadata: { openai_chatgpt_moderation_metadata: { prompt: {}, generation: {}, tool_call: {}, tool_response: {} } }
} as any)
if (responses && mode === 'mismatch') (events[2] as any).delta = 'truncated'
const wire = events.map(x => `data: ${JSON.stringify(x)}\r\n\r\n`).join('') + (responses ? '' : 'data: [DONE]\n\n')
const bytes = Buffer.from(mode === 'eof' ? wire.slice(0, wire.indexOf('data:', 10)) : wire)
@@ -129,12 +142,14 @@ try {
try {
for (const stream of [false, true]) {
mode = 'normal'
const result = await send({ ...request, stream })
const result = await send({ ...request, stream, context_management: { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] } })
check(result.status === 200, `${api}: HTTP ${stream ? 'stream' : 'json'}`)
check(!('context_management' in last.body), `${api}: SDK keep-all hint is not forwarded upstream`)
check(last.path === (api === 'openai-responses' ? '/v1/responses' : '/v1/chat/completions') && last.body.model === 'offline-model' && last.auth === 'Bearer mock-upstream-secret', `${api}: frozen route and credentials`)
if (stream) {
const frames: any[] = []; for await (const event of readSSE(result.body!)) frames.push(event)
check(frames[0].type === 'message_start' && frames.at(-1).type === 'message_stop', `${api}: valid stream envelope`)
check(!frames.some(x => x.type === 'error'), `${api}: provider metadata does not interrupt the stream`)
check(frames.filter(x => x.type === 'content_block_start').length === 2 && frames.filter(x => x.type === 'content_block_stop').length === 2, `${api}: closed text/tool blocks`)
check(frames.find(x => x.type === 'message_delta').usage.input_tokens === 9, `${api}: reported stream usage`)
check(frames.find(x => x.type === 'message_delta').delta.stop_reason === 'tool_use', `${api}: tool stop reason`)
@@ -147,7 +162,7 @@ try {
check((await send(request, '/v1/other')).status === 404, 'Unknown path denied')
check((await send(request, '/v1/messages?url=http://bad')).status === 404, 'Query injection denied')
check(hits === before, 'Invalid requests never reach upstream')
const count = await send(request, '/v1/messages/count_tokens'), countBody = await count.json()
const count = await send({ ...request, context_management: { edits: [{ type: 'clear_thinking_20251015', keep: 'all' }] } }, '/v1/messages/count_tokens'), countBody = await count.json()
check(countBody.estimated === true && count.headers.get('x-ycode-token-count-source')?.includes('estimate'), 'Token count explicitly estimated')
mode = 'error'; const error = await send(request); check(error.status === 401 && !(await error.text()).includes('mock-upstream-secret'), 'Upstream errors redacted')
mode = 'redirect'; check((await send(request)).status === 502, 'Redirect not followed')