@youdotcom-oss/mcp 1.3.7 → 1.3.9
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.
- package/README.md +2 -0
- package/bin/stdio.js +900 -781
- package/dist/contents/contents.schemas.d.ts +39 -0
- package/dist/contents/contents.utils.d.ts +28 -0
- package/dist/contents/register-contents-tool.d.ts +10 -0
- package/dist/express/express.schemas.d.ts +56 -0
- package/dist/express/express.utils.d.ts +45 -0
- package/dist/express/register-express-tool.d.ts +6 -0
- package/dist/get-mcp-server.d.ts +2 -0
- package/dist/http.d.ts +3 -0
- package/{src/utils.ts → dist/main.d.ts} +1 -0
- package/dist/search/register-search-tool.d.ts +6 -0
- package/dist/search/search.schemas.d.ts +131 -0
- package/dist/search/search.utils.d.ts +96 -0
- package/dist/shared/api-constants.d.ts +9 -0
- package/dist/shared/check-response-for-errors.d.ts +6 -0
- package/dist/shared/format-search-results-text.d.ts +19 -0
- package/dist/shared/generate-error-report-link.d.ts +9 -0
- package/dist/shared/get-logger.d.ts +7 -0
- package/dist/shared/use-client-version.d.ts +6 -0
- package/dist/stdio.d.ts +2 -0
- package/package.json +15 -7
- package/src/contents/contents.utils.ts +1 -2
- package/src/contents/tests/contents.utils.spec.ts +69 -57
- package/src/express/express.utils.ts +2 -4
- package/src/express/tests/express.utils.spec.ts +74 -62
- package/src/main.ts +9 -0
- package/src/search/search.schemas.ts +23 -4
- package/src/search/search.utils.ts +3 -2
- package/src/search/tests/search.utils.spec.ts +125 -57
- package/src/shared/api-constants.ts +10 -0
- package/src/tests/http.spec.ts +35 -31
- package/src/tests/tool.spec.ts +490 -402
|
@@ -7,63 +7,75 @@ const getUserAgent = () => 'MCP/test (You.com; test-client)';
|
|
|
7
7
|
// NOTE: The following tests require a You.com API key with access to the Contents API
|
|
8
8
|
// Using example.com/example.org as test URLs since You.com blocks self-scraping
|
|
9
9
|
describe('fetchContents', () => {
|
|
10
|
-
test(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
10
|
+
test(
|
|
11
|
+
'returns valid response structure for single URL',
|
|
12
|
+
async () => {
|
|
13
|
+
const result = await fetchContents({
|
|
14
|
+
contentsQuery: {
|
|
15
|
+
urls: ['https://documentation.you.com/developer-resources/mcp-server'],
|
|
16
|
+
format: 'markdown',
|
|
17
|
+
},
|
|
18
|
+
getUserAgent,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(Array.isArray(result)).toBe(true);
|
|
22
|
+
expect(result.length).toBeGreaterThan(0);
|
|
23
|
+
|
|
24
|
+
const firstItem = result[0];
|
|
25
|
+
expect(firstItem).toBeDefined();
|
|
26
|
+
|
|
27
|
+
// Should have markdown content
|
|
28
|
+
expect(firstItem?.markdown).toBeDefined();
|
|
29
|
+
expect(typeof firstItem?.markdown).toBe('string');
|
|
30
|
+
},
|
|
31
|
+
{ retry: 2 },
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
test(
|
|
35
|
+
'handles multiple URLs',
|
|
36
|
+
async () => {
|
|
37
|
+
const result = await fetchContents({
|
|
38
|
+
contentsQuery: {
|
|
39
|
+
urls: [
|
|
40
|
+
'https://documentation.you.com/developer-resources/mcp-server',
|
|
41
|
+
'https://documentation.you.com/developer-resources/python-sdk',
|
|
42
|
+
],
|
|
43
|
+
format: 'markdown',
|
|
44
|
+
},
|
|
45
|
+
getUserAgent,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(Array.isArray(result)).toBe(true);
|
|
49
|
+
expect(result.length).toBe(2);
|
|
50
|
+
|
|
51
|
+
for (const item of result) {
|
|
52
|
+
expect(item).toHaveProperty('url');
|
|
53
|
+
expect(item.markdown).toBeDefined();
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{ retry: 2 },
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
test(
|
|
60
|
+
'handles html format',
|
|
61
|
+
async () => {
|
|
62
|
+
const result = await fetchContents({
|
|
63
|
+
contentsQuery: {
|
|
64
|
+
urls: ['https://documentation.you.com/developer-resources/mcp-server'],
|
|
65
|
+
format: 'html',
|
|
66
|
+
},
|
|
67
|
+
getUserAgent,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(Array.isArray(result)).toBe(true);
|
|
71
|
+
const firstItem = result[0];
|
|
72
|
+
expect(firstItem).toBeDefined();
|
|
73
|
+
|
|
74
|
+
expect(firstItem?.html).toBeDefined();
|
|
75
|
+
expect(typeof firstItem?.html).toBe('string');
|
|
76
|
+
},
|
|
77
|
+
{ retry: 2 },
|
|
78
|
+
);
|
|
67
79
|
});
|
|
68
80
|
|
|
69
81
|
describe('formatContentsResponse', () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EXPRESS_API_URL } from '../shared/api-constants.ts';
|
|
1
2
|
import { checkResponseForErrors } from '../shared/check-response-for-errors.ts';
|
|
2
3
|
import { formatSearchResultsText } from '../shared/format-search-results-text.ts';
|
|
3
4
|
import {
|
|
@@ -7,9 +8,6 @@ import {
|
|
|
7
8
|
type ExpressAgentMcpResponse,
|
|
8
9
|
} from './express.schemas.ts';
|
|
9
10
|
|
|
10
|
-
// Express Agent Constants
|
|
11
|
-
const AGENTS_RUN_URL = 'https://api.you.com/v1/agents/runs';
|
|
12
|
-
|
|
13
11
|
/**
|
|
14
12
|
* Checks response status and throws appropriate errors for agent API calls
|
|
15
13
|
*/
|
|
@@ -70,7 +68,7 @@ export const callExpressAgent = async ({
|
|
|
70
68
|
body: JSON.stringify(requestBody),
|
|
71
69
|
};
|
|
72
70
|
|
|
73
|
-
const response = await fetch(
|
|
71
|
+
const response = await fetch(EXPRESS_API_URL, options);
|
|
74
72
|
|
|
75
73
|
if (!response.ok) {
|
|
76
74
|
await agentThrowOnFailedStatus(response);
|
|
@@ -7,68 +7,80 @@ const getUserAgent = () => 'MCP/test (You.com; test-client)';
|
|
|
7
7
|
setDefaultTimeout(20_000);
|
|
8
8
|
|
|
9
9
|
describe('callExpressAgent', () => {
|
|
10
|
-
test(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
10
|
+
test(
|
|
11
|
+
'returns answer only (WITHOUT web_search tools)',
|
|
12
|
+
async () => {
|
|
13
|
+
const result = await callExpressAgent({
|
|
14
|
+
agentInput: { input: 'What is machine learning?' },
|
|
15
|
+
getUserAgent,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Verify MCP response structure
|
|
19
|
+
expect(result).toHaveProperty('answer');
|
|
20
|
+
expect(typeof result.answer).toBe('string');
|
|
21
|
+
expect(result.answer.length).toBeGreaterThan(0);
|
|
22
|
+
|
|
23
|
+
// Should NOT have results when web_search is not used
|
|
24
|
+
expect(result.results).toBeUndefined();
|
|
25
|
+
|
|
26
|
+
expect(result.agent).toBe('express');
|
|
27
|
+
},
|
|
28
|
+
{ retry: 2 },
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
test(
|
|
32
|
+
'returns answer and search results (WITH web_search tools)',
|
|
33
|
+
async () => {
|
|
34
|
+
const result = await callExpressAgent({
|
|
35
|
+
agentInput: {
|
|
36
|
+
input: 'Latest developments in quantum computing',
|
|
37
|
+
tools: [{ type: 'web_search' }],
|
|
38
|
+
},
|
|
39
|
+
getUserAgent,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Verify MCP response has both answer and results
|
|
43
|
+
expect(result).toHaveProperty('answer');
|
|
44
|
+
expect(typeof result.answer).toBe('string');
|
|
45
|
+
expect(result.answer.length).toBeGreaterThan(0);
|
|
46
|
+
|
|
47
|
+
expect(result).toHaveProperty('results');
|
|
48
|
+
expect(result.results).toHaveProperty('web');
|
|
49
|
+
expect(Array.isArray(result.results?.web)).toBe(true);
|
|
50
|
+
expect(result.results?.web.length).toBeGreaterThan(0);
|
|
51
|
+
|
|
52
|
+
// Verify each search result has required fields
|
|
53
|
+
const firstResult = result.results?.web[0];
|
|
54
|
+
expect(firstResult).toHaveProperty('url');
|
|
55
|
+
expect(firstResult).toHaveProperty('title');
|
|
56
|
+
expect(firstResult).toHaveProperty('snippet');
|
|
57
|
+
expect(typeof firstResult?.url).toBe('string');
|
|
58
|
+
expect(typeof firstResult?.title).toBe('string');
|
|
59
|
+
expect(typeof firstResult?.snippet).toBe('string');
|
|
60
|
+
expect(firstResult?.url.length).toBeGreaterThan(0);
|
|
61
|
+
expect(firstResult?.title.length).toBeGreaterThan(0);
|
|
62
|
+
|
|
63
|
+
expect(result.agent).toBe('express');
|
|
64
|
+
},
|
|
65
|
+
{ timeout: 30_000, retry: 2 },
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
test(
|
|
69
|
+
'works without optional parameters',
|
|
70
|
+
async () => {
|
|
71
|
+
const result = await callExpressAgent({
|
|
72
|
+
agentInput: { input: 'What is the capital of France?' },
|
|
73
|
+
getUserAgent,
|
|
74
|
+
// No progressToken or sendProgress provided
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Should work normally without progress tracking
|
|
78
|
+
expect(result).toHaveProperty('answer');
|
|
79
|
+
expect(result.answer.length).toBeGreaterThan(0);
|
|
80
|
+
expect(result.agent).toBe('express');
|
|
81
|
+
},
|
|
82
|
+
{ retry: 2 },
|
|
83
|
+
);
|
|
72
84
|
});
|
|
73
85
|
|
|
74
86
|
describe('formatExpressAgentResponse', () => {
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './contents/contents.schemas.ts';
|
|
2
|
+
export * from './contents/contents.utils.ts';
|
|
3
|
+
export * from './express/express.schemas.ts';
|
|
4
|
+
export * from './express/express.utils.ts';
|
|
5
|
+
export * from './search/search.schemas.ts';
|
|
6
|
+
export * from './search/search.utils.ts';
|
|
7
|
+
export * from './shared/api-constants.ts';
|
|
8
|
+
export * from './shared/check-response-for-errors.ts';
|
|
9
|
+
export * from './shared/format-search-results-text.ts';
|
|
@@ -2,8 +2,8 @@ import * as z from 'zod';
|
|
|
2
2
|
|
|
3
3
|
export const SearchQuerySchema = z.object({
|
|
4
4
|
query: z.string().min(1, 'Query is required').describe('Search query (supports +, -, site:, filetype:, lang:)'),
|
|
5
|
-
count: z.number().int().min(1).max(
|
|
6
|
-
freshness: z.
|
|
5
|
+
count: z.number().int().min(1).max(100).optional().describe('Max results per section'),
|
|
6
|
+
freshness: z.string().optional().describe('day/week/month/year or YYYY-MM-DDtoYYYY-MM-DD'),
|
|
7
7
|
offset: z.number().int().min(0).max(9).optional().describe('Pagination offset'),
|
|
8
8
|
country: z
|
|
9
9
|
.enum([
|
|
@@ -52,6 +52,8 @@ export const SearchQuerySchema = z.object({
|
|
|
52
52
|
language: z.string().optional().describe('ISO 639-1 language code'),
|
|
53
53
|
excludeTerms: z.string().optional().describe('Terms to exclude (pipe-separated)'),
|
|
54
54
|
exactTerms: z.string().optional().describe('Exact terms (pipe-separated)'),
|
|
55
|
+
livecrawl: z.enum(['web', 'news', 'all']).optional().describe('Live-crawl sections for full content'),
|
|
56
|
+
livecrawl_formats: z.enum(['html', 'markdown']).optional().describe('Format for crawled content'),
|
|
55
57
|
});
|
|
56
58
|
|
|
57
59
|
export type SearchQuery = z.infer<typeof SearchQuerySchema>;
|
|
@@ -63,6 +65,15 @@ const WebResultSchema = z.object({
|
|
|
63
65
|
snippets: z.array(z.string()).describe('Content snippets'),
|
|
64
66
|
page_age: z.string().optional().describe('Publication timestamp'),
|
|
65
67
|
authors: z.array(z.string()).optional().describe('Authors'),
|
|
68
|
+
thumbnail_url: z.string().optional().describe('Thumbnail image URL'),
|
|
69
|
+
favicon_url: z.string().optional().describe('Favicon URL'),
|
|
70
|
+
contents: z
|
|
71
|
+
.object({
|
|
72
|
+
html: z.string().optional().describe('Full HTML content'),
|
|
73
|
+
markdown: z.string().optional().describe('Full Markdown content'),
|
|
74
|
+
})
|
|
75
|
+
.optional()
|
|
76
|
+
.describe('Live-crawled page content'),
|
|
66
77
|
});
|
|
67
78
|
|
|
68
79
|
const NewsResultSchema = z.object({
|
|
@@ -70,12 +81,20 @@ const NewsResultSchema = z.object({
|
|
|
70
81
|
description: z.string().describe('Description'),
|
|
71
82
|
page_age: z.string().describe('Publication timestamp'),
|
|
72
83
|
url: z.string().describe('URL'),
|
|
84
|
+
thumbnail_url: z.string().optional().describe('Thumbnail image URL'),
|
|
85
|
+
contents: z
|
|
86
|
+
.object({
|
|
87
|
+
html: z.string().optional().describe('Full HTML content'),
|
|
88
|
+
markdown: z.string().optional().describe('Full Markdown content'),
|
|
89
|
+
})
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('Live-crawled page content'),
|
|
73
92
|
});
|
|
74
93
|
|
|
75
94
|
export type NewsResult = z.infer<typeof NewsResultSchema>;
|
|
76
95
|
|
|
77
96
|
const MetadataSchema = z.object({
|
|
78
|
-
|
|
97
|
+
search_uuid: z.string().optional().describe('Unique search request ID'),
|
|
79
98
|
query: z.string().describe('Query'),
|
|
80
99
|
latency: z.number().describe('Latency in seconds'),
|
|
81
100
|
});
|
|
@@ -91,7 +110,7 @@ export const SearchResponseSchema = z.object({
|
|
|
91
110
|
export type SearchResponse = z.infer<typeof SearchResponseSchema>;
|
|
92
111
|
|
|
93
112
|
// Minimal schema for structuredContent (reduces payload duplication)
|
|
94
|
-
// Excludes metadata (query,
|
|
113
|
+
// Excludes metadata (query, search_uuid, latency) as these are not actionable by LLM
|
|
95
114
|
export const SearchStructuredContentSchema = z.object({
|
|
96
115
|
resultCounts: z.object({
|
|
97
116
|
web: z.number().describe('Web results'),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SEARCH_API_URL } from '../shared/api-constants.ts';
|
|
1
2
|
import { checkResponseForErrors } from '../shared/check-response-for-errors.ts';
|
|
2
3
|
import { formatSearchResultsText } from '../shared/format-search-results-text.ts';
|
|
3
4
|
import { type NewsResult, type SearchQuery, type SearchResponse, SearchResponseSchema } from './search.schemas.ts';
|
|
@@ -11,14 +12,14 @@ export const fetchSearchResults = async ({
|
|
|
11
12
|
YDC_API_KEY?: string;
|
|
12
13
|
getUserAgent: () => string;
|
|
13
14
|
}) => {
|
|
14
|
-
const url = new URL(
|
|
15
|
+
const url = new URL(SEARCH_API_URL);
|
|
15
16
|
|
|
16
17
|
const searchParams = new URLSearchParams();
|
|
17
18
|
|
|
18
19
|
// Build Query Param
|
|
19
20
|
const searchQuery = [query];
|
|
20
21
|
site && searchQuery.push(`site:${site}`);
|
|
21
|
-
fileType && searchQuery.push(`
|
|
22
|
+
fileType && searchQuery.push(`filetype:${fileType}`);
|
|
22
23
|
language && searchQuery.push(`lang:${language}`);
|
|
23
24
|
if (exactTerms && excludeTerms) {
|
|
24
25
|
throw new Error('Cannot specify both exactTerms and excludeTerms - please use only one');
|
|
@@ -5,67 +5,135 @@ import { fetchSearchResults, formatSearchResults } from '../search.utils.ts';
|
|
|
5
5
|
const getUserAgent = () => 'MCP/test (You.com; test-client)';
|
|
6
6
|
|
|
7
7
|
describe('fetchSearchResults', () => {
|
|
8
|
-
test(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
test(
|
|
9
|
+
'returns valid response structure for basic query',
|
|
10
|
+
async () => {
|
|
11
|
+
const result = await fetchSearchResults({
|
|
12
|
+
searchQuery: { query: 'latest stock news' },
|
|
13
|
+
getUserAgent,
|
|
14
|
+
});
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
expect(result).toHaveProperty('results');
|
|
17
|
+
expect(result).toHaveProperty('metadata');
|
|
18
|
+
expect(result.results).toHaveProperty('web');
|
|
19
|
+
expect(result.results).toHaveProperty('news');
|
|
20
|
+
expect(Array.isArray(result.results.web)).toBe(true);
|
|
21
|
+
expect(Array.isArray(result.results.news)).toBe(true);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
// Assert required metadata fields
|
|
24
|
+
expect(typeof result.metadata?.query).toBe('string');
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
expect(typeof result.metadata
|
|
27
|
-
}
|
|
28
|
-
|
|
26
|
+
// search_uuid is optional but should be string if present
|
|
27
|
+
expect(result.metadata?.search_uuid).toBeDefined();
|
|
28
|
+
expect(typeof result.metadata?.search_uuid).toBe('string');
|
|
29
|
+
},
|
|
30
|
+
{ retry: 2 },
|
|
31
|
+
);
|
|
29
32
|
|
|
30
|
-
test(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
test(
|
|
34
|
+
'handles search with filters',
|
|
35
|
+
async () => {
|
|
36
|
+
const result = await fetchSearchResults({
|
|
37
|
+
searchQuery: {
|
|
38
|
+
query: 'javascript tutorial',
|
|
39
|
+
count: 3,
|
|
40
|
+
freshness: 'week',
|
|
41
|
+
country: 'US',
|
|
42
|
+
},
|
|
43
|
+
getUserAgent,
|
|
44
|
+
});
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
expect(result.results.web?.length).toBeLessThanOrEqual(3);
|
|
47
|
+
expect(result.metadata?.query).toContain('javascript tutorial');
|
|
48
|
+
},
|
|
49
|
+
{ retry: 2 },
|
|
50
|
+
);
|
|
44
51
|
|
|
45
|
-
test(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
test(
|
|
53
|
+
'validates response schema',
|
|
54
|
+
async () => {
|
|
55
|
+
const result = await fetchSearchResults({
|
|
56
|
+
searchQuery: { query: 'latest technology news' },
|
|
57
|
+
getUserAgent,
|
|
58
|
+
});
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
// Test that web results have required properties
|
|
61
|
+
// biome-ignore lint/style/noNonNullAssertion: Test
|
|
62
|
+
const webResult = result.results.web![0];
|
|
63
|
+
|
|
64
|
+
expect(webResult).toHaveProperty('url');
|
|
65
|
+
expect(webResult).toHaveProperty('title');
|
|
66
|
+
expect(webResult).toHaveProperty('description');
|
|
67
|
+
expect(webResult).toHaveProperty('snippets');
|
|
68
|
+
expect(Array.isArray(webResult?.snippets)).toBe(true);
|
|
69
|
+
|
|
70
|
+
// Test that news results have required properties
|
|
71
|
+
// biome-ignore lint/style/noNonNullAssertion: Test
|
|
72
|
+
const newsResult = result.results.news![0];
|
|
73
|
+
expect(newsResult).toHaveProperty('url');
|
|
74
|
+
expect(newsResult).toHaveProperty('title');
|
|
75
|
+
expect(newsResult).toHaveProperty('description');
|
|
76
|
+
expect(newsResult).toHaveProperty('page_age');
|
|
77
|
+
},
|
|
78
|
+
{ retry: 2 },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
test(
|
|
82
|
+
'handles livecrawl parameters',
|
|
83
|
+
async () => {
|
|
84
|
+
const result = await fetchSearchResults({
|
|
85
|
+
searchQuery: {
|
|
86
|
+
query: 'python tutorial',
|
|
87
|
+
count: 2,
|
|
88
|
+
livecrawl: 'web',
|
|
89
|
+
livecrawl_formats: 'markdown',
|
|
90
|
+
},
|
|
91
|
+
getUserAgent,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result.results.web?.length).toBeLessThanOrEqual(2);
|
|
95
|
+
// Livecrawl should return contents field (fails naturally if not present)
|
|
96
|
+
expect(result.results.web?.[0]).toHaveProperty('contents');
|
|
97
|
+
expect(result.results.web?.[0]?.contents).toHaveProperty('markdown');
|
|
98
|
+
expect(typeof result.results.web?.[0]?.contents?.markdown).toBe('string');
|
|
99
|
+
},
|
|
100
|
+
{ retry: 2 },
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
test(
|
|
104
|
+
'handles freshness date ranges',
|
|
105
|
+
async () => {
|
|
106
|
+
const result = await fetchSearchResults({
|
|
107
|
+
searchQuery: {
|
|
108
|
+
query: 'AI news',
|
|
109
|
+
freshness: '2024-01-01to2024-12-31',
|
|
110
|
+
count: 3,
|
|
111
|
+
},
|
|
112
|
+
getUserAgent,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(result).toHaveProperty('results');
|
|
116
|
+
expect(result.metadata?.query).toContain('AI news');
|
|
117
|
+
},
|
|
118
|
+
{ retry: 2 },
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
test(
|
|
122
|
+
'handles count greater than 20',
|
|
123
|
+
async () => {
|
|
124
|
+
const result = await fetchSearchResults({
|
|
125
|
+
searchQuery: {
|
|
126
|
+
query: 'machine learning',
|
|
127
|
+
count: 50,
|
|
128
|
+
},
|
|
129
|
+
getUserAgent,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.results.web?.length).toBeGreaterThan(0);
|
|
133
|
+
expect(result.results.web?.length).toBeLessThanOrEqual(50);
|
|
134
|
+
},
|
|
135
|
+
{ retry: 2 },
|
|
136
|
+
);
|
|
69
137
|
});
|
|
70
138
|
|
|
71
139
|
describe('formatSearchResults', () => {
|
|
@@ -85,7 +153,7 @@ describe('formatSearchResults', () => {
|
|
|
85
153
|
news: [],
|
|
86
154
|
},
|
|
87
155
|
metadata: {
|
|
88
|
-
|
|
156
|
+
search_uuid: 'test-uuid',
|
|
89
157
|
query: 'test query',
|
|
90
158
|
latency: 0.1,
|
|
91
159
|
},
|
|
@@ -132,7 +200,7 @@ describe('formatSearchResults', () => {
|
|
|
132
200
|
],
|
|
133
201
|
},
|
|
134
202
|
metadata: {
|
|
135
|
-
|
|
203
|
+
search_uuid: 'test-uuid',
|
|
136
204
|
query: 'test query',
|
|
137
205
|
latency: 0.1,
|
|
138
206
|
},
|
|
@@ -182,7 +250,7 @@ describe('formatSearchResults', () => {
|
|
|
182
250
|
],
|
|
183
251
|
},
|
|
184
252
|
metadata: {
|
|
185
|
-
|
|
253
|
+
search_uuid: 'test-uuid',
|
|
186
254
|
query: 'test query',
|
|
187
255
|
latency: 0.1,
|
|
188
256
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* You.com API endpoints
|
|
3
|
+
*
|
|
4
|
+
* These constants define the base URLs for You.com's APIs.
|
|
5
|
+
* Exported for use in tests and external packages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const SEARCH_API_URL = 'https://ydc-index.io/v1/search';
|
|
9
|
+
export const EXPRESS_API_URL = 'https://api.you.com/v1/agents/runs';
|
|
10
|
+
export const CONTENTS_API_URL = 'https://ydc-index.io/v1/contents';
|