d3b5340583
* fix: 9 unaddressed log-audit / run-audit findings Co-authored-by: Cursor <cursoragent@cursor.com> #815 entryPost stdlib-only imports; #823 MCP timeoutMs on checkout_pr/shell; #816 FREE_FALLBACK → opencode/big-pickle; #822 chunk GraphQL nodes ≤100; #817/#821 vip_audit 404 skip paths; #813 longer serializable retries; #818 run-context handler-entered log; #805 audit severity template. * fix: update footer test for big-pickle fallback slug Co-authored-by: Cursor <cursoragent@cursor.com> * fix: anneal round 1 — ghaCore getState casing, post-hook timeout Match @actions/core STATE_ key semantics (no uppercasing), cap postApiFetch at 30s, trim serializable retries to stay under GitHub's 10s webhook window, log Clerk failures in getUserTokenByGithubLogin. Co-authored-by: Cursor <cursoragent@cursor.com> * revert: drop run-context handler log (#818 deferred) The #692 client-side fix is already on main; residual SyntaxError hits are old action pins. Per-request log added noise without fixing anything. Co-authored-by: Cursor <cursoragent@cursor.com> * document per-issue Closes syntax for audit PRs GitHub only auto-closes the first issue when numbers are comma-separated; /audits and AGENTS.md now require Closes before each issue number. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: anneal round 2 — outreach privacy, alert resilience, vertex cleanup Filter private repos from VIP authority output, harden console alert lines against DB failures, drop spoofable changesets body check, and unset GOOGLE_APPLICATION_CREDENTIALS after vertex credential cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: drop codexHome re-export of detectCodexRefresh Import detectCodexRefresh directly from codexRefreshDetect.ts everywhere; rename the unit test file to match. codexHome.ts stays install-only. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: drop deprecated minimax-m2.5-free; add paid minimax-m2.5 Remove the deprecated free MiniMax promo from the catalog, docs, and tests. BYOK fallback and picker copy stay on opencode/big-pickle. Add opencode/minimax-m2.5 and openrouter/minimax-m2.5 for Zen BYOK and Router. Pin #816 regressions with freeFallbackCatalog and runErrorRenderer unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: hidden minimax-m2.5-free fallback for stored slugs Re-add opencode/minimax-m2.5-free as a hidden fallback alias to big-pickle so repos with the legacy slug still resolve as free. Drop live Zen API experiment tests in freeFallbackCatalog.test.ts. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { resolveCliModel } from "../models.ts";
|
|
import { FREE_FALLBACK_SLUG, selectFallbackModelIfNeeded } from "./byokFallback.ts";
|
|
|
|
describe("FREE_FALLBACK_SLUG", () => {
|
|
it("resolves in the curated catalog", () => {
|
|
expect(resolveCliModel(FREE_FALLBACK_SLUG)).toBe("opencode/big-pickle");
|
|
});
|
|
|
|
it("is opencode/big-pickle", () => {
|
|
expect(FREE_FALLBACK_SLUG).toBe("opencode/big-pickle");
|
|
});
|
|
});
|
|
|
|
describe("selectFallbackModelIfNeeded", () => {
|
|
const originalEnv = { ...process.env };
|
|
const KEYS = [
|
|
"ANTHROPIC_API_KEY",
|
|
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
"OPENAI_API_KEY",
|
|
"OPENROUTER_API_KEY",
|
|
"GEMINI_API_KEY",
|
|
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
"XAI_API_KEY",
|
|
"DEEPSEEK_API_KEY",
|
|
"MOONSHOT_API_KEY",
|
|
"OPENCODE_API_KEY",
|
|
] as const;
|
|
|
|
beforeEach(() => {
|
|
for (const k of KEYS) delete process.env[k];
|
|
});
|
|
afterEach(() => {
|
|
for (const k of KEYS) {
|
|
if (originalEnv[k] === undefined) delete process.env[k];
|
|
else process.env[k] = originalEnv[k];
|
|
}
|
|
});
|
|
|
|
it("falls back when the resolved model needs a key that isn't set", () => {
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: "anthropic/claude-opus-4-7",
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result).toEqual({
|
|
fallback: true,
|
|
from: "anthropic/claude-opus-4-7",
|
|
to: FREE_FALLBACK_SLUG,
|
|
});
|
|
});
|
|
|
|
it("does not fall back when the resolved model's key IS set", () => {
|
|
process.env.ANTHROPIC_API_KEY = "sk-test";
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: "anthropic/claude-opus-4-7",
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("does not fall back on Router runs (proxyModel set)", () => {
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: undefined,
|
|
proxyModel: "openrouter/anthropic/claude-opus-4.7",
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("does not fall back when no model is resolved (auto-select path)", () => {
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: undefined,
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("does not fall back when the resolved model is itself the free fallback", () => {
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: FREE_FALLBACK_SLUG,
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("does not fall back for Bedrock routing (raw model ID has no slash)", () => {
|
|
// resolveModel({slug:"bedrock/byok"}) returns the raw BEDROCK_MODEL_ID
|
|
// value (e.g. "us.anthropic.claude-opus-4-7"), which has no `/`. without
|
|
// a guard, hasProviderKey → parseModel would throw and crash the action
|
|
// before validateBedrockSetup can surface its tailored error.
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: "us.anthropic.claude-opus-4-7",
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("does not fall back for free models that need no key", () => {
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: "opencode/mimo-v2-pro-free",
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("does not fall back when stored minimax-m2.5-free resolves to big-pickle", () => {
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: resolveCliModel("opencode/minimax-m2.5-free"),
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(false);
|
|
});
|
|
|
|
it("treats empty-string env vars as missing (matches GH Actions secret-not-found behavior)", () => {
|
|
process.env.ANTHROPIC_API_KEY = "";
|
|
const result = selectFallbackModelIfNeeded({
|
|
resolvedModel: "anthropic/claude-opus-4-7",
|
|
proxyModel: undefined,
|
|
});
|
|
expect(result.fallback).toBe(true);
|
|
});
|
|
});
|