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

213 lines
5.8 KiB
TypeScript

import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
import { resolve, dirname, join } from "path";
import { fileURLToPath } from "url";
import { tmpdir } from "os";
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/v1";
const DEFAULT_MODEL = "openai/o3";
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");
}
let evalHome: string | null = null;
function getEvalHome(model: string): string {
if (!evalHome) {
const tmpRoot = existsSync("/private/tmp") ? "/private/tmp" : tmpdir();
evalHome = join(tmpRoot, `agent-browser-evals-${process.pid}`);
}
const configDir = join(evalHome, ".codex");
const configPath = join(configDir, "config.toml");
const config = `model = "${model}"
model_provider = "vercel-ai-gateway"
[model_providers.vercel-ai-gateway]
name = "Vercel AI Gateway"
base_url = "${AI_GATEWAY_URL}"
env_key = "AI_GATEWAY_API_KEY"
wire_api = "responses"
`;
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}
writeFileSync(configPath, config, "utf-8");
return evalHome;
}
function getCodexEnv(model: string): 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>),
HOME: getEvalHome(model),
AI_GATEWAY_API_KEY: apiKey,
};
}
function parseJsonlOutput(raw: string): string {
const lines = raw.split("\n");
const textParts: string[] = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const parsed = JSON.parse(trimmed);
const eventType = parsed.type as string;
if (eventType === "item.completed") {
const item = parsed.item as Record<string, unknown> | undefined;
if (item?.type === "agent_message") {
const text = item.text as string | undefined;
if (text) textParts.push(text);
}
}
} catch {
// Non-JSON line (e.g. stderr leak), skip
}
}
return textParts.join("\n\n").trim();
}
function spawnCodex(
prompt: string,
model: string,
timeout: number,
): Promise<{ output: string; stderr: string; exitCode: number }> {
const proc = Bun.spawn(
[
"codex",
"exec",
"--dangerously-bypass-approvals-and-sandbox",
"--json",
"--model",
model,
"-",
],
{
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: getCodexEnv(model),
},
);
proc.stdin.write(prompt);
proc.stdin.end();
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 codexProvider: Provider = {
name: "codex",
defaultModel: DEFAULT_MODEL,
async call(
userPrompt: string,
options: ProviderOptions = {},
context?: string,
): Promise<ProviderResponse> {
const { model = DEFAULT_MODEL, timeout = 120_000 } = options;
const prompt = buildPrompt(userPrompt, context);
const start = performance.now();
try {
const result = await spawnCodex(prompt, model, timeout);
const durationMs = Math.round(performance.now() - start);
if (result.exitCode !== 0) {
const detail = result.stderr.trim() || result.output.slice(0, 1000);
return {
output: "",
durationMs,
error: `codex exited with code ${result.exitCode}: ${detail}`,
};
}
const output = parseJsonlOutput(result.output);
return { output, 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 = 120_000 } = options;
const start = performance.now();
try {
const result = await spawnCodex(prompt, model, timeout);
const durationMs = Math.round(performance.now() - start);
if (result.exitCode !== 0) {
const detail = result.stderr.trim() || result.output.slice(0, 1000);
return {
output: "",
durationMs,
error: `codex exited with code ${result.exitCode}: ${detail}`,
};
}
const output = parseJsonlOutput(result.output);
return { output, durationMs };
} catch (err) {
const durationMs = Math.round(performance.now() - start);
const message = err instanceof Error ? err.message : String(err);
return { output: "", durationMs, error: message };
}
},
};