diff --git a/packages/gateway/src/routes/completion.ts b/packages/gateway/src/routes/completion.ts index 4106c39..2415e88 100644 --- a/packages/gateway/src/routes/completion.ts +++ b/packages/gateway/src/routes/completion.ts @@ -728,13 +728,40 @@ function isCodexBridgeModel(model: string | undefined): boolean { return /^(codex-default|codex-|gpt-5(?:\.|-|$))/i.test(model ?? ''); } -function chatMessagesToPrompt(messages: OpenAIChatCompletionRequest['messages']): string { +function chatMessagesToPrompt( + messages: OpenAIChatCompletionRequest['messages'], + options: { includeSystem?: boolean } = {} +): string { return messages + .filter((message) => options.includeSystem !== false || message.role !== 'system') .map((message) => `${message.role}: ${contentToText(message.content)}`) .join('\n\n') .trim(); } +function compressChatMessagesForBridge(body: OpenAIChatCompletionRequest): { + body: OpenAIChatCompletionRequest; + inputText: string; + compression: CompressionResult; +} { + const inputText = chatMessagesToPrompt(body.messages, { includeSystem: false }); + const compression = compressContext(inputText, { enabled: true, mode: 'auto' }); + if (!compression.applied) return { body, inputText, compression }; + + const systemMessages = body.messages.filter((message) => message.role === 'system'); + return { + body: { + ...body, + messages: [ + ...systemMessages, + { role: 'user', content: compression.input }, + ], + }, + inputText: compression.input, + compression, + }; +} + interface SubscriptionBridgeTarget { subscriptionId: string; provider: string; @@ -857,12 +884,18 @@ async function postSubscriptionBridgeCompletion( return { statusCode: chatResponse.status, ok: chatResponse.ok, data: chatData }; } + const system = body.messages + .filter((message) => message.role === 'system') + .map((message) => contentToText(message.content)) + .filter(Boolean) + .join('\n\n'); const generateResponse = await fetch(`${bridgeUrl}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: body.model, prompt: inputText, + system: system || undefined, stream: false, temperature: body.temperature ?? 0, max_tokens: body.max_tokens, @@ -880,9 +913,10 @@ async function callSubscriptionBridgeChatCompletion( const target = subscriptionBridgeTargetForModel(body.model); if (!target) return null; - const inputText = chatMessagesToPrompt(body.messages); + const compressed = compressChatMessagesForBridge(body); + const inputText = compressed.inputText; try { - const upstream = await postSubscriptionBridgeCompletion(target, body, inputText); + const upstream = await postSubscriptionBridgeCompletion(target, compressed.body, inputText); if (!upstream.ok || upstream.data?.success === false) { const error = bridgeErrorMessage(upstream.data, upstream.statusCode); logger.warn( @@ -920,6 +954,7 @@ async function callSubscriptionBridgeChatCompletion( subscription_id: target.subscriptionId, latency_ms: Date.now() - startMs, passthrough: true, + compression: buildCompressionResponse(compressed.compression), }, }, }; @@ -1400,10 +1435,12 @@ export async function completionRoute(fastify: FastifyInstance): Promise { if (isCodexBridgeModel(parsed.data.model)) { try { const bridgeUrl = process.env['CODEX_BRIDGE_URL'] ?? 'http://127.0.0.1:3253'; - const inputText = typeof parsed.data.input === 'string' + const originalInputText = typeof parsed.data.input === 'string' ? parsed.data.input : (Array.isArray(parsed.data.input) ? parsed.data.input.map((p: any) => typeof p?.content === 'string' ? p.content : (Array.isArray(p?.content) ? p.content.map((c: any) => c?.text ?? '').join(' ') : '')).join(' ') : ''); + const compression = compressContext(originalInputText, { enabled: true, mode: 'auto' }); + const inputText = compression.input; const upstream = await fetch(`${bridgeUrl}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -1421,11 +1458,17 @@ export async function completionRoute(fastify: FastifyInstance): Promise { } if (upstream.ok && upstreamJson?.success !== false) { const text = upstreamJson?.content ?? upstreamJson?.response ?? upstreamJson?.choices?.[0]?.message?.content ?? ''; - const respBody = toOpenAIResponsesResponse({ output: text, model: parsed.data.model, status: 'approved' }, parsed.data.model); logger.info({ callId, model: parsed.data.model, len: text.length }, 'codex-bridge passthrough OK'); const bridgeCaller = (request.headers['x-llm-interceptor-caller'] as string) || 'codex-app'; const bridgeTokensIn = (upstreamJson?.usage?.prompt_tokens as number | undefined) ?? Math.ceil(inputText.length / 4); const bridgeTokensOut = (upstreamJson?.usage?.completion_tokens as number | undefined) ?? Math.ceil(text.length / 4); + const respBody = toOpenAIResponsesResponse({ + output: text, + model: parsed.data.model, + status: 'approved', + tokens: { in: bridgeTokensIn, out: bridgeTokensOut }, + compression: buildCompressionResponse(compression), + }, parsed.data.model); // Track against the merged OpenAI (ChatGPT+Codex) subscription pool. try { const subId = modelToSubscriptionId(parsed.data.model ?? '') ?? 'codex'; @@ -1447,7 +1490,7 @@ export async function completionRoute(fastify: FastifyInstance): Promise { 0, Date.now() - startMs, 0, - false, + compression.applied, undefined, ); } catch (e) { @@ -1465,7 +1508,8 @@ export async function completionRoute(fastify: FastifyInstance): Promise { latency_ms: Date.now() - startMs, confidence: 0, cost_usd: 0, - compression_applied: false, + compression_applied: compression.applied, + metadata: { compression: buildCompressionResponse(compression) }, model: parsed.data.model ?? 'gpt-5.5', } as any); } catch (e) {