@youdotcom-oss/api 0.2.0 → 0.2.2
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 +26 -26
- package/bin/cli.js +7 -4
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/deep-search/deep-search.utils.ts +7 -3
- package/src/deep-search/tests/deep-search.request.spec.ts +109 -0
- package/src/deep-search/tests/deep-search.schema-validation.spec.ts +71 -0
- package/src/deep-search/tests/deep-search.utils.docker.ts +139 -0
package/README.md
CHANGED
|
@@ -19,11 +19,11 @@ Fast, lightweight API client and CLI tools for web search, AI answers, and conte
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
# Use with bunx (no install needed) - Schema-driven JSON input
|
|
22
|
-
bunx @youdotcom-oss/api search --json '{"query":"AI developments"}' --client
|
|
22
|
+
bunx @youdotcom-oss/api search --json '{"query":"AI developments"}' --client ClaudeCode
|
|
23
23
|
|
|
24
24
|
# Or install globally to use 'ydc' command
|
|
25
25
|
bun i -g @youdotcom-oss/api
|
|
26
|
-
ydc search --json '{"query":"AI developments"}' --client
|
|
26
|
+
ydc search --json '{"query":"AI developments"}' --client ClaudeCode
|
|
27
27
|
|
|
28
28
|
# Get comprehensive research with citations
|
|
29
29
|
bunx @youdotcom-oss/api deep-search --json '{
|
|
@@ -137,14 +137,14 @@ ydc search --json '{"query":"..."}' [options]
|
|
|
137
137
|
|
|
138
138
|
Examples:
|
|
139
139
|
# Basic search
|
|
140
|
-
ydc search --json '{"query":"machine learning"}' --client
|
|
140
|
+
ydc search --json '{"query":"machine learning"}' --client ClaudeCode
|
|
141
141
|
|
|
142
142
|
# Search with livecrawl (KEY FEATURE)
|
|
143
143
|
ydc search --json '{
|
|
144
144
|
"query":"documentation",
|
|
145
145
|
"livecrawl":"web",
|
|
146
146
|
"livecrawl_formats":"markdown"
|
|
147
|
-
}' --client
|
|
147
|
+
}' --client ClaudeCode
|
|
148
148
|
|
|
149
149
|
# Advanced filters
|
|
150
150
|
ydc search --json '{
|
|
@@ -153,10 +153,10 @@ Examples:
|
|
|
153
153
|
"fileType":"pdf",
|
|
154
154
|
"freshness":"month",
|
|
155
155
|
"count":10
|
|
156
|
-
}' --client
|
|
156
|
+
}' --client ClaudeCode
|
|
157
157
|
|
|
158
158
|
# Parse with jq
|
|
159
|
-
api search --json '{"query":"AI"}' --client
|
|
159
|
+
api search --json '{"query":"AI"}' --client ClaudeCode | \
|
|
160
160
|
jq -r '.results.web[] | .title'
|
|
161
161
|
|
|
162
162
|
# Extract livecrawl content
|
|
@@ -164,7 +164,7 @@ Examples:
|
|
|
164
164
|
"query":"docs",
|
|
165
165
|
"livecrawl":"web",
|
|
166
166
|
"livecrawl_formats":"markdown"
|
|
167
|
-
}' --client
|
|
167
|
+
}' --client ClaudeCode | \
|
|
168
168
|
jq -r '.results.web[0].contents.markdown'
|
|
169
169
|
```
|
|
170
170
|
|
|
@@ -190,18 +190,18 @@ ydc deep-search --json '{"query":"..."}' [options]
|
|
|
190
190
|
|
|
191
191
|
Examples:
|
|
192
192
|
# Comprehensive research with medium effort
|
|
193
|
-
api deep-search --json '{"query":"What is quantum computing?"}' --client
|
|
193
|
+
api deep-search --json '{"query":"What is quantum computing?"}' --client ClaudeCode
|
|
194
194
|
|
|
195
195
|
# High-effort deep research (up to 5 minutes)
|
|
196
196
|
api deep-search --json '{
|
|
197
197
|
"query":"Latest breakthroughs in AI agents",
|
|
198
198
|
"search_effort":"high"
|
|
199
|
-
}' --client
|
|
199
|
+
}' --client ClaudeCode
|
|
200
200
|
|
|
201
201
|
# Parse answer and sources
|
|
202
202
|
api deep-search --json '{
|
|
203
203
|
"query":"AI trends 2026"
|
|
204
|
-
}' --client
|
|
204
|
+
}' --client ClaudeCode | \
|
|
205
205
|
jq -r '.answer, "\nSources:", (.results[]? | "- \(.title): \(.url)")'
|
|
206
206
|
```
|
|
207
207
|
|
|
@@ -219,25 +219,25 @@ Examples:
|
|
|
219
219
|
api contents --json '{
|
|
220
220
|
"urls":["https://example.com"],
|
|
221
221
|
"formats":["markdown"]
|
|
222
|
-
}' --client
|
|
222
|
+
}' --client ClaudeCode
|
|
223
223
|
|
|
224
224
|
# Multiple formats
|
|
225
225
|
api contents --json '{
|
|
226
226
|
"urls":["https://example.com"],
|
|
227
227
|
"formats":["markdown","html","metadata"]
|
|
228
|
-
}' --client
|
|
228
|
+
}' --client ClaudeCode
|
|
229
229
|
|
|
230
230
|
# Multiple URLs
|
|
231
231
|
api contents --json '{
|
|
232
232
|
"urls":["https://a.com","https://b.com"],
|
|
233
233
|
"formats":["markdown"]
|
|
234
|
-
}' --client
|
|
234
|
+
}' --client ClaudeCode
|
|
235
235
|
|
|
236
236
|
# Save to file
|
|
237
237
|
api contents --json '{
|
|
238
238
|
"urls":["https://example.com"],
|
|
239
239
|
"formats":["markdown"]
|
|
240
|
-
}' --client
|
|
240
|
+
}' --client ClaudeCode | \
|
|
241
241
|
jq -r '.[0].markdown' > output.md
|
|
242
242
|
|
|
243
243
|
# With timeout
|
|
@@ -245,7 +245,7 @@ Examples:
|
|
|
245
245
|
"urls":["https://example.com"],
|
|
246
246
|
"formats":["markdown","metadata"],
|
|
247
247
|
"crawl_timeout":30
|
|
248
|
-
}' --client
|
|
248
|
+
}' --client ClaudeCode
|
|
249
249
|
```
|
|
250
250
|
|
|
251
251
|
**Available contents parameters** (use `--schema` to see full schema):
|
|
@@ -363,7 +363,7 @@ console.log(response[0].metadata); // Structured metadata
|
|
|
363
363
|
set -e
|
|
364
364
|
|
|
365
365
|
# Capture result, check exit code
|
|
366
|
-
if ! result=$(api search --json '{"query":"AI developments"}' --client
|
|
366
|
+
if ! result=$(api search --json '{"query":"AI developments"}' --client ClaudeCode); then
|
|
367
367
|
echo "Search failed with code $?"
|
|
368
368
|
exit 1
|
|
369
369
|
fi
|
|
@@ -377,7 +377,7 @@ echo "$result" | jq .
|
|
|
377
377
|
```bash
|
|
378
378
|
#!/usr/bin/env bash
|
|
379
379
|
for i in {1..3}; do
|
|
380
|
-
if api search --json '{"query":"AI"}' --client
|
|
380
|
+
if api search --json '{"query":"AI"}' --client ClaudeCode; then
|
|
381
381
|
exit 0
|
|
382
382
|
fi
|
|
383
383
|
[ $i -lt 3 ] && sleep 5
|
|
@@ -390,9 +390,9 @@ exit 1
|
|
|
390
390
|
|
|
391
391
|
```bash
|
|
392
392
|
#!/usr/bin/env bash
|
|
393
|
-
ydc search --json '{"query":"AI"}' --client
|
|
394
|
-
ydc search --json '{"query":"ML"}' --client
|
|
395
|
-
ydc search --json '{"query":"LLM"}' --client
|
|
393
|
+
ydc search --json '{"query":"AI"}' --client ClaudeCode &
|
|
394
|
+
ydc search --json '{"query":"ML"}' --client ClaudeCode &
|
|
395
|
+
ydc search --json '{"query":"LLM"}' --client ClaudeCode &
|
|
396
396
|
wait
|
|
397
397
|
```
|
|
398
398
|
|
|
@@ -408,18 +408,18 @@ search=$(api search --json '{
|
|
|
408
408
|
"count":5,
|
|
409
409
|
"livecrawl":"web",
|
|
410
410
|
"livecrawl_formats":"markdown"
|
|
411
|
-
}' --client
|
|
411
|
+
}' --client ClaudeCode)
|
|
412
412
|
|
|
413
413
|
# Get comprehensive research with citations
|
|
414
414
|
answer=$(api deep-search --json '{
|
|
415
415
|
"query":"Summarize AI developments in 2026",
|
|
416
416
|
"search_effort":"high"
|
|
417
|
-
}' --client
|
|
417
|
+
}' --client ClaudeCode)
|
|
418
418
|
|
|
419
419
|
# Extract top result URL and fetch content
|
|
420
420
|
url=$(echo "$search" | jq -r '.results.web[0].url')
|
|
421
421
|
ydc contents --json "{\"urls\":[\"$url\"],\"formats\":[\"markdown\"]}" \
|
|
422
|
-
--client
|
|
422
|
+
--client ClaudeCode | jq -r '.[0].markdown' > output.md
|
|
423
423
|
```
|
|
424
424
|
|
|
425
425
|
### Schema-Driven Agent
|
|
@@ -441,7 +441,7 @@ query=$(jq -n '{
|
|
|
441
441
|
}')
|
|
442
442
|
|
|
443
443
|
# Execute search
|
|
444
|
-
ydc search --json "$query" --client
|
|
444
|
+
ydc search --json "$query" --client ClaudeCode
|
|
445
445
|
```
|
|
446
446
|
|
|
447
447
|
## Agent Skills Integration
|
|
@@ -462,10 +462,10 @@ The [youdotcom-cli skill](https://github.com/youdotcom-oss/agent-skills/tree/mai
|
|
|
462
462
|
### Compatible Agents
|
|
463
463
|
|
|
464
464
|
Works with any bash-capable agent supporting Agent Skills:
|
|
465
|
-
- **OpenClaw** - Open source bash agent
|
|
466
465
|
- **Claude Code** - Anthropic's coding tool
|
|
467
|
-
- **Codex** - OpenAI's CLI agent
|
|
468
466
|
- **Cursor** - AI-powered code editor
|
|
467
|
+
- **Droid** - Factory.ai agent
|
|
468
|
+
- **Codex** - OpenAI's CLI agent
|
|
469
469
|
- **Roo Code** - VS Code extension
|
|
470
470
|
- And more...
|
|
471
471
|
|
package/bin/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ import { parseArgs as parseArgs2 } from "node:util";
|
|
|
15
15
|
// package.json
|
|
16
16
|
var package_default = {
|
|
17
17
|
name: "@youdotcom-oss/api",
|
|
18
|
-
version: "0.2.
|
|
18
|
+
version: "0.2.2",
|
|
19
19
|
description: "You.com API client with bundled CLI for agents supporting Agent Skills",
|
|
20
20
|
license: "MIT",
|
|
21
21
|
engines: {
|
|
@@ -4856,13 +4856,16 @@ var DeepSearchResponseSchema = object({
|
|
|
4856
4856
|
// src/deep-search/deep-search.utils.ts
|
|
4857
4857
|
var callDeepSearch = async ({
|
|
4858
4858
|
deepSearchQuery,
|
|
4859
|
-
YDC_API_KEY,
|
|
4859
|
+
YDC_API_KEY = process.env.YDC_API_KEY,
|
|
4860
4860
|
getUserAgent
|
|
4861
4861
|
}) => {
|
|
4862
|
+
if (!YDC_API_KEY) {
|
|
4863
|
+
throw new Error("YDC_API_KEY is required for Deep Search API");
|
|
4864
|
+
}
|
|
4862
4865
|
const response = await fetch(DEEP_SEARCH_API_URL, {
|
|
4863
4866
|
method: "POST",
|
|
4864
4867
|
headers: new Headers({
|
|
4865
|
-
"X-API-Key": YDC_API_KEY
|
|
4868
|
+
"X-API-Key": YDC_API_KEY,
|
|
4866
4869
|
"Content-Type": "application/json",
|
|
4867
4870
|
"User-Agent": getUserAgent()
|
|
4868
4871
|
}),
|
|
@@ -5222,7 +5225,7 @@ Output Format:
|
|
|
5222
5225
|
Invalid args: Error message on stderr (exit 2)
|
|
5223
5226
|
|
|
5224
5227
|
Examples:
|
|
5225
|
-
ydc search --json '{"query":"AI developments"}' --client
|
|
5228
|
+
ydc search --json '{"query":"AI developments"}' --client ClaudeCode
|
|
5226
5229
|
ydc deep-search --json '{"query":"What are the latest breakthroughs in AI?","search_effort":"high"}' --client MyAgent
|
|
5227
5230
|
ydc contents --json '{"urls":["https://example.com"],"formats":["markdown"]}'
|
|
5228
5231
|
ydc search --schema # Get JSON schema for search --json input
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -62,7 +62,7 @@ Output Format:
|
|
|
62
62
|
Invalid args: Error message on stderr (exit 2)
|
|
63
63
|
|
|
64
64
|
Examples:
|
|
65
|
-
ydc search --json '{"query":"AI developments"}' --client
|
|
65
|
+
ydc search --json '{"query":"AI developments"}' --client ClaudeCode
|
|
66
66
|
ydc deep-search --json '{"query":"What are the latest breakthroughs in AI?","search_effort":"high"}' --client MyAgent
|
|
67
67
|
ydc contents --json '{"urls":["https://example.com"],"formats":["markdown"]}'
|
|
68
68
|
ydc search --schema # Get JSON schema for search --json input
|
|
@@ -14,17 +14,21 @@ import { type DeepSearchQuery, DeepSearchResponseSchema } from './deep-search.sc
|
|
|
14
14
|
*/
|
|
15
15
|
export const callDeepSearch = async ({
|
|
16
16
|
deepSearchQuery,
|
|
17
|
-
YDC_API_KEY,
|
|
17
|
+
YDC_API_KEY = process.env.YDC_API_KEY,
|
|
18
18
|
getUserAgent,
|
|
19
19
|
}: {
|
|
20
20
|
deepSearchQuery: DeepSearchQuery
|
|
21
|
-
YDC_API_KEY
|
|
21
|
+
YDC_API_KEY?: string
|
|
22
22
|
getUserAgent: GetUserAgent
|
|
23
23
|
}) => {
|
|
24
|
+
if (!YDC_API_KEY) {
|
|
25
|
+
throw new Error('YDC_API_KEY is required for Deep Search API')
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
const response = await fetch(DEEP_SEARCH_API_URL, {
|
|
25
29
|
method: 'POST',
|
|
26
30
|
headers: new Headers({
|
|
27
|
-
'X-API-Key': YDC_API_KEY
|
|
31
|
+
'X-API-Key': YDC_API_KEY,
|
|
28
32
|
'Content-Type': 'application/json',
|
|
29
33
|
'User-Agent': getUserAgent(),
|
|
30
34
|
}),
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { DEEP_SEARCH_API_URL } from '../../shared/api.constants.ts'
|
|
3
|
+
import { buildDeepSearchRequest } from '../../shared/dry-run-utils.ts'
|
|
4
|
+
|
|
5
|
+
describe('buildDeepSearchRequest', () => {
|
|
6
|
+
const getUserAgent = () => 'test-agent'
|
|
7
|
+
const YDC_API_KEY = 'test-key'
|
|
8
|
+
|
|
9
|
+
test('builds basic deep-search request with query only', () => {
|
|
10
|
+
const request = buildDeepSearchRequest({
|
|
11
|
+
deepSearchQuery: { query: 'What is AI?' },
|
|
12
|
+
YDC_API_KEY,
|
|
13
|
+
getUserAgent,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(request.url).toBe(DEEP_SEARCH_API_URL)
|
|
17
|
+
expect(request.method).toBe('POST')
|
|
18
|
+
expect(request.headers['X-API-Key']).toBe('test-key')
|
|
19
|
+
expect(request.headers['Content-Type']).toBe('application/json')
|
|
20
|
+
expect(request.headers['User-Agent']).toBe('test-agent')
|
|
21
|
+
|
|
22
|
+
const body = JSON.parse(request.body!)
|
|
23
|
+
expect(body.query).toBe('What is AI?')
|
|
24
|
+
// search_effort not in body when not provided (default applied by schema validation, not dry-run)
|
|
25
|
+
expect(body.search_effort).toBeUndefined()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('builds request with explicit medium search effort', () => {
|
|
29
|
+
const request = buildDeepSearchRequest({
|
|
30
|
+
deepSearchQuery: { query: 'What is AI?', search_effort: 'medium' },
|
|
31
|
+
YDC_API_KEY,
|
|
32
|
+
getUserAgent,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const body = JSON.parse(request.body!)
|
|
36
|
+
expect(body.query).toBe('What is AI?')
|
|
37
|
+
expect(body.search_effort).toBe('medium')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('builds request with low search effort', () => {
|
|
41
|
+
const request = buildDeepSearchRequest({
|
|
42
|
+
deepSearchQuery: {
|
|
43
|
+
query: 'Quick explanation of JWT',
|
|
44
|
+
search_effort: 'low',
|
|
45
|
+
},
|
|
46
|
+
YDC_API_KEY,
|
|
47
|
+
getUserAgent,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const body = JSON.parse(request.body!)
|
|
51
|
+
expect(body.query).toBe('Quick explanation of JWT')
|
|
52
|
+
expect(body.search_effort).toBe('low')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('builds request with high search effort', () => {
|
|
56
|
+
const request = buildDeepSearchRequest({
|
|
57
|
+
deepSearchQuery: {
|
|
58
|
+
query: 'Comprehensive analysis of climate change impacts',
|
|
59
|
+
search_effort: 'high',
|
|
60
|
+
},
|
|
61
|
+
YDC_API_KEY,
|
|
62
|
+
getUserAgent,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const body = JSON.parse(request.body!)
|
|
66
|
+
expect(body.query).toBe('Comprehensive analysis of climate change impacts')
|
|
67
|
+
expect(body.search_effort).toBe('high')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('builds request with complex research question', () => {
|
|
71
|
+
const complexQuery = `What are the key differences between microservices and monolithic architecture?
|
|
72
|
+
Include pros and cons of each approach, best use cases, and migration strategies.`
|
|
73
|
+
|
|
74
|
+
const request = buildDeepSearchRequest({
|
|
75
|
+
deepSearchQuery: {
|
|
76
|
+
query: complexQuery,
|
|
77
|
+
search_effort: 'high',
|
|
78
|
+
},
|
|
79
|
+
YDC_API_KEY,
|
|
80
|
+
getUserAgent,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const body = JSON.parse(request.body!)
|
|
84
|
+
expect(body.query).toBe(complexQuery)
|
|
85
|
+
expect(body.search_effort).toBe('high')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('uses correct API URL', () => {
|
|
89
|
+
const request = buildDeepSearchRequest({
|
|
90
|
+
deepSearchQuery: { query: 'test' },
|
|
91
|
+
YDC_API_KEY,
|
|
92
|
+
getUserAgent,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
expect(request.url).toBe('https://api.you.com/v1/deep_search')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('includes all required headers', () => {
|
|
99
|
+
const request = buildDeepSearchRequest({
|
|
100
|
+
deepSearchQuery: { query: 'test' },
|
|
101
|
+
YDC_API_KEY: 'my-api-key',
|
|
102
|
+
getUserAgent: () => 'CustomAgent/1.0',
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
expect(request.headers['X-API-Key']).toBe('my-api-key')
|
|
106
|
+
expect(request.headers['Content-Type']).toBe('application/json')
|
|
107
|
+
expect(request.headers['User-Agent']).toBe('CustomAgent/1.0')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { DeepSearchQuerySchema, SearchEffortSchema } from '../deep-search.schemas.ts'
|
|
3
|
+
|
|
4
|
+
describe('DeepSearchQuerySchema OpenAPI validation', () => {
|
|
5
|
+
test('accepts valid query parameters', () => {
|
|
6
|
+
const validQueries = [
|
|
7
|
+
{ query: 'What is quantum computing?' },
|
|
8
|
+
{ query: 'Explain machine learning', search_effort: 'low' },
|
|
9
|
+
{ query: 'Latest AI developments', search_effort: 'medium' },
|
|
10
|
+
{ query: 'Comprehensive research on climate change', search_effort: 'high' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
for (const validQuery of validQueries) {
|
|
14
|
+
expect(() => DeepSearchQuerySchema.parse(validQuery)).not.toThrow()
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('rejects invalid query parameters', () => {
|
|
19
|
+
const invalidQueries = [
|
|
20
|
+
{}, // Missing query
|
|
21
|
+
{ query: '' }, // Empty query
|
|
22
|
+
{ query: 'test', search_effort: 'invalid' }, // Invalid search_effort
|
|
23
|
+
{ query: 'test', search_effort: 'extreme' }, // Invalid effort level
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
for (const invalidQuery of invalidQueries) {
|
|
27
|
+
expect(() => DeepSearchQuerySchema.parse(invalidQuery)).toThrow()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('defaults search_effort to medium when not provided', () => {
|
|
32
|
+
const result = DeepSearchQuerySchema.parse({ query: 'test query' })
|
|
33
|
+
expect(result.search_effort).toBe('medium')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('SearchEffortSchema accepts all valid effort levels', () => {
|
|
37
|
+
const validEfforts = ['low', 'medium', 'high']
|
|
38
|
+
|
|
39
|
+
for (const effort of validEfforts) {
|
|
40
|
+
expect(() => SearchEffortSchema.parse(effort)).not.toThrow()
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('SearchEffortSchema rejects invalid effort levels', () => {
|
|
45
|
+
const invalidEfforts = ['none', 'extreme', 'ultra', 'minimal', '']
|
|
46
|
+
|
|
47
|
+
for (const effort of invalidEfforts) {
|
|
48
|
+
expect(() => SearchEffortSchema.parse(effort)).toThrow()
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('accepts complex research questions', () => {
|
|
53
|
+
const complexQueries = [
|
|
54
|
+
{
|
|
55
|
+
query: 'What are the key differences between REST and GraphQL APIs, and when should each be used?',
|
|
56
|
+
search_effort: 'high',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
query: 'Compare the advantages and disadvantages of microservices architecture versus monolithic architecture',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
query: 'What happened in AI research during 2024? Provide a comprehensive summary with key breakthroughs.',
|
|
63
|
+
search_effort: 'high',
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for (const query of complexQueries) {
|
|
68
|
+
expect(() => DeepSearchQuerySchema.parse(query)).not.toThrow()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import { callDeepSearch } from '../deep-search.utils.ts'
|
|
3
|
+
|
|
4
|
+
const getUserAgent = () => 'API/test (You.com;TEST)'
|
|
5
|
+
|
|
6
|
+
describe('callDeepSearch', () => {
|
|
7
|
+
test(
|
|
8
|
+
'returns valid response structure for basic query',
|
|
9
|
+
async () => {
|
|
10
|
+
const result = await callDeepSearch({
|
|
11
|
+
deepSearchQuery: {
|
|
12
|
+
query: 'What is TypeScript?',
|
|
13
|
+
search_effort: 'low',
|
|
14
|
+
},
|
|
15
|
+
getUserAgent,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
expect(result).toHaveProperty('answer')
|
|
19
|
+
expect(result).toHaveProperty('results')
|
|
20
|
+
expect(typeof result.answer).toBe('string')
|
|
21
|
+
expect(Array.isArray(result.results)).toBe(true)
|
|
22
|
+
expect(result.answer.length).toBeGreaterThan(0)
|
|
23
|
+
},
|
|
24
|
+
{ retry: 2 },
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
test(
|
|
28
|
+
'handles medium search effort',
|
|
29
|
+
async () => {
|
|
30
|
+
const result = await callDeepSearch({
|
|
31
|
+
deepSearchQuery: {
|
|
32
|
+
query: 'Explain REST API principles',
|
|
33
|
+
search_effort: 'medium',
|
|
34
|
+
},
|
|
35
|
+
getUserAgent,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
expect(result).toHaveProperty('answer')
|
|
39
|
+
expect(result).toHaveProperty('results')
|
|
40
|
+
expect(typeof result.answer).toBe('string')
|
|
41
|
+
expect(Array.isArray(result.results)).toBe(true)
|
|
42
|
+
},
|
|
43
|
+
{ retry: 2 },
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
test(
|
|
47
|
+
'validates response schema with sources',
|
|
48
|
+
async () => {
|
|
49
|
+
const result = await callDeepSearch({
|
|
50
|
+
deepSearchQuery: {
|
|
51
|
+
query: 'What are the benefits of microservices?',
|
|
52
|
+
search_effort: 'low',
|
|
53
|
+
},
|
|
54
|
+
getUserAgent,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Test that results have required properties
|
|
58
|
+
expect(result.results.length).toBeGreaterThan(0)
|
|
59
|
+
|
|
60
|
+
const source = result.results[0]
|
|
61
|
+
expect(source).toBeDefined()
|
|
62
|
+
expect(source).toHaveProperty('url')
|
|
63
|
+
expect(source).toHaveProperty('title')
|
|
64
|
+
expect(source).toHaveProperty('snippets')
|
|
65
|
+
expect(typeof source?.url).toBe('string')
|
|
66
|
+
expect(typeof source?.title).toBe('string')
|
|
67
|
+
expect(Array.isArray(source?.snippets)).toBe(true)
|
|
68
|
+
},
|
|
69
|
+
{ retry: 2 },
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
test(
|
|
73
|
+
'answer contains markdown with inline citations',
|
|
74
|
+
async () => {
|
|
75
|
+
const result = await callDeepSearch({
|
|
76
|
+
deepSearchQuery: {
|
|
77
|
+
query: 'What is JWT authentication?',
|
|
78
|
+
search_effort: 'low',
|
|
79
|
+
},
|
|
80
|
+
getUserAgent,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Answer should be non-empty markdown string
|
|
84
|
+
expect(typeof result.answer).toBe('string')
|
|
85
|
+
expect(result.answer.length).toBeGreaterThan(0)
|
|
86
|
+
|
|
87
|
+
// Answer typically contains citations in the format [1], [2], etc.
|
|
88
|
+
// This is a soft check - citations may or may not be present
|
|
89
|
+
if (result.answer.includes('[')) {
|
|
90
|
+
expect(result.answer).toMatch(/\[\d+\]/)
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{ retry: 2 },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
test(
|
|
97
|
+
'handles complex multi-part questions',
|
|
98
|
+
async () => {
|
|
99
|
+
const result = await callDeepSearch({
|
|
100
|
+
deepSearchQuery: {
|
|
101
|
+
query: 'What is GraphQL and how does it differ from REST?',
|
|
102
|
+
search_effort: 'low',
|
|
103
|
+
},
|
|
104
|
+
getUserAgent,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
expect(result).toHaveProperty('answer')
|
|
108
|
+
expect(result).toHaveProperty('results')
|
|
109
|
+
expect(result.results.length).toBeGreaterThan(0)
|
|
110
|
+
|
|
111
|
+
// Complex questions should have substantial answers
|
|
112
|
+
expect(result.answer.length).toBeGreaterThan(100)
|
|
113
|
+
},
|
|
114
|
+
{ retry: 2 },
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
test(
|
|
118
|
+
'sources include relevant snippets',
|
|
119
|
+
async () => {
|
|
120
|
+
const result = await callDeepSearch({
|
|
121
|
+
deepSearchQuery: {
|
|
122
|
+
query: 'What is Docker containerization?',
|
|
123
|
+
search_effort: 'low',
|
|
124
|
+
},
|
|
125
|
+
getUserAgent,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Check that at least one source has snippets
|
|
129
|
+
const sourcesWithSnippets = result.results.filter((source) => source.snippets.length > 0)
|
|
130
|
+
expect(sourcesWithSnippets.length).toBeGreaterThan(0)
|
|
131
|
+
|
|
132
|
+
// Snippets should be non-empty strings
|
|
133
|
+
const firstSource = sourcesWithSnippets[0]
|
|
134
|
+
expect(firstSource).toBeDefined()
|
|
135
|
+
expect(firstSource?.snippets[0]?.length).toBeGreaterThan(0)
|
|
136
|
+
},
|
|
137
|
+
{ retry: 2 },
|
|
138
|
+
)
|
|
139
|
+
})
|