diff --git a/apps/x/packages/core/src/agents/runtime.ts b/apps/x/packages/core/src/agents/runtime.ts index 0246cc2f..ce34d732 100644 --- a/apps/x/packages/core/src/agents/runtime.ts +++ b/apps/x/packages/core/src/agents/runtime.ts @@ -401,6 +401,13 @@ async function buildTools(agent: z.infer): Promise { const tools: ToolSet = {}; for (const [name, tool] of Object.entries(agent.tools ?? {})) { try { + // Skip builtin tools that declare themselves unavailable + if (tool.type === 'builtin') { + const builtin = BuiltinTools[tool.name]; + if (builtin?.isAvailable && !(await builtin.isAvailable())) { + continue; + } + } tools[name] = await mapAgentTool(tool); } catch (error) { console.error(`Error mapping tool ${name}:`, error); diff --git a/apps/x/packages/core/src/application/lib/builtin-tools.ts b/apps/x/packages/core/src/application/lib/builtin-tools.ts index cda3c580..1fce5702 100644 --- a/apps/x/packages/core/src/application/lib/builtin-tools.ts +++ b/apps/x/packages/core/src/application/lib/builtin-tools.ts @@ -33,6 +33,7 @@ const BuiltinToolsSchema = z.record(z.string(), z.object({ input: z.any(), // (input, ctx?) => Promise output: z.promise(z.any()), }), + isAvailable: z.custom<() => Promise>().optional(), })); type SlackToolHint = { @@ -1277,6 +1278,17 @@ export const BuiltinTools: z.infer = { count: z.number().optional().describe('Number of results to return (default: 5, max: 20)'), freshness: z.string().optional().describe('Filter by freshness: pd (past day), pw (past week), pm (past month), py (past year)'), }), + isAvailable: async () => { + try { + const homedir = process.env.HOME || process.env.USERPROFILE || ''; + const braveConfigPath = path.join(homedir, '.rowboat', 'config', 'brave-search.json'); + const raw = await fs.readFile(braveConfigPath, 'utf8'); + const config = JSON.parse(raw); + return !!config.apiKey; + } catch { + return false; + } + }, execute: async ({ query, count, freshness }: { query: string; count?: number; freshness?: string }) => { try { // Read API key from config