mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
Remove brave (#452)
* remove brave search * remove constraint on single search
This commit is contained in:
parent
678e645bbc
commit
07d34471f5
6 changed files with 14 additions and 143 deletions
|
|
@ -266,7 +266,7 @@ function ChatInputInner({
|
||||||
return () => window.removeEventListener('models-config-changed', handler)
|
return () => window.removeEventListener('models-config-changed', handler)
|
||||||
}, [loadModelConfig])
|
}, [loadModelConfig])
|
||||||
|
|
||||||
// Check search tool availability (brave or exa, or signed-in via gateway)
|
// Check search tool availability (exa or signed-in via gateway)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkSearch = async () => {
|
const checkSearch = async () => {
|
||||||
if (isRowboatConnected) {
|
if (isRowboatConnected) {
|
||||||
|
|
@ -275,17 +275,10 @@ function ChatInputInner({
|
||||||
}
|
}
|
||||||
let available = false
|
let available = false
|
||||||
try {
|
try {
|
||||||
const raw = await window.ipc.invoke('workspace:readFile', { path: 'config/brave-search.json' })
|
const raw = await window.ipc.invoke('workspace:readFile', { path: 'config/exa-search.json' })
|
||||||
const config = JSON.parse(raw.data)
|
const config = JSON.parse(raw.data)
|
||||||
if (config.apiKey) available = true
|
if (config.apiKey) available = true
|
||||||
} catch { /* not configured */ }
|
} catch { /* not configured */ }
|
||||||
if (!available) {
|
|
||||||
try {
|
|
||||||
const raw = await window.ipc.invoke('workspace:readFile', { path: 'config/exa-search.json' })
|
|
||||||
const config = JSON.parse(raw.data)
|
|
||||||
if (config.apiKey) available = true
|
|
||||||
} catch { /* not configured */ }
|
|
||||||
}
|
|
||||||
setSearchAvailable(available)
|
setSearchAvailable(available)
|
||||||
}
|
}
|
||||||
checkSearch()
|
checkSearch()
|
||||||
|
|
|
||||||
|
|
@ -115,35 +115,27 @@ export type WebSearchCardData = {
|
||||||
|
|
||||||
export const getWebSearchCardData = (tool: ToolCall): WebSearchCardData | null => {
|
export const getWebSearchCardData = (tool: ToolCall): WebSearchCardData | null => {
|
||||||
if (tool.name === 'web-search') {
|
if (tool.name === 'web-search') {
|
||||||
const input = normalizeToolInput(tool.input) as Record<string, unknown> | undefined
|
|
||||||
const result = tool.result as Record<string, unknown> | undefined
|
|
||||||
return {
|
|
||||||
query: (input?.query as string) || '',
|
|
||||||
results: (result?.results as WebSearchCardResult[]) || [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tool.name === 'research-search') {
|
|
||||||
const input = normalizeToolInput(tool.input) as Record<string, unknown> | undefined
|
const input = normalizeToolInput(tool.input) as Record<string, unknown> | undefined
|
||||||
const result = tool.result as Record<string, unknown> | undefined
|
const result = tool.result as Record<string, unknown> | undefined
|
||||||
const rawResults = (result?.results as Array<{
|
const rawResults = (result?.results as Array<{
|
||||||
title: string
|
title: string
|
||||||
url: string
|
url: string
|
||||||
|
description?: string
|
||||||
highlights?: string[]
|
highlights?: string[]
|
||||||
text?: string
|
text?: string
|
||||||
}>) || []
|
}>) || []
|
||||||
const mapped = rawResults.map((entry) => ({
|
const mapped = rawResults.map((entry) => ({
|
||||||
title: entry.title,
|
title: entry.title,
|
||||||
url: entry.url,
|
url: entry.url,
|
||||||
description: entry.highlights?.[0] || (entry.text ? entry.text.slice(0, 200) : ''),
|
description: entry.description || entry.highlights?.[0] || (entry.text ? entry.text.slice(0, 200) : ''),
|
||||||
}))
|
}))
|
||||||
const category = input?.category as string | undefined
|
const category = input?.category as string | undefined
|
||||||
return {
|
return {
|
||||||
query: (input?.query as string) || '',
|
query: (input?.query as string) || '',
|
||||||
results: mapped,
|
results: mapped,
|
||||||
title: category
|
title: (!category || category === 'general')
|
||||||
? `${category.charAt(0).toUpperCase() + category.slice(1)} search`
|
? 'Web search'
|
||||||
: 'Researched the web',
|
: `${category.charAt(0).toUpperCase() + category.slice(1)} search`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1059,7 +1059,7 @@ export async function* streamAgent({
|
||||||
}
|
}
|
||||||
if (searchEnabled) {
|
if (searchEnabled) {
|
||||||
loopLogger.log('search enabled, injecting search prompt');
|
loopLogger.log('search enabled, injecting search prompt');
|
||||||
instructionsWithDateTime += `\n\n# Search\nThe user has requested a search. Load the search skill and use web search or research search as needed to answer their query.`;
|
instructionsWithDateTime += `\n\n# Search\nThe user has requested a search. Use the web-search tool to answer their query.`;
|
||||||
}
|
}
|
||||||
let streamError: string | null = null;
|
let streamError: string | null = null;
|
||||||
for await (const event of streamLlm(
|
for await (const event of streamLlm(
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ ${runtimeContextPrompt}
|
||||||
- \`addMcpServer\`, \`listMcpServers\`, \`listMcpTools\`, \`executeMcpTool\` - MCP server management and execution
|
- \`addMcpServer\`, \`listMcpServers\`, \`listMcpTools\`, \`executeMcpTool\` - MCP server management and execution
|
||||||
- \`loadSkill\` - Skill loading
|
- \`loadSkill\` - Skill loading
|
||||||
- \`slack-checkConnection\`, \`slack-listAvailableTools\`, \`slack-executeAction\` - Slack integration (requires Slack to be connected via Composio). Use \`slack-listAvailableTools\` first to discover available tool slugs, then \`slack-executeAction\` to execute them.
|
- \`slack-checkConnection\`, \`slack-listAvailableTools\`, \`slack-executeAction\` - Slack integration (requires Slack to be connected via Composio). Use \`slack-listAvailableTools\` first to discover available tool slugs, then \`slack-executeAction\` to execute them.
|
||||||
- \`web-search\` and \`research-search\` - Web and research search tools (available when configured). **You MUST load the \`web-search\` skill before using either of these tools.** It tells you which tool to pick and how many searches to do.
|
- \`web-search\` - Search the web. Returns rich results with full text, highlights, and metadata. The \`category\` parameter defaults to \`general\` (full web search) — only use a specific category like \`news\`, \`company\`, \`research paper\` etc. when the query is clearly about that type. For everyday queries (weather, restaurants, prices, how-to), use \`general\`.
|
||||||
- \`app-navigation\` - Control the app UI: open notes, switch views, filter/search the knowledge base, manage saved views. **Load the \`app-navigation\` skill before using this tool.**
|
- \`app-navigation\` - Control the app UI: open notes, switch views, filter/search the knowledge base, manage saved views. **Load the \`app-navigation\` skill before using this tool.**
|
||||||
- \`save-to-memory\` - Save observations about the user to the agent memory system. Use this proactively during conversations.
|
- \`save-to-memory\` - Save observations about the user to the agent memory system. Use this proactively during conversations.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import organizeFilesSkill from "./organize-files/skill.js";
|
||||||
import slackSkill from "./slack/skill.js";
|
import slackSkill from "./slack/skill.js";
|
||||||
import backgroundAgentsSkill from "./background-agents/skill.js";
|
import backgroundAgentsSkill from "./background-agents/skill.js";
|
||||||
import createPresentationsSkill from "./create-presentations/skill.js";
|
import createPresentationsSkill from "./create-presentations/skill.js";
|
||||||
import webSearchSkill from "./web-search/skill.js";
|
|
||||||
import appNavigationSkill from "./app-navigation/skill.js";
|
import appNavigationSkill from "./app-navigation/skill.js";
|
||||||
|
|
||||||
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
@ -84,12 +84,6 @@ const definitions: SkillDefinition[] = [
|
||||||
summary: "Discovering, executing, and integrating MCP tools. Use this to check what external capabilities are available and execute MCP tools on behalf of users.",
|
summary: "Discovering, executing, and integrating MCP tools. Use this to check what external capabilities are available and execute MCP tools on behalf of users.",
|
||||||
content: mcpIntegrationSkill,
|
content: mcpIntegrationSkill,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "web-search",
|
|
||||||
title: "Web Search",
|
|
||||||
summary: "Searching the web or researching a topic. Guidance on when to use web-search vs research-search, and how many searches to do.",
|
|
||||||
content: webSearchSkill,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "deletion-guardrails",
|
id: "deletion-guardrails",
|
||||||
title: "Deletion Guardrails",
|
title: "Deletion Guardrails",
|
||||||
|
|
|
||||||
|
|
@ -1026,123 +1026,15 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Web Search (Brave Search API)
|
// Web Search (Exa Search API)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
'web-search': {
|
'web-search': {
|
||||||
description: 'Search the web using Brave Search. Returns web results with titles, URLs, and descriptions.',
|
description: 'Search the web for articles, blog posts, papers, companies, people, news, or explore a topic in depth. Returns rich results with full text, highlights, and metadata.',
|
||||||
inputSchema: z.object({
|
|
||||||
query: z.string().describe('The search query'),
|
|
||||||
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 () => {
|
|
||||||
if (await isSignedIn()) return true;
|
|
||||||
try {
|
|
||||||
const braveConfigPath = path.join(WorkDir, '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 {
|
|
||||||
const resultCount = Math.min(Math.max(count || 5, 1), 20);
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
q: query,
|
|
||||||
count: String(resultCount),
|
|
||||||
});
|
|
||||||
if (freshness) {
|
|
||||||
params.set('freshness', freshness);
|
|
||||||
}
|
|
||||||
|
|
||||||
let response: Response;
|
|
||||||
|
|
||||||
if (await isSignedIn()) {
|
|
||||||
// Use proxy
|
|
||||||
const accessToken = await getAccessToken();
|
|
||||||
response = await fetch(`${API_URL}/v1/search/brave?${params.toString()}`, {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Read API key from config
|
|
||||||
const braveConfigPath = path.join(WorkDir, 'config', 'brave-search.json');
|
|
||||||
|
|
||||||
let apiKey: string;
|
|
||||||
try {
|
|
||||||
const raw = await fs.readFile(braveConfigPath, 'utf8');
|
|
||||||
const config = JSON.parse(raw);
|
|
||||||
apiKey = config.apiKey;
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Brave Search API key not configured. Create ~/.rowboat/config/brave-search.json with { "apiKey": "<your-key>" }',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Brave Search API key is empty. Set "apiKey" in ~/.rowboat/config/brave-search.json',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params.toString()}`, {
|
|
||||||
headers: {
|
|
||||||
'X-Subscription-Token': apiKey,
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const body = await response.text();
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `Brave Search API error (${response.status}): ${body}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json() as {
|
|
||||||
web?: { results?: Array<{ title?: string; url?: string; description?: string }> };
|
|
||||||
};
|
|
||||||
|
|
||||||
const results = (data.web?.results || []).map((r: { title?: string; url?: string; description?: string }) => ({
|
|
||||||
title: r.title || '',
|
|
||||||
url: r.url || '',
|
|
||||||
description: r.description || '',
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
query,
|
|
||||||
results,
|
|
||||||
count: results.length,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Research Search (Exa Search API)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
'research-search': {
|
|
||||||
description: 'Use this for finding articles, blog posts, papers, companies, people, or exploring a topic in depth. Best for discovery and research where you need quality sources, not a quick fact.',
|
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
query: z.string().describe('The search query'),
|
query: z.string().describe('The search query'),
|
||||||
numResults: z.number().optional().describe('Number of results to return (default: 5, max: 20)'),
|
numResults: z.number().optional().describe('Number of results to return (default: 5, max: 20)'),
|
||||||
category: z.enum(['company', 'research paper', 'news', 'tweet', 'personal site', 'financial report', 'people']).optional().describe('Filter results by category'),
|
category: z.enum(['general', 'company', 'research paper', 'news', 'tweet', 'personal site', 'financial report', 'people']).optional().describe('Search category. Defaults to "general" which searches the entire web. Only use a specific category when the query is clearly about that type (e.g. "research paper" for academic papers, "company" for company info). For everyday queries like weather, restaurants, prices, how-to, etc., use "general" or omit entirely.'),
|
||||||
}),
|
}),
|
||||||
isAvailable: async () => {
|
isAvailable: async () => {
|
||||||
if (await isSignedIn()) return true;
|
if (await isSignedIn()) return true;
|
||||||
|
|
@ -1168,7 +1060,7 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
||||||
highlights: true,
|
highlights: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (category) {
|
if (category && category !== 'general') {
|
||||||
reqBody.category = category;
|
reqBody.category = category;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue