remove any default mapping for models

This commit is contained in:
Shawn Morreau
2025-12-09 11:53:19 -05:00
parent 06fdedb8c5
commit bbda005ee9
5 changed files with 96 additions and 25 deletions
+48 -17
View File
@@ -6,7 +6,6 @@ import { addInstructions } from "./instructions.ts";
import { import {
agent, agent,
type ConfigureMcpServersParams, type ConfigureMcpServersParams,
createAgentEnv,
installFromNpmTarball, installFromNpmTarball,
setupProcessAgentEnv, setupProcessAgentEnv,
} from "./shared.ts"; } from "./shared.ts";
@@ -211,34 +210,54 @@ export const opencode = agent({
installDependencies: true, installDependencies: true,
}); });
}, },
run: async ({ payload, apiKey, mcpServers, cliPath }) => { run: async ({ payload, apiKey, apiKeys, mcpServers, cliPath }) => {
// 1. configure home/config directory // 1. configure home/config directory
const tempHome = process.env.PULLFROG_TEMP_DIR!; const tempHome = process.env.PULLFROG_TEMP_DIR!;
const configDir = join(tempHome, ".config", "opencode"); const configDir = join(tempHome, ".config", "opencode");
mkdirSync(configDir, { recursive: true }); mkdirSync(configDir, { recursive: true });
// 2. initialize MCP servers and sandbox if (!apiKey) {
throw new Error("at least one API key is required for opencode agent");
}
// 2. build env vars from all available API keys (map to OpenCode's expected env var names)
const apiKeyEnv: Record<string, string> = {};
const envVarMap: Record<string, string> = {
anthropic_api_key: "ANTHROPIC_API_KEY",
openai_api_key: "OPENAI_API_KEY",
google_api_key: "GOOGLE_GENERATIVE_AI_API_KEY",
gemini_api_key: "GOOGLE_GENERATIVE_AI_API_KEY",
};
for (const [inputKey, value] of Object.entries(apiKeys || {})) {
const envVarName = envVarMap[inputKey] || inputKey.toUpperCase();
apiKeyEnv[envVarName] = value;
}
// 3. initialize MCP servers and sandbox
configureOpenCodeMcpServers({ mcpServers }); configureOpenCodeMcpServers({ mcpServers });
configureOpenCodeSandbox({ sandbox: payload.sandbox ?? false }); configureOpenCodeSandbox({ sandbox: payload.sandbox ?? false });
if (!apiKey) { // 4. prepare prompt and args
throw new Error("anthropic_api_key is required for opencode agent");
}
// 3. prepare prompt and args
const prompt = addInstructions(payload); const prompt = addInstructions(payload);
const args = ["run", "--format", "json", "-m", "anthropic/claude-sonnet-4-20250514", prompt]; const args = ["run", "--format", "json", prompt];
if (payload.sandbox) { if (payload.sandbox) {
log.info("🔒 sandbox mode enabled: restricting to read-only operations"); log.info("🔒 sandbox mode enabled: restricting to read-only operations");
} }
// 4. set up environment // 6. set up environment
const packageDir = join(cliPath, "..", ".."); const packageDir = join(cliPath, "..", "..");
setupProcessAgentEnv({ ANTHROPIC_API_KEY: apiKey, HOME: tempHome }); setupProcessAgentEnv({ HOME: tempHome, ...apiKeyEnv });
const env = createAgentEnv({ ANTHROPIC_API_KEY: apiKey, HOME: tempHome }); // don't pass GITHUB_TOKEN to opencode - it may try to use it incorrectly
const env: Record<string, string> = {
PATH: process.env.PATH || "",
HOME: tempHome,
LOG_LEVEL: process.env.LOG_LEVEL || "",
NODE_ENV: process.env.NODE_ENV || "",
...apiKeyEnv,
};
// 5. spawn and stream JSON output // 7. spawn and stream JSON output
let output = ""; let output = "";
const result = await spawn({ const result = await spawn({
cmd: cliPath, cmd: cliPath,
@@ -278,7 +297,7 @@ export const opencode = agent({
}, },
}); });
// 6. log tokens if they weren't logged yet (fallback if result event wasn't emitted) // 8. log tokens if they weren't logged yet (fallback if result event wasn't emitted)
if (!tokensLogged && (accumulatedTokens.input > 0 || accumulatedTokens.output > 0)) { if (!tokensLogged && (accumulatedTokens.input > 0 || accumulatedTokens.output > 0)) {
const totalTokens = accumulatedTokens.input + accumulatedTokens.output; const totalTokens = accumulatedTokens.input + accumulatedTokens.output;
await log.summaryTable([ await log.summaryTable([
@@ -291,11 +310,23 @@ export const opencode = agent({
]); ]);
} }
// 7. return result // 9. return result
if (result.exitCode !== 0) {
const errorMessage =
result.stderr || result.stdout || "Unknown error - no output from OpenCode CLI";
log.error(`OpenCode CLI exited with code ${result.exitCode}: ${errorMessage}`);
log.debug(`OpenCode stdout: ${result.stdout?.substring(0, 500)}`);
log.debug(`OpenCode stderr: ${result.stderr?.substring(0, 500)}`);
return {
success: false,
output: finalOutput || output,
error: errorMessage,
};
}
return { return {
success: result.exitCode === 0, success: true,
output: finalOutput || output, output: finalOutput || output,
error: result.exitCode !== 0 ? result.stderr : undefined,
}; };
}, },
}); });
+1
View File
@@ -25,6 +25,7 @@ export interface AgentResult {
*/ */
export interface AgentConfig { export interface AgentConfig {
apiKey: string; apiKey: string;
apiKeys?: Record<string, string>; // all available keys for this agent
payload: Payload; payload: Payload;
mcpServers: Record<string, McpHttpServerConfig>; mcpServers: Record<string, McpHttpServerConfig>;
cliPath: string; cliPath: string;
+1 -1
View File
@@ -40,7 +40,7 @@ export const agentsManifest = {
}, },
opencode: { opencode: {
displayName: "OpenCode", displayName: "OpenCode",
apiKeyNames: ["anthropic_api_key"], apiKeyNames: ["anthropic_api_key", "openai_api_key", "google_api_key", "gemini_api_key"],
url: "https://opencode.ai", url: "https://opencode.ai",
}, },
} as const satisfies Record<string, AgentManifest>; } as const satisfies Record<string, AgentManifest>;
+1 -1
View File
@@ -1 +1 @@
say hello Use the debug_shell_command MCP tool to run `git status` and show me the output
+45 -6
View File
@@ -185,13 +185,17 @@ interface MainContext {
mcpServers: ReturnType<typeof createMcpConfigs>; mcpServers: ReturnType<typeof createMcpConfigs>;
cliPath: string; cliPath: string;
apiKey: string; apiKey: string;
apiKeys: Record<string, string>;
} }
async function initializeContext( async function initializeContext(
inputs: Inputs, inputs: Inputs,
payload: Payload payload: Payload
): Promise< ): Promise<
Omit<MainContext, "mcpServerUrl" | "mcpServerClose" | "mcpServers" | "cliPath" | "apiKey"> Omit<
MainContext,
"mcpServerUrl" | "mcpServerClose" | "mcpServers" | "cliPath" | "apiKey" | "apiKeys"
>
> { > {
log.info(`🐸 Running pullfrog/action@${packageJson.version}...`); log.info(`🐸 Running pullfrog/action@${packageJson.version}...`);
Inputs.assert(inputs); Inputs.assert(inputs);
@@ -240,8 +244,31 @@ async function resolveAgent(
if (!agent) { if (!agent) {
throw new Error(`invalid agent name: ${agentName}`); throw new Error(`invalid agent name: ${agentName}`);
} }
log.info(`Selected configured agent: ${agentName}`);
return { agentName, agent }; // if explicitly configured (via override or payload), respect it even without matching keys
// this allows users to force an agent selection (will fail later with clear error if no keys)
const isExplicitOverride = agentOverride !== undefined || payload.agent !== null;
if (isExplicitOverride) {
log.info(`Selected configured agent: ${agentName}`);
return { agentName, agent };
}
// for repo-level defaults, check if agent has matching keys before selecting
const hasMatchingKey = agent.apiKeyNames.some((inputKey) => inputs[inputKey]);
if (!hasMatchingKey) {
log.warning(
`Repo default agent ${agentName} has no matching API keys. Available agents: ${
getAvailableAgents(inputs)
.map((a) => a.name)
.join(", ") || "none"
}`
);
// fall through to auto-selection for repo defaults
} else {
log.info(`Selected configured agent: ${agentName}`);
return { agentName, agent };
}
} }
const availableAgents = getAvailableAgents(inputs); const availableAgents = getAvailableAgents(inputs);
@@ -330,8 +357,16 @@ async function installAgentCli(ctx: MainContext): Promise<void> {
} }
async function validateApiKey(ctx: MainContext): Promise<void> { async function validateApiKey(ctx: MainContext): Promise<void> {
const matchingInputKey = ctx.agent.apiKeyNames.find((inputKey) => ctx.inputs[inputKey]); // collect all matching API keys for this agent
if (!matchingInputKey) { const apiKeys: Record<string, string> = {};
for (const inputKey of ctx.agent.apiKeyNames) {
const value = ctx.inputs[inputKey];
if (value) {
apiKeys[inputKey] = value;
}
}
if (Object.keys(apiKeys).length === 0) {
await throwMissingApiKeyError({ await throwMissingApiKeyError({
agent: ctx.agent, agent: ctx.agent,
repoContext: ctx.repoContext, repoContext: ctx.repoContext,
@@ -339,7 +374,10 @@ async function validateApiKey(ctx: MainContext): Promise<void> {
// unreachable - throwMissingApiKeyError always throws // unreachable - throwMissingApiKeyError always throws
return; return;
} }
ctx.apiKey = ctx.inputs[matchingInputKey]!;
// keep apiKey for backward compat (first available key)
ctx.apiKey = Object.values(apiKeys)[0];
ctx.apiKeys = apiKeys;
} }
async function runAgent(ctx: MainContext): Promise<AgentResult> { async function runAgent(ctx: MainContext): Promise<AgentResult> {
@@ -354,6 +392,7 @@ async function runAgent(ctx: MainContext): Promise<AgentResult> {
payload: ctx.payload, payload: ctx.payload,
mcpServers: ctx.mcpServers, mcpServers: ctx.mcpServers,
apiKey: ctx.apiKey, apiKey: ctx.apiKey,
apiKeys: ctx.apiKeys,
cliPath: ctx.cliPath, cliPath: ctx.cliPath,
}); });
} }