Skip to content

Commit 0f51055

Browse files
committed
Add context7
1 parent 0aec9ef commit 0f51055

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { createLogger } from '@sim/logger'
2+
import { BookOpen, Loader2, MinusCircle, XCircle } from 'lucide-react'
3+
import {
4+
BaseClientTool,
5+
type BaseClientToolMetadata,
6+
ClientToolCallState,
7+
} from '@/lib/copilot/tools/client/base-tool'
8+
import { ExecuteResponseSuccessSchema } from '@/lib/copilot/tools/shared/schemas'
9+
10+
interface SearchLibraryDocsArgs {
11+
library_name: string
12+
query: string
13+
version?: string
14+
}
15+
16+
export class SearchLibraryDocsClientTool extends BaseClientTool {
17+
static readonly id = 'search_library_docs'
18+
19+
constructor(toolCallId: string) {
20+
super(toolCallId, SearchLibraryDocsClientTool.id, SearchLibraryDocsClientTool.metadata)
21+
}
22+
23+
static readonly metadata: BaseClientToolMetadata = {
24+
displayNames: {
25+
[ClientToolCallState.generating]: { text: 'Reading docs', icon: Loader2 },
26+
[ClientToolCallState.pending]: { text: 'Reading docs', icon: Loader2 },
27+
[ClientToolCallState.executing]: { text: 'Reading docs', icon: Loader2 },
28+
[ClientToolCallState.success]: { text: 'Read docs', icon: BookOpen },
29+
[ClientToolCallState.error]: { text: 'Failed to read docs', icon: XCircle },
30+
[ClientToolCallState.aborted]: { text: 'Aborted reading docs', icon: XCircle },
31+
[ClientToolCallState.rejected]: { text: 'Skipped reading docs', icon: MinusCircle },
32+
},
33+
getDynamicText: (params, state) => {
34+
const libraryName = params?.library_name
35+
if (libraryName && typeof libraryName === 'string') {
36+
switch (state) {
37+
case ClientToolCallState.success:
38+
return `Read ${libraryName} docs`
39+
case ClientToolCallState.executing:
40+
case ClientToolCallState.generating:
41+
case ClientToolCallState.pending:
42+
return `Reading ${libraryName} docs`
43+
case ClientToolCallState.error:
44+
return `Failed to read ${libraryName} docs`
45+
case ClientToolCallState.aborted:
46+
return `Aborted reading ${libraryName} docs`
47+
case ClientToolCallState.rejected:
48+
return `Skipped reading ${libraryName} docs`
49+
}
50+
}
51+
return undefined
52+
},
53+
}
54+
55+
async execute(args?: SearchLibraryDocsArgs): Promise<void> {
56+
const logger = createLogger('SearchLibraryDocsClientTool')
57+
try {
58+
this.setState(ClientToolCallState.executing)
59+
const res = await fetch('/api/copilot/execute-copilot-server-tool', {
60+
method: 'POST',
61+
headers: { 'Content-Type': 'application/json' },
62+
body: JSON.stringify({ toolName: 'search_library_docs', payload: args || {} }),
63+
})
64+
if (!res.ok) {
65+
const txt = await res.text().catch(() => '')
66+
throw new Error(txt || `Server error (${res.status})`)
67+
}
68+
const json = await res.json()
69+
const parsed = ExecuteResponseSuccessSchema.parse(json)
70+
this.setState(ClientToolCallState.success)
71+
await this.markToolComplete(
72+
200,
73+
`Library documentation search complete for ${args?.library_name || 'unknown'}`,
74+
parsed.result
75+
)
76+
this.setState(ClientToolCallState.success)
77+
} catch (e: any) {
78+
logger.error('execute failed', { message: e?.message })
79+
this.setState(ClientToolCallState.error)
80+
await this.markToolComplete(500, e?.message || 'Library documentation search failed')
81+
}
82+
}
83+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { createLogger } from '@sim/logger'
2+
import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool'
3+
import { env } from '@/lib/core/config/env'
4+
import { executeTool } from '@/tools'
5+
6+
interface SearchLibraryDocsParams {
7+
library_name: string
8+
query: string
9+
version?: string
10+
}
11+
12+
interface SearchLibraryDocsResult {
13+
results: Array<{
14+
title: string
15+
link: string
16+
snippet: string
17+
position?: number
18+
}>
19+
query: string
20+
library: string
21+
version?: string
22+
totalResults: number
23+
}
24+
25+
export const searchLibraryDocsServerTool: BaseServerTool<
26+
SearchLibraryDocsParams,
27+
SearchLibraryDocsResult
28+
> = {
29+
name: 'search_library_docs',
30+
async execute(params: SearchLibraryDocsParams): Promise<SearchLibraryDocsResult> {
31+
const logger = createLogger('SearchLibraryDocsServerTool')
32+
const { library_name, query, version } = params
33+
34+
if (!library_name || typeof library_name !== 'string') {
35+
throw new Error('library_name is required')
36+
}
37+
if (!query || typeof query !== 'string') {
38+
throw new Error('query is required')
39+
}
40+
41+
// Build a search query that targets the library's documentation
42+
const searchQuery = version
43+
? `${library_name} ${version} documentation ${query}`
44+
: `${library_name} documentation ${query}`
45+
46+
logger.info('Searching library documentation', {
47+
library: library_name,
48+
query,
49+
version,
50+
fullSearchQuery: searchQuery,
51+
})
52+
53+
// Check which API keys are available
54+
const hasExaApiKey = Boolean(env.EXA_API_KEY && String(env.EXA_API_KEY).length > 0)
55+
const hasSerperApiKey = Boolean(env.SERPER_API_KEY && String(env.SERPER_API_KEY).length > 0)
56+
57+
// Try Exa first if available (better for documentation searches)
58+
if (hasExaApiKey) {
59+
try {
60+
logger.debug('Attempting exa_search for library docs', { library: library_name })
61+
const exaResult = await executeTool('exa_search', {
62+
query: searchQuery,
63+
numResults: 10,
64+
type: 'auto',
65+
apiKey: env.EXA_API_KEY || '',
66+
})
67+
68+
const exaResults = (exaResult as any)?.output?.results || []
69+
const count = Array.isArray(exaResults) ? exaResults.length : 0
70+
71+
logger.info('exa_search for library docs completed', {
72+
success: exaResult.success,
73+
resultsCount: count,
74+
library: library_name,
75+
})
76+
77+
if (exaResult.success && count > 0) {
78+
const transformedResults = exaResults.map((result: any, idx: number) => ({
79+
title: result.title || '',
80+
link: result.url || '',
81+
snippet: result.text || result.summary || '',
82+
position: idx + 1,
83+
}))
84+
85+
return {
86+
results: transformedResults,
87+
query,
88+
library: library_name,
89+
version,
90+
totalResults: count,
91+
}
92+
}
93+
94+
logger.warn('exa_search returned no results for library docs, falling back to Serper', {
95+
library: library_name,
96+
})
97+
} catch (exaError: any) {
98+
logger.warn('exa_search failed for library docs, falling back to Serper', {
99+
error: exaError?.message,
100+
library: library_name,
101+
})
102+
}
103+
}
104+
105+
// Fall back to Serper if Exa failed or wasn't available
106+
if (!hasSerperApiKey) {
107+
throw new Error('No search API keys available (EXA_API_KEY or SERPER_API_KEY required)')
108+
}
109+
110+
try {
111+
logger.debug('Calling serper_search for library docs', { library: library_name })
112+
const result = await executeTool('serper_search', {
113+
query: searchQuery,
114+
num: 10,
115+
type: 'search',
116+
apiKey: env.SERPER_API_KEY || '',
117+
})
118+
119+
const results = (result as any)?.output?.searchResults || []
120+
const count = Array.isArray(results) ? results.length : 0
121+
122+
logger.info('serper_search for library docs completed', {
123+
success: result.success,
124+
resultsCount: count,
125+
library: library_name,
126+
})
127+
128+
if (!result.success) {
129+
logger.error('serper_search failed for library docs', { error: (result as any)?.error })
130+
throw new Error((result as any)?.error || 'Library documentation search failed')
131+
}
132+
133+
// Transform serper results to match expected format
134+
const transformedResults = results.map((result: any, idx: number) => ({
135+
title: result.title || '',
136+
link: result.link || '',
137+
snippet: result.snippet || '',
138+
position: idx + 1,
139+
}))
140+
141+
return {
142+
results: transformedResults,
143+
query,
144+
library: library_name,
145+
version,
146+
totalResults: count,
147+
}
148+
} catch (e: any) {
149+
logger.error('search_library_docs execution error', {
150+
message: e?.message,
151+
library: library_name,
152+
})
153+
throw e
154+
}
155+
},
156+
}

apps/sim/lib/copilot/tools/server/router.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getBlocksAndToolsServerTool } from '@/lib/copilot/tools/server/blocks/g
66
import { getBlocksMetadataServerTool } from '@/lib/copilot/tools/server/blocks/get-blocks-metadata-tool'
77
import { getTriggerBlocksServerTool } from '@/lib/copilot/tools/server/blocks/get-trigger-blocks'
88
import { searchDocumentationServerTool } from '@/lib/copilot/tools/server/docs/search-documentation'
9+
import { searchLibraryDocsServerTool } from '@/lib/copilot/tools/server/docs/search-library-docs'
910
import {
1011
KnowledgeBaseInput,
1112
knowledgeBaseServerTool,
@@ -47,6 +48,7 @@ serverToolRegistry[getTriggerBlocksServerTool.name] = getTriggerBlocksServerTool
4748
serverToolRegistry[editWorkflowServerTool.name] = editWorkflowServerTool
4849
serverToolRegistry[getWorkflowConsoleServerTool.name] = getWorkflowConsoleServerTool
4950
serverToolRegistry[searchDocumentationServerTool.name] = searchDocumentationServerTool
51+
serverToolRegistry[searchLibraryDocsServerTool.name] = searchLibraryDocsServerTool
5052
serverToolRegistry[searchOnlineServerTool.name] = searchOnlineServerTool
5153
serverToolRegistry[setEnvironmentVariablesServerTool.name] = setEnvironmentVariablesServerTool
5254
serverToolRegistry[getCredentialsServerTool.name] = getCredentialsServerTool

apps/sim/stores/panel/copilot/store.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { RememberDebugClientTool } from '@/lib/copilot/tools/client/other/rememb
4242
import { ResearchClientTool } from '@/lib/copilot/tools/client/other/research'
4343
import { SearchDocumentationClientTool } from '@/lib/copilot/tools/client/other/search-documentation'
4444
import { SearchErrorsClientTool } from '@/lib/copilot/tools/client/other/search-errors'
45+
import { SearchLibraryDocsClientTool } from '@/lib/copilot/tools/client/other/search-library-docs'
4546
import { SearchOnlineClientTool } from '@/lib/copilot/tools/client/other/search-online'
4647
import { SearchPatternsClientTool } from '@/lib/copilot/tools/client/other/search-patterns'
4748
import { SleepClientTool } from '@/lib/copilot/tools/client/other/sleep'
@@ -116,6 +117,7 @@ const CLIENT_TOOL_INSTANTIATORS: Record<string, (id: string) => any> = {
116117
get_trigger_blocks: (id) => new GetTriggerBlocksClientTool(id),
117118
search_online: (id) => new SearchOnlineClientTool(id),
118119
search_documentation: (id) => new SearchDocumentationClientTool(id),
120+
search_library_docs: (id) => new SearchLibraryDocsClientTool(id),
119121
search_patterns: (id) => new SearchPatternsClientTool(id),
120122
search_errors: (id) => new SearchErrorsClientTool(id),
121123
remember_debug: (id) => new RememberDebugClientTool(id),
@@ -174,6 +176,7 @@ export const CLASS_TOOL_METADATA: Record<string, BaseClientToolMetadata | undefi
174176
get_trigger_blocks: (GetTriggerBlocksClientTool as any)?.metadata,
175177
search_online: (SearchOnlineClientTool as any)?.metadata,
176178
search_documentation: (SearchDocumentationClientTool as any)?.metadata,
179+
search_library_docs: (SearchLibraryDocsClientTool as any)?.metadata,
177180
search_patterns: (SearchPatternsClientTool as any)?.metadata,
178181
search_errors: (SearchErrorsClientTool as any)?.metadata,
179182
remember_debug: (RememberDebugClientTool as any)?.metadata,

0 commit comments

Comments
 (0)