diff --git a/hypaware-core/plugins-workspace/codex/src/exchange-projector.js b/hypaware-core/plugins-workspace/codex/src/exchange-projector.js index d9b078f..b0b8edd 100644 --- a/hypaware-core/plugins-workspace/codex/src/exchange-projector.js +++ b/hypaware-core/plugins-workspace/codex/src/exchange-projector.js @@ -98,7 +98,7 @@ export function createCodexExchangeProjector(opts = {}) { request_id: resolveRequestId(input), prompt_id: codexContext?.turn_id, model: resolveModel(reqBody, responseBody), - system_text: extractSystemText(reqBody.system), + system_text: extractSystemText(reqBody.system ?? reqBody.instructions), tools: /** @type {any} */ (reqBody.tools), attributes: projectionAttributes, messages, @@ -701,7 +701,14 @@ function resolveModel(reqBody, responseBody) { return stringValue(reqBody.model) ?? stringValue(readKey(responseBody, 'model')) } -/** @param {unknown} system */ +/** + * Accepts the Chat Completions `system` field (string or content blocks) + * or the Responses API top-level `instructions` string. Codex traffic + * uses the latter, so without it `system_text` is empty for every + * Responses-shaped exchange. + * + * @param {unknown} system + */ function extractSystemText(system) { if (typeof system === 'string' && system.length > 0) return system if (Array.isArray(system)) { diff --git a/test/plugins/codex-exchange-projector.test.js b/test/plugins/codex-exchange-projector.test.js index b99abe4..e54f92b 100644 --- a/test/plugins/codex-exchange-projector.test.js +++ b/test/plugins/codex-exchange-projector.test.js @@ -104,6 +104,39 @@ test('OpenAI Responses with output_text in the body produces an assistant messag assert.deepEqual(projection.messages[1].content, [{ type: 'text', text: 'because' }]) }) +test('OpenAI Responses captures top-level instructions into system_text', () => { + const projector = createCodexExchangeProjector({ env: {} }) + const projection = /** @type {any} */ (projector.project(exchange({ + path: '/backend-api/codex/responses', + provider: 'chatgpt', + request_body: JSON.stringify({ + model: 'gpt-5', + instructions: 'You are Codex, a coding agent.', + input: [{ role: 'user', content: [{ type: 'input_text', text: 'how' }] }], + }), + response_body: JSON.stringify({ id: 'resp_1', output_text: 'because' }), + }), context())) + + assert.equal(projection.system_text, 'You are Codex, a coding agent.') +}) + +test('OpenAI Chat system field still wins over instructions', () => { + const projector = createCodexExchangeProjector({ env: {} }) + const projection = /** @type {any} */ (projector.project(exchange({ + path: '/v1/chat/completions', + provider: 'openai', + request_body: JSON.stringify({ + model: 'gpt-5', + system: 'chat-system', + instructions: 'responses-instructions', + messages: [{ role: 'user', content: 'hi' }], + }), + response_body: JSON.stringify({ choices: [{ message: { role: 'assistant', content: 'yo' } }] }), + }), context())) + + assert.equal(projection.system_text, 'chat-system') +}) + test('OpenAI Responses SSE deltas reconstruct the assistant body', () => { const projector = createCodexExchangeProjector({ env: {} }) const projection = /** @type {any} */ (projector.project(exchange({