chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevClassifyNaicsParams,
|
||||
ContextDevClassifyNaicsResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { CLASSIFICATION_CODE_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevClassifyNaicsTool: ToolConfig<
|
||||
ContextDevClassifyNaicsParams,
|
||||
ContextDevClassifyNaicsResponse
|
||||
> = {
|
||||
id: 'context_dev_classify_naics',
|
||||
name: 'Context.dev Classify NAICS',
|
||||
description: 'Classify a brand into NAICS industry codes from its domain or company name.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevClassifyNaicsParams>(),
|
||||
|
||||
params: {
|
||||
input: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Brand domain or company name to classify (e.g., "stripe.com" or "Stripe")',
|
||||
},
|
||||
minResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum number of codes to return (1-10, default: 1)',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of codes to return (1-10, default: 5)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/naics`)
|
||||
appendParam(url.searchParams, 'input', params.input)
|
||||
appendParam(url.searchParams, 'minResults', params.minResults)
|
||||
appendParam(url.searchParams, 'maxResults', params.maxResults)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: data.status ?? '',
|
||||
domain: data.domain ?? null,
|
||||
type: data.type ?? null,
|
||||
codes: data.codes ?? [],
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Classification status' },
|
||||
domain: { type: 'string', description: 'Resolved domain', optional: true },
|
||||
type: { type: 'string', description: 'Input type that was resolved', optional: true },
|
||||
codes: {
|
||||
type: 'array',
|
||||
description: 'Matched NAICS codes with name and confidence',
|
||||
items: { type: 'object', properties: CLASSIFICATION_CODE_OUTPUT_PROPERTIES },
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevClassifySicParams,
|
||||
ContextDevClassifySicResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { CLASSIFICATION_CODE_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevClassifySicTool: ToolConfig<
|
||||
ContextDevClassifySicParams,
|
||||
ContextDevClassifySicResponse
|
||||
> = {
|
||||
id: 'context_dev_classify_sic',
|
||||
name: 'Context.dev Classify SIC',
|
||||
description: 'Classify a brand into SIC industry codes from its domain or company name.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevClassifySicParams>(),
|
||||
|
||||
params: {
|
||||
input: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Brand domain or company name to classify (e.g., "stripe.com" or "Stripe")',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'SIC taxonomy version: "original_sic" (default) or "latest_sec"',
|
||||
},
|
||||
minResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Minimum number of codes to return (1-10, default: 1)',
|
||||
},
|
||||
maxResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of codes to return (1-10, default: 5)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/sic`)
|
||||
appendParam(url.searchParams, 'input', params.input)
|
||||
appendParam(url.searchParams, 'type', params.type)
|
||||
appendParam(url.searchParams, 'minResults', params.minResults)
|
||||
appendParam(url.searchParams, 'maxResults', params.maxResults)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: data.status ?? '',
|
||||
domain: data.domain ?? null,
|
||||
type: data.type ?? null,
|
||||
classification: data.classification ?? null,
|
||||
codes: data.codes ?? [],
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Classification status' },
|
||||
domain: { type: 'string', description: 'Resolved domain', optional: true },
|
||||
type: { type: 'string', description: 'Input type that was resolved', optional: true },
|
||||
classification: {
|
||||
type: 'string',
|
||||
description: 'SIC taxonomy version used (original_sic or latest_sec)',
|
||||
optional: true,
|
||||
},
|
||||
codes: {
|
||||
type: 'array',
|
||||
description: 'Matched SIC codes with name, confidence, and group metadata',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...CLASSIFICATION_CODE_OUTPUT_PROPERTIES,
|
||||
majorGroup: { type: 'string', description: 'Major group code (original_sic only)' },
|
||||
majorGroupName: { type: 'string', description: 'Major group name (original_sic only)' },
|
||||
office: { type: 'string', description: 'SEC office (latest_sec only)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type { ContextDevCrawlParams, ContextDevCrawlResponse } from '@/tools/context_dev/types'
|
||||
import { CRAWL_RESULT_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevJsonHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevCrawlTool: ToolConfig<ContextDevCrawlParams, ContextDevCrawlResponse> = {
|
||||
id: 'context_dev_crawl',
|
||||
name: 'Context.dev Crawl',
|
||||
description: 'Crawl an entire website and return each discovered page as clean markdown.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevCrawlParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The starting URL to crawl (must include http:// or https://)',
|
||||
},
|
||||
maxPages: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of pages to crawl (1-500, default: 100)',
|
||||
},
|
||||
maxDepth: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum link depth from the starting URL (0 = start page only)',
|
||||
},
|
||||
urlRegex: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Regex pattern to filter which URLs are crawled',
|
||||
},
|
||||
includeLinks: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Preserve hyperlinks in the markdown output (default: true)',
|
||||
},
|
||||
includeImages: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include image references in the markdown output (default: false)',
|
||||
},
|
||||
useMainContentOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Strip headers, footers, and sidebars from each page (default: false)',
|
||||
},
|
||||
followSubdomains: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Follow links to subdomains of the starting domain (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 86400000)',
|
||||
},
|
||||
waitForMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Browser wait time after page load in milliseconds (0-30000)',
|
||||
},
|
||||
stopAfterMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Soft crawl time budget in milliseconds (10000-110000, default: 80000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: () => `${CONTEXT_DEV_BASE_URL}/web/crawl`,
|
||||
headers: (params) => contextDevJsonHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { url: params.url }
|
||||
if (params.maxPages != null) body.maxPages = params.maxPages
|
||||
if (params.maxDepth != null) body.maxDepth = params.maxDepth
|
||||
if (params.urlRegex) body.urlRegex = params.urlRegex
|
||||
if (params.includeLinks != null) body.includeLinks = params.includeLinks
|
||||
if (params.includeImages != null) body.includeImages = params.includeImages
|
||||
if (params.useMainContentOnly != null) body.useMainContentOnly = params.useMainContentOnly
|
||||
if (params.followSubdomains != null) body.followSubdomains = params.followSubdomains
|
||||
if (params.maxAgeMs != null) body.maxAgeMs = params.maxAgeMs
|
||||
if (params.waitForMs != null) body.waitForMs = params.waitForMs
|
||||
if (params.stopAfterMs != null) body.stopAfterMs = params.stopAfterMs
|
||||
if (params.timeoutMS != null) body.timeoutMS = params.timeoutMS
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results: data.results ?? [],
|
||||
metadata: data.metadata ?? {},
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Crawled pages with markdown content and per-page metadata',
|
||||
items: { type: 'object', properties: CRAWL_RESULT_OUTPUT_PROPERTIES },
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Crawl summary (numUrls, maxCrawlDepth, numSucceeded, numFailed, numSkipped)',
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type { ContextDevExtractParams, ContextDevExtractResponse } from '@/tools/context_dev/types'
|
||||
import {
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevJsonHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevExtractTool: ToolConfig<ContextDevExtractParams, ContextDevExtractResponse> =
|
||||
{
|
||||
id: 'context_dev_extract',
|
||||
name: 'Context.dev Extract',
|
||||
description: 'Crawl a website and extract structured data matching a provided JSON schema.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevExtractParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The starting website URL (must include http:// or https://)',
|
||||
},
|
||||
schema: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'JSON Schema describing the structure of the data to extract',
|
||||
},
|
||||
instructions: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional extraction guidance for link prioritization (max 2000 chars)',
|
||||
},
|
||||
factCheck: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Require extracted values to be grounded in page facts (default: false)',
|
||||
},
|
||||
followSubdomains: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Follow links on subdomains of the starting domain (default: false)',
|
||||
},
|
||||
maxPages: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of pages to analyze (1-50, default: 5)',
|
||||
},
|
||||
maxDepth: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum link depth from the starting URL',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 604800000)',
|
||||
},
|
||||
stopAfterMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Soft crawl time budget in milliseconds (10000-110000, default: 80000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: () => `${CONTEXT_DEV_BASE_URL}/web/extract`,
|
||||
headers: (params) => contextDevJsonHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { url: params.url, schema: params.schema }
|
||||
if (params.instructions) body.instructions = params.instructions
|
||||
if (params.factCheck != null) body.factCheck = params.factCheck
|
||||
if (params.followSubdomains != null) body.followSubdomains = params.followSubdomains
|
||||
if (params.maxPages != null) body.maxPages = params.maxPages
|
||||
if (params.maxDepth != null) body.maxDepth = params.maxDepth
|
||||
if (params.maxAgeMs != null) body.maxAgeMs = params.maxAgeMs
|
||||
if (params.stopAfterMs != null) body.stopAfterMs = params.stopAfterMs
|
||||
if (params.timeoutMS != null) body.timeoutMS = params.timeoutMS
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: data.status ?? '',
|
||||
url: data.url ?? '',
|
||||
urlsAnalyzed: data.urls_analyzed ?? [],
|
||||
data: data.data ?? {},
|
||||
metadata: data.metadata ?? {},
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Extraction status' },
|
||||
url: { type: 'string', description: 'The starting URL that was crawled' },
|
||||
urlsAnalyzed: {
|
||||
type: 'array',
|
||||
description: 'URLs that were analyzed during extraction',
|
||||
items: { type: 'string', description: 'Analyzed page URL' },
|
||||
},
|
||||
data: { type: 'json', description: 'Structured data matching the requested schema' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Crawl summary (numUrls, maxCrawlDepth, numSucceeded, numFailed, numSkipped)',
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevExtractProductParams,
|
||||
ContextDevExtractProductResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { PRODUCT_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevJsonHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevExtractProductTool: ToolConfig<
|
||||
ContextDevExtractProductParams,
|
||||
ContextDevExtractProductResponse
|
||||
> = {
|
||||
id: 'context_dev_extract_product',
|
||||
name: 'Context.dev Extract Product',
|
||||
description: 'Detect and extract structured product details from a single product page URL.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevExtractProductParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The product page URL (must include http:// or https://)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 604800000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: () => `${CONTEXT_DEV_BASE_URL}/brand/ai/product`,
|
||||
headers: (params) => contextDevJsonHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { url: params.url }
|
||||
if (params.maxAgeMs != null) body.maxAgeMs = params.maxAgeMs
|
||||
if (params.timeoutMS != null) body.timeoutMS = params.timeoutMS
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
isProductPage: data.is_product_page ?? false,
|
||||
platform: data.platform ?? null,
|
||||
product: data.product ?? null,
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
isProductPage: { type: 'boolean', description: 'Whether the URL is a product page' },
|
||||
platform: {
|
||||
type: 'string',
|
||||
description: 'Detected platform (amazon, tiktok_shop, etsy, generic)',
|
||||
optional: true,
|
||||
},
|
||||
product: {
|
||||
type: 'object',
|
||||
description: 'Extracted product details',
|
||||
properties: PRODUCT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevExtractProductsParams,
|
||||
ContextDevExtractProductsResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { PRODUCT_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevJsonHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevExtractProductsTool: ToolConfig<
|
||||
ContextDevExtractProductsParams,
|
||||
ContextDevExtractProductsResponse
|
||||
> = {
|
||||
id: 'context_dev_extract_products',
|
||||
name: 'Context.dev Extract Products',
|
||||
description: "Extract the product catalog from a brand's website by domain (beta).",
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevExtractProductsParams>(),
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The domain to extract products from (e.g., "example.com")',
|
||||
},
|
||||
maxProducts: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of products to extract (1-12)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 604800000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: () => `${CONTEXT_DEV_BASE_URL}/brand/ai/products`,
|
||||
headers: (params) => contextDevJsonHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { domain: params.domain }
|
||||
if (params.maxProducts != null) body.maxProducts = params.maxProducts
|
||||
if (params.maxAgeMs != null) body.maxAgeMs = params.maxAgeMs
|
||||
if (params.timeoutMS != null) body.timeoutMS = params.timeoutMS
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
products: data.products ?? [],
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
products: {
|
||||
type: 'array',
|
||||
description: 'Extracted products with pricing, features, and metadata',
|
||||
items: { type: 'object', properties: PRODUCT_OUTPUT_PROPERTIES },
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type { ContextDevBrandResponse, ContextDevGetBrandParams } from '@/tools/context_dev/types'
|
||||
import { BRAND_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
parseContextDevResponse,
|
||||
transformBrandResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevGetBrandTool: ToolConfig<ContextDevGetBrandParams, ContextDevBrandResponse> =
|
||||
{
|
||||
id: 'context_dev_get_brand',
|
||||
name: 'Context.dev Get Brand',
|
||||
description:
|
||||
'Retrieve brand data for a domain: logos, colors, backdrops, socials, address, and industry.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevGetBrandParams>(),
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The domain to retrieve brand data for (e.g., "airbnb.com")',
|
||||
},
|
||||
forceLanguage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override the detected language with a supported language code',
|
||||
},
|
||||
maxSpeed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip time-consuming operations for a faster response (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache max age in milliseconds (86400000-31536000000, default: 7776000000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/brand/retrieve`)
|
||||
appendParam(url.searchParams, 'domain', params.domain)
|
||||
appendParam(url.searchParams, 'force_language', params.forceLanguage)
|
||||
appendParam(url.searchParams, 'maxSpeed', params.maxSpeed)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return { success: true, output: transformBrandResponse(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Retrieval status' },
|
||||
brand: {
|
||||
type: 'object',
|
||||
description: 'Brand data object',
|
||||
properties: BRAND_OUTPUT_PROPERTIES,
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevBrandResponse,
|
||||
ContextDevGetBrandByEmailParams,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { BRAND_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
parseContextDevResponse,
|
||||
transformBrandResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevGetBrandByEmailTool: ToolConfig<
|
||||
ContextDevGetBrandByEmailParams,
|
||||
ContextDevBrandResponse
|
||||
> = {
|
||||
id: 'context_dev_get_brand_by_email',
|
||||
name: 'Context.dev Get Brand by Email',
|
||||
description:
|
||||
'Retrieve brand data from a work email address. Free/disposable emails are rejected (422).',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevGetBrandByEmailParams>(),
|
||||
|
||||
params: {
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Work email address; the domain is extracted (free providers are rejected)',
|
||||
},
|
||||
forceLanguage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override the detected language with a supported language code',
|
||||
},
|
||||
maxSpeed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip time-consuming operations for a faster response (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache max age in milliseconds (86400000-31536000000, default: 7776000000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/brand/retrieve-by-email`)
|
||||
appendParam(url.searchParams, 'email', params.email)
|
||||
appendParam(url.searchParams, 'force_language', params.forceLanguage)
|
||||
appendParam(url.searchParams, 'maxSpeed', params.maxSpeed)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return { success: true, output: transformBrandResponse(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Retrieval status' },
|
||||
brand: {
|
||||
type: 'object',
|
||||
description: 'Brand data object',
|
||||
properties: BRAND_OUTPUT_PROPERTIES,
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevBrandResponse,
|
||||
ContextDevGetBrandByNameParams,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { BRAND_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
parseContextDevResponse,
|
||||
transformBrandResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevGetBrandByNameTool: ToolConfig<
|
||||
ContextDevGetBrandByNameParams,
|
||||
ContextDevBrandResponse
|
||||
> = {
|
||||
id: 'context_dev_get_brand_by_name',
|
||||
name: 'Context.dev Get Brand by Name',
|
||||
description:
|
||||
'Retrieve brand data by company name: logos, colors, socials, address, and industry.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevGetBrandByNameParams>(),
|
||||
|
||||
params: {
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Company name to retrieve brand data for (3-30 chars, e.g., "Apple Inc")',
|
||||
},
|
||||
countryGl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 2-letter country code to prioritize (e.g., "us")',
|
||||
},
|
||||
forceLanguage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override the detected language with a supported language code',
|
||||
},
|
||||
maxSpeed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip time-consuming operations for a faster response (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache max age in milliseconds (86400000-31536000000, default: 7776000000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/brand/retrieve-by-name`)
|
||||
appendParam(url.searchParams, 'name', params.name)
|
||||
appendParam(url.searchParams, 'country_gl', params.countryGl)
|
||||
appendParam(url.searchParams, 'force_language', params.forceLanguage)
|
||||
appendParam(url.searchParams, 'maxSpeed', params.maxSpeed)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return { success: true, output: transformBrandResponse(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Retrieval status' },
|
||||
brand: {
|
||||
type: 'object',
|
||||
description: 'Brand data object',
|
||||
properties: BRAND_OUTPUT_PROPERTIES,
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevBrandResponse,
|
||||
ContextDevGetBrandByTickerParams,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { BRAND_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
parseContextDevResponse,
|
||||
transformBrandResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevGetBrandByTickerTool: ToolConfig<
|
||||
ContextDevGetBrandByTickerParams,
|
||||
ContextDevBrandResponse
|
||||
> = {
|
||||
id: 'context_dev_get_brand_by_ticker',
|
||||
name: 'Context.dev Get Brand by Ticker',
|
||||
description: 'Retrieve brand data for a public company by its stock ticker symbol.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevGetBrandByTickerParams>(),
|
||||
|
||||
params: {
|
||||
ticker: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Stock ticker symbol (e.g., "AAPL", "GOOGL", "BRK.A")',
|
||||
},
|
||||
tickerExchange: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Exchange code for the ticker (e.g., "NASDAQ", "NYSE", "LSE"). Default: NASDAQ',
|
||||
},
|
||||
forceLanguage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override the detected language with a supported language code',
|
||||
},
|
||||
maxSpeed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip time-consuming operations for a faster response (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache max age in milliseconds (86400000-31536000000, default: 7776000000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/brand/retrieve-by-ticker`)
|
||||
appendParam(url.searchParams, 'ticker', params.ticker)
|
||||
appendParam(url.searchParams, 'ticker_exchange', params.tickerExchange)
|
||||
appendParam(url.searchParams, 'force_language', params.forceLanguage)
|
||||
appendParam(url.searchParams, 'maxSpeed', params.maxSpeed)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return { success: true, output: transformBrandResponse(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Retrieval status' },
|
||||
brand: {
|
||||
type: 'object',
|
||||
description: 'Brand data object',
|
||||
properties: BRAND_OUTPUT_PROPERTIES,
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { contextDevClassifyNaicsTool } from '@/tools/context_dev/classify_naics'
|
||||
import { contextDevGetBrandTool } from '@/tools/context_dev/get_brand'
|
||||
import { CONTEXT_DEV_CREDIT_USD } from '@/tools/context_dev/hosting'
|
||||
import { contextDevScrapeMarkdownTool } from '@/tools/context_dev/scrape_markdown'
|
||||
import { contextDevScreenshotTool } from '@/tools/context_dev/screenshot'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
function cost(tool: ToolConfig<any, any>, params: any, output: Record<string, unknown>) {
|
||||
const pricing = tool.hosting?.pricing
|
||||
if (!pricing || pricing.type !== 'custom') throw new Error('Expected custom pricing')
|
||||
const result = pricing.getCost(params, output)
|
||||
return typeof result === 'number' ? { cost: result } : result
|
||||
}
|
||||
|
||||
describe('Context.dev hosted key pricing', () => {
|
||||
it('declares hosting with the shared env prefix and BYOK provider on every tool', () => {
|
||||
for (const tool of [
|
||||
contextDevScrapeMarkdownTool,
|
||||
contextDevScreenshotTool,
|
||||
contextDevGetBrandTool,
|
||||
contextDevClassifyNaicsTool,
|
||||
]) {
|
||||
expect(tool.hosting?.envKeyPrefix).toBe('CONTEXT_DEV_API_KEY')
|
||||
expect(tool.hosting?.byokProviderId).toBe('context_dev')
|
||||
}
|
||||
})
|
||||
|
||||
it('charges the reported credits at the per-credit rate for a 1-credit scrape', () => {
|
||||
expect(cost(contextDevScrapeMarkdownTool, {}, { creditsConsumed: 1 }).cost).toBeCloseTo(
|
||||
CONTEXT_DEV_CREDIT_USD
|
||||
)
|
||||
})
|
||||
|
||||
it('charges the reported credits at the per-credit rate for a 5-credit screenshot', () => {
|
||||
expect(cost(contextDevScreenshotTool, {}, { creditsConsumed: 5 }).cost).toBeCloseTo(
|
||||
5 * CONTEXT_DEV_CREDIT_USD
|
||||
)
|
||||
})
|
||||
|
||||
it('charges the reported credits at the per-credit rate for a 10-credit brand lookup', () => {
|
||||
expect(cost(contextDevGetBrandTool, {}, { creditsConsumed: 10 }).cost).toBeCloseTo(
|
||||
10 * CONTEXT_DEV_CREDIT_USD
|
||||
)
|
||||
})
|
||||
|
||||
it('charges zero when the response has no credit accounting', () => {
|
||||
expect(cost(contextDevClassifyNaicsTool, {}, { creditsConsumed: null }).cost).toBe(0)
|
||||
expect(cost(contextDevClassifyNaicsTool, {}, {}).cost).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { ToolHostingConfig } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Env var prefix for Context.dev hosted keys. Provide keys as
|
||||
* `CONTEXT_DEV_API_KEY_COUNT` plus `CONTEXT_DEV_API_KEY_1..N`.
|
||||
*/
|
||||
export const CONTEXT_DEV_API_KEY_PREFIX = 'CONTEXT_DEV_API_KEY'
|
||||
|
||||
/**
|
||||
* Dollar cost of a single Context.dev credit.
|
||||
*
|
||||
* Estimated from the $25/month Developer plan (10,000 credits = $0.0025/credit)
|
||||
* — https://www.context.dev/pricing. Endpoints cost 1, 5, or 10 credits
|
||||
* depending on operation; actual usage is read from each response's
|
||||
* `key_metadata.credits_consumed` rather than hardcoded per endpoint.
|
||||
*/
|
||||
export const CONTEXT_DEV_CREDIT_USD = 0.0025
|
||||
|
||||
/**
|
||||
* Build a Context.dev `hosting` config. Every Context.dev response reports the
|
||||
* exact credits consumed via `key_metadata.credits_consumed`, already surfaced
|
||||
* on tool output as `creditsConsumed` by `extractCreditMetadata` — so cost is
|
||||
* read directly from the response rather than estimated per endpoint.
|
||||
*/
|
||||
export function contextDevHosting<P>(): ToolHostingConfig<P> {
|
||||
return {
|
||||
envKeyPrefix: CONTEXT_DEV_API_KEY_PREFIX,
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'context_dev',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
const credits = (output.creditsConsumed as number | null) ?? 0
|
||||
return { cost: credits * CONTEXT_DEV_CREDIT_USD, metadata: { credits } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 60,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevBrandResponse,
|
||||
ContextDevIdentifyTransactionParams,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { BRAND_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
parseContextDevResponse,
|
||||
transformBrandResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevIdentifyTransactionTool: ToolConfig<
|
||||
ContextDevIdentifyTransactionParams,
|
||||
ContextDevBrandResponse
|
||||
> = {
|
||||
id: 'context_dev_identify_transaction',
|
||||
name: 'Context.dev Identify Transaction',
|
||||
description:
|
||||
'Identify the brand behind a raw bank/card transaction descriptor and return its brand data.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevIdentifyTransactionParams>(),
|
||||
|
||||
params: {
|
||||
transactionInfo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The raw transaction descriptor or identifier to resolve to a brand',
|
||||
},
|
||||
countryGl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ISO 2-letter country code from the transaction (e.g., "us", "gb")',
|
||||
},
|
||||
city: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'City name to prioritize in the search',
|
||||
},
|
||||
mcc: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Merchant Category Code for the business category',
|
||||
},
|
||||
phone: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Phone number from the transaction for verification',
|
||||
},
|
||||
highConfidenceOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Enforce additional verification steps for higher confidence (default: false)',
|
||||
},
|
||||
forceLanguage: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Override the detected language with a supported language code',
|
||||
},
|
||||
maxSpeed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip time-consuming operations for a faster response (default: false)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/brand/transaction_identifier`)
|
||||
appendParam(url.searchParams, 'transaction_info', params.transactionInfo)
|
||||
appendParam(url.searchParams, 'country_gl', params.countryGl)
|
||||
appendParam(url.searchParams, 'city', params.city)
|
||||
appendParam(url.searchParams, 'mcc', params.mcc)
|
||||
appendParam(url.searchParams, 'phone', params.phone)
|
||||
appendParam(url.searchParams, 'high_confidence_only', params.highConfidenceOnly)
|
||||
appendParam(url.searchParams, 'force_language', params.forceLanguage)
|
||||
appendParam(url.searchParams, 'maxSpeed', params.maxSpeed)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return { success: true, output: transformBrandResponse(data) }
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Identification status' },
|
||||
brand: {
|
||||
type: 'object',
|
||||
description: 'Brand data for the identified merchant',
|
||||
properties: BRAND_OUTPUT_PROPERTIES,
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export { contextDevClassifyNaicsTool } from '@/tools/context_dev/classify_naics'
|
||||
export { contextDevClassifySicTool } from '@/tools/context_dev/classify_sic'
|
||||
export { contextDevCrawlTool } from '@/tools/context_dev/crawl'
|
||||
export { contextDevExtractTool } from '@/tools/context_dev/extract'
|
||||
export { contextDevExtractProductTool } from '@/tools/context_dev/extract_product'
|
||||
export { contextDevExtractProductsTool } from '@/tools/context_dev/extract_products'
|
||||
export { contextDevGetBrandTool } from '@/tools/context_dev/get_brand'
|
||||
export { contextDevGetBrandByEmailTool } from '@/tools/context_dev/get_brand_by_email'
|
||||
export { contextDevGetBrandByNameTool } from '@/tools/context_dev/get_brand_by_name'
|
||||
export { contextDevGetBrandByTickerTool } from '@/tools/context_dev/get_brand_by_ticker'
|
||||
export { contextDevIdentifyTransactionTool } from '@/tools/context_dev/identify_transaction'
|
||||
export { contextDevMapTool } from '@/tools/context_dev/map'
|
||||
export { contextDevScrapeFontsTool } from '@/tools/context_dev/scrape_fonts'
|
||||
export { contextDevScrapeHtmlTool } from '@/tools/context_dev/scrape_html'
|
||||
export { contextDevScrapeImagesTool } from '@/tools/context_dev/scrape_images'
|
||||
export { contextDevScrapeMarkdownTool } from '@/tools/context_dev/scrape_markdown'
|
||||
export { contextDevScrapeStyleguideTool } from '@/tools/context_dev/scrape_styleguide'
|
||||
export { contextDevScreenshotTool } from '@/tools/context_dev/screenshot'
|
||||
export { contextDevSearchTool } from '@/tools/context_dev/search'
|
||||
@@ -0,0 +1,94 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type { ContextDevMapParams, ContextDevMapResponse } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevMapTool: ToolConfig<ContextDevMapParams, ContextDevMapResponse> = {
|
||||
id: 'context_dev_map',
|
||||
name: 'Context.dev Map',
|
||||
description: 'Build a sitemap of a domain and return every discovered page URL.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevMapParams>(),
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The domain to build a sitemap for (e.g., "example.com")',
|
||||
},
|
||||
maxLinks: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of URLs to return (1-100000, default: 10000)',
|
||||
},
|
||||
urlRegex: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'RE2-compatible regex to filter URLs (max 256 chars)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/scrape/sitemap`)
|
||||
appendParam(url.searchParams, 'domain', params.domain)
|
||||
appendParam(url.searchParams, 'maxLinks', params.maxLinks)
|
||||
appendParam(url.searchParams, 'urlRegex', params.urlRegex)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
domain: data.domain ?? '',
|
||||
urls: data.urls ?? [],
|
||||
meta: data.meta ?? {},
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
domain: { type: 'string', description: 'The domain that was mapped' },
|
||||
urls: {
|
||||
type: 'array',
|
||||
description: 'All page URLs discovered from the sitemap',
|
||||
items: { type: 'string', description: 'Page URL' },
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Sitemap discovery stats (sitemapsDiscovered, sitemapsFetched, sitemapsSkipped, errors)',
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevScrapeFontsParams,
|
||||
ContextDevScrapeFontsResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { FONT_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevScrapeFontsTool: ToolConfig<
|
||||
ContextDevScrapeFontsParams,
|
||||
ContextDevScrapeFontsResponse
|
||||
> = {
|
||||
id: 'context_dev_scrape_fonts',
|
||||
name: 'Context.dev Scrape Fonts',
|
||||
description: 'Extract the font families, usage stats, and font files used by a domain.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevScrapeFontsParams>(),
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The domain to extract fonts from (e.g., "example.com")',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache max age in milliseconds (86400000-31536000000, default: 7776000000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/fonts`)
|
||||
appendParam(url.searchParams, 'domain', params.domain)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: data.status ?? '',
|
||||
domain: data.domain ?? '',
|
||||
fonts: data.fonts ?? [],
|
||||
fontLinks: data.fontLinks ?? {},
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Extraction status' },
|
||||
domain: { type: 'string', description: 'The domain that was analyzed' },
|
||||
fonts: {
|
||||
type: 'array',
|
||||
description: 'Fonts with usage statistics and fallbacks',
|
||||
items: { type: 'object', properties: FONT_OUTPUT_PROPERTIES },
|
||||
},
|
||||
fontLinks: {
|
||||
type: 'json',
|
||||
description: 'Font family download links keyed by font name (type, files, category)',
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevScrapeHtmlParams,
|
||||
ContextDevScrapeHtmlResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevScrapeHtmlTool: ToolConfig<
|
||||
ContextDevScrapeHtmlParams,
|
||||
ContextDevScrapeHtmlResponse
|
||||
> = {
|
||||
id: 'context_dev_scrape_html',
|
||||
name: 'Context.dev Scrape HTML',
|
||||
description: 'Scrape any URL and return the raw HTML content of the page.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevScrapeHtmlParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The full URL to scrape (must include http:// or https://)',
|
||||
},
|
||||
useMainContentOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only main content, excluding headers, footers, and navigation',
|
||||
},
|
||||
includeFrames: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Render iframe contents inline into the returned HTML (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 86400000)',
|
||||
},
|
||||
waitForMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Browser wait time after page load in milliseconds (0-30000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/scrape/html`)
|
||||
appendParam(url.searchParams, 'url', params.url)
|
||||
appendParam(url.searchParams, 'useMainContentOnly', params.useMainContentOnly)
|
||||
appendParam(url.searchParams, 'includeFrames', params.includeFrames)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'waitForMs', params.waitForMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
html: data.html ?? '',
|
||||
url: data.url ?? '',
|
||||
type: data.type ?? 'html',
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
html: { type: 'string', description: 'Raw HTML content of the page' },
|
||||
url: { type: 'string', description: 'The scraped URL' },
|
||||
type: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Detected content type (html, xml, json, text, csv, markdown, svg, pdf, doc, docx)',
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevScrapeImagesParams,
|
||||
ContextDevScrapeImagesResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import { IMAGE_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevScrapeImagesTool: ToolConfig<
|
||||
ContextDevScrapeImagesParams,
|
||||
ContextDevScrapeImagesResponse
|
||||
> = {
|
||||
id: 'context_dev_scrape_images',
|
||||
name: 'Context.dev Scrape Images',
|
||||
description: 'Discover every image asset on a page, with optional dimension and type enrichment.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevScrapeImagesParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The full URL to scrape images from (must include http:// or https://)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 86400000)',
|
||||
},
|
||||
waitForMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Browser wait time after page load in milliseconds (0-30000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
enrichResolution: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Measure image dimensions (enables 5-credit enrichment)',
|
||||
},
|
||||
enrichHostedUrl: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Host images on a CDN and return their URL and MIME type (enables enrichment)',
|
||||
},
|
||||
enrichClassification: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Classify each image by visual asset type (enables enrichment)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/scrape/images`)
|
||||
appendParam(url.searchParams, 'url', params.url)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'waitForMs', params.waitForMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
appendParam(url.searchParams, 'enrichment[resolution]', params.enrichResolution)
|
||||
appendParam(url.searchParams, 'enrichment[hostedUrl]', params.enrichHostedUrl)
|
||||
appendParam(url.searchParams, 'enrichment[classification]', params.enrichClassification)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
success: data.success ?? true,
|
||||
images: data.images ?? [],
|
||||
url: data.url ?? '',
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Whether the scrape succeeded' },
|
||||
images: {
|
||||
type: 'array',
|
||||
description: 'Discovered image assets with source, element, type, and optional enrichment',
|
||||
items: { type: 'object', properties: IMAGE_OUTPUT_PROPERTIES },
|
||||
},
|
||||
url: { type: 'string', description: 'The scraped URL' },
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevScrapeMarkdownParams,
|
||||
ContextDevScrapeMarkdownResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevScrapeMarkdownTool: ToolConfig<
|
||||
ContextDevScrapeMarkdownParams,
|
||||
ContextDevScrapeMarkdownResponse
|
||||
> = {
|
||||
id: 'context_dev_scrape_markdown',
|
||||
name: 'Context.dev Scrape Markdown',
|
||||
description: 'Scrape any URL and return clean, LLM-ready markdown content.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevScrapeMarkdownParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The full URL to scrape (must include http:// or https://)',
|
||||
},
|
||||
useMainContentOnly: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Return only main content, excluding headers, footers, and navigation',
|
||||
},
|
||||
includeLinks: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Preserve hyperlinks in the markdown output (default: true)',
|
||||
},
|
||||
includeImages: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include image references in the markdown output (default: false)',
|
||||
},
|
||||
includeFrames: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Render iframe contents inline (default: false)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 86400000)',
|
||||
},
|
||||
waitForMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Browser wait time after page load in milliseconds (0-30000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/scrape/markdown`)
|
||||
appendParam(url.searchParams, 'url', params.url)
|
||||
appendParam(url.searchParams, 'useMainContentOnly', params.useMainContentOnly)
|
||||
appendParam(url.searchParams, 'includeLinks', params.includeLinks)
|
||||
appendParam(url.searchParams, 'includeImages', params.includeImages)
|
||||
appendParam(url.searchParams, 'includeFrames', params.includeFrames)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'waitForMs', params.waitForMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
markdown: data.markdown ?? '',
|
||||
url: data.url ?? '',
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
markdown: { type: 'string', description: 'Page content as clean markdown' },
|
||||
url: { type: 'string', description: 'The scraped URL' },
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevScrapeStyleguideParams,
|
||||
ContextDevScrapeStyleguideResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevScrapeStyleguideTool: ToolConfig<
|
||||
ContextDevScrapeStyleguideParams,
|
||||
ContextDevScrapeStyleguideResponse
|
||||
> = {
|
||||
id: 'context_dev_scrape_styleguide',
|
||||
name: 'Context.dev Scrape Styleguide',
|
||||
description:
|
||||
"Extract a domain's design system: colors, typography, spacing, shadows, and UI components.",
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevScrapeStyleguideParams>(),
|
||||
|
||||
params: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The domain to extract the styleguide from (e.g., "example.com")',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache max age in milliseconds (86400000-31536000000, default: 7776000000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/styleguide`)
|
||||
appendParam(url.searchParams, 'domain', params.domain)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
status: data.status ?? '',
|
||||
domain: data.domain ?? '',
|
||||
styleguide: data.styleguide ?? null,
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
status: { type: 'string', description: 'Extraction status' },
|
||||
domain: { type: 'string', description: 'The domain that was analyzed' },
|
||||
styleguide: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Design system: mode, colors, typography, elementSpacing, shadows, fontLinks, components',
|
||||
},
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type {
|
||||
ContextDevScreenshotParams,
|
||||
ContextDevScreenshotResponse,
|
||||
} from '@/tools/context_dev/types'
|
||||
import {
|
||||
appendParam,
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig, ToolFileData } from '@/tools/types'
|
||||
|
||||
/** Maps a lowercase image file extension to its MIME type. */
|
||||
const IMAGE_MIME_BY_EXTENSION: Record<string, string> = {
|
||||
png: 'image/png',
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
webp: 'image/webp',
|
||||
gif: 'image/gif',
|
||||
avif: 'image/avif',
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the file extension and MIME type for a stored screenshot from its URL,
|
||||
* falling back to PNG when the URL has no recognizable image extension.
|
||||
*/
|
||||
function screenshotFileMeta(url: string): { extension: string; mimeType: string } {
|
||||
try {
|
||||
const ext = new URL(url).pathname.split('.').pop()?.toLowerCase() ?? ''
|
||||
if (IMAGE_MIME_BY_EXTENSION[ext]) {
|
||||
return { extension: ext, mimeType: IMAGE_MIME_BY_EXTENSION[ext] }
|
||||
}
|
||||
} catch {
|
||||
// Unparseable URL — fall back to the default below.
|
||||
}
|
||||
return { extension: 'png', mimeType: 'image/png' }
|
||||
}
|
||||
|
||||
export const contextDevScreenshotTool: ToolConfig<
|
||||
ContextDevScreenshotParams,
|
||||
ContextDevScreenshotResponse
|
||||
> = {
|
||||
id: 'context_dev_screenshot',
|
||||
name: 'Context.dev Screenshot',
|
||||
description: 'Capture a screenshot of any web page and store it as a downloadable image file.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevScreenshotParams>(),
|
||||
|
||||
params: {
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The full URL to capture (must include http:// or https://)',
|
||||
},
|
||||
fullScreenshot: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Capture the full scrollable page instead of just the viewport (default: false)',
|
||||
},
|
||||
handleCookiePopup: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Attempt to dismiss cookie banners before capturing (default: false)',
|
||||
},
|
||||
viewportWidth: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Viewport width in pixels (240-7680, default: 1920)',
|
||||
},
|
||||
viewportHeight: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Viewport height in pixels (240-4320, default: 1080)',
|
||||
},
|
||||
maxAgeMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Cache duration in milliseconds (0-2592000000, default: 86400000)',
|
||||
},
|
||||
waitForMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Post-load delay before capturing in milliseconds (0-30000, default: 3000)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: (params) => {
|
||||
const url = new URL(`${CONTEXT_DEV_BASE_URL}/web/screenshot`)
|
||||
appendParam(url.searchParams, 'directUrl', params.url)
|
||||
appendParam(url.searchParams, 'fullScreenshot', params.fullScreenshot)
|
||||
appendParam(url.searchParams, 'handleCookiePopup', params.handleCookiePopup)
|
||||
appendParam(url.searchParams, 'viewport[width]', params.viewportWidth)
|
||||
appendParam(url.searchParams, 'viewport[height]', params.viewportHeight)
|
||||
appendParam(url.searchParams, 'maxAgeMs', params.maxAgeMs)
|
||||
appendParam(url.searchParams, 'waitForMs', params.waitForMs)
|
||||
appendParam(url.searchParams, 'timeoutMS', params.timeoutMS)
|
||||
return url.toString()
|
||||
},
|
||||
headers: (params) => contextDevHeaders(params.apiKey),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
const screenshotUrl: string = data.screenshot ?? ''
|
||||
const domain: string | null = data.domain ?? null
|
||||
|
||||
const { extension, mimeType } = screenshotFileMeta(screenshotUrl)
|
||||
const file: ToolFileData | undefined = screenshotUrl
|
||||
? {
|
||||
name: `${domain ?? 'screenshot'}.${extension}`,
|
||||
mimeType,
|
||||
url: screenshotUrl,
|
||||
}
|
||||
: undefined
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...(file ? { file } : {}),
|
||||
screenshotUrl,
|
||||
screenshotType: data.screenshotType ?? null,
|
||||
domain,
|
||||
width: data.width ?? null,
|
||||
height: data.height ?? null,
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
file: { type: 'file', description: 'Stored screenshot image file', optional: true },
|
||||
screenshotUrl: { type: 'string', description: 'Public URL of the captured screenshot' },
|
||||
screenshotType: {
|
||||
type: 'string',
|
||||
description: 'Screenshot type (viewport or fullPage)',
|
||||
optional: true,
|
||||
},
|
||||
domain: { type: 'string', description: 'Domain that was captured', optional: true },
|
||||
width: { type: 'number', description: 'Screenshot width in pixels', optional: true },
|
||||
height: { type: 'number', description: 'Screenshot height in pixels', optional: true },
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { contextDevHosting } from '@/tools/context_dev/hosting'
|
||||
import type { ContextDevSearchParams, ContextDevSearchResponse } from '@/tools/context_dev/types'
|
||||
import { SEARCH_RESULT_OUTPUT_PROPERTIES } from '@/tools/context_dev/types'
|
||||
import {
|
||||
CONTEXT_DEV_BASE_URL,
|
||||
CREDIT_OUTPUTS,
|
||||
contextDevJsonHeaders,
|
||||
extractCreditMetadata,
|
||||
parseContextDevResponse,
|
||||
} from '@/tools/context_dev/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const contextDevSearchTool: ToolConfig<ContextDevSearchParams, ContextDevSearchResponse> = {
|
||||
id: 'context_dev_search',
|
||||
name: 'Context.dev Search',
|
||||
description: 'Search the web with natural language and optionally scrape results to markdown.',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: contextDevHosting<ContextDevSearchParams>(),
|
||||
|
||||
params: {
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The natural language search query (1-500 characters)',
|
||||
},
|
||||
includeDomains: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return results from these domains',
|
||||
},
|
||||
excludeDomains: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Exclude results from these domains',
|
||||
},
|
||||
freshness: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Recency filter (last_24_hours, last_week, last_month, last_year)',
|
||||
},
|
||||
numResults: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to return (10-100, default 10)',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Restrict results to a country (ISO 3166-1 alpha-2 code, e.g. US)',
|
||||
},
|
||||
queryFanout: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Expand the query into parallel variants for broader coverage',
|
||||
},
|
||||
markdownEnabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Scrape each result page to markdown (default: false)',
|
||||
},
|
||||
timeoutMS: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Request timeout in milliseconds (1000-300000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Context.dev API key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: () => `${CONTEXT_DEV_BASE_URL}/web/search`,
|
||||
headers: (params) => contextDevJsonHeaders(params.apiKey),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = { query: params.query }
|
||||
if (params.includeDomains?.length) body.includeDomains = params.includeDomains
|
||||
if (params.excludeDomains?.length) body.excludeDomains = params.excludeDomains
|
||||
if (params.freshness) body.freshness = params.freshness
|
||||
if (params.numResults != null) {
|
||||
// Context.dev accepts 10-100 results — clamp to the documented bounds.
|
||||
const requested = Math.trunc(Number(params.numResults))
|
||||
if (Number.isFinite(requested)) {
|
||||
body.numResults = Math.min(100, Math.max(10, requested))
|
||||
}
|
||||
}
|
||||
if (params.country) {
|
||||
const country = String(params.country).trim().toUpperCase()
|
||||
if (!/^[A-Z]{2}$/.test(country)) {
|
||||
throw new Error('country must be a 2-letter ISO 3166-1 alpha-2 code (e.g., US)')
|
||||
}
|
||||
body.country = country
|
||||
}
|
||||
if (params.queryFanout != null) body.queryFanout = params.queryFanout
|
||||
if (params.markdownEnabled != null) {
|
||||
body.markdownOptions = { enabled: params.markdownEnabled }
|
||||
}
|
||||
if (params.timeoutMS != null) body.timeoutMS = params.timeoutMS
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await parseContextDevResponse(response)
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results: data.results ?? [],
|
||||
query: data.query ?? '',
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Search results with url, title, description, relevance, and optional markdown',
|
||||
items: { type: 'object', properties: SEARCH_RESULT_OUTPUT_PROPERTIES },
|
||||
},
|
||||
query: { type: 'string', description: 'The query that was searched' },
|
||||
...CREDIT_OUTPUTS,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
import type { ToolFileData, ToolResponse } from '@/tools/types'
|
||||
|
||||
/** Credit accounting fields surfaced on every Context.dev tool output. */
|
||||
interface CreditFields {
|
||||
creditsConsumed: number | null
|
||||
creditsRemaining: number | null
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeMarkdownParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
useMainContentOnly?: boolean
|
||||
includeLinks?: boolean
|
||||
includeImages?: boolean
|
||||
includeFrames?: boolean
|
||||
maxAgeMs?: number
|
||||
waitForMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeMarkdownResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
markdown: string
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeHtmlParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
useMainContentOnly?: boolean
|
||||
includeFrames?: boolean
|
||||
maxAgeMs?: number
|
||||
waitForMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeHtmlResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
html: string
|
||||
url: string
|
||||
type: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevScreenshotParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
fullScreenshot?: boolean
|
||||
handleCookiePopup?: boolean
|
||||
viewportWidth?: number
|
||||
viewportHeight?: number
|
||||
maxAgeMs?: number
|
||||
waitForMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevScreenshotResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
file?: ToolFileData
|
||||
screenshotUrl: string
|
||||
screenshotType: string | null
|
||||
domain: string | null
|
||||
width: number | null
|
||||
height: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeImagesParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
maxAgeMs?: number
|
||||
waitForMs?: number
|
||||
timeoutMS?: number
|
||||
enrichResolution?: boolean
|
||||
enrichHostedUrl?: boolean
|
||||
enrichClassification?: boolean
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeImagesResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
success: boolean
|
||||
images: Array<Record<string, unknown>>
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevCrawlParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
maxPages?: number
|
||||
maxDepth?: number
|
||||
urlRegex?: string
|
||||
includeLinks?: boolean
|
||||
includeImages?: boolean
|
||||
useMainContentOnly?: boolean
|
||||
followSubdomains?: boolean
|
||||
maxAgeMs?: number
|
||||
waitForMs?: number
|
||||
stopAfterMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevCrawlResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
results: Array<{
|
||||
markdown: string
|
||||
metadata: Record<string, unknown>
|
||||
}>
|
||||
metadata: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevMapParams {
|
||||
apiKey: string
|
||||
domain: string
|
||||
maxLinks?: number
|
||||
urlRegex?: string
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevMapResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
domain: string
|
||||
urls: string[]
|
||||
meta: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevSearchParams {
|
||||
apiKey: string
|
||||
query: string
|
||||
includeDomains?: string[]
|
||||
excludeDomains?: string[]
|
||||
freshness?: string
|
||||
numResults?: number
|
||||
country?: string
|
||||
queryFanout?: boolean
|
||||
markdownEnabled?: boolean
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevSearchResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
results: Array<Record<string, unknown>>
|
||||
query: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevExtractParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
schema: Record<string, unknown>
|
||||
instructions?: string
|
||||
factCheck?: boolean
|
||||
followSubdomains?: boolean
|
||||
maxPages?: number
|
||||
maxDepth?: number
|
||||
maxAgeMs?: number
|
||||
stopAfterMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevExtractResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
status: string
|
||||
url: string
|
||||
urlsAnalyzed: string[]
|
||||
data: Record<string, unknown>
|
||||
metadata: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevExtractProductParams {
|
||||
apiKey: string
|
||||
url: string
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevExtractProductResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
isProductPage: boolean
|
||||
platform: string | null
|
||||
product: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevExtractProductsParams {
|
||||
apiKey: string
|
||||
domain: string
|
||||
maxProducts?: number
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevExtractProductsResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
products: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeFontsParams {
|
||||
apiKey: string
|
||||
domain: string
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeFontsResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
status: string
|
||||
domain: string
|
||||
fonts: Array<Record<string, unknown>>
|
||||
fontLinks: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeStyleguideParams {
|
||||
apiKey: string
|
||||
domain: string
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevScrapeStyleguideResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
status: string
|
||||
domain: string
|
||||
styleguide: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevClassifyNaicsParams {
|
||||
apiKey: string
|
||||
input: string
|
||||
minResults?: number
|
||||
maxResults?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevClassifyNaicsResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
status: string
|
||||
domain: string | null
|
||||
type: string | null
|
||||
codes: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevClassifySicParams {
|
||||
apiKey: string
|
||||
input: string
|
||||
type?: string
|
||||
minResults?: number
|
||||
maxResults?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevClassifySicResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
status: string
|
||||
domain: string | null
|
||||
type: string | null
|
||||
classification: string | null
|
||||
codes: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
/** Shared response shape for every brand-returning endpoint (full brand object). */
|
||||
export interface ContextDevBrandResponse extends ToolResponse {
|
||||
output: CreditFields & {
|
||||
status: string
|
||||
brand: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContextDevGetBrandParams {
|
||||
apiKey: string
|
||||
domain: string
|
||||
forceLanguage?: string
|
||||
maxSpeed?: boolean
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevGetBrandByNameParams {
|
||||
apiKey: string
|
||||
name: string
|
||||
countryGl?: string
|
||||
forceLanguage?: string
|
||||
maxSpeed?: boolean
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevGetBrandByEmailParams {
|
||||
apiKey: string
|
||||
email: string
|
||||
forceLanguage?: string
|
||||
maxSpeed?: boolean
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevGetBrandByTickerParams {
|
||||
apiKey: string
|
||||
ticker: string
|
||||
tickerExchange?: string
|
||||
forceLanguage?: string
|
||||
maxSpeed?: boolean
|
||||
maxAgeMs?: number
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
export interface ContextDevIdentifyTransactionParams {
|
||||
apiKey: string
|
||||
transactionInfo: string
|
||||
countryGl?: string
|
||||
city?: string
|
||||
mcc?: string
|
||||
phone?: number
|
||||
highConfidenceOnly?: boolean
|
||||
forceLanguage?: string
|
||||
maxSpeed?: boolean
|
||||
timeoutMS?: number
|
||||
}
|
||||
|
||||
/** Output schema for a single web search result. */
|
||||
export const SEARCH_RESULT_OUTPUT_PROPERTIES = {
|
||||
url: { type: 'string', description: 'Result page URL' },
|
||||
title: { type: 'string', description: 'Result page title' },
|
||||
description: { type: 'string', description: 'Result snippet/description' },
|
||||
relevance: { type: 'string', description: 'Relevance rating (high, medium, low)' },
|
||||
markdown: {
|
||||
type: 'json',
|
||||
description: 'Scraped markdown for the result (when markdown scraping is enabled)',
|
||||
},
|
||||
} as const
|
||||
|
||||
/** Output schema for a single crawled page. */
|
||||
export const CRAWL_RESULT_OUTPUT_PROPERTIES = {
|
||||
markdown: { type: 'string', description: 'Page content as markdown' },
|
||||
metadata: { type: 'json', description: 'Page metadata (url, title, crawlDepth, statusCode)' },
|
||||
} as const
|
||||
|
||||
/** Output schema for a single industry classification code. */
|
||||
export const CLASSIFICATION_CODE_OUTPUT_PROPERTIES = {
|
||||
code: { type: 'string', description: 'Industry code' },
|
||||
name: { type: 'string', description: 'Industry name' },
|
||||
confidence: { type: 'string', description: 'Match confidence (high, medium, low)' },
|
||||
} as const
|
||||
|
||||
/** Output schema for the full brand object returned by brand-intelligence endpoints. */
|
||||
export const BRAND_OUTPUT_PROPERTIES = {
|
||||
domain: { type: 'string', description: 'Brand domain' },
|
||||
title: { type: 'string', description: 'Brand title' },
|
||||
description: { type: 'string', description: 'Brand description' },
|
||||
slogan: { type: 'string', description: 'Brand slogan' },
|
||||
colors: { type: 'json', description: 'Brand colors (hex and name)' },
|
||||
logos: { type: 'json', description: 'Brand logos with mode, colors, resolution, and type' },
|
||||
backdrops: { type: 'json', description: 'Brand backdrop images' },
|
||||
socials: { type: 'json', description: 'Social media profiles (type and url)' },
|
||||
address: { type: 'json', description: 'Brand address' },
|
||||
stock: { type: 'json', description: 'Stock info (ticker and exchange)' },
|
||||
is_nsfw: { type: 'boolean', description: 'Whether the brand contains adult content' },
|
||||
email: { type: 'string', description: 'Brand contact email' },
|
||||
phone: { type: 'string', description: 'Brand contact phone' },
|
||||
industries: { type: 'json', description: 'Industry taxonomy (eic industry/subindustry pairs)' },
|
||||
links: {
|
||||
type: 'json',
|
||||
description: 'Key brand links (careers, privacy, terms, blog, pricing, contact)',
|
||||
},
|
||||
primary_language: { type: 'string', description: 'Primary language of the brand site' },
|
||||
} as const
|
||||
|
||||
/** Output schema for the reduced brand object returned by the simplified endpoint. */
|
||||
export const SIMPLIFIED_BRAND_OUTPUT_PROPERTIES = {
|
||||
domain: { type: 'string', description: 'Brand domain' },
|
||||
title: { type: 'string', description: 'Brand title' },
|
||||
colors: { type: 'json', description: 'Brand colors (hex and name)' },
|
||||
logos: { type: 'json', description: 'Brand logos with mode, colors, resolution, and type' },
|
||||
backdrops: { type: 'json', description: 'Brand backdrop images' },
|
||||
} as const
|
||||
|
||||
/** Output schema for a single extracted product. */
|
||||
export const PRODUCT_OUTPUT_PROPERTIES = {
|
||||
name: { type: 'string', description: 'Product name' },
|
||||
description: { type: 'string', description: 'Product description' },
|
||||
price: { type: 'number', description: 'Product price' },
|
||||
currency: { type: 'string', description: 'Price currency' },
|
||||
billing_frequency: {
|
||||
type: 'string',
|
||||
description: 'Billing frequency (monthly, yearly, one_time, usage_based)',
|
||||
},
|
||||
pricing_model: {
|
||||
type: 'string',
|
||||
description: 'Pricing model (per_seat, flat, tiered, freemium, custom)',
|
||||
},
|
||||
url: { type: 'string', description: 'Product URL' },
|
||||
category: { type: 'string', description: 'Product category' },
|
||||
features: { type: 'json', description: 'Product features' },
|
||||
target_audience: { type: 'json', description: 'Target audience' },
|
||||
tags: { type: 'json', description: 'Product tags' },
|
||||
image_url: { type: 'string', description: 'Primary product image URL' },
|
||||
images: { type: 'json', description: 'Product image URLs' },
|
||||
sku: { type: 'string', description: 'Product SKU' },
|
||||
} as const
|
||||
|
||||
/** Output schema for a single font usage entry. */
|
||||
export const FONT_OUTPUT_PROPERTIES = {
|
||||
font: { type: 'string', description: 'Font family name' },
|
||||
uses: { type: 'json', description: 'Where the font is used' },
|
||||
fallbacks: { type: 'json', description: 'Fallback font families' },
|
||||
num_elements: { type: 'number', description: 'Number of elements using the font' },
|
||||
num_words: { type: 'number', description: 'Number of words rendered in the font' },
|
||||
percent_words: { type: 'number', description: 'Percent of words using the font' },
|
||||
percent_elements: { type: 'number', description: 'Percent of elements using the font' },
|
||||
} as const
|
||||
|
||||
/** Output schema for a single scraped image. */
|
||||
export const IMAGE_OUTPUT_PROPERTIES = {
|
||||
src: { type: 'string', description: 'Image source URL or data' },
|
||||
element: {
|
||||
type: 'string',
|
||||
description: 'Source element (img, svg, link, source, video, css, object, meta, background)',
|
||||
},
|
||||
type: { type: 'string', description: 'Image representation (url, html, base64)' },
|
||||
alt: { type: 'string', description: 'Alt text', optional: true },
|
||||
enrichment: {
|
||||
type: 'json',
|
||||
description: 'Optional enrichment (width, height, mimetype, url, type) when requested',
|
||||
},
|
||||
} as const
|
||||
@@ -0,0 +1,99 @@
|
||||
/** Base URL for all Context.dev API endpoints. */
|
||||
export const CONTEXT_DEV_BASE_URL = 'https://api.context.dev/v1'
|
||||
|
||||
/**
|
||||
* Builds the standard Context.dev request headers with Bearer authentication.
|
||||
*/
|
||||
export function contextDevHeaders(apiKey: string): Record<string, string> {
|
||||
return {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
Accept: 'application/json',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds JSON request headers with Bearer authentication for POST endpoints.
|
||||
*/
|
||||
export function contextDevJsonHeaders(apiKey: string): Record<string, string> {
|
||||
return {
|
||||
...contextDevHeaders(apiKey),
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a descriptive error when a Context.dev response is not successful.
|
||||
* Returns the parsed JSON body on success.
|
||||
*/
|
||||
export async function parseContextDevResponse(response: Response): Promise<any> {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Context.dev API error (${response.status}): ${errorText}`)
|
||||
}
|
||||
return response.json()
|
||||
}
|
||||
|
||||
/** Shape of the credit accounting object present on every Context.dev response. */
|
||||
interface ContextDevKeyMetadata {
|
||||
credits_consumed?: number
|
||||
credits_remaining?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the credit accounting fields shared by every Context.dev response.
|
||||
*/
|
||||
export function extractCreditMetadata(keyMetadata: ContextDevKeyMetadata | undefined): {
|
||||
creditsConsumed: number | null
|
||||
creditsRemaining: number | null
|
||||
} {
|
||||
return {
|
||||
creditsConsumed: keyMetadata?.credits_consumed ?? null,
|
||||
creditsRemaining: keyMetadata?.credits_remaining ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a brand-returning Context.dev response into the shared tool output shape.
|
||||
* Used by every endpoint that returns a `brand` object.
|
||||
*/
|
||||
export function transformBrandResponse(data: any): {
|
||||
status: string
|
||||
brand: Record<string, unknown> | null
|
||||
creditsConsumed: number | null
|
||||
creditsRemaining: number | null
|
||||
} {
|
||||
return {
|
||||
status: data.status ?? '',
|
||||
brand: data.brand ?? null,
|
||||
...extractCreditMetadata(data.key_metadata),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a parameter to a URLSearchParams instance only when it is defined and non-empty.
|
||||
* Booleans are serialized as the literal strings 'true' / 'false'.
|
||||
*/
|
||||
export function appendParam(
|
||||
search: URLSearchParams,
|
||||
key: string,
|
||||
value: string | number | boolean | undefined | null
|
||||
): void {
|
||||
if (value === undefined || value === null || value === '') return
|
||||
const serialized = typeof value === 'string' ? value.trim() : String(value)
|
||||
if (serialized === '') return
|
||||
search.append(key, serialized)
|
||||
}
|
||||
|
||||
/** Output definitions for the credit accounting fields, reused across every tool. */
|
||||
export const CREDIT_OUTPUTS = {
|
||||
creditsConsumed: {
|
||||
type: 'number',
|
||||
description: 'Credits consumed by this request',
|
||||
optional: true,
|
||||
},
|
||||
creditsRemaining: {
|
||||
type: 'number',
|
||||
description: 'Credits remaining on the API key',
|
||||
optional: true,
|
||||
},
|
||||
} as const
|
||||
Reference in New Issue
Block a user