@llmindset/hf-mcp 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/docs-search/doc-fetch.d.ts +24 -0
  2. package/dist/docs-search/doc-fetch.d.ts.map +1 -0
  3. package/dist/docs-search/doc-fetch.js +54 -0
  4. package/dist/docs-search/doc-fetch.js.map +1 -0
  5. package/dist/docs-search/doc-fetch.test.d.ts +2 -0
  6. package/dist/docs-search/doc-fetch.test.d.ts.map +1 -0
  7. package/dist/docs-search/doc-fetch.test.js +52 -0
  8. package/dist/docs-search/doc-fetch.test.js.map +1 -0
  9. package/dist/docs-search/doc-mappings.d.ts +7 -0
  10. package/dist/docs-search/doc-mappings.d.ts.map +1 -0
  11. package/dist/docs-search/doc-mappings.js +75 -0
  12. package/dist/docs-search/doc-mappings.js.map +1 -0
  13. package/dist/docs-search/docs-semantic-search.d.ts +41 -0
  14. package/dist/docs-search/docs-semantic-search.d.ts.map +1 -0
  15. package/dist/docs-search/docs-semantic-search.js +138 -0
  16. package/dist/docs-search/docs-semantic-search.js.map +1 -0
  17. package/dist/docs-search/docs-semantic-search.test.d.ts +2 -0
  18. package/dist/docs-search/docs-semantic-search.test.d.ts.map +1 -0
  19. package/dist/docs-search/docs-semantic-search.test.js +244 -0
  20. package/dist/docs-search/docs-semantic-search.test.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/tool-ids.d.ts +6 -4
  26. package/dist/tool-ids.d.ts.map +1 -1
  27. package/dist/tool-ids.js +7 -16
  28. package/dist/tool-ids.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/docs-search/doc-fetch.test.ts +74 -0
  31. package/src/docs-search/doc-fetch.ts +93 -0
  32. package/src/docs-search/doc-mappings.ts +79 -0
  33. package/src/docs-search/docs-semantic-search.test.ts +318 -0
  34. package/src/docs-search/docs-semantic-search.ts +207 -0
  35. package/src/index.ts +2 -0
  36. package/src/tool-ids.ts +9 -18
@@ -0,0 +1,318 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { DocSearchTool } from './docs-semantic-search.js';
3
+
4
+ // Mock the fetch function
5
+ const mockFetch = vi.fn();
6
+ global.fetch = mockFetch as typeof fetch;
7
+
8
+ describe('DocSearchTool', () => {
9
+ let docSearchTool: DocSearchTool;
10
+
11
+ beforeEach(() => {
12
+ docSearchTool = new DocSearchTool();
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ describe('search', () => {
17
+ it('should return "No query provided" when query is empty', async () => {
18
+ const result = await docSearchTool.search('');
19
+ expect(result).toBe('No query provided');
20
+ });
21
+
22
+ it('should return no results message when API returns empty array', async () => {
23
+ mockFetch.mockResolvedValueOnce({
24
+ ok: true,
25
+ json: () => Promise.resolve([]),
26
+ });
27
+
28
+ const result = await docSearchTool.search('nonexistent');
29
+ expect(result).toBe(`No documentation found for query 'nonexistent'`);
30
+ });
31
+
32
+ it('should return no results message with product filter', async () => {
33
+ mockFetch.mockResolvedValueOnce({
34
+ ok: true,
35
+ json: () => Promise.resolve([]),
36
+ });
37
+
38
+ const result = await docSearchTool.search('nonexistent', 'hub');
39
+ expect(result).toBe(`No documentation found for query 'nonexistent' in product 'hub'`);
40
+ });
41
+
42
+ it('should format results grouped by product and page', async () => {
43
+ const sampleResults = [
44
+ {
45
+ text: 'Download a comprehensive CSV file containing analytics',
46
+ product: 'hub',
47
+ heading1: 'Analytics',
48
+ source_page_url: 'https://huggingface.co/docs/hub/enterprise-hub-analytics#export-analytics-as-csv',
49
+ source_page_title: 'Enterprise-hub-analytics',
50
+ heading2: 'Export Analytics as CSV',
51
+ },
52
+ {
53
+ text: 'View analytics for your repositories',
54
+ product: 'hub',
55
+ heading1: 'Analytics',
56
+ source_page_url: 'https://huggingface.co/docs/hub/enterprise-hub-analytics#export-analytics-as-csv',
57
+ source_page_title: 'Enterprise-hub-analytics',
58
+ heading2: 'View Analytics',
59
+ },
60
+ {
61
+ text: 'In this quickstart, you will learn how to use the dataset viewer REST API',
62
+ product: 'dataset-viewer',
63
+ heading1: 'Quickstart',
64
+ source_page_url: 'https://huggingface.co/docs/dataset-viewer/quick_start#quickstart',
65
+ source_page_title: 'Quick start',
66
+ },
67
+ ];
68
+
69
+ mockFetch.mockResolvedValueOnce({
70
+ ok: true,
71
+ json: () => Promise.resolve(sampleResults),
72
+ });
73
+
74
+ const result = await docSearchTool.search('analytics');
75
+
76
+ // Check header
77
+ expect(result).toContain('# Documentation Search Results for "analytics"');
78
+ expect(result).toContain('Found 3 results');
79
+
80
+ // Check product grouping - hub should come before dataset-viewer (hub has 2 results, dataset-viewer has 1)
81
+ const hubIndex = result.indexOf('## Results for Product: hub');
82
+ const datasetViewerIndex = result.indexOf('## Results for Product: dataset-viewer');
83
+ expect(hubIndex).toBeLessThan(datasetViewerIndex);
84
+ expect(hubIndex).toBeGreaterThan(-1);
85
+ expect(datasetViewerIndex).toBeGreaterThan(-1);
86
+
87
+ // Check that result counts are shown
88
+ expect(result).toContain('## Results for Product: hub (2 results)');
89
+ expect(result).toContain('## Results for Product: dataset-viewer (1 results)');
90
+
91
+ // Check page links
92
+ expect(result).toContain('### Results from [Analytics](https://huggingface.co/docs/hub/enterprise-hub-analytics#export-analytics-as-csv)');
93
+ expect(result).toContain('### Results from [Quickstart](https://huggingface.co/docs/dataset-viewer/quick_start#quickstart)');
94
+
95
+ // Check excerpts with heading2
96
+ expect(result).toContain('**Excerpt from "Export Analytics as CSV":**');
97
+ expect(result).toContain('**Excerpt from "View Analytics":**');
98
+
99
+ // Check excerpt content appears as plain text
100
+ expect(result).toContain('Download a comprehensive CSV file containing analytics');
101
+ expect(result).toContain('View analytics for your repositories');
102
+ expect(result).toContain('In this quickstart, you will learn how to use the dataset viewer REST API');
103
+
104
+ // Check footer
105
+ expect(result).toContain('Use the "fetch_hf_doc" tool to download a specific document.');
106
+ });
107
+
108
+ it('should handle results without heading2', async () => {
109
+ const sampleResults = [
110
+ {
111
+ text: 'This is a simple text without heading2',
112
+ product: 'transformers',
113
+ heading1: 'Introduction',
114
+ source_page_url: 'https://huggingface.co/docs/transformers/index',
115
+ source_page_title: 'Transformers',
116
+ },
117
+ ];
118
+
119
+ mockFetch.mockResolvedValueOnce({
120
+ ok: true,
121
+ json: () => Promise.resolve(sampleResults),
122
+ });
123
+
124
+ const result = await docSearchTool.search('transformers');
125
+
126
+ // Should not contain "Excerpt from" when heading2 is missing
127
+ expect(result).not.toContain('**Excerpt from');
128
+ expect(result).toContain('This is a simple text without heading2');
129
+ });
130
+
131
+ it('should properly escape markdown special characters', async () => {
132
+ const sampleResults = [
133
+ {
134
+ text: 'Text with [brackets] and *asterisks* and _underscores_',
135
+ product: 'hub',
136
+ heading1: 'Special * Characters',
137
+ source_page_url: 'https://huggingface.co/docs/hub/test',
138
+ source_page_title: 'Test',
139
+ heading2: 'Section with [brackets]',
140
+ },
141
+ ];
142
+
143
+ mockFetch.mockResolvedValueOnce({
144
+ ok: true,
145
+ json: () => Promise.resolve(sampleResults),
146
+ });
147
+
148
+ const result = await docSearchTool.search('special');
149
+
150
+ // Check that special characters are escaped in headings and page titles
151
+ expect(result).toContain('Special \\* Characters');
152
+ // Note: heading2 appears in bold text, which doesn't get escaped
153
+ expect(result).toContain('"Section with [brackets]"');
154
+ });
155
+
156
+ it('should clean HTML tags from text', async () => {
157
+ const sampleResults = [
158
+ {
159
+ text: 'Text with <div class="test">HTML tags</div> and <img src="test.png" alt="image"/>',
160
+ product: 'hub',
161
+ heading1: 'HTML Test',
162
+ source_page_url: 'https://huggingface.co/docs/hub/html-test',
163
+ source_page_title: 'HTML Test',
164
+ },
165
+ ];
166
+
167
+ mockFetch.mockResolvedValueOnce({
168
+ ok: true,
169
+ json: () => Promise.resolve(sampleResults),
170
+ });
171
+
172
+ const result = await docSearchTool.search('html');
173
+
174
+ // HTML tags should be removed
175
+ expect(result).toContain('Text with HTML tags and');
176
+ expect(result).not.toContain('<div');
177
+ expect(result).not.toContain('<img');
178
+ });
179
+
180
+ it('should sort multiple products and pages correctly by count', async () => {
181
+ const sampleResults = [
182
+ // Hub has 3 results (should be first)
183
+ {
184
+ text: 'Result from hub page 1 - first',
185
+ product: 'hub',
186
+ heading1: 'Page 1',
187
+ source_page_url: 'https://huggingface.co/docs/hub/page1',
188
+ source_page_title: 'Page 1',
189
+ },
190
+ {
191
+ text: 'Result from hub page 1 - second',
192
+ product: 'hub',
193
+ heading1: 'Page 1',
194
+ source_page_url: 'https://huggingface.co/docs/hub/page1',
195
+ source_page_title: 'Page 1',
196
+ },
197
+ {
198
+ text: 'Result from hub page 2',
199
+ product: 'hub',
200
+ heading1: 'Page 2',
201
+ source_page_url: 'https://huggingface.co/docs/hub/page2',
202
+ source_page_title: 'Page 2',
203
+ },
204
+ // Transformers has 1 result (should be second)
205
+ {
206
+ text: 'Result from transformers',
207
+ product: 'transformers',
208
+ heading1: 'Transformers Page',
209
+ source_page_url: 'https://huggingface.co/docs/transformers/page1',
210
+ source_page_title: 'Transformers',
211
+ },
212
+ // Datasets has 1 result (should be third)
213
+ {
214
+ text: 'Result from datasets',
215
+ product: 'datasets',
216
+ heading1: 'Datasets Page',
217
+ source_page_url: 'https://huggingface.co/docs/datasets/page1',
218
+ source_page_title: 'Datasets',
219
+ },
220
+ ];
221
+
222
+ mockFetch.mockResolvedValueOnce({
223
+ ok: true,
224
+ json: () => Promise.resolve(sampleResults),
225
+ });
226
+
227
+ const result = await docSearchTool.search('test');
228
+
229
+ // Check product order by count: hub (3) > transformers (1) = datasets (1)
230
+ const hubIndex = result.indexOf('## Results for Product: hub');
231
+ const transformersIndex = result.indexOf('## Results for Product: transformers');
232
+ const datasetsIndex = result.indexOf('## Results for Product: datasets');
233
+
234
+ expect(hubIndex).toBeLessThan(transformersIndex);
235
+ expect(hubIndex).toBeLessThan(datasetsIndex);
236
+
237
+ // Check that hub shows total count
238
+ expect(result).toContain('## Results for Product: hub (3 results)');
239
+
240
+ // Check page order within hub product: page1 (2 results) should come before page2 (1 result)
241
+ const page1Index = result.indexOf('https://huggingface.co/docs/hub/page1');
242
+ const page2Index = result.indexOf('https://huggingface.co/docs/hub/page2');
243
+ expect(page1Index).toBeLessThan(page2Index);
244
+
245
+ // Check that page1 shows its multiple results count
246
+ expect(result).toContain('### Results from [Page 1](https://huggingface.co/docs/hub/page1) (2 results)');
247
+ });
248
+
249
+ it('should include product filter in API call when provided', async () => {
250
+ mockFetch.mockResolvedValueOnce({
251
+ ok: true,
252
+ json: () => Promise.resolve([]),
253
+ });
254
+
255
+ await docSearchTool.search('test', 'hub');
256
+
257
+ expect(mockFetch).toHaveBeenCalledWith(
258
+ expect.stringContaining('q=test&product=hub'),
259
+ expect.any(Object)
260
+ );
261
+ });
262
+
263
+ it('should handle API errors gracefully', async () => {
264
+ mockFetch.mockRejectedValueOnce(new Error('Network error'));
265
+
266
+ await expect(docSearchTool.search('test')).rejects.toThrow('Failed to search documentation:');
267
+ });
268
+ });
269
+
270
+ describe('groupResults', () => {
271
+ it('should group results by product and page URL', async () => {
272
+ const sampleResults = [
273
+ {
274
+ text: 'Result 1',
275
+ product: 'hub',
276
+ heading1: 'Page 1',
277
+ source_page_url: 'https://example.com/page1',
278
+ source_page_title: 'Page 1',
279
+ },
280
+ {
281
+ text: 'Result 2',
282
+ product: 'hub',
283
+ heading1: 'Page 1',
284
+ source_page_url: 'https://example.com/page1',
285
+ source_page_title: 'Page 1',
286
+ },
287
+ {
288
+ text: 'Result 3',
289
+ product: 'transformers',
290
+ heading1: 'Page 2',
291
+ source_page_url: 'https://example.com/page2',
292
+ source_page_title: 'Page 2',
293
+ },
294
+ ];
295
+
296
+ mockFetch.mockResolvedValueOnce({
297
+ ok: true,
298
+ json: () => Promise.resolve(sampleResults),
299
+ });
300
+
301
+ const result = await docSearchTool.search('test');
302
+
303
+ // Verify grouping structure in output
304
+ expect(result).toContain('## Results for Product: hub');
305
+ expect(result).toContain('## Results for Product: transformers');
306
+
307
+ // Verify that both results from the same page are together
308
+ const result1Index = result.indexOf('Result 1');
309
+ const result2Index = result.indexOf('Result 2');
310
+ const result3Index = result.indexOf('Result 3');
311
+
312
+ // Results 1 and 2 should be close together (same page)
313
+ expect(Math.abs(result2Index - result1Index)).toBeLessThan(100);
314
+ // Result 3 should be further away (different product)
315
+ expect(Math.abs(result3Index - result1Index)).toBeGreaterThan(50);
316
+ });
317
+ });
318
+ });
@@ -0,0 +1,207 @@
1
+ import { z } from 'zod';
2
+ import { HfApiCall } from '../hf-api-call.js';
3
+ import { escapeMarkdown } from '../utilities.js';
4
+ import { DOC_FETCH_CONFIG } from './doc-fetch.js';
5
+
6
+ export const DOCS_SEMANTIC_SEARCH_CONFIG = {
7
+ name: 'docs_search',
8
+ description:
9
+ 'Search the Hugging Face documentation library with semantic search. Returns documentation excerpts ' +
10
+ 'grouped by Product and document page.',
11
+ schema: z.object({
12
+ query: z
13
+ .string()
14
+ .min(3, 'Supply at least one search term')
15
+ .max(200, 'Query too long')
16
+ .describe('Semantic search query'),
17
+ product: z
18
+ .string()
19
+ .optional()
20
+ .describe('Filter by specific product (e.g., "hub", "dataset-viewer", "transformers")'),
21
+ }),
22
+ annotations: {
23
+ title: 'Hugging Face Documentation Search',
24
+ destructiveHint: false,
25
+ readOnlyHint: true,
26
+ openWorldHint: true,
27
+ },
28
+ } as const;
29
+
30
+ export type DocSearchParams = z.infer<typeof DOCS_SEMANTIC_SEARCH_CONFIG.schema>;
31
+
32
+ interface DocSearchResult {
33
+ text: string;
34
+ product: string;
35
+ heading1: string;
36
+ source_page_url: string;
37
+ source_page_title: string;
38
+ heading2?: string;
39
+ }
40
+
41
+ interface DocSearchApiParams {
42
+ q: string;
43
+ product?: string;
44
+ }
45
+
46
+ /**
47
+ * Use the Hugging Face Semantic Document Search API
48
+ */
49
+ export class DocSearchTool extends HfApiCall<DocSearchApiParams, DocSearchResult[]> {
50
+ /**
51
+ * @param apiUrl The URL of the Hugging Face document search API
52
+ * @param hfToken Optional Hugging Face token for API access
53
+ */
54
+ constructor(hfToken?: string, apiUrl = 'https://hf.co/api/docs/search') {
55
+ super(apiUrl, hfToken);
56
+ }
57
+
58
+ /**
59
+ * @param query Search query string (e.g. "rate limits", "analytics")
60
+ * @param product Optional product filter
61
+ */
62
+ async search(query: string, product?: string): Promise<string> {
63
+ try {
64
+ if (!query) return 'No query provided';
65
+
66
+ const params: DocSearchApiParams = { q: query.toLowerCase() };
67
+ if (product) {
68
+ params.product = product;
69
+ }
70
+
71
+ const results = await this.callApi<DocSearchResult[]>(params);
72
+
73
+ if (results.length === 0) {
74
+ return product
75
+ ? `No documentation found for query '${query}' in product '${product}'`
76
+ : `No documentation found for query '${query}'`;
77
+ }
78
+
79
+ return formatSearchResults(query, results, product);
80
+ } catch (error) {
81
+ if (error instanceof Error) {
82
+ throw new Error(`Failed to search documentation: ${error.message}`);
83
+ }
84
+ throw error;
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Group results by product and source page URL
91
+ */
92
+ function groupResults(results: DocSearchResult[]): Map<string, Map<string, DocSearchResult[]>> {
93
+ const grouped = new Map<string, Map<string, DocSearchResult[]>>();
94
+
95
+ for (const result of results) {
96
+ if (!grouped.has(result.product)) {
97
+ grouped.set(result.product, new Map());
98
+ }
99
+
100
+ const productGroup = grouped.get(result.product);
101
+ if (!productGroup) continue;
102
+
103
+ if (!productGroup.has(result.source_page_url)) {
104
+ productGroup.set(result.source_page_url, []);
105
+ }
106
+
107
+ const pageResults = productGroup.get(result.source_page_url);
108
+ if (pageResults) {
109
+ pageResults.push(result);
110
+ }
111
+ }
112
+
113
+ return grouped;
114
+ }
115
+
116
+ /**
117
+ * Format a single result excerpt
118
+ */
119
+ function formatExcerpt(result: DocSearchResult): string {
120
+ const lines: string[] = [];
121
+
122
+ if (result.heading2) {
123
+ lines.push(`**Excerpt from "${escapeMarkdown(result.heading2)}":**`);
124
+ }
125
+
126
+ // Clean up the text - remove HTML tags if any
127
+ const cleanText = result.text
128
+ .replace(/<[^>]*>/g, '')
129
+ .replace(/\n\s*\n/g, '\n')
130
+ .trim();
131
+
132
+ lines.push(cleanText);
133
+ lines.push('');
134
+
135
+ return lines.join('\n');
136
+ }
137
+
138
+ /**
139
+ * Format search results grouped by product and page
140
+ */
141
+ function formatSearchResults(query: string, results: DocSearchResult[], productFilter?: string): string {
142
+ const lines: string[] = [];
143
+
144
+ // Header
145
+ const filterText = productFilter ? ` (filtered by product: ${productFilter})` : '';
146
+ lines.push(`# Documentation Search Results for "${escapeMarkdown(query)}"${filterText}`);
147
+ lines.push('');
148
+ lines.push(`Found ${results.length} results`);
149
+ lines.push('');
150
+
151
+ // Group results
152
+ const grouped = groupResults(results);
153
+
154
+ // Sort products by count (most hits first)
155
+ const sortedProducts = Array.from(grouped.keys()).sort((a, b) => {
156
+ const productGroupA = grouped.get(a);
157
+ const productGroupB = grouped.get(b);
158
+ if (!productGroupA || !productGroupB) return 0;
159
+
160
+ const countA = Array.from(productGroupA.values()).reduce((sum, arr) => sum + arr.length, 0);
161
+ const countB = Array.from(productGroupB.values()).reduce((sum, arr) => sum + arr.length, 0);
162
+ return countB - countA; // Descending order
163
+ });
164
+
165
+ for (const product of sortedProducts) {
166
+ const productGroup = grouped.get(product);
167
+ if (!productGroup) continue;
168
+
169
+ const totalProductHits = Array.from(productGroup.values()).reduce((sum, arr) => sum + arr.length, 0);
170
+ lines.push(`## Results for Product: ${escapeMarkdown(product)} (${totalProductHits} results)`);
171
+ lines.push('');
172
+
173
+ // Sort URLs within each product by count (most hits first)
174
+ const sortedUrls = Array.from(productGroup.keys()).sort((a, b) => {
175
+ const pageResultsA = productGroup.get(a);
176
+ const pageResultsB = productGroup.get(b);
177
+ if (!pageResultsA || !pageResultsB) return 0;
178
+ return pageResultsB.length - pageResultsA.length;
179
+ });
180
+
181
+ for (const url of sortedUrls) {
182
+ const pageResults = productGroup.get(url);
183
+ if (!pageResults || pageResults.length === 0) continue;
184
+ const firstResult = pageResults[0];
185
+
186
+ // Skip if no results (shouldn't happen but TypeScript safety)
187
+ if (!firstResult) continue;
188
+
189
+ // Page header with link and hit count
190
+ const pageTitle = firstResult.heading1 || firstResult.source_page_title;
191
+ const hitCount = pageResults.length > 1 ? ` (${pageResults.length} results)` : '';
192
+ lines.push(`### Results from [${escapeMarkdown(pageTitle)}](${url})${hitCount}`);
193
+ lines.push('');
194
+
195
+ // Add each excerpt from this page
196
+ for (const result of pageResults) {
197
+ lines.push(formatExcerpt(result));
198
+ }
199
+ }
200
+ }
201
+
202
+ // Add suggestion to use doc fetch tool
203
+ lines.push('---');
204
+ lines.push(`Use the "${DOC_FETCH_CONFIG.name}" tool to download a specific document.`);
205
+
206
+ return lines.join('\n');
207
+ }
package/src/index.ts CHANGED
@@ -13,6 +13,8 @@ export * from './space-info.js';
13
13
  export * from './space-files.js';
14
14
  export * from './user-summary.js';
15
15
  export * from './paper-summary.js';
16
+ export * from './docs-search/docs-semantic-search.js';
17
+ export * from './docs-search/doc-fetch.js';
16
18
 
17
19
  // Export tool IDs for external use - these are the canonical tool identifiers
18
20
  export * from './tool-ids.js';
package/src/tool-ids.ts CHANGED
@@ -15,6 +15,8 @@ import {
15
15
  SPACE_FILES_TOOL_CONFIG,
16
16
  USER_SUMMARY_PROMPT_CONFIG,
17
17
  PAPER_SUMMARY_PROMPT_CONFIG,
18
+ DOCS_SEMANTIC_SEARCH_CONFIG,
19
+ DOC_FETCH_CONFIG,
18
20
  } from './index.js';
19
21
 
20
22
  // Extract tool IDs from their configs (single source of truth)
@@ -27,9 +29,12 @@ export const DATASET_DETAIL_TOOL_ID = DATASET_DETAIL_TOOL_CONFIG.name;
27
29
  export const DUPLICATE_SPACE_TOOL_ID = DUPLICATE_SPACE_TOOL_CONFIG.name;
28
30
  export const SPACE_INFO_TOOL_ID = SPACE_INFO_TOOL_CONFIG.name;
29
31
  export const SPACE_FILES_TOOL_ID = SPACE_FILES_TOOL_CONFIG.name;
32
+ export const DOCS_SEMANTIC_SEARCH_TOOL_ID = DOCS_SEMANTIC_SEARCH_CONFIG.name;
33
+ export const DOC_FETCH_TOOL_ID = DOC_FETCH_CONFIG.name;
30
34
  export const USER_SUMMARY_PROMPT_ID = USER_SUMMARY_PROMPT_CONFIG.name;
31
35
  export const PAPER_SUMMARY_PROMPT_ID = PAPER_SUMMARY_PROMPT_CONFIG.name;
32
36
 
37
+
33
38
  // Complete list of all built-in tool IDs
34
39
  export const ALL_BUILTIN_TOOL_IDS = [
35
40
  SPACE_SEARCH_TOOL_ID,
@@ -41,12 +46,15 @@ export const ALL_BUILTIN_TOOL_IDS = [
41
46
  DUPLICATE_SPACE_TOOL_ID,
42
47
  SPACE_INFO_TOOL_ID,
43
48
  SPACE_FILES_TOOL_ID,
49
+ DOCS_SEMANTIC_SEARCH_TOOL_ID,
50
+ DOC_FETCH_TOOL_ID,
44
51
  ] as const;
45
52
  // Grouped tool IDs for bouquet configurations
46
53
  export const TOOL_ID_GROUPS = {
47
- search: [SPACE_SEARCH_TOOL_ID, MODEL_SEARCH_TOOL_ID, DATASET_SEARCH_TOOL_ID, PAPER_SEARCH_TOOL_ID] as const,
54
+ search: [SPACE_SEARCH_TOOL_ID, MODEL_SEARCH_TOOL_ID, DATASET_SEARCH_TOOL_ID, PAPER_SEARCH_TOOL_ID, DOCS_SEMANTIC_SEARCH_TOOL_ID] as const,
48
55
  spaces: [SPACE_SEARCH_TOOL_ID, DUPLICATE_SPACE_TOOL_ID, SPACE_INFO_TOOL_ID, SPACE_FILES_TOOL_ID] as const,
49
56
  detail: [MODEL_DETAIL_TOOL_ID, DATASET_DETAIL_TOOL_ID] as const,
57
+ docs: [DOCS_SEMANTIC_SEARCH_TOOL_ID, DOC_FETCH_TOOL_ID] as const,
50
58
  hf_api: [
51
59
  SPACE_SEARCH_TOOL_ID,
52
60
  MODEL_SEARCH_TOOL_ID,
@@ -65,20 +73,3 @@ export type BuiltinToolId = (typeof ALL_BUILTIN_TOOL_IDS)[number];
65
73
  export function isValidBuiltinToolId(toolId: string): toolId is BuiltinToolId {
66
74
  return (ALL_BUILTIN_TOOL_IDS as readonly string[]).includes(toolId);
67
75
  }
68
-
69
- // Helper to get tool description from configs
70
- export function getToolDescription(toolId: BuiltinToolId): string {
71
- const configs = {
72
- [SPACE_SEARCH_TOOL_ID]: SEMANTIC_SEARCH_TOOL_CONFIG,
73
- [MODEL_SEARCH_TOOL_ID]: MODEL_SEARCH_TOOL_CONFIG,
74
- [MODEL_DETAIL_TOOL_ID]: MODEL_DETAIL_TOOL_CONFIG,
75
- [PAPER_SEARCH_TOOL_ID]: PAPER_SEARCH_TOOL_CONFIG,
76
- [DATASET_SEARCH_TOOL_ID]: DATASET_SEARCH_TOOL_CONFIG,
77
- [DATASET_DETAIL_TOOL_ID]: DATASET_DETAIL_TOOL_CONFIG,
78
- [DUPLICATE_SPACE_TOOL_ID]: DUPLICATE_SPACE_TOOL_CONFIG,
79
- [SPACE_INFO_TOOL_ID]: SPACE_INFO_TOOL_CONFIG,
80
- [SPACE_FILES_TOOL_ID]: SPACE_FILES_TOOL_CONFIG,
81
- } as const;
82
-
83
- return configs[toolId]?.description || `Tool: ${toolId}`;
84
- }