remove any default mapping for models
This commit is contained in:
+48
-17
@@ -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,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -1 +1 @@
|
|||||||
say hello
|
Use the debug_shell_command MCP tool to run `git status` and show me the output
|
||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user