fix(ai): preserve GitHub Copilot long-context pricing tiers, closes #6668

This commit is contained in:
David Brailovsky
2026-07-17 12:38:12 +02:00
parent a9f6a3159a
commit ce48d9b4ef
4 changed files with 222 additions and 8 deletions
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env node
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
function printUsage() {
console.log(`Usage: node scripts/diff-model-catalog.mjs [provider ...]
Generates the model catalog at HEAD and in the current worktree, then shows
JSON differences. If providers are omitted, all providers are compared.
Examples:
node scripts/diff-model-catalog.mjs github-copilot
npm run diff:model-catalog -- github-copilot
`);
}
function run(command, args, options = {}) {
const result = spawnSync(command, args, {
cwd: options.cwd,
encoding: "utf8",
stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
});
if (result.status !== 0) {
const details = [result.stdout, result.stderr].filter(Boolean).join("\n");
throw new Error(`Command failed: ${[command, ...args].join(" ")}\n${details}`);
}
return result.stdout ?? "";
}
function runDiff(args, cwd) {
return spawnSync("git", args, {
cwd,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
}
const args = process.argv.slice(2);
if (args.includes("--help")) {
printUsage();
process.exit(0);
}
if (args.some((arg) => arg.startsWith("-"))) {
printUsage();
process.exit(1);
}
const repoRoot = run("git", ["rev-parse", "--show-toplevel"], { capture: true }).trim();
const temporaryRoot = mkdtempSync(join(tmpdir(), "pi-model-catalog-diff-"));
const baselineWorktree = join(temporaryRoot, "baseline-worktree");
const baselineOutput = join(temporaryRoot, "before");
const currentOutput = join(temporaryRoot, "after");
let worktreeAdded = false;
function generateCatalog(cwd, outputDir, pretty = false) {
const args = ["packages/ai/scripts/generate-models.ts", "--strict", "--json-only", "--json-output", outputDir];
if (pretty) args.push("--pretty");
run(process.execPath, args, { cwd, capture: true });
}
function formatProviderCatalogs(outputDir) {
const providersDir = join(outputDir, "providers");
for (const entry of readdirSync(providersDir)) {
if (!entry.endsWith(".json")) continue;
const path = join(providersDir, entry);
const value = JSON.parse(readFileSync(path, "utf8"));
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
}
}
function readProviderCatalog(outputDir, provider) {
const path = join(outputDir, "providers", `${provider}.json`);
return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : undefined;
}
function writeModelSnapshot(path, model) {
writeFileSync(path, model === undefined ? "" : `${JSON.stringify(model, null, 2)}\n`);
}
function writeChangedLines(output) {
const changedLines = output.split("\n").filter((line) => {
const withoutColor = line.replace(/\u001b\[[0-9;]*m/g, "");
return (
(withoutColor.startsWith("+") && !withoutColor.startsWith("+++")) ||
(withoutColor.startsWith("-") && !withoutColor.startsWith("---"))
);
});
if (changedLines.length > 0) process.stdout.write(`${changedLines.join("\n")}\n`);
}
try {
run("git", ["worktree", "add", "--detach", baselineWorktree, "HEAD"], { cwd: repoRoot });
worktreeAdded = true;
const nodeModules = join(repoRoot, "node_modules");
if (existsSync(nodeModules)) {
symlinkSync(nodeModules, join(baselineWorktree, "node_modules"), process.platform === "win32" ? "junction" : "dir");
}
console.log("Generating catalog from HEAD...");
generateCatalog(baselineWorktree, baselineOutput);
formatProviderCatalogs(baselineOutput);
console.log("Generating catalog from the current worktree...");
generateCatalog(repoRoot, currentOutput, true);
formatProviderCatalogs(currentOutput);
const beforeProviders = JSON.parse(readFileSync(join(baselineOutput, "providers.json"), "utf8"));
const afterProviders = JSON.parse(readFileSync(join(currentOutput, "providers.json"), "utf8"));
const providers = args.length > 0 ? args : [...new Set([...beforeProviders, ...afterProviders])].sort();
const beforeModelPath = "before-model.json";
const afterModelPath = "after-model.json";
const changedModels = [];
let differences = 0;
for (const provider of providers) {
const beforeModels = readProviderCatalog(baselineOutput, provider);
const afterModels = readProviderCatalog(currentOutput, provider);
if (beforeModels === undefined && afterModels === undefined) {
throw new Error(`Unknown provider: ${provider}`);
}
const modelIds = [...new Set([...Object.keys(beforeModels ?? {}), ...Object.keys(afterModels ?? {})])].sort();
for (const modelId of modelIds) {
const beforeModel = beforeModels?.[modelId];
const afterModel = afterModels?.[modelId];
if (JSON.stringify(beforeModel) === JSON.stringify(afterModel)) continue;
writeModelSnapshot(join(temporaryRoot, beforeModelPath), beforeModel);
writeModelSnapshot(join(temporaryRoot, afterModelPath), afterModel);
const result = runDiff(
[
"diff",
"--no-index",
"--no-ext-diff",
"--color=always",
"--unified=0",
"--",
beforeModelPath,
afterModelPath,
],
temporaryRoot,
);
if (result.status === 1) {
const changedModel = `${provider}/${modelId}`;
console.log(`\n${changedModel}`);
writeChangedLines(result.stdout);
changedModels.push(changedModel);
differences++;
} else if (result.status !== 0) {
throw new Error(`Could not compare ${provider}/${modelId}: ${result.stderr || result.stdout}`);
}
}
}
if (differences === 0) {
console.log(`No model catalog changes${args.length === 1 ? ` for ${args[0]}` : ""}.`);
} else {
console.log(`\n${differences} model catalog entr${differences === 1 ? "y" : "ies"} changed.`);
for (const changedModel of changedModels) {
console.log(`- ${changedModel}`);
}
}
} finally {
if (worktreeAdded) {
try {
run("git", ["worktree", "remove", "--force", baselineWorktree], { cwd: repoRoot });
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
}
}
rmSync(temporaryRoot, { recursive: true, force: true });
}