Files
ai_ops/src/examples/claude.ts
2026-02-24 18:57:20 -05:00

137 lines
3.8 KiB
TypeScript

import "dotenv/config";
import { query, type Options, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
import { pathToFileURL } from "node:url";
import {
buildClaudeAuthEnv,
getConfig,
resolveAnthropicToken,
type AppConfig,
} from "../config.js";
import { createSessionContext } from "./session-context.js";
function requiredPrompt(argv: string[]): string {
const prompt = argv.slice(2).join(" ").trim();
if (!prompt) {
throw new Error('Usage: npm run claude -- "your prompt"');
}
return prompt;
}
function buildOptions(config = getConfig()): Options {
return {
maxTurns: config.provider.claudeMaxTurns,
...(config.provider.claudeModel ? { model: config.provider.claudeModel } : {}),
...(config.provider.claudeCodePath
? { pathToClaudeCodeExecutable: config.provider.claudeCodePath }
: {}),
};
}
type ClaudeAuthOptionOverrides = {
apiKey?: string;
authToken?: string;
};
function buildClaudeAuthOptionOverrides(
config: Readonly<AppConfig>,
): ClaudeAuthOptionOverrides {
if (config.provider.anthropicOauthToken) {
return { authToken: config.provider.anthropicOauthToken };
}
const token = resolveAnthropicToken(config.provider);
return token ? { apiKey: token } : {};
}
export async function readClaudeResult(
session: AsyncIterable<SDKMessage>,
): Promise<string> {
let result = "";
for await (const message of session) {
if (message.type === "result" && message.subtype === "success") {
result = message.result.trim();
}
if (message.type === "result" && message.subtype !== "success") {
const detail = message.errors.join("; ");
throw new Error(
`Claude query failed (${message.subtype})${detail ? `: ${detail}` : ""}`,
);
}
}
if (!result) {
throw new Error("Claude run completed without a final result.");
}
return result;
}
type RunClaudePromptDependencies = {
config?: Readonly<AppConfig>;
createSessionContextFn?: typeof createSessionContext;
queryFn?: typeof query;
writeOutput?: (output: string) => void;
};
export async function runClaudePrompt(
prompt: string,
dependencies: RunClaudePromptDependencies = {},
): Promise<void> {
const config = dependencies.config ?? getConfig();
const createSessionContextFn = dependencies.createSessionContextFn ?? createSessionContext;
const queryFn = dependencies.queryFn ?? query;
const writeOutput = dependencies.writeOutput ?? ((output: string) => console.log(output));
const sessionContext = await createSessionContextFn("claude", {
prompt,
workspaceRoot: process.cwd(),
config,
});
try {
const runtimeEnv = {
...sessionContext.runtimeInjection.env,
...buildClaudeAuthEnv(config.provider),
};
const authOptionOverrides = buildClaudeAuthOptionOverrides(config);
const finalResponse = await sessionContext.runInSession(async () => {
const session = queryFn({
prompt: sessionContext.promptWithContext,
options: {
...buildOptions(config),
...authOptionOverrides,
...(sessionContext.mcp.claudeMcpServers
? { mcpServers: sessionContext.mcp.claudeMcpServers as Options["mcpServers"] }
: {}),
cwd: sessionContext.runtimeInjection.workingDirectory,
env: runtimeEnv,
},
});
try {
return await readClaudeResult(session);
} finally {
session.close();
}
});
writeOutput(finalResponse);
} finally {
await sessionContext.close();
}
}
async function main(): Promise<void> {
const prompt = requiredPrompt(process.argv);
await runClaudePrompt(prompt);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});
}