improve agents external integration

This commit is contained in:
David Blass
2025-11-20 13:54:29 -05:00
parent 9c51c450bc
commit 917b8804c0
10 changed files with 40 additions and 30 deletions
-1
View File
@@ -6,7 +6,6 @@ import { agent, installFromNpmTarball } from "./shared.ts";
export const claude = agent({
name: "claude",
inputKeys: ["anthropic_api_key"],
install: async () => {
const versionRange = packageJson.dependencies["@anthropic-ai/claude-agent-sdk"] || "latest";
return await installFromNpmTarball({
-1
View File
@@ -6,7 +6,6 @@ import { agent, type ConfigureMcpServersParams, installFromNpmTarball } from "./
export const codex = agent({
name: "codex",
inputKeys: ["openai_api_key"],
install: async () => {
return await installFromNpmTarball({
packageName: "@openai/codex",
-1
View File
@@ -7,7 +7,6 @@ import { agent, type ConfigureMcpServersParams, installFromCurl } from "./shared
export const cursor = agent({
name: "cursor",
inputKeys: ["cursor_api_key"],
install: async () => {
return await installFromCurl({
installUrl: "https://cursor.com/install",
-1
View File
@@ -6,7 +6,6 @@ import { agent, type ConfigureMcpServersParams, installFromNpmTarball } from "./
export const gemini = agent({
name: "gemini",
inputKeys: ["google_api_key", "gemini_api_key"],
install: async () => {
return await installFromNpmTarball({
packageName: "@google/gemini-cli",
-2
View File
@@ -11,5 +11,3 @@ export const agents = {
cursor,
gemini,
} satisfies Record<AgentName, Agent>;
export type AgentInputKey = (typeof agents)[keyof typeof agents]["inputKeys"][number];
+13 -7
View File
@@ -3,7 +3,8 @@ import { chmodSync, createWriteStream, existsSync } from "node:fs";
import { join } from "node:path";
import { pipeline } from "node:stream/promises";
import type { McpStdioServerConfig } from "@anthropic-ai/claude-agent-sdk";
import type { Payload } from "../external.ts";
import type { show } from "@ark/util";
import { type AgentManifest, type AgentName, agentsManifest, type Payload } from "../external.ts";
import { log } from "../utils/cli.ts";
/**
@@ -232,13 +233,18 @@ export async function installFromCurl({
return cliPath;
}
export const agent = <const agent extends Agent>(agent: agent): agent => {
return agent;
export const agent = <const input extends AgentInput>(input: input): defineAgent<input> => {
return { ...input, ...agentsManifest[input.name] } as never;
};
export type Agent = {
name: string;
inputKeys: string[];
export interface AgentInput {
name: AgentName;
install: () => Promise<string>;
run: (config: AgentConfig) => Promise<AgentResult>;
};
}
export interface Agent extends AgentInput, AgentManifest {}
type agentManifest<name extends AgentName> = (typeof agentsManifest)[name];
type defineAgent<input extends AgentInput> = show<input & agentManifest<input["name"]>>;
+1 -1
View File
@@ -24,7 +24,7 @@ async function run(): Promise<void> {
prompt: core.getInput("prompt", { required: true }),
agent: core.getInput("agent") ? AgentName.assert(core.getInput("agent")) : undefined,
...flatMorph(agents, (_, agent) =>
agent.inputKeys.map((inputKey) => [inputKey, core.getInput(inputKey)])
agent.apiKeyNames.map((inputKey) => [inputKey, core.getInput(inputKey)])
),
};
+17 -10
View File
@@ -7,31 +7,38 @@
import type { Mode } from "./modes.ts";
// mcp name constant
export const ghPullfrogMcpName = "gh-pullfrog";
export const ghPullfrogMcpName = "gh_pullfrog";
export interface AgentManifest {
displayName: string;
apiKeyNames: string[];
}
// agent manifest - static metadata about available agents
export const agentsManifest = {
claude: {
name: "Claude Code",
apiKeys: ["anthropic_api_key"],
displayName: "Claude Code",
apiKeyNames: ["anthropic_api_key"],
},
codex: {
name: "Codex CLI",
apiKeys: ["openai_api_key"],
displayName: "Codex CLI",
apiKeyNames: ["openai_api_key"],
},
cursor: {
name: "Cursor CLI",
apiKeys: ["cursor_api_key"],
displayName: "Cursor CLI",
apiKeyNames: ["cursor_api_key"],
},
gemini: {
name: "Gemini CLI",
apiKeys: ["google_api_key", "gemini_api_key"],
displayName: "Gemini CLI",
apiKeyNames: ["google_api_key", "gemini_api_key"],
},
} as const;
} as const satisfies Record<string, AgentManifest>;
// agent name type - union of agent slugs
export type AgentName = keyof typeof agentsManifest;
export type AgentApiKeyName = (typeof agentsManifest)[AgentName]["apiKeyNames"][number];
// payload type for agent execution
export type Payload = {
"~pullfrog": true;
+5 -5
View File
@@ -24,12 +24,12 @@ import { setupGitAuth, setupGitConfig } from "./utils/setup.ts";
export const AgentName = type.enumerated(...Object.values(agents).map((agent) => agent.name));
export const AgentInputKey = type.enumerated(
...Object.values(agents).flatMap((agent) => agent.inputKeys)
...Object.values(agents).flatMap((agent) => agent.apiKeyNames)
);
export type AgentInputKey = typeof AgentInputKey.infer;
const keyInputDefs = flatMorph(agents, (_, agent) =>
agent.inputKeys.map((inputKey) => [inputKey, "string | undefined?"] as const)
agent.apiKeyNames.map((inputKey) => [inputKey, "string | undefined?"] as const)
);
export const Inputs = type({
@@ -102,7 +102,7 @@ function throwMissingApiKeyError({
// Find which agents have inputKeys that match the provided inputs
const availableAgents = Object.values(agents).filter((agent) =>
agent.inputKeys.some((inputKey) => inputs[inputKey])
agent.apiKeyNames.some((inputKey) => inputs[inputKey])
);
let message = `Pullfrog is configured to use ${agentName}, but the associated API key was not provided.
@@ -230,13 +230,13 @@ async function installAgentCli(ctx: MainContext): Promise<void> {
}
function validateApiKey(ctx: MainContext): void {
const matchingInputKey = ctx.agent.inputKeys.find(
const matchingInputKey = ctx.agent.apiKeyNames.find(
(inputKey: string) => ctx.inputs[inputKey as AgentInputKey]
);
if (!matchingInputKey) {
throwMissingApiKeyError({
agentName: ctx.agentName,
inputKeys: ctx.agent.inputKeys,
inputKeys: ctx.agent.apiKeyNames,
repoContext: ctx.repoContext,
inputs: ctx.inputs,
});
+4 -1
View File
@@ -7,6 +7,8 @@
[] test if home directory mcp.json works if mcp.json is specified in repo
[] add footer to the working comment ("executed by {agent}", link to pullfrog (homepage) w/ small logo?, feedback (create github issue), link to workflow run)- see https://github.com/colinhacks/zod/issues/5459#issuecomment-3548382991
[] avoid passing all of process.env into agents: minimum # of vars
[] external.ts align to agents
[] toon encode in prompt
## MAYBE
@@ -32,4 +34,5 @@
[x] standardize mcp server
[x] entry.js
[x] split up prompts, load dynamically based on mode
[x] log.txt to stdout
[x] log.txt to stdout
[x] rename mcp to use underscore