fix: skip API key validation for free opencode models
free models (big-pickle, gpt-5-nano, etc.) define envVars: [] and isFree: true but validateAgentApiKey always required at least one provider key. now the validation is model-aware: free models bypass the check, keyed models validate their specific vars, and auto-select still requires at least one known key. closes #483 Made-with: Cursor
This commit is contained in:
committed by
pullfrog[bot]
parent
8a734c32f4
commit
30d68e53a7
@@ -0,0 +1,75 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { validateAgentApiKey } from "./apiKeys.ts";
|
||||
|
||||
const base = {
|
||||
agent: { name: "opentoad" },
|
||||
owner: "test-owner",
|
||||
name: "test-repo",
|
||||
};
|
||||
|
||||
const savedEnv = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
// strip all known provider keys so tests start clean
|
||||
for (const key of Object.keys(process.env)) {
|
||||
if (key.endsWith("_API_KEY")) delete process.env[key];
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = { ...savedEnv };
|
||||
});
|
||||
|
||||
describe("validateAgentApiKey", () => {
|
||||
describe("free model (no keys required)", () => {
|
||||
it("passes with zero env keys", () => {
|
||||
expect(() => validateAgentApiKey({ ...base, model: "opencode/big-pickle" })).not.toThrow();
|
||||
});
|
||||
|
||||
it("passes for other free opencode models", () => {
|
||||
for (const slug of [
|
||||
"opencode/gpt-5-nano",
|
||||
"opencode/mimo-v2-flash-free",
|
||||
"opencode/minimax-m2.5-free",
|
||||
"opencode/nemotron-3-super-free",
|
||||
]) {
|
||||
expect(() => validateAgentApiKey({ ...base, model: slug })).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("keyed model", () => {
|
||||
it("passes when the required key is present", () => {
|
||||
process.env.ANTHROPIC_API_KEY = "sk-test";
|
||||
expect(() => validateAgentApiKey({ ...base, model: "anthropic/claude-opus" })).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws when the required key is missing", () => {
|
||||
expect(() => validateAgentApiKey({ ...base, model: "anthropic/claude-opus" })).toThrow(
|
||||
"no API key found"
|
||||
);
|
||||
});
|
||||
|
||||
it("passes for opencode keyed model with OPENCODE_API_KEY", () => {
|
||||
process.env.OPENCODE_API_KEY = "sk-test";
|
||||
expect(() => validateAgentApiKey({ ...base, model: "opencode/claude-opus" })).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws for opencode keyed model without OPENCODE_API_KEY", () => {
|
||||
expect(() => validateAgentApiKey({ ...base, model: "opencode/claude-opus" })).toThrow(
|
||||
"no API key found"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("no model (auto-select)", () => {
|
||||
it("passes when any known provider key is present", () => {
|
||||
process.env.OPENAI_API_KEY = "sk-test";
|
||||
expect(() => validateAgentApiKey({ ...base, model: undefined })).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws when no provider keys are present", () => {
|
||||
expect(() => validateAgentApiKey({ ...base, model: undefined })).toThrow("no API key found");
|
||||
});
|
||||
});
|
||||
});
|
||||
+18
-4
@@ -1,4 +1,4 @@
|
||||
import { providers } from "../models.ts";
|
||||
import { getModelEnvVars, providers } from "../models.ts";
|
||||
import { getApiUrl } from "./apiUrl.ts";
|
||||
|
||||
const knownApiKeys: Set<string> = new Set(Object.values(providers).flatMap((p) => [...p.envVars]));
|
||||
@@ -23,15 +23,29 @@ to fix this, add the required secret to your GitHub repository:
|
||||
configure your model at ${settingsUrl}`;
|
||||
}
|
||||
|
||||
function hasEnvVar(name: string): boolean {
|
||||
const value = process.env[name];
|
||||
return typeof value === "string" && value.length > 0;
|
||||
}
|
||||
|
||||
export function validateAgentApiKey(params: {
|
||||
agent: { name: string };
|
||||
model: string | undefined;
|
||||
owner: string;
|
||||
name: string;
|
||||
}): void {
|
||||
const hasAnyKey = Object.entries(process.env).some(
|
||||
([key, value]) => value && typeof value === "string" && knownApiKeys.has(key)
|
||||
);
|
||||
// if a specific model is configured, only check that model's required env vars
|
||||
if (params.model) {
|
||||
const requiredVars = getModelEnvVars(params.model);
|
||||
// free models have no required env vars — skip validation entirely
|
||||
if (requiredVars.length === 0) return;
|
||||
if (requiredVars.some((v) => hasEnvVar(v))) return;
|
||||
|
||||
throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
|
||||
}
|
||||
|
||||
// no model configured — auto-select requires at least one known provider key
|
||||
const hasAnyKey = [...knownApiKeys].some((k) => hasEnvVar(k));
|
||||
if (!hasAnyKey) {
|
||||
throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user