Files
deepseek-harness/packages/subagent/subagent-codex/tests/responses-fixture.ts
2026-08-05 01:30:42 +08:00

289 lines
7.8 KiB
TypeScript

import { createServer } from 'node:http'
import type {
IncomingHttpHeaders,
IncomingMessage,
Server,
ServerResponse,
} from 'node:http'
/** One request observed by the package-private Responses fixture. */
interface RecordedResponsesRequest {
readonly method: string | undefined
readonly path: string | undefined
readonly headers: IncomingHttpHeaders
readonly body: Record<string, unknown>
}
/** Behavior consumed by one Responses request. */
export type ResponsesBehavior =
| { readonly kind: 'complete'; readonly text: string }
| {
readonly kind: 'functionCall'
readonly name: string
readonly arguments: Record<string, unknown>
}
| { readonly kind: 'hold' }
/** Running package-private Responses fixture. */
export interface ResponsesFixture {
readonly baseUrl: string
readonly requests: RecordedResponsesRequest[]
readonly requestStarted: Promise<void>
close(): Promise<void>
}
function responseObject(text: string): Record<string, unknown> {
const message = {
id: 'msg_fixture',
type: 'message',
status: 'completed',
role: 'assistant',
content: [{
type: 'output_text',
annotations: [],
logprobs: [],
text,
}],
}
return {
id: 'resp_fixture',
object: 'response',
created_at: 1,
status: 'completed',
background: false,
error: null,
incomplete_details: null,
instructions: null,
max_output_tokens: null,
max_tool_calls: null,
model: 'fixture-model',
output: [message],
parallel_tool_calls: true,
previous_response_id: null,
prompt_cache_key: null,
prompt_cache_retention: null,
reasoning: { effort: null, summary: null },
safety_identifier: null,
service_tier: 'default',
store: false,
temperature: null,
text: { format: { type: 'text' }, verbosity: 'medium' },
tool_choice: 'auto',
tools: [],
top_logprobs: 0,
top_p: null,
truncation: 'disabled',
usage: {
input_tokens: 10,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 1,
output_tokens_details: { reasoning_tokens: 0 },
total_tokens: 11,
},
user: null,
metadata: {},
}
}
/**
* Build the minimal Responses SSE event sequence consumed by Codex 0.146.0.
* @param text - exact assistant answer.
* @returns ordered response lifecycle events.
*/
export function completeResponsesEvents(text: string): Record<string, unknown>[] {
const completed = responseObject(text)
const message = (completed.output as Record<string, unknown>[])[0]!
const part = (message.content as Record<string, unknown>[])[0]!
return [
{
type: 'response.created',
response: { ...completed, status: 'in_progress', output: [] },
},
{
type: 'response.output_item.added',
output_index: 0,
item: { ...message, status: 'in_progress', content: [] },
},
{
type: 'response.content_part.added',
item_id: message.id,
output_index: 0,
content_index: 0,
part: { ...part, text: '' },
},
{
type: 'response.output_text.delta',
item_id: message.id,
output_index: 0,
content_index: 0,
delta: text,
logprobs: [],
},
{
type: 'response.output_text.done',
item_id: message.id,
output_index: 0,
content_index: 0,
text,
logprobs: [],
},
{
type: 'response.content_part.done',
item_id: message.id,
output_index: 0,
content_index: 0,
part,
},
{
type: 'response.output_item.done',
output_index: 0,
item: message,
},
{ type: 'response.completed', response: completed },
]
}
function functionCallEvents(
name: string,
argumentsValue: Record<string, unknown>,
): Record<string, unknown>[] {
const argumentsText = JSON.stringify(argumentsValue)
const item = {
id: 'fc_fixture',
type: 'function_call',
status: 'completed',
name,
arguments: argumentsText,
call_id: 'call_fixture',
}
const completed = {
...responseObject(''),
output: [item],
usage: {
input_tokens: 10,
input_tokens_details: { cached_tokens: 0 },
output_tokens: 5,
output_tokens_details: { reasoning_tokens: 0 },
total_tokens: 15,
},
}
return [
{
type: 'response.created',
response: { ...completed, status: 'in_progress', output: [] },
},
{
type: 'response.output_item.added',
output_index: 0,
item: { ...item, status: 'in_progress', arguments: '' },
},
{
type: 'response.function_call_arguments.delta',
item_id: item.id,
output_index: 0,
delta: argumentsText,
},
{
type: 'response.function_call_arguments.done',
item_id: item.id,
output_index: 0,
arguments: argumentsText,
},
{
type: 'response.output_item.done',
output_index: 0,
item,
},
{ type: 'response.completed', response: completed },
]
}
function readRequest(request: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let body = ''
request.setEncoding('utf8')
request.on('data', (chunk: string) => { body += chunk })
request.on('end', () => { resolve(body) })
request.on('error', reject)
})
}
function closeServer(server: Server): Promise<void> {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error !== undefined) reject(error)
else resolve()
})
server.closeAllConnections()
})
}
/**
* Start a loopback-only Responses SSE fixture.
* @param script - one behavior per expected Responses request.
* @returns the running fixture and its observed requests.
*/
export async function startResponsesFixture(
script: readonly ResponsesBehavior[],
): Promise<ResponsesFixture> {
const behaviors = [...script]
const requests: RecordedResponsesRequest[] = []
const started = Promise.withResolvers<undefined>()
const openResponses = new Set<ServerResponse>()
const server = createServer((request, response) => {
openResponses.add(response)
response.on('close', () => { openResponses.delete(response) })
void readRequest(request).then((body) => {
requests.push({
method: request.method,
path: request.url,
headers: request.headers,
body: JSON.parse(body) as Record<string, unknown>,
})
started.resolve(undefined)
const behavior = behaviors.shift()
if (behavior === undefined) {
response.writeHead(500, { 'content-type': 'application/json' })
response.end(JSON.stringify({ error: { message: 'fixture script exhausted' } }))
return
}
response.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
connection: 'keep-alive',
'x-request-id': 'req_fixture',
})
if (behavior.kind === 'hold') return
const events = behavior.kind === 'complete'
? completeResponsesEvents(behavior.text)
: functionCallEvents(behavior.name, behavior.arguments)
for (const event of events) {
response.write(`data: ${JSON.stringify(event)}\n\n`)
}
response.end('data: [DONE]\n\n')
}).catch((error: unknown) => {
response.destroy(error instanceof Error ? error : new Error(String(error)))
})
})
await new Promise<void>((resolve, reject) => {
server.once('error', reject)
server.listen(0, '127.0.0.1', () => {
server.off('error', reject)
resolve()
})
})
const address = server.address()
if (address === null || typeof address === 'string') {
throw new Error('responses fixture did not acquire a TCP port')
}
return {
baseUrl: `http://127.0.0.1:${address.port}/v1`,
requests,
requestStarted: started.promise,
async close(): Promise<void> {
for (const response of openResponses) response.destroy()
await closeServer(server)
},
}
}