From 5aa0ad9a5293cbaf714caa7725762c3a474cfaf2 Mon Sep 17 00:00:00 2001 From: David Slater Date: Wed, 4 Nov 2026 14:48:08 -0800 Subject: [PATCH 1/5] Show observed models powering auto in attribution footers --- actions/setup/js/messages.test.cjs | 21 +++++++++++++ actions/setup/js/model_attribution.cjs | 25 ++++++++++++++- actions/setup/js/model_attribution.test.cjs | 35 +++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/messages.test.cjs b/actions/setup/js/messages.test.cjs index 85d11a3e0e0..b07b5bfe91d 100644 --- a/actions/setup/js/messages.test.cjs +++ b/actions/setup/js/messages.test.cjs @@ -11,6 +11,8 @@ */ import { describe, it, expect, beforeEach, vi } from "vitest"; import path from "path"; +import fs from "node:fs"; +import os from "node:os"; import { fileURLToPath } from "url"; // Mock core for GitHub Actions environment @@ -177,6 +179,25 @@ describe("messages.cjs", () => { }); describe("getFooterMessage", () => { + it.each([false, true])("shows the model powering auto in default and custom credit footers (custom: %s)", async custom => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "gh-aw-auto-footer-")); + try { + process.env.GH_AW_TMP_DIR = root; + process.env.GH_AW_ENGINE_ID = "copilot"; + process.env.GH_AW_ENGINE_MODEL = "auto"; + process.env.GH_AW_AGENT_AIC = "14"; + process.env.GH_AW_THREAT_DETECTION_AIC = "1"; + process.env.GH_AW_AMBIENT_CONTEXT = "9200"; + if (custom) process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ footer: "> Generated by [{workflow_name}]({run_url}){ai_credits_suffix}{history_link}" }); + fs.writeFileSync(path.join(root, "aw_info.json"), JSON.stringify({ requested_model: "auto", model_selection: { status: "observed", effective_model: "gemini-3.8-flash" } })); + const { getFooterMessage } = await import("./messages.cjs"); + const result = getFooterMessage({ workflowName: "Test Workflow", runUrl: "https://github.com/test/repo/actions/runs/123", historyUrl: "https://github.com/search" }); + expect(result).toContain(" · copilot · auto (powered by Gemini 3.8 Flash) · \\~14 AIC · ⌖ \\~1 AIC · ⊞ 9.2K · [◷]"); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }); + it("should return default footer when no custom config", async () => { const { getFooterMessage } = await import("./messages.cjs"); diff --git a/actions/setup/js/model_attribution.cjs b/actions/setup/js/model_attribution.cjs index e157fb3b7a4..f6878e03c7f 100644 --- a/actions/setup/js/model_attribution.cjs +++ b/actions/setup/js/model_attribution.cjs @@ -318,7 +318,21 @@ function getEffectiveModelLabel(infoPath = `${process.env.GH_AW_TMP_DIR || "/tmp const attribution = resolveEffectiveModel(infoPath, phase, env); const { reduceModelNameToIdentifier } = require("./model_aliases.cjs"); const shortModel = reduceModelNameToIdentifier(attribution.model); - if (!attribution.routing) return shortModel; + if (!attribution.routing) { + const requested = attribution.requestedModel || requestedModelIdentifier(env.GH_AW_INFO_MODEL || env.GH_AW_ENGINE_MODEL); + if (requested.toLowerCase() !== "auto") return shortModel; + const models = new Set(); + if (phase === "agent") { + const sessionPath = require("path").join(require("path").dirname(infoPath), "usage/aw_session.jsonl"); + for (const event of readModelRoutingSession(sessionPath)) { + if (event.type !== "firewall.token_usage" || (event.data?.purpose && event.data.purpose !== "agent")) continue; + const model = validateModelIdentifier(event.data?.model); + if (model && model.toLowerCase() !== "auto") models.add(model); + } + } + if (!models.size && attribution.model && attribution.model.toLowerCase() !== "auto") models.add(attribution.model); + return models.size ? `auto (powered by ${[...models].map(formatAttributionModelName).join(", ")})` : "auto"; + } if (attribution.routing.status === "selected") { const routedModel = reduceModelNameToIdentifier(attribution.routing.wire_model); const routed = `routed: ${routedModel}${attribution.effort ? ` ${attribution.effort}` : ""}`; @@ -329,6 +343,15 @@ function getEffectiveModelLabel(infoPath = `${process.env.GH_AW_TMP_DIR || "/tmp return `routing ${attribution.routing.status}${failureCode ? ` (${failureCode})` : ""}`; } +// Keep the full model version and tier readable rather than compressing them into an alias. +function formatAttributionModelName(model) { + return model + .replace(/^.*\//, "") + .split(/[-_]/) + .map(part => (/^(gpt|o\d+)$/i.test(part) ? part.toUpperCase() : /^\d/.test(part) ? part : part.charAt(0).toUpperCase() + part.slice(1))) + .join(" "); +} + function recordFallbackModelFromUsage(content, env = process.env, infoPath = `${env.GH_AW_TMP_DIR || "/tmp/gh-aw"}/aw_info.json`, logger = console.warn) { let model = ""; for (const entry of iterateJsonl(content, { diff --git a/actions/setup/js/model_attribution.test.cjs b/actions/setup/js/model_attribution.test.cjs index 9f16cf7fa79..2b750ec819d 100644 --- a/actions/setup/js/model_attribution.test.cjs +++ b/actions/setup/js/model_attribution.test.cjs @@ -17,6 +17,41 @@ function writeJSONL(filePath, records) { } describe("observed model attribution", () => { + it.each([ + ["gemini-3.8-flash", "auto (powered by Gemini 3.8 Flash)"], + ["gpt-6.1-sol", "auto (powered by GPT 6.1 Sol)"], + ["", "auto"], + ["auto", "auto"], + ["bad model