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:
Theodore Li
2026-03-10 16:53:56 -04:00
committed by GitHub
co-authored by Theodore Li
parent 6db8bc4934
commit fa1ae1398e
19 changed files with 114 additions and 235 deletions
@@ -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',
-1
View File
@@ -72,7 +72,6 @@ export const BrowserUseBlock: BlockConfig<BrowserUseResponse> = {
password: true,
placeholder: 'Enter your BrowserUse API key',
required: true,
hideWhenHosted: true,
},
],
tools: {
-1
View File
@@ -48,7 +48,6 @@ export const ElevenLabsBlock: BlockConfig<ElevenLabsBlockResponse> = {
placeholder: 'Enter your ElevenLabs API key',
password: true,
required: true,
hideWhenHosted: true,
},
],
+17 -1
View File
@@ -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' },
+10 -10
View File
@@ -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
-57
View File
@@ -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',
-5
View File
@@ -1,5 +0,0 @@
export const FLASH_TURBO_MODELS = new Set([
'eleven_turbo_v2',
'eleven_turbo_v2_5',
'eleven_flash_v2_5',
])
-27
View File
@@ -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',
-25
View File
@@ -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)',
+11 -8
View File
@@ -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',
},
},
}
+11 -8
View File
@@ -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',
},
},
}
+12 -6
View File
@@ -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' },
},
}
+11 -5
View File
@@ -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' },
},
}
+11 -5
View File
@@ -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' },
},
}
+4 -10
View File
@@ -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 -8
View File
@@ -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,
-27
View File
@@ -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',
+2 -4
View File
@@ -6,12 +6,10 @@ export type BYOKProviderId =
| 'anthropic'
| 'google'
| 'mistral'
| 'exa'
| 'browser_use'
| 'serper'
| 'firecrawl'
| 'exa'
| 'serper'
| 'jina'
| 'elevenlabs'
| 'perplexity'
| 'google_cloud'
| 'linkup'