From b01696c6f60ef000af2325571de00a3697deee08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:51:16 +0000 Subject: [PATCH 1/2] Initial plan From 55c0bd35cec24707431fed273b723163d787b525 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:19:28 +0000 Subject: [PATCH 2/2] feat: add gen_ai.request.model to conclusion span attributes Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d1760b0e-2f53-41d1-b739-bc6e3eee152e Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/send_otlp_span.cjs | 1 + actions/setup/js/send_otlp_span.test.cjs | 38 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/actions/setup/js/send_otlp_span.cjs b/actions/setup/js/send_otlp_span.cjs index 52bfe0c290e..e3ec3477ec6 100644 --- a/actions/setup/js/send_otlp_span.cjs +++ b/actions/setup/js/send_otlp_span.cjs @@ -749,6 +749,7 @@ async function sendJobConclusionSpan(spanName, options = {}) { if (jobName) attributes.push(buildAttr("gh-aw.job.name", jobName)); if (engineId) attributes.push(buildAttr("gh-aw.engine.id", engineId)); + if (model) attributes.push(buildAttr("gen_ai.request.model", model)); if (eventName) attributes.push(buildAttr("gh-aw.event_name", eventName)); // Deployment state: prefer the env var (set from github.event.deployment_status.state // in the compiled workflow), fall back to aw_info.deployment_state or aw_context propagation. diff --git a/actions/setup/js/send_otlp_span.test.cjs b/actions/setup/js/send_otlp_span.test.cjs index 14deced9e95..756338b39f8 100644 --- a/actions/setup/js/send_otlp_span.test.cjs +++ b/actions/setup/js/send_otlp_span.test.cjs @@ -1874,6 +1874,44 @@ describe("sendJobConclusionSpan", () => { expect(keys).not.toContain("gen_ai.workflow.name"); }); + it("includes gen_ai.request.model on the conclusion span when model is set in aw_info.json", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com"; + + const readFileSpy = vi.spyOn(fs, "readFileSync").mockImplementation(filePath => { + if (filePath === "/tmp/gh-aw/aw_info.json") { + return JSON.stringify({ model: "claude-3-5-sonnet-20241022", engine_id: "claude" }); + } + throw Object.assign(new Error("ENOENT"), { code: "ENOENT" }); + }); + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + readFileSpy.mockRestore(); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + expect(span.name).toBe("gh-aw.job.conclusion"); + const attrs = Object.fromEntries(span.attributes.map(a => [a.key, a.value.stringValue ?? a.value.intValue])); + expect(attrs["gen_ai.request.model"]).toBe("claude-3-5-sonnet-20241022"); + }); + + it("omits gen_ai.request.model from the conclusion span when model is absent in aw_info.json", async () => { + const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); + vi.stubGlobal("fetch", mockFetch); + + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "https://traces.example.com"; + + await sendJobConclusionSpan("gh-aw.job.conclusion"); + + const body = JSON.parse(mockFetch.mock.calls[0][1].body); + const span = body.resourceSpans[0].scopeSpans[0].spans[0]; + expect(span.name).toBe("gh-aw.job.conclusion"); + const keys = span.attributes.map(a => a.key); + expect(keys).not.toContain("gen_ai.request.model"); + }); + it("includes gh-aw.run.attempt attribute from GITHUB_RUN_ATTEMPT env var", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, status: 200, statusText: "OK" }); vi.stubGlobal("fetch", mockFetch);