@expandai/ai
Advanced tools
+23
-50
@@ -12,13 +12,14 @@ 'use strict'; | ||
| // src/tool.ts | ||
| var snippetsFormatSchema = zod.z.object({ | ||
| query: zod.z.string().describe("Query to find relevant content snippets from the page"), | ||
| maxSnippets: zod.z.number().int().min(1).max(50).optional().describe("Maximum number of snippets to return (1-50, default: 5)"), | ||
| targetSnippetSize: zod.z.number().int().min(100).max(2e3).optional().describe("Target snippet size in characters (100-2000, default: 384)") | ||
| var searchParamsSchema = zod.z.object({ | ||
| query: zod.z.string().describe("Query to find relevant content snippets"), | ||
| minScore: zod.z.number().min(0).max(1).default(0.6).describe("Minimum relevance score (0-1)"), | ||
| maxResults: zod.z.number().int().min(1).max(50).default(5).describe("Maximum snippets to return") | ||
| }); | ||
| var formatSchema = zod.z.union([ | ||
| zod.z.literal("markdown").describe("Return as cleaned markdown content"), | ||
| zod.z.literal("html").describe("Return as raw HTML content"), | ||
| zod.z.literal("summary").describe("Return AI-generated summary of the page content"), | ||
| snippetsFormatSchema | ||
| ]).describe("Output format for the fetched content"); | ||
| var inputSchema = zod.z.object({ | ||
| url: zod.z.string().url().describe("The URL to fetch content from"), | ||
| includeMeta: zod.z.boolean().default(false).describe("Include page meta tags (title, description, Open Graph)"), | ||
| search: searchParamsSchema.optional().describe( | ||
| "Semantic search. When provided, returns snippets instead of markdown." | ||
| ) | ||
| }); | ||
| function createExpandFetchTool(options) { | ||
@@ -31,3 +32,3 @@ const apiKey = options?.apiKey ?? process.env.EXPAND_API_KEY; | ||
| } | ||
| const { description, defaults, ...clientOptions } = options ?? {}; | ||
| const { description, ...clientOptions } = options ?? {}; | ||
| const client = new Expand__default.default({ | ||
@@ -37,50 +38,22 @@ ...clientOptions, | ||
| }); | ||
| const defaultFormat = defaults?.format ?? "markdown"; | ||
| const defaultIncludeMeta = defaults?.includeMeta ?? true; | ||
| const defaultScrollFullPage = defaults?.scrollFullPage ?? false; | ||
| const inputSchema = zod.z.object({ | ||
| url: zod.z.string().url().describe("The URL to fetch content from"), | ||
| format: formatSchema.default(defaultFormat), | ||
| includeMeta: zod.z.boolean().default(defaultIncludeMeta).describe("Whether to include page meta tags in the response"), | ||
| scrollFullPage: zod.z.boolean().default(defaultScrollFullPage).describe( | ||
| "Whether to scroll the full page to capture lazy-loaded content. Set to true for pages with infinite scroll or dynamic content loading." | ||
| ) | ||
| }); | ||
| return ai.tool({ | ||
| description: description ?? "Fetch and extract content from any URL.", | ||
| description: description ?? "Fetch and extract content from any URL.\n\nReturns markdown by default. When `search` is provided, returns semantically relevant snippets instead.", | ||
| inputSchema, | ||
| execute: async (input) => { | ||
| const { url, format, includeMeta, scrollFullPage } = input; | ||
| const { url, includeMeta, search } = input; | ||
| try { | ||
| const isSnippets = typeof format === "object" && "query" in format; | ||
| const hasSearch = search !== void 0; | ||
| const response = await client.fetch({ | ||
| url, | ||
| select: { | ||
| markdown: format === "markdown", | ||
| html: format === "html", | ||
| summary: format === "summary", | ||
| snippets: isSnippets ? format : void 0, | ||
| markdown: !hasSearch, | ||
| snippets: hasSearch ? { | ||
| query: search.query, | ||
| maxSnippets: search.maxResults, | ||
| minScore: search.minScore | ||
| } : void 0, | ||
| meta: includeMeta | ||
| }, | ||
| ...scrollFullPage && { browserConfig: { scrollFullPage: true } } | ||
| } | ||
| }); | ||
| const data = response.data; | ||
| let content; | ||
| if (format === "markdown" && data.markdown) { | ||
| content = data.markdown; | ||
| } else if (format === "html" && data.html) { | ||
| content = data.html; | ||
| } else if (format === "summary" && data.summary) { | ||
| content = data.summary; | ||
| } else if (isSnippets && data.snippets) { | ||
| const snippets = data.snippets; | ||
| content = snippets.map((s) => `${s.text} (Score: ${s.score})`).join("\n"); | ||
| } else { | ||
| content = ""; | ||
| } | ||
| return { | ||
| content, | ||
| url: response.data.response.url, | ||
| ...includeMeta && data.meta ? { meta: data.meta } : {} | ||
| }; | ||
| return response.data; | ||
| } catch (error) { | ||
@@ -87,0 +60,0 @@ if (error instanceof Expand__default.default.APIError) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/tool.ts"],"names":["z","Expand","tool"],"mappings":";;;;;;;;;;;AAuEA,IAAM,oBAAA,GAAuBA,MAAE,MAAA,CAAO;AAAA,EACrC,KAAA,EAAOA,KAAA,CACL,MAAA,EAAO,CACP,SAAS,uDAAuD,CAAA;AAAA,EAClE,WAAA,EAAaA,KAAA,CACX,MAAA,EAAO,CACP,KAAI,CACJ,GAAA,CAAI,CAAC,CAAA,CACL,IAAI,EAAE,CAAA,CACN,QAAA,EAAS,CACT,SAAS,yDAAyD,CAAA;AAAA,EACpE,iBAAA,EAAmBA,KAAA,CACjB,MAAA,EAAO,CACP,KAAI,CACJ,GAAA,CAAI,GAAG,CAAA,CACP,IAAI,GAAI,CAAA,CACR,QAAA,EAAS,CACT,SAAS,4DAA4D;AACxE,CAAC,CAAA;AAGD,IAAM,YAAA,GAAeA,MACnB,KAAA,CAAM;AAAA,EACNA,KAAA,CAAE,OAAA,CAAQ,UAAU,CAAA,CAAE,SAAS,oCAAoC,CAAA;AAAA,EACnEA,KAAA,CAAE,OAAA,CAAQ,MAAM,CAAA,CAAE,SAAS,4BAA4B,CAAA;AAAA,EACvDA,KAAA,CACE,OAAA,CAAQ,SAAS,CAAA,CACjB,SAAS,iDAAiD,CAAA;AAAA,EAC5D;AACD,CAAC,CAAA,CACA,SAAS,uCAAuC,CAAA;AAsC3C,SAAS,sBAAsB,OAAA,EAAkC;AACvE,EAAA,MAAM,MAAA,GAAS,OAAA,EAAS,MAAA,IAAU,OAAA,CAAQ,GAAA,CAAI,cAAA;AAE9C,EAAA,IAAI,CAAC,MAAA,EAAQ;AACZ,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AAGA,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,EAAU,GAAG,aAAA,EAAc,GAAI,WAAW,EAAC;AAGhE,EAAA,MAAM,MAAA,GAAS,IAAIC,uBAAA,CAAO;AAAA,IACzB,GAAG,aAAA;AAAA,IACH;AAAA,GACA,CAAA;AAGD,EAAA,MAAM,aAAA,GAAwB,UAAU,MAAA,IAAU,UAAA;AAClD,EAAA,MAAM,kBAAA,GAAqB,UAAU,WAAA,IAAe,IAAA;AACpD,EAAA,MAAM,qBAAA,GAAwB,UAAU,cAAA,IAAkB,KAAA;AAE1D,EAAA,MAAM,WAAA,GAAcD,MAAE,MAAA,CAAO;AAAA,IAC5B,KAAKA,KAAA,CAAE,MAAA,GAAS,GAAA,EAAI,CAAE,SAAS,+BAA+B,CAAA;AAAA,IAC9D,MAAA,EAAQ,YAAA,CAAa,OAAA,CAAQ,aAAa,CAAA;AAAA,IAC1C,WAAA,EAAaA,MACX,OAAA,EAAQ,CACR,QAAQ,kBAAkB,CAAA,CAC1B,SAAS,mDAAmD,CAAA;AAAA,IAC9D,gBAAgBA,KAAA,CACd,OAAA,EAAQ,CACR,OAAA,CAAQ,qBAAqB,CAAA,CAC7B,QAAA;AAAA,MACA;AAAA;AACD,GACD,CAAA;AAID,EAAA,OAAOE,OAAA,CAAK;AAAA,IACX,aAAa,WAAA,IAAe,yCAAA;AAAA,IAC5B,WAAA;AAAA,IACA,OAAA,EAAS,OAAO,KAAA,KAA2C;AAC1D,MAAA,MAAM,EAAE,GAAA,EAAK,MAAA,EAAQ,WAAA,EAAa,gBAAe,GAAI,KAAA;AACrD,MAAA,IAAI;AAEH,QAAA,MAAM,UAAA,GAAa,OAAO,MAAA,KAAW,QAAA,IAAY,OAAA,IAAW,MAAA;AAE5D,QAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,KAAA,CAAM;AAAA,UACnC,GAAA;AAAA,UACA,MAAA,EAAQ;AAAA,YACP,UAAU,MAAA,KAAW,UAAA;AAAA,YACrB,MAAM,MAAA,KAAW,MAAA;AAAA,YACjB,SAAS,MAAA,KAAW,SAAA;AAAA,YACpB,QAAA,EAAU,aAAa,MAAA,GAAS,MAAA;AAAA,YAChC,IAAA,EAAM;AAAA,WACP;AAAA,UACA,GAAI,cAAA,IAAkB,EAAE,eAAe,EAAE,cAAA,EAAgB,MAAK;AAAE,SAChE,CAAA;AAGD,QAAA,MAAM,OAAO,QAAA,CAAS,IAAA;AACtB,QAAA,IAAI,OAAA;AAEJ,QAAA,IAAI,MAAA,KAAW,UAAA,IAAc,IAAA,CAAK,QAAA,EAAU;AAC3C,UAAA,OAAA,GAAU,IAAA,CAAK,QAAA;AAAA,QAChB,CAAA,MAAA,IAAW,MAAA,KAAW,MAAA,IAAU,IAAA,CAAK,IAAA,EAAM;AAC1C,UAAA,OAAA,GAAU,IAAA,CAAK,IAAA;AAAA,QAChB,CAAA,MAAA,IAAW,MAAA,KAAW,SAAA,IAAa,IAAA,CAAK,OAAA,EAAS;AAChD,UAAA,OAAA,GAAU,IAAA,CAAK,OAAA;AAAA,QAChB,CAAA,MAAA,IAAW,UAAA,IAAc,IAAA,CAAK,QAAA,EAAU;AACvC,UAAA,MAAM,WAAW,IAAA,CAAK,QAAA;AAItB,UAAA,OAAA,GAAU,QAAA,CACR,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,SAAA,EAAY,CAAA,CAAE,KAAK,CAAA,CAAA,CAAG,CAAA,CAC1C,KAAK,IAAI,CAAA;AAAA,QACZ,CAAA,MAAO;AACN,UAAA,OAAA,GAAU,EAAA;AAAA,QACX;AAEA,QAAA,OAAO;AAAA,UACN,OAAA;AAAA,UACA,GAAA,EAAK,QAAA,CAAS,IAAA,CAAK,QAAA,CAAS,GAAA;AAAA,UAC5B,GAAI,eAAe,IAAA,CAAK,IAAA,GACrB,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAA4B,GACzC;AAAC,SACL;AAAA,MACD,SAAS,KAAA,EAAO;AAEf,QAAA,IAAI,KAAA,YAAiBD,wBAAO,QAAA,EAAU;AACrC,UAAA,MAAM,IAAI,KAAA;AAAA,YACT,mBAAmB,GAAG,CAAA,EAAA,EAAK,MAAM,OAAO,CAAA,UAAA,EAAa,MAAM,MAAM,CAAA,CAAA;AAAA,WAClE;AAAA,QACD;AACA,QAAA,MAAM,IAAI,KAAA;AAAA,UACT,mBAAmB,GAAG,CAAA,EAAA,EAAK,iBAAiB,KAAA,GAAQ,KAAA,CAAM,UAAU,eAAe,CAAA;AAAA,SACpF;AAAA,MACD;AAAA,IACD;AAAA,GACA,CAAA;AACF;AAmBA,IAAI,gBAAA;AAEG,IAAM,kBAAkB,IAAI,KAAA;AAAA,EAClC,EAAC;AAAA,EACD;AAAA,IACC,GAAA,CAAI,SAAS,IAAA,EAAM;AAClB,MAAA,IAAI,CAAC,gBAAA,EAAkB;AACtB,QAAA,gBAAA,GAAmB,qBAAA,EAAsB;AAAA,MAC1C;AACA,MAAA,OAAQ,iBAAsD,IAAI,CAAA;AAAA,IACnE;AAAA;AAEF","file":"index.cjs","sourcesContent":["import type { ClientOptions } from '@expandai/sdk'\nimport Expand from '@expandai/sdk'\nimport { tool } from 'ai'\nimport { z } from 'zod'\n\n// Re-export ClientOptions for convenience\nexport type { ClientOptions }\n\n/**\n * Snippets format configuration for semantic search within page content.\n */\nexport interface SnippetsFormat {\n\t/** Query to find relevant content snippets from the page */\n\tquery: string\n\t/** Maximum number of snippets to return (1-50, default: 5) */\n\tmaxSnippets?: number\n\t/** Target snippet size in characters (100-2000, default: 384) */\n\ttargetSnippetSize?: number\n}\n\n/**\n * Output format for the fetched content.\n * - 'markdown': Return as cleaned markdown content\n * - 'html': Return as raw HTML content\n * - 'summary': Return AI-generated summary of the page content\n * - SnippetsFormat: Return relevant content snippets matching a query\n */\nexport type Format = 'markdown' | 'html' | 'summary' | SnippetsFormat\n\n/**\n * Default configuration for the fetch tool.\n * These values are used when the LLM doesn't specify them.\n */\nexport interface FetchToolDefaults {\n\t/** Default output format (default: 'markdown') */\n\tformat?: Format\n\t/** Whether to include page meta tags by default (default: true) */\n\tincludeMeta?: boolean\n\t/** Whether to scroll the full page to capture lazy-loaded content (default: false) */\n\tscrollFullPage?: boolean\n}\n\n/**\n * Configuration options for the Expand fetch tool.\n */\nexport interface ExpandFetchToolOptions extends ClientOptions {\n\t/** Custom description for the tool (overrides default) */\n\tdescription?: string\n\t/** Default values for tool parameters */\n\tdefaults?: FetchToolDefaults\n}\n\n/**\n * Result returned by the fetch tool.\n */\nexport interface FetchResult {\n\t/** The fetched content in the requested format */\n\tcontent: string\n\t/** The final URL after any redirects */\n\turl: string\n\t/** Page metadata (title, description, etc.) if includeMeta was true */\n\tmeta?: {\n\t\ttitle?: string | null\n\t\tdescription?: string | null\n\t\tcanonicalUrl?: string | null\n\t\tlanguage?: string | null\n\t\tcharset?: string | null\n\t}\n}\n\n// Zod schema for snippets format - matches MCP server\nconst snippetsFormatSchema = z.object({\n\tquery: z\n\t\t.string()\n\t\t.describe('Query to find relevant content snippets from the page'),\n\tmaxSnippets: z\n\t\t.number()\n\t\t.int()\n\t\t.min(1)\n\t\t.max(50)\n\t\t.optional()\n\t\t.describe('Maximum number of snippets to return (1-50, default: 5)'),\n\ttargetSnippetSize: z\n\t\t.number()\n\t\t.int()\n\t\t.min(100)\n\t\t.max(2000)\n\t\t.optional()\n\t\t.describe('Target snippet size in characters (100-2000, default: 384)'),\n})\n\n// Zod schema for format parameter - mirrors MCP server exactly\nconst formatSchema = z\n\t.union([\n\t\tz.literal('markdown').describe('Return as cleaned markdown content'),\n\t\tz.literal('html').describe('Return as raw HTML content'),\n\t\tz\n\t\t\t.literal('summary')\n\t\t\t.describe('Return AI-generated summary of the page content'),\n\t\tsnippetsFormatSchema,\n\t])\n\t.describe('Output format for the fetched content')\n\n/**\n * Create an Expand fetch tool for the Vercel AI SDK.\n *\n * The tool mirrors the MCP server's fetch tool, providing consistent behavior\n * across all Expand integrations.\n *\n * @param options - Configuration options (SDK client options + tool-specific options)\n * @returns A Vercel AI SDK tool that fetches and extracts web content\n *\n * @example\n * ```typescript\n * import { createExpandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n * import { openai } from '@ai-sdk/openai'\n *\n * // With default markdown format\n * const expandTool = createExpandFetchTool({\n * apiKey: process.env.EXPAND_API_KEY,\n * })\n *\n * // With custom defaults\n * const expandTool = createExpandFetchTool({\n * apiKey: process.env.EXPAND_API_KEY,\n * defaults: {\n * format: 'summary',\n * includeMeta: false,\n * },\n * })\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandTool },\n * prompt: 'What is on the Expand homepage?',\n * })\n * ```\n */\nexport function createExpandFetchTool(options?: ExpandFetchToolOptions) {\n\tconst apiKey = options?.apiKey ?? process.env.EXPAND_API_KEY\n\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t'Expand API key is required. Provide it via options.apiKey or set EXPAND_API_KEY environment variable.',\n\t\t)\n\t}\n\n\t// Extract tool-specific options, pass rest to SDK client\n\tconst { description, defaults, ...clientOptions } = options ?? {}\n\n\t// Create Expand client with all client options\n\tconst client = new Expand({\n\t\t...clientOptions,\n\t\tapiKey,\n\t})\n\n\t// Apply defaults\n\tconst defaultFormat: Format = defaults?.format ?? 'markdown'\n\tconst defaultIncludeMeta = defaults?.includeMeta ?? true\n\tconst defaultScrollFullPage = defaults?.scrollFullPage ?? false\n\n\tconst inputSchema = z.object({\n\t\turl: z.string().url().describe('The URL to fetch content from'),\n\t\tformat: formatSchema.default(defaultFormat),\n\t\tincludeMeta: z\n\t\t\t.boolean()\n\t\t\t.default(defaultIncludeMeta)\n\t\t\t.describe('Whether to include page meta tags in the response'),\n\t\tscrollFullPage: z\n\t\t\t.boolean()\n\t\t\t.default(defaultScrollFullPage)\n\t\t\t.describe(\n\t\t\t\t'Whether to scroll the full page to capture lazy-loaded content. Set to true for pages with infinite scroll or dynamic content loading.',\n\t\t\t),\n\t})\n\n\ttype ToolInput = z.infer<typeof inputSchema>\n\n\treturn tool({\n\t\tdescription: description ?? 'Fetch and extract content from any URL.',\n\t\tinputSchema,\n\t\texecute: async (input: ToolInput): Promise<FetchResult> => {\n\t\t\tconst { url, format, includeMeta, scrollFullPage } = input\n\t\t\ttry {\n\t\t\t\t// Determine if format is snippets\n\t\t\t\tconst isSnippets = typeof format === 'object' && 'query' in format\n\n\t\t\t\tconst response = await client.fetch({\n\t\t\t\t\turl,\n\t\t\t\t\tselect: {\n\t\t\t\t\t\tmarkdown: format === 'markdown',\n\t\t\t\t\t\thtml: format === 'html',\n\t\t\t\t\t\tsummary: format === 'summary',\n\t\t\t\t\t\tsnippets: isSnippets ? format : undefined,\n\t\t\t\t\t\tmeta: includeMeta,\n\t\t\t\t\t},\n\t\t\t\t\t...(scrollFullPage && { browserConfig: { scrollFullPage: true } }),\n\t\t\t\t})\n\n\t\t\t\t// Build content based on format\n\t\t\t\tconst data = response.data as unknown as Record<string, unknown>\n\t\t\t\tlet content: string\n\n\t\t\t\tif (format === 'markdown' && data.markdown) {\n\t\t\t\t\tcontent = data.markdown as string\n\t\t\t\t} else if (format === 'html' && data.html) {\n\t\t\t\t\tcontent = data.html as string\n\t\t\t\t} else if (format === 'summary' && data.summary) {\n\t\t\t\t\tcontent = data.summary as string\n\t\t\t\t} else if (isSnippets && data.snippets) {\n\t\t\t\t\tconst snippets = data.snippets as Array<{\n\t\t\t\t\t\ttext: string\n\t\t\t\t\t\tscore: number\n\t\t\t\t\t}>\n\t\t\t\t\tcontent = snippets\n\t\t\t\t\t\t.map((s) => `${s.text} (Score: ${s.score})`)\n\t\t\t\t\t\t.join('\\n')\n\t\t\t\t} else {\n\t\t\t\t\tcontent = ''\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent,\n\t\t\t\t\turl: response.data.response.url,\n\t\t\t\t\t...(includeMeta && data.meta\n\t\t\t\t\t\t? { meta: data.meta as FetchResult['meta'] }\n\t\t\t\t\t\t: {}),\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Transform SDK errors into LLM-friendly messages\n\t\t\t\tif (error instanceof Expand.APIError) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch ${url}: ${error.message} (status: ${error.status})`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to fetch ${url}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t})\n}\n\n/**\n * Default Expand fetch tool instance.\n * Uses EXPAND_API_KEY environment variable for authentication.\n * Created lazily to avoid errors when importing the module without an API key.\n *\n * @example\n * ```typescript\n * import { expandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandFetchTool },\n * prompt: 'Summarize https://expand.ai',\n * })\n * ```\n */\nlet _expandFetchTool: ReturnType<typeof createExpandFetchTool> | undefined\n\nexport const expandFetchTool = new Proxy(\n\t{} as ReturnType<typeof createExpandFetchTool>,\n\t{\n\t\tget(_target, prop) {\n\t\t\tif (!_expandFetchTool) {\n\t\t\t\t_expandFetchTool = createExpandFetchTool()\n\t\t\t}\n\t\t\treturn (_expandFetchTool as Record<string | symbol, unknown>)[prop]\n\t\t},\n\t},\n)\n"]} | ||
| {"version":3,"sources":["../src/tool.ts"],"names":["z","Expand","tool"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ClientOptions } from '@expandai/sdk'\nimport Expand from '@expandai/sdk'\nimport { tool } from 'ai'\nimport { z } from 'zod'\n\n// Re-export ClientOptions for convenience\nexport type { ClientOptions }\n\n/**\n * Configuration options for the Expand fetch tool.\n */\nexport interface ExpandFetchToolOptions extends ClientOptions {\n\t/** Custom description for the tool (overrides default) */\n\tdescription?: string\n}\n\n// Zod schema for search params - matches MCP server\nconst searchParamsSchema = z.object({\n\tquery: z.string().describe('Query to find relevant content snippets'),\n\tminScore: z\n\t\t.number()\n\t\t.min(0)\n\t\t.max(1)\n\t\t.default(0.6)\n\t\t.describe('Minimum relevance score (0-1)'),\n\tmaxResults: z\n\t\t.number()\n\t\t.int()\n\t\t.min(1)\n\t\t.max(50)\n\t\t.default(5)\n\t\t.describe('Maximum snippets to return'),\n})\n\nconst inputSchema = z.object({\n\turl: z.string().url().describe('The URL to fetch content from'),\n\tincludeMeta: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe('Include page meta tags (title, description, Open Graph)'),\n\tsearch: searchParamsSchema\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Semantic search. When provided, returns snippets instead of markdown.',\n\t\t),\n})\n\n/**\n * Create an Expand fetch tool for the Vercel AI SDK.\n *\n * The tool mirrors the MCP server's fetch tool, providing consistent behavior\n * across all Expand integrations.\n *\n * @param options - Configuration options (SDK client options + tool-specific options)\n * @returns A Vercel AI SDK tool that fetches and extracts web content\n *\n * @example\n * ```typescript\n * import { createExpandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n * import { openai } from '@ai-sdk/openai'\n *\n * const expandTool = createExpandFetchTool({\n * apiKey: process.env.EXPAND_API_KEY,\n * })\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandTool },\n * prompt: 'What is on the Expand homepage?',\n * })\n * ```\n */\nexport function createExpandFetchTool(options?: ExpandFetchToolOptions) {\n\tconst apiKey = options?.apiKey ?? process.env.EXPAND_API_KEY\n\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t'Expand API key is required. Provide it via options.apiKey or set EXPAND_API_KEY environment variable.',\n\t\t)\n\t}\n\n\t// Extract tool-specific options, pass rest to SDK client\n\tconst { description, ...clientOptions } = options ?? {}\n\n\t// Create Expand client with all client options\n\tconst client = new Expand({\n\t\t...clientOptions,\n\t\tapiKey,\n\t})\n\n\treturn tool({\n\t\tdescription:\n\t\t\tdescription ??\n\t\t\t'Fetch and extract content from any URL.\\n\\nReturns markdown by default. When `search` is provided, returns semantically relevant snippets instead.',\n\t\tinputSchema,\n\t\texecute: async (input) => {\n\t\t\tconst { url, includeMeta, search } = input\n\t\t\ttry {\n\t\t\t\tconst hasSearch = search !== undefined\n\t\t\t\tconst response = await client.fetch({\n\t\t\t\t\turl,\n\t\t\t\t\tselect: {\n\t\t\t\t\t\tmarkdown: !hasSearch,\n\t\t\t\t\t\tsnippets: hasSearch\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tquery: search.query,\n\t\t\t\t\t\t\t\t\tmaxSnippets: search.maxResults,\n\t\t\t\t\t\t\t\t\tminScore: search.minScore,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tmeta: includeMeta,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn response.data\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Expand.APIError) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch ${url}: ${error.message} (status: ${error.status})`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to fetch ${url}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t})\n}\n\n/**\n * Default Expand fetch tool instance.\n * Uses EXPAND_API_KEY environment variable for authentication.\n * Created lazily to avoid errors when importing the module without an API key.\n *\n * @example\n * ```typescript\n * import { expandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandFetchTool },\n * prompt: 'Summarize https://expand.ai',\n * })\n * ```\n */\nlet _expandFetchTool: ReturnType<typeof createExpandFetchTool> | undefined\n\nexport const expandFetchTool = new Proxy(\n\t{} as ReturnType<typeof createExpandFetchTool>,\n\t{\n\t\tget(_target, prop) {\n\t\t\tif (!_expandFetchTool) {\n\t\t\t\t_expandFetchTool = createExpandFetchTool()\n\t\t\t}\n\t\t\treturn (_expandFetchTool as Record<string | symbol, unknown>)[prop]\n\t\t},\n\t},\n)\n"]} |
+14
-76
| import * as ai from 'ai'; | ||
| import { ClientOptions } from '@expandai/sdk'; | ||
| import Expand, { ClientOptions } from '@expandai/sdk'; | ||
| export { ClientOptions } from '@expandai/sdk'; | ||
| /** | ||
| * Snippets format configuration for semantic search within page content. | ||
| */ | ||
| interface SnippetsFormat { | ||
| /** Query to find relevant content snippets from the page */ | ||
| query: string; | ||
| /** Maximum number of snippets to return (1-50, default: 5) */ | ||
| maxSnippets?: number; | ||
| /** Target snippet size in characters (100-2000, default: 384) */ | ||
| targetSnippetSize?: number; | ||
| } | ||
| /** | ||
| * Output format for the fetched content. | ||
| * - 'markdown': Return as cleaned markdown content | ||
| * - 'html': Return as raw HTML content | ||
| * - 'summary': Return AI-generated summary of the page content | ||
| * - SnippetsFormat: Return relevant content snippets matching a query | ||
| */ | ||
| type Format = 'markdown' | 'html' | 'summary' | SnippetsFormat; | ||
| /** | ||
| * Default configuration for the fetch tool. | ||
| * These values are used when the LLM doesn't specify them. | ||
| */ | ||
| interface FetchToolDefaults { | ||
| /** Default output format (default: 'markdown') */ | ||
| format?: Format; | ||
| /** Whether to include page meta tags by default (default: true) */ | ||
| includeMeta?: boolean; | ||
| /** Whether to scroll the full page to capture lazy-loaded content (default: false) */ | ||
| scrollFullPage?: boolean; | ||
| } | ||
| /** | ||
| * Configuration options for the Expand fetch tool. | ||
@@ -42,23 +11,4 @@ */ | ||
| description?: string; | ||
| /** Default values for tool parameters */ | ||
| defaults?: FetchToolDefaults; | ||
| } | ||
| /** | ||
| * Result returned by the fetch tool. | ||
| */ | ||
| interface FetchResult { | ||
| /** The fetched content in the requested format */ | ||
| content: string; | ||
| /** The final URL after any redirects */ | ||
| url: string; | ||
| /** Page metadata (title, description, etc.) if includeMeta was true */ | ||
| meta?: { | ||
| title?: string | null; | ||
| description?: string | null; | ||
| canonicalUrl?: string | null; | ||
| language?: string | null; | ||
| charset?: string | null; | ||
| }; | ||
| } | ||
| /** | ||
| * Create an Expand fetch tool for the Vercel AI SDK. | ||
@@ -78,3 +28,2 @@ * | ||
| * | ||
| * // With default markdown format | ||
| * const expandTool = createExpandFetchTool({ | ||
@@ -84,11 +33,2 @@ * apiKey: process.env.EXPAND_API_KEY, | ||
| * | ||
| * // With custom defaults | ||
| * const expandTool = createExpandFetchTool({ | ||
| * apiKey: process.env.EXPAND_API_KEY, | ||
| * defaults: { | ||
| * format: 'summary', | ||
| * includeMeta: false, | ||
| * }, | ||
| * }) | ||
| * | ||
| * const result = await generateText({ | ||
@@ -103,21 +43,19 @@ * model: openai('gpt-4'), | ||
| url: string; | ||
| format: "markdown" | "html" | "summary" | { | ||
| includeMeta: boolean; | ||
| search?: { | ||
| query: string; | ||
| maxSnippets?: number | undefined; | ||
| targetSnippetSize?: number | undefined; | ||
| }; | ||
| includeMeta: boolean; | ||
| scrollFullPage: boolean; | ||
| }, FetchResult>; | ||
| minScore: number; | ||
| maxResults: number; | ||
| } | undefined; | ||
| }, Expand.FetchResponse.Data>; | ||
| declare const expandFetchTool: ai.Tool<{ | ||
| url: string; | ||
| format: "markdown" | "html" | "summary" | { | ||
| includeMeta: boolean; | ||
| search?: { | ||
| query: string; | ||
| maxSnippets?: number | undefined; | ||
| targetSnippetSize?: number | undefined; | ||
| }; | ||
| includeMeta: boolean; | ||
| scrollFullPage: boolean; | ||
| }, FetchResult>; | ||
| minScore: number; | ||
| maxResults: number; | ||
| } | undefined; | ||
| }, Expand.FetchResponse.Data>; | ||
| export { type ExpandFetchToolOptions, type FetchResult, type FetchToolDefaults, type Format, type SnippetsFormat, createExpandFetchTool, expandFetchTool }; | ||
| export { type ExpandFetchToolOptions, createExpandFetchTool, expandFetchTool }; |
+14
-76
| import * as ai from 'ai'; | ||
| import { ClientOptions } from '@expandai/sdk'; | ||
| import Expand, { ClientOptions } from '@expandai/sdk'; | ||
| export { ClientOptions } from '@expandai/sdk'; | ||
| /** | ||
| * Snippets format configuration for semantic search within page content. | ||
| */ | ||
| interface SnippetsFormat { | ||
| /** Query to find relevant content snippets from the page */ | ||
| query: string; | ||
| /** Maximum number of snippets to return (1-50, default: 5) */ | ||
| maxSnippets?: number; | ||
| /** Target snippet size in characters (100-2000, default: 384) */ | ||
| targetSnippetSize?: number; | ||
| } | ||
| /** | ||
| * Output format for the fetched content. | ||
| * - 'markdown': Return as cleaned markdown content | ||
| * - 'html': Return as raw HTML content | ||
| * - 'summary': Return AI-generated summary of the page content | ||
| * - SnippetsFormat: Return relevant content snippets matching a query | ||
| */ | ||
| type Format = 'markdown' | 'html' | 'summary' | SnippetsFormat; | ||
| /** | ||
| * Default configuration for the fetch tool. | ||
| * These values are used when the LLM doesn't specify them. | ||
| */ | ||
| interface FetchToolDefaults { | ||
| /** Default output format (default: 'markdown') */ | ||
| format?: Format; | ||
| /** Whether to include page meta tags by default (default: true) */ | ||
| includeMeta?: boolean; | ||
| /** Whether to scroll the full page to capture lazy-loaded content (default: false) */ | ||
| scrollFullPage?: boolean; | ||
| } | ||
| /** | ||
| * Configuration options for the Expand fetch tool. | ||
@@ -42,23 +11,4 @@ */ | ||
| description?: string; | ||
| /** Default values for tool parameters */ | ||
| defaults?: FetchToolDefaults; | ||
| } | ||
| /** | ||
| * Result returned by the fetch tool. | ||
| */ | ||
| interface FetchResult { | ||
| /** The fetched content in the requested format */ | ||
| content: string; | ||
| /** The final URL after any redirects */ | ||
| url: string; | ||
| /** Page metadata (title, description, etc.) if includeMeta was true */ | ||
| meta?: { | ||
| title?: string | null; | ||
| description?: string | null; | ||
| canonicalUrl?: string | null; | ||
| language?: string | null; | ||
| charset?: string | null; | ||
| }; | ||
| } | ||
| /** | ||
| * Create an Expand fetch tool for the Vercel AI SDK. | ||
@@ -78,3 +28,2 @@ * | ||
| * | ||
| * // With default markdown format | ||
| * const expandTool = createExpandFetchTool({ | ||
@@ -84,11 +33,2 @@ * apiKey: process.env.EXPAND_API_KEY, | ||
| * | ||
| * // With custom defaults | ||
| * const expandTool = createExpandFetchTool({ | ||
| * apiKey: process.env.EXPAND_API_KEY, | ||
| * defaults: { | ||
| * format: 'summary', | ||
| * includeMeta: false, | ||
| * }, | ||
| * }) | ||
| * | ||
| * const result = await generateText({ | ||
@@ -103,21 +43,19 @@ * model: openai('gpt-4'), | ||
| url: string; | ||
| format: "markdown" | "html" | "summary" | { | ||
| includeMeta: boolean; | ||
| search?: { | ||
| query: string; | ||
| maxSnippets?: number | undefined; | ||
| targetSnippetSize?: number | undefined; | ||
| }; | ||
| includeMeta: boolean; | ||
| scrollFullPage: boolean; | ||
| }, FetchResult>; | ||
| minScore: number; | ||
| maxResults: number; | ||
| } | undefined; | ||
| }, Expand.FetchResponse.Data>; | ||
| declare const expandFetchTool: ai.Tool<{ | ||
| url: string; | ||
| format: "markdown" | "html" | "summary" | { | ||
| includeMeta: boolean; | ||
| search?: { | ||
| query: string; | ||
| maxSnippets?: number | undefined; | ||
| targetSnippetSize?: number | undefined; | ||
| }; | ||
| includeMeta: boolean; | ||
| scrollFullPage: boolean; | ||
| }, FetchResult>; | ||
| minScore: number; | ||
| maxResults: number; | ||
| } | undefined; | ||
| }, Expand.FetchResponse.Data>; | ||
| export { type ExpandFetchToolOptions, type FetchResult, type FetchToolDefaults, type Format, type SnippetsFormat, createExpandFetchTool, expandFetchTool }; | ||
| export { type ExpandFetchToolOptions, createExpandFetchTool, expandFetchTool }; |
+23
-50
@@ -6,13 +6,14 @@ import Expand from '@expandai/sdk'; | ||
| // src/tool.ts | ||
| var snippetsFormatSchema = z.object({ | ||
| query: z.string().describe("Query to find relevant content snippets from the page"), | ||
| maxSnippets: z.number().int().min(1).max(50).optional().describe("Maximum number of snippets to return (1-50, default: 5)"), | ||
| targetSnippetSize: z.number().int().min(100).max(2e3).optional().describe("Target snippet size in characters (100-2000, default: 384)") | ||
| var searchParamsSchema = z.object({ | ||
| query: z.string().describe("Query to find relevant content snippets"), | ||
| minScore: z.number().min(0).max(1).default(0.6).describe("Minimum relevance score (0-1)"), | ||
| maxResults: z.number().int().min(1).max(50).default(5).describe("Maximum snippets to return") | ||
| }); | ||
| var formatSchema = z.union([ | ||
| z.literal("markdown").describe("Return as cleaned markdown content"), | ||
| z.literal("html").describe("Return as raw HTML content"), | ||
| z.literal("summary").describe("Return AI-generated summary of the page content"), | ||
| snippetsFormatSchema | ||
| ]).describe("Output format for the fetched content"); | ||
| var inputSchema = z.object({ | ||
| url: z.string().url().describe("The URL to fetch content from"), | ||
| includeMeta: z.boolean().default(false).describe("Include page meta tags (title, description, Open Graph)"), | ||
| search: searchParamsSchema.optional().describe( | ||
| "Semantic search. When provided, returns snippets instead of markdown." | ||
| ) | ||
| }); | ||
| function createExpandFetchTool(options) { | ||
@@ -25,3 +26,3 @@ const apiKey = options?.apiKey ?? process.env.EXPAND_API_KEY; | ||
| } | ||
| const { description, defaults, ...clientOptions } = options ?? {}; | ||
| const { description, ...clientOptions } = options ?? {}; | ||
| const client = new Expand({ | ||
@@ -31,50 +32,22 @@ ...clientOptions, | ||
| }); | ||
| const defaultFormat = defaults?.format ?? "markdown"; | ||
| const defaultIncludeMeta = defaults?.includeMeta ?? true; | ||
| const defaultScrollFullPage = defaults?.scrollFullPage ?? false; | ||
| const inputSchema = z.object({ | ||
| url: z.string().url().describe("The URL to fetch content from"), | ||
| format: formatSchema.default(defaultFormat), | ||
| includeMeta: z.boolean().default(defaultIncludeMeta).describe("Whether to include page meta tags in the response"), | ||
| scrollFullPage: z.boolean().default(defaultScrollFullPage).describe( | ||
| "Whether to scroll the full page to capture lazy-loaded content. Set to true for pages with infinite scroll or dynamic content loading." | ||
| ) | ||
| }); | ||
| return tool({ | ||
| description: description ?? "Fetch and extract content from any URL.", | ||
| description: description ?? "Fetch and extract content from any URL.\n\nReturns markdown by default. When `search` is provided, returns semantically relevant snippets instead.", | ||
| inputSchema, | ||
| execute: async (input) => { | ||
| const { url, format, includeMeta, scrollFullPage } = input; | ||
| const { url, includeMeta, search } = input; | ||
| try { | ||
| const isSnippets = typeof format === "object" && "query" in format; | ||
| const hasSearch = search !== void 0; | ||
| const response = await client.fetch({ | ||
| url, | ||
| select: { | ||
| markdown: format === "markdown", | ||
| html: format === "html", | ||
| summary: format === "summary", | ||
| snippets: isSnippets ? format : void 0, | ||
| markdown: !hasSearch, | ||
| snippets: hasSearch ? { | ||
| query: search.query, | ||
| maxSnippets: search.maxResults, | ||
| minScore: search.minScore | ||
| } : void 0, | ||
| meta: includeMeta | ||
| }, | ||
| ...scrollFullPage && { browserConfig: { scrollFullPage: true } } | ||
| } | ||
| }); | ||
| const data = response.data; | ||
| let content; | ||
| if (format === "markdown" && data.markdown) { | ||
| content = data.markdown; | ||
| } else if (format === "html" && data.html) { | ||
| content = data.html; | ||
| } else if (format === "summary" && data.summary) { | ||
| content = data.summary; | ||
| } else if (isSnippets && data.snippets) { | ||
| const snippets = data.snippets; | ||
| content = snippets.map((s) => `${s.text} (Score: ${s.score})`).join("\n"); | ||
| } else { | ||
| content = ""; | ||
| } | ||
| return { | ||
| content, | ||
| url: response.data.response.url, | ||
| ...includeMeta && data.meta ? { meta: data.meta } : {} | ||
| }; | ||
| return response.data; | ||
| } catch (error) { | ||
@@ -81,0 +54,0 @@ if (error instanceof Expand.APIError) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/tool.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ClientOptions } from '@expandai/sdk'\nimport Expand from '@expandai/sdk'\nimport { tool } from 'ai'\nimport { z } from 'zod'\n\n// Re-export ClientOptions for convenience\nexport type { ClientOptions }\n\n/**\n * Snippets format configuration for semantic search within page content.\n */\nexport interface SnippetsFormat {\n\t/** Query to find relevant content snippets from the page */\n\tquery: string\n\t/** Maximum number of snippets to return (1-50, default: 5) */\n\tmaxSnippets?: number\n\t/** Target snippet size in characters (100-2000, default: 384) */\n\ttargetSnippetSize?: number\n}\n\n/**\n * Output format for the fetched content.\n * - 'markdown': Return as cleaned markdown content\n * - 'html': Return as raw HTML content\n * - 'summary': Return AI-generated summary of the page content\n * - SnippetsFormat: Return relevant content snippets matching a query\n */\nexport type Format = 'markdown' | 'html' | 'summary' | SnippetsFormat\n\n/**\n * Default configuration for the fetch tool.\n * These values are used when the LLM doesn't specify them.\n */\nexport interface FetchToolDefaults {\n\t/** Default output format (default: 'markdown') */\n\tformat?: Format\n\t/** Whether to include page meta tags by default (default: true) */\n\tincludeMeta?: boolean\n\t/** Whether to scroll the full page to capture lazy-loaded content (default: false) */\n\tscrollFullPage?: boolean\n}\n\n/**\n * Configuration options for the Expand fetch tool.\n */\nexport interface ExpandFetchToolOptions extends ClientOptions {\n\t/** Custom description for the tool (overrides default) */\n\tdescription?: string\n\t/** Default values for tool parameters */\n\tdefaults?: FetchToolDefaults\n}\n\n/**\n * Result returned by the fetch tool.\n */\nexport interface FetchResult {\n\t/** The fetched content in the requested format */\n\tcontent: string\n\t/** The final URL after any redirects */\n\turl: string\n\t/** Page metadata (title, description, etc.) if includeMeta was true */\n\tmeta?: {\n\t\ttitle?: string | null\n\t\tdescription?: string | null\n\t\tcanonicalUrl?: string | null\n\t\tlanguage?: string | null\n\t\tcharset?: string | null\n\t}\n}\n\n// Zod schema for snippets format - matches MCP server\nconst snippetsFormatSchema = z.object({\n\tquery: z\n\t\t.string()\n\t\t.describe('Query to find relevant content snippets from the page'),\n\tmaxSnippets: z\n\t\t.number()\n\t\t.int()\n\t\t.min(1)\n\t\t.max(50)\n\t\t.optional()\n\t\t.describe('Maximum number of snippets to return (1-50, default: 5)'),\n\ttargetSnippetSize: z\n\t\t.number()\n\t\t.int()\n\t\t.min(100)\n\t\t.max(2000)\n\t\t.optional()\n\t\t.describe('Target snippet size in characters (100-2000, default: 384)'),\n})\n\n// Zod schema for format parameter - mirrors MCP server exactly\nconst formatSchema = z\n\t.union([\n\t\tz.literal('markdown').describe('Return as cleaned markdown content'),\n\t\tz.literal('html').describe('Return as raw HTML content'),\n\t\tz\n\t\t\t.literal('summary')\n\t\t\t.describe('Return AI-generated summary of the page content'),\n\t\tsnippetsFormatSchema,\n\t])\n\t.describe('Output format for the fetched content')\n\n/**\n * Create an Expand fetch tool for the Vercel AI SDK.\n *\n * The tool mirrors the MCP server's fetch tool, providing consistent behavior\n * across all Expand integrations.\n *\n * @param options - Configuration options (SDK client options + tool-specific options)\n * @returns A Vercel AI SDK tool that fetches and extracts web content\n *\n * @example\n * ```typescript\n * import { createExpandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n * import { openai } from '@ai-sdk/openai'\n *\n * // With default markdown format\n * const expandTool = createExpandFetchTool({\n * apiKey: process.env.EXPAND_API_KEY,\n * })\n *\n * // With custom defaults\n * const expandTool = createExpandFetchTool({\n * apiKey: process.env.EXPAND_API_KEY,\n * defaults: {\n * format: 'summary',\n * includeMeta: false,\n * },\n * })\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandTool },\n * prompt: 'What is on the Expand homepage?',\n * })\n * ```\n */\nexport function createExpandFetchTool(options?: ExpandFetchToolOptions) {\n\tconst apiKey = options?.apiKey ?? process.env.EXPAND_API_KEY\n\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t'Expand API key is required. Provide it via options.apiKey or set EXPAND_API_KEY environment variable.',\n\t\t)\n\t}\n\n\t// Extract tool-specific options, pass rest to SDK client\n\tconst { description, defaults, ...clientOptions } = options ?? {}\n\n\t// Create Expand client with all client options\n\tconst client = new Expand({\n\t\t...clientOptions,\n\t\tapiKey,\n\t})\n\n\t// Apply defaults\n\tconst defaultFormat: Format = defaults?.format ?? 'markdown'\n\tconst defaultIncludeMeta = defaults?.includeMeta ?? true\n\tconst defaultScrollFullPage = defaults?.scrollFullPage ?? false\n\n\tconst inputSchema = z.object({\n\t\turl: z.string().url().describe('The URL to fetch content from'),\n\t\tformat: formatSchema.default(defaultFormat),\n\t\tincludeMeta: z\n\t\t\t.boolean()\n\t\t\t.default(defaultIncludeMeta)\n\t\t\t.describe('Whether to include page meta tags in the response'),\n\t\tscrollFullPage: z\n\t\t\t.boolean()\n\t\t\t.default(defaultScrollFullPage)\n\t\t\t.describe(\n\t\t\t\t'Whether to scroll the full page to capture lazy-loaded content. Set to true for pages with infinite scroll or dynamic content loading.',\n\t\t\t),\n\t})\n\n\ttype ToolInput = z.infer<typeof inputSchema>\n\n\treturn tool({\n\t\tdescription: description ?? 'Fetch and extract content from any URL.',\n\t\tinputSchema,\n\t\texecute: async (input: ToolInput): Promise<FetchResult> => {\n\t\t\tconst { url, format, includeMeta, scrollFullPage } = input\n\t\t\ttry {\n\t\t\t\t// Determine if format is snippets\n\t\t\t\tconst isSnippets = typeof format === 'object' && 'query' in format\n\n\t\t\t\tconst response = await client.fetch({\n\t\t\t\t\turl,\n\t\t\t\t\tselect: {\n\t\t\t\t\t\tmarkdown: format === 'markdown',\n\t\t\t\t\t\thtml: format === 'html',\n\t\t\t\t\t\tsummary: format === 'summary',\n\t\t\t\t\t\tsnippets: isSnippets ? format : undefined,\n\t\t\t\t\t\tmeta: includeMeta,\n\t\t\t\t\t},\n\t\t\t\t\t...(scrollFullPage && { browserConfig: { scrollFullPage: true } }),\n\t\t\t\t})\n\n\t\t\t\t// Build content based on format\n\t\t\t\tconst data = response.data as unknown as Record<string, unknown>\n\t\t\t\tlet content: string\n\n\t\t\t\tif (format === 'markdown' && data.markdown) {\n\t\t\t\t\tcontent = data.markdown as string\n\t\t\t\t} else if (format === 'html' && data.html) {\n\t\t\t\t\tcontent = data.html as string\n\t\t\t\t} else if (format === 'summary' && data.summary) {\n\t\t\t\t\tcontent = data.summary as string\n\t\t\t\t} else if (isSnippets && data.snippets) {\n\t\t\t\t\tconst snippets = data.snippets as Array<{\n\t\t\t\t\t\ttext: string\n\t\t\t\t\t\tscore: number\n\t\t\t\t\t}>\n\t\t\t\t\tcontent = snippets\n\t\t\t\t\t\t.map((s) => `${s.text} (Score: ${s.score})`)\n\t\t\t\t\t\t.join('\\n')\n\t\t\t\t} else {\n\t\t\t\t\tcontent = ''\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent,\n\t\t\t\t\turl: response.data.response.url,\n\t\t\t\t\t...(includeMeta && data.meta\n\t\t\t\t\t\t? { meta: data.meta as FetchResult['meta'] }\n\t\t\t\t\t\t: {}),\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Transform SDK errors into LLM-friendly messages\n\t\t\t\tif (error instanceof Expand.APIError) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch ${url}: ${error.message} (status: ${error.status})`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to fetch ${url}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t})\n}\n\n/**\n * Default Expand fetch tool instance.\n * Uses EXPAND_API_KEY environment variable for authentication.\n * Created lazily to avoid errors when importing the module without an API key.\n *\n * @example\n * ```typescript\n * import { expandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandFetchTool },\n * prompt: 'Summarize https://expand.ai',\n * })\n * ```\n */\nlet _expandFetchTool: ReturnType<typeof createExpandFetchTool> | undefined\n\nexport const expandFetchTool = new Proxy(\n\t{} as ReturnType<typeof createExpandFetchTool>,\n\t{\n\t\tget(_target, prop) {\n\t\t\tif (!_expandFetchTool) {\n\t\t\t\t_expandFetchTool = createExpandFetchTool()\n\t\t\t}\n\t\t\treturn (_expandFetchTool as Record<string | symbol, unknown>)[prop]\n\t\t},\n\t},\n)\n"]} | ||
| {"version":3,"sources":["../src/tool.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ClientOptions } from '@expandai/sdk'\nimport Expand from '@expandai/sdk'\nimport { tool } from 'ai'\nimport { z } from 'zod'\n\n// Re-export ClientOptions for convenience\nexport type { ClientOptions }\n\n/**\n * Configuration options for the Expand fetch tool.\n */\nexport interface ExpandFetchToolOptions extends ClientOptions {\n\t/** Custom description for the tool (overrides default) */\n\tdescription?: string\n}\n\n// Zod schema for search params - matches MCP server\nconst searchParamsSchema = z.object({\n\tquery: z.string().describe('Query to find relevant content snippets'),\n\tminScore: z\n\t\t.number()\n\t\t.min(0)\n\t\t.max(1)\n\t\t.default(0.6)\n\t\t.describe('Minimum relevance score (0-1)'),\n\tmaxResults: z\n\t\t.number()\n\t\t.int()\n\t\t.min(1)\n\t\t.max(50)\n\t\t.default(5)\n\t\t.describe('Maximum snippets to return'),\n})\n\nconst inputSchema = z.object({\n\turl: z.string().url().describe('The URL to fetch content from'),\n\tincludeMeta: z\n\t\t.boolean()\n\t\t.default(false)\n\t\t.describe('Include page meta tags (title, description, Open Graph)'),\n\tsearch: searchParamsSchema\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Semantic search. When provided, returns snippets instead of markdown.',\n\t\t),\n})\n\n/**\n * Create an Expand fetch tool for the Vercel AI SDK.\n *\n * The tool mirrors the MCP server's fetch tool, providing consistent behavior\n * across all Expand integrations.\n *\n * @param options - Configuration options (SDK client options + tool-specific options)\n * @returns A Vercel AI SDK tool that fetches and extracts web content\n *\n * @example\n * ```typescript\n * import { createExpandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n * import { openai } from '@ai-sdk/openai'\n *\n * const expandTool = createExpandFetchTool({\n * apiKey: process.env.EXPAND_API_KEY,\n * })\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandTool },\n * prompt: 'What is on the Expand homepage?',\n * })\n * ```\n */\nexport function createExpandFetchTool(options?: ExpandFetchToolOptions) {\n\tconst apiKey = options?.apiKey ?? process.env.EXPAND_API_KEY\n\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t'Expand API key is required. Provide it via options.apiKey or set EXPAND_API_KEY environment variable.',\n\t\t)\n\t}\n\n\t// Extract tool-specific options, pass rest to SDK client\n\tconst { description, ...clientOptions } = options ?? {}\n\n\t// Create Expand client with all client options\n\tconst client = new Expand({\n\t\t...clientOptions,\n\t\tapiKey,\n\t})\n\n\treturn tool({\n\t\tdescription:\n\t\t\tdescription ??\n\t\t\t'Fetch and extract content from any URL.\\n\\nReturns markdown by default. When `search` is provided, returns semantically relevant snippets instead.',\n\t\tinputSchema,\n\t\texecute: async (input) => {\n\t\t\tconst { url, includeMeta, search } = input\n\t\t\ttry {\n\t\t\t\tconst hasSearch = search !== undefined\n\t\t\t\tconst response = await client.fetch({\n\t\t\t\t\turl,\n\t\t\t\t\tselect: {\n\t\t\t\t\t\tmarkdown: !hasSearch,\n\t\t\t\t\t\tsnippets: hasSearch\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tquery: search.query,\n\t\t\t\t\t\t\t\t\tmaxSnippets: search.maxResults,\n\t\t\t\t\t\t\t\t\tminScore: search.minScore,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tmeta: includeMeta,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\treturn response.data\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Expand.APIError) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to fetch ${url}: ${error.message} (status: ${error.status})`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to fetch ${url}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t})\n}\n\n/**\n * Default Expand fetch tool instance.\n * Uses EXPAND_API_KEY environment variable for authentication.\n * Created lazily to avoid errors when importing the module without an API key.\n *\n * @example\n * ```typescript\n * import { expandFetchTool } from '@expandai/ai'\n * import { generateText } from 'ai'\n *\n * const result = await generateText({\n * model: openai('gpt-4'),\n * tools: { fetch: expandFetchTool },\n * prompt: 'Summarize https://expand.ai',\n * })\n * ```\n */\nlet _expandFetchTool: ReturnType<typeof createExpandFetchTool> | undefined\n\nexport const expandFetchTool = new Proxy(\n\t{} as ReturnType<typeof createExpandFetchTool>,\n\t{\n\t\tget(_target, prop) {\n\t\t\tif (!_expandFetchTool) {\n\t\t\t\t_expandFetchTool = createExpandFetchTool()\n\t\t\t}\n\t\t\treturn (_expandFetchTool as Record<string | symbol, unknown>)[prop]\n\t\t},\n\t},\n)\n"]} |
+8
-8
| { | ||
| "name": "@expandai/ai", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "Vercel AI SDK integration for expand.ai - fetch and extract content from any URL", | ||
@@ -59,14 +59,14 @@ "type": "module", | ||
| "dependencies": { | ||
| "@expandai/sdk": "^0.5.1" | ||
| "@expandai/sdk": "^0.16.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@biomejs/biome": "^2.1.1", | ||
| "@biomejs/biome": "^2.4.4", | ||
| "@changesets/changelog-github": "^0.5.2", | ||
| "@changesets/cli": "^2.29.8", | ||
| "@types/node": "^22.10.5", | ||
| "ai": "^6.0.0", | ||
| "@types/node": "^25.3.0", | ||
| "ai": "^6.0.97", | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^5.7.3", | ||
| "vitest": "^2.1.8", | ||
| "zod": "^3.25.76" | ||
| "typescript": "^5.9.3", | ||
| "vitest": "^4.0.18", | ||
| "zod": "^4.3.6" | ||
| }, | ||
@@ -73,0 +73,0 @@ "scripts": { |
+0
-4
@@ -29,6 +29,2 @@ /** | ||
| expandFetchTool, | ||
| type FetchResult, | ||
| type FetchToolDefaults, | ||
| type Format, | ||
| type SnippetsFormat, | ||
| } from './tool.js' |
+40
-155
@@ -10,36 +10,2 @@ import type { ClientOptions } from '@expandai/sdk' | ||
| /** | ||
| * Snippets format configuration for semantic search within page content. | ||
| */ | ||
| export interface SnippetsFormat { | ||
| /** Query to find relevant content snippets from the page */ | ||
| query: string | ||
| /** Maximum number of snippets to return (1-50, default: 5) */ | ||
| maxSnippets?: number | ||
| /** Target snippet size in characters (100-2000, default: 384) */ | ||
| targetSnippetSize?: number | ||
| } | ||
| /** | ||
| * Output format for the fetched content. | ||
| * - 'markdown': Return as cleaned markdown content | ||
| * - 'html': Return as raw HTML content | ||
| * - 'summary': Return AI-generated summary of the page content | ||
| * - SnippetsFormat: Return relevant content snippets matching a query | ||
| */ | ||
| export type Format = 'markdown' | 'html' | 'summary' | SnippetsFormat | ||
| /** | ||
| * Default configuration for the fetch tool. | ||
| * These values are used when the LLM doesn't specify them. | ||
| */ | ||
| export interface FetchToolDefaults { | ||
| /** Default output format (default: 'markdown') */ | ||
| format?: Format | ||
| /** Whether to include page meta tags by default (default: true) */ | ||
| includeMeta?: boolean | ||
| /** Whether to scroll the full page to capture lazy-loaded content (default: false) */ | ||
| scrollFullPage?: boolean | ||
| } | ||
| /** | ||
| * Configuration options for the Expand fetch tool. | ||
@@ -50,57 +16,35 @@ */ | ||
| description?: string | ||
| /** Default values for tool parameters */ | ||
| defaults?: FetchToolDefaults | ||
| } | ||
| /** | ||
| * Result returned by the fetch tool. | ||
| */ | ||
| export interface FetchResult { | ||
| /** The fetched content in the requested format */ | ||
| content: string | ||
| /** The final URL after any redirects */ | ||
| url: string | ||
| /** Page metadata (title, description, etc.) if includeMeta was true */ | ||
| meta?: { | ||
| title?: string | null | ||
| description?: string | null | ||
| canonicalUrl?: string | null | ||
| language?: string | null | ||
| charset?: string | null | ||
| } | ||
| } | ||
| // Zod schema for snippets format - matches MCP server | ||
| const snippetsFormatSchema = z.object({ | ||
| query: z | ||
| .string() | ||
| .describe('Query to find relevant content snippets from the page'), | ||
| maxSnippets: z | ||
| // Zod schema for search params - matches MCP server | ||
| const searchParamsSchema = z.object({ | ||
| query: z.string().describe('Query to find relevant content snippets'), | ||
| minScore: z | ||
| .number() | ||
| .min(0) | ||
| .max(1) | ||
| .default(0.6) | ||
| .describe('Minimum relevance score (0-1)'), | ||
| maxResults: z | ||
| .number() | ||
| .int() | ||
| .min(1) | ||
| .max(50) | ||
| .default(5) | ||
| .describe('Maximum snippets to return'), | ||
| }) | ||
| const inputSchema = z.object({ | ||
| url: z.string().url().describe('The URL to fetch content from'), | ||
| includeMeta: z | ||
| .boolean() | ||
| .default(false) | ||
| .describe('Include page meta tags (title, description, Open Graph)'), | ||
| search: searchParamsSchema | ||
| .optional() | ||
| .describe('Maximum number of snippets to return (1-50, default: 5)'), | ||
| targetSnippetSize: z | ||
| .number() | ||
| .int() | ||
| .min(100) | ||
| .max(2000) | ||
| .optional() | ||
| .describe('Target snippet size in characters (100-2000, default: 384)'), | ||
| .describe( | ||
| 'Semantic search. When provided, returns snippets instead of markdown.', | ||
| ), | ||
| }) | ||
| // Zod schema for format parameter - mirrors MCP server exactly | ||
| const formatSchema = z | ||
| .union([ | ||
| z.literal('markdown').describe('Return as cleaned markdown content'), | ||
| z.literal('html').describe('Return as raw HTML content'), | ||
| z | ||
| .literal('summary') | ||
| .describe('Return AI-generated summary of the page content'), | ||
| snippetsFormatSchema, | ||
| ]) | ||
| .describe('Output format for the fetched content') | ||
| /** | ||
@@ -121,3 +65,2 @@ * Create an Expand fetch tool for the Vercel AI SDK. | ||
| * | ||
| * // With default markdown format | ||
| * const expandTool = createExpandFetchTool({ | ||
@@ -127,11 +70,2 @@ * apiKey: process.env.EXPAND_API_KEY, | ||
| * | ||
| * // With custom defaults | ||
| * const expandTool = createExpandFetchTool({ | ||
| * apiKey: process.env.EXPAND_API_KEY, | ||
| * defaults: { | ||
| * format: 'summary', | ||
| * includeMeta: false, | ||
| * }, | ||
| * }) | ||
| * | ||
| * const result = await generateText({ | ||
@@ -154,3 +88,3 @@ * model: openai('gpt-4'), | ||
| // Extract tool-specific options, pass rest to SDK client | ||
| const { description, defaults, ...clientOptions } = options ?? {} | ||
| const { description, ...clientOptions } = options ?? {} | ||
@@ -163,76 +97,27 @@ // Create Expand client with all client options | ||
| // Apply defaults | ||
| const defaultFormat: Format = defaults?.format ?? 'markdown' | ||
| const defaultIncludeMeta = defaults?.includeMeta ?? true | ||
| const defaultScrollFullPage = defaults?.scrollFullPage ?? false | ||
| const inputSchema = z.object({ | ||
| url: z.string().url().describe('The URL to fetch content from'), | ||
| format: formatSchema.default(defaultFormat), | ||
| includeMeta: z | ||
| .boolean() | ||
| .default(defaultIncludeMeta) | ||
| .describe('Whether to include page meta tags in the response'), | ||
| scrollFullPage: z | ||
| .boolean() | ||
| .default(defaultScrollFullPage) | ||
| .describe( | ||
| 'Whether to scroll the full page to capture lazy-loaded content. Set to true for pages with infinite scroll or dynamic content loading.', | ||
| ), | ||
| }) | ||
| type ToolInput = z.infer<typeof inputSchema> | ||
| return tool({ | ||
| description: description ?? 'Fetch and extract content from any URL.', | ||
| description: | ||
| description ?? | ||
| 'Fetch and extract content from any URL.\n\nReturns markdown by default. When `search` is provided, returns semantically relevant snippets instead.', | ||
| inputSchema, | ||
| execute: async (input: ToolInput): Promise<FetchResult> => { | ||
| const { url, format, includeMeta, scrollFullPage } = input | ||
| execute: async (input) => { | ||
| const { url, includeMeta, search } = input | ||
| try { | ||
| // Determine if format is snippets | ||
| const isSnippets = typeof format === 'object' && 'query' in format | ||
| const hasSearch = search !== undefined | ||
| const response = await client.fetch({ | ||
| url, | ||
| select: { | ||
| markdown: format === 'markdown', | ||
| html: format === 'html', | ||
| summary: format === 'summary', | ||
| snippets: isSnippets ? format : undefined, | ||
| markdown: !hasSearch, | ||
| snippets: hasSearch | ||
| ? { | ||
| query: search.query, | ||
| maxSnippets: search.maxResults, | ||
| minScore: search.minScore, | ||
| } | ||
| : undefined, | ||
| meta: includeMeta, | ||
| }, | ||
| ...(scrollFullPage && { browserConfig: { scrollFullPage: true } }), | ||
| }) | ||
| // Build content based on format | ||
| const data = response.data as unknown as Record<string, unknown> | ||
| let content: string | ||
| if (format === 'markdown' && data.markdown) { | ||
| content = data.markdown as string | ||
| } else if (format === 'html' && data.html) { | ||
| content = data.html as string | ||
| } else if (format === 'summary' && data.summary) { | ||
| content = data.summary as string | ||
| } else if (isSnippets && data.snippets) { | ||
| const snippets = data.snippets as Array<{ | ||
| text: string | ||
| score: number | ||
| }> | ||
| content = snippets | ||
| .map((s) => `${s.text} (Score: ${s.score})`) | ||
| .join('\n') | ||
| } else { | ||
| content = '' | ||
| } | ||
| return { | ||
| content, | ||
| url: response.data.response.url, | ||
| ...(includeMeta && data.meta | ||
| ? { meta: data.meta as FetchResult['meta'] } | ||
| : {}), | ||
| } | ||
| return response.data | ||
| } catch (error) { | ||
| // Transform SDK errors into LLM-friendly messages | ||
| if (error instanceof Expand.APIError) { | ||
@@ -239,0 +124,0 @@ throw new Error( |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
7
-46.15%26
-18.75%44838
-32.69%385
-36.78%+ Added
- Removed
Updated