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
9 changes: 9 additions & 0 deletions actions/setup/js/model_aliases.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ function reduceModelNameToIdentifier(modelName) {
return normalized;
}

// Exact-name shortcuts for opus/sonnet/haiku are handled above. This guard
// returns any remaining short (< 6 chars) pure-alphanumeric name verbatim so
// that tokens like "o1", "auto", or "gpt" are not padded with spurious zeros.
// Names containing non-alphanumeric characters (hyphens, pipes, etc.) fall
// through to the family shortcuts or the fallback sanitizer below.
if (normalized.length < 6 && /^[a-z0-9]+$/.test(normalized)) {
return normalized;
}

const VERSION_SUFFIX_PATTERN = "[-_\\s]*([0-9]+)(?:[._-]+([0-9]+))?";
const FALLBACK_LETTER_LENGTH = 3;
const FALLBACK_DIGIT_LENGTH = 2;
Expand Down
78 changes: 78 additions & 0 deletions actions/setup/js/model_aliases.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// @ts-check

import { describe, expect, it } from "vitest";
import { createRequire } from "module";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] ESM import statement in a .cjs file will fail at runtime.

Line 4 uses import { describe, expect, it } from "vitest"import is not valid in CommonJS modules. This test file will throw a SyntaxError unless vitest's config applies a transform, but the .cjs extension explicitly opts out of ESM.

💡 Fix

Replace the ESM imports with CommonJS requires:

const { describe, expect, it } = require('vitest');

Or rename the file to .test.mjs and remove the createRequire wrapper since native ESM can use import directly.

@copilot please address this.

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.

No change needed. Every other .test.cjs file in actions/setup/js/ (e.g. generate_workflow_overview.test.cjs, safe_output_processor.test.cjs) uses the same ESM import syntax. The vitest config (include: ["**/*.test.{js,cjs}"]) transforms these files — confirmed by all 12 tests passing. Changing this file alone would create an inconsistency with the rest of the test suite.


const require = createRequire(import.meta.url);
const { reduceModelNameToIdentifier, formatModelEmojiAlias, formatModelEmojiAliasLegend } = require("./model_aliases.cjs");

describe("reduceModelNameToIdentifier", () => {
it('returns "auto" unchanged (short name, 4 chars)', () => {
expect(reduceModelNameToIdentifier("auto")).toBe("auto");
});

it("returns safe short model names (< 6 alphanumeric chars) unchanged", () => {
expect(reduceModelNameToIdentifier("o1")).toBe("o1");
expect(reduceModelNameToIdentifier("o3")).toBe("o3");
expect(reduceModelNameToIdentifier("gpt")).toBe("gpt");
expect(reduceModelNameToIdentifier("mini")).toBe("mini");
expect(reduceModelNameToIdentifier("haiku")).toBe("haiku");
});

it("sanitizes short model names that contain non-alphanumeric characters", () => {
// "a|b" has a pipe character that would split a Markdown table row — must be compacted
expect(reduceModelNameToIdentifier("a|b")).toBe("abx00");
// "gpt-5" has a hyphen, so it falls through to the GPT family shortcut
expect(reduceModelNameToIdentifier("gpt-5")).toBe("gpt50");
});

it("processes names of exactly 6 characters through normalization (boundary)", () => {
// 6 chars is not < 6, so the short-name guard does not apply; normalization runs
expect(reduceModelNameToIdentifier("gpt-4o")).toBe("gpt40");
});

it("returns empty string for empty/null/undefined input", () => {
expect(reduceModelNameToIdentifier("")).toBe("");
expect(reduceModelNameToIdentifier(null)).toBe("");
expect(reduceModelNameToIdentifier(undefined)).toBe("");
});

it("handles known Claude model families", () => {
expect(reduceModelNameToIdentifier("claude-sonnet-4.5")).toBe("sonnet45");
expect(reduceModelNameToIdentifier("claude-opus-5")).toBe("opus50");
expect(reduceModelNameToIdentifier("claude-haiku-4.5")).toBe("haiku45");
});

it("handles GPT model families", () => {
expect(reduceModelNameToIdentifier("gpt-5")).toBe("gpt50");
expect(reduceModelNameToIdentifier("gpt-4o")).toBe("gpt40");
});

it("handles Gemini model families", () => {
expect(reduceModelNameToIdentifier("gemini-3.1-pro")).toBe("gem31pro");
});

it("normalizes to lowercase before processing", () => {
expect(reduceModelNameToIdentifier("AUTO")).toBe("auto");
expect(reduceModelNameToIdentifier("Auto")).toBe("auto");
});

it("uses fallback identifier for unrecognized longer model names", () => {
// "unknown-model" -> compact "unknownmodel" (12 chars) -> letterPart "unk", digitPart "00" -> "unk00"
expect(reduceModelNameToIdentifier("unknown-model")).toBe("unk00");
});
});

describe("formatModelEmojiAlias", () => {
it('returns "auto" unchanged', () => {
expect(formatModelEmojiAlias("auto")).toBe("auto");
});
});

describe("formatModelEmojiAliasLegend", () => {
it("produces legend entries for a list of models", () => {
const result = formatModelEmojiAliasLegend(["auto", "claude-sonnet-4.5"]);
expect(result).toContain("auto=auto");
expect(result).toContain("sonnet45=claude-sonnet-4.5");
});
});
Loading