diff --git a/internal/cloud/server-code-agent-http.ts b/internal/cloud/server-code-agent-http.ts index 3ab7449a..61008a79 100644 --- a/internal/cloud/server-code-agent-http.ts +++ b/internal/cloud/server-code-agent-http.ts @@ -1987,19 +1987,60 @@ export async function handleCodeAgentTurnHttp(request, response, url, options) { return; } const requestOptions = codeAgentRequestScopedOptions(options); - const projectMismatch = await measureCodeAgentHttpPhase(requestOptions, "turn_project_check", () => traceProjectMismatchSnapshot(traceId, url, requestOptions, "turn")); - if (projectMismatch) { - recordCodeAgentTurnReadMetric(options, url, projectMismatch.statusCode, projectMismatch.body, startedAt); - sendJson(response, projectMismatch.statusCode, projectMismatch.body); - return; - } - const resolved = await measureCodeAgentHttpPhase(requestOptions, "turn_resolve_status", () => resolveCodeAgentTurnStatusSnapshot(traceId, requestOptions)); - recordCodeAgentTurnReadMetric(options, url, resolved.statusCode, resolved.body, startedAt); - void emitCodeAgentOtelSpan("turn_status_read", traceId, requestOptions.env ?? process.env, { - startTimeMs: startedAt, - attributes: { "http.method": "GET", "http.route": "/v1/agent/turns/:traceId", "http.status_code": resolved.statusCode, status: resolved.body?.status ?? null, terminal: resolved.body?.terminal ?? null } + let completed = false; + const emitTurnStatusReadSpan = (name, attributes = {}, error = null) => { + void emitCodeAgentOtelSpan(name, traceId, requestOptions.env ?? process.env, { + startTimeMs: startedAt, + status: error ? "error" : undefined, + error, + attributes: { + "http.method": "GET", + "http.route": "/v1/agent/turns/:traceId", + durationMs: nowMs() - startedAt, + ...attributes + } + }); + }; + response.once?.("close", () => { + if (completed) return; + emitTurnStatusReadSpan("turn_status_read.client_closed", { + status: "client_closed", + phase: "response_close", + terminal: false + }, new Error("Code Agent turn status client connection closed before response completed.")); }); - sendJson(response, resolved.statusCode, resolved.body); + try { + const projectMismatch = await measureCodeAgentHttpPhase(requestOptions, "turn_project_check", () => traceProjectMismatchSnapshot(traceId, url, requestOptions, "turn")); + if (projectMismatch) { + completed = true; + recordCodeAgentTurnReadMetric(options, url, projectMismatch.statusCode, projectMismatch.body, startedAt); + emitTurnStatusReadSpan("turn_status_read", { "http.status_code": projectMismatch.statusCode, status: projectMismatch.body?.status ?? null, terminal: projectMismatch.body?.terminal ?? null, phase: "project_check" }); + sendJson(response, projectMismatch.statusCode, projectMismatch.body); + return; + } + const resolved = await measureCodeAgentHttpPhase(requestOptions, "turn_resolve_status", () => resolveCodeAgentTurnStatusSnapshot(traceId, requestOptions)); + completed = true; + recordCodeAgentTurnReadMetric(options, url, resolved.statusCode, resolved.body, startedAt); + emitTurnStatusReadSpan("turn_status_read", { "http.status_code": resolved.statusCode, status: resolved.body?.status ?? null, terminal: resolved.body?.terminal ?? null, phase: "resolved" }); + sendJson(response, resolved.statusCode, resolved.body); + } catch (error) { + completed = true; + const body = { + ok: false, + status: "unknown", + running: false, + terminal: false, + traceId, + error: { + code: error?.code ?? "turn_status_read_failed", + message: error?.message ?? "Code Agent turn status read failed." + }, + valuesRedacted: true + }; + recordCodeAgentTurnReadMetric(options, url, 500, body, startedAt); + emitTurnStatusReadSpan("turn_status_read", { "http.status_code": 500, status: body.status, terminal: false, phase: "failed", errorCode: body.error.code }, error); + sendJson(response, 500, body); + } } async function resolveCodeAgentTurnStatusSnapshot(traceId, options) {