#!/usr/bin/env node import { createHash } from "node:crypto";import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync,} from "node:fs";import { mkdtemp, readdir, readFile, writeFile } from "node:fs/promises";import { tmpdir } from "node:os";import { dirname, join, resolve } from "node:path";import { spawnSync } from "node:child_process";import { fileURLToPath } from "node:url"; const SOURCE_REPOSITORY = "anomalyco/opencode";const SOURCE_VERSION = "1.3.13";const SOURCE_TARBALL_URL = `https://github.com/${SOURCE_REPOSITORY}/archive/refs/tags/v${SOURCE_VERSION}.tar.gz`; // Upstream `packages/app/package.json` pins `ghostty-web: github:anomalyco/ghostty-web#main`,// but the bundled bun.lock snapshots the SHA below. Because `main` is a moving ref, a fresh// `bun install --frozen-lockfile` resolves to whatever `main` points at *now* and fails the// lockfile check. Rewriting the manifest to the lockfile's SHA before install keeps frozen-// lockfile guarantees intact (i.e. CI still breaks loudly if either side drifts) without// trusting arbitrary new HEADs on the ghostty-web branch.const GHOSTTY_WEB_PINNED_SHA = "4af877d"; const __dirname = dirname(fileURLToPath(import.meta.url));const packageDir = resolve(__dirname, "..");const distDir = resolve(packageDir, "dist");const cacheDir = resolve(packageDir, "node_modules", ".cache", "opencode-build");const patchPath = resolve(packageDir, "upstream", `opencode-v${SOURCE_VERSION}.patch`);const bundleDir = resolve(distDir, "opencode-acp");const manifestPath = resolve(distDir, "opencode-acp.manifest.json");const SQL_JS_VERSION = "1.14.1";const bunBin = resolve( packageDir, "node_modules", ".bin", process.platform === "win32" ? "bun.cmd" : "bun",); function run(command, args, options = {}) { const result = spawnSync(command, args, { stdio: "inherit", ...options, }); if (result.status !== 0) { throw new Error( `Command failed (${result.status ?? "unknown"}): ${command} ${args.join(" ")}`, ); } return result;} function pinGhosttyWebRef(sourceRoot) { const manifestPath = resolve(sourceRoot, "packages", "app", "package.json"); const raw = readFileSync(manifestPath, "utf-8"); const movingRef = '"ghostty-web": "github:anomalyco/ghostty-web#main"'; const pinnedRef = `"ghostty-web": "github:anomalyco/ghostty-web#${GHOSTTY_WEB_PINNED_SHA}"`; if (raw.includes(pinnedRef)) return; if (!raw.includes(movingRef)) { throw new Error( `Expected ghostty-web ref ${movingRef} in ${manifestPath}; upstream layout changed — re-audit GHOSTTY_WEB_PINNED_SHA before updating.`, ); } writeFileSync(manifestPath, raw.replace(movingRef, pinnedRef));} const PATCHED_SOURCE_FILES = [ "packages/opencode/src/cli/cmd/acp.ts", "packages/opencode/src/config/config.ts", "packages/opencode/src/plugin/index.ts", "packages/opencode/src/server/instance.ts", "packages/opencode/src/server/server.ts",]; async function ensureNodeAcpPatch(sourceRoot, tarballPath) { const serverFile = resolve( sourceRoot, "packages", "opencode", "src", "server", "server.ts", ); const pluginFile = resolve( sourceRoot, "packages", "opencode", "src", "plugin", "index.ts", ); const acpFile = resolve( sourceRoot, "packages", "opencode", "src", "cli", "cmd", "acp.ts", ); const serverSource = readFileSync(serverFile, "utf-8"); const pluginSource = readFileSync(pluginFile, "utf-8"); const acpSource = readFileSync(acpFile, "utf-8"); const alreadyPatched = serverSource.includes('from "node:http"') && !serverSource.includes('from "hono/bun"') && pluginSource.includes("Bun shell is unavailable in the Node ACP build") && acpSource.includes("const server = await Server.listen(opts)"); if (alreadyPatched) { return; } const scratchRoot = await mkdtemp(join(tmpdir(), "agentos-opencode-patch-")); try { run("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", scratchRoot]); run("git", ["apply", "--whitespace=nowarn", patchPath], { cwd: scratchRoot }); for (const relativePath of PATCHED_SOURCE_FILES) { copyFileSync( resolve(scratchRoot, relativePath), resolve(sourceRoot, relativePath), ); } } finally { rmSync(scratchRoot, { recursive: true, force: true }); } const verifiedServerSource = readFileSync(serverFile, "utf-8"); const verifiedPluginSource = readFileSync(pluginFile, "utf-8"); const verifiedAcpSource = readFileSync(acpFile, "utf-8"); if ( !verifiedServerSource.includes('from "node:http"') || verifiedServerSource.includes('from "hono/bun"') || !verifiedPluginSource.includes("Bun shell is unavailable in the Node ACP build") || !verifiedAcpSource.includes("const server = await Server.listen(opts)") ) { throw new Error("Failed to stage the Node ACP patches into the prepared OpenCode source tree"); }} async function downloadFile(url, destination) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`); } const buffer = Buffer.from(await response.arrayBuffer()); await writeFile(destination, buffer);} async function readMigrations(sourceRoot) { const migrationRoot = join(sourceRoot, "packages", "opencode", "migration"); const entries = (await readdir(migrationRoot, { withFileTypes: true })) .filter((entry) => entry.isDirectory() && /^\d{14}/.test(entry.name)) .map((entry) => entry.name) .sort(); return Promise.all( entries.map(async (name) => { const sql = await readFile( join(migrationRoot, name, "migration.sql"), "utf8", ); const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name); const timestamp = match ? Date.UTC( Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]), ) : 0; return { name, sql, timestamp }; }), );} async function rewriteSourceFile(sourceRoot, relativePath, transform) { const filePath = resolve(sourceRoot, relativePath); const original = await readFile(filePath, "utf8"); const next = transform(original); if (next !== original) { await writeFile(filePath, next); }} async function ensureSqlJsDependency(sourceRoot) { const packageJsonPath = resolve( sourceRoot, "packages", "opencode", "package.json", ); const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); const dependencies = packageJson.dependencies ?? {}; const bunStoreDir = resolve(sourceRoot, "node_modules", ".bun"); const hasInstalledSqlJs = existsSync(bunStoreDir) && (await readdir(bunStoreDir)).some((entry) => entry.startsWith("sql.js@")); if (dependencies["sql.js"] === SQL_JS_VERSION && hasInstalledSqlJs) { return; } packageJson.dependencies = { ...dependencies, "sql.js": SQL_JS_VERSION, }; await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`); run(bunBin, ["install"], { cwd: sourceRoot });} async function applyNodeAcpRuntimeTweaks(sourceRoot) { await writeFile( resolve(sourceRoot, "packages/opencode/src/cli/cmd/acp.ts"), `import type { Argv, InferredOptionTypes } from "yargs"import { cmd } from "./cmd"import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"import { Log } from "../../util/log" const options = { port: { type: "number" as const, describe: "port to listen on", default: 0, }, hostname: { type: "string" as const, describe: "hostname to listen on", default: "127.0.0.1", }, mdns: { type: "boolean" as const, describe: "enable mDNS service discovery (defaults hostname to 0.0.0.0)", default: false, }, "mdns-domain": { type: "string" as const, describe: "custom domain name for mDNS service (default: opencode.local)", default: "opencode.local", }, cors: { type: "string" as const, array: true, describe: "additional domains to allow for CORS", default: [] as string[], },} type NetworkOptions = InferredOptionTypes<typeof options> function withNetworkOptions<T>(yargs: Argv<T>) { return yargs.options(options)} async function resolveNetworkOptions(args: NetworkOptions) { const { Config } = await import("../../config/config") const config = await Config.getGlobal() const portExplicitlySet = process.argv.includes("--port") const hostnameExplicitlySet = process.argv.includes("--hostname") const mdnsExplicitlySet = process.argv.includes("--mdns") const mdnsDomainExplicitlySet = process.argv.includes("--mdns-domain") const corsExplicitlySet = process.argv.includes("--cors") const mdns = mdnsExplicitlySet ? args.mdns : (config?.server?.mdns ?? args.mdns) const mdnsDomain = mdnsDomainExplicitlySet ? args["mdns-domain"] : (config?.server?.mdnsDomain ?? args["mdns-domain"]) const port = portExplicitlySet ? args.port : (config?.server?.port ?? args.port) const hostname = hostnameExplicitlySet ? args.hostname : mdns && !config?.server?.hostname ? "0.0.0.0" : (config?.server?.hostname ?? args.hostname) const configCors = config?.server?.cors ?? [] const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] const cors = [...configCors, ...argsCors] return { hostname, port, mdns, mdnsDomain, cors }} function wrapData<T>(data: T) { return { data }} async function loadProviderCatalog(directory: string | undefined) { const [{ Config }] = await Promise.all([import("../../config/config")]) return withDirectory(directory, async () => { const config = await Config.get() const providers: any[] = [] if (config?.provider?.anthropic || config?.model?.startsWith("anthropic/") || process.env.ANTHROPIC_API_KEY) { providers.push({ id: "anthropic", name: "Anthropic", source: "config", env: ["ANTHROPIC_API_KEY"], options: { ...(config?.provider?.anthropic?.options ?? {}), }, models: { "claude-sonnet-4-20250514": { id: "claude-sonnet-4-20250514", providerID: "anthropic", api: { id: "claude-sonnet-4-20250514", url: "", npm: "@ai-sdk/anthropic" }, name: "Claude Sonnet 4", family: "claude-sonnet-4", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-05-14", variants: {}, }, "claude-opus-4-1-20250805": { id: "claude-opus-4-1-20250805", providerID: "anthropic", api: { id: "claude-opus-4-1-20250805", url: "", npm: "@ai-sdk/anthropic" }, name: "Claude Opus 4.1", family: "claude-opus-4-1", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-08-05", variants: {}, }, "claude-haiku-4-5-20251001": { id: "claude-haiku-4-5-20251001", providerID: "anthropic", api: { id: "claude-haiku-4-5-20251001", url: "", npm: "@ai-sdk/anthropic" }, name: "Claude Haiku 4.5", family: "claude-haiku-4-5", capabilities: { temperature: true, reasoning: false, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 16000 }, status: "active", options: {}, headers: {}, release_date: "2025-10-01", variants: {}, }, }, }) } if (config?.provider?.openai || config?.model?.startsWith("openai/") || process.env.OPENAI_API_KEY) { providers.push({ id: "openai", name: "OpenAI", source: "config", env: ["OPENAI_API_KEY"], options: { ...(config?.provider?.openai?.options ?? {}), }, models: { "gpt-5": { id: "gpt-5", providerID: "openai", api: { id: "gpt-5", url: "", npm: "@ai-sdk/openai" }, name: "GPT-5", family: "gpt-5", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, "gpt-5-mini": { id: "gpt-5-mini", providerID: "openai", api: { id: "gpt-5-mini", url: "", npm: "@ai-sdk/openai" }, name: "GPT-5 Mini", family: "gpt-5-mini", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 16000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, }, }) } if ( config?.provider?.google || config?.model?.startsWith("google/") || process.env.GOOGLE_GENERATIVE_AI_API_KEY ) { providers.push({ id: "google", name: "Google", source: "config", env: ["GOOGLE_GENERATIVE_AI_API_KEY"], options: { ...(config?.provider?.google?.options ?? {}), }, models: { "gemini-2.5-pro": { id: "gemini-2.5-pro", providerID: "google", api: { id: "gemini-2.5-pro", url: "", npm: "@ai-sdk/google" }, name: "Gemini 2.5 Pro", family: "gemini-2.5-pro", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, "gemini-2.5-flash": { id: "gemini-2.5-flash", providerID: "google", api: { id: "gemini-2.5-flash", url: "", npm: "@ai-sdk/google" }, name: "Gemini 2.5 Flash", family: "gemini-2.5-flash", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 16000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, },Lines 1–500 of 3681. Load more
#!/usr/bin/env node import { createHash } from "node:crypto"; import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs"; import { mkdtemp, readdir, readFile, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const SOURCE_REPOSITORY = "anomalyco/opencode"; const SOURCE_VERSION = "1.3.13"; const SOURCE_TARBALL_URL = `https://github.com/${SOURCE_REPOSITORY}/archive/refs/tags/v${SOURCE_VERSION}.tar.gz`; // Upstream `packages/app/package.json` pins `ghostty-web: github:anomalyco/ghostty-web#main`, // but the bundled bun.lock snapshots the SHA below. Because `main` is a moving ref, a fresh // `bun install --frozen-lockfile` resolves to whatever `main` points at *now* and fails the // lockfile check. Rewriting the manifest to the lockfile's SHA before install keeps frozen- // lockfile guarantees intact (i.e. CI still breaks loudly if either side drifts) without // trusting arbitrary new HEADs on the ghostty-web branch. const GHOSTTY_WEB_PINNED_SHA = "4af877d"; const __dirname = dirname(fileURLToPath(import.meta.url)); const packageDir = resolve(__dirname, ".."); const distDir = resolve(packageDir, "dist"); const cacheDir = resolve(packageDir, "node_modules", ".cache", "opencode-build"); const patchPath = resolve(packageDir, "upstream", `opencode-v${SOURCE_VERSION}.patch`); const bundleDir = resolve(distDir, "opencode-acp"); const manifestPath = resolve(distDir, "opencode-acp.manifest.json"); const SQL_JS_VERSION = "1.14.1"; const bunBin = resolve( packageDir, "node_modules", ".bin", process.platform === "win32" ? "bun.cmd" : "bun", ); function run(command, args, options = {}) { const result = spawnSync(command, args, { stdio: "inherit", ...options, }); if (result.status !== 0) { throw new Error( `Command failed (${result.status ?? "unknown"}): ${command} ${args.join(" ")}`, ); } return result; } function pinGhosttyWebRef(sourceRoot) { const manifestPath = resolve(sourceRoot, "packages", "app", "package.json"); const raw = readFileSync(manifestPath, "utf-8"); const movingRef = '"ghostty-web": "github:anomalyco/ghostty-web#main"'; const pinnedRef = `"ghostty-web": "github:anomalyco/ghostty-web#${GHOSTTY_WEB_PINNED_SHA}"`; if (raw.includes(pinnedRef)) return; if (!raw.includes(movingRef)) { throw new Error( `Expected ghostty-web ref ${movingRef} in ${manifestPath}; upstream layout changed — re-audit GHOSTTY_WEB_PINNED_SHA before updating.`, ); } writeFileSync(manifestPath, raw.replace(movingRef, pinnedRef)); } const PATCHED_SOURCE_FILES = [ "packages/opencode/src/cli/cmd/acp.ts", "packages/opencode/src/config/config.ts", "packages/opencode/src/plugin/index.ts", "packages/opencode/src/server/instance.ts", "packages/opencode/src/server/server.ts", ]; async function ensureNodeAcpPatch(sourceRoot, tarballPath) { const serverFile = resolve( sourceRoot, "packages", "opencode", "src", "server", "server.ts", ); const pluginFile = resolve( sourceRoot, "packages", "opencode", "src", "plugin", "index.ts", ); const acpFile = resolve( sourceRoot, "packages", "opencode", "src", "cli", "cmd", "acp.ts", ); const serverSource = readFileSync(serverFile, "utf-8"); const pluginSource = readFileSync(pluginFile, "utf-8"); const acpSource = readFileSync(acpFile, "utf-8"); const alreadyPatched = serverSource.includes('from "node:http"') && !serverSource.includes('from "hono/bun"') && pluginSource.includes("Bun shell is unavailable in the Node ACP build") && acpSource.includes("const server = await Server.listen(opts)"); if (alreadyPatched) { return; } const scratchRoot = await mkdtemp(join(tmpdir(), "agentos-opencode-patch-")); try { run("tar", ["-xzf", tarballPath, "--strip-components=1", "-C", scratchRoot]); run("git", ["apply", "--whitespace=nowarn", patchPath], { cwd: scratchRoot }); for (const relativePath of PATCHED_SOURCE_FILES) { copyFileSync( resolve(scratchRoot, relativePath), resolve(sourceRoot, relativePath), ); } } finally { rmSync(scratchRoot, { recursive: true, force: true }); } const verifiedServerSource = readFileSync(serverFile, "utf-8"); const verifiedPluginSource = readFileSync(pluginFile, "utf-8"); const verifiedAcpSource = readFileSync(acpFile, "utf-8"); if ( !verifiedServerSource.includes('from "node:http"') || verifiedServerSource.includes('from "hono/bun"') || !verifiedPluginSource.includes("Bun shell is unavailable in the Node ACP build") || !verifiedAcpSource.includes("const server = await Server.listen(opts)") ) { throw new Error("Failed to stage the Node ACP patches into the prepared OpenCode source tree"); } } async function downloadFile(url, destination) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`); } const buffer = Buffer.from(await response.arrayBuffer()); await writeFile(destination, buffer); } async function readMigrations(sourceRoot) { const migrationRoot = join(sourceRoot, "packages", "opencode", "migration"); const entries = (await readdir(migrationRoot, { withFileTypes: true })) .filter((entry) => entry.isDirectory() && /^\d{14}/.test(entry.name)) .map((entry) => entry.name) .sort(); return Promise.all( entries.map(async (name) => { const sql = await readFile( join(migrationRoot, name, "migration.sql"), "utf8", ); const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name); const timestamp = match ? Date.UTC( Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]), ) : 0; return { name, sql, timestamp }; }), ); } async function rewriteSourceFile(sourceRoot, relativePath, transform) { const filePath = resolve(sourceRoot, relativePath); const original = await readFile(filePath, "utf8"); const next = transform(original); if (next !== original) { await writeFile(filePath, next); } } async function ensureSqlJsDependency(sourceRoot) { const packageJsonPath = resolve( sourceRoot, "packages", "opencode", "package.json", ); const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); const dependencies = packageJson.dependencies ?? {}; const bunStoreDir = resolve(sourceRoot, "node_modules", ".bun"); const hasInstalledSqlJs = existsSync(bunStoreDir) && (await readdir(bunStoreDir)).some((entry) => entry.startsWith("sql.js@")); if (dependencies["sql.js"] === SQL_JS_VERSION && hasInstalledSqlJs) { return; } packageJson.dependencies = { ...dependencies, "sql.js": SQL_JS_VERSION, }; await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`); run(bunBin, ["install"], { cwd: sourceRoot }); } async function applyNodeAcpRuntimeTweaks(sourceRoot) { await writeFile( resolve(sourceRoot, "packages/opencode/src/cli/cmd/acp.ts"), `import type { Argv, InferredOptionTypes } from "yargs" import { cmd } from "./cmd" import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" import { Log } from "../../util/log" const options = { port: { type: "number" as const, describe: "port to listen on", default: 0, }, hostname: { type: "string" as const, describe: "hostname to listen on", default: "127.0.0.1", }, mdns: { type: "boolean" as const, describe: "enable mDNS service discovery (defaults hostname to 0.0.0.0)", default: false, }, "mdns-domain": { type: "string" as const, describe: "custom domain name for mDNS service (default: opencode.local)", default: "opencode.local", }, cors: { type: "string" as const, array: true, describe: "additional domains to allow for CORS", default: [] as string[], }, } type NetworkOptions = InferredOptionTypes<typeof options> function withNetworkOptions<T>(yargs: Argv<T>) { return yargs.options(options) } async function resolveNetworkOptions(args: NetworkOptions) { const { Config } = await import("../../config/config") const config = await Config.getGlobal() const portExplicitlySet = process.argv.includes("--port") const hostnameExplicitlySet = process.argv.includes("--hostname") const mdnsExplicitlySet = process.argv.includes("--mdns") const mdnsDomainExplicitlySet = process.argv.includes("--mdns-domain") const corsExplicitlySet = process.argv.includes("--cors") const mdns = mdnsExplicitlySet ? args.mdns : (config?.server?.mdns ?? args.mdns) const mdnsDomain = mdnsDomainExplicitlySet ? args["mdns-domain"] : (config?.server?.mdnsDomain ?? args["mdns-domain"]) const port = portExplicitlySet ? args.port : (config?.server?.port ?? args.port) const hostname = hostnameExplicitlySet ? args.hostname : mdns && !config?.server?.hostname ? "0.0.0.0" : (config?.server?.hostname ?? args.hostname) const configCors = config?.server?.cors ?? [] const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] const cors = [...configCors, ...argsCors] return { hostname, port, mdns, mdnsDomain, cors } } function wrapData<T>(data: T) { return { data } } async function loadProviderCatalog(directory: string | undefined) { const [{ Config }] = await Promise.all([import("../../config/config")]) return withDirectory(directory, async () => { const config = await Config.get() const providers: any[] = [] if (config?.provider?.anthropic || config?.model?.startsWith("anthropic/") || process.env.ANTHROPIC_API_KEY) { providers.push({ id: "anthropic", name: "Anthropic", source: "config", env: ["ANTHROPIC_API_KEY"], options: { ...(config?.provider?.anthropic?.options ?? {}), }, models: { "claude-sonnet-4-20250514": { id: "claude-sonnet-4-20250514", providerID: "anthropic", api: { id: "claude-sonnet-4-20250514", url: "", npm: "@ai-sdk/anthropic" }, name: "Claude Sonnet 4", family: "claude-sonnet-4", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-05-14", variants: {}, }, "claude-opus-4-1-20250805": { id: "claude-opus-4-1-20250805", providerID: "anthropic", api: { id: "claude-opus-4-1-20250805", url: "", npm: "@ai-sdk/anthropic" }, name: "Claude Opus 4.1", family: "claude-opus-4-1", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-08-05", variants: {}, }, "claude-haiku-4-5-20251001": { id: "claude-haiku-4-5-20251001", providerID: "anthropic", api: { id: "claude-haiku-4-5-20251001", url: "", npm: "@ai-sdk/anthropic" }, name: "Claude Haiku 4.5", family: "claude-haiku-4-5", capabilities: { temperature: true, reasoning: false, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 16000 }, status: "active", options: {}, headers: {}, release_date: "2025-10-01", variants: {}, }, }, }) } if (config?.provider?.openai || config?.model?.startsWith("openai/") || process.env.OPENAI_API_KEY) { providers.push({ id: "openai", name: "OpenAI", source: "config", env: ["OPENAI_API_KEY"], options: { ...(config?.provider?.openai?.options ?? {}), }, models: { "gpt-5": { id: "gpt-5", providerID: "openai", api: { id: "gpt-5", url: "", npm: "@ai-sdk/openai" }, name: "GPT-5", family: "gpt-5", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, "gpt-5-mini": { id: "gpt-5-mini", providerID: "openai", api: { id: "gpt-5-mini", url: "", npm: "@ai-sdk/openai" }, name: "GPT-5 Mini", family: "gpt-5-mini", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 16000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, }, }) } if ( config?.provider?.google || config?.model?.startsWith("google/") || process.env.GOOGLE_GENERATIVE_AI_API_KEY ) { providers.push({ id: "google", name: "Google", source: "config", env: ["GOOGLE_GENERATIVE_AI_API_KEY"], options: { ...(config?.provider?.google?.options ?? {}), }, models: { "gemini-2.5-pro": { id: "gemini-2.5-pro", providerID: "google", api: { id: "gemini-2.5-pro", url: "", npm: "@ai-sdk/google" }, name: "Gemini 2.5 Pro", family: "gemini-2.5-pro", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 32000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, "gemini-2.5-flash": { id: "gemini-2.5-flash", providerID: "google", api: { id: "gemini-2.5-flash", url: "", npm: "@ai-sdk/google" }, name: "Gemini 2.5 Flash", family: "gemini-2.5-flash", capabilities: { temperature: true, reasoning: true, attachment: true, toolcall: true, input: { text: true, audio: false, image: true, video: false, pdf: true }, output: { text: true, audio: false, image: false, video: false, pdf: false }, interleaved: false, }, cost: { input: 0, output: 0, cache: { read: 0, write: 0 } }, limit: { context: 200000, output: 16000 }, status: "active", options: {}, headers: {}, release_date: "2025-01-01", variants: {}, }, },