From c47a6f39f164523d76b5fde38ca6cf64538b75e4 Mon Sep 17 00:00:00 2001 From: UniDesk Codex Date: Tue, 30 Jun 2026 15:03:47 +0800 Subject: [PATCH] fix: trace opencode event stream start --- internal/dev-entrypoint/cloud-web-runtime.mjs | 56 +++++++++++++++++++ .../dev-entrypoint/cloud-web-runtime.test.mjs | 17 +++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/internal/dev-entrypoint/cloud-web-runtime.mjs b/internal/dev-entrypoint/cloud-web-runtime.mjs index 85acc0d9..d09749d7 100644 --- a/internal/dev-entrypoint/cloud-web-runtime.mjs +++ b/internal/dev-entrypoint/cloud-web-runtime.mjs @@ -800,6 +800,18 @@ async function proxyOpencodeRequest({ request, response, url, cloudApiBaseUrl, c const target = opencodeTargetUrl(url, opencodeUpstreamUrl); const streamTransform = opencodeEventDirectoryTransformForTarget(target, opencodeEventDirectoryRewrite); + if (streamTransform?.stats?.enabled) { + emitOpencodeProxyStreamStartSpanAsync({ + traceContext, + serviceId, + request, + url, + target, + ticketAuth, + sessionStatusCode: session.statusCode || 0, + streamTransformStats: streamTransform.stats + }); + } const extraResponseHeaders = { ...cloudWebTraceHeaders(traceContext, serviceId), ...(ticketAuth.ticket ? { "set-cookie": opencodeTicketSetCookie(ticketAuth.ticket) } : {}) @@ -1043,6 +1055,50 @@ function opencodeStreamTransformStatsAttributes(stats) { }; } +function emitOpencodeProxyStreamStartSpanAsync({ traceContext, serviceId, request, url, target = null, ticketAuth = null, sessionStatusCode = 0, streamTransformStats = null } = {}) { + const endpoint = otelTracesEndpoint(process.env); + if (!endpoint || !traceContext?.traceId) return; + const observedAtMs = Date.now(); + const span = { + traceId: traceContext.traceId, + spanId: randomOtelSpanId(), + parentSpanId: traceContext.spanId || traceContext.parentSpanId || "", + name: "opencode.proxy.stream.start", + kind: 2, + startTimeMs: observedAtMs, + endTimeMs: observedAtMs, + statusCode: 0, + errorCode: "", + attributes: { + "http.request.method": request?.method || "GET", + "http.route": opencodeProxyRoute(url?.pathname || ""), + "url.scheme": target?.protocol ? target.protocol.replace(":", "") : undefined, + "server.address": target?.hostname || undefined, + "server.port": target ? Number(target.port || (target.protocol === "https:" ? 443 : 80)) : undefined, + "opencode.proxy.phase": "stream_start", + "opencode.proxy.ticket_present": ticketAuth?.ticketPresent === true, + "opencode.proxy.ticket_accepted": Boolean(ticketAuth?.ticket), + "opencode.proxy.session_status_code": sessionStatusCode || undefined, + "opencode.proxy.streaming": true, + ...opencodeStreamTransformStatsAttributes(streamTransformStats), + valuesPrinted: false + } + }; + setTimeout(() => { + emitOtelSpan(endpoint, span, serviceId || "hwlab-cloud-web", "hwlab.cloud_web.opencode_proxy").catch((error) => { + process.stderr.write(JSON.stringify({ + event: "cloud-web-opencode-otel-emit-failed", + serviceId: serviceId || "hwlab-cloud-web", + traceId: traceContext.traceId, + requestId: traceContext.requestId, + span: "opencode.proxy.stream.start", + error: error instanceof Error ? error.message : String(error), + valuesPrinted: false + }) + "\n"); + }); + }, 0); +} + function opencodeProxyRoute(pathname) { const path = String(pathname || "/"); if (path === "/global/event") return "/global/event"; diff --git a/internal/dev-entrypoint/cloud-web-runtime.test.mjs b/internal/dev-entrypoint/cloud-web-runtime.test.mjs index 573a9431..efc26e1f 100644 --- a/internal/dev-entrypoint/cloud-web-runtime.test.mjs +++ b/internal/dev-entrypoint/cloud-web-runtime.test.mjs @@ -882,10 +882,23 @@ test("cloud web OpenCode event stream emits rewrite OTLP stats when the client c assert.equal(first.done, false); const text = new TextDecoder().decode(first.value); assert.match(text, /"directory":"\/"/u); + + const collectSpans = () => otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []); + await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.stream.start"), "expected OpenCode event stream start OTel span"); + const startSpan = collectSpans().find((item) => item.name === "opencode.proxy.stream.start"); + assert.ok(startSpan, "expected opencode.proxy.stream.start span"); + const startAttrs = new Map((startSpan.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue])); + assert.equal(startAttrs.get("http.route"), "/global/event"); + assert.equal(startAttrs.get("opencode.proxy.phase"), "stream_start"); + assert.equal(startAttrs.get("opencode.proxy.streaming"), true); + assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_enabled"), true); + assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_from"), "/workspace"); + assert.equal(startAttrs.get("opencode.proxy.sse.directory_rewrite_to"), "/"); + await reader.cancel(); - await waitFor(() => otelBodies.length > 0, "expected OpenCode event stream OTel span"); - const spans = otelBodies.flatMap((body) => body.resourceSpans?.flatMap((resourceSpan) => resourceSpan.scopeSpans?.flatMap((scopeSpan) => scopeSpan.spans ?? []) ?? []) ?? []); + await waitFor(() => collectSpans().some((item) => item.name === "opencode.proxy.request"), "expected OpenCode event stream completion OTel span"); + const spans = collectSpans(); const span = spans.find((item) => item.name === "opencode.proxy.request"); assert.ok(span, "expected opencode.proxy.request span"); const attrs = new Map((span.attributes ?? []).map((entry) => [entry.key, entry.value?.stringValue ?? entry.value?.intValue ?? entry.value?.boolValue]));