init
This commit is contained in:
committed by
Colin McDonnell
parent
2f3ae3e481
commit
c335032c37
+28
-8
@@ -1,9 +1,18 @@
|
||||
import { type Options, query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { Effort } from "../external.ts";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { addInstructions } from "./instructions.ts";
|
||||
import { agent, createAgentEnv, installFromNpmTarball } from "./shared.ts";
|
||||
|
||||
// model configuration based on effort level
|
||||
// uses model family aliases that auto-resolve to latest version
|
||||
const claudeModels: Record<Effort, { model: string; thinking: boolean }> = {
|
||||
nothink: { model: "claude-haiku-4-5", thinking: false },
|
||||
think: { model: "claude-sonnet-4-5", thinking: true },
|
||||
max: { model: "claude-opus-4-5", thinking: true },
|
||||
} as const;
|
||||
|
||||
export const claude = agent({
|
||||
name: "claude",
|
||||
install: async () => {
|
||||
@@ -14,13 +23,17 @@ export const claude = agent({
|
||||
executablePath: "cli.js",
|
||||
});
|
||||
},
|
||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo }) => {
|
||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort }) => {
|
||||
// Ensure API key is NOT in process.env - only pass via SDK's env option
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
const prompt = addInstructions({ payload, repo });
|
||||
log.group("Full prompt", () => log.info(prompt));
|
||||
|
||||
// get model configuration based on effort
|
||||
const modelConfig = claudeModels[effort];
|
||||
log.info(`Using model: ${modelConfig.model}, thinking: ${modelConfig.thinking}`);
|
||||
|
||||
// SECURITY: For PUBLIC repos, Claude Code spawns subprocesses with full process.env, leaking API keys.
|
||||
// disable native Bash; agents use MCP bash tool which filters secrets.
|
||||
// for private repos, native Bash is allowed since secrets are less exposed.
|
||||
@@ -46,15 +59,22 @@ export const claude = agent({
|
||||
|
||||
// Pass secrets via SDK's env option only (not process.env)
|
||||
// This ensures secrets are only available to Claude Code subprocess, not user code
|
||||
const queryOptions: Options = {
|
||||
...sandboxOptions,
|
||||
mcpServers,
|
||||
model: modelConfig.model,
|
||||
pathToClaudeCodeExecutable: cliPath,
|
||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }),
|
||||
};
|
||||
// only set maxThinkingTokens when we want to disable thinking (0)
|
||||
// omit the property to use default when thinking is enabled
|
||||
if (!modelConfig.thinking) {
|
||||
queryOptions.maxThinkingTokens = 0;
|
||||
}
|
||||
|
||||
const queryInstance = query({
|
||||
prompt,
|
||||
options: {
|
||||
...sandboxOptions,
|
||||
mcpServers,
|
||||
// model: "claude-opus-4-5",
|
||||
pathToClaudeCodeExecutable: cliPath,
|
||||
env: createAgentEnv({ ANTHROPIC_API_KEY: apiKey }),
|
||||
},
|
||||
options: queryOptions,
|
||||
});
|
||||
|
||||
// Stream the results
|
||||
|
||||
+16
-1
@@ -2,10 +2,19 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { McpHttpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import { Codex, type CodexOptions, type ThreadEvent } from "@openai/codex-sdk";
|
||||
import type { Effort } from "../external.ts";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { addInstructions } from "./instructions.ts";
|
||||
import { agent, installFromNpmTarball, setupProcessAgentEnv } from "./shared.ts";
|
||||
|
||||
// model configuration based on effort level
|
||||
// uses model family aliases that auto-resolve to latest version
|
||||
const codexModels: Record<Effort, string> = {
|
||||
nothink: "gpt-4o-mini",
|
||||
think: "gpt-5.2-instant",
|
||||
max: "gpt-5.2-thinking",
|
||||
} as const;
|
||||
|
||||
interface WriteCodexConfigParams {
|
||||
tempHome: string;
|
||||
mcpServers: Record<string, McpHttpServerConfig>;
|
||||
@@ -60,7 +69,7 @@ export const codex = agent({
|
||||
executablePath: "bin/codex.js",
|
||||
});
|
||||
},
|
||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo }) => {
|
||||
run: async ({ payload, mcpServers, apiKey, cliPath, repo, effort }) => {
|
||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||
|
||||
// create config directory for codex before setting HOME
|
||||
@@ -79,6 +88,10 @@ export const codex = agent({
|
||||
CODEX_HOME: codexDir, // point Codex to our config directory
|
||||
});
|
||||
|
||||
// get model based on effort level
|
||||
const model = codexModels[effort];
|
||||
log.info(`Using model: ${model}`);
|
||||
|
||||
// Configure Codex
|
||||
const codexOptions: CodexOptions = {
|
||||
apiKey,
|
||||
@@ -93,11 +106,13 @@ export const codex = agent({
|
||||
const thread = codex.startThread(
|
||||
payload.sandbox
|
||||
? {
|
||||
model,
|
||||
approvalPolicy: "never",
|
||||
sandboxMode: "read-only",
|
||||
networkAccessEnabled: false,
|
||||
}
|
||||
: {
|
||||
model,
|
||||
approvalPolicy: "never",
|
||||
// use danger-full-access to allow git operations (workspace-write blocks .git directory writes)
|
||||
sandboxMode: "danger-full-access",
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@ export const cursor = agent({
|
||||
executableName: "cursor-agent",
|
||||
});
|
||||
},
|
||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo }) => {
|
||||
run: async ({ payload, apiKey, cliPath, mcpServers, repo, effort: _effort }) => {
|
||||
configureCursorMcpServers({ mcpServers, cliPath });
|
||||
configureCursorSandbox({ sandbox: payload.sandbox ?? false, isPublicRepo: repo.isPublic });
|
||||
|
||||
|
||||
+16
-2
@@ -11,6 +11,14 @@ import {
|
||||
installFromGithub,
|
||||
} from "./shared.ts";
|
||||
|
||||
// model configuration based on effort level
|
||||
// uses model family aliases that auto-resolve to latest version
|
||||
const geminiModels = {
|
||||
nothink: "gemini-2.5-pro",
|
||||
think: "gemini-3-pro",
|
||||
max: "gemini-3-pro",
|
||||
} as const;
|
||||
|
||||
// gemini cli event types inferred from stream-json output (NDJSON format)
|
||||
interface GeminiInitEvent {
|
||||
type: "init";
|
||||
@@ -156,13 +164,17 @@ export const gemini = agent({
|
||||
...(githubInstallationToken && { githubInstallationToken }),
|
||||
});
|
||||
},
|
||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo }) => {
|
||||
run: async ({ payload, apiKey, mcpServers, cliPath, repo, effort }) => {
|
||||
configureGeminiMcpServers({ mcpServers, isPublicRepo: repo.isPublic });
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error("google_api_key or gemini_api_key is required for gemini agent");
|
||||
}
|
||||
|
||||
// get model based on effort level
|
||||
const model = geminiModels[effort];
|
||||
log.info(`Using model: ${model}`);
|
||||
|
||||
const sessionPrompt = addInstructions({ payload, repo });
|
||||
log.group("Full prompt", () => log.info(sessionPrompt));
|
||||
|
||||
@@ -172,6 +184,8 @@ export const gemini = agent({
|
||||
if (payload.sandbox) {
|
||||
// sandbox mode: read-only tools only
|
||||
args = [
|
||||
"--model",
|
||||
model,
|
||||
"--allowed-tools",
|
||||
"read_file,list_directory,search_file_content,glob,save_memory,write_todos",
|
||||
"--allowed-mcp-server-names",
|
||||
@@ -183,7 +197,7 @@ export const gemini = agent({
|
||||
} else {
|
||||
// normal mode: --yolo for auto-approval
|
||||
// for public repos, shell is excluded via settings.json excludeTools
|
||||
args = ["--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||
args = ["--model", model, "--yolo", "--output-format=stream-json", "-p", sessionPrompt];
|
||||
if (repo.isPublic) {
|
||||
log.info("🔒 public repo: native shell disabled via excludeTools, using MCP bash");
|
||||
}
|
||||
|
||||
+11
-4
@@ -22,7 +22,15 @@ export const opencode = agent({
|
||||
installDependencies: true,
|
||||
});
|
||||
},
|
||||
run: async ({ payload, apiKey: _apiKey, apiKeys, mcpServers, cliPath, repo }) => {
|
||||
run: async ({
|
||||
payload,
|
||||
apiKey: _apiKey,
|
||||
apiKeys,
|
||||
mcpServers,
|
||||
cliPath,
|
||||
repo,
|
||||
effort: _effort,
|
||||
}) => {
|
||||
// 1. configure home/config directory
|
||||
const tempHome = process.env.PULLFROG_TEMP_DIR!;
|
||||
const configDir = join(tempHome, ".config", "opencode");
|
||||
@@ -60,10 +68,9 @@ export const opencode = agent({
|
||||
|
||||
// add API keys from apiKeys object
|
||||
for (const [key, value] of Object.entries(apiKeys || {})) {
|
||||
const upperKey = key.toUpperCase();
|
||||
env[upperKey] = value;
|
||||
env[key] = value;
|
||||
// also set GOOGLE_GENERATIVE_AI_API_KEY for Google provider compatibility
|
||||
if (upperKey === "GEMINI_API_KEY") {
|
||||
if (key === "GEMINI_API_KEY") {
|
||||
env.GOOGLE_GENERATIVE_AI_API_KEY = value;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -6,7 +6,13 @@ import { join } from "node:path";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import type { McpHttpServerConfig } from "@anthropic-ai/claude-agent-sdk";
|
||||
import type { show } from "@ark/util";
|
||||
import { type AgentManifest, type AgentName, agentsManifest, type Payload } from "../external.ts";
|
||||
import {
|
||||
type AgentManifest,
|
||||
type AgentName,
|
||||
agentsManifest,
|
||||
type Effort,
|
||||
type Payload,
|
||||
} from "../external.ts";
|
||||
import { log } from "../utils/cli.ts";
|
||||
import { getGitHubInstallationToken } from "../utils/github.ts";
|
||||
|
||||
@@ -40,6 +46,7 @@ export interface AgentConfig {
|
||||
mcpServers: Record<string, McpHttpServerConfig>;
|
||||
cliPath: string;
|
||||
repo: RepoInfo;
|
||||
effort: Effort;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user