@youdotcom-oss/api 0.1.1 → 0.2.1

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 (31) hide show
  1. package/README.md +36 -36
  2. package/bin/cli.js +233 -400
  3. package/package.json +1 -1
  4. package/src/cli.ts +92 -21
  5. package/src/contents/contents.schemas.ts +8 -7
  6. package/src/contents/tests/contents.request.spec.ts +109 -0
  7. package/src/contents/tests/contents.schema-validation.spec.ts +75 -0
  8. package/src/deep-search/deep-search.schemas.ts +48 -0
  9. package/src/deep-search/deep-search.utils.ts +79 -0
  10. package/src/deep-search/tests/deep-search.request.spec.ts +109 -0
  11. package/src/deep-search/tests/deep-search.schema-validation.spec.ts +71 -0
  12. package/src/deep-search/tests/deep-search.utils.docker.ts +139 -0
  13. package/src/main.ts +4 -3
  14. package/src/search/search.schemas.ts +65 -6
  15. package/src/search/search.utils.ts +6 -28
  16. package/src/search/tests/search.request.spec.ts +122 -0
  17. package/src/search/tests/search.schema-validation.spec.ts +152 -0
  18. package/src/search/tests/{search.utils.spec.ts → search.utils.docker.ts} +0 -10
  19. package/src/shared/api.constants.ts +1 -1
  20. package/src/shared/check-response-for-errors.ts +1 -1
  21. package/src/shared/command-runner.ts +95 -0
  22. package/src/shared/dry-run-utils.ts +141 -0
  23. package/src/shared/tests/command-runner.spec.ts +210 -0
  24. package/src/shared/use-get-user-agents.ts +1 -1
  25. package/src/commands/contents.ts +0 -52
  26. package/src/commands/express.ts +0 -52
  27. package/src/commands/search.ts +0 -52
  28. package/src/express/express.schemas.ts +0 -85
  29. package/src/express/express.utils.ts +0 -113
  30. package/src/express/tests/express.utils.spec.ts +0 -83
  31. /package/src/contents/tests/{contents.utils.spec.ts → contents.utils.docker.ts} +0 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared command infrastructure for CLI commands
3
+ * Handles flag parsing, validation, and execution
4
+ *
5
+ * @internal
6
+ */
7
+
8
+ import { parseArgs } from 'node:util'
9
+ import * as z from 'zod'
10
+ import type { GetUserAgent } from './api.types.ts'
11
+ import type { DryRunResult } from './dry-run-utils.ts'
12
+ import { useGetUserAgent } from './use-get-user-agents.ts'
13
+
14
+ /**
15
+ * Configuration for a command
16
+ *
17
+ * @typeParam TInput - Zod-inferred input type
18
+ * @typeParam TOutput - Command output type
19
+ */
20
+ export type CommandConfig<TInput, TOutput> = {
21
+ schema: z.ZodType<TInput>
22
+ handler: (params: { input: TInput; YDC_API_KEY: string; getUserAgent: GetUserAgent }) => Promise<TOutput>
23
+ dryRunHandler?: (params: { input: TInput; YDC_API_KEY: string; getUserAgent: GetUserAgent }) => DryRunResult
24
+ }
25
+
26
+ /**
27
+ * Run a command with standardized flag parsing and validation
28
+ * Handles --schema, --json, --api-key, --client, and --dry-run flags
29
+ *
30
+ * @param args - Command line arguments
31
+ * @param config - Command configuration with schema and handler
32
+ *
33
+ * @internal
34
+ */
35
+ export const runCommand = async <TInput, TOutput>(args: string[], config: CommandConfig<TInput, TOutput>) => {
36
+ // Handle --schema flag
37
+ if (args.includes('--schema')) {
38
+ console.log(JSON.stringify(z.toJSONSchema(config.schema)))
39
+ process.exit(0)
40
+ }
41
+
42
+ // Parse flags with Node's built-in parseArgs
43
+ const { values } = parseArgs({
44
+ args,
45
+ options: {
46
+ json: { type: 'string' },
47
+ 'api-key': { type: 'string' },
48
+ client: { type: 'string' },
49
+ 'dry-run': { type: 'boolean' },
50
+ },
51
+ })
52
+
53
+ // --json is required
54
+ if (!values.json) {
55
+ throw new Error('--json flag is required')
56
+ }
57
+
58
+ // Parse JSON input
59
+ const input = JSON.parse(values.json)
60
+ const apiKey = values['api-key']
61
+ const client = values.client || process.env.YDC_CLIENT
62
+
63
+ // Get API key from options or environment
64
+ const YDC_API_KEY = apiKey || process.env.YDC_API_KEY
65
+ if (!YDC_API_KEY) {
66
+ throw new Error('YDC_API_KEY environment variable is required')
67
+ }
68
+
69
+ // Validate with schema
70
+ const validatedInput = config.schema.parse(input)
71
+
72
+ // Create getUserAgent function
73
+ const getUserAgent = useGetUserAgent(client)
74
+
75
+ // Handle --dry-run flag
76
+ if (values['dry-run'] && config.dryRunHandler) {
77
+ const dryRunResult = config.dryRunHandler({
78
+ input: validatedInput,
79
+ YDC_API_KEY,
80
+ getUserAgent,
81
+ })
82
+ console.log(JSON.stringify(dryRunResult))
83
+ return
84
+ }
85
+
86
+ // Execute handler
87
+ const response = await config.handler({
88
+ input: validatedInput,
89
+ YDC_API_KEY,
90
+ getUserAgent,
91
+ })
92
+
93
+ // Output response to stdout (success)
94
+ console.log(JSON.stringify(response))
95
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Dry-run utilities for testing request construction without API calls
3
+ * These functions build request details (URL, headers, body) that can be inspected
4
+ * without making actual API calls.
5
+ *
6
+ * @public
7
+ */
8
+
9
+ import type { ContentsQuery } from '../contents/contents.schemas.ts'
10
+ import type { DeepSearchQuery } from '../deep-search/deep-search.schemas.ts'
11
+ import type { SearchQuery } from '../search/search.schemas.ts'
12
+ import { CONTENTS_API_URL, DEEP_SEARCH_API_URL, SEARCH_API_URL } from './api.constants.ts'
13
+ import type { GetUserAgent } from './api.types.ts'
14
+
15
+ /**
16
+ * Result structure for dry-run request inspection
17
+ *
18
+ * @public
19
+ */
20
+ export type DryRunResult = {
21
+ url: string
22
+ method: 'GET' | 'POST'
23
+ headers: Record<string, string>
24
+ body?: string
25
+ queryParams?: Record<string, string>
26
+ }
27
+
28
+ /**
29
+ * Build search request details without making API call
30
+ * Useful for testing and debugging query construction
31
+ *
32
+ * @param params - Search query parameters
33
+ * @returns Request details including URL, headers, and query params
34
+ *
35
+ * @public
36
+ */
37
+ export const buildSearchRequest = ({
38
+ searchQuery,
39
+ YDC_API_KEY,
40
+ getUserAgent,
41
+ }: {
42
+ searchQuery: SearchQuery
43
+ YDC_API_KEY: string
44
+ getUserAgent: GetUserAgent
45
+ }): DryRunResult => {
46
+ // Convert all search query params to query string parameters
47
+ const queryParams: Record<string, string> = {}
48
+
49
+ for (const [name, value] of Object.entries(searchQuery)) {
50
+ if (value !== undefined && value !== null) {
51
+ queryParams[name] = `${value}`
52
+ }
53
+ }
54
+
55
+ return {
56
+ url: SEARCH_API_URL,
57
+ method: 'GET',
58
+ headers: {
59
+ 'X-API-Key': YDC_API_KEY,
60
+ 'User-Agent': getUserAgent(),
61
+ },
62
+ queryParams,
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Build contents request details without making API call
68
+ * Useful for testing and debugging POST body construction
69
+ *
70
+ * @param params - Contents query parameters
71
+ * @returns Request details including URL, headers, and POST body
72
+ *
73
+ * @public
74
+ */
75
+ export const buildContentsRequest = ({
76
+ contentsQuery: { urls, formats, format, crawl_timeout },
77
+ YDC_API_KEY,
78
+ getUserAgent,
79
+ }: {
80
+ contentsQuery: ContentsQuery
81
+ YDC_API_KEY: string
82
+ getUserAgent: GetUserAgent
83
+ }): DryRunResult => {
84
+ // Handle backward compatibility: prefer formats array, fallback to format string, default to ['markdown']
85
+ const requestFormats = formats || (format ? [format] : ['markdown'])
86
+
87
+ // Build request body
88
+ const requestBody: {
89
+ urls: string[]
90
+ formats: string[]
91
+ crawl_timeout?: number
92
+ } = {
93
+ urls,
94
+ formats: requestFormats,
95
+ }
96
+
97
+ if (crawl_timeout !== undefined) {
98
+ requestBody.crawl_timeout = crawl_timeout
99
+ }
100
+
101
+ return {
102
+ url: CONTENTS_API_URL,
103
+ method: 'POST',
104
+ headers: {
105
+ 'X-API-Key': YDC_API_KEY,
106
+ 'Content-Type': 'application/json',
107
+ 'User-Agent': getUserAgent(),
108
+ },
109
+ body: JSON.stringify(requestBody),
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Build deep-search request details without making API call
115
+ * Useful for testing and debugging POST body construction
116
+ *
117
+ * @param params - Deep-search query parameters
118
+ * @returns Request details including URL, headers, and POST body
119
+ *
120
+ * @public
121
+ */
122
+ export const buildDeepSearchRequest = ({
123
+ deepSearchQuery,
124
+ YDC_API_KEY,
125
+ getUserAgent,
126
+ }: {
127
+ deepSearchQuery: DeepSearchQuery
128
+ YDC_API_KEY: string
129
+ getUserAgent: GetUserAgent
130
+ }): DryRunResult => {
131
+ return {
132
+ url: DEEP_SEARCH_API_URL,
133
+ method: 'POST',
134
+ headers: {
135
+ 'X-API-Key': YDC_API_KEY,
136
+ 'Content-Type': 'application/json',
137
+ 'User-Agent': getUserAgent(),
138
+ },
139
+ body: JSON.stringify(deepSearchQuery),
140
+ }
141
+ }
@@ -0,0 +1,210 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test'
2
+ import * as z from 'zod'
3
+ import { runCommand } from '../command-runner.ts'
4
+
5
+ describe('runCommand', () => {
6
+ const TestSchema = z.object({
7
+ value: z.string(),
8
+ })
9
+
10
+ let originalApiKey: string | undefined
11
+
12
+ beforeAll(() => {
13
+ // Save original API key
14
+ originalApiKey = process.env.YDC_API_KEY
15
+ })
16
+
17
+ afterAll(() => {
18
+ // Restore original API key
19
+ if (originalApiKey) {
20
+ process.env.YDC_API_KEY = originalApiKey
21
+ } else {
22
+ delete process.env.YDC_API_KEY
23
+ }
24
+ })
25
+
26
+ test('outputs JSON schema when --schema flag is provided', async () => {
27
+ const originalExit = process.exit
28
+ const originalLog = console.log
29
+
30
+ process.exit = (() => {
31
+ throw new Error('EXIT')
32
+ }) as typeof process.exit
33
+
34
+ let outputData = ''
35
+ console.log = (data: string) => {
36
+ outputData = data
37
+ }
38
+
39
+ try {
40
+ await runCommand(['--schema'], {
41
+ schema: TestSchema,
42
+ handler: async () => ({ result: 'test' }),
43
+ })
44
+ } catch (error) {
45
+ if (error instanceof Error && error.message === 'EXIT') {
46
+ const schema = JSON.parse(outputData)
47
+ expect(schema.type).toBe('object')
48
+ expect(schema.properties).toBeDefined()
49
+ }
50
+ }
51
+
52
+ // Restore original functions
53
+ process.exit = originalExit
54
+ console.log = originalLog
55
+ })
56
+
57
+ test('throws error when --json flag is missing', async () => {
58
+ expect(async () => {
59
+ await runCommand([], {
60
+ schema: TestSchema,
61
+ handler: async () => ({ result: 'test' }),
62
+ })
63
+ }).toThrow('--json flag is required')
64
+ })
65
+
66
+ test('throws error when JSON is malformed', async () => {
67
+ expect(async () => {
68
+ await runCommand(['--json', 'invalid-json'], {
69
+ schema: TestSchema,
70
+ handler: async () => ({ result: 'test' }),
71
+ })
72
+ }).toThrow()
73
+ })
74
+
75
+ test('throws error when API key is missing from both flag and env', async () => {
76
+ const originalApiKey = process.env.YDC_API_KEY
77
+ delete process.env.YDC_API_KEY
78
+
79
+ expect(async () => {
80
+ await runCommand(['--json', '{"value":"test"}'], {
81
+ schema: TestSchema,
82
+ handler: async () => ({ result: 'test' }),
83
+ })
84
+ }).toThrow('YDC_API_KEY environment variable is required')
85
+
86
+ // Restore original value
87
+ if (originalApiKey) {
88
+ process.env.YDC_API_KEY = originalApiKey
89
+ }
90
+ })
91
+
92
+ test('resolves API key from --api-key flag over environment', async () => {
93
+ const originalLog = console.log
94
+ process.env.YDC_API_KEY = 'env-key'
95
+
96
+ let capturedKey = ''
97
+ console.log = () => {
98
+ // Suppress output
99
+ }
100
+
101
+ await runCommand(['--json', '{"value":"test"}', '--api-key', 'flag-key'], {
102
+ schema: TestSchema,
103
+ handler: async ({ YDC_API_KEY }) => {
104
+ capturedKey = YDC_API_KEY
105
+ return { result: 'test' }
106
+ },
107
+ })
108
+
109
+ expect(capturedKey).toBe('flag-key')
110
+
111
+ // Restore original function
112
+ console.log = originalLog
113
+ })
114
+
115
+ test('throws error when schema validation fails', async () => {
116
+ process.env.YDC_API_KEY = 'test-key'
117
+
118
+ expect(async () => {
119
+ await runCommand(['--json', '{"invalid":"field"}'], {
120
+ schema: TestSchema,
121
+ handler: async () => ({ result: 'test' }),
122
+ })
123
+ }).toThrow()
124
+ })
125
+
126
+ test('calls handler when all validations pass', async () => {
127
+ const originalLog = console.log
128
+ process.env.YDC_API_KEY = 'test-key'
129
+
130
+ let handlerCalled = false
131
+ let outputData = ''
132
+ console.log = (data: string) => {
133
+ outputData = data
134
+ }
135
+
136
+ await runCommand(['--json', '{"value":"test"}'], {
137
+ schema: TestSchema,
138
+ handler: async () => {
139
+ handlerCalled = true
140
+ return { result: 'success' }
141
+ },
142
+ })
143
+
144
+ expect(handlerCalled).toBe(true)
145
+ const output = JSON.parse(outputData)
146
+ expect(output.result).toBe('success')
147
+
148
+ // Restore original function
149
+ console.log = originalLog
150
+ })
151
+
152
+ test('calls dryRunHandler when --dry-run flag is provided', async () => {
153
+ const originalLog = console.log
154
+ process.env.YDC_API_KEY = 'test-key'
155
+
156
+ let dryRunCalled = false
157
+ let handlerCalled = false
158
+ let outputData = ''
159
+ console.log = (data: string) => {
160
+ outputData = data
161
+ }
162
+
163
+ await runCommand(['--json', '{"value":"test"}', '--dry-run'], {
164
+ schema: TestSchema,
165
+ handler: async () => {
166
+ handlerCalled = true
167
+ return { result: 'success' }
168
+ },
169
+ dryRunHandler: () => {
170
+ dryRunCalled = true
171
+ return {
172
+ url: 'https://test.com',
173
+ method: 'GET',
174
+ headers: { 'X-API-Key': 'test-key' },
175
+ }
176
+ },
177
+ })
178
+
179
+ expect(dryRunCalled).toBe(true)
180
+ expect(handlerCalled).toBe(false)
181
+ const output = JSON.parse(outputData)
182
+ expect(output.url).toBe('https://test.com')
183
+
184
+ // Restore original function
185
+ console.log = originalLog
186
+ })
187
+
188
+ test('respects --client flag for User-Agent', async () => {
189
+ const originalLog = console.log
190
+ process.env.YDC_API_KEY = 'test-key'
191
+
192
+ let capturedUserAgent = ''
193
+ console.log = () => {
194
+ // Suppress output
195
+ }
196
+
197
+ await runCommand(['--json', '{"value":"test"}', '--client', 'TestClient'], {
198
+ schema: TestSchema,
199
+ handler: async ({ getUserAgent }) => {
200
+ capturedUserAgent = getUserAgent()
201
+ return { result: 'test' }
202
+ },
203
+ })
204
+
205
+ expect(capturedUserAgent).toContain('TestClient')
206
+
207
+ // Restore original function
208
+ console.log = originalLog
209
+ })
210
+ })
@@ -5,4 +5,4 @@ import type { GetUserAgent } from './api.types.ts'
5
5
  export const useGetUserAgent =
6
6
  (client = ''): GetUserAgent =>
7
7
  () =>
8
- `CLI/ ${packageJson.version} (You.com;${client})`
8
+ `CLI/${packageJson.version}(You.com;${client})`
@@ -1,52 +0,0 @@
1
- import { parseArgs } from 'node:util'
2
- import * as z from 'zod'
3
- import { ContentsQuerySchema } from '../contents/contents.schemas.ts'
4
- import { fetchContents } from '../contents/contents.utils.ts'
5
- import { useGetUserAgent } from '../shared/use-get-user-agents.ts'
6
-
7
- export const contentsCommand = async (args: string[]) => {
8
- // Handle --schema flag
9
- if (args.includes('--schema')) {
10
- console.log(JSON.stringify(z.toJSONSchema(ContentsQuerySchema)))
11
- process.exit(0)
12
- }
13
-
14
- // Parse flags with Node's built-in parseArgs
15
- const { values } = parseArgs({
16
- args,
17
- options: {
18
- json: { type: 'string' },
19
- 'api-key': { type: 'string' },
20
- client: { type: 'string' },
21
- },
22
- })
23
-
24
- // --json is required
25
- if (!values.json) {
26
- throw new Error('--json flag is required')
27
- }
28
-
29
- // Parse JSON and validate with schema
30
- const query = JSON.parse(values.json)
31
- const apiKey = values['api-key']
32
- const client = values.client || process.env.YDC_CLIENT
33
-
34
- // Get API key from options or environment
35
- const YDC_API_KEY = apiKey || process.env.YDC_API_KEY
36
- if (!YDC_API_KEY) {
37
- throw new Error('YDC_API_KEY environment variable is required')
38
- }
39
-
40
- // Validate with schema (includes urls validation)
41
- const contentsQuery = ContentsQuerySchema.parse(query)
42
-
43
- // Fetch contents
44
- const response = await fetchContents({
45
- contentsQuery,
46
- YDC_API_KEY,
47
- getUserAgent: useGetUserAgent(client),
48
- })
49
-
50
- // Output response to stdout (success)
51
- console.log(JSON.stringify(response))
52
- }
@@ -1,52 +0,0 @@
1
- import { parseArgs } from 'node:util'
2
- import * as z from 'zod'
3
- import { ExpressAgentInputSchema } from '../express/express.schemas.ts'
4
- import { callExpressAgent } from '../express/express.utils.ts'
5
- import { useGetUserAgent } from '../shared/use-get-user-agents.ts'
6
-
7
- export const expressCommand = async (args: string[]) => {
8
- // Handle --schema flag
9
- if (args.includes('--schema')) {
10
- console.log(JSON.stringify(z.toJSONSchema(ExpressAgentInputSchema)))
11
- process.exit(0)
12
- }
13
-
14
- // Parse flags with Node's built-in parseArgs
15
- const { values } = parseArgs({
16
- args,
17
- options: {
18
- json: { type: 'string' },
19
- 'api-key': { type: 'string' },
20
- client: { type: 'string' },
21
- },
22
- })
23
-
24
- // --json is required
25
- if (!values.json) {
26
- throw new Error('--json flag is required')
27
- }
28
-
29
- // Parse JSON and validate with schema
30
- const input = JSON.parse(values.json)
31
- const apiKey = values['api-key']
32
- const client = values.client || process.env.YDC_CLIENT
33
-
34
- // Get API key from options or environment
35
- const YDC_API_KEY = apiKey || process.env.YDC_API_KEY
36
- if (!YDC_API_KEY) {
37
- throw new Error('YDC_API_KEY environment variable is required')
38
- }
39
-
40
- // Validate with schema (includes input validation)
41
- const agentInput = ExpressAgentInputSchema.parse(input)
42
-
43
- // Call agent
44
- const response = await callExpressAgent({
45
- agentInput,
46
- YDC_API_KEY,
47
- getUserAgent: useGetUserAgent(client),
48
- })
49
-
50
- // Output response to stdout (success)
51
- console.log(JSON.stringify(response))
52
- }
@@ -1,52 +0,0 @@
1
- import { parseArgs } from 'node:util'
2
- import * as z from 'zod'
3
- import { SearchQuerySchema } from '../search/search.schemas.ts'
4
- import { fetchSearchResults } from '../search/search.utils.ts'
5
- import { useGetUserAgent } from '../shared/use-get-user-agents.ts'
6
-
7
- export const searchCommand = async (args: string[]) => {
8
- // Handle --schema flag
9
- if (args.includes('--schema')) {
10
- console.log(JSON.stringify(z.toJSONSchema(SearchQuerySchema)))
11
- process.exit(0)
12
- }
13
-
14
- // Parse flags with Node's built-in parseArgs
15
- const { values } = parseArgs({
16
- args,
17
- options: {
18
- json: { type: 'string' },
19
- 'api-key': { type: 'string' },
20
- client: { type: 'string' },
21
- },
22
- })
23
-
24
- // --json is required
25
- if (!values.json) {
26
- throw new Error('--json flag is required')
27
- }
28
-
29
- // Parse JSON and validate with schema
30
- const query = JSON.parse(values.json)
31
- const apiKey = values['api-key']
32
- const client = values.client || process.env.YDC_CLIENT
33
-
34
- // Get API key from options or environment
35
- const YDC_API_KEY = apiKey || process.env.YDC_API_KEY
36
- if (!YDC_API_KEY) {
37
- throw new Error('YDC_API_KEY environment variable is required')
38
- }
39
-
40
- // Validate with schema (includes query validation)
41
- const searchQuery = SearchQuerySchema.parse(query)
42
-
43
- // Fetch results
44
- const response = await fetchSearchResults({
45
- searchQuery,
46
- YDC_API_KEY,
47
- getUserAgent: useGetUserAgent(client),
48
- })
49
-
50
- // Output response to stdout (success)
51
- console.log(JSON.stringify(response))
52
- }
@@ -1,85 +0,0 @@
1
- import * as z from 'zod'
2
-
3
- export const ExpressAgentInputSchema = z.object({
4
- input: z.string().min(1, 'Input is required').describe('Query or prompt'),
5
- tools: z
6
- .array(
7
- z.object({
8
- type: z.enum(['web_search']).describe('Tool type'),
9
- }),
10
- )
11
- .optional()
12
- .describe('Tools (web search only)'),
13
- })
14
-
15
- export type ExpressAgentInput = z.infer<typeof ExpressAgentInputSchema>
16
-
17
- // API Response Schema - Validates the full response from You.com API
18
-
19
- // Search result content item from web_search.results
20
- // Note: thumbnail_url, source_type, and provider are API-only pass-through fields not used in MCP output
21
- const ApiSearchResultItemSchema = z.object({
22
- source_type: z.string().nullable().optional(),
23
- citation_uri: z.string().optional(), // Used as fallback for url in transformation
24
- url: z.string(),
25
- title: z.string(),
26
- snippet: z.string(),
27
- thumbnail_url: z.string().nullable().optional(), // API-only, not transformed to MCP output
28
- provider: z.string().nullable().optional(), // API-only, not transformed to MCP output
29
- })
30
-
31
- // Union of possible output item types from API
32
- const ExpressAgentApiOutputItemSchema = z.union([
33
- // web_search.results type - has content array, no text
34
- z.object({
35
- type: z.literal('web_search.results'),
36
- content: z.array(ApiSearchResultItemSchema),
37
- }),
38
- // message.answer type - has text, no content
39
- z.object({
40
- type: z.literal('message.answer'),
41
- text: z.string(),
42
- }),
43
- ])
44
-
45
- export const ExpressAgentApiResponseSchema = z
46
- .object({
47
- output: z.array(ExpressAgentApiOutputItemSchema),
48
- agent: z.string().optional().describe('Agent identifier'),
49
- mode: z.string().optional().describe('Agent mode'),
50
- input: z
51
- .array(
52
- z.object({
53
- role: z.enum(['user']).describe('User role'),
54
- content: z.string().describe('User question'),
55
- }),
56
- )
57
- .optional()
58
- .describe('Input messages'),
59
- })
60
- .passthrough()
61
-
62
- export type ExpressAgentApiResponse = z.infer<typeof ExpressAgentApiResponseSchema>
63
-
64
- // MCP Output Schema - Defines what we return to the MCP client (answer + optional search results, token efficient)
65
-
66
- // Search result item for MCP output
67
- const McpSearchResultItemSchema = z.object({
68
- url: z.string().describe('URL'),
69
- title: z.string().describe('Title'),
70
- snippet: z.string().describe('Snippet'),
71
- })
72
-
73
- // MCP response structure: answer (always) + results (optional when web_search used)
74
- const ExpressAgentMcpResponseSchema = z.object({
75
- answer: z.string().describe('AI answer'),
76
- results: z
77
- .object({
78
- web: z.array(McpSearchResultItemSchema).describe('Web results'),
79
- })
80
- .optional()
81
- .describe('Search results'),
82
- agent: z.string().optional().describe('Agent ID'),
83
- })
84
-
85
- export type ExpressAgentMcpResponse = z.infer<typeof ExpressAgentMcpResponseSchema>