Files
WeHub Mirror 6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (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 / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

289 lines
9.8 KiB
TypeScript

import { createLogger } from '@sim/logger'
import { stripVersionSuffix } from '@sim/utils/string'
import {
normalizeRecord,
normalizeStringRecord,
normalizeWorkflowVariables,
} from '@/lib/core/utils/records'
import type { EnvironmentVariable } from '@/lib/environment/api'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import type { CustomToolDefinition } from '@/hooks/queries/custom-tools'
import { environmentKeys } from '@/hooks/queries/environment'
import { tools } from '@/tools/registry'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ToolsUtils')
/**
* Strips version suffix (_v2, _v3, etc.) from a tool ID or name.
* Re-exported from the canonical `@sim/utils/string` helper so existing
* `@/tools/utils` consumers keep working unchanged.
* @example stripVersionSuffix('notion_search_v2') => 'notion_search'
* @example stripVersionSuffix('github_create_pr_v3') => 'github_create_pr'
*/
export { stripVersionSuffix } from '@sim/utils/string'
/** Materialized HTTP request accepted by the legacy executeRequest helper. */
export interface RequestParams {
url: string
method: string
headers: Record<string, string>
body?: string
timeout?: number
proxyUrl?: string
stripAuthOnRedirect?: boolean
}
/**
* Filters a tools map to return only the latest version of each tool.
* If both `notion_search` and `notion_search_v2` exist, only `notion_search_v2` is returned.
* @param toolsMap Record of tool ID to ToolConfig
* @returns Filtered record containing only the latest version of each tool
*/
export function getLatestVersionTools(
toolsMap: Record<string, ToolConfig>
): Record<string, ToolConfig> {
const latestTools: Record<string, ToolConfig> = {}
const baseNameToVersions: Record<string, { toolId: string; version: number }[]> = {}
for (const toolId of Object.keys(toolsMap)) {
const baseName = stripVersionSuffix(toolId)
const versionMatch = toolId.match(/_v(\d+)$/)
const version = versionMatch ? Number.parseInt(versionMatch[1], 10) : 1
if (!baseNameToVersions[baseName]) {
baseNameToVersions[baseName] = []
}
baseNameToVersions[baseName].push({ toolId, version })
}
for (const versions of Object.values(baseNameToVersions)) {
const latest = versions.reduce((prev, curr) => (curr.version > prev.version ? curr : prev))
latestTools[latest.toolId] = toolsMap[latest.toolId]
}
return latestTools
}
/**
* Resolves a tool name to its actual tool ID in the registry.
* Handles both stripped names (e.g., 'notion_search') and versioned names (e.g., 'notion_search_v2').
*
* Server-side counterpart to `resolveToolId` in `@/tools/tool-ids`. Both exist
* deliberately: this one reads the live registry, so a tool added but not yet
* regenerated stays resolvable; that one resolves against the generated id list
* without pulling 4,300 tools into a client graph. Client code wants that one.
* `tool-metadata:check` asserts the two never diverge.
*
* @param toolName The tool name to resolve (may or may not have version suffix)
* @returns The actual tool ID in the registry, or the original name if not found
*/
export function resolveToolId(toolName: string): string {
if (tools[toolName]) {
return toolName
}
const latestTools = getLatestVersionTools(tools)
for (const toolId of Object.keys(latestTools)) {
if (stripVersionSuffix(toolId) === toolName) {
return toolId
}
}
return toolName
}
/**
* Formats a parameter name for user-friendly error messages
* Converts parameter names and descriptions to more readable format
*/
function formatParameterNameForError(paramName: string): string {
// Split camelCase and snake_case/kebab-case into words, then capitalize first letter of each word
return paramName
.split(/(?=[A-Z])|[_-]/)
.filter(Boolean)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')
}
/**
* Validates required parameters after LLM and user params have been merged
* This is the final validation before tool execution - ensures all required
* user-or-llm parameters are present after the merge process
*/
export function validateRequiredParametersAfterMerge(
toolId: string,
tool: ToolConfig | undefined,
params: Record<string, any>,
parameterNameMap?: Record<string, string>
): void {
if (!tool) {
throw new Error(`Tool not found: ${toolId}`)
}
// Validate all required user-or-llm parameters after merge
// user-only parameters should have been validated earlier during serialization
for (const [paramName, paramConfig] of Object.entries(tool.params)) {
if (
(paramConfig as any).visibility === 'user-or-llm' &&
paramConfig.required &&
(!(paramName in params) ||
params[paramName] === null ||
params[paramName] === undefined ||
params[paramName] === '')
) {
// Create a more user-friendly error message
const toolName = tool.name || toolId
const friendlyParamName =
parameterNameMap?.[paramName] || formatParameterNameForError(paramName)
throw new Error(`${friendlyParamName} is required for ${toolName}`)
}
}
}
/**
* Creates parameter schema from custom tool schema
*/
export function createParamSchema(customTool: any): Record<string, any> {
const params: Record<string, any> = {}
if (customTool.schema.function?.parameters?.properties) {
const properties = customTool.schema.function.parameters.properties
const required = customTool.schema.function.parameters.required || []
Object.entries(properties).forEach(([key, config]: [string, any]) => {
const isRequired = required.includes(key)
// Create the base parameter configuration
const paramConfig: Record<string, any> = {
type: config.type || 'string',
required: isRequired,
description: config.description || '',
}
// Set visibility based on whether it's required
if (isRequired) {
paramConfig.visibility = 'user-or-llm'
} else {
paramConfig.visibility = 'user-only'
}
params[key] = paramConfig
})
}
return params
}
/**
* Get environment variables from React Query cache (client-side only)
*/
export function getClientEnvVars(): Record<string, string> {
if (typeof window === 'undefined') return {}
try {
const allEnvVars =
getQueryClient().getQueryData<Record<string, EnvironmentVariable>>(
environmentKeys.personal()
) ?? {}
// Convert environment variables to a simple key-value object
return Object.entries(allEnvVars).reduce(
(acc, [key, variable]) => {
acc[key] = variable.value
return acc
},
{} as Record<string, string>
)
} catch (_error) {
// In case of any errors (like in testing), return empty object
return {}
}
}
/**
* Creates the request body configuration for custom tools
* @param customTool The custom tool configuration
* @param isClient Whether running on client side
* @param workflowId Optional workflow ID for server-side
*/
export function createCustomToolRequestBody(customTool: any, isClient = true, workflowId?: string) {
return (params: Record<string, any>) => {
// Get environment variables - try multiple sources in order of preference:
// 1. envVars parameter (passed from provider/agent context)
// 2. Client-side store (if running in browser)
// 3. Empty object (fallback)
const envVars = normalizeStringRecord(params.envVars || (isClient ? getClientEnvVars() : {}))
const workflowVariables = normalizeWorkflowVariables(params.workflowVariables)
const blockData = normalizeRecord(params.blockData)
const blockNameMapping = normalizeStringRecord(params.blockNameMapping)
// Include everything needed for execution
return {
code: customTool.code,
params: params, // These will be available in the VM context
schema: customTool.schema.function.parameters, // For validation
envVars: envVars, // Environment variables
workflowVariables: workflowVariables, // Workflow variables for <variable.name> resolution
blockData: blockData, // Runtime block outputs for <block.field> resolution
blockNameMapping: blockNameMapping, // Block name to ID mapping
workflowId: params._context?.workflowId || workflowId, // Pass workflowId for server-side context
userId: params._context?.userId, // Pass userId for auth context
isCustomTool: true, // Flag to indicate this is a custom tool execution
}
}
}
// Get a tool by its ID
export function getTool(toolId: string, _workspaceId?: string): ToolConfig | undefined {
// Check for built-in tools
const builtInTool = tools[resolveToolId(toolId)]
if (builtInTool) return builtInTool
// If not found or running on the server, return undefined
return undefined
}
// Helper function to create a tool config from a custom tool
export function createToolConfig(
customTool: CustomToolDefinition,
customToolId: string
): ToolConfig {
// Create a parameter schema from the custom tool schema
const params = createParamSchema(customTool)
// Create a tool config for the custom tool
return {
id: customToolId,
name: customTool.title,
description: customTool.schema.function?.description || '',
version: '1.0.0',
params,
// Request configuration - for custom tools we'll use the execute endpoint
request: {
url: '/api/function/execute',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: createCustomToolRequestBody(customTool, true),
},
// Standard response handling for custom tools
transformResponse: async (response: Response) => {
const data = await response.json()
if (!data.success) {
throw new Error(data.error || 'Custom tool execution failed')
}
return {
success: true,
output: data.output.result || data.output,
error: undefined,
}
},
}
}