mirror of
https://github.com/simstudioai/sim.git
synced 2026-10-25 09:34:44 +08:00
feat(clean-hosted-keys) Remove eleven labs, browseruse. Tweak firecrawl and mistral key impl (#3503)
* Remove eleven labs, browseruse, and firecrawl * Remove creditsUsed output * Add back mistral hosting for mistral blocks * Add back firecrawl since they queue up concurrent requests * Fix price calculation, remove agent since its super long running and will clog up queue * Define hosting per tool * Remove redundant token finding --------- Co-authored-by: Theodore Li <theo@sim.ai>
This commit is contained in:
co-authored by
Theodore Li
parent
6db8bc4934
commit
fa1ae1398e
@@ -18,15 +18,13 @@ const VALID_PROVIDERS = [
|
||||
'anthropic',
|
||||
'google',
|
||||
'mistral',
|
||||
'exa',
|
||||
'browser_use',
|
||||
'serper',
|
||||
'firecrawl',
|
||||
'exa',
|
||||
'serper',
|
||||
'linkup',
|
||||
'perplexity',
|
||||
'jina',
|
||||
'google_cloud',
|
||||
'elevenlabs',
|
||||
'parallel_ai',
|
||||
'brandfetch',
|
||||
] as const
|
||||
|
||||
@@ -16,8 +16,6 @@ import {
|
||||
import {
|
||||
AnthropicIcon,
|
||||
BrandfetchIcon,
|
||||
BrowserUseIcon,
|
||||
ElevenLabsIcon,
|
||||
ExaAIIcon,
|
||||
FirecrawlIcon,
|
||||
GeminiIcon,
|
||||
@@ -77,6 +75,13 @@ const PROVIDERS: {
|
||||
description: 'LLM calls and Knowledge Base OCR',
|
||||
placeholder: 'Enter your API key',
|
||||
},
|
||||
{
|
||||
id: 'firecrawl',
|
||||
name: 'Firecrawl',
|
||||
icon: FirecrawlIcon,
|
||||
description: 'Web scraping, crawling, search, and extraction',
|
||||
placeholder: 'Enter your Firecrawl API key',
|
||||
},
|
||||
{
|
||||
id: 'exa',
|
||||
name: 'Exa',
|
||||
@@ -84,13 +89,6 @@ const PROVIDERS: {
|
||||
description: 'AI-powered search and research',
|
||||
placeholder: 'Enter your Exa API key',
|
||||
},
|
||||
{
|
||||
id: 'browser_use',
|
||||
name: 'Browser Use',
|
||||
icon: BrowserUseIcon,
|
||||
description: 'Browser automation tasks',
|
||||
placeholder: 'Enter your Browser Use API key',
|
||||
},
|
||||
{
|
||||
id: 'serper',
|
||||
name: 'Serper',
|
||||
@@ -98,13 +96,6 @@ const PROVIDERS: {
|
||||
description: 'Google search API',
|
||||
placeholder: 'Enter your Serper API key',
|
||||
},
|
||||
{
|
||||
id: 'firecrawl',
|
||||
name: 'Firecrawl',
|
||||
icon: FirecrawlIcon,
|
||||
description: 'Web scraping, crawling, and data extraction',
|
||||
placeholder: 'Enter your Firecrawl API key',
|
||||
},
|
||||
{
|
||||
id: 'linkup',
|
||||
name: 'Linkup',
|
||||
@@ -133,13 +124,6 @@ const PROVIDERS: {
|
||||
description: 'Web reading and search',
|
||||
placeholder: 'jina_...',
|
||||
},
|
||||
{
|
||||
id: 'elevenlabs',
|
||||
name: 'ElevenLabs',
|
||||
icon: ElevenLabsIcon,
|
||||
description: 'Text-to-speech generation',
|
||||
placeholder: 'Enter your ElevenLabs API key',
|
||||
},
|
||||
{
|
||||
id: 'google_cloud',
|
||||
name: 'Google Cloud',
|
||||
|
||||
@@ -72,7 +72,6 @@ export const BrowserUseBlock: BlockConfig<BrowserUseResponse> = {
|
||||
password: true,
|
||||
placeholder: 'Enter your BrowserUse API key',
|
||||
required: true,
|
||||
hideWhenHosted: true,
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
|
||||
@@ -48,7 +48,6 @@ export const ElevenLabsBlock: BlockConfig<ElevenLabsBlockResponse> = {
|
||||
placeholder: 'Enter your ElevenLabs API key',
|
||||
password: true,
|
||||
required: true,
|
||||
hideWhenHosted: true,
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@@ -249,6 +249,23 @@ Example 2 - Product Data:
|
||||
password: true,
|
||||
required: true,
|
||||
hideWhenHosted: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'agent',
|
||||
not: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter your Firecrawl API key',
|
||||
password: true,
|
||||
required: true,
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'agent',
|
||||
},
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
@@ -444,7 +461,6 @@ Example 2 - Product Data:
|
||||
// Crawl output
|
||||
pages: { type: 'json', description: 'Crawled pages data' },
|
||||
total: { type: 'number', description: 'Total pages found' },
|
||||
creditsUsed: { type: 'number', description: 'Credits consumed' },
|
||||
// Map output
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
links: { type: 'json', description: 'Discovered URLs array' },
|
||||
|
||||
@@ -68,6 +68,7 @@ export const MistralParseBlock: BlockConfig<MistralParserOutput> = {
|
||||
placeholder: 'Enter your Mistral API key',
|
||||
password: true,
|
||||
required: true,
|
||||
hideWhenHosted: true,
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
@@ -75,13 +76,12 @@ export const MistralParseBlock: BlockConfig<MistralParserOutput> = {
|
||||
config: {
|
||||
tool: () => 'mistral_parser',
|
||||
params: (params) => {
|
||||
if (!params || !params.apiKey || params.apiKey.trim() === '') {
|
||||
throw new Error('Mistral API key is required')
|
||||
const parameters: Record<string, unknown> = {
|
||||
resultType: params.resultType || 'markdown',
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {
|
||||
apiKey: params.apiKey.trim(),
|
||||
resultType: params.resultType || 'markdown',
|
||||
if (params.apiKey?.trim()) {
|
||||
parameters.apiKey = params.apiKey.trim()
|
||||
}
|
||||
|
||||
const inputMethod = params.inputMethod || 'url'
|
||||
@@ -195,6 +195,7 @@ export const MistralParseV2Block: BlockConfig<MistralParserOutput> = {
|
||||
placeholder: 'Enter your Mistral API key',
|
||||
password: true,
|
||||
required: true,
|
||||
hideWhenHosted: true,
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
@@ -206,13 +207,12 @@ export const MistralParseV2Block: BlockConfig<MistralParserOutput> = {
|
||||
fallbackToolId: 'mistral_parser_v2',
|
||||
}),
|
||||
params: (params) => {
|
||||
if (!params || !params.apiKey || params.apiKey.trim() === '') {
|
||||
throw new Error('Mistral API key is required')
|
||||
const parameters: Record<string, unknown> = {
|
||||
resultType: params.resultType || 'markdown',
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {
|
||||
apiKey: params.apiKey.trim(),
|
||||
resultType: params.resultType || 'markdown',
|
||||
if (params.apiKey?.trim()) {
|
||||
parameters.apiKey = params.apiKey.trim()
|
||||
}
|
||||
|
||||
// Use canonical document param directly
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { getMaxExecutionTimeout } from '@/lib/core/execution-limits'
|
||||
import { PlatformEvents } from '@/lib/core/telemetry'
|
||||
import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from '@/tools/browser_use/types'
|
||||
import type { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
|
||||
@@ -257,62 +256,6 @@ export const runTaskTool: ToolConfig<BrowserUseRunTaskParams, BrowserUseRunTaskR
|
||||
},
|
||||
},
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: 'BROWSER_USE_API_KEY',
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'browser_use',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (params, output) => {
|
||||
if (!Array.isArray(output.steps)) {
|
||||
throw new Error('Browser Use response missing steps array, cannot determine cost')
|
||||
}
|
||||
const INIT_COST = 0.01
|
||||
const STEP_COSTS: Record<string, number> = {
|
||||
'browser-use-llm': 0.002,
|
||||
'browser-use-2.0': 0.006,
|
||||
o3: 0.03,
|
||||
'o4-mini': 0.03,
|
||||
'gemini-3-pro-preview': 0.03,
|
||||
'gemini-3-flash-preview': 0.015,
|
||||
'gemini-flash-latest': 0.0075,
|
||||
'gemini-flash-lite-latest': 0.005,
|
||||
'gemini-2.5-flash': 0.0075,
|
||||
'gemini-2.5-pro': 0.03,
|
||||
'claude-sonnet-4-5-20250929': 0.05,
|
||||
'claude-opus-4-5-20251101': 0.05,
|
||||
'claude-3-7-sonnet-20250219': 0.05,
|
||||
'gpt-4o': 0.006,
|
||||
'gpt-4o-mini': 0.006,
|
||||
'gpt-4.1': 0.006,
|
||||
'gpt-4.1-mini': 0.006,
|
||||
'llama-4-maverick-17b-128e-instruct': 0.006,
|
||||
}
|
||||
const DEFAULT_STEP_COST = 0.006
|
||||
const model = (params.model as string) || 'browser-use-2.0'
|
||||
const knownCost = STEP_COSTS[model]
|
||||
if (!knownCost) {
|
||||
logger.warn(
|
||||
`Unknown Browser Use model "${model}", using default step cost $${DEFAULT_STEP_COST}`
|
||||
)
|
||||
PlatformEvents.hostedKeyUnknownModelCost({
|
||||
toolId: 'browser_use_run_task',
|
||||
modelName: model,
|
||||
defaultCost: DEFAULT_STEP_COST,
|
||||
})
|
||||
}
|
||||
const stepCost = knownCost ?? DEFAULT_STEP_COST
|
||||
const stepCount = output.steps.length
|
||||
const total = INIT_COST + stepCount * stepCost
|
||||
return { cost: total, metadata: { model, stepCount, stepCost, initCost: INIT_COST } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 100,
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.browser-use.com/api/v2/tasks',
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export const FLASH_TURBO_MODELS = new Set([
|
||||
'eleven_turbo_v2',
|
||||
'eleven_turbo_v2_5',
|
||||
'eleven_flash_v2_5',
|
||||
])
|
||||
@@ -1,4 +1,3 @@
|
||||
import { FLASH_TURBO_MODELS } from '@/tools/elevenlabs/constants'
|
||||
import type { ElevenLabsTtsParams, ElevenLabsTtsResponse } from '@/tools/elevenlabs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -8,32 +7,6 @@ export const elevenLabsTtsTool: ToolConfig<ElevenLabsTtsParams, ElevenLabsTtsRes
|
||||
description: 'Convert TTS using ElevenLabs voices',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: 'ELEVENLABS_API_KEY',
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'elevenlabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (params, _output) => {
|
||||
const text = params.text as string | undefined
|
||||
if (!text) {
|
||||
throw new Error('Missing text parameter, cannot determine character cost')
|
||||
}
|
||||
const characterCount = text.length
|
||||
const modelId = (params.modelId as string) || 'eleven_monolingual_v1'
|
||||
// Flash/Turbo: $0.08/1K chars, Standard/Multilingual/v3: $0.18/1K chars
|
||||
// Scale tier additional character rates — https://elevenlabs.io/pricing/api
|
||||
const costPer1KChars = FLASH_TURBO_MODELS.has(modelId) ? 0.08 : 0.18
|
||||
const cost = (characterCount / 1000) * costPer1KChars
|
||||
return { cost, metadata: { characterCount, modelId, costPer1KChars } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 30,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
text: {
|
||||
type: 'string',
|
||||
|
||||
@@ -55,27 +55,6 @@ export const agentTool: ToolConfig<AgentParams, AgentResponse> = {
|
||||
},
|
||||
},
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: 'FIRECRAWL_API_KEY',
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'firecrawl',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
if (output.creditsUsed == null) {
|
||||
throw new Error('Firecrawl agent response missing creditsUsed field')
|
||||
}
|
||||
const creditsUsed = output.creditsUsed as number
|
||||
const cost = creditsUsed * 0.001
|
||||
return { cost, metadata: { creditsUsed } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 100,
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: 'https://api.firecrawl.dev/v2/agent',
|
||||
@@ -210,10 +189,6 @@ export const agentTool: ToolConfig<AgentParams, AgentResponse> = {
|
||||
type: 'object',
|
||||
description: 'Extracted data from the agent',
|
||||
},
|
||||
creditsUsed: {
|
||||
type: 'number',
|
||||
description: 'Number of credits consumed by this agent task',
|
||||
},
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
description: 'Timestamp when the results expire (24 hours)',
|
||||
|
||||
@@ -77,11 +77,18 @@ export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse>
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
if (output.creditsUsed == null) {
|
||||
throw new Error('Firecrawl crawl response missing creditsUsed field')
|
||||
throw new Error('Firecrawl response missing creditsUsed field')
|
||||
}
|
||||
|
||||
const creditsUsed = Number(output.creditsUsed)
|
||||
if (Number.isNaN(creditsUsed)) {
|
||||
throw new Error('Firecrawl response returned a non-numeric creditsUsed field')
|
||||
}
|
||||
|
||||
return {
|
||||
cost: creditsUsed * 0.001,
|
||||
metadata: { creditsUsed },
|
||||
}
|
||||
const creditsUsed = output.creditsUsed as number
|
||||
const cost = creditsUsed * 0.001
|
||||
return { cost, metadata: { creditsUsed } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
@@ -222,9 +229,5 @@ export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse>
|
||||
},
|
||||
},
|
||||
total: { type: 'number', description: 'Total number of pages found during crawl' },
|
||||
creditsUsed: {
|
||||
type: 'number',
|
||||
description: 'Number of credits consumed by the crawl operation',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -87,11 +87,18 @@ export const extractTool: ToolConfig<ExtractParams, ExtractResponse> = {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
if (output.creditsUsed == null) {
|
||||
throw new Error('Firecrawl extract response missing creditsUsed field')
|
||||
throw new Error('Firecrawl response missing creditsUsed field')
|
||||
}
|
||||
|
||||
const creditsUsed = Number(output.creditsUsed)
|
||||
if (Number.isNaN(creditsUsed)) {
|
||||
throw new Error('Firecrawl response returned a non-numeric creditsUsed field')
|
||||
}
|
||||
|
||||
return {
|
||||
cost: creditsUsed * 0.001,
|
||||
metadata: { creditsUsed },
|
||||
}
|
||||
const creditsUsed = output.creditsUsed as number
|
||||
const cost = creditsUsed * 0.001
|
||||
return { cost, metadata: { creditsUsed } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
@@ -233,9 +240,5 @@ export const extractTool: ToolConfig<ExtractParams, ExtractResponse> = {
|
||||
type: 'object',
|
||||
description: 'Extracted structured data according to the schema or prompt',
|
||||
},
|
||||
creditsUsed: {
|
||||
type: 'number',
|
||||
description: 'Number of Firecrawl credits consumed by the extraction',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -74,11 +74,18 @@ export const mapTool: ToolConfig<MapParams, MapResponse> = {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
if (output.creditsUsed == null) {
|
||||
throw new Error('Firecrawl map response missing creditsUsed field')
|
||||
throw new Error('Firecrawl response missing creditsUsed field')
|
||||
}
|
||||
|
||||
const creditsUsed = Number(output.creditsUsed)
|
||||
if (Number.isNaN(creditsUsed)) {
|
||||
throw new Error('Firecrawl response returned a non-numeric creditsUsed field')
|
||||
}
|
||||
|
||||
return {
|
||||
cost: creditsUsed * 0.001,
|
||||
metadata: { creditsUsed },
|
||||
}
|
||||
const creditsUsed = output.creditsUsed as number
|
||||
const cost = creditsUsed * 0.001
|
||||
return { cost, metadata: { creditsUsed } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
@@ -121,7 +128,7 @@ export const mapTool: ToolConfig<MapParams, MapResponse> = {
|
||||
output: {
|
||||
success: data.success,
|
||||
links: data.links || [],
|
||||
creditsUsed: data.creditsUsed,
|
||||
creditsUsed: 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -138,6 +145,5 @@ export const mapTool: ToolConfig<MapParams, MapResponse> = {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
creditsUsed: { type: 'number', description: 'Number of Firecrawl credits consumed' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -39,11 +39,18 @@ export const scrapeTool: ToolConfig<ScrapeParams, ScrapeResponse> = {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
if (output.creditsUsed == null) {
|
||||
throw new Error('Firecrawl scrape response missing creditsUsed field')
|
||||
throw new Error('Firecrawl response missing creditsUsed field')
|
||||
}
|
||||
|
||||
const creditsUsed = Number(output.creditsUsed)
|
||||
if (Number.isNaN(creditsUsed)) {
|
||||
throw new Error('Firecrawl response returned a non-numeric creditsUsed field')
|
||||
}
|
||||
|
||||
return {
|
||||
cost: creditsUsed * 0.001,
|
||||
metadata: { creditsUsed },
|
||||
}
|
||||
const creditsUsed = output.creditsUsed as number
|
||||
const cost = creditsUsed * 0.001
|
||||
return { cost, metadata: { creditsUsed } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
@@ -116,6 +123,5 @@ export const scrapeTool: ToolConfig<ScrapeParams, ScrapeResponse> = {
|
||||
description: 'Page metadata including SEO and Open Graph information',
|
||||
properties: PAGE_METADATA_OUTPUT_PROPERTIES,
|
||||
},
|
||||
creditsUsed: { type: 'number', description: 'Number of Firecrawl credits consumed' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -31,11 +31,18 @@ export const searchTool: ToolConfig<SearchParams, SearchResponse> = {
|
||||
type: 'custom',
|
||||
getCost: (_params, output) => {
|
||||
if (output.creditsUsed == null) {
|
||||
throw new Error('Firecrawl search response missing creditsUsed field')
|
||||
throw new Error('Firecrawl response missing creditsUsed field')
|
||||
}
|
||||
|
||||
const creditsUsed = Number(output.creditsUsed)
|
||||
if (Number.isNaN(creditsUsed)) {
|
||||
throw new Error('Firecrawl response returned a non-numeric creditsUsed field')
|
||||
}
|
||||
|
||||
return {
|
||||
cost: creditsUsed * 0.001,
|
||||
metadata: { creditsUsed },
|
||||
}
|
||||
const creditsUsed = output.creditsUsed as number
|
||||
const cost = creditsUsed * 0.001
|
||||
return { cost, metadata: { creditsUsed } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
@@ -93,6 +100,5 @@ export const searchTool: ToolConfig<SearchParams, SearchResponse> = {
|
||||
properties: SEARCH_RESULT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
creditsUsed: { type: 'number', description: 'Number of Firecrawl credits consumed' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -167,7 +167,6 @@ export const CRAWL_OUTPUT_PROPERTIES = {
|
||||
},
|
||||
},
|
||||
total: { type: 'number', description: 'Total number of pages found during crawl' },
|
||||
creditsUsed: { type: 'number', description: 'Number of credits consumed by the crawl operation' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
@@ -274,11 +273,6 @@ export const AGENT_OUTPUT_PROPERTIES = {
|
||||
type: 'object',
|
||||
description: 'Extracted data from the agent based on the prompt and schema',
|
||||
},
|
||||
creditsUsed: {
|
||||
type: 'number',
|
||||
description: 'Number of credits consumed by this agent task',
|
||||
optional: true,
|
||||
},
|
||||
expiresAt: {
|
||||
type: 'string',
|
||||
description: 'ISO timestamp when the results expire (24 hours after completion)',
|
||||
@@ -448,7 +442,7 @@ export interface ScrapeResponse extends ToolResponse {
|
||||
statusCode: number
|
||||
error?: string
|
||||
}
|
||||
creditsUsed: number
|
||||
creditsUsed?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,7 +465,7 @@ export interface SearchResponse extends ToolResponse {
|
||||
error?: string
|
||||
}
|
||||
}>
|
||||
creditsUsed: number
|
||||
creditsUsed?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +488,7 @@ export interface FirecrawlCrawlResponse extends ToolResponse {
|
||||
}
|
||||
}>
|
||||
total: number
|
||||
creditsUsed: number
|
||||
creditsUsed?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +496,7 @@ export interface MapResponse extends ToolResponse {
|
||||
output: {
|
||||
success: boolean
|
||||
links: string[]
|
||||
creditsUsed: number
|
||||
creditsUsed?: number
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,21 @@ const MISTRAL_OCR_HOSTING = {
|
||||
byokProviderId: 'mistral' as const,
|
||||
pricing: {
|
||||
type: 'custom' as const,
|
||||
getCost: (_params: Record<string, unknown>, output: Record<string, unknown>) => {
|
||||
// Mistral OCR: $2 per 1,000 pages ($0.002/page) for real-time API
|
||||
// Batch inference is $1/1K pages but we call the real-time endpoint
|
||||
// https://mistral.ai/pricing#api
|
||||
const usageInfo = output.usage_info as { pages_processed?: number } | undefined
|
||||
if (usageInfo?.pages_processed == null) {
|
||||
throw new Error('Mistral OCR response missing pages_processed in usage_info')
|
||||
getCost: (_params: unknown, output: Record<string, unknown>) => {
|
||||
// Mistral OCR 3 standard pricing: $2 per 1,000 pages ($0.002/page).
|
||||
// Annotated pages are priced separately at $3 per 1,000 annotated pages, but this tool does
|
||||
// not submit annotation requests. Source: https://docs.mistral.ai/models/ocr-3-25-12
|
||||
const rawUsageInfo = output.usage_info as { pages_processed?: number } | undefined
|
||||
const transformedUsageInfo = (
|
||||
output.metadata as { usageInfo?: { pagesProcessed?: number } } | undefined
|
||||
)?.usageInfo
|
||||
const pagesProcessed = rawUsageInfo?.pages_processed ?? transformedUsageInfo?.pagesProcessed
|
||||
|
||||
if (pagesProcessed == null) {
|
||||
throw new Error(
|
||||
'Mistral OCR response missing pages_processed in usage_info or metadata.usageInfo.pagesProcessed'
|
||||
)
|
||||
}
|
||||
const pagesProcessed = usageInfo.pages_processed
|
||||
const cost = pagesProcessed * 0.002
|
||||
return { cost, metadata: { pagesProcessed } }
|
||||
},
|
||||
@@ -40,6 +46,7 @@ export const mistralParserTool: ToolConfig<MistralParserInput, MistralParserOutp
|
||||
name: 'Mistral PDF Parser',
|
||||
description: 'Parse PDF documents using Mistral OCR API',
|
||||
version: '1.0.0',
|
||||
hosting: MISTRAL_OCR_HOSTING,
|
||||
|
||||
params: {
|
||||
filePath: {
|
||||
@@ -379,6 +386,7 @@ export const mistralParserV2Tool: ToolConfig<MistralParserInput, MistralParserV2
|
||||
name: 'Mistral PDF Parser',
|
||||
description: 'Parse PDF documents using Mistral OCR API',
|
||||
version: '2.0.0',
|
||||
hosting: MISTRAL_OCR_HOSTING,
|
||||
|
||||
params: mistralParserTool.params,
|
||||
request: mistralParserTool.request,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { FLASH_TURBO_MODELS } from '@/tools/elevenlabs/constants'
|
||||
import type { ElevenLabsTtsUnifiedParams, TtsBlockResponse } from '@/tools/tts/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -8,32 +7,6 @@ export const elevenLabsTtsUnifiedTool: ToolConfig<ElevenLabsTtsUnifiedParams, Tt
|
||||
description: 'Convert text to speech using ElevenLabs voices',
|
||||
version: '1.0.0',
|
||||
|
||||
hosting: {
|
||||
envKeyPrefix: 'ELEVENLABS_API_KEY',
|
||||
apiKeyParam: 'apiKey',
|
||||
byokProviderId: 'elevenlabs',
|
||||
pricing: {
|
||||
type: 'custom',
|
||||
getCost: (params, _output) => {
|
||||
const text = params.text as string | undefined
|
||||
if (!text) {
|
||||
throw new Error('Missing text parameter, cannot determine character cost')
|
||||
}
|
||||
const characterCount = text.length
|
||||
const modelId = (params.modelId as string) || 'eleven_turbo_v2_5'
|
||||
// Flash/Turbo: $0.08/1K chars, Standard/Multilingual/v3: $0.18/1K chars
|
||||
// Scale tier additional character rates — https://elevenlabs.io/pricing/api
|
||||
const costPer1KChars = FLASH_TURBO_MODELS.has(modelId) ? 0.08 : 0.18
|
||||
const cost = (characterCount / 1000) * costPer1KChars
|
||||
return { cost, metadata: { characterCount, modelId, costPer1KChars } }
|
||||
},
|
||||
},
|
||||
rateLimit: {
|
||||
mode: 'per_request',
|
||||
requestsPerMinute: 30,
|
||||
},
|
||||
},
|
||||
|
||||
params: {
|
||||
text: {
|
||||
type: 'string',
|
||||
|
||||
@@ -6,12 +6,10 @@ export type BYOKProviderId =
|
||||
| 'anthropic'
|
||||
| 'google'
|
||||
| 'mistral'
|
||||
| 'exa'
|
||||
| 'browser_use'
|
||||
| 'serper'
|
||||
| 'firecrawl'
|
||||
| 'exa'
|
||||
| 'serper'
|
||||
| 'jina'
|
||||
| 'elevenlabs'
|
||||
| 'perplexity'
|
||||
| 'google_cloud'
|
||||
| 'linkup'
|
||||
|
||||
Reference in New Issue
Block a user