Files
wehub-resource-sync cb15c5e0d8
Release / Check for new version (push) Has been cancelled
Release / Build macOS ARM64 (push) Has been cancelled
Release / Build macOS x64 (push) Has been cancelled
Release / Build Linux ARM64 (push) Has been cancelled
Release / Build Linux musl ARM64 (push) Has been cancelled
Release / Build Linux musl x64 (push) Has been cancelled
Release / Build Linux x64 (push) Has been cancelled
Release / Build Windows x64 (push) Has been cancelled
Release / Publish to npm (push) Has been cancelled
Release / Publish sandbox package to npm (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
CI / Rust (windows-latest - x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Native E2E Tests (push) Has been cancelled
CI / Windows Integration Test (push) Has been cancelled
CI / Global Install (macos-latest) (push) Has been cancelled
CI / Global Install (ubuntu-latest) (push) Has been cancelled
CI / Version Sync Check (push) Has been cancelled
CI / Rust (push) Has been cancelled
CI / Dashboard (push) Has been cancelled
CI / Sandbox Package (push) Has been cancelled
CI / Rust (macos-latest - aarch64-apple-darwin) (push) Has been cancelled
CI / Rust (macos-latest - x86_64-apple-darwin) (push) Has been cancelled
CI / Global Install (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:58 +08:00

143 lines
4.0 KiB
TypeScript

import { readFileSync } from "fs";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
import type { Provider, ProviderOptions, ProviderResponse } from "./types.ts";
const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILL_PATH = resolve(__dirname, "../../skills/agent-browser/SKILL.md");
const AI_GATEWAY_URL = "https://ai-gateway.vercel.sh";
const DEFAULT_MODEL = "anthropic/claude-sonnet-4.6";
let cachedSkillContent: string | null = null;
function getSkillContent(): string {
if (!cachedSkillContent) {
cachedSkillContent = readFileSync(SKILL_PATH, "utf-8");
}
return cachedSkillContent;
}
function buildPrompt(userTask: string, context?: string): string {
const skill = getSkillContent();
const parts = [
"You have the following skill installed:\n",
"<skill>",
skill,
"</skill>\n",
];
if (context) {
parts.push(context + "\n");
}
parts.push(
`Complete this task: ${userTask}\n`,
"Show the exact shell commands you would run. Do not explain, just show the commands.",
);
return parts.join("\n");
}
function getGatewayEnv(): Record<string, string> {
const apiKey = process.env.AI_GATEWAY_API_KEY;
if (!apiKey) {
throw new Error(
"AI_GATEWAY_API_KEY is not set. Export it before running evals.",
);
}
return {
...(process.env as Record<string, string>),
ANTHROPIC_API_KEY: apiKey,
ANTHROPIC_BASE_URL: AI_GATEWAY_URL,
};
}
function spawnClaude(
prompt: string,
model: string,
timeout: number,
): Promise<{ output: string; stderr: string; exitCode: number }> {
const proc = Bun.spawn(
["claude", "-p", "--output-format", "text", "--model", model, prompt],
{
stdout: "pipe",
stderr: "pipe",
env: getGatewayEnv(),
},
);
return Promise.race([
(async () => {
const output = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();
const exitCode = await proc.exited;
return { output, stderr, exitCode };
})(),
new Promise<never>((_, reject) =>
setTimeout(() => {
proc.kill();
reject(new Error(`Timed out after ${timeout}ms`));
}, timeout),
),
]);
}
export const claudeProvider: Provider = {
name: "claude",
defaultModel: DEFAULT_MODEL,
async call(
userPrompt: string,
options: ProviderOptions = {},
context?: string,
): Promise<ProviderResponse> {
const { model = DEFAULT_MODEL, timeout = 60_000 } = options;
const prompt = buildPrompt(userPrompt, context);
const start = performance.now();
try {
const result = await spawnClaude(prompt, model, timeout);
const durationMs = Math.round(performance.now() - start);
if (result.exitCode !== 0) {
return {
output: "",
durationMs,
error: `claude exited with code ${result.exitCode}: ${result.stderr}`,
};
}
return { output: result.output.trim(), durationMs };
} catch (err) {
const durationMs = Math.round(performance.now() - start);
const message = err instanceof Error ? err.message : String(err);
return { output: "", durationMs, error: message };
}
},
async callRaw(
prompt: string,
options: ProviderOptions = {},
): Promise<ProviderResponse> {
const { model = DEFAULT_MODEL, timeout = 60_000 } = options;
const start = performance.now();
try {
const result = await spawnClaude(prompt, model, timeout);
const durationMs = Math.round(performance.now() - start);
if (result.exitCode !== 0) {
return {
output: "",
durationMs,
error: `claude exited with code ${result.exitCode}: ${result.stderr}`,
};
}
return { output: result.output.trim(), durationMs };
} catch (err) {
const durationMs = Math.round(performance.now() - start);
const message = err instanceof Error ? err.message : String(err);
return { output: "", durationMs, error: message };
}
},
};