From 9588ffd4b63552745629041942ecdcecde33f838 Mon Sep 17 00:00:00 2001 From: David Blass Date: Wed, 12 Nov 2025 19:57:34 -0500 Subject: [PATCH] MASSIVE IMPROVCE --- agents/claude.ts | 27 +++----- agents/codex.ts | 10 +-- agents/index.ts | 6 +- agents/shared.ts | 7 ++- entry.js | 156 ++++++++++++++++++++++------------------------- main.ts | 34 +++++------ play.ts | 82 +++++++++++++------------ 7 files changed, 155 insertions(+), 167 deletions(-) diff --git a/agents/claude.ts b/agents/claude.ts index 1f8d75c..68b98e1 100644 --- a/agents/claude.ts +++ b/agents/claude.ts @@ -8,17 +8,12 @@ import { pipeline } from "node:stream/promises"; import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk"; import packageJson from "../package.json" with { type: "json" }; import { log } from "../utils/cli.ts"; -import { type Agent, instructions } from "./shared.ts"; - -let cachedCliPath: string | undefined; - -export const claude: Agent = { - install: async (): Promise => { - if (cachedCliPath) { - log.info(`Using cached Claude Code CLI at ${cachedCliPath}`); - return cachedCliPath; - } +import { agent, instructions } from "./shared.ts"; +export const claude = agent({ + name: "claude", + inputKey: "anthropic_api_key", + install: async () => { // Get the SDK version from package.json and resolve to actual version const versionRange = packageJson.dependencies["@anthropic-ai/claude-agent-sdk"] || "latest"; let sdkVersion: string; @@ -83,8 +78,6 @@ export const claude: Agent = { if (!existsSync(cliPath)) { throw new Error(`cli.js not found in extracted package at ${cliPath}`); } - - cachedCliPath = cliPath; log.info(`✓ Claude Code CLI installed at ${cliPath}`); return cliPath; } catch (error) { @@ -97,19 +90,15 @@ export const claude: Agent = { throw error; } }, - run: async ({ prompt, mcpServers, apiKey }) => { + run: async ({ prompt, mcpServers, apiKey, cliPath }) => { process.env.ANTHROPIC_API_KEY = apiKey; - if (!cachedCliPath) { - throw new Error("Claude CLI not installed. Call install() before run()."); - } - const queryInstance = query({ prompt: `${instructions}\n\n****** USER PROMPT ******\n${prompt}`, options: { permissionMode: "bypassPermissions", mcpServers, - pathToClaudeCodeExecutable: cachedCliPath, + pathToClaudeCodeExecutable: cliPath, }, }); @@ -124,7 +113,7 @@ export const claude: Agent = { output: "", }; }, -}; +}); type SDKMessageType = SDKMessage["type"]; diff --git a/agents/codex.ts b/agents/codex.ts index 090a75f..bb533b0 100644 --- a/agents/codex.ts +++ b/agents/codex.ts @@ -1,10 +1,12 @@ import { spawnSync } from "node:child_process"; import { findCliPath, log } from "../utils/cli.ts"; -import { type Agent, instructions } from "./shared.ts"; +import { agent, instructions } from "./shared.ts"; -export const codex: Agent = { - install: async (): Promise => { +export const codex = agent({ + name: "codex", + inputKey: "openai_api_key", + install: async () => { const globalCodexPath = findCliPath("codex"); if (globalCodexPath) { log.info(`Using global Codex CLI at ${globalCodexPath}`); @@ -133,4 +135,4 @@ export const codex: Agent = { }; } }, -}; +}); diff --git a/agents/index.ts b/agents/index.ts index 7e684d1..553aadf 100644 --- a/agents/index.ts +++ b/agents/index.ts @@ -1,9 +1,9 @@ -import type { AgentName } from "../main.ts"; import { claude } from "./claude.ts"; import { codex } from "./codex.ts"; -import type { Agent } from "./shared.ts"; export const agents = { claude, codex, -} as const satisfies Record; +} as const; + +export type AgentInputKey = (typeof agents)[keyof typeof agents]["inputKey"]; diff --git a/agents/shared.ts b/agents/shared.ts index 0f7cb8a..c020f5c 100644 --- a/agents/shared.ts +++ b/agents/shared.ts @@ -23,12 +23,15 @@ export interface AgentConfig { cliPath: string; } -type InputKey = "anthropic_api_key" | "openai_api_key"; +export const agent = (agent: agent): agent => { + return agent; +}; export type Agent = { + name: string; + inputKey: string; install: () => Promise; run: (config: AgentConfig) => Promise; - inputKey: InputKey; }; export const instructions = ` diff --git a/entry.js b/entry.js index 4e2c30d..2c69bf8 100755 --- a/entry.js +++ b/entry.js @@ -313,28 +313,28 @@ var require_tunnel = __commonJS({ exports.httpOverHttps = httpOverHttps; exports.httpsOverHttps = httpsOverHttps; function httpOverHttp(options) { - var agent = new TunnelingAgent(options); - agent.request = http.request; - return agent; + var agent2 = new TunnelingAgent(options); + agent2.request = http.request; + return agent2; } function httpsOverHttp(options) { - var agent = new TunnelingAgent(options); - agent.request = http.request; - agent.createSocket = createSecureSocket; - agent.defaultPort = 443; - return agent; + var agent2 = new TunnelingAgent(options); + agent2.request = http.request; + agent2.createSocket = createSecureSocket; + agent2.defaultPort = 443; + return agent2; } function httpOverHttps(options) { - var agent = new TunnelingAgent(options); - agent.request = https.request; - return agent; + var agent2 = new TunnelingAgent(options); + agent2.request = https.request; + return agent2; } function httpsOverHttps(options) { - var agent = new TunnelingAgent(options); - agent.request = https.request; - agent.createSocket = createSecureSocket; - agent.defaultPort = 443; - return agent; + var agent2 = new TunnelingAgent(options); + agent2.request = https.request; + agent2.createSocket = createSecureSocket; + agent2.defaultPort = 443; + return agent2; } function TunnelingAgent(options) { var self2 = this; @@ -9136,18 +9136,18 @@ var require_agent = __commonJS({ } } ); - const agent = this; + const agent2 = this; this[kOnDrain] = (origin, targets) => { - agent.emit("drain", origin, [agent, ...targets]); + agent2.emit("drain", origin, [agent2, ...targets]); }; this[kOnConnect] = (origin, targets) => { - agent.emit("connect", origin, [agent, ...targets]); + agent2.emit("connect", origin, [agent2, ...targets]); }; this[kOnDisconnect] = (origin, targets, err2) => { - agent.emit("disconnect", origin, [agent, ...targets], err2); + agent2.emit("disconnect", origin, [agent2, ...targets], err2); }; this[kOnConnectionError] = (origin, targets, err2) => { - agent.emit("connectionError", origin, [agent, ...targets], err2); + agent2.emit("connectionError", origin, [agent2, ...targets], err2); }; } get [kRunning]() { @@ -10527,16 +10527,16 @@ var require_mock_utils = __commonJS({ return true; } function buildMockDispatch() { - const agent = this[kMockAgent]; + const agent2 = this[kMockAgent]; const origin = this[kOrigin]; const originalDispatch = this[kOriginalDispatch]; return function dispatch(opts, handler) { - if (agent.isMockActive) { + if (agent2.isMockActive) { try { mockDispatch.call(this, opts, handler); } catch (error2) { if (error2 instanceof MockNotMatchedError) { - const netConnect = agent[kGetNetConnect](); + const netConnect = agent2[kGetNetConnect](); if (netConnect === false) { throw new MockNotMatchedError(`${error2.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`); } @@ -10565,7 +10565,7 @@ var require_mock_utils = __commonJS({ } function buildMockOptions(opts) { if (opts) { - const { agent, ...mockOptions } = opts; + const { agent: agent2, ...mockOptions } = opts; return mockOptions; } } @@ -10965,9 +10965,9 @@ var require_mock_agent = __commonJS({ if (opts && opts.agent && typeof opts.agent.dispatch !== "function") { throw new InvalidArgumentError("Argument opts.agent must implement Agent"); } - const agent = opts && opts.agent ? opts.agent : new Agent(opts); - this[kAgent] = agent; - this[kClients] = agent[kClients]; + const agent2 = opts && opts.agent ? opts.agent : new Agent(opts); + this[kAgent] = agent2; + this[kClients] = agent2[kClients]; this[kOptions] = buildMockOptions(opts); } get(origin) { @@ -11493,12 +11493,12 @@ var require_global2 = __commonJS({ if (getGlobalDispatcher() === void 0) { setGlobalDispatcher(new Agent()); } - function setGlobalDispatcher(agent) { - if (!agent || typeof agent.dispatch !== "function") { + function setGlobalDispatcher(agent2) { + if (!agent2 || typeof agent2.dispatch !== "function") { throw new InvalidArgumentError("Argument agent must implement Agent"); } Object.defineProperty(globalThis, globalDispatcher, { - value: agent, + value: agent2, writable: true, enumerable: false, configurable: false @@ -13850,8 +13850,8 @@ var require_fetch = __commonJS({ return response; async function dispatch({ body }) { const url2 = requestCurrentURL(request); - const agent = fetchParams.controller.dispatcher; - return new Promise((resolve, reject) => agent.dispatch( + const agent2 = fetchParams.controller.dispatcher; + return new Promise((resolve, reject) => agent2.dispatch( { path: url2.pathname + url2.search, origin: url2.origin, @@ -17223,8 +17223,8 @@ var require_undici = __commonJS({ } url2 = util2.parseURL(url2); } - const { agent, dispatcher = getGlobalDispatcher() } = opts; - if (agent) { + const { agent: agent2, dispatcher = getGlobalDispatcher() } = opts; + if (agent2) { throw new InvalidArgumentError("unsupported opts.agent. Did you mean opts.client?"); } return fn2.call(dispatcher, { @@ -17776,17 +17776,17 @@ var require_lib = __commonJS({ return additionalHeaders[header] || clientHeader || _default; } _getAgent(parsedUrl) { - let agent; + let agent2; const proxyUrl = pm.getProxyUrl(parsedUrl); const useProxy = proxyUrl && proxyUrl.hostname; if (this._keepAlive && useProxy) { - agent = this._proxyAgent; + agent2 = this._proxyAgent; } if (!useProxy) { - agent = this._agent; + agent2 = this._agent; } - if (agent) { - return agent; + if (agent2) { + return agent2; } const usingSsl = parsedUrl.protocol === "https:"; let maxSockets = 100; @@ -17808,20 +17808,20 @@ var require_lib = __commonJS({ } else { tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; } - agent = tunnelAgent(agentOptions); - this._proxyAgent = agent; + agent2 = tunnelAgent(agentOptions); + this._proxyAgent = agent2; } - if (!agent) { + if (!agent2) { const options = { keepAlive: this._keepAlive, maxSockets }; - agent = usingSsl ? new https.Agent(options) : new http.Agent(options); - this._agent = agent; + agent2 = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent2; } if (usingSsl && this._ignoreSslError) { - agent.options = Object.assign(agent.options || {}, { + agent2.options = Object.assign(agent2.options || {}, { rejectUnauthorized: false }); } - return agent; + return agent2; } _getProxyAgentDispatcher(parsedUrl, proxyUrl) { let proxyAgent; @@ -41172,6 +41172,9 @@ var workflows = [ ]; // agents/shared.ts +var agent = (agent2) => { + return agent2; +}; var instructions = ` # General instructions @@ -41217,13 +41220,10 @@ ${w.prompt}`).join("\n\n")} `; // agents/claude.ts -var cachedCliPath; -var claude = { +var claude = agent({ + name: "claude", + inputKey: "anthropic_api_key", install: async () => { - if (cachedCliPath) { - log.info(`Using cached Claude Code CLI at ${cachedCliPath}`); - return cachedCliPath; - } const versionRange = package_default.dependencies["@anthropic-ai/claude-agent-sdk"] || "latest"; let sdkVersion; if (versionRange.startsWith("^") || versionRange.startsWith("~")) { @@ -41268,7 +41268,6 @@ var claude = { if (!existsSync3(cliPath)) { throw new Error(`cli.js not found in extracted package at ${cliPath}`); } - cachedCliPath = cliPath; log.info(`\u2713 Claude Code CLI installed at ${cliPath}`); return cliPath; } catch (error2) { @@ -41279,11 +41278,8 @@ var claude = { throw error2; } }, - run: async ({ prompt, mcpServers, apiKey }) => { + run: async ({ prompt, mcpServers, apiKey, cliPath }) => { process.env.ANTHROPIC_API_KEY = apiKey; - if (!cachedCliPath) { - throw new Error("Claude CLI not installed. Call install() before run()."); - } const queryInstance = query({ prompt: `${instructions} @@ -41292,7 +41288,7 @@ ${prompt}`, options: { permissionMode: "bypassPermissions", mcpServers, - pathToClaudeCodeExecutable: cachedCliPath + pathToClaudeCodeExecutable: cliPath } }); for await (const message of queryInstance) { @@ -41304,7 +41300,7 @@ ${prompt}`, output: "" }; } -}; +}); var bashToolIds = /* @__PURE__ */ new Set(); var messageHandlers = { assistant: (data) => { @@ -41400,7 +41396,9 @@ var messageHandlers = { // agents/codex.ts import { spawnSync as spawnSync2 } from "node:child_process"; -var codex = { +var codex = agent({ + name: "codex", + inputKey: "openai_api_key", install: async () => { const globalCodexPath = findCliPath("codex"); if (globalCodexPath) { @@ -41507,7 +41505,7 @@ ${prompt}`; }; } } -}; +}); // agents/index.ts var agents = { @@ -41598,11 +41596,17 @@ function setupGitAuth(githubToken, repoContext) { } // main.ts -var AgentName = type.enumerated("codex", "claude"); +var AgentName = type.enumerated(...Object.values(agents).map((agent2) => agent2.name)); +var AgentInputKey = type.enumerated( + ...Object.values(agents).map((agent2) => agent2.inputKey) +); +var keyInputDefs = flatMorph( + agents, + (_, agent2) => [agent2.inputKey, "string | undefined?"] +); var Inputs = type({ prompt: "string", - "anthropic_api_key?": "string | undefined", - "openai_api_key?": "string | undefined", + ...keyInputDefs, "agent?": AgentName }); async function main(inputs) { @@ -41621,28 +41625,16 @@ async function main(inputs) { repoContext }); const agentName = inputs.agent || repoSettings.defaultAgent || "claude"; - const agent = agents[agentName]; + const agent2 = agents[agentName]; setupGitAuth(githubInstallationToken, repoContext); const mcpServers = createMcpConfigs(githubInstallationToken); log.debug(`\u{1F4CB} MCP Config: ${JSON.stringify(mcpServers, null, 2)}`); - const cliPath = await agent.install(); + const cliPath = await agent2.install(); log.info(`Running ${agentName} Agent SDK...`); log.box(inputs.prompt, { title: "Prompt" }); - let apiKey; - if (agentName === "claude") { - if (!inputs.anthropic_api_key) { - throw new Error("ANTHROPIC_API_KEY is required for Claude agent"); - } - apiKey = inputs.anthropic_api_key; - } else if (agentName === "codex") { - if (!inputs.openai_api_key) { - throw new Error("OPENAI_API_KEY is required for Codex agent"); - } - apiKey = inputs.openai_api_key; - } else { - throw new Error(`API key configuration not implemented for agent: ${agentName}`); - } - const result = await agent.run({ + const apiKey = inputs[agent2.inputKey]; + if (!apiKey) throw new Error(`${agent2.inputKey} is required for ${agentName} agent`); + const result = await agent2.run({ prompt: inputs.prompt, mcpServers, githubInstallationToken, diff --git a/main.ts b/main.ts index 05cb33d..9170ad8 100644 --- a/main.ts +++ b/main.ts @@ -1,3 +1,4 @@ +import { flatMorph } from "@ark/util"; import { type } from "arktype"; import { agents } from "./agents/index.ts"; import { createMcpConfigs } from "./mcp/config.ts"; @@ -11,13 +12,22 @@ import { } from "./utils/github.ts"; import { setupGitAuth, setupGitConfig } from "./utils/setup.ts"; -export const AgentName = type.enumerated("codex", "claude"); +export const AgentName = type.enumerated(...Object.values(agents).map((agent) => agent.name)); export type AgentName = typeof AgentName.infer; +export const AgentInputKey = type.enumerated( + ...Object.values(agents).map((agent) => agent.inputKey) +); +export type AgentInputKey = typeof AgentInputKey.infer; + +const keyInputDefs = flatMorph( + agents, + (_, agent) => [agent.inputKey, "string | undefined?"] as const +); + export const Inputs = type({ prompt: "string", - "anthropic_api_key?": "string | undefined", - "openai_api_key?": "string | undefined", + ...keyInputDefs, "agent?": AgentName, }); @@ -72,20 +82,10 @@ export async function main(inputs: Inputs): Promise { // for webhook payloads, check the specified `agent` field // Get API key based on agent type - let apiKey: string; - if (agentName === "claude") { - if (!inputs.anthropic_api_key) { - throw new Error("ANTHROPIC_API_KEY is required for Claude agent"); - } - apiKey = inputs.anthropic_api_key; - } else if (agentName === "codex") { - if (!inputs.openai_api_key) { - throw new Error("OPENAI_API_KEY is required for Codex agent"); - } - apiKey = inputs.openai_api_key; - } else { - throw new Error(`API key configuration not implemented for agent: ${agentName}`); - } + + const apiKey = inputs[agent.inputKey]; + + if (!apiKey) throw new Error(`${agent.inputKey} is required for ${agentName} agent`); const result = await agent.run({ prompt: inputs.prompt, diff --git a/play.ts b/play.ts index 26be467..3735fbb 100644 --- a/play.ts +++ b/play.ts @@ -28,6 +28,7 @@ export async function run( const inputs: Inputs = { prompt, openai_api_key: process.env.OPENAI_API_KEY, + anthropic_api_key: process.env.ANTHROPIC_API_KEY, agent: "codex", }; @@ -87,54 +88,55 @@ Examples: // Default to testing tool calls if no file specified const filePath = args._[0] || null; if (!filePath) { - prompt = "List all available MCP tools from the gh-pullfrog server and show what each tool does."; + prompt = + "List all available MCP tools from the gh-pullfrog server and show what each tool does."; } else { - const ext = extname(filePath).toLowerCase(); - let resolvedPath: string; + const ext = extname(filePath).toLowerCase(); + let resolvedPath: string; - const fixturesPath = fromHere("fixtures", filePath); - if (existsSync(fixturesPath)) { - resolvedPath = fixturesPath; - } else if (existsSync(filePath)) { - resolvedPath = resolve(filePath); - } else { - throw new Error(`File not found: ${filePath}`); - } - - switch (ext) { - case ".txt": { - prompt = readFileSync(resolvedPath, "utf8").trim(); - break; + const fixturesPath = fromHere("fixtures", filePath); + if (existsSync(fixturesPath)) { + resolvedPath = fixturesPath; + } else if (existsSync(filePath)) { + resolvedPath = resolve(filePath); + } else { + throw new Error(`File not found: ${filePath}`); } - case ".json": { - const content = readFileSync(resolvedPath, "utf8"); - const parsed = JSON.parse(content); - prompt = JSON.stringify(parsed, null, 2); - break; - } - - case ".ts": { - const fileUrl = pathToFileURL(resolvedPath).href; - const module = await import(fileUrl); - - if (!module.default) { - throw new Error(`TypeScript file ${filePath} must have a default export`); + switch (ext) { + case ".txt": { + prompt = readFileSync(resolvedPath, "utf8").trim(); + break; } - if (typeof module.default === "string") { - prompt = module.default; - } else if (typeof module.default === "object" && module.default.prompt) { - prompt = module.default.prompt; - } else { - prompt = JSON.stringify(module.default, null, 2); + case ".json": { + const content = readFileSync(resolvedPath, "utf8"); + const parsed = JSON.parse(content); + prompt = JSON.stringify(parsed, null, 2); + break; } - break; - } - default: - throw new Error(`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`); - } + case ".ts": { + const fileUrl = pathToFileURL(resolvedPath).href; + const module = await import(fileUrl); + + if (!module.default) { + throw new Error(`TypeScript file ${filePath} must have a default export`); + } + + if (typeof module.default === "string") { + prompt = module.default; + } else if (typeof module.default === "object" && module.default.prompt) { + prompt = module.default.prompt; + } else { + prompt = JSON.stringify(module.default, null, 2); + } + break; + } + + default: + throw new Error(`Unsupported file type: ${ext}. Supported types: .txt, .json, .ts`); + } } }