@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.
Files changed (33) hide show
  1. package/README.md +2 -0
  2. package/bin/stdio.js +900 -781
  3. package/dist/contents/contents.schemas.d.ts +39 -0
  4. package/dist/contents/contents.utils.d.ts +28 -0
  5. package/dist/contents/register-contents-tool.d.ts +10 -0
  6. package/dist/express/express.schemas.d.ts +56 -0
  7. package/dist/express/express.utils.d.ts +45 -0
  8. package/dist/express/register-express-tool.d.ts +6 -0
  9. package/dist/get-mcp-server.d.ts +2 -0
  10. package/dist/http.d.ts +3 -0
  11. package/{src/utils.ts → dist/main.d.ts} +1 -0
  12. package/dist/search/register-search-tool.d.ts +6 -0
  13. package/dist/search/search.schemas.d.ts +131 -0
  14. package/dist/search/search.utils.d.ts +96 -0
  15. package/dist/shared/api-constants.d.ts +9 -0
  16. package/dist/shared/check-response-for-errors.d.ts +6 -0
  17. package/dist/shared/format-search-results-text.d.ts +19 -0
  18. package/dist/shared/generate-error-report-link.d.ts +9 -0
  19. package/dist/shared/get-logger.d.ts +7 -0
  20. package/dist/shared/use-client-version.d.ts +6 -0
  21. package/dist/stdio.d.ts +2 -0
  22. package/package.json +15 -7
  23. package/src/contents/contents.utils.ts +1 -2
  24. package/src/contents/tests/contents.utils.spec.ts +69 -57
  25. package/src/express/express.utils.ts +2 -4
  26. package/src/express/tests/express.utils.spec.ts +74 -62
  27. package/src/main.ts +9 -0
  28. package/src/search/search.schemas.ts +23 -4
  29. package/src/search/search.utils.ts +3 -2
  30. package/src/search/tests/search.utils.spec.ts +125 -57
  31. package/src/shared/api-constants.ts +10 -0
  32. package/src/tests/http.spec.ts +35 -31
  33. 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('returns valid response structure for single URL', async () => {
11
- const result = await fetchContents({
12
- contentsQuery: {
13
- urls: ['https://documentation.you.com/developer-resources/mcp-server'],
14
- format: 'markdown',
15
- },
16
- getUserAgent,
17
- });
18
-
19
- expect(Array.isArray(result)).toBe(true);
20
- expect(result.length).toBeGreaterThan(0);
21
-
22
- const firstItem = result[0];
23
- expect(firstItem).toBeDefined();
24
-
25
- // Should have markdown content
26
- expect(firstItem?.markdown).toBeDefined();
27
- expect(typeof firstItem?.markdown).toBe('string');
28
- });
29
-
30
- test('handles multiple URLs', async () => {
31
- const result = await fetchContents({
32
- contentsQuery: {
33
- urls: [
34
- 'https://documentation.you.com/developer-resources/mcp-server',
35
- 'https://documentation.you.com/developer-resources/python-sdk',
36
- ],
37
- format: 'markdown',
38
- },
39
- getUserAgent,
40
- });
41
-
42
- expect(Array.isArray(result)).toBe(true);
43
- expect(result.length).toBe(2);
44
-
45
- for (const item of result) {
46
- expect(item).toHaveProperty('url');
47
- expect(item.markdown).toBeDefined();
48
- }
49
- });
50
-
51
- test('handles html format', async () => {
52
- const result = await fetchContents({
53
- contentsQuery: {
54
- urls: ['https://documentation.you.com/developer-resources/mcp-server'],
55
- format: 'html',
56
- },
57
- getUserAgent,
58
- });
59
-
60
- expect(Array.isArray(result)).toBe(true);
61
- const firstItem = result[0];
62
- expect(firstItem).toBeDefined();
63
-
64
- expect(firstItem?.html).toBeDefined();
65
- expect(typeof firstItem?.html).toBe('string');
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(AGENTS_RUN_URL, options);
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('returns answer only (WITHOUT web_search tools)', async () => {
11
- const result = await callExpressAgent({
12
- agentInput: { input: 'What is machine learning?' },
13
- getUserAgent,
14
- });
15
-
16
- // Verify MCP response structure
17
- expect(result).toHaveProperty('answer');
18
- expect(typeof result.answer).toBe('string');
19
- expect(result.answer.length).toBeGreaterThan(0);
20
-
21
- // Should NOT have results when web_search is not used
22
- expect(result.results).toBeUndefined();
23
-
24
- expect(result.agent).toBe('express');
25
- });
26
-
27
- test('returns answer and search results (WITH web_search tools)', async () => {
28
- const result = await callExpressAgent({
29
- agentInput: {
30
- input: 'Latest developments in quantum computing',
31
- tools: [{ type: 'web_search' }],
32
- },
33
- getUserAgent,
34
- });
35
-
36
- // Verify MCP response has both answer and results
37
- expect(result).toHaveProperty('answer');
38
- expect(typeof result.answer).toBe('string');
39
- expect(result.answer.length).toBeGreaterThan(0);
40
-
41
- expect(result).toHaveProperty('results');
42
- expect(result.results).toHaveProperty('web');
43
- expect(Array.isArray(result.results?.web)).toBe(true);
44
- expect(result.results?.web.length).toBeGreaterThan(0);
45
-
46
- // Verify each search result has required fields
47
- const firstResult = result.results?.web[0];
48
- expect(firstResult).toHaveProperty('url');
49
- expect(firstResult).toHaveProperty('title');
50
- expect(firstResult).toHaveProperty('snippet');
51
- expect(typeof firstResult?.url).toBe('string');
52
- expect(typeof firstResult?.title).toBe('string');
53
- expect(typeof firstResult?.snippet).toBe('string');
54
- expect(firstResult?.url.length).toBeGreaterThan(0);
55
- expect(firstResult?.title.length).toBeGreaterThan(0);
56
-
57
- expect(result.agent).toBe('express');
58
- }, 30000);
59
-
60
- test('works without optional parameters', async () => {
61
- const result = await callExpressAgent({
62
- agentInput: { input: 'What is the capital of France?' },
63
- getUserAgent,
64
- // No progressToken or sendProgress provided
65
- });
66
-
67
- // Should work normally without progress tracking
68
- expect(result).toHaveProperty('answer');
69
- expect(result.answer.length).toBeGreaterThan(0);
70
- expect(result.agent).toBe('express');
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(20).optional().describe('Max results per section'),
6
- freshness: z.enum(['day', 'week', 'month', 'year']).optional().describe('Filter by freshness'),
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
- request_uuid: z.string().optional().describe('Request ID'),
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, request_uuid, latency) as these are not actionable by LLM
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('https://ydc-index.io/v1/search');
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(`fileType:${fileType}`);
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('returns valid response structure for basic query', async () => {
9
- const result = await fetchSearchResults({
10
- searchQuery: { query: 'latest stock news' },
11
- getUserAgent,
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
- expect(result).toHaveProperty('results');
15
- expect(result).toHaveProperty('metadata');
16
- expect(result.results).toHaveProperty('web');
17
- expect(result.results).toHaveProperty('news');
18
- expect(Array.isArray(result.results.web)).toBe(true);
19
- expect(Array.isArray(result.results.news)).toBe(true);
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
- // Assert required metadata fields
22
- expect(typeof result.metadata?.query).toBe('string');
23
+ // Assert required metadata fields
24
+ expect(typeof result.metadata?.query).toBe('string');
23
25
 
24
- // Optional fields: only assert type if present
25
- if (result.metadata?.request_uuid !== undefined) {
26
- expect(typeof result.metadata.request_uuid).toBe('string');
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('handles search with filters', async () => {
31
- const result = await fetchSearchResults({
32
- searchQuery: {
33
- query: 'javascript tutorial',
34
- count: 3,
35
- freshness: 'week',
36
- country: 'US',
37
- },
38
- getUserAgent,
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
- expect(result.results.web?.length).toBeLessThanOrEqual(3);
42
- expect(result.metadata?.query).toContain('javascript tutorial');
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('validates response schema', async () => {
46
- const result = await fetchSearchResults({
47
- searchQuery: { query: 'latest technology news' },
48
- getUserAgent,
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
- // Test that web results have required properties
52
- // biome-ignore lint/style/noNonNullAssertion: Test
53
- const webResult = result.results.web![0];
54
-
55
- expect(webResult).toHaveProperty('url');
56
- expect(webResult).toHaveProperty('title');
57
- expect(webResult).toHaveProperty('description');
58
- expect(webResult).toHaveProperty('snippets');
59
- expect(Array.isArray(webResult?.snippets)).toBe(true);
60
-
61
- // Test that news results have required properties
62
- // biome-ignore lint/style/noNonNullAssertion: Test
63
- const newsResult = result.results.news![0];
64
- expect(newsResult).toHaveProperty('url');
65
- expect(newsResult).toHaveProperty('title');
66
- expect(newsResult).toHaveProperty('description');
67
- expect(newsResult).toHaveProperty('page_age');
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
- request_uuid: 'test-uuid',
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
- request_uuid: 'test-uuid',
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
- request_uuid: 'test-uuid',
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';