codexProxyService.ts ×19

Frontier kind: Code frontier

unlabeled · c_2bb1fa372f82

6 tests · 20706 LOC · 85 files · introduces 0 tests · 118 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
19 ranges118 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1654 ranges20706 lines · 85 files · Browse complete extent
All tests (intent)
6 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 118 introduced LOC across 19 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/codex/codexProxyService.ts 118 introduced LOC · 19 ranges

Open complete file

127 }
128
129 > function getDumpDir(): string | undefined { codexProxyService.ts
130 > const dir = process.env[DEBUG_DUMP_DIR_ENV];
131 > if (!dir) {
132 > return undefined;
133 > }
134 try {
135 fs.mkdirSync(dir, { recursive: true });
138 return undefined;
139 }
141
142 function writeJsonError(res: http.ServerResponse, status: number, type: string, message: string): void {
236 return;
237 }
239 > // Codex sends `/v1/responses`, `//responses` (when base_url ends in `/`),
240 > // or plain `/responses`. Accept all three.
241 if (method === 'POST' && (pathname === '/v1/responses' || pathname === '/responses' || pathname === '//responses')) {
242 > await this._handleResponses(req, res, runtime); codexProxyService.ts
243 > return;
244 > }
245
246 writeJsonError(res, 404, 'not_found_error', `No route for ${method} ${pathname}`);
248
249 private async _handleResponses(
250 > req: http.IncomingMessage, codexProxyService.ts
251 > res: http.ServerResponse,
252 > runtime: ICodexProxyRuntime,
253 > ): Promise<void> {
254 > let body: string;
255 > try {
256 > body = await readProxyRequestBody(req);
257 > } catch (err) {
258 writeJsonError(res, 400, 'invalid_request_error', `Failed to read request body: ${err instanceof Error ? err.message : String(err)}`);
259 return;
260 }
262 > // Remap the unsupported auto-review reviewer model onto the session's
263 > // primary model before forwarding, so the "Auto-review" preset works
264 > // against the Copilot CAPI (which does not expose `codex-auto-review`).
265 > // All downstream handling (dump, logging, forward) uses the outbound
266 > // body so logs reflect exactly what is sent upstream.
267 > const remap = remapCodexReviewerModel(body, runtime.state);
268 > if (remap.remappedFrom) {
269 this._logService.info(`[${PROXY_USER_FACING_NAME}] remapped unsupported reviewer model '${remap.remappedFrom}' -> '${remap.remappedTo}'`);
270 }
271 > body = remap.body; codexProxyService.ts
272 >
273 > const dumpDir = getDumpDir();
274 > const dumpSeq = dumpDir ? nextDumpSeq() : undefined;
275 > if (dumpDir && dumpSeq) {
276 const reqFile = join(dumpDir, `req-${dumpSeq}-${Date.now()}.json`);
277 try {
282 }
283 }
284 > try { codexProxyService.ts
285 > const parsed = JSON.parse(body);
286 > this._logService.info(`[${PROXY_USER_FACING_NAME}] >>> /responses body: model=${parsed.model ?? '<none>'}, previous_response_id=${parsed.previous_response_id ?? '<none>'}, stream=${parsed.stream ?? '<none>'}, input_items=${Array.isArray(parsed.input) ? parsed.input.length : '<not-array>'}`);
287 > if (Array.isArray(parsed.input)) {
288 > for (let i = 0; i < parsed.input.length; i++) {
289 const item = parsed.input[i];
290 const type = item?.type ?? '<none>';
307 this._logService.info(`[${PROXY_USER_FACING_NAME}] input[${i}] type=${type} keys=[${keys}] ${detail}`);
308 }
310 > const topLevelKeys = Object.keys(parsed).filter(k => k !== 'input').sort();
311 > this._logService.info(`[${PROXY_USER_FACING_NAME}] top-level keys (excl. input)=[${topLevelKeys.join(', ')}]`);
312 > for (const k of topLevelKeys) {
313 > if (k === 'instructions' || k === 'tools') {
314 const v = parsed[k];
315 const size = typeof v === 'string' ? v.length : JSON.stringify(v).length;
317 continue;
318 }
319 > const v = parsed[k]; codexProxyService.ts
320 > const preview = typeof v === 'object' ? JSON.stringify(v).slice(0, 300) : String(v);
321 > this._logService.info(`[${PROXY_USER_FACING_NAME}] ${k}=${preview}`);
322 > }
323 > } catch {
324 this._logService.info(`[${PROXY_USER_FACING_NAME}] >>> /responses body (unparseable): ${body.slice(0, 200)}`);
325 }
327 > const entry: IProxyInFlight = { ac: new AbortController(), res, clientGone: false };
328 > runtime.inFlight.add(entry);
329 > const onClose = () => {
330 entry.clientGone = true;
331 entry.ac.abort();
332 };
333 > res.on('close', onClose); codexProxyService.ts
334 >
335 > // Snapshot the token at dispatch time so an in-flight request keeps
336 > // using the value it started with; subsequent requests will pick up
337 > // whatever `runtime.state.githubToken` has been rotated to.
338 > const dispatchedToken = runtime.state.githubToken;
339 >
340 > const headers = buildOutboundHeaders(req.headers);
341 >
342 > try {
343 > this._logService.info(`[${PROXY_USER_FACING_NAME}] forwarding to CAPI responses...`);
344 > const upstream = await this._copilotApiService.responses(dispatchedToken, body, { headers, signal: entry.ac.signal, suppressIntegrationId: true });
345 > const contentType = upstream.headers.get('content-type') ?? 'application/json';
346 > const upstreamHeaders = [...upstream.headers.entries()].map(([k, v]) => `${k}: ${v}`).join(', ');
347 > this._logService.info(`[${PROXY_USER_FACING_NAME}] <<< CAPI response: status=${upstream.status}, contentType=${contentType}, headers=[${upstreamHeaders}]`);
348 > res.writeHead(upstream.status, { 'Content-Type': contentType });
349 > if (!upstream.body) {
350 res.end();
351 return;
352 }
353 > const reader = upstream.body.getReader(); codexProxyService.ts
354 > const resDumpStream = dumpDir && dumpSeq
355 ? fs.createWriteStream(join(dumpDir, `res-${dumpSeq}-${Date.now()}.txt`))
356 > : undefined; codexProxyService.ts
357 > let sseBuf = '';
358 > const eventCounts: Record<string, number> = {};
359 > try {
360 > while (true) {
361 > const { done, value } = await reader.read();
362 > if (done) {
363 > break;
364 > }
365 > if (entry.clientGone) {
366 break;
367 }
368 > if (value && value.byteLength > 0) { codexProxyService.ts
369 > const buf = Buffer.from(value);
370 > res.write(buf);
371 > if (resDumpStream) {
372 resDumpStream.write(buf);
373 }
374 > sseBuf += buf.toString('utf8'); codexProxyService.ts
375 > let nl: number;
376 > while ((nl = sseBuf.indexOf('\n')) >= 0) {
377 > const line = sseBuf.slice(0, nl).trimEnd();
378 > sseBuf = sseBuf.slice(nl + 1);
379 > if (line.startsWith('event:')) {
380 > const ev = line.slice('event:'.length).trim();
381 > eventCounts[ev] = (eventCounts[ev] ?? 0) + 1;
382 > }
383 > }
384 > }
385 > }
386 > } finally {
387 > try { reader.releaseLock(); } catch { /* ignore */ }
388 > resDumpStream?.end();
389 > }
390 > if (Object.keys(eventCounts).length) {
391 > const summary = Object.entries(eventCounts).map(([k, v]) => `${k}=${v}`).join(', ');
392 > this._logService.info(`[${PROXY_USER_FACING_NAME}] <<< SSE event counts: ${summary}`);
393 > }
394 > res.end();
395 > } catch (err) {
396 if (entry.clientGone) {
397 this._logService.info(`[${PROXY_USER_FACING_NAME}] client disconnected during upstream call`);
406 this._logService.error(`[${PROXY_USER_FACING_NAME}] upstream error: ${err instanceof Error ? err.message : String(err)}`);
407 writeJsonError(res, 502, 'api_error', err instanceof Error ? err.message : String(err));
408 > } finally { codexProxyService.ts
409 > res.removeListener('close', onClose);
410 > runtime.inFlight.delete(entry);
411 > }
412 > }
413 }
414
455
456
457 > function buildOutboundHeaders(inbound: http.IncomingHttpHeaders): Record<string, string> { codexProxyService.ts
458 > const out: Record<string, string> = {};
459 > const userAgent = inbound['user-agent'];
460 > if (typeof userAgent === 'string' && userAgent.length > 0) {
461 out['User-Agent'] = transformUserAgent(userAgent);
462 }
463 > return out; codexProxyService.ts
464 > }
465
466 /**