Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion actions/setup/js/handle_agent_failure.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ describe("handle_agent_failure", () => {
it("includes AIC and ambient context metrics in the generated failure issue footer", async () => {
process.env.GH_AW_AIC = "1.25";
process.env.GH_AW_AMBIENT_CONTEXT = "900";
process.env.GH_AW_ENGINE_MODEL = "claude-sonnet-4.6";
/** @type {string} */
let capturedIssueBody = "";

Expand Down Expand Up @@ -296,10 +297,11 @@ describe("handle_agent_failure", () => {
try {
await main();

expect(capturedIssueBody).toContain("> Generated from [Test Workflow](https://github.com/owner/repo/actions/runs/123456) · 1.25 AIC · ⊞ 900");
expect(capturedIssueBody).toContain("> Generated from [Test Workflow](https://github.com/owner/repo/actions/runs/123456) · sonnet46 1.25 AIC · ⊞ 900");
} finally {
delete process.env.GH_AW_AIC;
delete process.env.GH_AW_AMBIENT_CONTEXT;
delete process.env.GH_AW_ENGINE_MODEL;
}
});

Expand Down
4 changes: 3 additions & 1 deletion actions/setup/js/handle_noop_message.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { isStagedMode } = require("./safe_output_helpers.cjs");
const { generateHistoryUrl } = require("./generate_history_link.cjs");
const { formatAIC } = require("./model_costs.cjs");
const { reduceModelNameToIdentifier } = require("./model_aliases.cjs");
/**
* Search for or create the parent issue for all agentic workflow no-op runs
* @returns {Promise<{number: number, node_id: string}>} Parent issue number and node ID
Expand Down Expand Up @@ -87,11 +88,12 @@ function buildAICSuffix() {
const detectionRaw = process.env.GH_AW_THREAT_DETECTION_AIC;
const agentAIC = agentRaw ? Number.parseFloat(agentRaw) : NaN;
const detectionAIC = detectionRaw ? Number.parseFloat(detectionRaw) : NaN;
const compressedModelName = reduceModelNameToIdentifier(process.env.GH_AW_PRIMARY_MODEL || process.env.GH_AW_ENGINE_MODEL);
const totalAIC = (Number.isFinite(agentAIC) && agentAIC > 0 ? agentAIC : 0) + (Number.isFinite(detectionAIC) && detectionAIC > 0 ? detectionAIC : 0);
if (totalAIC <= 0) {
return "";
}
return ` · ${formatAIC(totalAIC)} AIC`;
return ` · ${compressedModelName ? `${compressedModelName} ` : ""}${formatAIC(totalAIC)} AIC`;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/handle_noop_message.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ This issue helps you:
process.env.GH_AW_AGENT_CONCLUSION = "success";
process.env.GH_AW_AIC = "0.100";
process.env.GH_AW_THREAT_DETECTION_AIC = "0.025";
process.env.GH_AW_ENGINE_MODEL = "claude-sonnet-4.6";

const outputFile = path.join(tempDir, "agent_output.json");
fs.writeFileSync(outputFile, JSON.stringify({ items: [{ type: "noop", message: "No action needed" }] }));
Expand All @@ -777,7 +778,7 @@ This issue helps you:
await main();

const commentCall = mockGithub.rest.issues.createComment.mock.calls[0][0];
expect(commentCall.body).toContain("0.125 AIC");
expect(commentCall.body).toContain("sonnet46 0.125 AIC");
});

it("should not include AIC suffix in comment footer when GH_AW_AIC is not set", async () => {
Expand Down
10 changes: 7 additions & 3 deletions actions/setup/js/messages.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("messages.cjs", () => {
delete process.env.GH_AW_ENGINE_ID;
delete process.env.GH_AW_ENGINE_VERSION;
delete process.env.GH_AW_ENGINE_MODEL;
delete process.env.GH_AW_PRIMARY_MODEL;
delete process.env.GH_AW_TRACKER_ID;
delete process.env.GITHUB_RUN_ID;
delete process.env.GH_AW_WORKFLOW_ID;
Expand Down Expand Up @@ -403,6 +404,7 @@ describe("messages.cjs", () => {
it("should include AI Credits without effective tokens when GH_AW_AIC is set", async () => {
process.env.GH_AW_EFFECTIVE_TOKENS = "5000";
process.env.GH_AW_AIC = "0.125";
process.env.GH_AW_ENGINE_MODEL = "claude-sonnet-4.6";

const { getFooterMessage } = await import("./messages.cjs");

Expand All @@ -411,7 +413,7 @@ describe("messages.cjs", () => {
runUrl: "https://github.com/test/repo/actions/runs/123",
});

expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) · 0.125 AIC");
expect(result).toBe("> Generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) · sonnet46 0.125 AIC");
});

it("should include ambient context in the default footer when GH_AW_AMBIENT_CONTEXT is set", async () => {
Expand Down Expand Up @@ -1142,6 +1144,7 @@ describe("messages.cjs", () => {
it("should include AIC and ambient context in default footer when available", async () => {
process.env.GH_AW_AIC = "1.25";
process.env.GH_AW_AMBIENT_CONTEXT = "900";
process.env.GH_AW_ENGINE_MODEL = "claude-sonnet-4.6";

const { getFooterAgentFailureIssueMessage } = await import("./messages.cjs");

Expand All @@ -1150,7 +1153,7 @@ describe("messages.cjs", () => {
runUrl: "https://github.com/test/repo/actions/runs/123",
});

expect(result).toBe("> Generated from [Test Workflow](https://github.com/test/repo/actions/runs/123) · 1.25 AIC · ⊞ 900");
expect(result).toBe("> Generated from [Test Workflow](https://github.com/test/repo/actions/runs/123) · sonnet46 1.25 AIC · ⊞ 900");
});

it("should suppress env AIC fallback when explicit context AIC is zero", async () => {
Expand Down Expand Up @@ -1241,6 +1244,7 @@ describe("messages.cjs", () => {
it("should include ambient context in the default footer when available", async () => {
process.env.GH_AW_AIC = "1.25";
process.env.GH_AW_AMBIENT_CONTEXT = "900";
process.env.GH_AW_ENGINE_MODEL = "claude-sonnet-4.6";

const { getFooterAgentFailureCommentMessage } = await import("./messages.cjs");

Expand All @@ -1249,7 +1253,7 @@ describe("messages.cjs", () => {
runUrl: "https://github.com/test/repo/actions/runs/123",
});

expect(result).toBe("> Generated from [Test Workflow](https://github.com/test/repo/actions/runs/123) · 1.25 AIC · ⊞ 900");
expect(result).toBe("> Generated from [Test Workflow](https://github.com/test/repo/actions/runs/123) · sonnet46 1.25 AIC · ⊞ 900");
});

it("should expose ai_credits_suffix in custom comment footer templates", async () => {
Expand Down
24 changes: 16 additions & 8 deletions actions/setup/js/messages_footer.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { getBlockedDomains, generateBlockedDomainsSection } = require("./firewall
const { getDifcFilteredEvents, generateDifcFilteredSection } = require("./gateway_difc_filtered.cjs");
const { formatCompactInteger } = require("./compact_numbers.cjs");
const { formatAIC } = require("./model_costs.cjs");
const { reduceModelNameToIdentifier } = require("./model_aliases.cjs");
const { getDetectionWarningMessage } = require("./messages_run_status.cjs");

/**
Expand Down Expand Up @@ -76,15 +77,16 @@ function getAmbientContextFromEnv() {
/**
* @param {string} label
* @param {number|undefined} value
* @param {string|undefined} [modelAlias]
* @returns {{ value: number|undefined, formatted: string|undefined, suffix: string }}
*/
function buildAICEntry(label, value) {
function buildAICEntry(label, value, modelAlias) {
const formatted = typeof value === "number" ? formatAIC(value) : undefined;
const labelPrefix = label ? `${label} ` : "";
const prefix = [label, modelAlias].filter(Boolean).join(" ");
return {
value,
formatted,
suffix: formatted ? ` · ${labelPrefix}${formatted} AIC` : "",
suffix: formatted ? ` · ${prefix ? `${prefix} ` : ""}${formatted} AIC` : "",
};
}

Expand All @@ -95,6 +97,7 @@ function buildAICEntry(label, value) {
* aiCredits: number|undefined,
* aiCreditsFormatted: string|undefined,
* aiCreditsSuffix: string,
* compressedModelName: string|undefined,
* agentAiCredits: number|undefined,
* agentAiCreditsFormatted: string|undefined,
* agentAiCreditsSuffix: string,
Expand All @@ -104,21 +107,23 @@ function buildAICEntry(label, value) {
* }}
*/
function getAICFromEnv() {
const compressedModelName = reduceModelNameToIdentifier(process.env.GH_AW_PRIMARY_MODEL || process.env.GH_AW_ENGINE_MODEL);
const totalAIC = parsePositiveAIC(process.env.GH_AW_AIC);
const explicitAgentAIC = parsePositiveAIC(process.env.GH_AW_AGENT_AIC);
const threatDetectionAIC = parsePositiveAIC(process.env.GH_AW_THREAT_DETECTION_AIC);
const agentAIC = typeof explicitAgentAIC === "number" ? explicitAgentAIC : totalAIC;
const agentEntry = buildAICEntry("", agentAIC);
const agentEntry = buildAICEntry("", agentAIC, compressedModelName);
const threatDetectionEntry = buildAICEntry("⌖", threatDetectionAIC);
const useBreakdown = threatDetectionEntry.suffix.length > 0;
const aiCredits = useBreakdown ? (agentAIC || 0) + (threatDetectionAIC || 0) : typeof totalAIC === "number" ? totalAIC : agentAIC;
const aiCreditsFormatted = typeof aiCredits === "number" ? formatAIC(aiCredits) : undefined;
const aiCreditsSuffix = useBreakdown ? `${agentEntry.suffix}${threatDetectionEntry.suffix}` : aiCreditsFormatted ? ` · ${aiCreditsFormatted} AIC` : "";
const aiCreditsSuffix = useBreakdown ? `${agentEntry.suffix}${threatDetectionEntry.suffix}` : buildAICEntry("", aiCredits, compressedModelName).suffix;

return {
aiCredits,
aiCreditsFormatted,
aiCreditsSuffix,
compressedModelName,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit. compressedModelName: string|undefined is now included in the getAICFromEnv() JSDoc @returns type, resolving the TS2353/TS2339 errors.

agentAiCredits: agentEntry.value,
agentAiCreditsFormatted: agentEntry.formatted,
agentAiCreditsSuffix: agentEntry.suffix,
Expand Down Expand Up @@ -158,6 +163,7 @@ function getFooterMessage(ctx) {
aiCredits: envAIC,
aiCreditsFormatted: envAICFormatted,
aiCreditsSuffix: envAICSuffix,
compressedModelName,
agentAiCredits,
agentAiCreditsFormatted,
agentAiCreditsSuffix,
Expand All @@ -181,7 +187,7 @@ function getFooterMessage(ctx) {
let aiCreditsSuffix = envAICSuffix;
if (hasExplicitContextAIC) {
aiCreditsFormatted = explicitContextAIC ? formatAIC(explicitContextAIC) : undefined;
aiCreditsSuffix = aiCreditsFormatted ? ` · ${aiCreditsFormatted} AIC` : "";
aiCreditsSuffix = buildAICEntry("", explicitContextAIC, compressedModelName).suffix;
}
const aiCreditsSuffixForTemplate = `${aiCreditsSuffix}${envAmbientContextSuffix}`;

Expand Down Expand Up @@ -377,6 +383,7 @@ function getFooterAgentFailureIssueMessage(ctx) {
aiCredits: envAIC,
aiCreditsFormatted: envAICFormatted,
aiCreditsSuffix: envAICSuffix,
compressedModelName,
agentAiCredits,
agentAiCreditsFormatted,
agentAiCreditsSuffix,
Expand All @@ -389,7 +396,7 @@ function getFooterAgentFailureIssueMessage(ctx) {
const explicitContextAIC = parseExplicitContextAIC(ctx.aiCredits);
const aiCredits = hasExplicitContextAIC ? explicitContextAIC : envAIC;
const aiCreditsFormatted = hasExplicitContextAIC ? (explicitContextAIC ? formatAIC(explicitContextAIC) : undefined) : envAICFormatted;
const aiCreditsSuffix = hasExplicitContextAIC ? (aiCreditsFormatted ? ` · ${aiCreditsFormatted} AIC` : "") : envAICSuffix;
const aiCreditsSuffix = hasExplicitContextAIC ? buildAICEntry("", explicitContextAIC, compressedModelName).suffix : envAICSuffix;
const aiCreditsSuffixForTemplate = `${aiCreditsSuffix}${ambientContextSuffix}`;

// Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url
Expand Down Expand Up @@ -452,6 +459,7 @@ function getFooterAgentFailureCommentMessage(ctx) {
aiCredits: envAIC,
aiCreditsFormatted: envAICFormatted,
aiCreditsSuffix: envAICSuffix,
compressedModelName,
agentAiCredits,
agentAiCreditsFormatted,
agentAiCreditsSuffix,
Expand All @@ -464,7 +472,7 @@ function getFooterAgentFailureCommentMessage(ctx) {
const explicitContextAIC = parseExplicitContextAIC(ctx.aiCredits);
const aiCredits = hasExplicitContextAIC ? explicitContextAIC : envAIC;
const aiCreditsFormatted = hasExplicitContextAIC ? (explicitContextAIC ? formatAIC(explicitContextAIC) : undefined) : envAICFormatted;
const aiCreditsSuffix = hasExplicitContextAIC ? (aiCreditsFormatted ? ` · ${aiCreditsFormatted} AIC` : "") : envAICSuffix;
const aiCreditsSuffix = hasExplicitContextAIC ? buildAICEntry("", explicitContextAIC, compressedModelName).suffix : envAICSuffix;
const aiCreditsSuffixForTemplate = `${aiCreditsSuffix}${ambientContextSuffix}`;

// Create context with both camelCase and snake_case keys, including computed history_link and agentic_workflow_url
Expand Down
5 changes: 5 additions & 0 deletions actions/setup/js/parse_token_usage.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ async function main() {
};
fs.writeFileSync(AGENT_USAGE_PATH, JSON.stringify(agentUsage) + "\n");

if (primaryModel) {
core.exportVariable("GH_AW_PRIMARY_MODEL", primaryModel);
core.setOutput("primary_model", primaryModel);
core.info(`Primary model: ${primaryModel}`);
}
if (summary.totalAIC > 0) {
const aic = summary.totalAIC.toFixed(3);
core.exportVariable("GH_AW_AIC", aic);
Expand Down
3 changes: 3 additions & 0 deletions actions/setup/js/parse_token_usage.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ describe("parse_token_usage", () => {
expect(typeof agentUsage.ai_credits).toBe("number");
// primary_model is the actual model from token-usage data (not a user alias)
expect(agentUsage.primary_model).toBe("claude-sonnet-4-6");
// GH_AW_PRIMARY_MODEL is exported so footer attribution can use the real model name
expect(mockCore.exportVariable).toHaveBeenCalledWith("GH_AW_PRIMARY_MODEL", "claude-sonnet-4-6");
expect(mockCore.setOutput).toHaveBeenCalledWith("primary_model", "claude-sonnet-4-6");
});

test("handles multiple model entries", async () => {
Expand Down
Loading