From a041474a7b237c93beba5f4948be0d231592f46f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 14 Aug 2026 14:27:12 -0700 Subject: [PATCH 1/6] refactor: consolidate local isRecord guards onto shared isRecordLike Nineteen files had re-declared a local `isRecord` guard rather than using the shared one from `@sim/utils/object`, drift that reappeared after #5061 first consolidated them. Two more imported the shared guard under an `isRecordLike as isRecord` alias. The copies were not interchangeable. Nine matched `isRecordLike` exactly. The rest omitted the array exclusion (`typeof x === 'object' && x !== null`, or `Boolean(x) && typeof x === 'object'`), so arrays passed the guard. Each of those call sites was reviewed individually: in every case the guard is followed by string/number field checks that an array fails anyway, so the outcome is unchanged. The one exception is `isOptionsTagData`, where `Object.values` on an array of option items really did make an array-form `` tag render. It now accepts arrays explicitly rather than by accident. `executor/handlers/pi/search/extension-source.ts` keeps its own copy: it is source text written into an E2B/Daytona sandbox at runtime and cannot import. --- .../components/special-tags/special-tags.tsx | 28 +++++++------- .../home/hooks/stream/turn-model.ts | 14 +++---- .../hooks/use-workflow-execution.ts | 18 ++++----- .../lib/copy/cleanup-failed.ts | 9 +++-- .../workspace-forking/lib/copy/copy-chats.ts | 4 +- .../lib/copy/copy-resources.ts | 7 ++-- .../lib/mapping/dependent-reconfigs.ts | 7 ++-- .../lib/promote/cleared-refs.ts | 6 +-- .../lib/remap/remap-references.ts | 35 ++++++++++-------- .../handlers/pi/cloud/authoring/backend.ts | 7 ++-- .../handlers/pi/cloud/babysit/github.ts | 22 +++++------ .../executor/handlers/pi/cloud/github-pr.ts | 8 ++-- .../handlers/pi/cloud/review/backend.ts | 5 ++- .../executor/handlers/pi/search/normalize.ts | 11 ++---- apps/sim/hooks/queries/organization.ts | 11 ++---- .../lib/logs/execution/progress-markers.ts | 6 +-- apps/sim/lib/webhooks/providers/whatsapp.ts | 37 +++++++++---------- .../persistence/remap-internal-ids.ts | 10 ++--- .../lib/workflows/search-replace/indexer.ts | 10 ++--- .../workflows/search-replace/value-walker.ts | 11 ++---- .../streaming/agent-stream-protocol.ts | 21 +++++------ apps/sim/providers/openai/utils.ts | 11 ++---- apps/sim/providers/stream-events.ts | 7 +--- .../function-sandbox-parity-manifest.ts | 7 +--- apps/sim/tools/file/parser.ts | 30 +++++++-------- apps/sim/tools/github/create_pr_review.ts | 6 +-- apps/sim/tools/github/graphql.ts | 11 +++--- apps/sim/tools/github/list_review_threads.ts | 21 ++++++----- apps/sim/tools/github/pr.ts | 12 +++--- apps/sim/tools/github/reply_review_thread.ts | 7 ++-- .../sim/tools/github/resolve_review_thread.ts | 7 ++-- apps/sim/tools/github/response-parsers.ts | 9 ++--- apps/sim/tools/github/status_check_rollup.ts | 12 +++--- apps/sim/tools/google_forms/utils.ts | 9 ++--- apps/sim/tools/tiktok/utils.ts | 7 +--- apps/sim/tools/whatsapp/utils.ts | 15 +++----- 36 files changed, 213 insertions(+), 245 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx index 0562ce7c616..3dab7328451 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx @@ -14,6 +14,7 @@ import { toast, } from '@sim/emcn' import { TerminalWindow } from '@sim/emcn/icons' +import { isRecordLike } from '@sim/utils/object' import { useParams } from 'next/navigation' import { ThinkingLoader } from '@/components/ui' import { useSession } from '@/lib/auth/auth-client' @@ -353,22 +354,23 @@ export const SPECIAL_TAG_NAMES = [ 'question', ] as const -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} - function isOptionsItemData(value: unknown): value is OptionsItemData { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return typeof value.title === 'string' && typeof value.description === 'string' } +/** + * Arrays are accepted alongside keyed objects: an agent that emits + * `[{title,description},…]` still renders, with the array + * index standing in as the option key. + */ function isOptionsTagData(value: unknown): value is OptionsTagData { - if (!isRecord(value)) return false + if (!isRecordLike(value) && !Array.isArray(value)) return false return Object.values(value).every(isOptionsItemData) } function isUsageUpgradeTagData(value: unknown): value is UsageUpgradeTagData { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return ( typeof value.reason === 'string' && typeof value.message === 'string' && @@ -378,7 +380,7 @@ function isUsageUpgradeTagData(value: unknown): value is UsageUpgradeTagData { } function isCredentialItemData(value: unknown): value is CredentialItemData { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false if ( typeof value.type !== 'string' || !(CREDENTIAL_TAG_TYPES as readonly string[]).includes(value.type) @@ -452,7 +454,7 @@ export function parseLastCredentialTag(content: string): CredentialTagData | nul } function isMothershipErrorTagData(value: unknown): value is MothershipErrorTagData { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return ( typeof value.message === 'string' && (value.code === undefined || typeof value.code === 'string') && @@ -461,7 +463,7 @@ function isMothershipErrorTagData(value: unknown): value is MothershipErrorTagDa } function isWorkspaceResourceTagData(value: unknown): value is WorkspaceResourceTagData { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false if ( typeof value.type !== 'string' || !(WORKSPACE_RESOURCE_TAG_TYPES as readonly string[]).includes(value.type) @@ -479,7 +481,7 @@ function isWorkspaceResourceTagData(value: unknown): value is WorkspaceResourceT } function isQuestionOption(value: unknown): value is QuestionOption { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return typeof value.id === 'string' && typeof value.label === 'string' } @@ -497,7 +499,7 @@ const SELF_PROVIDED_OPTION_LABELS = new Set([ ]) function isQuestionItem(value: unknown): value is QuestionItem { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false if ( typeof value.type !== 'string' || !(QUESTION_TYPES as readonly string[]).includes(value.type) @@ -551,7 +553,7 @@ function recoverQuestionPrompts(body: string): string | null { const parsed = JSON.parse(body) as unknown const items = Array.isArray(parsed) ? parsed : [parsed] const prompts = items - .filter(isRecord) + .filter(isRecordLike) .map((item) => (typeof item.prompt === 'string' ? item.prompt.trim() : '')) .filter((prompt) => prompt.length > 0) return prompts.length > 0 ? prompts.join('\n\n') : null diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.ts index 6ac6b32e3f3..9139fe504ee 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/stream/turn-model.ts @@ -1,4 +1,4 @@ -import { isRecordLike as isRecord } from '@sim/utils/object' +import { isRecordLike } from '@sim/utils/object' import { resolveStreamToolOutcome } from '@/lib/copilot/chat/stream-tool-outcome' import { MothershipStreamV1CompletionStatus, @@ -220,10 +220,10 @@ function rebindResolvedIntegrationCall(node: ToolNode, toolName: string): void { /** * Reads a wire event payload as a generic record. The payload is a wide * discriminated union; the reducer accesses fields uniformly, so this narrows - * through the `unknown`-typed {@link isRecord} guard rather than a double cast. + * through the `unknown`-typed {@link isRecordLike} guard rather than a double cast. */ function payloadRecord(payload: unknown): Record { - return isRecord(payload) ? payload : {} + return isRecordLike(payload) ? payload : {} } /** Parses a wire `ts` to epoch ms, or undefined when absent/unparseable. */ @@ -523,7 +523,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve // back into an ordinary running row without waiting for the result. node.status = 'running' } - if (isRecord(payload.arguments)) node.args = payload.arguments + if (isRecordLike(payload.arguments)) node.args = payload.arguments // Only the snapshot-replay path (contentBlocksToModel) carries this // field — the live wire never does; it restores the rebound gateway // description across a preserve-state rebuild. @@ -531,7 +531,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve if (restoredDescription) node.integrationDescription = restoredDescription // Tool-call titles are derived from the tool name (+args) at serialize // time; the stream only carries behavioral flags now. - const ui = isRecord(payload.ui) ? payload.ui : undefined + const ui = isRecordLike(payload.ui) ? payload.ui : undefined if (ui?.hidden === true) node.hidden = true } else if (phase === MothershipStreamV1ToolPhase.args_delta) { const node = upsertToolNode( @@ -559,7 +559,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve case MothershipStreamV1EventType.span: { const payload = envelope.payload if (payload.kind !== MothershipStreamV1SpanPayloadKind.subagent) break - const data = isRecord(payload.data) ? payload.data : undefined + const data = isRecordLike(payload.data) ? payload.data : undefined const triggerToolCallId = scope?.parentToolCallId ?? asString(data?.tool_call_id) ?? asString(data?.toolCallId) const agentId = asString(payload.agent) ?? scope?.agentId ?? '' @@ -686,7 +686,7 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve const payload = payloadRecord(envelope.payload) // An async pause is not a turn terminal — the paused tools/subagents // legitimately stay open until a later resume leg completes them. - const response = isRecord(payload.response) ? payload.response : undefined + const response = isRecordLike(payload.response) ? payload.response : undefined if (response && 'async_pause' in response) break const status = payload.status if (status === MothershipStreamV1CompletionStatus.cancelled) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts index 97fb6a3f507..f999a2294ee 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts @@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage, toError } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'next/navigation' import { useShallow } from 'zustand/react/shallow' @@ -132,10 +133,6 @@ async function persistExecutionPointerProgress( await saveExecutionPointer({ workflowId, executionId, lastEventId }) } -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} - function isRecoverableStreamRecoveryError( error: unknown ): error is SSEEventHandlerError | SSEStreamInterruptedError { @@ -158,12 +155,12 @@ function normalizeErrorMessage(error: unknown): string { if (message) return message } - if (isRecord(error)) { + if (isRecordLike(error)) { const directMessage = sanitizeMessage(error.message) if (directMessage) return directMessage const nestedError = error.error - if (isRecord(nestedError)) { + if (isRecordLike(nestedError)) { const nestedMessage = sanitizeMessage(nestedError.message) if (nestedMessage) return nestedMessage } else { @@ -181,7 +178,7 @@ interface ChatWorkflowInput { } function isChatWorkflowInput(value: unknown): value is ChatWorkflowInput { - return isRecord(value) && 'input' in value + return isRecordLike(value) && 'input' in value } export interface ChatWorkflowRunResult { @@ -199,7 +196,7 @@ export class WorkflowAttachmentUploadError extends Error { export function isChatWorkflowRunResult(value: unknown): value is ChatWorkflowRunResult { return ( - isRecord(value) && + isRecordLike(value) && value.success === true && value.stream instanceof ReadableStream && Array.isArray(value.uploadedAttachments) @@ -1688,10 +1685,11 @@ export function useWorkflowExecution() { } let notificationMessage = WORKFLOW_EXECUTION_FAILURE_MESSAGE - const requestError = isRecord(error) && isRecord(error.request) ? error.request : undefined + const requestError = + isRecordLike(error) && isRecordLike(error.request) ? error.request : undefined if (requestError && sanitizeMessage(requestError.url)) { notificationMessage += `: Request to ${(requestError.url as string).trim()} failed` - if (isRecord(error) && typeof error.status === 'number') { + if (isRecordLike(error) && typeof error.status === 'number') { notificationMessage += ` (Status: ${error.status})` } } else if (sanitizeMessage(errorResult.error)) { diff --git a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts index a33abf5d12b..a28aed94fe8 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts @@ -9,8 +9,9 @@ import { } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { and, asc, eq, exists, gt, inArray, isNull, notExists, sql } from 'drizzle-orm' -import { isRecord, type SubBlockRecord } from '@/lib/workflows/persistence/remap-internal-ids' +import type { SubBlockRecord } from '@/lib/workflows/persistence/remap-internal-ids' import { invalidateDeployedStateCache } from '@/lib/workflows/persistence/utils' import { FORK_DOCUMENT_ID_PATTERN, @@ -325,13 +326,13 @@ export function rewriteDeploymentVersionState( state: unknown, resolve: ForkCopyResolver ): { state: unknown; changed: boolean } { - if (!isRecord(state) || !isRecord(state.blocks)) return { state, changed: false } + if (!isRecordLike(state) || !isRecordLike(state.blocks)) return { state, changed: false } let nextBlocks: Record | null = null for (const [blockId, block] of Object.entries(state.blocks)) { - if (!isRecord(block)) continue + if (!isRecordLike(block)) continue const blockType = typeof block.type === 'string' ? block.type : undefined - if (!blockType || !isRecord(block.subBlocks)) continue + if (!blockType || !isRecordLike(block.subBlocks)) continue const { subBlocks: cleared, changed } = clearFailedSubBlockReferences( block.subBlocks as SubBlockRecord, blockType, diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts index bf4eeebe9f4..312f765c641 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts @@ -1,10 +1,10 @@ import { chat } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId, generateShortId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { randomInt } from '@sim/utils/random' import { and, inArray, isNull } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' -import { isRecord } from '@/lib/workflows/persistence/remap-internal-ids' const logger = createLogger('WorkspaceForkCopyChats') @@ -47,7 +47,7 @@ function remapChatOutputConfigs( ): unknown { if (!Array.isArray(value)) return value return value.map((entry) => { - if (!isRecord(entry) || typeof entry.blockId !== 'string') return entry + if (!isRecordLike(entry) || typeof entry.blockId !== 'string') return entry return { ...entry, blockId: resolveBlockId(targetWorkflowId, entry.blockId) } }) } diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts index 94f9d9402d9..991b1fd7a1d 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts @@ -20,7 +20,7 @@ import { createLogger } from '@sim/logger' import { sha256Hex } from '@sim/security/hash' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' -import { omit } from '@sim/utils/object' +import { isRecordLike, omit } from '@sim/utils/object' import { and, asc, @@ -71,7 +71,6 @@ import { recordKnowledgeBaseFileOwnership, } from '@/lib/uploads/server/metadata' import { MAX_FILE_SIZE } from '@/lib/uploads/utils/validation' -import { isRecord } from '@/lib/workflows/persistence/remap-internal-ids' import { deleteCopiedResourceMappingsByTargets, type ForkMappingUpsert, @@ -538,7 +537,7 @@ export async function copyForkResourceContainers( const inserts: (typeof mcpServers.$inferInsert)[] = [] for (const row of rows) { const childId = generateId() - const headers = isRecord(row.headers) + const headers = isRecordLike(row.headers) ? Object.fromEntries( Object.entries(row.headers).map(([key, value]) => [ key, @@ -942,7 +941,7 @@ function remapTableRowResourceUrls(value: unknown, maps: ForkContentRefMaps): un }) return changed ? next : value } - if (isRecord(value)) { + if (isRecordLike(value)) { let changed = false const next: Record = {} for (const [key, item] of Object.entries(value)) { diff --git a/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts b/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts index f5684cd9b26..4a4bbbf2a5e 100644 --- a/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts @@ -1,5 +1,6 @@ +import { isRecordLike } from '@sim/utils/object' import type { ForkDependentReconfig, ForkResourceUsage } from '@/lib/api/contracts/workspace-fork' -import { coerceObjectArray, isRecord } from '@/lib/workflows/persistence/remap-internal-ids' +import { coerceObjectArray } from '@/lib/workflows/persistence/remap-internal-ids' import { getWorkflowSearchDependentClears } from '@/lib/workflows/search-replace/dependencies' import { buildSelectorContextFromBlock, @@ -293,10 +294,10 @@ export function collectForkDependentReconfigs( if (!tools) continue for (let index = 0; index < tools.length; index++) { const tool = tools[index] - if (!isRecord(tool) || typeof tool.type !== 'string') continue + if (!isRecordLike(tool) || typeof tool.type !== 'string') continue const toolConfig = getBlock(tool.type) if (!toolConfig) continue - const toolParams = isRecord(tool.params) ? tool.params : {} + const toolParams = isRecordLike(tool.params) ? tool.params : {} // A tool's `operation` is stored at the tool level, not in params, but subblock // conditions reference it (e.g. a Gmail label only under `read_gmail`). Merge it // in so condition-gating matches the editor's `{ operation, ...params }`. diff --git a/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts b/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts index 540c2e10e84..a3776dc2ccb 100644 --- a/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts @@ -1,4 +1,5 @@ import { mcpServers, workflow } from '@sim/db/schema' +import { isRecordLike } from '@sim/utils/object' import { and, eq, inArray } from 'drizzle-orm' import type { ForkClearedRef, @@ -8,7 +9,6 @@ import type { import type { DbOrTx } from '@/lib/db/types' import { coerceObjectArray, - isRecord, type SubBlockRecord, } from '@/lib/workflows/persistence/remap-internal-ids' import { @@ -136,9 +136,9 @@ function collectForkWorkflowReferences( if (!array) continue for (const tool of array) { if ( - isRecord(tool) && + isRecordLike(tool) && tool.type === 'workflow_input' && - isRecord(tool.params) && + isRecordLike(tool.params) && typeof tool.params.workflowId === 'string' && tool.params.workflowId ) { diff --git a/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts index 9dfb419f3ac..f422496a4be 100644 --- a/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts @@ -1,13 +1,12 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { omit } from '@sim/utils/object' +import { isRecordLike, omit } from '@sim/utils/object' import type { SubBlockType } from '@sim/workflow-types/blocks' import type { z } from 'zod' import type { forkRemapKindSchema } from '@/lib/api/contracts/workspace-fork' import { createMcpToolId } from '@/lib/mcp/shared' import { coerceObjectArray, - isRecord, type SubBlockRecord, } from '@/lib/workflows/persistence/remap-internal-ids' import { CREDENTIAL_SUBBLOCK_IDS } from '@/lib/workflows/persistence/utils' @@ -345,7 +344,7 @@ function remapEnvInValue( } // Recurse plain objects so `{{ENV}}` nested in array-form tool params (and other // object-valued subblocks) is rewritten, not just top-level strings/arrays. - if (isRecord(value)) { + if (isRecordLike(value)) { let changed = false const next: Record = {} for (const [key, nested] of Object.entries(value)) { @@ -398,7 +397,7 @@ export function remapToolBlockResources( ): Record { if (typeof tool.type !== 'string') return tool const params = tool.params - if (!isRecord(params)) return tool + if (!isRecordLike(params)) return tool let nextParams: Record | null = null const setParam = (paramId: string, value: unknown) => { @@ -678,7 +677,7 @@ function remapForkToolInputValue( next.push(nextTool) } - if (!isRecord(tool) || typeof tool.type !== 'string') { + if (!isRecordLike(tool) || typeof tool.type !== 'string') { keep(tool) return } @@ -701,7 +700,11 @@ function remapForkToolInputValue( keep(tool) return } - if (tool.type === 'mcp' && isRecord(tool.params) && typeof tool.params.serverId === 'string') { + if ( + tool.type === 'mcp' && + isRecordLike(tool.params) && + typeof tool.params.serverId === 'string' + ) { const serverId = tool.params.serverId const target = resolve('mcp-server', serverId) opts.record?.('mcp-server', serverId, target != null) @@ -774,7 +777,7 @@ function remapForkSkillInputValue( if (!array) return value let changed = false const next = array.flatMap((entry) => { - if (!isRecord(entry) || typeof entry.skillId !== 'string') return [entry] + if (!isRecordLike(entry) || typeof entry.skillId !== 'string') return [entry] if (entry.skillId.startsWith('builtin-')) return [entry] const target = resolve('skill', entry.skillId) opts.record?.('skill', entry.skillId, target != null) @@ -1221,12 +1224,12 @@ function collectClearedToolParamDependents( for (let index = 0; index < mergedTools.length; index++) { const tool = mergedTools[index] const targetTool = targetTools[index] - if (!isRecord(tool) || typeof tool.type !== 'string') continue - if (!isRecord(targetTool) || targetTool.type !== tool.type) continue + if (!isRecordLike(tool) || typeof tool.type !== 'string') continue + if (!isRecordLike(targetTool) || targetTool.type !== tool.type) continue const toolConfig = getBlock(tool.type) if (!toolConfig) continue - const targetParams = isRecord(targetTool.params) ? targetTool.params : {} - const mergedParams = isRecord(tool.params) ? tool.params : {} + const targetParams = isRecordLike(targetTool.params) ? targetTool.params : {} + const mergedParams = isRecordLike(tool.params) ? tool.params : {} // A tool's `operation` lives at the tool level, not in params, but conditions // reference it - merge it in so condition/required gating matches the editor. const mergedValues = @@ -1364,10 +1367,10 @@ export function readTargetDraftDependentValue( if (nested) { const { toolInputId, index, paramId } = nested const targetTool = coerceObjectArray(targetDraftSubBlocks[toolInputId]?.value).array?.[index] - if (!isRecord(targetTool) || typeof targetTool.type !== 'string') return '' + if (!isRecordLike(targetTool) || typeof targetTool.type !== 'string') return '' const sourceTool = coerceObjectArray(sourceSubBlocks?.[toolInputId]?.value).array?.[index] - if (!isRecord(sourceTool) || sourceTool.type !== targetTool.type) return '' - const params = isRecord(targetTool.params) ? targetTool.params : {} + if (!isRecordLike(sourceTool) || sourceTool.type !== targetTool.type) return '' + const params = isRecordLike(targetTool.params) ? targetTool.params : {} const value = params[paramId] return typeof value === 'string' ? value : '' } @@ -1395,7 +1398,7 @@ function applyNestedToolOverrides( const merged = array.map((tool, index) => { const forTool = items.filter((item) => item.index === index) if (forTool.length === 0) return tool - if (!isRecord(tool) || typeof tool.type !== 'string') return tool + if (!isRecordLike(tool) || typeof tool.type !== 'string') return tool const toolConfig = getBlock(tool.type) if (!toolConfig) return tool const allowed = new Set( @@ -1403,7 +1406,7 @@ function applyNestedToolOverrides( .filter((cfg) => cfg.id && cfg.dependsOn && cfg.selectorKey) .map((cfg) => cfg.id) ) - const params = isRecord(tool.params) ? tool.params : {} + const params = isRecordLike(tool.params) ? tool.params : {} let nextParams: Record | null = null for (const item of forTool) { if (!allowed.has(item.paramId)) continue diff --git a/apps/sim/executor/handlers/pi/cloud/authoring/backend.ts b/apps/sim/executor/handlers/pi/cloud/authoring/backend.ts index fa2a7bcde13..d874c6ab477 100644 --- a/apps/sim/executor/handlers/pi/cloud/authoring/backend.ts +++ b/apps/sim/executor/handlers/pi/cloud/authoring/backend.ts @@ -20,6 +20,7 @@ import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import { getMaxExecutionTimeout, getRemainingExecutionMs } from '@/lib/core/execution-limits' import { withPiSandbox } from '@/lib/execution/remote-sandbox' @@ -81,7 +82,7 @@ import { } from '@/executor/handlers/pi/search/extension-source' import { getPiProviderId } from '@/providers/pi-providers' import { executeTool } from '@/tools' -import { isRecord, requiredRecord, requiredTrimmedString } from '@/tools/github/response-parsers' +import { requiredRecord, requiredTrimmedString } from '@/tools/github/response-parsers' const logger = createLogger('PiCloudBackend') @@ -187,7 +188,7 @@ async function openPullRequest( throw new Error(`PR creation failed for branch ${branch}: ${result.error ?? 'unknown error'}`) } - if (!isRecord(result.output)) { + if (!isRecordLike(result.output)) { throw new Error(`PR creation returned an invalid response for branch ${branch}`) } const metadata = requiredRecord(result.output, 'metadata', 'GitHub create pull request response') @@ -224,7 +225,7 @@ async function repositoryDefaultBranch( `Failed to determine the repository default branch: ${result.error ?? 'unknown error'}` ) } - if (!isRecord(result.output)) { + if (!isRecordLike(result.output)) { throw new Error('GitHub repository response must be an object') } return requiredTrimmedString(result.output, 'default_branch', 'GitHub repository response') diff --git a/apps/sim/executor/handlers/pi/cloud/babysit/github.ts b/apps/sim/executor/handlers/pi/cloud/babysit/github.ts index 5113746c80b..30556c8acaf 100644 --- a/apps/sim/executor/handlers/pi/cloud/babysit/github.ts +++ b/apps/sim/executor/handlers/pi/cloud/babysit/github.ts @@ -1,4 +1,5 @@ import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import type { BabysitRoundDecision } from '@/executor/handlers/pi/cloud/babysit/round' import { @@ -10,7 +11,6 @@ import { import { scrubPiSecrets } from '@/executor/handlers/pi/core/redaction' import { executeTool } from '@/tools' import { - isRecord, nullableNumber, nullableString, requiredBoolean, @@ -205,12 +205,12 @@ function toolFailure(label: string, error: unknown): Error { } function parseReviewThread(value: unknown, index: number): ReviewThread { - if (!isRecord(value)) throw new Error(`Review thread ${index} must be an object`) + if (!isRecordLike(value)) throw new Error(`Review thread ${index} must be an object`) const commentsValue = value.comments if (!Array.isArray(commentsValue)) throw new Error(`Review thread ${index}.comments must be an array`) const comments = commentsValue.map((comment, commentIndex) => { - if (!isRecord(comment)) { + if (!isRecordLike(comment)) { throw new Error(`Review thread ${index}.comments[${commentIndex}] must be an object`) } return { @@ -244,7 +244,7 @@ function parseReviewThread(value: unknown, index: number): ReviewThread { function parseLatestReview(value: unknown): SubmittedReviewSummary | null { if (value === null) return null - if (!isRecord(value)) throw new Error('latestReview must be an object or null') + if (!isRecordLike(value)) throw new Error('latestReview must be an object or null') return { state: requiredString(value, 'state', 'latestReview'), submittedAt: requiredString(value, 'submittedAt', 'latestReview'), @@ -290,7 +290,7 @@ export async function fetchBabysitThreads( ) if (!result.success) throw toolFailure('Failed to fetch review threads', result.error) const output = result.output - if (!isRecord(output) || !Array.isArray(output.threads)) { + if (!isRecordLike(output) || !Array.isArray(output.threads)) { throw new Error('Review thread response is incomplete') } const totalCount = requiredNumber(output, 'totalCount', 'Review thread response') @@ -365,7 +365,7 @@ function normalizeCheck(context: StatusCheckRollupContext): BabysitCheck { } function parseCheckContext(value: unknown, index: number): StatusCheckRollupContext { - if (!isRecord(value)) throw new Error(`Check context ${index} must be an object`) + if (!isRecordLike(value)) throw new Error(`Check context ${index} must be an object`) const type = requiredString(value, '__typename', `Check context ${index}`) if (type === 'CheckRun') { return { @@ -425,7 +425,7 @@ export async function fetchBabysitCheckState( ) if (!result.success) throw toolFailure('Failed to fetch checks', result.error) const output = result.output - if (!isRecord(output) || !Array.isArray(output.contexts)) { + if (!isRecordLike(output) || !Array.isArray(output.contexts)) { throw new Error('Check response is incomplete') } const totalCount = requiredNumber(output, 'totalCount', 'Check response') @@ -521,7 +521,7 @@ async function fetchCheckDiagnostic( }, { signal } ) - if (result.success && isRecord(result.output) && typeof result.output.logs === 'string') { + if (result.success && isRecordLike(result.output) && typeof result.output.logs === 'string') { text = result.output.logs } else { // GitHub Actions reports null `title` and `summary` on every check run it @@ -698,7 +698,7 @@ export async function requestBabysitReview( ) if ( result.success && - isRecord(result.output) && + isRecordLike(result.output) && typeof result.output.id === 'number' && Number.isSafeInteger(result.output.id) ) { @@ -750,11 +750,11 @@ export async function babysitReviewLandedSince( }, { signal } ) - if (!result.success || !isRecord(result.output) || !Array.isArray(result.output.items)) { + if (!result.success || !isRecordLike(result.output) || !Array.isArray(result.output.items)) { return false } for (const item of result.output.items) { - if (!isRecord(item) || !isRecord(item.user)) continue + if (!isRecordLike(item) || !isRecordLike(item.user)) continue const id = item.id const createdAt = item.created_at if ( diff --git a/apps/sim/executor/handlers/pi/cloud/github-pr.ts b/apps/sim/executor/handlers/pi/cloud/github-pr.ts index bab384185d0..dc72e46aced 100644 --- a/apps/sim/executor/handlers/pi/cloud/github-pr.ts +++ b/apps/sim/executor/handlers/pi/cloud/github-pr.ts @@ -6,10 +6,10 @@ * repository a credential is pointed at and which commit a write lands on. */ +import { isRecordLike } from '@sim/utils/object' import { executeTool } from '@/tools' import { GITHUB_GRAPHQL_URL, githubGraphQlHeaders, readGraphQlData } from '@/tools/github/graphql' import { - isRecord, nullableBoolean, nullableString, requiredBoolean, @@ -65,7 +65,7 @@ function requiredSha(record: Record, field: string, context: st } export function parsePullRequestSnapshot(value: unknown): PullRequestSnapshot { - if (!isRecord(value)) throw new Error(`${PULL_REQUEST_RESPONSE_CONTEXT} must be an object`) + if (!isRecordLike(value)) throw new Error(`${PULL_REQUEST_RESPONSE_CONTEXT} must be an object`) const head = requiredRecord(value, 'head', PULL_REQUEST_RESPONSE_CONTEXT) const base = requiredRecord(value, 'base', PULL_REQUEST_RESPONSE_CONTEXT) @@ -179,7 +179,7 @@ export async function findOpenPrForBranch( } const output = result.output - if (!isRecord(output)) { + if (!isRecordLike(output)) { throw new Error('GitHub pull request list response.output must be an object') } const items = output.items @@ -193,7 +193,7 @@ export async function findOpenPrForBranch( throw new Error(`Update PR found multiple open pull requests for branch ${params.branch}`) } - if (!isRecord(items[0])) { + if (!isRecordLike(items[0])) { throw new Error('GitHub pull request list response item must be an object') } const pullNumber = requiredNumber(items[0], 'number', 'GitHub pull request list response item') diff --git a/apps/sim/executor/handlers/pi/cloud/review/backend.ts b/apps/sim/executor/handlers/pi/cloud/review/backend.ts index 36c1bb7034e..cfd25533952 100644 --- a/apps/sim/executor/handlers/pi/cloud/review/backend.ts +++ b/apps/sim/executor/handlers/pi/cloud/review/backend.ts @@ -9,6 +9,7 @@ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import { withPiSandbox } from '@/lib/execution/remote-sandbox' import { resolvePiRunLifetimeMs } from '@/lib/execution/remote-sandbox/pi-lifetime' @@ -53,7 +54,7 @@ import { } from '@/executor/handlers/pi/search/normalize' import { getPiProviderId } from '@/providers/pi-providers' import { executeTool } from '@/tools' -import { isRecord, requiredTrimmedString } from '@/tools/github/response-parsers' +import { requiredTrimmedString } from '@/tools/github/response-parsers' import type { ReviewFindings } from '@/tools/github/review-schema' const logger = createLogger('PiCloudReviewBackend') @@ -191,7 +192,7 @@ async function submitReview( } const output: unknown = result.output - if (!isRecord(output)) throw new Error(`${REVIEW_RESPONSE_CONTEXT} must be an object`) + if (!isRecordLike(output)) throw new Error(`${REVIEW_RESPONSE_CONTEXT} must be an object`) if (output.commit_id !== null && output.commit_id !== headSha) { throw new Error('GitHub review response did not match the reviewed commit') } diff --git a/apps/sim/executor/handlers/pi/search/normalize.ts b/apps/sim/executor/handlers/pi/search/normalize.ts index 9bc556d331f..af63a96a7c0 100644 --- a/apps/sim/executor/handlers/pi/search/normalize.ts +++ b/apps/sim/executor/handlers/pi/search/normalize.ts @@ -9,6 +9,7 @@ * holds the two request paths together. */ +import { isRecordLike } from '@sim/utils/object' import type { PiSearchProvider } from '@/executor/handlers/pi/core/keys' /** The tool name Pi sees, in every mode. */ @@ -175,10 +176,6 @@ export function buildPiSearchProviderArgs( } } -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} - function asText(value: unknown): string { return typeof value === 'string' ? value : '' } @@ -258,7 +255,7 @@ export function buildPiSearchResult(fields: { */ function firecrawlRecords(data: unknown): unknown[] { if (Array.isArray(data)) return data - if (!isRecord(data)) return [] + if (!isRecordLike(data)) return [] return Object.values(data).flatMap((value) => (Array.isArray(value) ? value : [])) } @@ -276,7 +273,7 @@ export function normalizePiSearchRecords( for (const record of records) { if (results.length >= limit) break - if (!isRecord(record)) continue + if (!isRecordLike(record)) continue let built: PiSearchResult | undefined switch (provider) { @@ -328,7 +325,7 @@ export function normalizePiSearchRecords( /** Extracts the provider's result records from a normalized-or-raw response payload. */ export function extractPiSearchRecords(provider: PiSearchProvider, payload: unknown): unknown[] { - if (!isRecord(payload)) return [] + if (!isRecordLike(payload)) return [] switch (provider) { case 'exa': return Array.isArray(payload.results) ? payload.results : [] diff --git a/apps/sim/hooks/queries/organization.ts b/apps/sim/hooks/queries/organization.ts index a0ce1295633..b8d9768293b 100644 --- a/apps/sim/hooks/queries/organization.ts +++ b/apps/sim/hooks/queries/organization.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { keepPreviousData, type UseQueryResult, @@ -74,14 +75,10 @@ type OrganizationSubscriptionCandidate = { type OrganizationBillingQueryResult = UseQueryResult -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} - function isOrganizationSubscriptionCandidate( value: unknown ): value is OrganizationSubscriptionCandidate { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return ( typeof value.id === 'string' && typeof value.referenceId === 'string' && @@ -402,8 +399,8 @@ export function useUpdateOrganizationUsageLimit() { queryClient.setQueryData( organizationKeys.billing(organizationId), (old: unknown) => { - if (!isRecord(old) || !isRecord(old.data)) return old - const usage = isRecord(old.data.usage) ? old.data.usage : {} + if (!isRecordLike(old) || !isRecordLike(old.data)) return old + const usage = isRecordLike(old.data.usage) ? old.data.usage : {} const currentUsage = readNumber(old.data.currentUsage) ?? readNumber(usage.current) ?? diff --git a/apps/sim/lib/logs/execution/progress-markers.ts b/apps/sim/lib/logs/execution/progress-markers.ts index 8499f9e9fed..32dcb11547e 100644 --- a/apps/sim/lib/logs/execution/progress-markers.ts +++ b/apps/sim/lib/logs/execution/progress-markers.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { isRecordLike as isRecord } from '@sim/utils/object' +import { isRecordLike } from '@sim/utils/object' import { getRedisClient } from '@/lib/core/config/redis' import { getExecutionReservationTtlMs } from '@/lib/core/execution-limits' import type { ExecutionLastCompletedBlock, ExecutionLastStartedBlock } from '@/lib/logs/types' @@ -167,7 +167,7 @@ function safeJsonParse(raw: string | undefined): unknown { */ function parseStartedMarker(raw: string | undefined): ExecutionLastStartedBlock | undefined { const v = safeJsonParse(raw) - if (!isRecord(v)) return undefined + if (!isRecordLike(v)) return undefined const { blockId, blockName, blockType, startedAt } = v if ( typeof blockId === 'string' && @@ -186,7 +186,7 @@ function parseStartedMarker(raw: string | undefined): ExecutionLastStartedBlock */ function parseCompletedMarker(raw: string | undefined): ExecutionLastCompletedBlock | undefined { const v = safeJsonParse(raw) - if (!isRecord(v)) return undefined + if (!isRecordLike(v)) return undefined const { blockId, blockName, blockType, endedAt, success } = v if ( typeof blockId === 'string' && diff --git a/apps/sim/lib/webhooks/providers/whatsapp.ts b/apps/sim/lib/webhooks/providers/whatsapp.ts index 5bde7ef5de7..a62fec720c4 100644 --- a/apps/sim/lib/webhooks/providers/whatsapp.ts +++ b/apps/sim/lib/webhooks/providers/whatsapp.ts @@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { sha256Hex } from '@sim/security/hash' import { hmacSha256Hex } from '@sim/security/hmac' +import { isRecordLike } from '@sim/utils/object' import { and, eq, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import type { @@ -14,26 +15,22 @@ import type { const logger = createLogger('WebhookProvider:WhatsApp') -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} - function getWhatsAppChanges( body: unknown ): Array<{ field?: string; value: Record }> { - if (!isRecord(body) || !Array.isArray(body.entry)) { + if (!isRecordLike(body) || !Array.isArray(body.entry)) { return [] } const changes: Array<{ field?: string; value: Record }> = [] for (const entry of body.entry) { - if (!isRecord(entry) || !Array.isArray(entry.changes)) { + if (!isRecordLike(entry) || !Array.isArray(entry.changes)) { continue } for (const change of entry.changes) { - if (!isRecord(change) || !isRecord(change.value)) { + if (!isRecordLike(change) || !isRecordLike(change.value)) { continue } @@ -48,7 +45,7 @@ function getWhatsAppChanges( } function normalizeWhatsAppContact(contact: Record) { - const profile = isRecord(contact.profile) ? contact.profile : undefined + const profile = isRecordLike(contact.profile) ? contact.profile : undefined return { wa_id: typeof contact.wa_id === 'string' ? contact.wa_id : undefined, @@ -75,7 +72,7 @@ function extractWhatsAppMedia(message: Record) { return undefined } - const media = isRecord(message[type]) ? (message[type] as Record) : undefined + const media = isRecordLike(message[type]) ? (message[type] as Record) : undefined if (!media) { return undefined } @@ -93,7 +90,7 @@ function normalizeWhatsAppMessage( message: Record, metadata?: Record ) { - const text = isRecord(message.text) ? message.text : undefined + const text = isRecordLike(message.text) ? message.text : undefined const media = extractWhatsAppMedia(message) return { @@ -132,8 +129,8 @@ function normalizeWhatsAppStatus( : undefined, status: typeof status.status === 'string' ? status.status : undefined, timestamp: typeof status.timestamp === 'string' ? status.timestamp : undefined, - conversation: isRecord(status.conversation) ? status.conversation : undefined, - pricing: isRecord(status.pricing) ? status.pricing : undefined, + conversation: isRecordLike(status.conversation) ? status.conversation : undefined, + pricing: isRecordLike(status.pricing) ? status.pricing : undefined, raw: status, } } @@ -270,7 +267,7 @@ export const whatsappHandler: WebhookProviderHandler = { for (const { field, value } of getWhatsAppChanges(body)) { if (Array.isArray(value.messages)) { for (const message of value.messages) { - if (!isRecord(message) || typeof message.id !== 'string') { + if (!isRecordLike(message) || typeof message.id !== 'string') { continue } @@ -280,7 +277,7 @@ export const whatsappHandler: WebhookProviderHandler = { if (Array.isArray(value.statuses)) { for (const status of value.statuses) { - if (!isRecord(status) || typeof status.id !== 'string') { + if (!isRecordLike(status) || typeof status.id !== 'string') { continue } @@ -292,7 +289,7 @@ export const whatsappHandler: WebhookProviderHandler = { if (Array.isArray(value.groups)) { for (const group of value.groups) { - if (!isRecord(group) || typeof group.request_id !== 'string') { + if (!isRecordLike(group) || typeof group.request_id !== 'string') { continue } @@ -309,7 +306,7 @@ export const whatsappHandler: WebhookProviderHandler = { }, async formatInput({ body }: FormatInputContext): Promise { - const payload = isRecord(body) ? body : undefined + const payload = isRecordLike(body) ? body : undefined const contacts: Array<{ wa_id?: string; profile?: { name?: string } }> = [] const messages: Array<{ messageId?: string @@ -339,11 +336,11 @@ export const whatsappHandler: WebhookProviderHandler = { }> = [] for (const { value } of getWhatsAppChanges(body)) { - const metadata = isRecord(value.metadata) ? value.metadata : undefined + const metadata = isRecordLike(value.metadata) ? value.metadata : undefined if (Array.isArray(value.contacts)) { for (const contact of value.contacts) { - if (!isRecord(contact)) { + if (!isRecordLike(contact)) { continue } @@ -353,7 +350,7 @@ export const whatsappHandler: WebhookProviderHandler = { if (Array.isArray(value.messages)) { for (const message of value.messages) { - if (!isRecord(message)) { + if (!isRecordLike(message)) { continue } @@ -363,7 +360,7 @@ export const whatsappHandler: WebhookProviderHandler = { if (Array.isArray(value.statuses)) { for (const status of value.statuses) { - if (!isRecord(status)) { + if (!isRecordLike(status)) { continue } diff --git a/apps/sim/lib/workflows/persistence/remap-internal-ids.ts b/apps/sim/lib/workflows/persistence/remap-internal-ids.ts index 09217823190..2e5213ef178 100644 --- a/apps/sim/lib/workflows/persistence/remap-internal-ids.ts +++ b/apps/sim/lib/workflows/persistence/remap-internal-ids.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { remapConditionBlockIds } from '@/lib/workflows/condition-ids' import { isDynamicHandleSubblock } from '@/lib/workflows/dynamic-handle-topology' import { @@ -26,10 +27,6 @@ const DUPLICATE_STRIPPED_SYSTEM_SUBBLOCK_IDS = new Set( SYSTEM_SUBBLOCK_IDS.filter((id) => id !== 'triggerCredentials') ) -export function isRecord(value: unknown): value is Record { - return Boolean(value && typeof value === 'object' && !Array.isArray(value)) -} - /** Coerce a subblock value that holds a JSON array (stored as an array or a JSON string). */ export function coerceObjectArray(value: unknown): { array: unknown[] | null; wasString: boolean } { if (Array.isArray(value)) return { array: value, wasString: false } @@ -62,7 +59,7 @@ function remapVariableAssignment(value: unknown, varIdMap: Map): if (Array.isArray(value)) { return value.map((item) => remapVariableAssignment(item, varIdMap)) } - if (!isRecord(value)) { + if (!isRecordLike(value)) { return value } const assignment = value as VariableAssignment @@ -300,7 +297,8 @@ function remapWorkflowInputTools( if (!array) return value let changed = false const next = array.flatMap((tool) => { - if (!isRecord(tool) || tool.type !== 'workflow_input' || !isRecord(tool.params)) return [tool] + if (!isRecordLike(tool) || tool.type !== 'workflow_input' || !isRecordLike(tool.params)) + return [tool] const workflowId = tool.params.workflowId if (typeof workflowId !== 'string') return [tool] const mapped = workflowIdMap.get(workflowId) diff --git a/apps/sim/lib/workflows/search-replace/indexer.ts b/apps/sim/lib/workflows/search-replace/indexer.ts index 2d0fdb23b7f..ae94808aa23 100644 --- a/apps/sim/lib/workflows/search-replace/indexer.ts +++ b/apps/sim/lib/workflows/search-replace/indexer.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { DEFAULT_SUBBLOCK_TYPE } from '@sim/workflow-persistence/subblocks' import type { SubBlockType } from '@sim/workflow-types/blocks' import { isWorkflowBlockProtected } from '@sim/workflow-types/workflow' @@ -138,10 +139,6 @@ const TOOL_INPUT_TEXT_EXCLUDED_PATH_KEYS = new Set(['schema']) type WorkflowSearchSubBlockConfig = Pick & Partial type DisplayLabelLeaf = { value: string; path: WorkflowSearchValuePath; fieldTitle?: string } -function isRecord(value: unknown): value is Record { - return value !== null && typeof value === 'object' && !Array.isArray(value) -} - function looksLikeStoredSkillList(value: unknown): boolean { return ( Array.isArray(value) && @@ -315,10 +312,11 @@ function getOptionLabelLeaves( function getMcpDynamicArgEnumLabelLeaves(value: unknown, schema: unknown): DisplayLabelLeaf[] { const parsedValue = typeof value === 'string' ? safeParseJson(value) : value - if (!isRecord(parsedValue) || !isRecord(schema) || !isRecord(schema.properties)) return [] + if (!isRecordLike(parsedValue) || !isRecordLike(schema) || !isRecordLike(schema.properties)) + return [] return Object.entries(schema.properties).flatMap(([paramName, paramSchema]) => { - if (!isRecord(paramSchema) || !Array.isArray(paramSchema.enum)) return [] + if (!isRecordLike(paramSchema) || !Array.isArray(paramSchema.enum)) return [] const selectedValue = parsedValue[paramName] if (selectedValue === undefined || selectedValue === null || selectedValue === '') return [] return [ diff --git a/apps/sim/lib/workflows/search-replace/value-walker.ts b/apps/sim/lib/workflows/search-replace/value-walker.ts index 3f9070fbfd6..4842f7670f7 100644 --- a/apps/sim/lib/workflows/search-replace/value-walker.ts +++ b/apps/sim/lib/workflows/search-replace/value-walker.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { WorkflowSearchValuePath } from '@/lib/workflows/search-replace/types' export interface WalkedStringValue { @@ -6,10 +7,6 @@ export interface WalkedStringValue { originalValue: unknown } -function isRecord(value: unknown): value is Record { - return value !== null && typeof value === 'object' && !Array.isArray(value) -} - export function walkStringValues( value: unknown, path: WorkflowSearchValuePath = [] @@ -26,7 +23,7 @@ export function walkStringValues( return value.flatMap((item, index) => walkStringValues(item, [...path, index])) } - if (isRecord(value)) { + if (isRecordLike(value)) { return Object.entries(value).flatMap(([key, item]) => walkStringValues(item, [...path, key])) } @@ -38,7 +35,7 @@ export function getValueAtPath(value: unknown, path: WorkflowSearchValuePath): u if (Array.isArray(current) && typeof segment === 'number') { return current[segment] } - if (isRecord(current) && typeof segment === 'string') { + if (isRecordLike(current) && typeof segment === 'string') { return current[segment] } return undefined @@ -61,7 +58,7 @@ export function setValueAtPath( return copy } - if (isRecord(value)) { + if (isRecordLike(value)) { if (typeof segment !== 'string') return value return { ...value, diff --git a/apps/sim/lib/workflows/streaming/agent-stream-protocol.ts b/apps/sim/lib/workflows/streaming/agent-stream-protocol.ts index 6d7eb892696..f18ef078638 100644 --- a/apps/sim/lib/workflows/streaming/agent-stream-protocol.ts +++ b/apps/sim/lib/workflows/streaming/agent-stream-protocol.ts @@ -25,6 +25,7 @@ * See docs: workflows/deployment/agent-events. */ +import { isRecordLike } from '@sim/utils/object' import { isToolCallEndStatus, type ToolCallEndStatus } from '@/providers/stream-events' /** Lookup key. Lowercase because HTTP/2 lowercases on the wire; `Headers.get` is case-insensitive either way. */ @@ -112,17 +113,13 @@ export type ChatStreamFrame = | ChatStreamErrorFrame | ChatStreamStreamErrorFrame -function isRecord(value: unknown): value is Record { - return Boolean(value) && typeof value === 'object' -} - /** * Answer text frame: `{ blockId, chunk }` with no `event` discriminator. * Positively defined so thinking/tool/terminal frames can never be appended * into the answer by a client that checks this first. */ export function isChatChunkFrame(value: unknown): value is ChatStreamChunkFrame { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return ( typeof value.blockId === 'string' && typeof value.chunk === 'string' && @@ -132,12 +129,12 @@ export function isChatChunkFrame(value: unknown): value is ChatStreamChunkFrame } export function isChatChunkResetFrame(value: unknown): value is ChatStreamChunkResetFrame { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return value.event === 'chunk_reset' && typeof value.blockId === 'string' } export function isChatThinkingFrame(value: unknown): value is ChatStreamThinkingFrame { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return ( value.event === 'thinking' && typeof value.blockId === 'string' && @@ -146,7 +143,7 @@ export function isChatThinkingFrame(value: unknown): value is ChatStreamThinking } export function isChatToolFrame(value: unknown): value is ChatStreamToolFrame { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return ( value.event === 'tool' && typeof value.blockId === 'string' && @@ -163,17 +160,17 @@ export function isChatToolFrame(value: unknown): value is ChatStreamToolFrame { } export function isChatFinalFrame(value: unknown): value is ChatStreamFinalFrame { - if (!isRecord(value)) return false - return value.event === 'final' && isRecord(value.data) + if (!isRecordLike(value)) return false + return value.event === 'final' && isRecordLike(value.data) } export function isChatErrorFrame(value: unknown): value is ChatStreamErrorFrame { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return value.event === 'error' } export function isChatStreamErrorFrame(value: unknown): value is ChatStreamStreamErrorFrame { - if (!isRecord(value)) return false + if (!isRecordLike(value)) return false return value.event === 'stream_error' } diff --git a/apps/sim/providers/openai/utils.ts b/apps/sim/providers/openai/utils.ts index 93d05f6b9c8..0586bf8741f 100644 --- a/apps/sim/providers/openai/utils.ts +++ b/apps/sim/providers/openai/utils.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type OpenAI from 'openai' import { Stream } from 'openai/streaming' import { buildOpenAIMessageContent } from '@/providers/attachments' @@ -255,12 +256,8 @@ export function toResponsesToolChoice( return 'auto' } -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} - function extractTextFromMessageItem(item: unknown): string { - if (!isRecord(item)) { + if (!isRecordLike(item)) { return '' } @@ -274,7 +271,7 @@ function extractTextFromMessageItem(item: unknown): string { const textParts: string[] = [] for (const part of item.content) { - if (!isRecord(part)) { + if (!isRecordLike(part)) { continue } @@ -361,7 +358,7 @@ export function extractResponseToolCalls( const toolCalls: ResponsesToolCall[] = [] for (const item of output) { - if (!isRecord(item)) { + if (!isRecordLike(item)) { continue } diff --git a/apps/sim/providers/stream-events.ts b/apps/sim/providers/stream-events.ts index 09e460f730f..3180ffe1fb6 100644 --- a/apps/sim/providers/stream-events.ts +++ b/apps/sim/providers/stream-events.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' /** * Canonical agent stream event contract (provider → executor). * @@ -56,10 +57,6 @@ export type AgentStreamSink = { export type UnsubscribeAgentStreamSink = () => void -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} - export function isToolCallEndStatus(value: unknown): value is ToolCallEndStatus { return value === 'success' || value === 'error' || value === 'cancelled' } @@ -73,7 +70,7 @@ export function isTextDeltaClassification(value: unknown): value is TextDeltaCla } export function isAgentStreamEvent(value: unknown): value is AgentStreamEvent { - if (!isRecord(value) || typeof value.type !== 'string') { + if (!isRecordLike(value) || typeof value.type !== 'string') { return false } diff --git a/apps/sim/scripts/function-sandbox-parity-manifest.ts b/apps/sim/scripts/function-sandbox-parity-manifest.ts index 652607c3f0b..1c524860efa 100644 --- a/apps/sim/scripts/function-sandbox-parity-manifest.ts +++ b/apps/sim/scripts/function-sandbox-parity-manifest.ts @@ -1,5 +1,6 @@ import { readFileSync } from 'node:fs' import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { FUNCTION_NODE_MAJOR, FUNCTION_NPM_CLI_PACKAGE_CONTRACT, @@ -36,12 +37,8 @@ export interface FunctionSandboxParityManifest { commands: string[] } -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} - function requireRecord(value: unknown, field: string): Record { - if (!isRecord(value)) throw new Error(`${field} must be an object`) + if (!isRecordLike(value)) throw new Error(`${field} must be an object`) return value } diff --git a/apps/sim/tools/file/parser.ts b/apps/sim/tools/file/parser.ts index 569cbf1ada0..44efaaf1b53 100644 --- a/apps/sim/tools/file/parser.ts +++ b/apps/sim/tools/file/parser.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { inferContextFromKey } from '@/lib/uploads/utils/file-utils' import type { UserFile } from '@/executor/types' import type { @@ -15,11 +16,8 @@ import type { ToolConfig } from '@/tools/types' const logger = createLogger('FileParserTool') -const isRecord = (value: unknown): value is Record => - Boolean(value) && typeof value === 'object' - const isUserFile = (value: unknown): value is UserFile => - isRecord(value) && + isRecordLike(value) && typeof value.id === 'string' && typeof value.name === 'string' && typeof value.url === 'string' && @@ -28,7 +26,7 @@ const isUserFile = (value: unknown): value is UserFile => typeof value.key === 'string' const isFileParseResult = (value: unknown): value is FileParseResult => - isRecord(value) && + isRecordLike(value) && typeof value.content === 'string' && typeof value.fileType === 'string' && typeof value.size === 'number' && @@ -50,7 +48,7 @@ const normalizeHeaders = (headers: FileParserInput['headers']): Record { - if (isRecord(value) && isFileParseResult(value.output)) { + if (isRecordLike(value) && isFileParseResult(value.output)) { return value.output } @@ -58,9 +56,9 @@ const normalizeFileParseResult = (value: unknown): FileParseResult => { return value } - const record = isRecord(value) ? value : {} + const record = isRecordLike(value) ? value : {} const file = isUserFile(record.file) ? record.file : undefined - const metadata = isRecord(record.metadata) ? record.metadata : undefined + const metadata = isRecordLike(record.metadata) ? record.metadata : undefined const fallback: FileParseResult = { content: typeof record.content === 'string' ? record.content : '', fileType: typeof record.fileType === 'string' ? record.fileType : '', @@ -90,15 +88,15 @@ const parseFileParserResponse = async (response: Response): Promise isRecord(fileResult) && fileResult.success === false + (fileResult) => isRecordLike(fileResult) && fileResult.success === false ) if (failedResults.length === result.results.length) { const firstError = failedResults.find( - (fileResult) => isRecord(fileResult) && typeof fileResult.error === 'string' + (fileResult) => isRecordLike(fileResult) && typeof fileResult.error === 'string' ) return { success: false, @@ -107,7 +105,7 @@ const parseFileParserResponse = async (response: Response): Promise !(isRecord(fileResult) && fileResult.success === false)) + .filter((fileResult) => !(isRecordLike(fileResult) && fileResult.success === false)) .map((fileResult) => normalizeFileParseResult(fileResult)) const processedFiles = fileResults.flatMap((file) => (file.file ? [file.file] : [])) @@ -144,7 +142,7 @@ const parseFileParserResponse = async (response: Response): Promise = { const determinedFileType: string | undefined = params.fileType const resolveFilePath = (fileInput: unknown): string | null => { - if (!isRecord(fileInput)) return null + if (!isRecordLike(fileInput)) return null if (typeof fileInput.path === 'string') { return fileInput.path diff --git a/apps/sim/tools/github/create_pr_review.ts b/apps/sim/tools/github/create_pr_review.ts index b0ace3d7636..09c3335689e 100644 --- a/apps/sim/tools/github/create_pr_review.ts +++ b/apps/sim/tools/github/create_pr_review.ts @@ -1,5 +1,5 @@ +import { isRecordLike } from '@sim/utils/object' import { - isRecord, nullableNonEmptyString, optionalNonEmptyString, readGitHubErrorMessage, @@ -56,7 +56,7 @@ const REVIEW_RESPONSE_CONTEXT = 'GitHub review response' function parseReviewUser(value: unknown): GitHubReviewUser | null { if (value === null) return null - if (!isRecord(value)) throw new Error('GitHub review response has an invalid user') + if (!isRecordLike(value)) throw new Error('GitHub review response has an invalid user') const context = `${REVIEW_RESPONSE_CONTEXT}.user` return { login: requiredNonEmptyString(value, 'login', context), @@ -68,7 +68,7 @@ function parseReviewUser(value: unknown): GitHubReviewUser | null { } function parseGitHubReview(value: unknown): GitHubReview { - if (!isRecord(value)) throw new Error('GitHub review response must be an object') + if (!isRecordLike(value)) throw new Error('GitHub review response must be an object') const submittedAt = optionalNonEmptyString(value, 'submitted_at', REVIEW_RESPONSE_CONTEXT) return { id: requiredNumber(value, 'id', REVIEW_RESPONSE_CONTEXT), diff --git a/apps/sim/tools/github/graphql.ts b/apps/sim/tools/github/graphql.ts index 9df77d2d10e..63558cd7164 100644 --- a/apps/sim/tools/github/graphql.ts +++ b/apps/sim/tools/github/graphql.ts @@ -3,7 +3,8 @@ * the two response shapes every query has to handle the same way. */ -import { isRecord, readGitHubErrorMessage } from '@/tools/github/response-parsers' +import { isRecordLike } from '@sim/utils/object' +import { readGitHubErrorMessage } from '@/tools/github/response-parsers' export const GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' @@ -36,20 +37,20 @@ export async function readGraphQlData( } const payload: unknown = await response.json() - if (!isRecord(payload)) throw new Error(`${context} must be an object`) + if (!isRecordLike(payload)) throw new Error(`${context} must be an object`) const errors = payload.errors if (Array.isArray(errors) && errors.length > 0) { const first: unknown = errors[0] const message = - isRecord(first) && typeof first.message === 'string' && first.message.trim() + isRecordLike(first) && typeof first.message === 'string' && first.message.trim() ? first.message : 'unknown GraphQL error' throw new Error(`${context} returned an error: ${message}`) } const data = payload.data - if (!isRecord(data)) throw new Error(`${context}.data must be an object`) + if (!isRecordLike(data)) throw new Error(`${context}.data must be an object`) return data } @@ -61,7 +62,7 @@ export function parsePageInfo( value: unknown, context: string ): { hasNextPage: boolean; endCursor: string | null } { - if (!isRecord(value)) throw new Error(`${context} must be an object`) + if (!isRecordLike(value)) throw new Error(`${context} must be an object`) const hasNextPage = value.hasNextPage if (typeof hasNextPage !== 'boolean') { throw new Error(`${context}.hasNextPage must be a boolean`) diff --git a/apps/sim/tools/github/list_review_threads.ts b/apps/sim/tools/github/list_review_threads.ts index fb675d8dd51..ef706a20cab 100644 --- a/apps/sim/tools/github/list_review_threads.ts +++ b/apps/sim/tools/github/list_review_threads.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { GITHUB_GRAPHQL_MAX_PAGE_SIZE, GITHUB_GRAPHQL_URL, @@ -6,7 +7,6 @@ import { readGraphQlData, } from '@/tools/github/graphql' import { - isRecord, nullableNumber, requiredBoolean, requiredNumber, @@ -84,7 +84,7 @@ function parseAuthor( context: string ): { authorLogin: string | null; authorType: string | null } { if (value === null || value === undefined) return { authorLogin: null, authorType: null } - if (!isRecord(value)) throw new Error(`${context} must be an object or null`) + if (!isRecordLike(value)) throw new Error(`${context} must be an object or null`) return { authorLogin: requiredString(value, 'login', context), authorType: requiredString(value, '__typename', context), @@ -92,7 +92,7 @@ function parseAuthor( } function parseComment(value: unknown, context: string): ReviewThreadComment { - if (!isRecord(value)) throw new Error(`${context} must be an object`) + if (!isRecordLike(value)) throw new Error(`${context} must be an object`) return { body: requiredString(value, 'body', context), authorAssociation: requiredString(value, 'authorAssociation', context), @@ -102,10 +102,10 @@ function parseComment(value: unknown, context: string): ReviewThreadComment { function parseThread(value: unknown, index: number): ReviewThread { const context = `${CONTEXT}.threads[${index}]` - if (!isRecord(value)) throw new Error(`${context} must be an object`) + if (!isRecordLike(value)) throw new Error(`${context} must be an object`) const comments = value.comments - if (!isRecord(comments)) throw new Error(`${context}.comments must be an object`) + if (!isRecordLike(comments)) throw new Error(`${context}.comments must be an object`) const commentNodes = comments.nodes if (!Array.isArray(commentNodes)) throw new Error(`${context}.comments.nodes must be an array`) @@ -122,14 +122,14 @@ function parseThread(value: unknown, index: number): ReviewThread { } function parseLatestReview(value: unknown): SubmittedReviewSummary | null { - if (!isRecord(value)) throw new Error(`${CONTEXT}.reviews must be an object`) + if (!isRecordLike(value)) throw new Error(`${CONTEXT}.reviews must be an object`) const nodes = value.nodes if (!Array.isArray(nodes)) throw new Error(`${CONTEXT}.reviews.nodes must be an array`) const newest: unknown = nodes.at(-1) if (newest === undefined) return null const context = `${CONTEXT}.reviews.latest` - if (!isRecord(newest)) throw new Error(`${context} must be an object`) + if (!isRecordLike(newest)) throw new Error(`${context} must be an object`) const submittedAt = newest.submittedAt if (typeof submittedAt !== 'string') { throw new Error(`${context}.submittedAt must be a string`) @@ -221,12 +221,13 @@ export const listReviewThreadsTool: ToolConfig { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} - +import { isRecordLike } from '@sim/utils/object' export function requiredString( record: Record, key: string, @@ -136,14 +133,14 @@ export function requiredRecord( context: string ): Record { const value = record[key] - if (!isRecord(value)) throw new Error(`${context}.${key} must be an object`) + if (!isRecordLike(value)) throw new Error(`${context}.${key} must be an object`) return value } export async function readGitHubErrorMessage(response: Response): Promise { try { const value: unknown = await response.json() - if (!isRecord(value)) return undefined + if (!isRecordLike(value)) return undefined const message = value.message return typeof message === 'string' && message.trim() ? message : undefined } catch { diff --git a/apps/sim/tools/github/status_check_rollup.ts b/apps/sim/tools/github/status_check_rollup.ts index 6675c05ded0..6cf929ae335 100644 --- a/apps/sim/tools/github/status_check_rollup.ts +++ b/apps/sim/tools/github/status_check_rollup.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { GITHUB_GRAPHQL_MAX_PAGE_SIZE, GITHUB_GRAPHQL_URL, @@ -6,7 +7,6 @@ import { readGraphQlData, } from '@/tools/github/graphql' import { - isRecord, nullableNumber, nullableString, requiredBoolean, @@ -74,7 +74,7 @@ const ROLLUP_QUERY = ` function parseRollupContext(value: unknown, index: number): StatusCheckRollupContext { const context = `${CONTEXT}.contexts[${index}]` - if (!isRecord(value)) throw new Error(`${context} must be an object`) + if (!isRecordLike(value)) throw new Error(`${context} must be an object`) const typename = requiredString(value, '__typename', context) if (typename === 'CheckRun') { @@ -215,12 +215,12 @@ export const statusCheckRollupTool: ToolConfig { - return value !== null && typeof value === 'object' -} - export function getGoogleFormsErrorMessage(data: unknown, fallback: string): string { - if (!isRecord(data)) return fallback + if (!isRecordLike(data)) return fallback const { error } = data - if (!isRecord(error)) return fallback + if (!isRecordLike(error)) return fallback const { message } = error return typeof message === 'string' ? message : fallback diff --git a/apps/sim/tools/tiktok/utils.ts b/apps/sim/tools/tiktok/utils.ts index 4fc6320e4c7..7cfeb4440db 100644 --- a/apps/sim/tools/tiktok/utils.ts +++ b/apps/sim/tools/tiktok/utils.ts @@ -1,4 +1,5 @@ import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import type { ZodType } from 'zod' import { isPayloadSizeLimitError, readResponseTextWithLimit } from '@/lib/core/utils/stream-limits' @@ -79,12 +80,8 @@ interface ReadTikTokApiResponseOptions { signal?: AbortSignal } -function isRecord(value: unknown): value is Record { - return value !== null && typeof value === 'object' && !Array.isArray(value) -} - function asRecord(value: unknown): Record | null { - return isRecord(value) ? value : null + return isRecordLike(value) ? value : null } function parseTikTokError(value: unknown): TikTokApiError | null { diff --git a/apps/sim/tools/whatsapp/utils.ts b/apps/sim/tools/whatsapp/utils.ts index 8b176422ebe..882a5178415 100644 --- a/apps/sim/tools/whatsapp/utils.ts +++ b/apps/sim/tools/whatsapp/utils.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { WhatsAppMediaType, WhatsAppSendResponse } from '@/tools/whatsapp/types' /** WhatsApp Cloud API Graph version used by every outbound tool. */ @@ -65,14 +66,10 @@ export function buildAuthHeaders(accessToken: string | undefined): Record { - return typeof value === 'object' && value !== null -} - export async function parseWhatsAppResponse(response: Response): Promise> { const responseText = await response.text() const parsed = responseText ? (JSON.parse(responseText) as unknown) : {} - return isRecord(parsed) ? parsed : {} + return isRecordLike(parsed) ? parsed : {} } /** @@ -84,10 +81,10 @@ export async function parseWhatsAppResponse(response: Response): Promise, status: number): string { - const error = isRecord(data.error) ? data.error : undefined + const error = isRecordLike(data.error) ? data.error : undefined const summary = typeof error?.message === 'string' ? error.message : undefined const details = - isRecord(error?.error_data) && typeof error.error_data.details === 'string' + isRecordLike(error?.error_data) && typeof error.error_data.details === 'string' ? error.error_data.details : undefined const code = typeof error?.code === 'number' ? error.code : undefined @@ -170,13 +167,13 @@ export async function transformWhatsAppSendResponse( } const contacts = Array.isArray(data.contacts) - ? data.contacts.filter(isRecord).map((contact) => ({ + ? data.contacts.filter(isRecordLike).map((contact) => ({ input: typeof contact.input === 'string' ? contact.input : '', wa_id: typeof contact.wa_id === 'string' ? contact.wa_id : null, })) : [] const firstMessage = - Array.isArray(data.messages) && isRecord(data.messages[0]) ? data.messages[0] : undefined + Array.isArray(data.messages) && isRecordLike(data.messages[0]) ? data.messages[0] : undefined const messageId = typeof firstMessage?.id === 'string' ? firstMessage.id : undefined const messageStatus = typeof firstMessage?.message_status === 'string' ? firstMessage.message_status : undefined From 0fa258fee5771c273629cb136cc26bb7eefe15b3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 14 Aug 2026 14:38:26 -0700 Subject: [PATCH 2/6] refactor: replace inline record guards with isRecordLike MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 103 inline `typeof x === 'object' && x !== null && !Array.isArray(x)` guards (and the `x &&` / `Boolean(x)` spellings of the same conjunction) now call the shared guard. Inside a conjunction that already asserts `typeof x === 'object'`, `x &&` and `x !== null` are interchangeable, so all three orderings are the same predicate at runtime. Only exactly-equivalent conjunctions were converted. Matching required the three clauses to be one adjacent conjunction over the same operand, so a nearby but unrelated clause cannot be absorbed — generic-handler.ts, where the array case is handled inside the block rather than excluded by the guard, is correctly left alone. Two sites were reverted after type-check rejected them: instagram/server-utils.ts and workflows/[id]/log/route.ts both cast straight to a specific interface, which is legal from `object` but not from `Record`. Their narrowing is genuinely not identical, so they keep the inline form rather than acquiring a double cast. Left as-is: `packages/ts-sdk` (published with no runtime dependencies) and the two sandbox sources written into E2B/Daytona as text, which cannot import. --- .../main/browser-import/chromium-profiles.ts | 3 ++- .../app/api/desktop/tool/authorize/route.ts | 6 ++--- apps/sim/app/api/function/execute/route.ts | 5 ++-- .../sim/app/api/mcp/serve/[serverId]/route.ts | 12 ++++----- apps/sim/app/api/tools/file/manage/route.ts | 3 ++- apps/sim/app/api/tools/tts/unified/route.ts | 10 +++---- .../preview/use-file-preview-controller.ts | 5 ++-- apps/sim/blocks/blocks/smartlead.ts | 3 ++- .../lib/background-work/store.ts | 5 ++-- .../lib/copy/copy-workflows.ts | 3 ++- apps/sim/executor/execution/block-executor.ts | 3 ++- .../human-in-the-loop-handler.ts | 8 +++--- apps/sim/executor/handlers/pi/core/pi-sdk.ts | 3 ++- .../workflow/custom-block-tool-runner.ts | 5 ++-- .../handlers/workflow/workflow-handler.ts | 2 +- .../handlers/workflow/workflow-tool-runner.ts | 8 +++--- apps/sim/executor/utils/start-block.ts | 2 +- apps/sim/lib/admin/dashboard.ts | 5 ++-- apps/sim/lib/auth/connectors/providers.ts | 8 +++--- apps/sim/lib/billing/enterprise-outbox.ts | 5 ++-- .../lib/billing/enterprise-provisioning.ts | 5 ++-- .../request/go/file-preview-adapter.ts | 5 ++-- apps/sim/lib/copilot/request/go/stream.ts | 5 ++-- apps/sim/lib/copilot/request/sse-utils.ts | 3 ++- .../lib/copilot/request/tool-call-state.ts | 3 ++- apps/sim/lib/copilot/request/tools/files.ts | 10 +++---- .../tools/client/terminal-tool-execution.ts | 5 ++-- .../tools/registry/server-tool-adapter.ts | 6 ++--- apps/sim/lib/copilot/tools/server/router.ts | 7 ++--- apps/sim/lib/copilot/tools/tool-display.ts | 15 ++++------- .../lib/execution/model-input-provenance.ts | 12 +++------ .../lib/execution/private-tool-metadata.ts | 6 ++--- apps/sim/lib/logs/execution/trace-store.ts | 6 ++--- apps/sim/lib/managed-agents/session-client.ts | 3 ++- .../sim/lib/table/query-builder/converters.ts | 2 +- apps/sim/lib/table/select-values.ts | 3 ++- apps/sim/lib/webhooks/env-resolver.ts | 3 ++- apps/sim/lib/webhooks/processor.ts | 5 ++-- apps/sim/lib/webhooks/providers/gitlab.ts | 7 ++--- apps/sim/lib/webhooks/providers/incidentio.ts | 3 ++- apps/sim/lib/webhooks/providers/linear.ts | 2 +- apps/sim/lib/webhooks/providers/salesforce.ts | 14 ++++------ apps/sim/lib/webhooks/providers/servicenow.ts | 5 ++-- apps/sim/lib/webhooks/providers/vercel.ts | 5 ++-- apps/sim/lib/webhooks/providers/zoom.ts | 10 +++---- .../application/update-workflow-content.ts | 10 +++---- .../lib/workflows/blocks/flatten-outputs.ts | 5 ++-- .../sim/lib/workflows/comparison/normalize.ts | 5 ++-- .../executor/human-in-the-loop-manager.ts | 7 +---- .../lib/workflows/persistence/duplicate.ts | 27 +++++-------------- .../lib/workflows/search-replace/indexer.ts | 10 +++---- apps/sim/lib/workflows/tool-input/types.ts | 14 +++++----- .../lib/workflows/triggers/mock-payload.ts | 4 ++- .../sim/lib/workflows/triggers/run-options.ts | 4 +-- apps/sim/lib/workflows/triggers/triggers.ts | 3 ++- .../sim/lib/workspace-events/subscriptions.ts | 6 ++--- apps/sim/providers/anthropic/core.ts | 5 +--- .../anthropic/streaming-tool-loop.ts | 5 +--- apps/sim/providers/bedrock/index.ts | 12 +++------ apps/sim/providers/trace-enrichment.ts | 3 ++- apps/sim/tools/azure_data_explorer/query.ts | 3 ++- apps/sim/tools/convex/list_tables.ts | 6 ++--- apps/sim/tools/dynatrace/utils.ts | 9 +++---- apps/sim/tools/index.ts | 12 +++------ apps/sim/tools/merge-params.ts | 6 +---- apps/sim/tools/netsuite/utils.ts | 3 ++- apps/sim/tools/persona/utils.ts | 9 +++---- apps/sim/tools/prospeo/utils.ts | 5 ++-- apps/sim/tools/rabbitmq/get_overview.ts | 5 ++-- apps/sim/tools/rabbitmq/publish_message.ts | 6 ++--- apps/sim/tools/rabbitmq/utils.ts | 5 ++-- apps/sim/tools/rocketlane/types.ts | 3 ++- apps/sim/tools/shared/tags.ts | 3 ++- apps/sim/tools/supabase/invoke_function.ts | 3 ++- apps/sim/tools/uptimerobot/types.ts | 3 ++- apps/sim/tools/wiza/prospect_search.ts | 8 ++---- packages/logger/src/index.ts | 4 +-- 77 files changed, 190 insertions(+), 277 deletions(-) diff --git a/apps/desktop/src/main/browser-import/chromium-profiles.ts b/apps/desktop/src/main/browser-import/chromium-profiles.ts index a82525aecd5..3f87c04e3b6 100644 --- a/apps/desktop/src/main/browser-import/chromium-profiles.ts +++ b/apps/desktop/src/main/browser-import/chromium-profiles.ts @@ -2,6 +2,7 @@ import { constants } from 'node:fs' import { access, lstat, readdir, readFile, realpath } from 'node:fs/promises' import { homedir } from 'node:os' import { join } from 'node:path' +import { isRecordLike } from '@sim/utils/object' import { BROWSER_SOURCES, type BrowserSource, @@ -134,7 +135,7 @@ async function readProfileDisplayNames(userDataDir: string): Promise)) { if (!PROFILE_DIR_PATTERN.test(dir)) continue const name = (info as { name?: unknown })?.name diff --git a/apps/sim/app/api/desktop/tool/authorize/route.ts b/apps/sim/app/api/desktop/tool/authorize/route.ts index 1ebbae72f2b..27c8515e78c 100644 --- a/apps/sim/app/api/desktop/tool/authorize/route.ts +++ b/apps/sim/app/api/desktop/tool/authorize/route.ts @@ -1,5 +1,6 @@ import { isBrowserToolName } from '@sim/browser-protocol' import { isTerminalToolName } from '@sim/terminal-protocol' +import { isRecordLike } from '@sim/utils/object' import { type NextRequest, NextResponse } from 'next/server' import { authorizeDesktopToolContract } from '@/lib/api/contracts/desktop-tool-authorization' import { parseRequest } from '@/lib/api/server' @@ -43,10 +44,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return createNotFoundResponse('Pending client tool call not found') } - const args = - toolCall.args && typeof toolCall.args === 'object' && !Array.isArray(toolCall.args) - ? (toolCall.args as Record) - : {} + const args = isRecordLike(toolCall.args) ? (toolCall.args as Record) : {} const isBrowserTool = isBrowserToolName(toolCall.toolName) const isTerminalTool = isTerminalToolName(toolCall.toolName) const authorized = diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 47d16dcb288..15f15c88b5f 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -2,6 +2,7 @@ import type { Principal } from '@sim/auth/principal' import { createLogger } from '@sim/logger' import { sha256Hex } from '@sim/security/hash' import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { type NextRequest, NextResponse } from 'next/server' import { functionExecuteContract } from '@/lib/api/contracts' import { parseRequest } from '@/lib/api/server' @@ -1031,9 +1032,7 @@ function inspectMountedWorkspaceFileProvenance( } function asRecord(value: unknown): Record { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function getPositiveNumber(value: unknown): number | undefined { diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.ts index 80c16fdda81..52bf7c0fd74 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.ts @@ -26,6 +26,7 @@ import { workspace, } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { and, asc, eq, gt, isNull, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { @@ -265,12 +266,9 @@ function toToolInputSchema(schema: unknown): Partial { if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return {} const candidate = schema as Record - const properties = - candidate.properties && - typeof candidate.properties === 'object' && - !Array.isArray(candidate.properties) - ? (candidate.properties as Tool['inputSchema']['properties']) - : {} + const properties = isRecordLike(candidate.properties) + ? (candidate.properties as Tool['inputSchema']['properties']) + : {} const required = Array.isArray(candidate.required) ? candidate.required.filter((entry): entry is string => typeof entry === 'string') : undefined @@ -282,7 +280,7 @@ function toToolInputSchema(schema: unknown): Partial { } function isJsonObject(value: unknown): value is Record { - return value !== null && typeof value === 'object' && !Array.isArray(value) + return isRecordLike(value) } function parseJsonValue(text: string): { success: true; value: unknown } | { success: false } { diff --git a/apps/sim/app/api/tools/file/manage/route.ts b/apps/sim/app/api/tools/file/manage/route.ts index 71191f1887d..48936493e09 100644 --- a/apps/sim/app/api/tools/file/manage/route.ts +++ b/apps/sim/app/api/tools/file/manage/route.ts @@ -2,6 +2,7 @@ import { Buffer, isUtf8 } from 'buffer' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateShortId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import JSZip from 'jszip' import { type NextRequest, NextResponse } from 'next/server' import { fileManageContract } from '@/lib/api/contracts/tools/file' @@ -480,7 +481,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const { fileId, fileInput } = body const selectedFileId = fileId || - (fileInput && typeof fileInput === 'object' && !Array.isArray(fileInput) + (isRecordLike(fileInput) ? (() => { const obj = fileInput as Record return typeof obj.id === 'string' diff --git a/apps/sim/app/api/tools/tts/unified/route.ts b/apps/sim/app/api/tools/tts/unified/route.ts index 80cc10db05b..f86d6332970 100644 --- a/apps/sim/app/api/tools/tts/unified/route.ts +++ b/apps/sim/app/api/tools/tts/unified/route.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' import { @@ -167,12 +168,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { modelId: body.modelId, voice: body.voice, language: body.language, - outputFormat: - body.outputFormat && - typeof body.outputFormat === 'object' && - !Array.isArray(body.outputFormat) - ? (body.outputFormat as CartesiaTtsParams['outputFormat']) - : undefined, + outputFormat: isRecordLike(body.outputFormat) + ? (body.outputFormat as CartesiaTtsParams['outputFormat']) + : undefined, speed: body.speed, emotion: body.emotion, }) diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/preview/use-file-preview-controller.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/preview/use-file-preview-controller.ts index 02ca681bf37..1ae960be93b 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/preview/use-file-preview-controller.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/preview/use-file-preview-controller.ts @@ -7,6 +7,7 @@ import { useCallback, useRef, } from 'react' +import { isRecordLike } from '@sim/utils/object' import { useQueryClient } from '@tanstack/react-query' import type { SyntheticFilePreviewPayload } from '@/lib/copilot/request/session' import type { FilePreviewSession } from '@/lib/copilot/request/session/file-preview-session-contract' @@ -32,9 +33,7 @@ interface FilePreviewControllerDeps { } function asPayloadRecord(value: unknown): Record | undefined { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : undefined + return isRecordLike(value) ? (value as Record) : undefined } /** diff --git a/apps/sim/blocks/blocks/smartlead.ts b/apps/sim/blocks/blocks/smartlead.ts index 6da7223db1c..2eef1dc6269 100644 --- a/apps/sim/blocks/blocks/smartlead.ts +++ b/apps/sim/blocks/blocks/smartlead.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { SmartleadIcon } from '@/components/icons' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' @@ -1206,7 +1207,7 @@ function parseJsonArray(value: unknown, label: string): unknown[] | undefined { } function parseJsonObject(value: unknown, label: string): Record | undefined { - if (value && typeof value === 'object' && !Array.isArray(value)) { + if (isRecordLike(value)) { return value as Record } if (typeof value !== 'string' || value.trim() === '') return undefined diff --git a/apps/sim/ee/workspace-forking/lib/background-work/store.ts b/apps/sim/ee/workspace-forking/lib/background-work/store.ts index facb4431fbd..bc9eb612f2d 100644 --- a/apps/sim/ee/workspace-forking/lib/background-work/store.ts +++ b/apps/sim/ee/workspace-forking/lib/background-work/store.ts @@ -1,6 +1,7 @@ import { backgroundWorkStatus, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { and, desc, @@ -192,9 +193,7 @@ export async function finishBackgroundWork( /** Coerce an unknown jsonb metadata value to a plain record for safe merging. */ function toMetadataRecord(value: unknown): Record { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } /** diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts index 3d5cbb2bf71..cc8da0e3600 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts @@ -1,6 +1,7 @@ import { folder as folderTable, workflow, workflowBlocks } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { and, eq, inArray, isNull } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' import { assertFolderCollectionHasRoom } from '@/lib/folders/queries' @@ -447,7 +448,7 @@ export async function copyWorkflowStateIntoTarget( const newBlockId = blockIdMapping.get(oldBlockId)! let updatedData = block.data - if (block.data && typeof block.data === 'object' && !Array.isArray(block.data)) { + if (isRecordLike(block.data)) { const dataObj = block.data as Record if (typeof dataObj.parentId === 'string' && blockIdMapping.has(dataObj.parentId)) { updatedData = { diff --git a/apps/sim/executor/execution/block-executor.ts b/apps/sim/executor/execution/block-executor.ts index 58458d14c90..2ffc212f49b 100644 --- a/apps/sim/executor/execution/block-executor.ts +++ b/apps/sim/executor/execution/block-executor.ts @@ -1,5 +1,6 @@ import { createLogger, type Logger } from '@sim/logger' import { sleep } from '@sim/utils/helpers' +import { isRecordLike } from '@sim/utils/object' import { isTimeoutAbortReason } from '@/lib/core/execution-limits/types' import { redactApiKeys } from '@/lib/core/security/redaction' import { normalizeStringArray } from '@/lib/core/utils/arrays' @@ -852,7 +853,7 @@ export class BlockExecutor { } })() : mapping - inputs = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {} + inputs = isRecordLike(parsed) ? parsed : {} } const result: Record = {} diff --git a/apps/sim/executor/handlers/human-in-the-loop/human-in-the-loop-handler.ts b/apps/sim/executor/handlers/human-in-the-loop/human-in-the-loop-handler.ts index 032640d1b7e..1b019b01805 100644 --- a/apps/sim/executor/handlers/human-in-the-loop/human-in-the-loop-handler.ts +++ b/apps/sim/executor/handlers/human-in-the-loop/human-in-the-loop-handler.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { getBaseUrl } from '@/lib/core/utils/urls' import type { BlockOutput } from '@/blocks/types' import { @@ -126,7 +127,7 @@ export class HumanInTheLoopBlockHandler implements BlockHandler { if (operation === PAUSE_RESUME.OPERATION.API) { const parsed = this.parseResponseData(inputs) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (isRecordLike(parsed)) { responseData = { ...parsed, operation, @@ -169,10 +170,7 @@ export class HumanInTheLoopBlockHandler implements BlockHandler { } const responseDataWithResume = - resumeLinks && - responseData && - typeof responseData === 'object' && - !Array.isArray(responseData) + resumeLinks && isRecordLike(responseData) ? { ...responseData, _resume: resumeLinks } : responseData diff --git a/apps/sim/executor/handlers/pi/core/pi-sdk.ts b/apps/sim/executor/handlers/pi/core/pi-sdk.ts index 2ee17ae3e9c..30e43c61b54 100644 --- a/apps/sim/executor/handlers/pi/core/pi-sdk.ts +++ b/apps/sim/executor/handlers/pi/core/pi-sdk.ts @@ -1,5 +1,6 @@ import { InMemoryCredentialStore } from '@earendil-works/pi-ai' import type { ModelRuntime, ResourceLoader, ToolDefinition } from '@earendil-works/pi-coding-agent' +import { isRecordLike } from '@sim/utils/object' import type { PiToolSpec } from '@/executor/handlers/pi/core/backend' import { createScrubbedPiError, scrubPiSecrets } from '@/executor/handlers/pi/core/redaction' @@ -20,7 +21,7 @@ export function loadPiSdk(): Promise { } function isToolArguments(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) + return isRecordLike(value) } /** diff --git a/apps/sim/executor/handlers/workflow/custom-block-tool-runner.ts b/apps/sim/executor/handlers/workflow/custom-block-tool-runner.ts index c1dd651be1a..f0f0083192d 100644 --- a/apps/sim/executor/handlers/workflow/custom-block-tool-runner.ts +++ b/apps/sim/executor/handlers/workflow/custom-block-tool-runner.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' -import { isPlainRecord } from '@sim/utils/object' +import { isPlainRecord, isRecordLike } from '@sim/utils/object' import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' import type { PiiBlockOutputRedaction } from '@/executor/execution/types' import { WorkflowBlockHandler } from '@/executor/handlers/workflow/workflow-handler' @@ -160,8 +160,7 @@ export async function runCustomBlockTool( }) // Custom blocks never stream (no `onStream` on the synthetic ctx), so the // handler always returns the projected BlockOutput object. - const normalized: Record = - output && typeof output === 'object' && !Array.isArray(output) ? output : { result: output } + const normalized: Record = isRecordLike(output) ? output : { result: output } return { success: true, output: normalized } } catch (error) { // The handler throws a consumer-safe `ChildWorkflowError` on failure. The diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index 796aae1eb01..b56631f9167 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -347,7 +347,7 @@ export class WorkflowBlockHandler implements BlockHandler { if (inputs.inputMapping !== undefined && inputs.inputMapping !== null) { const normalized = parseJSON(inputs.inputMapping, inputs.inputMapping) - if (normalized && typeof normalized === 'object' && !Array.isArray(normalized)) { + if (isRecordLike(normalized)) { // Custom blocks key their mapping by the source field's stable id so a // rename never orphans the consumer's value; remap id → current name // before the child (which is addressed by name) receives it. diff --git a/apps/sim/executor/handlers/workflow/workflow-tool-runner.ts b/apps/sim/executor/handlers/workflow/workflow-tool-runner.ts index b37675bc849..010b7cae36e 100644 --- a/apps/sim/executor/handlers/workflow/workflow-tool-runner.ts +++ b/apps/sim/executor/handlers/workflow/workflow-tool-runner.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { calculateCostSummary } from '@/lib/logs/execution/logging-factory' import type { TraceSpan } from '@/lib/logs/types' import { ChildWorkflowError } from '@/executor/errors/child-workflow-error' @@ -123,10 +124,9 @@ export async function runWorkflowTool( workflowId: params.workflowId, inputMapping, }) - const normalized: Record = - output && typeof output === 'object' && !Array.isArray(output) - ? (output as Record) - : { result: output } + const normalized: Record = isRecordLike(output) + ? (output as Record) + : { result: output } const result: ToolResponse = { success: true, output: normalized } await markResultProvenanceCrossing(options.resolvedSecretTraceRegistry, result) return result diff --git a/apps/sim/executor/utils/start-block.ts b/apps/sim/executor/utils/start-block.ts index 169307452cf..a40fc42260a 100644 --- a/apps/sim/executor/utils/start-block.ts +++ b/apps/sim/executor/utils/start-block.ts @@ -580,7 +580,7 @@ function extractSubBlocks(block: SerializedBlock): Record | und } const subBlocks = maybeWithSubBlocks.subBlocks - if (subBlocks && typeof subBlocks === 'object' && !Array.isArray(subBlocks)) { + if (isRecordLike(subBlocks)) { return subBlocks } diff --git a/apps/sim/lib/admin/dashboard.ts b/apps/sim/lib/admin/dashboard.ts index 6f0ee065cc1..94e98072645 100644 --- a/apps/sim/lib/admin/dashboard.ts +++ b/apps/sim/lib/admin/dashboard.ts @@ -12,6 +12,7 @@ import { } from '@sim/db/schema' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { and, count, countDistinct, desc, eq, ilike, inArray, isNull, or, sql } from 'drizzle-orm' import { getOrganizationUsageLimitFallbackDollars, @@ -72,9 +73,7 @@ export interface AdminMutationActor { } function metadataRecord(value: unknown): Record { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function metadataNumber(metadata: Record, key: string): number | null { diff --git a/apps/sim/lib/auth/connectors/providers.ts b/apps/sim/lib/auth/connectors/providers.ts index 0119da1e4ac..041b0c35ec8 100644 --- a/apps/sim/lib/auth/connectors/providers.ts +++ b/apps/sim/lib/auth/connectors/providers.ts @@ -3,6 +3,7 @@ import { getOAuth2Tokens } from '@better-auth/core/oauth2' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import type { GenericOAuthConfig } from 'better-auth/plugins' import { syntheticConnectorEmail } from '@/lib/auth/connector-email' import { env } from '@/lib/core/config/env' @@ -1044,10 +1045,9 @@ export function buildConnectorProviders(): GenericOAuthConfig[] { // error_description: '...' }. The status-only guard therefore never // fires, so surface the actual error/description instead of collapsing // every failure into one opaque "no access token" string. - const errorObj = - data && typeof data === 'object' && !Array.isArray(data) - ? (data as { error?: unknown; error_description?: unknown }) - : {} + const errorObj = isRecordLike(data) + ? (data as { error?: unknown; error_description?: unknown }) + : {} const zohoError = typeof errorObj.error === 'string' ? errorObj.error : undefined const zohoErrorDescription = typeof errorObj.error_description === 'string' ? errorObj.error_description : undefined diff --git a/apps/sim/lib/billing/enterprise-outbox.ts b/apps/sim/lib/billing/enterprise-outbox.ts index 283f4330a38..b5c8576cd3f 100644 --- a/apps/sim/lib/billing/enterprise-outbox.ts +++ b/apps/sim/lib/billing/enterprise-outbox.ts @@ -1,4 +1,5 @@ import { outboxEvent } from '@sim/db/schema' +import { isRecordLike } from '@sim/utils/object' import { and, desc, eq, sql } from 'drizzle-orm' import type Stripe from 'stripe' import { z } from 'zod' @@ -201,9 +202,7 @@ export async function assertNoCompetingEnterpriseIssuance( } function metadataRecord(value: unknown): Record { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function positiveInteger(value: unknown): number | null { diff --git a/apps/sim/lib/billing/enterprise-provisioning.ts b/apps/sim/lib/billing/enterprise-provisioning.ts index 613b206070a..ed838f9c2c4 100644 --- a/apps/sim/lib/billing/enterprise-provisioning.ts +++ b/apps/sim/lib/billing/enterprise-provisioning.ts @@ -2,6 +2,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' import { member, organization, outboxEvent, subscription, user } from '@sim/db/schema' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { and, count, desc, eq, inArray, isNull, sql } from 'drizzle-orm' import type Stripe from 'stripe' import { parseBillingConcurrencyLimit } from '@/lib/billing/concurrency-defaults' @@ -33,9 +34,7 @@ import { enqueueOutboxEvent, type OutboxHandler } from '@/lib/core/outbox/servic const TERMINAL_STATUSES = new Set(TERMINAL_SUBSCRIPTION_STATUSES) function metadataRecord(value: unknown): Record { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function isNonterminalSubscriptionStatus(status: string | null | undefined): boolean { diff --git a/apps/sim/lib/copilot/request/go/file-preview-adapter.ts b/apps/sim/lib/copilot/request/go/file-preview-adapter.ts index b79254a211e..7810c55ff55 100644 --- a/apps/sim/lib/copilot/request/go/file-preview-adapter.ts +++ b/apps/sim/lib/copilot/request/go/file-preview-adapter.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { executeCopilotFileUseCase } from '@/lib/copilot/application/execute-file-use-case' import { MothershipStreamV1EventType } from '@/lib/copilot/generated/mothership-stream-v1' import { @@ -57,9 +58,7 @@ const PATCH_PREVIEW_SNAPSHOT_INTERVAL_MS = 80 const DELTA_PREVIEW_CHECKPOINT_INTERVAL_MS = 1000 function asJsonRecord(value: unknown): JsonRecord | undefined { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as JsonRecord) - : undefined + return isRecordLike(value) ? (value as JsonRecord) : undefined } function toPreviewTargetKind(kind: string | undefined): FilePreviewTargetKind | undefined { diff --git a/apps/sim/lib/copilot/request/go/stream.ts b/apps/sim/lib/copilot/request/go/stream.ts index ab924af5e67..3377e6bd6b2 100644 --- a/apps/sim/lib/copilot/request/go/stream.ts +++ b/apps/sim/lib/copilot/request/go/stream.ts @@ -1,6 +1,7 @@ import { type Context, SpanStatusCode } from '@opentelemetry/api' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { ORCHESTRATION_TIMEOUT_MS } from '@/lib/copilot/constants' import { MothershipStreamV1EventType, @@ -57,9 +58,7 @@ type SubagentSpanData = { } function asJsonRecord(value: unknown): JsonRecord | undefined { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as JsonRecord) - : undefined + return isRecordLike(value) ? (value as JsonRecord) : undefined } function parseSubagentSpanData(value: unknown): SubagentSpanData | undefined { diff --git a/apps/sim/lib/copilot/request/sse-utils.ts b/apps/sim/lib/copilot/request/sse-utils.ts index 47fa1f5b549..3f65c94827b 100644 --- a/apps/sim/lib/copilot/request/sse-utils.ts +++ b/apps/sim/lib/copilot/request/sse-utils.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { STREAM_BUFFER_MAX_DEDUP_ENTRIES } from '@/lib/copilot/constants' import { isToolCallStreamEvent, @@ -10,7 +11,7 @@ import type { StreamEvent } from '@/lib/copilot/request/types' /** Safely cast event.data to a record for property access. */ export const asRecord = (data: unknown): Record => - (data && typeof data === 'object' && !Array.isArray(data) ? data : {}) as Record + (isRecordLike(data) ? data : {}) as Record /** * In-memory tool event dedupe with bounded size. diff --git a/apps/sim/lib/copilot/request/tool-call-state.ts b/apps/sim/lib/copilot/request/tool-call-state.ts index fd4cdcf060d..d0c636e502b 100644 --- a/apps/sim/lib/copilot/request/tool-call-state.ts +++ b/apps/sim/lib/copilot/request/tool-call-state.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { toolResultForModel } from '@/lib/copilot/chat/sim-key-redaction' import { MothershipStreamV1ToolOutcome, @@ -88,7 +89,7 @@ function getToolCallTerminalDataRaw( typeof toolCall.error === 'string' && toolCall.error.length > 0 ? toolCall.error : 'Tool failed without an error message' - if (output && typeof output === 'object' && !Array.isArray(output)) { + if (isRecordLike(output)) { return 'error' in output ? output : { ...output, error } } return { output, error } diff --git a/apps/sim/lib/copilot/request/tools/files.ts b/apps/sim/lib/copilot/request/tools/files.ts index 035ca0b85d0..518c139beee 100644 --- a/apps/sim/lib/copilot/request/tools/files.ts +++ b/apps/sim/lib/copilot/request/tools/files.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { FunctionExecute, UserTable } from '@/lib/copilot/generated/tool-catalog-v1' import { CopilotOutputFileOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1' import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1' @@ -84,7 +85,7 @@ export function extractTabularData(output: unknown): Record[] | const obj = output as Record // user_table query_rows shape: { data: { rows: [{ data: {...} }], totalCount } } - if (obj.data && typeof obj.data === 'object' && !Array.isArray(obj.data)) { + if (isRecordLike(obj.data)) { const data = obj.data as Record if (Array.isArray(data.rows) && data.rows.length > 0) { const rows = data.rows as Record[] @@ -356,10 +357,9 @@ export async function maybeWriteOutputToFile( } const { userId, workspaceId } = context - const outputObject = - result.output && typeof result.output === 'object' && !Array.isArray(result.output) - ? (result.output as Record) - : undefined + const outputObject = isRecordLike(result.output) + ? (result.output as Record) + : undefined const resultObject = outputObject?.result && typeof outputObject.result === 'object' && diff --git a/apps/sim/lib/copilot/tools/client/terminal-tool-execution.ts b/apps/sim/lib/copilot/tools/client/terminal-tool-execution.ts index 9779eeae1ee..378f56fd133 100644 --- a/apps/sim/lib/copilot/tools/client/terminal-tool-execution.ts +++ b/apps/sim/lib/copilot/tools/client/terminal-tool-execution.ts @@ -7,6 +7,7 @@ * and reports the outcome via the confirm endpoint, which wakes the * server-side waiter. */ + import { createLogger } from '@sim/logger' import { isTerminalOperation, @@ -14,6 +15,7 @@ import { type TerminalToolArgs, } from '@sim/terminal-protocol' import { toError } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { ASYNC_TOOL_CONFIRMATION_STATUS } from '@/lib/copilot/async-runs/lifecycle' import { COPILOT_CONFIRM_API_PATH } from '@/lib/copilot/constants' import { reportClientToolCompletion } from '@/lib/copilot/tools/client/completion' @@ -89,8 +91,7 @@ function parseCall(params: Record): { const args = params.args return { operation, - args: - args && typeof args === 'object' && !Array.isArray(args) ? (args as TerminalToolArgs) : {}, + args: isRecordLike(args) ? (args as TerminalToolArgs) : {}, } } diff --git a/apps/sim/lib/copilot/tools/registry/server-tool-adapter.ts b/apps/sim/lib/copilot/tools/registry/server-tool-adapter.ts index 76e001fe4f5..b853e6ae492 100644 --- a/apps/sim/lib/copilot/tools/registry/server-tool-adapter.ts +++ b/apps/sim/lib/copilot/tools/registry/server-tool-adapter.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { messageForCopilotApplicationError } from '@/lib/copilot/application/error' import { projectToolErrorMessageForCopilot } from '@/lib/copilot/request/tools/resolved-secret-result' import type { ToolExecutionResult, ToolHandler } from '@/lib/copilot/tool-executor/types' @@ -30,10 +31,7 @@ export function createServerToolHandler(toolId: string): ToolHandler { resolvedSecretTraceRegistry: context.resolvedSecretTraceRegistry, }) - const rec = - result && typeof result === 'object' && !Array.isArray(result) - ? (result as Record) - : null + const rec = isRecordLike(result) ? (result as Record) : null if (rec?.success === false) { const message = (typeof rec.error === 'string' && rec.error) || diff --git a/apps/sim/lib/copilot/tools/server/router.ts b/apps/sim/lib/copilot/tools/server/router.ts index 2eb7b1d681a..d53a43748ad 100644 --- a/apps/sim/lib/copilot/tools/server/router.ts +++ b/apps/sim/lib/copilot/tools/server/router.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { z } from 'zod' import { getBlockVisibilityForCopilot } from '@/lib/copilot/block-visibility' import { @@ -233,11 +234,7 @@ export async function routeExecution( // nested "args" object. Unwrap that before validation so the generated // JSON Schema sees the flat tool contract shape. let normalizedPayload = payload ?? {} - if ( - normalizedPayload && - typeof normalizedPayload === 'object' && - !Array.isArray(normalizedPayload) - ) { + if (isRecordLike(normalizedPayload)) { const raw = normalizedPayload as Record if (raw.args && typeof raw.args === 'object' && !raw.operation) { const nested = raw.args as Record diff --git a/apps/sim/lib/copilot/tools/tool-display.ts b/apps/sim/lib/copilot/tools/tool-display.ts index 9d3e30c37c6..c62db5ce326 100644 --- a/apps/sim/lib/copilot/tools/tool-display.ts +++ b/apps/sim/lib/copilot/tools/tool-display.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { stripVersionSuffix } from '@sim/utils/string' /** @@ -45,9 +46,7 @@ function nestedStringArg(args: ToolArgs, parentKey: string, ...keys: string[]): function recordArg(args: ToolArgs, key: string): Record | undefined { const value = args?.[key] - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : undefined + return isRecordLike(value) ? (value as Record) : undefined } function stringOrNumberArg(args: ToolArgs, key: string): string { @@ -369,9 +368,8 @@ function setGlobalWorkflowVariablesTitle(args: ToolArgs): string { const operations = args?.operations if (!Array.isArray(operations) || operations.length === 0) return 'Setting workflow variables' - const parsed = operations.filter( - (operation): operation is Record => - Boolean(operation) && typeof operation === 'object' && !Array.isArray(operation) + const parsed = operations.filter((operation): operation is Record => + isRecordLike(operation) ) const operationNames = parsed.map((operation) => stringArg(operation, 'operation')) const firstOperation = operationNames[0] @@ -645,10 +643,7 @@ const TERMINAL_OPERATION_TITLES: Record = { function terminalTitle(args: ToolArgs): string { const operation = stringArg(args, 'operation') const nested = args?.args - const inner: ToolArgs = - nested && typeof nested === 'object' && !Array.isArray(nested) - ? (nested as Record) - : undefined + const inner: ToolArgs = isRecordLike(nested) ? (nested as Record) : undefined if (operation === 'run') return runningCommandTitle(stringArg(inner, 'command')) if (operation === 'handoff') { // Matches the browser takeover row: the reason is the whole point of the diff --git a/apps/sim/lib/execution/model-input-provenance.ts b/apps/sim/lib/execution/model-input-provenance.ts index 40b04e852ef..6bc03c62bce 100644 --- a/apps/sim/lib/execution/model-input-provenance.ts +++ b/apps/sim/lib/execution/model-input-provenance.ts @@ -1,4 +1,4 @@ -import { isPlainRecord } from '@sim/utils/object' +import { isPlainRecord, isRecordLike } from '@sim/utils/object' import { PRIVATE_SECRET_PROVENANCE_BUNDLE_V1, PRIVATE_SECRET_PROVENANCE_FIELD, @@ -473,10 +473,7 @@ export function inspectModelInputProvenanceRequest( headers: HeaderReader, payload: unknown ): ModelInputProvenanceInspection { - const record = - payload !== null && typeof payload === 'object' && !Array.isArray(payload) - ? (payload as Record) - : undefined + const record = isRecordLike(payload) ? (payload as Record) : undefined const hasProvenance = record ? Object.hasOwn(record, RESOLVED_SECRET_PROVENANCE_FIELD) : false const receivedType = headers.get(PRIVATE_MODEL_INPUT_PROVENANCE_HEADER) @@ -493,10 +490,7 @@ export function inspectPrivateSecretProvenanceRequest( headers: HeaderReader, payload: unknown ): ModelInputProvenanceInspection { - const record = - payload !== null && typeof payload === 'object' && !Array.isArray(payload) - ? (payload as Record) - : undefined + const record = isRecordLike(payload) ? (payload as Record) : undefined const hasProvenance = record ? Object.hasOwn(record, PRIVATE_SECRET_PROVENANCE_FIELD) : false const receivedType = headers.get(PRIVATE_SECRET_PROVENANCE_HEADER) diff --git a/apps/sim/lib/execution/private-tool-metadata.ts b/apps/sim/lib/execution/private-tool-metadata.ts index 436408ac4be..d760522de74 100644 --- a/apps/sim/lib/execution/private-tool-metadata.ts +++ b/apps/sim/lib/execution/private-tool-metadata.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' export const PRIVATE_TOOL_METADATA_REQUEST_HEADER = 'x-sim-request-private-tool-metadata' export const PRIVATE_TOOL_METADATA_RESPONSE_HEADER = 'x-sim-private-tool-metadata' export const MAX_PRIVATE_TOOL_METADATA_OVERHEAD_BYTES = 10 * 1024 * 1024 @@ -109,10 +110,7 @@ export function inspectPrivateToolMetadataEnvelope( expectedType: PrivateToolMetadataType ): PrivateToolMetadataEnvelopeInspection { const capability = inspectPrivateToolMetadataResponseCapability(headers, expectedType) - const record = - payload !== null && typeof payload === 'object' && !Array.isArray(payload) - ? (payload as Record) - : undefined + const record = isRecordLike(payload) ? (payload as Record) : undefined const hasNames = record ? Object.hasOwn(record, RESOLVED_SECRET_NAMES_FIELD) : false const hasProvenance = record ? Object.hasOwn(record, RESOLVED_SECRET_PROVENANCE_FIELD) : false diff --git a/apps/sim/lib/logs/execution/trace-store.ts b/apps/sim/lib/logs/execution/trace-store.ts index 38d62dcdf8c..fc3306d597c 100644 --- a/apps/sim/lib/logs/execution/trace-store.ts +++ b/apps/sim/lib/logs/execution/trace-store.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { omit } from '@sim/utils/object' +import { isRecordLike, omit } from '@sim/utils/object' import { isLargeValueRef } from '@/lib/execution/payloads/large-value-ref' import { materializeLargeValueRef, storeLargeValue } from '@/lib/execution/payloads/store' import { FunctionalOutputsUnavailableError } from '@/lib/logs/execution/functional-outputs' @@ -343,9 +343,7 @@ export async function materializeExecutionDataForDisplayWithBlockOutputs( } function readRecord(value: unknown): Record | undefined { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : undefined + return isRecordLike(value) ? (value as Record) : undefined } async function importResolvedSecretTraceRegistry( diff --git a/apps/sim/lib/managed-agents/session-client.ts b/apps/sim/lib/managed-agents/session-client.ts index aa3a6c7643e..388aab8e4a6 100644 --- a/apps/sim/lib/managed-agents/session-client.ts +++ b/apps/sim/lib/managed-agents/session-client.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' /** * Provider-neutral HTTP client for the Claude Platform Managed Agents API. * @@ -743,7 +744,7 @@ export function parseSessionSnapshot(raw: unknown): SessionSnapshot { } if (typeof body.title === 'string') snapshot.title = body.title - if (body.metadata && typeof body.metadata === 'object' && !Array.isArray(body.metadata)) { + if (isRecordLike(body.metadata)) { const metadata: Record = {} for (const [key, value] of Object.entries(body.metadata as Record)) { if (typeof value === 'string') metadata[key] = value diff --git a/apps/sim/lib/table/query-builder/converters.ts b/apps/sim/lib/table/query-builder/converters.ts index 5e938c64731..2a861155fd3 100644 --- a/apps/sim/lib/table/query-builder/converters.ts +++ b/apps/sim/lib/table/query-builder/converters.ts @@ -210,7 +210,7 @@ function mergeConditions(existing: unknown, incoming: unknown): Record { - if (value !== null && typeof value === 'object' && !Array.isArray(value)) { + if (isRecordLike(value)) { return { ...(value as Record) } } return { $eq: value as JsonValue } diff --git a/apps/sim/lib/table/select-values.ts b/apps/sim/lib/table/select-values.ts index 5c1c2ae0f60..fa568546006 100644 --- a/apps/sim/lib/table/select-values.ts +++ b/apps/sim/lib/table/select-values.ts @@ -9,6 +9,7 @@ * both the legacy `$` grammar and the v2 predicate tree. */ +import { isRecordLike } from '@sim/utils/object' import { buildIdByName, getColumnId, predicateNamesToIds } from '@/lib/table/column-keys' import { resolveSelectOptionId } from '@/lib/table/select-options' import type { @@ -74,7 +75,7 @@ export function resolveFilterSelectValues(filter: Filter, columns: ColumnDefinit continue } const options = column.options - if (value !== null && typeof value === 'object' && !Array.isArray(value)) { + if (isRecordLike(value)) { const ops = value as ConditionOperators const next: ConditionOperators = { ...ops } if (ops.$eq !== undefined) diff --git a/apps/sim/lib/webhooks/env-resolver.ts b/apps/sim/lib/webhooks/env-resolver.ts index 1a2fb413fbd..13975c2c956 100644 --- a/apps/sim/lib/webhooks/env-resolver.ts +++ b/apps/sim/lib/webhooks/env-resolver.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' import { resolveEnvVarReferences } from '@/executor/utils/reference-validation' @@ -34,7 +35,7 @@ export async function resolveEnvVarsInObject>( * Normalizes webhook provider config into a plain object for runtime resolution. */ export function normalizeWebhookProviderConfig(providerConfig: unknown): Record { - if (providerConfig && typeof providerConfig === 'object' && !Array.isArray(providerConfig)) { + if (isRecordLike(providerConfig)) { return providerConfig as Record } diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index e1162a13a8e..35cba00ece0 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -2,6 +2,7 @@ import { db, webhook, webhookPathClaim, workflow, workflowDeploymentVersion } fr import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import { and, eq, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' @@ -693,9 +694,7 @@ export interface WebhookDispatchResult { } function parseProviderConfig(value: unknown): Record { - return value !== null && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function getCredentialId(providerConfig: Record): string | undefined { diff --git a/apps/sim/lib/webhooks/providers/gitlab.ts b/apps/sim/lib/webhooks/providers/gitlab.ts index 2d2d9e66f58..d1f0c4b7543 100644 --- a/apps/sim/lib/webhooks/providers/gitlab.ts +++ b/apps/sim/lib/webhooks/providers/gitlab.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { NextResponse } from 'next/server' import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' @@ -115,11 +116,7 @@ export const gitlabHandler: WebhookProviderHandler = { const branch = ref.replace('refs/heads/', '') const objectAttributes = b.object_attributes let input: Record = { ...b, event_type: eventType, branch } - if ( - objectAttributes && - typeof objectAttributes === 'object' && - !Array.isArray(objectAttributes) - ) { + if (isRecordLike(objectAttributes)) { const workItemType = (objectAttributes as Record).type if (workItemType !== undefined) { input = { diff --git a/apps/sim/lib/webhooks/providers/incidentio.ts b/apps/sim/lib/webhooks/providers/incidentio.ts index 256c763d367..e9faf76c3ed 100644 --- a/apps/sim/lib/webhooks/providers/incidentio.ts +++ b/apps/sim/lib/webhooks/providers/incidentio.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { hmacSha256Base64 } from '@sim/security/hmac' +import { isRecordLike } from '@sim/utils/object' import { NextResponse } from 'next/server' import type { AuthContext, @@ -57,7 +58,7 @@ function verifyIncidentioSignature( } function asObject(value: unknown): Record | null { - if (value && typeof value === 'object' && !Array.isArray(value)) { + if (isRecordLike(value)) { return value as Record } return null diff --git a/apps/sim/lib/webhooks/providers/linear.ts b/apps/sim/lib/webhooks/providers/linear.ts index dd0068ff561..0734212bd3b 100644 --- a/apps/sim/lib/webhooks/providers/linear.ts +++ b/apps/sim/lib/webhooks/providers/linear.ts @@ -110,7 +110,7 @@ export const linearHandler: WebhookProviderHandler = { const b = isRecordLike(body) ? body : {} const rawActor = b.actor let actor: unknown = null - if (rawActor && typeof rawActor === 'object' && !Array.isArray(rawActor)) { + if (isRecordLike(rawActor)) { const a = rawActor as Record const { type: linearActorType, ...rest } = a actor = { diff --git a/apps/sim/lib/webhooks/providers/salesforce.ts b/apps/sim/lib/webhooks/providers/salesforce.ts index 8ead8812df0..76d6fd32fee 100644 --- a/apps/sim/lib/webhooks/providers/salesforce.ts +++ b/apps/sim/lib/webhooks/providers/salesforce.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { NextResponse } from 'next/server' import type { AuthContext, @@ -26,7 +27,7 @@ export function extractSalesforceObjectTypeFromPayload( } const record = body.record - if (record && typeof record === 'object' && !Array.isArray(record)) { + if (isRecordLike(record)) { const r = record as Record if (typeof r.sobjectType === 'string') { return r.sobjectType @@ -50,14 +51,12 @@ function verifySalesforceSharedSecret(request: Request, secret: string): boolean } function asRecord(body: unknown): Record { - return body && typeof body === 'object' && !Array.isArray(body) - ? (body as Record) - : {} + return isRecordLike(body) ? (body as Record) : {} } function extractRecordCore(body: Record): Record { const nested = body.record - if (nested && typeof nested === 'object' && !Array.isArray(nested)) { + if (isRecordLike(nested)) { return { ...(nested as Record) } } @@ -150,10 +149,7 @@ export const salesforceHandler: WebhookProviderHandler = { async formatInput(ctx: FormatInputContext): Promise { const rawPc = (ctx.webhook as { providerConfig?: unknown }).providerConfig - const pc = - rawPc && typeof rawPc === 'object' && !Array.isArray(rawPc) - ? (rawPc as Record) - : {} + const pc = isRecordLike(rawPc) ? (rawPc as Record) : {} const id = typeof pc.triggerId === 'string' ? pc.triggerId : '' const body = asRecord(ctx.body) diff --git a/apps/sim/lib/webhooks/providers/servicenow.ts b/apps/sim/lib/webhooks/providers/servicenow.ts index 8118bd72ed8..2044385a42f 100644 --- a/apps/sim/lib/webhooks/providers/servicenow.ts +++ b/apps/sim/lib/webhooks/providers/servicenow.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { isRecordLike } from '@sim/utils/object' import { NextResponse } from 'next/server' import type { AuthContext, @@ -10,9 +11,7 @@ import { verifyTokenAuth } from '@/lib/webhooks/providers/utils' const logger = createLogger('WebhookProvider:ServiceNow') function asRecord(body: unknown): Record { - return body && typeof body === 'object' && !Array.isArray(body) - ? (body as Record) - : {} + return isRecordLike(body) ? (body as Record) : {} } export const servicenowHandler: WebhookProviderHandler = { diff --git a/apps/sim/lib/webhooks/providers/vercel.ts b/apps/sim/lib/webhooks/providers/vercel.ts index 099931c4ad4..93319493703 100644 --- a/apps/sim/lib/webhooks/providers/vercel.ts +++ b/apps/sim/lib/webhooks/providers/vercel.ts @@ -1,6 +1,7 @@ import crypto from 'crypto' import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' +import { isRecordLike } from '@sim/utils/object' import { NextResponse } from 'next/server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' import type { @@ -261,7 +262,7 @@ export const vercelHandler: WebhookProviderHandler = { const linksRaw = payload.links let links: { deployment: string; project: string } | null = null - if (linksRaw && typeof linksRaw === 'object' && !Array.isArray(linksRaw)) { + if (isRecordLike(linksRaw)) { const L = linksRaw as Record const dep = L.deployment const proj = L.project @@ -279,7 +280,7 @@ export const vercelHandler: WebhookProviderHandler = { let deploymentMeta: Record | null = null if (deployment && typeof deployment === 'object') { const meta = (deployment as Record).meta - if (meta && typeof meta === 'object' && !Array.isArray(meta)) { + if (isRecordLike(meta)) { deploymentMeta = meta as Record } } diff --git a/apps/sim/lib/webhooks/providers/zoom.ts b/apps/sim/lib/webhooks/providers/zoom.ts index 60f4e0ef749..677b39aa9cb 100644 --- a/apps/sim/lib/webhooks/providers/zoom.ts +++ b/apps/sim/lib/webhooks/providers/zoom.ts @@ -3,6 +3,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { hmacSha256Hex } from '@sim/security/hmac' import { toError } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { and, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' @@ -67,12 +68,9 @@ async function resolveZoomChallengeSecrets( const resolvedRows = await Promise.all( rows.map(async (row) => { - const rawConfig = - row.providerConfig && - typeof row.providerConfig === 'object' && - !Array.isArray(row.providerConfig) - ? (row.providerConfig as Record) - : {} + const rawConfig = isRecordLike(row.providerConfig) + ? (row.providerConfig as Record) + : {} try { const config = await resolveEnvVarsInObject( diff --git a/apps/sim/lib/workflows/application/update-workflow-content.ts b/apps/sim/lib/workflows/application/update-workflow-content.ts index 9d60b59d277..d654907c506 100644 --- a/apps/sim/lib/workflows/application/update-workflow-content.ts +++ b/apps/sim/lib/workflows/application/update-workflow-content.ts @@ -6,6 +6,7 @@ import { createLogger } from '@sim/logger' import { assertWorkflowMutable, WorkflowLockedError } from '@sim/platform-authz/workflow' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import type { BlockState, WorkflowState } from '@sim/workflow-types/workflow' import { and, eq, isNull } from 'drizzle-orm' import { OrchestrationError } from '@/lib/core/orchestration/types' @@ -87,7 +88,7 @@ function coerceWorkflowVariableValue(value: unknown, type: string): unknown { try { const parsed: unknown = JSON.parse(String(value)) if (type === 'array' && Array.isArray(parsed)) return parsed - if (type === 'object' && parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (type === 'object' && isRecordLike(parsed)) { return parsed } } catch (error) { @@ -103,10 +104,9 @@ function applyVariableOperations( currentVariables: unknown, operations: readonly WorkflowVariableOperation[] ): { variables: Record; changed: boolean } { - const current = - currentVariables && typeof currentVariables === 'object' && !Array.isArray(currentVariables) - ? (currentVariables as Record) - : {} + const current = isRecordLike(currentVariables) + ? (currentVariables as Record) + : {} const byName = new Map() for (const value of Object.values(current)) { if ( diff --git a/apps/sim/lib/workflows/blocks/flatten-outputs.ts b/apps/sim/lib/workflows/blocks/flatten-outputs.ts index c00c367d28a..44d393b6323 100644 --- a/apps/sim/lib/workflows/blocks/flatten-outputs.ts +++ b/apps/sim/lib/workflows/blocks/flatten-outputs.ts @@ -8,6 +8,7 @@ * output shapes, BFS sort order) don't drift between consumers. */ +import { isRecordLike } from '@sim/utils/object' import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs' /** @@ -90,9 +91,7 @@ export function flattenWorkflowOutputs( const add = (path: string, outputObj: unknown, prefix = ''): void => { const fullPath = prefix ? `${prefix}.${path}` : path const declaredType = - outputObj && - typeof outputObj === 'object' && - !Array.isArray(outputObj) && + isRecordLike(outputObj) && 'type' in (outputObj as object) && typeof (outputObj as { type: unknown }).type === 'string' ? (outputObj as { type: string }).type diff --git a/apps/sim/lib/workflows/comparison/normalize.ts b/apps/sim/lib/workflows/comparison/normalize.ts index 94a1ba0c17a..ac3d3b74365 100644 --- a/apps/sim/lib/workflows/comparison/normalize.ts +++ b/apps/sim/lib/workflows/comparison/normalize.ts @@ -3,6 +3,7 @@ * Used by both client-side signature computation and server-side comparison. */ +import { isRecordLike } from '@sim/utils/object' import { normalizeWorkflowEdgeSourceHandle, normalizeWorkflowEdgeTargetHandle, @@ -198,7 +199,7 @@ export function sanitizeTools(tools: unknown[] | undefined): Record { - if (tool && typeof tool === 'object' && !Array.isArray(tool)) { + if (isRecordLike(tool)) { const { isExpanded, ...rest } = tool as ToolWithExpanded return rest } @@ -290,7 +291,7 @@ type InputFormatItem = Record & { collapsed?: boolean } export function sanitizeInputFormat(inputFormat: unknown[] | undefined): Record[] { if (!Array.isArray(inputFormat)) return [] return inputFormat.map((item) => { - if (item && typeof item === 'object' && !Array.isArray(item)) { + if (isRecordLike(item)) { const { collapsed, ...rest } = item as InputFormatItem return rest } diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts index d6c51041c17..2dfef26f5d4 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts @@ -1189,12 +1189,7 @@ export class PauseResumeManager { })() const submissionPayload = - normalizedResumeInputRaw && - typeof normalizedResumeInputRaw === 'object' && - !Array.isArray(normalizedResumeInputRaw) && - normalizedResumeInputRaw.submission && - typeof normalizedResumeInputRaw.submission === 'object' && - !Array.isArray(normalizedResumeInputRaw.submission) + isRecordLike(normalizedResumeInputRaw) && isRecordLike(normalizedResumeInputRaw.submission) ? (normalizedResumeInputRaw.submission as Record) : (normalizedResumeInputRaw as Record) diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index 92d9f140628..31a7c342e56 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -12,6 +12,7 @@ import { FolderLockedError, } from '@sim/platform-authz/workflow' import { generateId } from '@sim/utils/id' +import { isRecordLike } from '@sim/utils/object' import { normalizeWorkflowEdgeSourceHandle, normalizeWorkflowEdgeTargetHandle, @@ -276,10 +277,7 @@ export async function duplicateWorkflow( const newBlockId = blockIdMapping.get(block.id)! // Update parent ID to point to the new parent block ID if it exists - const blockData = - block.data && typeof block.data === 'object' && !Array.isArray(block.data) - ? (block.data as any) - : {} + const blockData = isRecordLike(block.data) ? (block.data as any) : {} let newParentId = blockData.parentId if (blockData.parentId && blockIdMapping.has(blockData.parentId)) { newParentId = blockIdMapping.get(blockData.parentId)! @@ -288,7 +286,7 @@ export async function duplicateWorkflow( // Update data.parentId and extent if they exist in the data object let updatedData = block.data let newExtent = blockData.extent - if (block.data && typeof block.data === 'object' && !Array.isArray(block.data)) { + if (isRecordLike(block.data)) { const dataObj = block.data as any if (dataObj.parentId && typeof dataObj.parentId === 'string') { updatedData = { ...dataObj } @@ -303,29 +301,16 @@ export async function duplicateWorkflow( // Update variable references in subBlocks (e.g. variables-input assignments) let updatedSubBlocks = block.subBlocks - if ( - updatedSubBlocks && - typeof updatedSubBlocks === 'object' && - !Array.isArray(updatedSubBlocks) - ) { + if (isRecordLike(updatedSubBlocks)) { updatedSubBlocks = sanitizeSubBlocksForDuplicate(updatedSubBlocks as SubBlockRecord) } - if ( - varIdMapping.size > 0 && - updatedSubBlocks && - typeof updatedSubBlocks === 'object' && - !Array.isArray(updatedSubBlocks) - ) { + if (varIdMapping.size > 0 && isRecordLike(updatedSubBlocks)) { updatedSubBlocks = remapVariableIdsInSubBlocks( updatedSubBlocks as SubBlockRecord, varIdMapping ) } - if ( - updatedSubBlocks && - typeof updatedSubBlocks === 'object' && - !Array.isArray(updatedSubBlocks) - ) { + if (isRecordLike(updatedSubBlocks)) { updatedSubBlocks = remapWorkflowReferencesInSubBlocks( updatedSubBlocks as SubBlockRecord, workflowIdMap diff --git a/apps/sim/lib/workflows/search-replace/indexer.ts b/apps/sim/lib/workflows/search-replace/indexer.ts index ae94808aa23..5b5dccf4307 100644 --- a/apps/sim/lib/workflows/search-replace/indexer.ts +++ b/apps/sim/lib/workflows/search-replace/indexer.ts @@ -144,11 +144,7 @@ function looksLikeStoredSkillList(value: unknown): boolean { Array.isArray(value) && value.length > 0 && value.every( - (item) => - item && - typeof item === 'object' && - !Array.isArray(item) && - typeof (item as Record).skillId === 'string' + (item) => isRecordLike(item) && typeof (item as Record).skillId === 'string' ) ) } @@ -163,7 +159,7 @@ function looksLikeStructuredString(value: string): boolean { function getFallbackToolParamType(value: unknown, paramType?: string): SubBlockType { if (paramType === 'object') return 'workflow-input-mapper' - if (value && typeof value === 'object' && !Array.isArray(value)) return 'workflow-input-mapper' + if (isRecordLike(value)) return 'workflow-input-mapper' if (typeof value !== 'string') return DEFAULT_SUBBLOCK_TYPE as SubBlockType const trimmed = value.trim() @@ -173,7 +169,7 @@ function getFallbackToolParamType(value: unknown, paramType?: string): SubBlockT try { const parsed: unknown = JSON.parse(trimmed) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (isRecordLike(parsed)) { return 'workflow-input-mapper' } } catch {} diff --git a/apps/sim/lib/workflows/tool-input/types.ts b/apps/sim/lib/workflows/tool-input/types.ts index 6e4fe0ced6e..9c1914a7f2a 100644 --- a/apps/sim/lib/workflows/tool-input/types.ts +++ b/apps/sim/lib/workflows/tool-input/types.ts @@ -1,3 +1,5 @@ +import { isRecordLike } from '@sim/utils/object' + interface StoredToolSchema { description?: string properties?: Record @@ -39,10 +41,9 @@ export function parseStoredToolInputValue(value: unknown): ParsedStoredTool[] { const record = tool as Record if (typeof record.type !== 'string') return [] - const params = - record.params && typeof record.params === 'object' && !Array.isArray(record.params) - ? (record.params as Record) - : undefined + const params = isRecordLike(record.params) + ? (record.params as Record) + : undefined return [ { @@ -60,10 +61,7 @@ export function parseStoredToolInputValue(value: unknown): ParsedStoredTool[] { ? record.usageControl : undefined, isExpanded: typeof record.isExpanded === 'boolean' ? record.isExpanded : undefined, - schema: - record.schema && typeof record.schema === 'object' && !Array.isArray(record.schema) - ? (record.schema as StoredToolSchema) - : undefined, + schema: isRecordLike(record.schema) ? (record.schema as StoredToolSchema) : undefined, }, ] }) diff --git a/apps/sim/lib/workflows/triggers/mock-payload.ts b/apps/sim/lib/workflows/triggers/mock-payload.ts index 18c69b7397f..23bd9cc9bc8 100644 --- a/apps/sim/lib/workflows/triggers/mock-payload.ts +++ b/apps/sim/lib/workflows/triggers/mock-payload.ts @@ -1,3 +1,5 @@ +import { isRecordLike } from '@sim/utils/object' + /** * Mock payload generation from a trigger's `outputs` definition. * @@ -88,7 +90,7 @@ function processOutputField(key: string, field: unknown, depth = 0, maxDepth = 1 return generateMockValue(typedField.type, typedField.description, key) } - if (field && typeof field === 'object' && !Array.isArray(field)) { + if (isRecordLike(field)) { const nestedObject: Record = {} for (const [nestedKey, nestedField] of Object.entries(field)) { nestedObject[nestedKey] = processOutputField(nestedKey, nestedField, depth + 1, maxDepth) diff --git a/apps/sim/lib/workflows/triggers/run-options.ts b/apps/sim/lib/workflows/triggers/run-options.ts index 853459ee2f6..c8d79d588b6 100644 --- a/apps/sim/lib/workflows/triggers/run-options.ts +++ b/apps/sim/lib/workflows/triggers/run-options.ts @@ -58,7 +58,7 @@ export interface TriggerInputValidationResult { function readSubBlockValue(block: TriggerBlockLike, key: string): unknown { const raw = (block.subBlocks as Record | undefined)?.[key] - if (raw && typeof raw === 'object' && !Array.isArray(raw)) { + if (isRecordLike(raw)) { return (raw as { value?: unknown }).value } return undefined @@ -104,7 +104,7 @@ function outputFieldToSchema(field: unknown): Record { return { type: mapOutputType(typed.type) } } - if (field && typeof field === 'object' && !Array.isArray(field)) { + if (isRecordLike(field)) { const properties: Record = {} for (const [key, value] of Object.entries(field)) { properties[key] = outputFieldToSchema(value) diff --git a/apps/sim/lib/workflows/triggers/triggers.ts b/apps/sim/lib/workflows/triggers/triggers.ts index 6fe61714756..45a8dd0e533 100644 --- a/apps/sim/lib/workflows/triggers/triggers.ts +++ b/apps/sim/lib/workflows/triggers/triggers.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { getBlock } from '@/blocks' import type { BlockState } from '@/stores/workflows/workflow/types' @@ -224,7 +225,7 @@ type SubBlockWithValue = { value?: unknown } function readSubBlockValue(subBlocks: Record | undefined, key: string): unknown { const raw = subBlocks?.[key] - if (raw && typeof raw === 'object' && !Array.isArray(raw)) { + if (isRecordLike(raw)) { return (raw as SubBlockWithValue).value } return undefined diff --git a/apps/sim/lib/workspace-events/subscriptions.ts b/apps/sim/lib/workspace-events/subscriptions.ts index 67951b46519..ec254877383 100644 --- a/apps/sim/lib/workspace-events/subscriptions.ts +++ b/apps/sim/lib/workspace-events/subscriptions.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { webhook, workflow, workflowDeploymentVersion } from '@sim/db/schema' +import { isRecordLike } from '@sim/utils/object' import { and, eq, isNull, or } from 'drizzle-orm' import { deliverableWebhookPredicate } from '@/lib/webhooks/delivery-predicate' import { @@ -98,10 +99,7 @@ function parseBoundedNumber( * Returns null when the config has no recognizable event type. */ export function parseSubscriptionConfig(providerConfig: unknown): SimSubscriptionConfig | null { - const config = - providerConfig && typeof providerConfig === 'object' && !Array.isArray(providerConfig) - ? (providerConfig as Record) - : {} + const config = isRecordLike(providerConfig) ? (providerConfig as Record) : {} const eventType = config.eventType if ( diff --git a/apps/sim/providers/anthropic/core.ts b/apps/sim/providers/anthropic/core.ts index 163b988bb6b..74da60b1d20 100644 --- a/apps/sim/providers/anthropic/core.ts +++ b/apps/sim/providers/anthropic/core.ts @@ -974,10 +974,7 @@ function enrichLastModelSegmentFromAnthropicResponse( const toolCalls: IterationToolCall[] = toolUseBlocks.map((t) => ({ id: t.id, name: t.name, - arguments: - t.input && typeof t.input === 'object' && !Array.isArray(t.input) - ? (t.input as Record) - : {}, + arguments: isRecordLike(t.input) ? (t.input as Record) : {}, })) const usage = createAnthropicUsageAccumulator() diff --git a/apps/sim/providers/anthropic/streaming-tool-loop.ts b/apps/sim/providers/anthropic/streaming-tool-loop.ts index ff644eb0838..8563253e5bb 100644 --- a/apps/sim/providers/anthropic/streaming-tool-loop.ts +++ b/apps/sim/providers/anthropic/streaming-tool-loop.ts @@ -75,10 +75,7 @@ function enrichModelSegment( const toolCalls: IterationToolCall[] = toolUseBlocks.map((t) => ({ id: t.id, name: t.name, - arguments: - t.input && typeof t.input === 'object' && !Array.isArray(t.input) - ? (t.input as Record) - : {}, + arguments: isRecordLike(t.input) ? (t.input as Record) : {}, })) const usage = createAnthropicUsageAccumulator() diff --git a/apps/sim/providers/bedrock/index.ts b/apps/sim/providers/bedrock/index.ts index abd36b4c9cd..3acd28a99f1 100644 --- a/apps/sim/providers/bedrock/index.ts +++ b/apps/sim/providers/bedrock/index.ts @@ -77,10 +77,7 @@ function enrichLastModelSegmentFromBedrockResponse( return { id: b.toolUse.toolUseId ?? '', name: b.toolUse.name ?? '', - arguments: - input && typeof input === 'object' && !Array.isArray(input) - ? (input as Record) - : {}, + arguments: isRecordLike(input) ? (input as Record) : {}, } }) @@ -633,10 +630,9 @@ export const bedrockProvider: ProviderConfig = { const toolExecutionPromises = currentToolUses.map(async (toolUse: ToolUseBlock) => { const toolCallStartTime = Date.now() const toolName = toolUse.name || '' - const toolArgs = - toolUse.input && typeof toolUse.input === 'object' && !Array.isArray(toolUse.input) - ? (toolUse.input as Record) - : undefined + const toolArgs = isRecordLike(toolUse.input) + ? (toolUse.input as Record) + : undefined const toolUseId = toolUse.toolUseId || generateToolUseId(toolName) try { diff --git a/apps/sim/providers/trace-enrichment.ts b/apps/sim/providers/trace-enrichment.ts index 342fd38b570..cdd83116f84 100644 --- a/apps/sim/providers/trace-enrichment.ts +++ b/apps/sim/providers/trace-enrichment.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { BlockTokens, IterationToolCall, ProviderTimingSegment } from '@/executor/types' import { LIST_PRICE_POLICY, priceModelUsage } from '@/providers/cost-policy' import { @@ -128,7 +129,7 @@ function parseToolCallArguments(rawArguments: string): Record | if (typeof rawArguments !== 'string') return '' try { const parsed = JSON.parse(rawArguments) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (isRecordLike(parsed)) { return parsed as Record } return rawArguments diff --git a/apps/sim/tools/azure_data_explorer/query.ts b/apps/sim/tools/azure_data_explorer/query.ts index 546b66c6634..692ea4d9f45 100644 --- a/apps/sim/tools/azure_data_explorer/query.ts +++ b/apps/sim/tools/azure_data_explorer/query.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { AzureDataExplorerQueryParams, AzureDataExplorerTableResponse, @@ -16,7 +17,7 @@ function parseProperties( if (typeof input === 'object') return input try { const parsed = JSON.parse(input) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (isRecordLike(parsed)) { return parsed as Record } } catch { diff --git a/apps/sim/tools/convex/list_tables.ts b/apps/sim/tools/convex/list_tables.ts index 0b7db9feb47..d73edba3e49 100644 --- a/apps/sim/tools/convex/list_tables.ts +++ b/apps/sim/tools/convex/list_tables.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { ConvexListTablesParams, ConvexListTablesResponse } from '@/tools/convex/types' import { convexApiUrl, convexAuthHeaders, parseConvexResponse } from '@/tools/convex/utils' import type { ToolConfig } from '@/tools/types' @@ -32,10 +33,7 @@ export const listTablesTool: ToolConfig { const data = await parseConvexResponse(response) - const schemas = - data !== null && typeof data === 'object' && !Array.isArray(data) - ? (data as Record) - : {} + const schemas = isRecordLike(data) ? (data as Record) : {} return { success: true, diff --git a/apps/sim/tools/dynatrace/utils.ts b/apps/sim/tools/dynatrace/utils.ts index baac23920ae..67ca53c5fab 100644 --- a/apps/sim/tools/dynatrace/utils.ts +++ b/apps/sim/tools/dynatrace/utils.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import type { DynatraceAttack, @@ -165,15 +166,11 @@ function toStringArray(value: unknown): string[] { } function toRecord(value: unknown): Record { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function toRecordOrNull(value: unknown): Record | null { - return value && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : null + return isRecordLike(value) ? (value as Record) : null } /** Flattens an `EntityStub` (`{ entityId: { id, type }, name }`) into a single object. */ diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts index 0dcebc1ea23..95dd3735c8e 100644 --- a/apps/sim/tools/index.ts +++ b/apps/sim/tools/index.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { describeError, findCause, getErrorMessage, toError } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' -import { isPlainRecord } from '@sim/utils/object' +import { isPlainRecord, isRecordLike } from '@sim/utils/object' import { backoffWithJitter, parseRetryAfter } from '@sim/utils/retry' import { DrizzleQueryError } from 'drizzle-orm/errors' import { getBYOKKey } from '@/lib/api-key/byok' @@ -1345,10 +1345,7 @@ async function consumePrivateToolPayloadMetadata( if (!requestedType) return 'verified' const inspection = inspectPrivateToolMetadataEnvelope(headers, payload, requestedType) - const record = - payload !== null && typeof payload === 'object' && !Array.isArray(payload) - ? (payload as Record) - : undefined + const record = isRecordLike(payload) ? (payload as Record) : undefined if (requestedType === RESOLVED_SECRET_NAMES_DURABLE_FILES_METADATA_V2 && record) { const capability = inspectPrivateToolMetadataResponseCapability(headers, requestedType) @@ -2789,10 +2786,7 @@ async function executeToolRequest( const errorToTransform = createTransformedErrorFromErrorInfo(errorInfo, tool.errorExtractor) const hasStructuredErrorPayload = - errorData !== null && - typeof errorData === 'object' && - !Array.isArray(errorData) && - ('error' in errorData || 'message' in errorData) + isRecordLike(errorData) && ('error' in errorData || 'message' in errorData) if (response.status === 413 && !hasStructuredErrorPayload) { logger.error( diff --git a/apps/sim/tools/merge-params.ts b/apps/sim/tools/merge-params.ts index 1693f7c6761..e0914d4ff13 100644 --- a/apps/sim/tools/merge-params.ts +++ b/apps/sim/tools/merge-params.ts @@ -43,11 +43,7 @@ function deepMergeInputMapping( } catch { // Invalid JSON, treat as empty } - } else if ( - typeof userInputMapping === 'object' && - userInputMapping !== null && - !Array.isArray(userInputMapping) - ) { + } else if (isRecordLike(userInputMapping)) { parsedUserMapping = userInputMapping } diff --git a/apps/sim/tools/netsuite/utils.ts b/apps/sim/tools/netsuite/utils.ts index cc26c3e87e9..c99611615d6 100644 --- a/apps/sim/tools/netsuite/utils.ts +++ b/apps/sim/tools/netsuite/utils.ts @@ -1,4 +1,5 @@ import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import { DEFAULT_MAX_ERROR_BODY_BYTES, @@ -922,5 +923,5 @@ function sanitizeErrorText(value: string, auth?: NetSuiteAuthParams): string { } function isJsonObject(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) + return isRecordLike(value) } diff --git a/apps/sim/tools/persona/utils.ts b/apps/sim/tools/persona/utils.ts index c4c51e9f79d..efa5ff564fa 100644 --- a/apps/sim/tools/persona/utils.ts +++ b/apps/sim/tools/persona/utils.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { PersonaAccount, PersonaCase, @@ -77,9 +78,7 @@ export async function parsePersonaResponse(response: Response): Promise, key: string): string[] { function getObject(attrs: Record, key: string): Record | null { const value = attrs[key] - return value !== null && typeof value === 'object' && !Array.isArray(value) - ? (value as Record) - : null + return isRecordLike(value) ? (value as Record) : null } /** diff --git a/apps/sim/tools/prospeo/utils.ts b/apps/sim/tools/prospeo/utils.ts index 5fc0934f7d8..0aef46b185a 100644 --- a/apps/sim/tools/prospeo/utils.ts +++ b/apps/sim/tools/prospeo/utils.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' export function parseDataArray(value: unknown): unknown[] { if (Array.isArray(value)) return value if (typeof value === 'string' && value.trim().length > 0) { @@ -12,13 +13,13 @@ export function parseDataArray(value: unknown): unknown[] { } export function parseFiltersObject(value: unknown): Record { - if (value && typeof value === 'object' && !Array.isArray(value)) { + if (isRecordLike(value)) { return value as Record } if (typeof value === 'string' && value.trim().length > 0) { try { const parsed = JSON.parse(value) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (isRecordLike(parsed)) { return parsed as Record } } catch {} diff --git a/apps/sim/tools/rabbitmq/get_overview.ts b/apps/sim/tools/rabbitmq/get_overview.ts index aa4069176b2..aa6fa911bb9 100644 --- a/apps/sim/tools/rabbitmq/get_overview.ts +++ b/apps/sim/tools/rabbitmq/get_overview.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { RabbitmqGetOverviewParams, RabbitmqGetOverviewResponse } from '@/tools/rabbitmq/types' import { buildAuthHeaders, @@ -20,9 +21,7 @@ const EMPTY_OVERVIEW = { } as const function asRecord(value: unknown): Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function asStringOrNull(value: unknown): string | null { diff --git a/apps/sim/tools/rabbitmq/publish_message.ts b/apps/sim/tools/rabbitmq/publish_message.ts index f36f18c8e67..ac55d753052 100644 --- a/apps/sim/tools/rabbitmq/publish_message.ts +++ b/apps/sim/tools/rabbitmq/publish_message.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { RabbitmqPublishMessageParams, RabbitmqPublishMessageResponse, @@ -84,10 +85,7 @@ export const rabbitmqPublishMessageTool: ToolConfig< // Only merge onto an existing headers object. Spreading a string or array here would // turn it into index-keyed junk on the published message. const existing = properties.headers - const base = - typeof existing === 'object' && existing !== null && !Array.isArray(existing) - ? (existing as Record) - : {} + const base = isRecordLike(existing) ? (existing as Record) : {} properties.headers = { ...base, ...headers } } diff --git a/apps/sim/tools/rabbitmq/utils.ts b/apps/sim/tools/rabbitmq/utils.ts index eb6396113d4..08c420291f8 100644 --- a/apps/sim/tools/rabbitmq/utils.ts +++ b/apps/sim/tools/rabbitmq/utils.ts @@ -1,4 +1,5 @@ import { isLoopbackIp } from '@sim/security/ssrf' +import { isRecordLike } from '@sim/utils/object' import type { RabbitmqBinding, RabbitmqChannel, @@ -283,9 +284,7 @@ export function unwrapPaginated(data: unknown): PaginatedResult { } function asRecord(value: unknown): Record { - return typeof value === 'object' && value !== null && !Array.isArray(value) - ? (value as Record) - : {} + return isRecordLike(value) ? (value as Record) : {} } function asNumberOrNull(value: unknown): number | null { diff --git a/apps/sim/tools/rocketlane/types.ts b/apps/sim/tools/rocketlane/types.ts index 8f7705c58a3..b9e90a23e3d 100644 --- a/apps/sim/tools/rocketlane/types.ts +++ b/apps/sim/tools/rocketlane/types.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { OutputProperty, ToolResponse } from '@/tools/types' /** Base URL for the Rocketlane REST API (v1.0). */ @@ -58,7 +59,7 @@ function asBoolean(value: unknown): boolean | null { } function asObject(value: unknown): Raw | null { - return value && typeof value === 'object' && !Array.isArray(value) ? (value as Raw) : null + return isRecordLike(value) ? (value as Raw) : null } function asArray(value: unknown): unknown[] { diff --git a/apps/sim/tools/shared/tags.ts b/apps/sim/tools/shared/tags.ts index 35faeb9995b..eba49b21b53 100644 --- a/apps/sim/tools/shared/tags.ts +++ b/apps/sim/tools/shared/tags.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { StructuredFilter } from '@/lib/knowledge/types' /** @@ -85,7 +86,7 @@ function filterValidDocumentTags(tags: unknown[]): DocumentTagEntry[] { export function parseDocumentTags(value: unknown): DocumentTagEntry[] { if (!value) return [] - if (typeof value === 'object' && !Array.isArray(value) && value !== null) { + if (isRecordLike(value)) { return Object.entries(value) .filter(([tagName, tagValue]) => { if (!tagName || tagName.trim() === '') return false diff --git a/apps/sim/tools/supabase/invoke_function.ts b/apps/sim/tools/supabase/invoke_function.ts index 016a508f741..f9f2f3ef0f2 100644 --- a/apps/sim/tools/supabase/invoke_function.ts +++ b/apps/sim/tools/supabase/invoke_function.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { SupabaseInvokeFunctionParams, SupabaseInvokeFunctionResponse, @@ -87,7 +88,7 @@ export const invokeFunctionTool: ToolConfig< Authorization: `Bearer ${params.apiKey}`, 'Content-Type': 'application/json', } - if (params.headers && typeof params.headers === 'object' && !Array.isArray(params.headers)) { + if (isRecordLike(params.headers)) { for (const [key, value] of Object.entries(params.headers)) { headers[key] = String(value) } diff --git a/apps/sim/tools/uptimerobot/types.ts b/apps/sim/tools/uptimerobot/types.ts index c139046ea6f..ba440a640ff 100644 --- a/apps/sim/tools/uptimerobot/types.ts +++ b/apps/sim/tools/uptimerobot/types.ts @@ -1,4 +1,5 @@ import { getErrorMessage } from '@sim/utils/errors' +import { isRecordLike } from '@sim/utils/object' import type { OutputProperty, ToolResponse } from '@/tools/types' /** Base URL for the UptimeRobot v3 REST API. */ @@ -196,7 +197,7 @@ function asEnum(value: unknown): string | null { } function asObject(value: unknown): Raw | null { - return value && typeof value === 'object' && !Array.isArray(value) ? (value as Raw) : null + return isRecordLike(value) ? (value as Raw) : null } function asArray(value: unknown): unknown[] { diff --git a/apps/sim/tools/wiza/prospect_search.ts b/apps/sim/tools/wiza/prospect_search.ts index fb460b39b63..5031a76df62 100644 --- a/apps/sim/tools/wiza/prospect_search.ts +++ b/apps/sim/tools/wiza/prospect_search.ts @@ -1,3 +1,4 @@ +import { isRecordLike } from '@sim/utils/object' import type { ToolConfig } from '@/tools/types' import { wizaHosting } from '@/tools/wiza/hosting' import type { WizaProspectSearchParams, WizaProspectSearchResponse } from '@/tools/wiza/types' @@ -155,12 +156,7 @@ export const wizaProspectSearchTool: ToolConfig< body.size = Math.max(0, Math.min(params.size, 30)) } - if ( - params.filters && - typeof params.filters === 'object' && - !Array.isArray(params.filters) && - Object.keys(params.filters).length > 0 - ) { + if (isRecordLike(params.filters) && Object.keys(params.filters).length > 0) { body.filters = params.filters return body } diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index dd2624de6e1..0acda78e932 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -4,7 +4,7 @@ * Framework-agnostic logging utilities for the Sim platform. * Provides standardized console logging with environment-aware configuration. */ -import { filterUndefined } from '@sim/utils/object' +import { filterUndefined, isRecordLike } from '@sim/utils/object' import chalk from 'chalk' import { getRequestContext } from './request-context' @@ -162,7 +162,7 @@ const formatObject = (obj: unknown, isDev: boolean): string => { if (obj instanceof Error) { return JSON.stringify(errorToPlainObject(obj, isDev), null, isDev ? 2 : 0) } - if (obj && typeof obj === 'object' && !Array.isArray(obj)) { + if (isRecordLike(obj)) { let unwrapped: Record | undefined for (const [key, value] of Object.entries(obj as Record)) { if (!(value instanceof Error)) continue From 7a799258a1361971a0f44ab7d32f9aff5a99a234 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 14 Aug 2026 15:33:28 -0700 Subject: [PATCH 3/6] refactor: consolidate duplicate record coercion helpers onto @sim/utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A second sweep found 28 more local record helpers hiding under names the previous `isRecord` grep never matched — `asRecord`, `toRecord`, `toRecordOrNull`, `asObject`, `isJsonObject`. rabbitmq defined the same `asRecord` twice within one service; dynatrace had three variants in one file. Fourteen of them were re-deriving the same two shapes, so those shapes now live in `@sim/utils/object` beside the guards they wrap: toRecord(value) // isRecordLike(value) ? value : {} toRecordOrNull(value) // isRecordLike(value) ? value : null Both preserve identity on a hit, so no call site starts copying. Eighteen local definitions are gone. `tools/instantly/utils.ts` keeps its exported `asRecord` because its return type is the local `JsonRecord` alias, but its body now delegates. `app/api/mcp/serve/[serverId]/route.ts` had an `isJsonObject` with zero call sites — deleted outright. Six helpers were deliberately left alone because they are NOT equivalent: `pagerduty`/`zendesk`/`gitlab` do `(value as Record) || {}`, which type-checks nothing at all, and `copilot/resources/extraction.ts`, `edit-workflow/validation.ts`, `pi/core/events.ts` omit the array exclusion. Those sit on webhook ingress and copilot paths where tightening is a behavior change, not a cleanup; they are audited separately. Two guards were removed rather than substituted, each proven dominated by an earlier check: the bedrock streaming `toolUse.input` guard was unreachable (`parseToolInput` already throws on non-objects before the loop builds `assembledToolUses`), and four `driver.ts` re-narrows follow an `if (!isRecordLike(x) || ...) throw` that dominates the later use. --- apps/desktop/src/main/browser-agent/driver.ts | 13 +- apps/sim/app/api/function/execute/route.ts | 24 ++- .../sim/app/api/mcp/serve/[serverId]/route.ts | 4 - .../sim/lib/copilot/request/handlers/types.ts | 6 +- apps/sim/lib/copilot/request/sse-utils.ts | 5 - apps/sim/lib/webhooks/providers/emailbison.ts | 6 +- apps/sim/lib/webhooks/providers/incidentio.ts | 27 ++- apps/sim/lib/webhooks/providers/salesforce.ts | 12 +- apps/sim/lib/webhooks/providers/servicenow.ts | 8 +- apps/sim/lib/webhooks/providers/tiktok.ts | 16 +- .../providers/bedrock/streaming-tool-loop.ts | 8 +- apps/sim/tools/dynatrace/utils.ts | 10 +- apps/sim/tools/emailbison/utils.ts | 6 +- apps/sim/tools/instantly/utils.ts | 5 +- apps/sim/tools/netsuite/utils.ts | 30 ++-- apps/sim/tools/rabbitmq/get_overview.ts | 12 +- apps/sim/tools/rabbitmq/utils.ts | 44 +++-- apps/sim/tools/rocketlane/types.ts | 154 +++++++++--------- apps/sim/tools/smartlead/utils.ts | 6 +- apps/sim/tools/tiktok/utils.ts | 12 +- apps/sim/tools/uptimerobot/types.ts | 18 +- packages/utils/src/index.ts | 2 + packages/utils/src/object.test.ts | 35 +++- packages/utils/src/object.ts | 21 +++ 24 files changed, 231 insertions(+), 253 deletions(-) diff --git a/apps/desktop/src/main/browser-agent/driver.ts b/apps/desktop/src/main/browser-agent/driver.ts index 8d3674cbd73..5c1b43c0d07 100644 --- a/apps/desktop/src/main/browser-agent/driver.ts +++ b/apps/desktop/src/main/browser-agent/driver.ts @@ -29,7 +29,7 @@ import type { BrowserDownloadsState, BrowserToolbarCommand } from '@sim/desktop- import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' -import { isRecordLike, omit } from '@sim/utils/object' +import { isRecordLike, omit, toRecord } from '@sim/utils/object' import type { BrowserWindow, MenuItemConstructorOptions, WebContents, WebFrameMain } from 'electron' import { Menu } from 'electron' import * as cdp from '@/main/browser-agent/cdp' @@ -1015,7 +1015,7 @@ function raceAgainstWatchdog( */ async function activeElementState(target: PageExecutionTarget): Promise> { const state = await execInPage(target, readActiveElementState, []).catch(() => null) - return isRecordLike(state) ? state : {} + return toRecord(state) } function requireSnapshotForElementAction(): void { @@ -1191,7 +1191,7 @@ async function pageActionState( resetMutationRevision, elementId, ]).catch(() => null) - return isRecordLike(state) ? state : {} + return toRecord(state) } function pageEffect( @@ -2565,7 +2565,7 @@ async function executeToolInner( }, } return { - ...(isRecordLike(fallback) ? fallback : {}), + ...fallback, trusted, ...state, ...combinedObservation, @@ -2849,12 +2849,11 @@ async function executeToolInner( await sleep(50) const state = unwrapPageResult(await execInPage(target, readSelectElementState, [elementId])) const effectObserved = - isRecordLike(selected) && isRecordLike(state) && selected.selected === state.selected && selected.value === state.value return { - ...(isRecordLike(selected) ? selected : {}), + ...selected, effectObserved, readback: state, ...(!effectObserved @@ -2994,7 +2993,7 @@ async function executeToolInner( : {}), } return { - ...(isRecordLike(result) ? result : {}), + ...result, trusted, effect, possibleEffectObserved, diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 15f15c88b5f..3937235d454 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -2,7 +2,7 @@ import type { Principal } from '@sim/auth/principal' import { createLogger } from '@sim/logger' import { sha256Hex } from '@sim/security/hash' import { getErrorMessage } from '@sim/utils/errors' -import { isRecordLike } from '@sim/utils/object' +import { toRecord } from '@sim/utils/object' import { type NextRequest, NextResponse } from 'next/server' import { functionExecuteContract } from '@/lib/api/contracts' import { parseRequest } from '@/lib/api/server' @@ -1031,10 +1031,6 @@ function inspectMountedWorkspaceFileProvenance( } } -function asRecord(value: unknown): Record { - return isRecordLike(value) ? (value as Record) : {} -} - function getPositiveNumber(value: unknown): number | undefined { if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) { return undefined @@ -1053,8 +1049,8 @@ function getBrokerFileArgs(args: unknown): { offset?: number length?: number } { - const record = asRecord(args) - const options = asRecord(record.options) + const record = toRecord(args) + const options = toRecord(record.options) return { file: record.file, maxBytes: clampInlineBytes(options.maxBytes), @@ -1104,8 +1100,8 @@ function createFunctionRuntimeBrokers( 'sim.files.readBase64Chunk': (args) => readFile(args, 'base64', true), 'sim.files.readTextChunk': (args) => readFile(args, 'text', true), 'sim.values.read': async (args) => { - const record = asRecord(args) - const options = asRecord(record.options) + const record = toRecord(args) + const options = toRecord(record.options) const ref = record.ref if (!isLargeValueRef(ref)) { throw new Error('Expected a large execution value reference.') @@ -1124,8 +1120,8 @@ function createFunctionRuntimeBrokers( return value }, 'sim.values.readArray': async (args) => { - const record = asRecord(args) - const options = asRecord(record.options) + const record = toRecord(args) + const options = toRecord(record.options) const manifest = record.ref if (!isLargeArrayManifest(manifest)) { throw new Error('Expected a large array manifest.') @@ -1179,9 +1175,9 @@ async function functionJsonResponse( } function getFunctionResultProvenanceSurface(body: unknown): unknown { - const record = asRecord(body) - const output = asRecord(record.output) - const debug = asRecord(record.debug) + const record = toRecord(body) + const output = toRecord(record.output) + const debug = toRecord(record.debug) return [ Object.hasOwn(record, 'error') ? record.error : undefined, Object.hasOwn(output, 'result') ? output.result : undefined, diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.ts index 52bf7c0fd74..8814f630610 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.ts @@ -279,10 +279,6 @@ function toToolInputSchema(schema: unknown): Partial { } } -function isJsonObject(value: unknown): value is Record { - return isRecordLike(value) -} - function parseJsonValue(text: string): { success: true; value: unknown } | { success: false } { if (!text) return { success: true, value: {} } try { diff --git a/apps/sim/lib/copilot/request/handlers/types.ts b/apps/sim/lib/copilot/request/handlers/types.ts index 3e383e2c6fc..79338a4b599 100644 --- a/apps/sim/lib/copilot/request/handlers/types.ts +++ b/apps/sim/lib/copilot/request/handlers/types.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { isRecordLike } from '@sim/utils/object' +import { isRecordLike, toRecord } from '@sim/utils/object' import type { AsyncCompletionSignal, AsyncTerminalCompletionSnapshot, @@ -16,7 +16,7 @@ import { MothershipStreamV1ToolPhase, type MothershipStreamV1ToolResultPayload, } from '@/lib/copilot/generated/mothership-stream-v1' -import { asRecord, markToolResultSeen } from '@/lib/copilot/request/sse-utils' +import { markToolResultSeen } from '@/lib/copilot/request/sse-utils' import { setTerminalToolCallState } from '@/lib/copilot/request/tool-call-state' import type { ContentBlock, @@ -177,7 +177,7 @@ export function getToolCallUI(data: MothershipStreamV1ToolCallDescriptor): { internal: boolean hidden: boolean } { - const raw = asRecord(data.ui) + const raw = toRecord(data.ui) return { clientExecutable: raw.clientExecutable === true || data.executor === MothershipStreamV1ToolExecutor.client, diff --git a/apps/sim/lib/copilot/request/sse-utils.ts b/apps/sim/lib/copilot/request/sse-utils.ts index 3f65c94827b..db7f8623d86 100644 --- a/apps/sim/lib/copilot/request/sse-utils.ts +++ b/apps/sim/lib/copilot/request/sse-utils.ts @@ -1,4 +1,3 @@ -import { isRecordLike } from '@sim/utils/object' import { STREAM_BUFFER_MAX_DEDUP_ENTRIES } from '@/lib/copilot/constants' import { isToolCallStreamEvent, @@ -9,10 +8,6 @@ import { import { TOOL_CALL_STATUS } from '@/lib/copilot/request/session/event' import type { StreamEvent } from '@/lib/copilot/request/types' -/** Safely cast event.data to a record for property access. */ -export const asRecord = (data: unknown): Record => - (isRecordLike(data) ? data : {}) as Record - /** * In-memory tool event dedupe with bounded size. * diff --git a/apps/sim/lib/webhooks/providers/emailbison.ts b/apps/sim/lib/webhooks/providers/emailbison.ts index 838d042a1a5..ee4d1ba1b31 100644 --- a/apps/sim/lib/webhooks/providers/emailbison.ts +++ b/apps/sim/lib/webhooks/providers/emailbison.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { isRecordLike } from '@sim/utils/object' +import { isRecordLike, toRecordOrNull } from '@sim/utils/object' import { type SecureFetchResponse, secureFetchWithPinnedIP, @@ -325,10 +325,6 @@ function toNumberOrNull(value: unknown): number | null { return Number.isFinite(parsed) ? parsed : null } -function toRecordOrNull(value: unknown): Record | null { - return isRecordLike(value) ? value : null -} - function renameTypeField(value: unknown, targetKey: string): Record | null { if (!isRecordLike(value)) return null diff --git a/apps/sim/lib/webhooks/providers/incidentio.ts b/apps/sim/lib/webhooks/providers/incidentio.ts index e9faf76c3ed..25011781744 100644 --- a/apps/sim/lib/webhooks/providers/incidentio.ts +++ b/apps/sim/lib/webhooks/providers/incidentio.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { hmacSha256Base64 } from '@sim/security/hmac' -import { isRecordLike } from '@sim/utils/object' +import { toRecordOrNull } from '@sim/utils/object' import { NextResponse } from 'next/server' import type { AuthContext, @@ -57,13 +57,6 @@ function verifyIncidentioSignature( } } -function asObject(value: unknown): Record | null { - if (isRecordLike(value)) { - return value as Record - } - return null -} - function asString(value: unknown): string | null { return typeof value === 'string' ? value : null } @@ -85,9 +78,9 @@ function extractEntity( eventType: string, key: 'incident' | 'alert' ): Record | null { - const wrapper = eventType ? asObject(body[eventType]) : null + const wrapper = eventType ? toRecordOrNull(body[eventType]) : null if (!wrapper) return null - return asObject(wrapper[key]) ?? wrapper + return toRecordOrNull(wrapper[key]) ?? wrapper } export const incidentioHandler: WebhookProviderHandler = { @@ -153,9 +146,9 @@ export const incidentioHandler: WebhookProviderHandler = { }, async formatInput({ body }: FormatInputContext): Promise { - const b = (asObject(body) ?? {}) as Record + const b = (toRecordOrNull(body) ?? {}) as Record const eventType = typeof b.event_type === 'string' ? b.event_type : '' - const wrapper = eventType ? asObject(b[eventType]) : null + const wrapper = eventType ? toRecordOrNull(b[eventType]) : null const isAlert = eventType.startsWith('public_alert.') if (isAlert) { @@ -188,15 +181,15 @@ export const incidentioHandler: WebhookProviderHandler = { name: asString(incident?.name), reference: asString(incident?.reference), summary: asString(incident?.summary), - incident_status: asObject(incident?.incident_status), - severity: asObject(incident?.severity), + incident_status: toRecordOrNull(incident?.incident_status), + severity: toRecordOrNull(incident?.severity), mode: asString(incident?.mode), visibility: asString(incident?.visibility), permalink: asString(incident?.permalink), created_at: asString(incident?.created_at), updated_at: asString(incident?.updated_at), - new_status: asObject(wrapper?.new_status), - previous_status: asObject(wrapper?.previous_status), + new_status: toRecordOrNull(wrapper?.new_status), + previous_status: toRecordOrNull(wrapper?.previous_status), update_message: asString(wrapper?.message), payload: b, }, @@ -204,7 +197,7 @@ export const incidentioHandler: WebhookProviderHandler = { }, extractIdempotencyId(body: unknown) { - const b = asObject(body) + const b = toRecordOrNull(body) if (!b) return null const eventType = typeof b.event_type === 'string' ? b.event_type : '' const key = eventType.startsWith('public_alert.') ? 'alert' : 'incident' diff --git a/apps/sim/lib/webhooks/providers/salesforce.ts b/apps/sim/lib/webhooks/providers/salesforce.ts index 76d6fd32fee..f53dcf65f54 100644 --- a/apps/sim/lib/webhooks/providers/salesforce.ts +++ b/apps/sim/lib/webhooks/providers/salesforce.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { isRecordLike } from '@sim/utils/object' +import { isRecordLike, toRecord } from '@sim/utils/object' import { NextResponse } from 'next/server' import type { AuthContext, @@ -50,10 +50,6 @@ function verifySalesforceSharedSecret(request: Request, secret: string): boolean return verifyTokenAuth(request, secret) } -function asRecord(body: unknown): Record { - return isRecordLike(body) ? (body as Record) : {} -} - function extractRecordCore(body: Record): Record { const nested = body.record if (isRecordLike(nested)) { @@ -134,7 +130,7 @@ export const salesforceHandler: WebhookProviderHandler = { const { isSalesforceEventMatch } = await import('@/triggers/salesforce/utils') const configuredObjectType = providerConfig.objectType as string | undefined - const obj = asRecord(body) + const obj = toRecord(body) if (!isSalesforceEventMatch(triggerId, obj, configuredObjectType)) { logger.debug( @@ -151,7 +147,7 @@ export const salesforceHandler: WebhookProviderHandler = { const rawPc = (ctx.webhook as { providerConfig?: unknown }).providerConfig const pc = isRecordLike(rawPc) ? (rawPc as Record) : {} const id = typeof pc.triggerId === 'string' ? pc.triggerId : '' - const body = asRecord(ctx.body) + const body = toRecord(ctx.body) const record = extractRecordCore(body) const objectType = @@ -296,7 +292,7 @@ export const salesforceHandler: WebhookProviderHandler = { }, extractIdempotencyId(body: unknown): string | null { - const b = asRecord(body) + const b = toRecord(body) const record = extractRecordCore(b) const id = pickRecordId(b, record) const et = diff --git a/apps/sim/lib/webhooks/providers/servicenow.ts b/apps/sim/lib/webhooks/providers/servicenow.ts index 2044385a42f..43c79e4e159 100644 --- a/apps/sim/lib/webhooks/providers/servicenow.ts +++ b/apps/sim/lib/webhooks/providers/servicenow.ts @@ -1,5 +1,5 @@ import { createLogger } from '@sim/logger' -import { isRecordLike } from '@sim/utils/object' +import { toRecord } from '@sim/utils/object' import { NextResponse } from 'next/server' import type { AuthContext, @@ -10,10 +10,6 @@ import { verifyTokenAuth } from '@/lib/webhooks/providers/utils' const logger = createLogger('WebhookProvider:ServiceNow') -function asRecord(body: unknown): Record { - return isRecordLike(body) ? (body as Record) : {} -} - export const servicenowHandler: WebhookProviderHandler = { verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null { const secret = providerConfig.webhookSecret as string | undefined @@ -41,7 +37,7 @@ export const servicenowHandler: WebhookProviderHandler = { const { isServiceNowEventMatch } = await import('@/triggers/servicenow/utils') const configuredTableName = providerConfig.tableName as string | undefined - const obj = asRecord(body) + const obj = toRecord(body) if (!isServiceNowEventMatch(triggerId, obj, configuredTableName)) { logger.debug( diff --git a/apps/sim/lib/webhooks/providers/tiktok.ts b/apps/sim/lib/webhooks/providers/tiktok.ts index 4196a11a2fd..a3aed74ab05 100644 --- a/apps/sim/lib/webhooks/providers/tiktok.ts +++ b/apps/sim/lib/webhooks/providers/tiktok.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { hmacSha256Hex } from '@sim/security/hmac' +import { toRecord, toRecordOrNull } from '@sim/utils/object' import { NextResponse } from 'next/server' import { env } from '@/lib/core/config/env' import type { @@ -93,20 +94,15 @@ export function verifyTikTokSignature( return null } -function asRecord(value: unknown): Record | null { - if (!value || typeof value !== 'object' || Array.isArray(value)) return null - return value as Record -} - /** * Parse the TikTok envelope `content` field (a JSON string) into an object. */ export function parseTikTokContent(content: unknown): Record { if (typeof content !== 'string' || content.length === 0) { - return asRecord(content) ?? {} + return toRecord(content) } try { - return asRecord(JSON.parse(content)) ?? {} + return toRecord(JSON.parse(content)) } catch { logger.warn('Failed to parse TikTok webhook content JSON string') return {} @@ -141,7 +137,7 @@ export const tiktokHandler: WebhookProviderHandler = { if (!triggerId) return true const { isTikTokEventMatch } = await import('@/triggers/tiktok/utils') - const event = stringField(asRecord(body) ?? {}, 'event') + const event = stringField(toRecord(body), 'event') if (!isTikTokEventMatch(triggerId, event)) { logger.debug( `[${requestId}] TikTok event mismatch for trigger ${triggerId}. Event: ${event}. Skipping.` @@ -152,7 +148,7 @@ export const tiktokHandler: WebhookProviderHandler = { }, async formatInput({ body }: FormatInputContext): Promise { - const envelope = asRecord(body) ?? {} + const envelope = toRecord(body) const content = parseTikTokContent(envelope.content) const event = typeof envelope.event === 'string' ? envelope.event : '' const commonInput: Record = { @@ -205,7 +201,7 @@ export const tiktokHandler: WebhookProviderHandler = { }, extractIdempotencyId(body: unknown) { - const envelope = asRecord(body) + const envelope = toRecordOrNull(body) if (!envelope) return null const event = typeof envelope.event === 'string' ? envelope.event : null diff --git a/apps/sim/providers/bedrock/streaming-tool-loop.ts b/apps/sim/providers/bedrock/streaming-tool-loop.ts index d79e4277b29..c84b2ead349 100644 --- a/apps/sim/providers/bedrock/streaming-tool-loop.ts +++ b/apps/sim/providers/bedrock/streaming-tool-loop.ts @@ -373,16 +373,14 @@ export function createBedrockStreamingToolLoopStream( assembledToolUses.map(async (toolUse) => { const toolCallStartTime = Date.now() const toolName = toolUse.name || '' - const toolArgs = isRecordLike(toolUse.input) ? toolUse.input : undefined + /** Already a non-null, non-array object: `parseToolInput` throws otherwise. */ + const toolArgs: Record = toolUse.input const toolUseId = toolUse.toolUseId || generateToolUseId(toolName) try { if (loopAbortController.signal.aborted) { throw new DOMException('Stream aborted', 'AbortError') } - if (!toolArgs) { - throw new Error(`Arguments for tool "${toolName}" must be an object`) - } const tool = request.tools?.find((t) => t.id === toolName) if (!tool) { @@ -482,7 +480,7 @@ export function createBedrockStreamingToolLoopStream( toolUse, toolUseId, toolName, - toolArgs: toolArgs ?? {}, + toolArgs, toolParams: {} as Record, result: { success: false as const, diff --git a/apps/sim/tools/dynatrace/utils.ts b/apps/sim/tools/dynatrace/utils.ts index 67ca53c5fab..cfe853e8129 100644 --- a/apps/sim/tools/dynatrace/utils.ts +++ b/apps/sim/tools/dynatrace/utils.ts @@ -1,4 +1,4 @@ -import { isRecordLike } from '@sim/utils/object' +import { toRecord, toRecordOrNull } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import type { DynatraceAttack, @@ -165,14 +165,6 @@ function toStringArray(value: unknown): string[] { return Array.isArray(value) ? (value as string[]) : [] } -function toRecord(value: unknown): Record { - return isRecordLike(value) ? (value as Record) : {} -} - -function toRecordOrNull(value: unknown): Record | null { - return isRecordLike(value) ? (value as Record) : null -} - /** Flattens an `EntityStub` (`{ entityId: { id, type }, name }`) into a single object. */ export function mapEntityStub(stub: unknown): DynatraceEntityStub | null { const record = toRecordOrNull(stub) diff --git a/apps/sim/tools/emailbison/utils.ts b/apps/sim/tools/emailbison/utils.ts index 07e8c3b2d71..f580db63053 100644 --- a/apps/sim/tools/emailbison/utils.ts +++ b/apps/sim/tools/emailbison/utils.ts @@ -1,4 +1,4 @@ -import { filterUndefined, isRecordLike } from '@sim/utils/object' +import { filterUndefined, isRecordLike, toRecord } from '@sim/utils/object' import type { EmailBisonBaseParams, EmailBisonCampaign, @@ -441,10 +441,6 @@ function mapReplyAttachment(value: unknown): EmailBisonReplyAttachment { } } -function toRecord(value: unknown): Record { - return isRecordLike(value) ? value : {} -} - function toArray(value: unknown): unknown[] { return Array.isArray(value) ? value : [] } diff --git a/apps/sim/tools/instantly/utils.ts b/apps/sim/tools/instantly/utils.ts index 222f7b06a37..43ff7fbee5f 100644 --- a/apps/sim/tools/instantly/utils.ts +++ b/apps/sim/tools/instantly/utils.ts @@ -1,4 +1,4 @@ -import { filterUndefined, isRecordLike } from '@sim/utils/object' +import { filterUndefined, isRecordLike, toRecord } from '@sim/utils/object' import type { InstantlyCampaign, InstantlyEmail, @@ -55,8 +55,9 @@ export async function parseInstantlyResponse(response: Response): Promise MAX_BATCH_RECORDS) { throw new Error(`Batch items must contain between 1 and ${MAX_BATCH_RECORDS} records`) } - if (items.some((item) => !isJsonObject(item))) { + if (items.some((item) => !isRecordLike(item))) { throw new Error('Every batch item must be a JSON object') } return items @@ -253,7 +253,7 @@ export async function executeNetSuiteRequest( let suiteTalkTimeoutId: ReturnType | undefined try { const request = buildRequest() - if (request.body !== undefined && !isJsonObject(request.body)) { + if (request.body !== undefined && !isRecordLike(request.body)) { throw new Error('NetSuite request body must be a JSON object') } const serializedBody = serializeRequestBody(request.body) @@ -563,7 +563,7 @@ function assertJsonBodyWithinLimit(body: unknown): void { state.ancestors.add(value) addJsonBytes(state, 1) frames.push({ kind: 'array', value, index: 0, depth }) - } else if (isJsonObject(value)) { + } else if (isRecordLike(value)) { const prototype = Object.getPrototypeOf(value) if (prototype !== Object.prototype && prototype !== null) throwNonPlainJsonError() rejectCustomJsonSerialization(value) @@ -667,13 +667,13 @@ function validateSuccessBody( if (successCase.body === 'none' && data !== null) { return `NetSuite HTTP ${successCase.status} response unexpectedly included a body` } - if (successCase.body === 'object' && !isJsonObject(data)) { + if (successCase.body === 'object' && !isRecordLike(data)) { return `NetSuite HTTP ${successCase.status} response did not include the documented JSON object` } - if (successCase.body === 'optional-object' && data !== null && !isJsonObject(data)) { + if (successCase.body === 'optional-object' && data !== null && !isRecordLike(data)) { return `NetSuite HTTP ${successCase.status} response was neither empty nor a JSON object` } - if (!successCase.validator || !isJsonObject(data)) return null + if (!successCase.validator || !isRecordLike(data)) return null switch (successCase.validator) { case 'collection-page': @@ -742,7 +742,7 @@ function validateMetadataCatalog(data: Record): string | null { return 'NetSuite metadata catalog response included invalid links' } for (const item of data.items) { - if (!isJsonObject(item) || !isNonEmptyString(item.name)) { + if (!isRecordLike(item) || !isNonEmptyString(item.name)) { return 'NetSuite metadata catalog response included an invalid record type' } if (item.links !== undefined && !Array.isArray(item.links)) { @@ -801,7 +801,7 @@ function validateRequiredProperties( type === 'array' ? Array.isArray(value) : type === 'object' - ? isJsonObject(value) + ? isRecordLike(value) : type === 'number' ? typeof value === 'number' && Number.isFinite(value) : typeof value === type @@ -815,7 +815,7 @@ function expectsAsyncJob(request: NetSuiteRequest): boolean { } function normalizeAuthParams(auth: NetSuiteAuthParams): NetSuiteAuthParams { - if (!isJsonObject(auth)) throw new Error('NetSuite credentials are required') + if (!isRecordLike(auth)) throw new Error('NetSuite credentials are required') return { oauthCredential: requiredTrim(auth.oauthCredential, 'NetSuite credential'), accessToken: requiredTrim(auth.accessToken ?? '', 'NetSuite access token'), @@ -860,23 +860,23 @@ function isIdempotentJobReplay(request: NetSuiteRequest, status: number, data: u const hasIdempotencyKey = Object.entries(request.headers ?? {}).some( ([name, value]) => name.toLowerCase() === 'x-netsuite-idempotency-key' && Boolean(value.trim()) ) - if (!hasIdempotencyKey || !isJsonObject(data)) return false + if (!hasIdempotencyKey || !isRecordLike(data)) return false const errorDetails = data['o:errorDetails'] return ( Array.isArray(errorDetails) && errorDetails.some( - (detail) => isJsonObject(detail) && detail['o:errorCode'] === 'IDEMPOTENCY_ERROR' + (detail) => isRecordLike(detail) && detail['o:errorCode'] === 'IDEMPOTENCY_ERROR' ) ) } function extractNetSuiteError(data: unknown, status: number, auth: NetSuiteAuthParams): string { - if (isJsonObject(data)) { + if (isRecordLike(data)) { const errorDetails = data['o:errorDetails'] if (Array.isArray(errorDetails)) { const summaries = errorDetails.flatMap((detail) => { - if (!isJsonObject(detail)) return [] + if (!isRecordLike(detail)) return [] const message = typeof detail.detail === 'string' ? detail.detail.trim() : '' const context = [ ['code', detail['o:errorCode']], @@ -921,7 +921,3 @@ function sanitizeErrorText(value: string, auth?: NetSuiteAuthParams): string { } return truncate(sanitized.replace(/\s+/g, ' ').trim(), 900) || 'Unknown error' } - -function isJsonObject(value: unknown): value is Record { - return isRecordLike(value) -} diff --git a/apps/sim/tools/rabbitmq/get_overview.ts b/apps/sim/tools/rabbitmq/get_overview.ts index aa6fa911bb9..63653e9138d 100644 --- a/apps/sim/tools/rabbitmq/get_overview.ts +++ b/apps/sim/tools/rabbitmq/get_overview.ts @@ -1,4 +1,4 @@ -import { isRecordLike } from '@sim/utils/object' +import { toRecord } from '@sim/utils/object' import type { RabbitmqGetOverviewParams, RabbitmqGetOverviewResponse } from '@/tools/rabbitmq/types' import { buildAuthHeaders, @@ -20,10 +20,6 @@ const EMPTY_OVERVIEW = { messageStats: {}, } as const -function asRecord(value: unknown): Record { - return isRecordLike(value) ? (value as Record) : {} -} - function asStringOrNull(value: unknown): string | null { return typeof value === 'string' ? value : null } @@ -64,9 +60,9 @@ export const rabbitmqGetOverviewTool: ToolConfig< erlangVersion: asStringOrNull(data?.erlang_version), clusterName: asStringOrNull(data?.cluster_name), node: asStringOrNull(data?.node), - objectTotals: asRecord(data?.object_totals), - queueTotals: asRecord(data?.queue_totals), - messageStats: asRecord(data?.message_stats), + objectTotals: toRecord(data?.object_totals), + queueTotals: toRecord(data?.queue_totals), + messageStats: toRecord(data?.message_stats), }, } }, diff --git a/apps/sim/tools/rabbitmq/utils.ts b/apps/sim/tools/rabbitmq/utils.ts index 08c420291f8..a834b7e03b0 100644 --- a/apps/sim/tools/rabbitmq/utils.ts +++ b/apps/sim/tools/rabbitmq/utils.ts @@ -1,5 +1,5 @@ import { isLoopbackIp } from '@sim/security/ssrf' -import { isRecordLike } from '@sim/utils/object' +import { toRecord } from '@sim/utils/object' import type { RabbitmqBinding, RabbitmqChannel, @@ -283,10 +283,6 @@ export function unwrapPaginated(data: unknown): PaginatedResult { } } -function asRecord(value: unknown): Record { - return isRecordLike(value) ? (value as Record) : {} -} - function asNumberOrNull(value: unknown): number | null { return typeof value === 'number' ? value : null } @@ -338,7 +334,7 @@ export const RABBITMQ_EXCHANGE_COLUMNS = [ * until it has collected stats for the queue, so they are nullable. */ export function projectQueue(raw: unknown): RabbitmqQueue { - const queue = asRecord(raw) + const queue = toRecord(raw) return { name: String(queue.name ?? ''), vhost: String(queue.vhost ?? ''), @@ -349,7 +345,7 @@ export function projectQueue(raw: unknown): RabbitmqQueue { exclusive: asBooleanOrNull(queue.exclusive), node: asStringOrNull(queue.node), policy: asStringOrNull(queue.policy), - arguments: asRecord(queue.arguments), + arguments: toRecord(queue.arguments), messages: asNumberOrNull(queue.messages), messagesReady: asNumberOrNull(queue.messages_ready), messagesUnacknowledged: asNumberOrNull(queue.messages_unacknowledged), @@ -359,7 +355,7 @@ export function projectQueue(raw: unknown): RabbitmqQueue { } export function projectExchange(raw: unknown): RabbitmqExchange { - const exchange = asRecord(raw) + const exchange = toRecord(raw) return { name: String(exchange.name ?? ''), vhost: String(exchange.vhost ?? ''), @@ -367,12 +363,12 @@ export function projectExchange(raw: unknown): RabbitmqExchange { durable: exchange.durable === true, autoDelete: exchange.auto_delete === true, internal: exchange.internal === true, - arguments: asRecord(exchange.arguments), + arguments: toRecord(exchange.arguments), } } export function projectBinding(raw: unknown): RabbitmqBinding { - const binding = asRecord(raw) + const binding = toRecord(raw) return { source: String(binding.source ?? ''), vhost: String(binding.vhost ?? ''), @@ -380,7 +376,7 @@ export function projectBinding(raw: unknown): RabbitmqBinding { destinationType: String(binding.destination_type ?? ''), routingKey: String(binding.routing_key ?? ''), propertiesKey: String(binding.properties_key ?? ''), - arguments: asRecord(binding.arguments), + arguments: toRecord(binding.arguments), } } @@ -390,7 +386,7 @@ export function projectBinding(raw: unknown): RabbitmqBinding { * makes truncation visible instead of silently handing back a short payload. */ export function projectMessage(raw: unknown, truncateLimit: number): RabbitmqMessage { - const message = asRecord(raw) + const message = toRecord(raw) const payloadBytes = typeof message.payload_bytes === 'number' ? message.payload_bytes : 0 return { truncated: payloadBytes > truncateLimit, @@ -401,7 +397,7 @@ export function projectMessage(raw: unknown, truncateLimit: number): RabbitmqMes routingKey: String(message.routing_key ?? ''), redelivered: message.redelivered === true, messageCount: typeof message.message_count === 'number' ? message.message_count : 0, - properties: asRecord(message.properties), + properties: toRecord(message.properties), } } @@ -522,7 +518,7 @@ export const RABBITMQ_POLICY_OUTPUT_PROPERTIES = { } as const export function projectVhost(raw: unknown): RabbitmqVhost { - const vhost = asRecord(raw) + const vhost = toRecord(raw) return { name: String(vhost.name ?? ''), description: asStringOrNull(vhost.description), @@ -532,12 +528,12 @@ export function projectVhost(raw: unknown): RabbitmqVhost { messages: asNumberOrNull(vhost.messages), messagesReady: asNumberOrNull(vhost.messages_ready), messagesUnacknowledged: asNumberOrNull(vhost.messages_unacknowledged), - clusterState: asRecord(vhost.cluster_state), + clusterState: toRecord(vhost.cluster_state), } } export function projectConnection(raw: unknown): RabbitmqConnection { - const connection = asRecord(raw) + const connection = toRecord(raw) return { name: String(connection.name ?? ''), user: String(connection.user ?? ''), @@ -554,8 +550,8 @@ export function projectConnection(raw: unknown): RabbitmqConnection { } export function projectChannel(raw: unknown): RabbitmqChannel { - const channel = asRecord(raw) - const connectionDetails = asRecord(channel.connection_details) + const channel = toRecord(raw) + const connectionDetails = toRecord(channel.connection_details) return { name: String(channel.name ?? ''), number: asNumberOrNull(channel.number), @@ -572,9 +568,9 @@ export function projectChannel(raw: unknown): RabbitmqChannel { } export function projectConsumer(raw: unknown): RabbitmqConsumer { - const consumer = asRecord(raw) - const queue = asRecord(consumer.queue) - const channelDetails = asRecord(consumer.channel_details) + const consumer = toRecord(raw) + const queue = toRecord(consumer.queue) + const channelDetails = toRecord(consumer.channel_details) return { consumerTag: String(consumer.consumer_tag ?? ''), queue: String(queue.name ?? ''), @@ -590,7 +586,7 @@ export function projectConsumer(raw: unknown): RabbitmqConsumer { } export function projectNode(raw: unknown): RabbitmqNode { - const node = asRecord(raw) + const node = toRecord(raw) return { name: String(node.name ?? ''), type: asStringOrNull(node.type), @@ -612,14 +608,14 @@ export function projectNode(raw: unknown): RabbitmqNode { } export function projectPolicy(raw: unknown): RabbitmqPolicy { - const policy = asRecord(raw) + const policy = toRecord(raw) return { name: String(policy.name ?? ''), vhost: String(policy.vhost ?? ''), pattern: String(policy.pattern ?? ''), applyTo: asStringOrNull(policy['apply-to']), priority: asNumberOrNull(policy.priority), - definition: asRecord(policy.definition), + definition: toRecord(policy.definition), } } diff --git a/apps/sim/tools/rocketlane/types.ts b/apps/sim/tools/rocketlane/types.ts index b9e90a23e3d..f638c5ccc3e 100644 --- a/apps/sim/tools/rocketlane/types.ts +++ b/apps/sim/tools/rocketlane/types.ts @@ -1,4 +1,4 @@ -import { isRecordLike } from '@sim/utils/object' +import { toRecordOrNull } from '@sim/utils/object' import type { OutputProperty, ToolResponse } from '@/tools/types' /** Base URL for the Rocketlane REST API (v1.0). */ @@ -58,10 +58,6 @@ function asBoolean(value: unknown): boolean | null { return typeof value === 'boolean' ? value : null } -function asObject(value: unknown): Raw | null { - return isRecordLike(value) ? (value as Raw) : null -} - function asArray(value: unknown): unknown[] { return Array.isArray(value) ? value : [] } @@ -77,7 +73,7 @@ export interface RocketlaneUserSummary { } export function mapUserSummary(value: unknown): RocketlaneUserSummary | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { userId: asNumber(raw.userId), @@ -103,7 +99,7 @@ export interface RocketlanePagination { } export function mapPagination(value: unknown): RocketlanePagination { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { pageSize: asNumber(raw.pageSize), hasMore: asBoolean(raw.hasMore), @@ -240,7 +236,7 @@ export interface RocketlaneTask { } function mapTaskProjectRef(value: unknown): RocketlaneTaskProjectRef | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { projectId: asNumber(raw.projectId), @@ -249,7 +245,7 @@ function mapTaskProjectRef(value: unknown): RocketlaneTaskProjectRef | null { } function mapTaskPhaseRef(value: unknown): RocketlaneTaskPhaseRef | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { phaseId: asNumber(raw.phaseId), @@ -258,7 +254,7 @@ function mapTaskPhaseRef(value: unknown): RocketlaneTaskPhaseRef | null { } function mapTaskChoice(value: unknown): RocketlaneTaskChoice | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { value: asNumber(raw.value), @@ -267,7 +263,7 @@ function mapTaskChoice(value: unknown): RocketlaneTaskChoice | null { } function mapTaskRole(value: unknown): RocketlaneTaskRole | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { roleId: asNumber(raw.roleId), @@ -276,7 +272,7 @@ function mapTaskRole(value: unknown): RocketlaneTaskRole | null { } function mapTaskPlaceholder(value: unknown): RocketlaneTaskPlaceholder { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { placeholderId: asNumber(raw.placeholderId), placeholderName: asString(raw.placeholderName), @@ -285,7 +281,7 @@ function mapTaskPlaceholder(value: unknown): RocketlaneTaskPlaceholder { } function mapTaskAssignees(value: unknown): RocketlaneTaskAssignees | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { members: asArray(raw.members) @@ -296,7 +292,7 @@ function mapTaskAssignees(value: unknown): RocketlaneTaskAssignees | null { } function mapTaskFollowers(value: unknown): RocketlaneTaskFollowers | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { members: asArray(raw.members) @@ -306,7 +302,7 @@ function mapTaskFollowers(value: unknown): RocketlaneTaskFollowers | null { } function mapTaskLite(value: unknown): RocketlaneTaskLite { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { taskId: asNumber(raw.taskId), taskName: asString(raw.taskName), @@ -314,7 +310,7 @@ function mapTaskLite(value: unknown): RocketlaneTaskLite { } function mapTaskField(value: unknown): RocketlaneTaskField { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { fieldId: asNumber(raw.fieldId), fieldLabel: asString(raw.fieldLabel), @@ -324,7 +320,7 @@ function mapTaskField(value: unknown): RocketlaneTaskField { } function mapTaskTimeEntryCategory(value: unknown): RocketlaneTaskTimeEntryCategory | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { categoryId: asNumber(raw.categoryId), @@ -333,7 +329,7 @@ function mapTaskTimeEntryCategory(value: unknown): RocketlaneTaskTimeEntryCatego } function mapTaskBudget(value: unknown): RocketlaneTaskBudget { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { budgetId: asNumber(raw.budgetId), budgetName: asString(raw.budgetName), @@ -341,7 +337,7 @@ function mapTaskBudget(value: unknown): RocketlaneTaskBudget { } export function mapTask(value: unknown): RocketlaneTask { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { taskId: asNumber(raw.taskId), taskName: asString(raw.taskName), @@ -368,7 +364,7 @@ export function mapTask(value: unknown): RocketlaneTask { assignees: mapTaskAssignees(raw.assignees), followers: mapTaskFollowers(raw.followers), dependencies: asArray(raw.dependencies).map(mapTaskLite), - parent: asObject(raw.parent) ? mapTaskLite(raw.parent) : null, + parent: toRecordOrNull(raw.parent) ? mapTaskLite(raw.parent) : null, externalReferenceId: asString(raw.externalReferenceId), billable: asBoolean(raw.billable), timeEntryCategory: mapTaskTimeEntryCategory(raw.timeEntryCategory), @@ -766,7 +762,7 @@ export interface RocketlaneProjectCompany { } export function mapProjectCompany(value: unknown): RocketlaneProjectCompany | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { companyId: asNumber(raw.companyId), @@ -788,7 +784,7 @@ export interface RocketlaneProjectStatus { } export function mapProjectStatus(value: unknown): RocketlaneProjectStatus | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { value: asNumber(raw.value), @@ -810,7 +806,7 @@ export interface RocketlaneProjectField { } export function mapProjectField(value: unknown): RocketlaneProjectField { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { fieldId: asNumber(raw.fieldId), fieldLabel: asString(raw.fieldLabel), @@ -837,7 +833,7 @@ export interface RocketlaneProjectPhase { } export function mapProjectPhase(value: unknown): RocketlaneProjectPhase { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { phaseId: asNumber(raw.phaseId), phaseName: asString(raw.phaseName), @@ -858,7 +854,7 @@ export interface RocketlaneProjectSource { } export function mapProjectSource(value: unknown): RocketlaneProjectSource { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { prefix: asString(raw.prefix), startDate: asString(raw.startDate), @@ -890,7 +886,7 @@ export interface RocketlaneProjectTeamMembers { } export function mapProjectTeamMembers(value: unknown): RocketlaneProjectTeamMembers { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { members: asArray(raw.members) .map(mapUserSummary) @@ -940,12 +936,12 @@ export interface RocketlaneProjectFinancials { } export function mapProjectFinancials(value: unknown): RocketlaneProjectFinancials | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null - const fixedFeeContract = asObject(raw.fixedFeeContract) ?? {} - const timeAndMaterialContract = asObject(raw.timeAndMaterialContract) ?? {} - const rateCard = asObject(timeAndMaterialContract.rateCard) ?? {} - const subscriptionContract = asObject(raw.subscriptionContract) ?? {} + const fixedFeeContract = toRecordOrNull(raw.fixedFeeContract) ?? {} + const timeAndMaterialContract = toRecordOrNull(raw.timeAndMaterialContract) ?? {} + const rateCard = toRecordOrNull(timeAndMaterialContract.rateCard) ?? {} + const subscriptionContract = toRecordOrNull(raw.subscriptionContract) ?? {} return { contractType: asString(raw.contractType), revenueRecognitionType: asString(raw.revenueRecognitionType), @@ -1063,7 +1059,7 @@ export interface RocketlaneProject { } export function mapProject(value: unknown): RocketlaneProject { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { projectId: asNumber(raw.projectId), projectName: asString(raw.projectName), @@ -1361,9 +1357,9 @@ export interface RocketlanePlaceholderRole { } export function mapPlaceholder(value: unknown): RocketlanePlaceholder { - const raw = asObject(value) ?? {} - const project = asObject(raw.project) - const role = asObject(raw.role) + const raw = toRecordOrNull(value) ?? {} + const project = toRecordOrNull(raw.project) + const role = toRecordOrNull(raw.role) return { placeholderId: asNumber(raw.placeholderId), placeholderName: asString(raw.placeholderName), @@ -1450,9 +1446,9 @@ export interface RocketlanePlaceholderRef { } export function mapPlaceholderMapping(value: unknown): RocketlanePlaceholderMapping { - const raw = asObject(value) ?? {} - const placeholder = asObject(raw.placeholder) - const user = asObject(raw.user) + const raw = toRecordOrNull(value) ?? {} + const placeholder = toRecordOrNull(raw.placeholder) + const user = toRecordOrNull(raw.user) return { placeholder: placeholder ? { @@ -1735,7 +1731,7 @@ export interface RocketlaneFieldOption { } export function mapFieldOption(value: unknown): RocketlaneFieldOption { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { optionValue: asNumber(raw.optionValue), optionLabel: asString(raw.optionLabel), @@ -1776,7 +1772,7 @@ export interface RocketlaneField { } export function mapField(value: unknown): RocketlaneField { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { fieldId: asNumber(raw.fieldId), fieldLabel: asString(raw.fieldLabel), @@ -1971,9 +1967,9 @@ export interface RocketlanePhase { } export function mapPhase(value: unknown): RocketlanePhase { - const raw = asObject(value) ?? {} - const project = asObject(raw.project) - const status = asObject(raw.status) + const raw = toRecordOrNull(value) ?? {} + const project = toRecordOrNull(raw.project) + const status = toRecordOrNull(raw.status) return { phaseId: asNumber(raw.phaseId), phaseName: asString(raw.phaseName), @@ -2201,7 +2197,7 @@ export interface RocketlaneTimeEntry { } function mapTimeEntryProject(value: unknown): RocketlaneTimeEntryProject | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { projectId: asNumber(raw.projectId), @@ -2210,7 +2206,7 @@ function mapTimeEntryProject(value: unknown): RocketlaneTimeEntryProject | null } function mapTimeEntryTask(value: unknown): RocketlaneTimeEntryTask | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { taskId: asNumber(raw.taskId), @@ -2219,7 +2215,7 @@ function mapTimeEntryTask(value: unknown): RocketlaneTimeEntryTask | null { } function mapTimeEntryPhase(value: unknown): RocketlaneTimeEntryPhase | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { phaseId: asNumber(raw.phaseId), @@ -2228,7 +2224,7 @@ function mapTimeEntryPhase(value: unknown): RocketlaneTimeEntryPhase | null { } export function mapTimeEntryCategory(value: unknown): RocketlaneTimeEntryCategory | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { categoryId: asNumber(raw.categoryId), @@ -2237,7 +2233,7 @@ export function mapTimeEntryCategory(value: unknown): RocketlaneTimeEntryCategor } function mapTimeEntryRate(value: unknown): RocketlaneTimeEntryRate | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { rate: asNumber(raw.rate), @@ -2246,7 +2242,7 @@ function mapTimeEntryRate(value: unknown): RocketlaneTimeEntryRate | null { } function mapTimeEntryField(value: unknown): RocketlaneTimeEntryField { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { fieldId: asNumber(raw.fieldId), fieldLabel: asString(raw.fieldLabel), @@ -2256,7 +2252,7 @@ function mapTimeEntryField(value: unknown): RocketlaneTimeEntryField { } export function mapTimeEntry(value: unknown): RocketlaneTimeEntry { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { timeEntryId: asNumber(raw.timeEntryId), date: asString(raw.date), @@ -2624,8 +2620,8 @@ export interface RocketlaneSpace { } export function mapSpace(value: unknown): RocketlaneSpace { - const raw = asObject(value) ?? {} - const project = asObject(raw.project) + const raw = toRecordOrNull(value) ?? {} + const project = toRecordOrNull(raw.project) return { spaceId: asNumber(raw.spaceId), spaceName: asString(raw.spaceName), @@ -2781,9 +2777,9 @@ export interface RocketlaneSpaceDocument { } export function mapSpaceDocument(value: unknown): RocketlaneSpaceDocument { - const raw = asObject(value) ?? {} - const space = asObject(raw.space) - const source = asObject(raw.source) + const raw = toRecordOrNull(value) ?? {} + const space = toRecordOrNull(raw.space) + const source = toRecordOrNull(raw.source) return { spaceDocumentId: asNumber(raw.spaceDocumentId), spaceDocumentName: asString(raw.spaceDocumentName), @@ -3004,11 +3000,11 @@ export interface RocketlaneUser { } export function mapUser(value: unknown): RocketlaneUser { - const raw = asObject(value) ?? {} - const role = asObject(raw.role) - const company = asObject(raw.company) - const permission = asObject(raw.permission) - const holidayCalendar = asObject(raw.holidayCalendar) + const raw = toRecordOrNull(value) ?? {} + const role = toRecordOrNull(raw.role) + const company = toRecordOrNull(raw.company) + const permission = toRecordOrNull(raw.permission) + const holidayCalendar = toRecordOrNull(raw.holidayCalendar) return { userId: asNumber(raw.userId), email: asString(raw.email), @@ -3035,7 +3031,7 @@ export function mapUser(value: unknown): RocketlaneUser { } : null, fields: asArray(raw.fields).map((field) => { - const fieldRaw = asObject(field) ?? {} + const fieldRaw = toRecordOrNull(field) ?? {} return { fieldId: asNumber(fieldRaw.fieldId), fieldLabel: asString(fieldRaw.fieldLabel), @@ -3331,7 +3327,7 @@ export interface RocketlaneTimeOff { } function mapTimeOffNotifyUsers(value: unknown): RocketlaneTimeOffNotifyUsers | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { projectOwners: asBoolean(raw.projectOwners), @@ -3345,7 +3341,7 @@ function mapTimeOffNotifyUsers(value: unknown): RocketlaneTimeOffNotifyUsers | n * Maps a raw time-off payload to the normalized {@link RocketlaneTimeOff} shape. */ export function mapTimeOff(value: unknown): RocketlaneTimeOff { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { timeOffId: asNumber(raw.timeOffId), user: mapUserSummary(raw.user), @@ -3523,7 +3519,7 @@ export interface RocketlaneResourceAllocation { } function mapResourceAllocationRole(value: unknown): RocketlaneResourceAllocationRole | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { roleId: asNumber(raw.roleId), @@ -3532,7 +3528,7 @@ function mapResourceAllocationRole(value: unknown): RocketlaneResourceAllocation } function mapResourceAllocationMember(value: unknown): RocketlaneResourceAllocationMember | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null const user = mapUserSummary(raw) if (!user) return null @@ -3545,7 +3541,7 @@ function mapResourceAllocationMember(value: unknown): RocketlaneResourceAllocati function mapResourceAllocationPlaceholder( value: unknown ): RocketlaneResourceAllocationPlaceholder | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { placeholderId: asNumber(raw.placeholderId), @@ -3557,7 +3553,7 @@ function mapResourceAllocationPlaceholder( function mapResourceAllocationDuration( value: unknown ): RocketlaneResourceAllocationDuration | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { daysConsider: asNumber(raw.daysConsider), @@ -3568,7 +3564,7 @@ function mapResourceAllocationDuration( } function mapResourceAllocationProject(value: unknown): RocketlaneResourceAllocationProject | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { projectId: asNumber(raw.projectId), @@ -3577,7 +3573,7 @@ function mapResourceAllocationProject(value: unknown): RocketlaneResourceAllocat } function mapResourceAllocationTask(value: unknown): RocketlaneResourceAllocationTask { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { taskId: asNumber(raw.taskId), taskName: asString(raw.taskName), @@ -3589,7 +3585,7 @@ function mapResourceAllocationTask(value: unknown): RocketlaneResourceAllocation * {@link RocketlaneResourceAllocation} shape. */ export function mapResourceAllocation(value: unknown): RocketlaneResourceAllocation { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { startDate: asString(raw.startDate), endDate: asString(raw.endDate), @@ -3920,7 +3916,7 @@ export interface RocketlaneInvoiceLineItem { } function mapInvoiceCompany(value: unknown): RocketlaneInvoiceCompany | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { companyId: asNumber(raw.companyId), @@ -3930,7 +3926,7 @@ function mapInvoiceCompany(value: unknown): RocketlaneInvoiceCompany | null { } function mapInvoiceProject(value: unknown): RocketlaneInvoiceProject { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { projectId: asNumber(raw.projectId), projectName: asString(raw.projectName), @@ -3938,7 +3934,7 @@ function mapInvoiceProject(value: unknown): RocketlaneInvoiceProject { } function mapInvoiceField(value: unknown): RocketlaneInvoiceField { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { fieldId: asNumber(raw.fieldId), fieldLabel: asString(raw.fieldLabel), @@ -3948,7 +3944,7 @@ function mapInvoiceField(value: unknown): RocketlaneInvoiceField { } function mapInvoiceAttachment(value: unknown): RocketlaneInvoiceAttachment { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { attachmentId: asNumber(raw.attachmentId), attachmentName: asString(raw.attachmentName), @@ -3963,7 +3959,7 @@ function mapInvoiceAttachment(value: unknown): RocketlaneInvoiceAttachment { * Maps a raw invoice payload to the normalized {@link RocketlaneInvoice} shape. */ export function mapInvoice(value: unknown): RocketlaneInvoice { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { invoiceId: asNumber(raw.invoiceId), invoiceNumber: asString(raw.invoiceNumber), @@ -3993,7 +3989,7 @@ export function mapInvoice(value: unknown): RocketlaneInvoice { * Maps a raw payment-record payload to the normalized {@link RocketlaneInvoicePayment} shape. */ export function mapInvoicePayment(value: unknown): RocketlaneInvoicePayment { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { paymentId: asNumber(raw.paymentId), paymentRecordType: asString(raw.paymentRecordType), @@ -4005,7 +4001,7 @@ export function mapInvoicePayment(value: unknown): RocketlaneInvoicePayment { } function mapInvoiceLineItemTaxCode(value: unknown): RocketlaneInvoiceLineItemTaxCode | null { - const raw = asObject(value) + const raw = toRecordOrNull(value) if (!raw) return null return { taxCodeId: asNumber(raw.taxCodeId), @@ -4016,7 +4012,7 @@ function mapInvoiceLineItemTaxCode(value: unknown): RocketlaneInvoiceLineItemTax } function mapInvoiceLineItemTaxComponent(value: unknown): RocketlaneInvoiceLineItemTaxComponent { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { taxComponentId: asNumber(raw.taxComponentId), taxComponentName: asString(raw.taxComponentName), @@ -4030,7 +4026,7 @@ function mapInvoiceLineItemTaxComponent(value: unknown): RocketlaneInvoiceLineIt * Maps a raw invoice line-item payload to the normalized {@link RocketlaneInvoiceLineItem} shape. */ export function mapInvoiceLineItem(value: unknown): RocketlaneInvoiceLineItem { - const raw = asObject(value) ?? {} + const raw = toRecordOrNull(value) ?? {} return { invoiceLineItemId: asNumber(raw.invoiceLineItemId), description: asString(raw.description), diff --git a/apps/sim/tools/smartlead/utils.ts b/apps/sim/tools/smartlead/utils.ts index d0efc3f22f6..097cfeaaa12 100644 --- a/apps/sim/tools/smartlead/utils.ts +++ b/apps/sim/tools/smartlead/utils.ts @@ -1,4 +1,4 @@ -import { filterUndefined, isRecordLike } from '@sim/utils/object' +import { filterUndefined, isRecordLike, toRecord } from '@sim/utils/object' import type { SmartleadBaseParams, SmartleadCampaign, @@ -550,10 +550,6 @@ export function mapCreatedCampaign(record: Record): { } } -function toRecord(value: unknown): Record { - return isRecordLike(value) ? value : {} -} - function toArray(value: unknown): unknown[] { return Array.isArray(value) ? value : [] } diff --git a/apps/sim/tools/tiktok/utils.ts b/apps/sim/tools/tiktok/utils.ts index 7cfeb4440db..42b621768bb 100644 --- a/apps/sim/tools/tiktok/utils.ts +++ b/apps/sim/tools/tiktok/utils.ts @@ -1,5 +1,5 @@ import { getErrorMessage } from '@sim/utils/errors' -import { isRecordLike } from '@sim/utils/object' +import { toRecordOrNull } from '@sim/utils/object' import { truncate } from '@sim/utils/string' import type { ZodType } from 'zod' import { isPayloadSizeLimitError, readResponseTextWithLimit } from '@/lib/core/utils/stream-limits' @@ -80,12 +80,8 @@ interface ReadTikTokApiResponseOptions { signal?: AbortSignal } -function asRecord(value: unknown): Record | null { - return isRecordLike(value) ? value : null -} - function parseTikTokError(value: unknown): TikTokApiError | null { - const error = asRecord(value) + const error = toRecordOrNull(value) if (!error) return null const code = typeof error.code === 'string' ? error.code : null @@ -146,7 +142,7 @@ async function readJsonObject( } } - const body = asRecord(parsed) + const body = toRecordOrNull(parsed) if (!body) { return { body: null, @@ -243,7 +239,7 @@ export async function readTikTokDraftInitResponse( } } - const output = asRecord(parsed.body.output) + const output = toRecordOrNull(parsed.body.output) const publishId = typeof output?.publishId === 'string' ? output.publishId : '' return publishId ? { success: true, publishId } diff --git a/apps/sim/tools/uptimerobot/types.ts b/apps/sim/tools/uptimerobot/types.ts index ba440a640ff..9c906cc265f 100644 --- a/apps/sim/tools/uptimerobot/types.ts +++ b/apps/sim/tools/uptimerobot/types.ts @@ -1,5 +1,5 @@ import { getErrorMessage } from '@sim/utils/errors' -import { isRecordLike } from '@sim/utils/object' +import { toRecordOrNull } from '@sim/utils/object' import type { OutputProperty, ToolResponse } from '@/tools/types' /** Base URL for the UptimeRobot v3 REST API. */ @@ -196,10 +196,6 @@ function asEnum(value: unknown): string | null { return typeof value === 'string' ? value : null } -function asObject(value: unknown): Raw | null { - return isRecordLike(value) ? (value as Raw) : null -} - function asArray(value: unknown): unknown[] { return Array.isArray(value) ? value : [] } @@ -311,7 +307,7 @@ export function buildMaintenanceWindowBody( // region Mappers (raw API JSON -> typed output objects) export function mapMonitor(raw: Raw): UptimeRobotMonitor { - const lastIncident = asObject(raw.lastIncident) + const lastIncident = toRecordOrNull(raw.lastIncident) return { id: asNumber(raw.id) ?? 0, friendlyName: asString(raw.friendlyName) ?? '', @@ -337,7 +333,7 @@ export function mapMonitor(raw: Raw): UptimeRobotMonitor { lastIncidentId: asString(raw.lastIncidentId), groupId: asNumber(raw.groupId), tags: asArray(raw.tags).map((tag) => { - const t = asObject(tag) ?? {} + const t = toRecordOrNull(tag) ?? {} return { id: asNumber(t.id) ?? 0, name: asString(t.name) ?? '', @@ -345,7 +341,7 @@ export function mapMonitor(raw: Raw): UptimeRobotMonitor { } }), assignedAlertContacts: asArray(raw.assignedAlertContacts).map((contact) => { - const c = asObject(contact) ?? {} + const c = toRecordOrNull(contact) ?? {} return { alertContactId: asNumber(c.alertContactId) ?? 0, threshold: asNumber(c.threshold) ?? 0, @@ -420,7 +416,7 @@ export function mapPsp(raw: Raw): UptimeRobotPsp { } export function mapIncidentSummary(raw: Raw): UptimeRobotIncidentSummary { - const monitor = asObject(raw.monitor) ?? {} + const monitor = toRecordOrNull(raw.monitor) ?? {} return { id: asString(raw.id) ?? '', status: asEnum(raw.status), @@ -438,7 +434,7 @@ export function mapIncidentSummary(raw: Raw): UptimeRobotIncidentSummary { } export function mapIncidentDetail(raw: Raw): UptimeRobotIncidentDetail { - const rootCause = asObject(raw.rootCause) + const rootCause = toRecordOrNull(raw.rootCause) return { id: asString(raw.id) ?? '', status: asEnum(raw.status), @@ -458,7 +454,7 @@ export function mapIncidentDetail(raw: Raw): UptimeRobotIncidentDetail { } export function mapAccount(raw: Raw): UptimeRobotAccount { - const subscription = asObject(raw.activeSubscription) ?? {} + const subscription = toRecordOrNull(raw.activeSubscription) ?? {} return { email: asString(raw.email), fullName: asString(raw.fullName), diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 880c7716507..9568f825ac6 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -20,6 +20,8 @@ export { isRecordLike, omit, sortObjectKeysDeep, + toRecord, + toRecordOrNull, } from './object' export { generateRandomBytes, diff --git a/packages/utils/src/object.test.ts b/packages/utils/src/object.test.ts index 29f21e2a6d6..78c3a900d87 100644 --- a/packages/utils/src/object.test.ts +++ b/packages/utils/src/object.test.ts @@ -2,7 +2,13 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { isPlainRecord, isRecordLike, sortObjectKeysDeep } from './object.js' +import { + isPlainRecord, + isRecordLike, + sortObjectKeysDeep, + toRecord, + toRecordOrNull, +} from './object.js' class Sample { value = 1 @@ -23,6 +29,33 @@ describe('isRecordLike', () => { }) }) +describe('toRecord', () => { + it('returns the value itself for records, preserving identity', () => { + const source = { a: 1 } + expect(toRecord(source)).toBe(source) + }) + + it('falls back to a fresh empty object for arrays, null, and primitives', () => { + expect(toRecord([])).toEqual({}) + expect(toRecord(null)).toEqual({}) + expect(toRecord('nope')).toEqual({}) + expect(toRecord(undefined)).not.toBe(toRecord(undefined)) + }) +}) + +describe('toRecordOrNull', () => { + it('returns the value itself for records, preserving identity', () => { + const source = { a: 1 } + expect(toRecordOrNull(source)).toBe(source) + }) + + it('falls back to null for arrays, null, and primitives', () => { + expect(toRecordOrNull([])).toBeNull() + expect(toRecordOrNull(null)).toBeNull() + expect(toRecordOrNull(42)).toBeNull() + }) +}) + describe('isPlainRecord', () => { it('returns true for plain objects', () => { expect(isPlainRecord({})).toBe(true) diff --git a/packages/utils/src/object.ts b/packages/utils/src/object.ts index 92505aa34bd..6ff480711a9 100644 --- a/packages/utils/src/object.ts +++ b/packages/utils/src/object.ts @@ -46,6 +46,27 @@ export function isRecordLike(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) } +/** + * Coerces {@link value} to a record, falling back to an empty object. The + * coercion counterpart to {@link isRecordLike}, for the common + * `isRecordLike(v) ? v : {}` shape when reading an untyped payload whose absence + * should read as "no fields" rather than as an error. + * + * @remarks Returns a fresh `{}` on every miss, so the result is never shared. + */ +export function toRecord(value: unknown): Record { + return isRecordLike(value) ? value : {} +} + +/** + * Coerces {@link value} to a record, falling back to `null`. Use over + * {@link toRecord} when callers must distinguish "absent or malformed" from + * "present but empty". + */ +export function toRecordOrNull(value: unknown): Record | null { + return isRecordLike(value) ? value : null +} + /** * Recursively sorts the keys of every plain object reachable from {@link value}, * preserving array order while recursing into array elements. Primitives and From 5c3295f8a423350472b9a09bfbc6ebe7c62e8ae1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 14 Aug 2026 15:42:13 -0700 Subject: [PATCH 4/6] fix(webhooks): guard non-string GitLab ref, and consolidate the last record helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two audits covered the six helpers held back from the previous commit for not being equivalent to isRecordLike. Five are now migrated; one is deliberately not. `gitlab.ts` carried a real crash path, independent of the guard work: const ref = (b.ref as string) || '' const branch = ref.replace('refs/heads/', '') The cast is unchecked and `|| ''` only catches falsy values, so a truthy non-string body field — `{"ref": 12345}` — reaches `.replace` and throws `TypeError: ref.replace is not a function` inside `formatInput`. That runs in the background worker after the webhook is already 200-ACKed, and GitLab does not auto-retry, so the delivery is lost silently. Now checks `typeof`. `pagerduty`/`zendesk`/`gitlab` each defined `asRecord` as `(value as Record) || {}`, which type-checks nothing — a string or array passed through and was then spread into the workflow trigger payload (`gitlab.ts:114`) as character- or index-keyed garbage. All three now use the shared `toRecord`. These sit behind `verifyProviderAuth`, so reaching them requires the shared secret; this is robustness, not authorization. `copilot/resources/extraction.ts` and `pi/core/events.ts` were array-permissive but provably inert — extraction.ts has no key enumeration or spread anywhere, and events.ts only diverges by returning `null` instead of `{type:'other'}` for an array, which every consumer already no-ops on. Pinned with a test. `edit-workflow/validation.ts` is left permissive ON PURPOSE. Its `Object.entries` walk mirrors the unguarded walk in `operations.ts:86,191`, so an array-shaped `nestedNodes` from the model is currently visited by both. Tightening only the validation side would stop `collectHostedApiKeyInput` from stripping platform-managed API keys while the apply side still creates those child blocks. Both paths have to change together, with tests, in their own PR. --- .../executor/handlers/pi/core/events.test.ts | 1 + apps/sim/executor/handlers/pi/core/events.ts | 20 ++++++------ apps/sim/lib/copilot/resources/extraction.ts | 31 +++++++++---------- apps/sim/lib/webhooks/providers/gitlab.ts | 18 +++++------ apps/sim/lib/webhooks/providers/pagerduty.ts | 19 +++++------- apps/sim/lib/webhooks/providers/zendesk.ts | 23 ++++++-------- 6 files changed, 49 insertions(+), 63 deletions(-) diff --git a/apps/sim/executor/handlers/pi/core/events.test.ts b/apps/sim/executor/handlers/pi/core/events.test.ts index 3fb216b56b6..f08d162d101 100644 --- a/apps/sim/executor/handlers/pi/core/events.test.ts +++ b/apps/sim/executor/handlers/pi/core/events.test.ts @@ -133,6 +133,7 @@ describe('normalizePiEvent', () => { expect(normalizePiEvent({ type: 'queue_update' })).toEqual({ type: 'other' }) expect(normalizePiEvent('nope')).toBeNull() expect(normalizePiEvent(null)).toBeNull() + expect(normalizePiEvent([])).toBeNull() }) }) diff --git a/apps/sim/executor/handlers/pi/core/events.ts b/apps/sim/executor/handlers/pi/core/events.ts index 0ff0864726a..2c05ad00b45 100644 --- a/apps/sim/executor/handlers/pi/core/events.ts +++ b/apps/sim/executor/handlers/pi/core/events.ts @@ -6,6 +6,8 @@ * run totals (final text, token usage, tool calls) the handler reports. */ +import { toRecordOrNull } from '@sim/utils/object' + /** A single normalized event emitted during a Pi run. */ export type PiEvent = | { type: 'text'; text: string } @@ -71,10 +73,6 @@ export function streamTextForEvent(event: PiEvent): string | null { return event.type === 'text' ? event.text : null } -function asRecord(value: unknown): Record | null { - return typeof value === 'object' && value !== null ? (value as Record) : null -} - function asString(value: unknown): string { return typeof value === 'string' ? value : '' } @@ -86,7 +84,7 @@ function asNumber(value: unknown): number { function extractAssistantText(message: Record): string { if (!Array.isArray(message.content)) return '' return message.content - .map((block) => asRecord(block)) + .map((block) => toRecordOrNull(block)) .filter((block): block is Record => block !== null) .filter((block) => asString(block.type) === 'text') .map((block) => asString(block.text)) @@ -104,11 +102,11 @@ function extractUsage( ev: Record ): { inputTokens: number; outputTokens: number } | null { const candidates: Array> = [] - const direct = asRecord(ev.usage) + const direct = toRecordOrNull(ev.usage) if (direct) candidates.push(direct) - const message = asRecord(ev.message) + const message = toRecordOrNull(ev.message) if (message) { - const messageUsage = asRecord(message.usage) + const messageUsage = toRecordOrNull(message.usage) if (messageUsage) candidates.push(messageUsage) } @@ -133,12 +131,12 @@ function extractUsage( * arrives only on the local and review paths and the cloud ones silently lose it. */ export function normalizePiEvent(raw: unknown): PiEvent | null { - const ev = asRecord(raw) + const ev = toRecordOrNull(raw) if (!ev) return null switch (asString(ev.type)) { case 'message_update': { - const assistantEvent = asRecord(ev.assistantMessageEvent) + const assistantEvent = toRecordOrNull(ev.assistantMessageEvent) const deltaType = assistantEvent ? asString(assistantEvent.type) : '' const delta = assistantEvent ? asString(assistantEvent.delta) : '' if (deltaType === 'text_delta') return { type: 'text', text: delta } @@ -157,7 +155,7 @@ export function normalizePiEvent(raw: unknown): PiEvent | null { if (ev.willRetry === true) return { type: 'other' } const messages = Array.isArray(ev.messages) ? ev.messages : [] for (let index = messages.length - 1; index >= 0; index -= 1) { - const message = asRecord(messages[index]) + const message = toRecordOrNull(messages[index]) if (!message || asString(message.role) !== 'assistant') continue const stopReason = asString(message.stopReason) if (stopReason === 'error' || stopReason === 'aborted') { diff --git a/apps/sim/lib/copilot/resources/extraction.ts b/apps/sim/lib/copilot/resources/extraction.ts index 2a614d944b6..fc2fd57e963 100644 --- a/apps/sim/lib/copilot/resources/extraction.ts +++ b/apps/sim/lib/copilot/resources/extraction.ts @@ -1,3 +1,4 @@ +import { toRecord } from '@sim/utils/object' import { CreateFile, CreateWorkflow, @@ -39,19 +40,15 @@ export function isResourceToolName(toolName: string): boolean { return RESOURCE_TOOL_NAMES.has(toolName) } -function asRecord(value: unknown): Record { - return value && typeof value === 'object' ? (value as Record) : {} -} - function getOperation(params: Record | undefined): string | undefined { - const args = asRecord(params?.args) + const args = toRecord(params?.args) return (args.operation ?? params?.operation) as string | undefined } function getWorkspaceFileTarget( params: Record | undefined ): Record { - return asRecord(params?.target) + return toRecord(params?.target) } const READ_ONLY_TABLE_OPS = new Set(['get', 'get_schema', 'get_row', 'query_rows']) @@ -70,8 +67,8 @@ export function extractResourcesFromToolResult( ): ChatResource[] { if (!isResourceToolName(toolName)) return [] - const result = asRecord(output) - const data = asRecord(result.data) + const result = toRecord(output) + const data = toRecord(result.data) switch (toolName) { case UserTable.id: { @@ -95,11 +92,11 @@ export function extractResourcesFromToolResult( }, ] } - const table = asRecord(data.table) + const table = toRecord(data.table) if (table.id) { return [{ type: 'table', id: table.id as string, title: (table.name as string) || 'Table' }] } - const args = asRecord(params?.args) + const args = toRecord(params?.args) const tableId = (data.tableId as string) ?? (args.tableId as string) ?? (params?.tableId as string) if (tableId) { @@ -112,7 +109,7 @@ export function extractResourcesFromToolResult( case CreateFile.id: case WorkspaceFile.id: { - const file = asRecord(data.file) + const file = toRecord(data.file) if (file.id) { return [{ type: 'file', id: file.id as string, title: (file.name as string) || 'File' }] } @@ -184,7 +181,7 @@ export function extractResourcesFromToolResult( case KnowledgeBase.id: { if (READ_ONLY_KB_OPS.has(getOperation(params) ?? '')) return [] - const args = asRecord(params?.args) + const args = toRecord(params?.args) const kbId = (args.knowledgeBaseId as string) ?? (params?.knowledgeBaseId as string) ?? @@ -261,16 +258,16 @@ export function extractDeletedResourcesFromToolResult( const resourceType = DELETE_CAPABLE_TOOL_RESOURCE_TYPE[toolName] if (!resourceType) return [] - const result = asRecord(output) - const data = asRecord(result.data) - const args = asRecord(params?.args) + const result = toRecord(output) + const data = toRecord(result.data) + const args = toRecord(params?.args) const operation = (args.operation ?? params?.operation) as string | undefined switch (toolName) { case Rm.id: { const outcomes = Array.isArray(result.results) ? result.results : [] return outcomes.flatMap((entry): ChatResource[] => { - const outcome = asRecord(entry) + const outcome = toRecord(entry) if (outcome.error) return [] const { id, kind, from } = outcome if (typeof id !== 'string' || !id || typeof kind !== 'string') return [] @@ -310,7 +307,7 @@ export function extractDeletedResourcesFromToolResult( if (operation !== 'delete') return [] const deleted = Array.isArray(data.deleted) ? data.deleted : [] const resources = deleted.flatMap((entry): ChatResource[] => { - const deletedKnowledgeBase = asRecord(entry) + const deletedKnowledgeBase = toRecord(entry) const knowledgeBaseId = deletedKnowledgeBase.id if (typeof knowledgeBaseId !== 'string' || !knowledgeBaseId) return [] return [ diff --git a/apps/sim/lib/webhooks/providers/gitlab.ts b/apps/sim/lib/webhooks/providers/gitlab.ts index d1f0c4b7543..6b5f4cd8d1b 100644 --- a/apps/sim/lib/webhooks/providers/gitlab.ts +++ b/apps/sim/lib/webhooks/providers/gitlab.ts @@ -1,7 +1,7 @@ import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { generateId } from '@sim/utils/id' -import { isRecordLike } from '@sim/utils/object' +import { isRecordLike, toRecord } from '@sim/utils/object' import { NextResponse } from 'next/server' import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' @@ -19,10 +19,6 @@ import { getGitLabApiBase, UnsafeGitLabHostError } from '@/tools/gitlab/utils' const logger = createLogger('WebhookProvider:GitLab') -function asRecord(value: unknown): Record { - return (value as Record) || {} -} - function gitlabProjectHooksUrl(projectId: string, host: unknown): string { return `${getGitLabApiBase(host)}/projects/${encodeURIComponent(projectId)}/hooks` } @@ -88,7 +84,7 @@ export const gitlabHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!triggerId || triggerId === 'gitlab_webhook') return true - const objectKind = asRecord(body).object_kind as string | undefined + const objectKind = toRecord(body).object_kind as string | undefined const { isGitLabEventMatch } = await import('@/triggers/gitlab/utils') if (!isGitLabEventMatch(triggerId, objectKind || '')) { @@ -110,9 +106,9 @@ export const gitlabHandler: WebhookProviderHandler = { * referencing the undocumented raw path keeps working. */ async formatInput({ body, headers }: FormatInputContext): Promise { - const b = asRecord(body) + const b = toRecord(body) const eventType = headers['x-gitlab-event'] || '' - const ref = (b.ref as string) || '' + const ref = typeof b.ref === 'string' ? b.ref : '' const branch = ref.replace('refs/heads/', '') const objectAttributes = b.object_attributes let input: Record = { ...b, event_type: eventType, branch } @@ -148,9 +144,9 @@ export const gitlabHandler: WebhookProviderHandler = { * (pending/running/success/failed) from colliding onto the same key. */ extractIdempotencyId(body: unknown): string | null { - const b = asRecord(body) + const b = toRecord(body) const objectKind = (b.object_kind as string) || '' - const project = asRecord(b.project) + const project = toRecord(b.project) const projectId = project.id != null ? String(project.id) : '' if (objectKind === 'push' || objectKind === 'tag_push') { @@ -160,7 +156,7 @@ export const gitlabHandler: WebhookProviderHandler = { return `gitlab:${objectKind}:${projectId}:${ref}:${checkoutSha}` } - const objectAttributes = asRecord(b.object_attributes) + const objectAttributes = toRecord(b.object_attributes) const id = objectAttributes.id != null ? String(objectAttributes.id) : '' if (!id) return null const version = diff --git a/apps/sim/lib/webhooks/providers/pagerduty.ts b/apps/sim/lib/webhooks/providers/pagerduty.ts index f16124eaa1f..fe15d0d475c 100644 --- a/apps/sim/lib/webhooks/providers/pagerduty.ts +++ b/apps/sim/lib/webhooks/providers/pagerduty.ts @@ -1,6 +1,7 @@ import crypto from 'crypto' import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' +import { toRecord } from '@sim/utils/object' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' import type { DeleteSubscriptionContext, @@ -42,10 +43,6 @@ function validatePagerDutySignature(secret: string, signature: string, body: str .some((part) => safeCompare(part.slice(3), computed)) } -function asRecord(value: unknown): Record { - return (value as Record) || {} -} - /** * Best-effort cleanup of a webhook subscription after a failed setup. Deletes by * id when known, otherwise finds the subscription pointing at `url` and deletes @@ -97,7 +94,7 @@ export const pagerdutyHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!triggerId || triggerId === 'pagerduty_webhook') return true - const event = asRecord(asRecord(body).event) + const event = toRecord(toRecord(body).event) const eventType = event.event_type as string | undefined const { isPagerDutyEventMatch } = await import('@/triggers/pagerduty/utils') @@ -111,8 +108,8 @@ export const pagerdutyHandler: WebhookProviderHandler = { }, async formatInput({ body }: FormatInputContext): Promise { - const event = asRecord(asRecord(body).event) - const data = asRecord(event.data) + const event = toRecord(toRecord(body).event) + const data = toRecord(event.data) const priority = referenceSummary(data.priority) return { @@ -139,7 +136,7 @@ export const pagerdutyHandler: WebhookProviderHandler = { }, extractIdempotencyId(body: unknown) { - const event = asRecord(asRecord(body).event) + const event = toRecord(toRecord(body).event) return (event.id as string | undefined) || null }, @@ -177,10 +174,10 @@ export const pagerdutyHandler: WebhookProviderHandler = { throw new Error(`Failed to create PagerDuty webhook subscription: ${res.status}`) } - const created = asRecord((await res.json().catch(() => ({}))) as unknown) - const subscription = asRecord(created.webhook_subscription) + const created = toRecord((await res.json().catch(() => ({}))) as unknown) + const subscription = toRecord(created.webhook_subscription) const externalId = subscription.id as string | undefined - const secret = asRecord(subscription.delivery_method).secret as string | undefined + const secret = toRecord(subscription.delivery_method).secret as string | undefined // The subscription exists once PagerDuty returns success; if it is missing // its id or signing secret, delete it so it is not orphaned, then fail. diff --git a/apps/sim/lib/webhooks/providers/zendesk.ts b/apps/sim/lib/webhooks/providers/zendesk.ts index 665f11b5853..452eec0ef37 100644 --- a/apps/sim/lib/webhooks/providers/zendesk.ts +++ b/apps/sim/lib/webhooks/providers/zendesk.ts @@ -1,6 +1,7 @@ import crypto from 'crypto' import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' +import { toRecord } from '@sim/utils/object' import { NextResponse } from 'next/server' import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils' import type { @@ -16,10 +17,6 @@ import type { const logger = createLogger('WebhookProvider:Zendesk') -function asRecord(value: unknown): Record { - return (value as Record) || {} -} - /** Zendesk API base for a subdomain. */ function zendeskApiBase(subdomain: string): string { return `https://${subdomain}.zendesk.com/api/v2` @@ -121,7 +118,7 @@ export const zendeskHandler: WebhookProviderHandler = { const triggerId = providerConfig.triggerId as string | undefined if (!triggerId || triggerId === 'zendesk_webhook') return true - const eventType = asRecord(body).type as string | undefined + const eventType = toRecord(body).type as string | undefined const { isZendeskEventMatch } = await import('@/triggers/zendesk/utils') if (!isZendeskEventMatch(triggerId, eventType || '')) { @@ -134,9 +131,9 @@ export const zendeskHandler: WebhookProviderHandler = { }, async formatInput({ body }: FormatInputContext): Promise { - const b = asRecord(body) - const detail = asRecord(b.detail) - const via = asRecord(detail.via) + const b = toRecord(body) + const detail = toRecord(b.detail) + const via = toRecord(detail.via) return { input: { @@ -167,7 +164,7 @@ export const zendeskHandler: WebhookProviderHandler = { }, extractIdempotencyId(body: unknown) { - return (asRecord(body).id as string | undefined) || null + return (toRecord(body).id as string | undefined) || null }, async createSubscription(ctx: SubscriptionContext): Promise { @@ -218,8 +215,8 @@ export const zendeskHandler: WebhookProviderHandler = { throw new Error(`Failed to create Zendesk webhook: ${createRes.status}`) } - const created = asRecord((await createRes.json().catch(() => ({}))) as unknown) - const externalId = asRecord(created.webhook).id as string | undefined + const created = toRecord((await createRes.json().catch(() => ({}))) as unknown) + const externalId = toRecord(created.webhook).id as string | undefined if (!externalId) throw new Error('Zendesk webhook created but no webhook ID was returned.') const secretRes = await fetch(`${apiBase}/webhooks/${externalId}/signing_secret`, { @@ -235,8 +232,8 @@ export const zendeskHandler: WebhookProviderHandler = { throw new Error(`Failed to fetch Zendesk signing secret: ${secretRes.status}`) } - const secretBody = asRecord((await secretRes.json().catch(() => ({}))) as unknown) - const secret = asRecord(secretBody.signing_secret).secret as string | undefined + const secretBody = toRecord((await secretRes.json().catch(() => ({}))) as unknown) + const secret = toRecord(secretBody.signing_secret).secret as string | undefined if (!secret) { await deleteZendeskWebhookQuietly(apiBase, authHeader, externalId) throw new Error('Zendesk did not return a signing secret for the webhook.') From c8006149b0461f67df0224d33d862b598dfe8ffe Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 14 Aug 2026 15:50:25 -0700 Subject: [PATCH 5/6] refactor: delete 31 unreachable module-local functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes 815 lines of provably dead code: module-local (non-exported) declarations whose identifier appears exactly once in their own file — the declaration itself. A non-exported symbol cannot be reached by an import, a barrel, a dynamic import, or a framework convention, so "unreferenced in its own file" is a complete proof of deadness rather than a heuristic. Notable removals include whole abandoned code paths: `findWebhookAndWorkflow` (78 lines), `calculateBillingProjection` and `initializeUserUsageLimit` (80 lines), `removeCredits` + `deductFromCredits` (54), `sendBatchSMS`, `executeToolBatch`, and four unused `async-runs/repository.ts` queries. Spans come from the TypeScript AST, not a regex. A regex cannot find a declaration's extent — a first-brace scan cuts inside a return-type annotation such as `Promise<{ canCreate: boolean }>` and silently corrupts the file. The AST pass also re-derives deadness from identifier nodes, which caught two wealthbox helpers that a regex export-check had wrongly reported as local. Note the runtime TypeScript API here is `@typescript/typescript6`; the bare `typescript` specifier resolves to the native compiler, which exposes no `createSourceFile`. --- apps/realtime/src/database/operations.ts | 28 ------ apps/sim/app/api/credentials/route.ts | 20 ---- apps/sim/app/api/files/authorization.ts | 39 -------- .../sim/app/api/mcp/serve/[serverId]/route.ts | 13 --- apps/sim/app/api/table/utils.ts | 5 - apps/sim/app/api/wand/route.ts | 8 -- apps/sim/lib/api-key/auth.ts | 19 ---- .../lib/billing/calculations/usage-monitor.ts | 47 --------- apps/sim/lib/billing/core/organization.ts | 65 ------------- apps/sim/lib/billing/core/usage.ts | 80 +--------------- apps/sim/lib/billing/credits/balance.ts | 54 +---------- apps/sim/lib/copilot/async-runs/repository.ts | 96 ------------------- apps/sim/lib/copilot/chat/process-contents.ts | 3 - .../sim/lib/copilot/tool-executor/executor.ts | 32 +------ .../copilot/tools/client/local-filesystem.ts | 18 ---- .../tools/server/files/file-intent-store.ts | 27 ------ .../copilot/validation/selector-validator.ts | 25 ----- apps/sim/lib/invitations/send.ts | 16 +--- apps/sim/lib/knowledge/tags/service.ts | 54 ----------- apps/sim/lib/messaging/sms/service.ts | 37 ------- apps/sim/lib/pptx-renderer/shapes/presets.ts | 18 ---- apps/sim/lib/table/billing.ts | 20 ---- .../contexts/chat/chat-file-manager.ts | 21 ---- apps/sim/lib/webhooks/processor.ts | 78 --------------- 24 files changed, 4 insertions(+), 819 deletions(-) diff --git a/apps/realtime/src/database/operations.ts b/apps/realtime/src/database/operations.ts index 4e7baa85d78..f28543a8004 100644 --- a/apps/realtime/src/database/operations.ts +++ b/apps/realtime/src/database/operations.ts @@ -278,34 +278,6 @@ function findDbDescendants(containerId: string, allBlocks: DbBlockRef[]): string return descendants } -/** - * Shared function to handle auto-connect edge insertion - * @param tx - Database transaction - * @param workflowId - The workflow ID - * @param autoConnectEdge - The auto-connect edge data - * @param logger - Logger instance - */ -async function insertAutoConnectEdge( - tx: any, - workflowId: string, - autoConnectEdge: any, - logger: any -) { - if (!autoConnectEdge) return - - await tx.insert(workflowEdges).values({ - id: autoConnectEdge.id, - workflowId, - sourceBlockId: autoConnectEdge.source, - targetBlockId: autoConnectEdge.target, - sourceHandle: normalizeWorkflowEdgeSourceHandle(autoConnectEdge.sourceHandle), - targetHandle: normalizeWorkflowEdgeTargetHandle(autoConnectEdge.targetHandle), - }) - logger.debug( - `Added auto-connect edge ${autoConnectEdge.id}: ${autoConnectEdge.source} -> ${autoConnectEdge.target}` - ) -} - enum SubflowType { LOOP = 'loop', PARALLEL = 'parallel', diff --git a/apps/sim/app/api/credentials/route.ts b/apps/sim/app/api/credentials/route.ts index 69ec1fb54e2..51d01be9981 100644 --- a/apps/sim/app/api/credentials/route.ts +++ b/apps/sim/app/api/credentials/route.ts @@ -117,26 +117,6 @@ async function findExistingCredentialBySourceWith( return null } -/** - * `return await` is load-bearing, not redundant. Next 16.3.0's Turbopack - * optimizer models a bare `return ()` tail call as returning the - * promise object, then propagates that always-truthy fact through the caller's - * `await`. It concludes `if (existingCredential)` is always taken and — because - * every branch inside that block returns — deletes the entire create path from - * the emitted bundle, so a first-time create throws on `existingCredential.id`. - * Awaiting here makes the optimizer model the resolved value instead. - */ -async function findExistingCredentialBySource(params: ExistingCredentialSourceParams) { - return await findExistingCredentialBySourceWith(db, params) -} - -async function findExistingCredentialBySourceTx( - tx: Parameters[0]>[0], - params: ExistingCredentialSourceParams -) { - return await findExistingCredentialBySourceWith(tx, params) -} - export const GET = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() const session = await getSession() diff --git a/apps/sim/app/api/files/authorization.ts b/apps/sim/app/api/files/authorization.ts index 2acd14d46a2..819081ff2e0 100644 --- a/apps/sim/app/api/files/authorization.ts +++ b/apps/sim/app/api/files/authorization.ts @@ -711,45 +711,6 @@ async function verifyRegularFileAccess( } } -/** - * Unified authorization function that returns structured result - */ -async function authorizeFileAccess( - key: string, - userId: string, - context?: StorageContext, - storageConfig?: StorageConfig, - isLocal?: boolean -): Promise { - const granted = await verifyFileAccess(key, userId, storageConfig, context, isLocal) - - if (granted) { - let workspaceId: string | undefined - const inferredContext = context || inferContextFromKey(key) - - if (inferredContext === 'workspace') { - const record = await lookupWorkspaceFileByKey(key) - workspaceId = record?.workspaceId - } else { - const extracted = extractWorkspaceIdFromKey(key) - if (extracted) { - workspaceId = extracted - } - } - - return { - granted: true, - reason: 'Access granted', - workspaceId, - } - } - - return { - granted: false, - reason: 'Access denied - insufficient permissions or file not found', - } -} - /** * Guard helper for tool routes that download user files from storage. * diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.ts index 8814f630610..faec1b78e24 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.ts @@ -279,19 +279,6 @@ function toToolInputSchema(schema: unknown): Partial { } } -function parseJsonValue(text: string): { success: true; value: unknown } | { success: false } { - if (!text) return { success: true, value: {} } - try { - return { success: true, value: JSON.parse(text) } - } catch { - return { success: false } - } -} - -function hasResponseField(value: Record, property: string): boolean { - return Object.hasOwn(value, property) -} - function getWorkflowErrorStatus(status: number): number { return [400, 401, 402, 403, 404, 408, 409, 413, 429, 499, 503].includes(status) ? status : 500 } diff --git a/apps/sim/app/api/table/utils.ts b/apps/sim/app/api/table/utils.ts index 037c6c83cc8..e548b0da607 100644 --- a/apps/sim/app/api/table/utils.ts +++ b/apps/sim/app/api/table/utils.ts @@ -313,11 +313,6 @@ export function tableAccessError( return NextResponse.json({ error: message }, { status }) } -async function verifyTableWorkspace(tableId: string, workspaceId: string): Promise { - const table = await getTableById(tableId) - return table?.workspaceId === workspaceId -} - export function errorResponse( message: string, status: number, diff --git a/apps/sim/app/api/wand/route.ts b/apps/sim/app/api/wand/route.ts index f01e90861b0..16743d93a6a 100644 --- a/apps/sim/app/api/wand/route.ts +++ b/apps/sim/app/api/wand/route.ts @@ -53,14 +53,6 @@ interface ChatMessage { content: string } -function safeStringify(value: unknown): string { - try { - return JSON.stringify(value) - } catch { - return '[unserializable]' - } -} - /** * Wand enricher function type. * Enrichers add context to the system prompt based on generationType. diff --git a/apps/sim/lib/api-key/auth.ts b/apps/sim/lib/api-key/auth.ts index 5a8e44ddcea..9afba959588 100644 --- a/apps/sim/lib/api-key/auth.ts +++ b/apps/sim/lib/api-key/auth.ts @@ -136,25 +136,6 @@ export function formatApiKeyForDisplay(apiKey: string): string { return `...${last4}` } -/** - * Gets the last 4 characters of an encrypted API key by decrypting it first - * @param encryptedKey - The encrypted API key from the database - * @returns Promise - The last 4 characters - */ -async function getEncryptedApiKeyLast4(encryptedKey: string): Promise { - try { - if (isEncryptedKey(encryptedKey)) { - const decryptedKey = await decryptApiKeyFromStorage(encryptedKey) - return getApiKeyLast4(decryptedKey) - } - // For plain text keys (legacy), return last 4 directly - return getApiKeyLast4(encryptedKey) - } catch (error) { - logger.error('Failed to get last 4 characters of API key:', { error }) - return '****' - } -} - /** * Validates API key format (basic validation) * @param apiKey - The API key to validate diff --git a/apps/sim/lib/billing/calculations/usage-monitor.ts b/apps/sim/lib/billing/calculations/usage-monitor.ts index 14a2cd47db5..0683b82a166 100644 --- a/apps/sim/lib/billing/calculations/usage-monitor.ts +++ b/apps/sim/lib/billing/calculations/usage-monitor.ts @@ -243,53 +243,6 @@ function buildUsageData(params: { } } -/** - * Displays a notification to the user when they're approaching their usage limit - * Can be called on app startup or before executing actions that might incur costs - */ -async function checkAndNotifyUsage(userId: string): Promise { - try { - if (!isBillingEnabled) { - return - } - - const usageData = await checkUsageStatus(userId) - - if (usageData.isExceeded) { - logger.warn('User has exceeded usage limits', { - userId, - usage: usageData.currentUsage, - limit: usageData.limit, - }) - - if (typeof window !== 'undefined') { - window.dispatchEvent( - new CustomEvent('usage-exceeded', { - detail: { usageData }, - }) - ) - } - } else if (usageData.isWarning) { - logger.info('User approaching usage limits', { - userId, - usage: usageData.currentUsage, - limit: usageData.limit, - percent: usageData.percentUsed, - }) - - if (typeof window !== 'undefined') { - window.dispatchEvent( - new CustomEvent('usage-warning', { - detail: { usageData }, - }) - ) - } - } - } catch (error) { - logger.error('Error in usage notification system', { error, userId }) - } -} - /** * Whether the exact hosted user account is billing-blocked. Organization * memberships are deliberately ignored; workspace payer checks are separate. diff --git a/apps/sim/lib/billing/core/organization.ts b/apps/sim/lib/billing/core/organization.ts index 7290bf1d9dc..dc1d59a7d18 100644 --- a/apps/sim/lib/billing/core/organization.ts +++ b/apps/sim/lib/billing/core/organization.ts @@ -349,71 +349,6 @@ export async function updateOrganizationUsageLimit( } } -/** - * Get organization billing summary for admin dashboard - */ -async function getOrganizationBillingSummary(organizationId: string) { - try { - const billingData = await getOrganizationBillingData(organizationId) - - if (!billingData) { - return null - } - - // Calculate additional metrics - const membersOverLimit = billingData.members.filter((m) => m.isOverLimit).length - const membersNearLimit = billingData.members.filter( - (m) => !m.isOverLimit && m.percentUsed >= 80 - ).length - - const topUsers = billingData.members.slice(0, 5).map((m) => ({ - name: m.userName, - usage: m.currentUsage, - limit: m.usageLimit, - percentUsed: m.percentUsed, - })) - - return { - organization: { - id: billingData.organizationId, - name: billingData.organizationName, - plan: billingData.subscriptionPlan, - status: billingData.subscriptionStatus, - }, - usage: { - total: billingData.totalCurrentUsage, - limit: billingData.totalUsageLimit, - average: billingData.averageUsagePerMember, - percentUsed: - billingData.totalUsageLimit > 0 - ? (billingData.totalCurrentUsage / billingData.totalUsageLimit) * 100 - : 0, - }, - seats: { - total: billingData.totalSeats, - used: billingData.usedSeats, - /** - * Clamped: Team seats track the member count rather than a ceiling, so - * any outstanding invitation would otherwise report negative headroom. - */ - available: Math.max(0, billingData.totalSeats - billingData.usedSeats), - }, - alerts: { - membersOverLimit, - membersNearLimit, - }, - billingPeriod: { - start: billingData.billingPeriodStart, - end: billingData.billingPeriodEnd, - }, - topUsers, - } - } catch (error) { - logger.error('Failed to get organization billing summary', { organizationId, error }) - throw error - } -} - /** * Error-tolerant wrapper around {@link isOrganizationAdminOrOwner} for billing * gates: on a DB error it logs and returns false instead of throwing, so a diff --git a/apps/sim/lib/billing/core/usage.ts b/apps/sim/lib/billing/core/usage.ts index b68d1982623..28f091016fa 100644 --- a/apps/sim/lib/billing/core/usage.ts +++ b/apps/sim/lib/billing/core/usage.ts @@ -30,7 +30,7 @@ import { hasUsableSubscriptionAccess, isOrgScopedSubscription, } from '@/lib/billing/subscriptions/utils' -import type { BillingData, UsageData, UsageLimitInfo } from '@/lib/billing/types' +import type { UsageData, UsageLimitInfo } from '@/lib/billing/types' import { buildUpgradeHref } from '@/lib/billing/upgrade-reasons' import { Decimal, toDecimal, toNumber } from '@/lib/billing/utils/decimal' import { isBillingEnabled } from '@/lib/core/config/env-flags' @@ -400,38 +400,6 @@ export async function getUserUsageLimitInfo(userId: string): Promise { - // Check if user already has usage stats - const existingStats = await db - .select() - .from(userStats) - .where(eq(userStats.userId, userId)) - .limit(1) - - if (existingStats.length > 0) { - return - } - - const subscription = await getHighestPrioritySubscription(userId) - const orgScoped = isOrgScopedSubscription(subscription, userId) - - await db.insert(userStats).values({ - id: generateId(), - userId, - currentUsageLimit: orgScoped ? null : getFreeTierLimit().toString(), - usageLimitUpdatedAt: new Date(), - }) - - logger.info('Initialized user stats', { - userId, - plan: subscription?.plan || 'free', - hasIndividualLimit: !orgScoped, - }) -} - /** * Update a user's custom usage limit */ @@ -782,52 +750,6 @@ export async function getEffectiveCurrentPeriodCost( return Math.max(0, rawCost - refreshConsumed) } -/** - * Calculate billing projection based on current usage - */ -async function calculateBillingProjection(userId: string): Promise { - try { - const usageData = await getUserUsageData(userId) - - if (!usageData.billingPeriodStart || !usageData.billingPeriodEnd) { - return { - currentPeriodCost: usageData.currentUsage, - projectedCost: usageData.currentUsage, - limit: usageData.limit, - billingPeriodStart: null, - billingPeriodEnd: null, - daysRemaining: 0, - } - } - - const now = new Date() - const periodStart = new Date(usageData.billingPeriodStart) - const periodEnd = new Date(usageData.billingPeriodEnd) - - const totalDays = Math.ceil( - (periodEnd.getTime() - periodStart.getTime()) / (1000 * 60 * 60 * 24) - ) - const daysElapsed = Math.ceil((now.getTime() - periodStart.getTime()) / (1000 * 60 * 60 * 24)) - const daysRemaining = Math.max(0, totalDays - daysElapsed) - - // Project cost based on daily usage rate - const dailyRate = daysElapsed > 0 ? usageData.currentUsage / daysElapsed : 0 - const projectedCost = dailyRate * totalDays - - return { - currentPeriodCost: usageData.currentUsage, - projectedCost: Math.min(projectedCost, usageData.limit), // Cap at limit - limit: usageData.limit, - billingPeriodStart: usageData.billingPeriodStart, - billingPeriodEnd: usageData.billingPeriodEnd, - daysRemaining, - } - } catch (error) { - logger.error('Failed to calculate billing projection', { userId, error }) - throw error - } -} - /** * Send usage threshold notification when crossing from <80% to ≥80%. * - Skips when billing is disabled. diff --git a/apps/sim/lib/billing/credits/balance.ts b/apps/sim/lib/billing/credits/balance.ts index cdf3dc784f5..d702056d809 100644 --- a/apps/sim/lib/billing/credits/balance.ts +++ b/apps/sim/lib/billing/credits/balance.ts @@ -9,7 +9,7 @@ import { hasUsableSubscriptionAccess, isOrgScopedSubscription, } from '@/lib/billing/subscriptions/utils' -import { Decimal, toDecimal, toFixedString, toNumber } from '@/lib/billing/utils/decimal' +import { toDecimal, toFixedString, toNumber } from '@/lib/billing/utils/decimal' import type { DbClient } from '@/lib/db/types' const logger = createLogger('CreditBalance') @@ -92,28 +92,6 @@ export async function addCredits( } } -async function removeCredits( - entityType: 'user' | 'organization', - entityId: string, - amount: number -): Promise { - if (entityType === 'organization') { - await db - .update(organization) - .set({ creditBalance: sql`GREATEST(0, ${organization.creditBalance} - ${amount})` }) - .where(eq(organization.id, entityId)) - - logger.info('Removed credits from organization', { organizationId: entityId, amount }) - } else { - await db - .update(userStats) - .set({ creditBalance: sql`GREATEST(0, ${userStats.creditBalance} - ${amount})` }) - .where(eq(userStats.userId, entityId)) - - logger.info('Removed credits from user', { userId: entityId, amount }) - } -} - interface DeductResult { creditsUsed: number overflow: number @@ -173,36 +151,6 @@ async function atomicDeductOrgCredits(orgId: string, cost: number): Promise { - if (cost <= 0) { - return { creditsUsed: 0, overflow: 0 } - } - - const subscription = await getHighestPrioritySubscription(userId) - const orgScoped = isOrgScopedSubscription(subscription, userId) - - let creditsUsed: number - - if (orgScoped && subscription?.referenceId) { - creditsUsed = await atomicDeductOrgCredits(subscription.referenceId, cost) - } else { - creditsUsed = await atomicDeductUserCredits(userId, cost) - } - - const overflow = toNumber(Decimal.max(0, toDecimal(cost).minus(creditsUsed))) - - if (creditsUsed > 0) { - logger.info('Deducted credits atomically', { - userId, - creditsUsed, - overflow, - entityType: orgScoped ? 'organization' : 'user', - }) - } - - return { creditsUsed, overflow } -} - export async function canPurchaseCredits(userId: string): Promise { const subscription = await getHighestPrioritySubscription(userId) if (!subscription) { diff --git a/apps/sim/lib/copilot/async-runs/repository.ts b/apps/sim/lib/copilot/async-runs/repository.ts index 1cff09c4a63..58efd23875b 100644 --- a/apps/sim/lib/copilot/async-runs/repository.ts +++ b/apps/sim/lib/copilot/async-runs/repository.ts @@ -5,7 +5,6 @@ import { type CopilotRunStatus, type CopilotToolPermissionDecision, copilotAsyncToolCalls, - copilotRunCheckpoints, copilotRuns, } from '@sim/db/schema' import { createLogger } from '@sim/logger' @@ -158,24 +157,6 @@ export async function updateRunStatus( ) } -async function getLatestRunForExecution(executionId: string) { - return await withDbSpan( - TraceSpan.CopilotAsyncRunsGetLatestForExecution, - 'SELECT', - 'copilot_runs', - { [TraceAttr.CopilotExecutionId]: executionId }, - async () => { - const [run] = await db - .select() - .from(copilotRuns) - .where(eq(copilotRuns.executionId, executionId)) - .orderBy(desc(copilotRuns.startedAt)) - .limit(1) - return run ?? null - } - ) -} - // Un-instrumented: called from a 4 Hz resume poll; per-call spans // swamped traces. Use Prom histograms if latency visibility is needed. export async function getLatestRunForStream(streamId: string, userId?: string) { @@ -215,38 +196,6 @@ export async function getRunSegment(runId: string) { ) } -async function createRunCheckpoint(input: { - runId: string - pendingToolCallId: string - conversationSnapshot: Record - agentState: Record - providerRequest: Record -}) { - return await withDbSpan( - TraceSpan.CopilotAsyncRunsCreateRunCheckpoint, - 'INSERT', - 'copilot_run_checkpoints', - { - [TraceAttr.RunId]: input.runId, - [TraceAttr.CopilotCheckpointPendingToolCallId]: input.pendingToolCallId, - }, - async () => { - const [checkpoint] = await db - .insert(copilotRunCheckpoints) - .values({ - runId: input.runId, - pendingToolCallId: input.pendingToolCallId, - conversationSnapshot: input.conversationSnapshot, - agentState: input.agentState, - providerRequest: input.providerRequest, - }) - .returning() - - return checkpoint - } - ) -} - export async function upsertAsyncToolCall(input: { runId?: string | null checkpointId?: string | null @@ -627,21 +576,6 @@ export async function recordToolPermissionDecision( ) } -async function listAsyncToolCallsForRun(runId: string) { - return await withDbSpan( - TraceSpan.CopilotAsyncRunsListForRun, - 'SELECT', - 'copilot_async_tool_calls', - { [TraceAttr.RunId]: runId }, - async () => - db - .select() - .from(copilotAsyncToolCalls) - .where(eq(copilotAsyncToolCalls.runId, runId)) - .orderBy(desc(copilotAsyncToolCalls.createdAt)) - ) -} - export async function getAsyncToolCalls(toolCallIds: string[]) { if (toolCallIds.length === 0) return [] return await withDbSpan( @@ -686,33 +620,3 @@ export async function claimCompletedAsyncToolCall(toolCallId: string, workerId: } ) } - -async function releaseCompletedAsyncToolClaim(toolCallId: string, workerId: string) { - return await withDbSpan( - TraceSpan.CopilotAsyncRunsReleaseClaim, - 'UPDATE', - 'copilot_async_tool_calls', - { - [TraceAttr.ToolCallId]: toolCallId, - [TraceAttr.CopilotAsyncToolWorkerId]: workerId, - }, - async () => { - const [row] = await db - .update(copilotAsyncToolCalls) - .set({ - claimedBy: null, - claimedAt: null, - updatedAt: new Date(), - }) - .where( - and( - eq(copilotAsyncToolCalls.toolCallId, toolCallId), - inArray(copilotAsyncToolCalls.status, ['completed', 'failed', 'cancelled']), - eq(copilotAsyncToolCalls.claimedBy, workerId) - ) - ) - .returning() - return row ?? null - } - ) -} diff --git a/apps/sim/lib/copilot/chat/process-contents.ts b/apps/sim/lib/copilot/chat/process-contents.ts index c21214d8367..8765e334f16 100644 --- a/apps/sim/lib/copilot/chat/process-contents.ts +++ b/apps/sim/lib/copilot/chat/process-contents.ts @@ -576,9 +576,6 @@ async function processPastChat(chatId: string, tagOverride?: string): Promise> { - const results = new Map() - - const executions = toolCalls.map(async ({ toolCallId, toolId, params }) => { - const result = await executeTool(toolId, params, context) - results.set(toolCallId, result) - }) - - await Promise.allSettled(executions) - - for (const { toolCallId } of toolCalls) { - if (!results.has(toolCallId)) { - results.set(toolCallId, { - success: false, - error: 'Tool execution did not produce a result', - }) - } - } - - return results -} - function buildAppToolParams( params: Record, context: ToolExecutionContext diff --git a/apps/sim/lib/copilot/tools/client/local-filesystem.ts b/apps/sim/lib/copilot/tools/client/local-filesystem.ts index 04091d484b4..53a9f80c773 100644 --- a/apps/sim/lib/copilot/tools/client/local-filesystem.ts +++ b/apps/sim/lib/copilot/tools/client/local-filesystem.ts @@ -152,24 +152,6 @@ function mountForPath(mounts: LocalFilesystemMount[], path: string): LocalFilesy return match } -function omitHostPaths(data: LocalFilesystemData): LocalFilesystemData { - if ('mount' in data) { - if (!data.mount) return data - const { path: _path, ...mount } = data.mount as LocalFilesystemMount & { path?: unknown } - return { ...data, mount } - } - if ('mounts' in data) { - return { - ...data, - mounts: data.mounts.map((rawMount) => { - const { path: _path, ...mount } = rawMount as LocalFilesystemMount & { path?: unknown } - return mount - }), - } - } - return data -} - async function executeUserLocalGlob( toolCallId: string, args: Record, diff --git a/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts b/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts index 82f7977b17d..915a2cf5a50 100644 --- a/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts +++ b/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts @@ -156,33 +156,6 @@ export async function storeFileIntent( }) } -async function consumeFileIntent( - workspaceId: string, - fileId: string, - scope?: FileIntentScope -): Promise { - const redis = getRedisClient() - if (!redis) { - const key = buildKey(workspaceId, buildScopedField(fileId, scope)) - const intent = memoryStore.get(key) - if (intent) { - memoryStore.delete(key) - } - return intent - } - - const raw = await withRedisRetry('consume_file_intent', workspaceId, async (client) => { - const key = getWorkspaceRedisKey(workspaceId) - const field = buildScopedField(fileId, scope) - const value = await client.hget(key, field) - if (value !== null) { - await client.hdel(key, field) - } - return value - }) - return parseIntent(raw) -} - export async function peekFileIntent( workspaceId: string, fileId: string, diff --git a/apps/sim/lib/copilot/validation/selector-validator.ts b/apps/sim/lib/copilot/validation/selector-validator.ts index 30490f583cc..183d63a7de4 100644 --- a/apps/sim/lib/copilot/validation/selector-validator.ts +++ b/apps/sim/lib/copilot/validation/selector-validator.ts @@ -277,28 +277,3 @@ export async function validateSelectorIds( invalid: idsArray.filter((id) => !existingSet.has(id)), } } - -/** - * Batch validate multiple selector fields - * Returns a map of field name to validation result - */ -async function validateAllSelectorFields( - fields: Array<{ fieldName: string; selectorType: string; value: string | string[] }>, - context: { userId: string; workspaceId?: string } -): Promise> { - const results = new Map() - - // Run validations in parallel for better performance - const validationPromises = fields.map(async ({ fieldName, selectorType, value }) => { - const result = await validateSelectorIds(selectorType, value, context) - return { fieldName, result } - }) - - const validationResults = await Promise.all(validationPromises) - - for (const { fieldName, result } of validationResults) { - results.set(fieldName, result) - } - - return results -} diff --git a/apps/sim/lib/invitations/send.ts b/apps/sim/lib/invitations/send.ts index ae6565b3d13..5d49cb0bd3e 100644 --- a/apps/sim/lib/invitations/send.ts +++ b/apps/sim/lib/invitations/send.ts @@ -12,7 +12,7 @@ import { isOrgAdminRole } from '@sim/platform-authz/workspace' import { getPostgresConstraintName, getPostgresErrorCode } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { normalizeEmail } from '@sim/utils/string' -import { and, asc, eq, inArray, ne, sql } from 'drizzle-orm' +import { and, asc, eq, inArray, sql } from 'drizzle-orm' import { getEmailSubject, renderBatchInvitationEmail, @@ -465,20 +465,6 @@ export async function revertPendingInvitationGrants(params: { }) } -async function countPendingInvitationsForOrganization(organizationId: string): Promise { - const [row] = await db - .select({ count: sql`count(*)::int` }) - .from(invitation) - .where( - and( - eq(invitation.organizationId, organizationId), - eq(invitation.status, 'pending'), - ne(invitation.membershipIntent, 'external') - ) - ) - return row?.count ?? 0 -} - /** * Workspaces this email already holds a pending grant for, across every * pending invitation. Callers use it to drop workspaces from a new invite diff --git a/apps/sim/lib/knowledge/tags/service.ts b/apps/sim/lib/knowledge/tags/service.ts index b7f559da7e9..bee395eace1 100644 --- a/apps/sim/lib/knowledge/tags/service.ts +++ b/apps/sim/lib/knowledge/tags/service.ts @@ -403,60 +403,6 @@ export async function getTagDefinitionById( } } -/** - * Update tags on all documents and chunks when a tag value is changed - */ -async function updateTagValuesInDocumentsAndChunks( - knowledgeBaseId: string, - tagSlot: string, - oldValue: string | null, - newValue: string | null, - requestId: string -): Promise<{ documentsUpdated: number; chunksUpdated: number }> { - validateTagSlot(tagSlot) - - let documentsUpdated = 0 - let chunksUpdated = 0 - - await db.transaction(async (tx) => { - if (oldValue) { - await tx - .update(document) - .set({ - [tagSlot]: newValue, - }) - .where( - and( - eq(document.knowledgeBaseId, knowledgeBaseId), - eq(sql.raw(`${document}.${tagSlot}`), oldValue) - ) - ) - documentsUpdated = 1 - } - - if (oldValue) { - await tx - .update(embedding) - .set({ - [tagSlot]: newValue, - }) - .where( - and( - eq(embedding.knowledgeBaseId, knowledgeBaseId), - eq(sql.raw(`${embedding}.${tagSlot}`), oldValue) - ) - ) - chunksUpdated = 1 - } - }) - - logger.info( - `[${requestId}] Updated tag values: ${documentsUpdated} documents, ${chunksUpdated} chunks` - ) - - return { documentsUpdated, chunksUpdated } -} - /** * Cleanup unused tag definitions for a knowledge base */ diff --git a/apps/sim/lib/messaging/sms/service.ts b/apps/sim/lib/messaging/sms/service.ts index 831c8e8fdea..eb07441e96e 100644 --- a/apps/sim/lib/messaging/sms/service.ts +++ b/apps/sim/lib/messaging/sms/service.ts @@ -146,40 +146,3 @@ async function sendSingleSMS(to: string, body: string, from: string): Promise { - try { - const results: SendSMSResult[] = [] - - logger.info('Sending batch SMS messages') - for (const smsOptions of options.messages) { - try { - const result = await sendSMS(smsOptions) - results.push(result) - } catch (error) { - results.push({ - success: false, - message: getErrorMessage(error, 'Failed to send SMS'), - }) - } - } - - const successCount = results.filter((r) => r.success).length - return { - success: successCount === results.length, - message: - successCount === results.length - ? 'All batch SMS messages sent successfully' - : `${successCount}/${results.length} SMS messages sent successfully`, - results, - data: { count: successCount }, - } - } catch (error) { - logger.error('Error in batch SMS sending:', error) - return { - success: false, - message: 'Failed to send batch SMS messages', - results: [], - } - } -} diff --git a/apps/sim/lib/pptx-renderer/shapes/presets.ts b/apps/sim/lib/pptx-renderer/shapes/presets.ts index 00f969a87ce..e079110a452 100644 --- a/apps/sim/lib/pptx-renderer/shapes/presets.ts +++ b/apps/sim/lib/pptx-renderer/shapes/presets.ts @@ -26,24 +26,6 @@ function adj( return raw / 100000 } -/** Helper: generate a regular polygon path (inscribed in bounding box). */ -function _regularPolygon(w: number, h: number, sides: number): string { - const cx = w / 2 - const cy = h / 2 - const rx = w / 2 - const ry = h / 2 - const parts: string[] = [] - for (let i = 0; i < sides; i++) { - // Start from top center (-90 degrees) - const angle = (2 * Math.PI * i) / sides - Math.PI / 2 - const x = cx + rx * Math.cos(angle) - const y = cy + ry * Math.sin(angle) - parts.push(i === 0 ? `M${x},${y}` : `L${x},${y}`) - } - parts.push('Z') - return parts.join(' ') -} - /** Raw adj helper: get adjustment value without dividing by 100000. */ function adjRaw( adjustments: Map | undefined, diff --git a/apps/sim/lib/table/billing.ts b/apps/sim/lib/table/billing.ts index ba931ad0c93..3602737aba4 100644 --- a/apps/sim/lib/table/billing.ts +++ b/apps/sim/lib/table/billing.ts @@ -252,26 +252,6 @@ export async function assertRowCapacity(params: { return limit } -/** - * Checks if a workspace can create more tables based on its plan limits. - * - * @param workspaceId - The workspace ID to check - * @param currentTableCount - The current number of tables in the workspace - * @returns Object with canCreate boolean and limit info - */ -async function canCreateTable( - workspaceId: string, - currentTableCount: number -): Promise<{ canCreate: boolean; maxTables: number; currentCount: number }> { - const limits = await getWorkspaceTableLimits(workspaceId) - - return { - canCreate: currentTableCount < limits.maxTables, - maxTables: limits.maxTables, - currentCount: currentTableCount, - } -} - /** * Gets the maximum rows allowed per table for a workspace based on its plan. * diff --git a/apps/sim/lib/uploads/contexts/chat/chat-file-manager.ts b/apps/sim/lib/uploads/contexts/chat/chat-file-manager.ts index 0217bc5b2ee..3a6c340cbb5 100644 --- a/apps/sim/lib/uploads/contexts/chat/chat-file-manager.ts +++ b/apps/sim/lib/uploads/contexts/chat/chat-file-manager.ts @@ -72,24 +72,3 @@ export async function processChatFiles( return userFiles } - -/** - * Upload a single chat file to temporary execution storage - * - * This is a convenience function for uploading individual files. - * For batch uploads, use processChatFiles() for better performance. - * - * @param file Chat file to upload - * @param executionContext Execution context for temporary storage - * @param requestId Unique request identifier - * @returns UserFile object with upload result - */ -async function uploadChatFile( - file: ChatFile, - executionContext: ChatExecutionContext, - requestId: string, - userId?: string -): Promise { - const [userFile] = await processChatFiles([file], executionContext, requestId, userId) - return userFile -} diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 35cba00ece0..a7a56b03f58 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -301,84 +301,6 @@ export function handlePreDeploymentVerification( return null } -async function findWebhookAndWorkflow( - options: WebhookProcessorOptions -): Promise { - if (options.webhookId) { - const results = await db - .select({ - webhook: webhook, - workflow: workflow, - }) - .from(webhook) - .innerJoin(workflow, eq(webhook.workflowId, workflow.id)) - .leftJoin( - workflowDeploymentVersion, - and( - eq(workflowDeploymentVersion.workflowId, workflow.id), - eq(workflowDeploymentVersion.isActive, true) - ) - ) - .where( - and( - eq(webhook.id, options.webhookId), - deliverableWebhookPredicate(webhook), - isNull(workflow.archivedAt), - or( - eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), - and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) - ) - ) - ) - .limit(1) - - if (results.length === 0) { - logger.warn(`[${options.requestId}] No active webhook found for id: ${options.webhookId}`) - return null - } - - return { webhook: results[0].webhook, workflow: results[0].workflow } - } - - if (options.path) { - const results = await db - .select({ - webhook: webhook, - workflow: workflow, - }) - .from(webhook) - .innerJoin(workflow, eq(webhook.workflowId, workflow.id)) - .leftJoin( - workflowDeploymentVersion, - and( - eq(workflowDeploymentVersion.workflowId, workflow.id), - eq(workflowDeploymentVersion.isActive, true) - ) - ) - .where( - and( - eq(webhook.path, options.path), - deliverableWebhookPredicate(webhook), - isNull(workflow.archivedAt), - or( - eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), - and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) - ) - ) - ) - .limit(1) - - if (results.length === 0) { - logger.warn(`[${options.requestId}] No active webhook found for path: ${options.path}`) - return null - } - - return { webhook: results[0].webhook, workflow: results[0].workflow } - } - - return null -} - /** * Finds all webhooks matching a path, scoped to a single workflow. * From 27ff7733d89cec2f0404acdac532d2f9b68ac6c6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 14 Aug 2026 16:33:15 -0700 Subject: [PATCH 6/6] refactor: delete 434 stranded exported symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes ~5,930 lines of unreachable code across 166 files: exported symbols that no other file in the repo mentions and that are unused inside their own file. Whole abandoned surfaces go with them — unused React Query hooks (useOrganizations, useOrganizationMembers, useUpgradeSubscription, ...), unused admin route contracts, dead executor constants and reference builders, and the landing-page StageWorkflow/LandingPreviewMount components. Three files left with no remaining code were removed outright. Candidates came from an AST pass; each was then verified individually against an UNFILTERED repo-wide search plus the reachability paths a name search misses: string-keyed tool/block registries, dynamic imports, `export *` barrels, and the docs generator's source-text parsing of tool files. 29 candidates were verified LIVE and kept. Those exposed a flaw in the candidate generator: it indexed only .ts/.tsx, while apps/docs/content/**/*.mdx imports React components directly — ActionImage appears in ~190 MDX pages and ActionVideo in ~115, and both scanned as dead. `app/global-error.tsx` was likewise kept, since Next.js reaches it by filename and its default export can never have a name reference. All 434 deleted names were afterwards cross-checked against every .mdx/.md/.json/.yaml in the repo: no hits. Verified with turbo type-check (23 workspaces), the full apps/sim suite (25,219 tests), all 26 audits, and a production `next build` — the last of these being what actually exercises route- and component-level reachability. --- .../main/browser-import/browser-sources.ts | 17 - apps/desktop/src/main/terminal/index.ts | 6 - .../components/hero-visual/stage-workflow.tsx | 133 ------- .../landing-preview/landing-preview-mount.tsx | 55 --- .../iso-marks/iso-illustration-style.ts | 1 - apps/sim/app/api/files/utils.ts | 16 - apps/sim/app/api/table/utils.ts | 32 -- apps/sim/app/api/tools/neo4j/utils.ts | 27 -- apps/sim/app/api/tools/ssh/utils.ts | 16 - apps/sim/app/api/v1/admin/types.ts | 13 - .../logs/components/log-details/utils.ts | 18 - .../scheduled-tasks/utils/recurrence.ts | 6 - .../upgrade/subscription-permissions.ts | 19 - .../w/[workflowId]/components/error/index.tsx | 29 -- .../components/user-input/constants.ts | 41 --- .../copilot/components/user-input/utils.ts | 49 --- .../w/[workflowId]/utils/node-derivation.ts | 11 - .../utils/workflow-canvas-helpers.ts | 40 --- apps/sim/blocks/utils.ts | 20 +- apps/sim/components/settings/navigation.ts | 7 - apps/sim/executor/constants.ts | 52 --- .../handlers/shared/response-format.ts | 32 -- apps/sim/executor/human-in-the-loop/utils.ts | 12 - apps/sim/executor/types/loop.ts | 4 - apps/sim/executor/types/parallel.ts | 6 - apps/sim/executor/utils/json.ts | 13 - .../executor/utils/reference-validation.ts | 19 - apps/sim/executor/utils/subflow-utils.ts | 4 - apps/sim/hooks/queries/general-settings.ts | 5 - apps/sim/hooks/queries/mothership-admin.ts | 22 -- apps/sim/hooks/queries/mothership-chats.ts | 42 --- .../hooks/queries/oauth/oauth-connections.ts | 29 +- apps/sim/hooks/queries/organization.ts | 187 +--------- apps/sim/hooks/queries/schedules.ts | 23 -- apps/sim/hooks/queries/subscription.ts | 90 ----- apps/sim/hooks/queries/workspace-files.ts | 50 --- apps/sim/hooks/queries/workspace.ts | 24 -- .../hooks/use-trigger-config-aggregation.ts | 68 ---- apps/sim/lib/api/contracts/admin.ts | 334 ------------------ apps/sim/lib/api/contracts/common.ts | 14 - apps/sim/lib/api/contracts/copilot.ts | 16 - apps/sim/lib/api/contracts/deployments.ts | 5 - apps/sim/lib/api/contracts/mcp-oauth.ts | 21 -- apps/sim/lib/api/contracts/mcp.ts | 26 -- apps/sim/lib/api/contracts/schedules.ts | 9 - .../sim/lib/api/contracts/storage-transfer.ts | 7 - .../lib/api/contracts/tools/media/image.ts | 7 - apps/sim/lib/api/contracts/v1/copilot.ts | 28 -- apps/sim/lib/api/contracts/v1/files.ts | 9 - apps/sim/lib/api/contracts/webhooks.ts | 12 - apps/sim/lib/api/contracts/workflows.ts | 7 - apps/sim/lib/api/contracts/workspaces.ts | 8 - apps/sim/lib/audio/extractor.ts | 39 -- apps/sim/lib/billing/client/consts.ts | 5 - apps/sim/lib/billing/plan-helpers.ts | 7 - apps/sim/lib/billing/plans.ts | 8 - apps/sim/lib/blog/registry.ts | 2 - apps/sim/lib/compare/data/types.ts | 10 - apps/sim/lib/copilot/model-visible-content.ts | 40 --- apps/sim/lib/copilot/request/tools/files.ts | 4 - .../sim/lib/copilot/tool-executor/executor.ts | 4 - apps/sim/lib/copilot/tool-executor/router.ts | 28 -- .../copilot/tools/client/local-filesystem.ts | 6 - .../tools/server/files/file-intent-store.ts | 53 +-- .../copilot/tools/shared/workflow-utils.ts | 12 - apps/sim/lib/core/admission/gate.ts | 7 - apps/sim/lib/core/execution-limits/types.ts | 7 - apps/sim/lib/core/idempotency/service.ts | 16 - .../lib/core/rate-limiter/hosted-key/queue.ts | 4 - apps/sim/lib/core/utils/response-format.ts | 27 -- apps/sim/lib/core/utils/theme.ts | 8 - apps/sim/lib/core/utils/user-file.ts | 17 - apps/sim/lib/credentials/client-state.ts | 26 -- apps/sim/lib/credentials/queries.ts | 19 - apps/sim/lib/execution/event-buffer.ts | 19 +- .../execution/remote-sandbox/sandbox-spec.ts | 2 - apps/sim/lib/folders/queries.ts | 28 -- apps/sim/lib/guardrails/pii-entities.ts | 14 - .../lib/integrations/availability.server.ts | 15 - apps/sim/lib/library/registry.ts | 2 - apps/sim/lib/logs/get-trigger-options.ts | 8 - apps/sim/lib/mcp/service.ts | 22 -- apps/sim/lib/mcp/storage/factory.ts | 11 - apps/sim/lib/mcp/workflow-tool-schema.ts | 17 - .../lib/mothership/inbox/agentmail-client.ts | 4 - apps/sim/lib/permission-groups/types.ts | 4 - apps/sim/lib/pptx-renderer/parser/units.ts | 21 -- .../renderer/predefined-table-styles.ts | 8 - apps/sim/lib/pptx-renderer/shapes/presets.ts | 15 - apps/sim/lib/table/cell-write.ts | 8 - apps/sim/lib/table/dispatcher.ts | 12 - apps/sim/lib/table/jobs/service.ts | 19 - apps/sim/lib/table/mutation-locks.ts | 10 - .../sim/lib/table/query-builder/converters.ts | 22 -- .../table/query-builder/use-query-builder.ts | 60 ---- apps/sim/lib/uploads/config.ts | 28 -- .../contexts/copilot/copilot-file-manager.ts | 83 +---- .../workspace-file-folder-manager.ts | 86 ----- apps/sim/lib/uploads/utils/file-utils.ts | 8 - apps/sim/lib/uploads/utils/validation.ts | 73 ---- .../sim/lib/workflows/autolayout/constants.ts | 23 -- .../credentials/credential-extractor.ts | 151 -------- .../lib/workflows/custom-blocks/operations.ts | 6 - .../lib/workflows/dynamic-handle-topology.ts | 6 - .../workflows/executor/execution-events.ts | 33 -- .../workflows/operations/deployment-utils.ts | 41 --- apps/sim/lib/workflows/schedules/utils.ts | 156 +------- .../search-replace/resources/resolvers.ts | 11 - .../sim/lib/workflows/subblocks/visibility.ts | 15 - .../lib/workflows/triggers/trigger-utils.ts | 67 ---- apps/sim/lib/workflows/triggers/triggers.ts | 4 - apps/sim/lib/workspaces/naming.ts | 61 ---- apps/sim/lib/workspaces/organization/utils.ts | 8 - apps/sim/providers/pi-providers.ts | 8 - apps/sim/providers/registry.ts | 16 - apps/sim/scripts/pi-sandbox-packages.ts | 7 - apps/sim/stores/chat/utils.ts | 8 - apps/sim/stores/folders/store.ts | 6 - apps/sim/tools/airweave/types.ts | 9 - apps/sim/tools/azure_devops/utils.ts | 3 - apps/sim/tools/calcom/types.ts | 76 ---- apps/sim/tools/confluence/types.ts | 81 ----- apps/sim/tools/context_dev/types.ts | 9 - apps/sim/tools/docusign/types.ts | 12 - apps/sim/tools/dropcontact/types.ts | 9 - apps/sim/tools/firecrawl/types.ts | 114 ------ apps/sim/tools/github/types.ts | 64 ---- apps/sim/tools/google/types.ts | 18 - apps/sim/tools/incidentio/types.ts | 63 ---- apps/sim/tools/intercom/types.ts | 133 ------- apps/sim/tools/jina/types.ts | 60 ---- apps/sim/tools/jira/types.ts | 87 ----- apps/sim/tools/jsm/types.ts | 7 - apps/sim/tools/kalshi/types.ts | 108 ------ apps/sim/tools/leadmagic/types.ts | 18 +- apps/sim/tools/linear/types.ts | 75 ---- apps/sim/tools/managed_agent/shared.ts | 14 - apps/sim/tools/mem0/types.ts | 18 - apps/sim/tools/mistral/types.ts | 27 -- apps/sim/tools/notion/types.ts | 44 --- apps/sim/tools/outlook/types.ts | 9 - apps/sim/tools/pipedrive/types.ts | 63 ---- apps/sim/tools/postgresql/types.ts | 18 - apps/sim/tools/qdrant/types.ts | 27 -- apps/sim/tools/reddit/types.ts | 68 ---- apps/sim/tools/salesforce/types.ts | 162 --------- apps/sim/tools/serper/types.ts | 157 -------- apps/sim/tools/slack/types.ts | 93 ----- apps/sim/tools/spotify/types.ts | 45 --- apps/sim/tools/stagehand/types.ts | 88 ----- apps/sim/tools/stripe/types.ts | 79 ----- apps/sim/tools/stt/types.ts | 9 - apps/sim/tools/supabase/types.ts | 89 ----- apps/sim/tools/tavily/types.ts | 63 ---- apps/sim/tools/tts/types.ts | 143 -------- apps/sim/tools/wealthbox/utils.ts | 7 - apps/sim/tools/webflow/types.ts | 9 - apps/sim/tools/wikipedia/types.ts | 36 -- apps/sim/tools/workday/soap.ts | 12 - apps/sim/tools/zep/types.ts | 9 - apps/sim/tools/zoho/types.ts | 34 -- apps/sim/tools/zoho/utils.ts | 126 ------- apps/sim/tools/zoom/types.ts | 81 ----- apps/sim/triggers/calendly/utils.ts | 46 --- apps/sim/triggers/github/utils.ts | 92 ----- apps/sim/triggers/index.ts | 10 - 166 files changed, 10 insertions(+), 5930 deletions(-) delete mode 100644 apps/sim/app/(landing)/components/hero/components/hero-visual/stage-workflow.tsx delete mode 100644 apps/sim/app/(landing)/components/landing-preview/landing-preview-mount.tsx delete mode 100644 apps/sim/lib/copilot/model-visible-content.ts diff --git a/apps/desktop/src/main/browser-import/browser-sources.ts b/apps/desktop/src/main/browser-import/browser-sources.ts index 800aecfc61a..7d65b37ca99 100644 --- a/apps/desktop/src/main/browser-import/browser-sources.ts +++ b/apps/desktop/src/main/browser-import/browser-sources.ts @@ -77,23 +77,6 @@ export function userDataDirFor(source: BrowserSource, home: string = homedir()): return join(home, ...source.userDataSegments) } -/** - * Splits a bridge profile id back into its browser and profile directory. - * - * Ids are namespaced (`arc:Profile 1`) because profile directory names repeat - * across browsers — every one of them has a `Default`. Returns null for - * anything malformed; the caller then resolves against discovered profiles - * anyway, so a bad id can never become a path. - */ -export function parseProfileId(profileId: string): { sourceId: string; directory: string } | null { - const separator = profileId.indexOf(':') - if (separator <= 0 || separator === profileId.length - 1) return null - return { - sourceId: profileId.slice(0, separator), - directory: profileId.slice(separator + 1), - } -} - export function formatProfileId(sourceId: string, directory: string): string { return `${sourceId}:${directory}` } diff --git a/apps/desktop/src/main/terminal/index.ts b/apps/desktop/src/main/terminal/index.ts index 3ac6530f1ed..859f80088e6 100644 --- a/apps/desktop/src/main/terminal/index.ts +++ b/apps/desktop/src/main/terminal/index.ts @@ -32,7 +32,6 @@ import { type TerminalToolResponse, } from '@sim/terminal-protocol' import { sleep } from '@sim/utils/helpers' -import { isRecordLike } from '@sim/utils/object' import type { BrowserWindow, WebContents } from 'electron' import { type FocusedResourceShortcut, @@ -1201,8 +1200,3 @@ export class TerminalService { function unknownTerminal(terminalId: string): string { return `No terminal with id ${terminalId}. Call terminal_list for the open ones.` } - -/** Narrows an IPC payload to the tool-call shape without trusting the sender. */ -export function parseToolParams(value: unknown): Record { - return isRecordLike(value) ? value : {} -} diff --git a/apps/sim/app/(landing)/components/hero/components/hero-visual/stage-workflow.tsx b/apps/sim/app/(landing)/components/hero/components/hero-visual/stage-workflow.tsx deleted file mode 100644 index da3d057c0b6..00000000000 --- a/apps/sim/app/(landing)/components/hero/components/hero-visual/stage-workflow.tsx +++ /dev/null @@ -1,133 +0,0 @@ -'use client' - -import { type CSSProperties, useEffect, useLayoutEffect, useRef, useState } from 'react' -import { cn } from '@sim/emcn' -import { WorkflowBlock } from '@/app/(landing)/components/hero/components/hero-visual/workflow-block' -import { - BLOCK_WIDTH, - BLOCKS, - CANVAS, - EDGES, - WORKFLOW_FOCUS_SCALE, -} from '@/app/(landing)/components/hero/components/hero-visual/workflow-data' - -/** The camera beat: held on the first block, panning out, or settled on the whole flow. */ -export type WorkflowCameraStage = 'focus' | 'out' | 'hold' - -interface StageWorkflowProps { - stage: WorkflowCameraStage -} - -/** First (GitHub) block center in design space - the camera's focus target. */ -const FOCUS_CENTER = { x: BLOCK_WIDTH / 2, y: 38 } -/** Whole-canvas center - the overview camera target. */ -const CANVAS_CENTER = { x: CANVAS.width / 2, y: CANVAS.height / 2 } -/** Zoomed-in scale while held on the first block (≈ the morphed chat card size). */ -const FOCUS_SCALE = WORKFLOW_FOCUS_SCALE -/** Pulled-back scale that fits the whole workflow in the panel. */ -const OVERVIEW_SCALE = 0.68 - -/** - * The workflow stage of the hero visual - a design-space canvas with a moving - * "camera". It opens **focused** on the first block (the chat card has just - * morphed into it), holds while that block's content lands and the first edge - * draws, then the camera **pans + zooms out together** to reveal the whole - * GitHub → Agent → Jira flow (the {@link stage} prop drives this). - * - * The camera is a transform on the design-space canvas, positioned so the focus - * point lands at the panel center: `translate(vpW/2 - cx·s, vpH/2 - cy·s) - * scale(s)` (origin top-left). The panel size is measured; until it is known, - * and on first mount, the transition is suppressed so the opening focus frame - * doesn't animate in from a fallback. Purely decorative - `aria-hidden`. - */ -export function StageWorkflow({ stage }: StageWorkflowProps) { - const viewportRef = useRef(null) - const [vp, setVp] = useState<{ w: number; h: number } | null>(null) - const [animate, setAnimate] = useState(false) - - useLayoutEffect(() => { - const el = viewportRef.current - if (!el) return - const measure = () => { - const r = el.getBoundingClientRect() - // Guard an unpainted/collapsed panel from poisoning the camera math. - if (r.width > 120 && r.height > 120) setVp({ w: r.width, h: r.height }) - } - measure() - const ro = new ResizeObserver(measure) - ro.observe(el) - return () => ro.disconnect() - }, []) - - // Enable the camera transition only after the opening focus frame is painted, - // so mounting (and the first measurement) snaps into focus rather than gliding. - useEffect(() => { - if (vp) setAnimate(true) - }, [vp]) - - const focused = stage === 'focus' - const center = focused ? FOCUS_CENTER : CANVAS_CENTER - const scale = focused ? FOCUS_SCALE : OVERVIEW_SCALE - const transform = vp - ? `translate(${vp.w / 2 - center.x * scale}px, ${vp.h / 2 - center.y * scale}px) scale(${scale})` - : `translate(0px, 0px) scale(${OVERVIEW_SCALE})` - - return ( -
-
- - {BLOCKS.map((block) => ( - // The first block is already on screen - the chat card morphed into it, - // and the focused camera lands it pixel-matched here; the rest sit in - // design space and are revealed by the camera pull-out. -
- -
- ))} -
-
- ) -} diff --git a/apps/sim/app/(landing)/components/landing-preview/landing-preview-mount.tsx b/apps/sim/app/(landing)/components/landing-preview/landing-preview-mount.tsx deleted file mode 100644 index b4e120fae45..00000000000 --- a/apps/sim/app/(landing)/components/landing-preview/landing-preview-mount.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client' - -import dynamic from 'next/dynamic' -import type { SidebarView } from '@/app/(landing)/components/landing-preview/components/landing-preview-sidebar/landing-preview-sidebar' -import { useLazyMount } from '@/app/(landing)/hooks/use-lazy-mount' - -/** Dimension-stable placeholder sized to the preview's exact footprint (zero CLS). */ -const PLACEHOLDER_CLASS = 'aspect-[1116/615] w-full rounded bg-[var(--surface-1)]' - -/** - * Client mount for the {@link LandingPreview} - the heavy, animated workspace - * island (framer-motion + reactflow). Isolated here so the sections that show it - * stay Server Components: only this leaf is `'use client'`. - * - * Loaded with `ssr: false` so the framer-motion/reactflow bundle never ships in - * the server-rendered HTML, and gated on viewport proximity via - * {@link useLazyMount} so the below-the-fold previews don't pull the heavy - * bundle into the initial homepage load. A dimension-stable placeholder (the - * preview's exact `aspect-[1116/615]` footprint, filled with the canvas - * surface) holds the space before and during load, so there is zero layout - * shift or flash. - */ -const LandingPreview = dynamic( - () => - import('@/app/(landing)/components/landing-preview/landing-preview').then( - (mod) => mod.LandingPreview - ), - { - ssr: false, - loading: () =>
, - } -) - -interface LandingPreviewMountProps { - /** Forwarded to {@link LandingPreview}; `false` renders a static snapshot. */ - autoplay?: boolean - /** Forwarded to {@link LandingPreview}; the static snapshot's staged view. */ - view?: SidebarView - /** Forwarded to {@link LandingPreview}; the static snapshot's workflow. */ - workflowId?: string -} - -export function LandingPreviewMount({ autoplay, view, workflowId }: LandingPreviewMountProps) { - const { ref, inView } = useLazyMount('400px') - - return ( -
- {inView ? ( - - ) : ( -
- )} -
- ) -} diff --git a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts index d70d636bf2c..bbb7a06986d 100644 --- a/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts +++ b/apps/sim/app/(landing)/components/mothership/components/iso-marks/iso-illustration-style.ts @@ -1,7 +1,6 @@ import type { SVGProps } from 'react' export const ISO_LINE_STROKE_WIDTH = 3.2 -export const ISO_ENDPOINT_STROKE_WIDTH = 3.3 export const ISO_STROKE = 'color-mix(in srgb, var(--text-subtle) 76%, var(--text-muted))' export const ISO_FILL_LOW = 'var(--surface-6)' export const ISO_FILL_MID = 'color-mix(in srgb, var(--surface-3) 58%, var(--surface-6))' diff --git a/apps/sim/app/api/files/utils.ts b/apps/sim/app/api/files/utils.ts index e9baede4e62..6480da5d0b7 100644 --- a/apps/sim/app/api/files/utils.ts +++ b/apps/sim/app/api/files/utils.ts @@ -79,22 +79,6 @@ export const contentTypeMap: Record = { googleFolder: 'application/vnd.google-apps.folder', } -export const binaryExtensions = [ - 'doc', - 'docx', - 'xls', - 'xlsx', - 'ppt', - 'pptx', - 'zip', - 'png', - 'jpg', - 'jpeg', - 'gif', - 'webp', - 'pdf', -] - export function getContentType(filename: string): string { const extension = filename.split('.').pop()?.toLowerCase() || '' return contentTypeMap[extension] || 'application/octet-stream' diff --git a/apps/sim/app/api/table/utils.ts b/apps/sim/app/api/table/utils.ts index e548b0da607..9c375aa8736 100644 --- a/apps/sim/app/api/table/utils.ts +++ b/apps/sim/app/api/table/utils.ts @@ -2,11 +2,6 @@ import { createLogger } from '@sim/logger' import { permissionSatisfies } from '@sim/platform-authz/workspace' import { toError } from '@sim/utils/errors' import { NextResponse } from 'next/server' -import { - createTableColumnBodySchema, - deleteTableColumnBodySchema, - updateTableColumnBodySchema, -} from '@/lib/api/contracts/tables' import { isFeatureEnabled } from '@/lib/core/config/feature-flags' import { asOrchestrationError, @@ -298,21 +293,6 @@ export function accessError( return NextResponse.json({ error: message }, { status: result.status }) } -/** - * Converts a TableAccessDenied result to an appropriate HTTP response. - * Use with checkTableAccess or checkTableWriteAccess. - */ -export function tableAccessError( - result: TableAccessDenied, - requestId: string, - context?: string -): NextResponse { - const status = result.notFound ? 404 : 403 - const message = result.notFound ? 'Table not found' : (result.reason ?? 'Access denied') - logger.warn(`[${requestId}] ${message}${context ? `: ${context}` : ''}`) - return NextResponse.json({ error: message }, { status }) -} - export function errorResponse( message: string, status: number, @@ -340,15 +320,3 @@ export function forbiddenResponse(message = 'Access denied') { export function notFoundResponse(message = 'Resource not found') { return errorResponse(message, 404) } - -export function serverErrorResponse(message = 'Internal server error') { - return errorResponse(message, 500) -} - -/** - * Re-exports from `lib/api/contracts/tables` so existing routes that import - * these names keep working while sharing a single source of truth. - */ -export const CreateColumnSchema = createTableColumnBodySchema -export const UpdateColumnSchema = updateTableColumnBodySchema -export const DeleteColumnSchema = deleteTableColumnBodySchema diff --git a/apps/sim/app/api/tools/neo4j/utils.ts b/apps/sim/app/api/tools/neo4j/utils.ts index ac0bdf0eb0e..75df20798d6 100644 --- a/apps/sim/app/api/tools/neo4j/utils.ts +++ b/apps/sim/app/api/tools/neo4j/utils.ts @@ -62,33 +62,6 @@ export function validateCypherQuery(query: string): { isValid: boolean; error?: return { isValid: true } } -export function sanitizeLabelName(name: string): string { - if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(name)) { - throw new Error( - 'Invalid label name. Must start with a letter and contain only letters, numbers, and underscores.' - ) - } - return name -} - -export function sanitizePropertyKey(key: string): string { - if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) { - throw new Error( - 'Invalid property key. Must start with a letter and contain only letters, numbers, and underscores.' - ) - } - return key -} - -export function sanitizeRelationshipType(type: string): string { - if (!/^[A-Z][A-Z0-9_]*$/.test(type)) { - throw new Error( - 'Invalid relationship type. Must start with an uppercase letter and contain only uppercase letters, numbers, and underscores.' - ) - } - return type -} - export function convertNeo4jTypesToJSON(value: unknown): unknown { if (value === null || value === undefined) { return value diff --git a/apps/sim/app/api/tools/ssh/utils.ts b/apps/sim/app/api/tools/ssh/utils.ts index 3d64440e22d..9f375ca2796 100644 --- a/apps/sim/app/api/tools/ssh/utils.ts +++ b/apps/sim/app/api/tools/ssh/utils.ts @@ -365,22 +365,6 @@ export function escapeShellArg(arg: string): string { return arg.replace(/'/g, "'\\''") } -/** - * Validate that authentication credentials are provided - */ -export function validateAuth(params: { password?: string; privateKey?: string }): { - isValid: boolean - error?: string -} { - if (!params.password && !params.privateKey) { - return { - isValid: false, - error: 'Either password or privateKey must be provided for authentication', - } - } - return { isValid: true } -} - /** * Parse file permissions from octal string */ diff --git a/apps/sim/app/api/v1/admin/types.ts b/apps/sim/app/api/v1/admin/types.ts index a6062ca8eee..3113d85e8a9 100644 --- a/apps/sim/app/api/v1/admin/types.ts +++ b/apps/sim/app/api/v1/admin/types.ts @@ -48,19 +48,6 @@ export interface PaginationMeta { export const DEFAULT_LIMIT = 50 export const MAX_LIMIT = 250 -export function parsePaginationParams(url: URL): PaginationParams { - return { - limit: parsePaginationNumber(url.searchParams.get('limit'), DEFAULT_LIMIT, MAX_LIMIT), - offset: parsePaginationNumber(url.searchParams.get('offset'), 0), - } -} - -function parsePaginationNumber(value: string | null, fallback: number, max?: number): number { - const parsed = value ? Number.parseInt(value, 10) : fallback - if (!Number.isInteger(parsed) || parsed < 1) return fallback - return max === undefined ? parsed : Math.min(parsed, max) -} - export function createPaginationMeta(total: number, limit: number, offset: number): PaginationMeta { return { total, diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts index b3dc95416bb..7b05e1dcf23 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts @@ -135,21 +135,3 @@ export function getDisplayName(span: TraceSpan): string { export function formatCostAmount(value: number | undefined): string | undefined { return formatCreditCost(value, { emptyForZeroOrLess: true }) } - -export function formatTokensSummary(tokens: TraceSpan['tokens']): string | undefined { - if (!tokens) return undefined - const parts: string[] = [] - const input = formatTokenCount(tokens.input) - const output = formatTokenCount(tokens.output) - const total = formatTokenCount(tokens.total) - const cacheRead = formatTokenCount(tokens.cacheRead) - const cacheWrite = formatTokenCount(tokens.cacheWrite) - const reasoning = formatTokenCount(tokens.reasoning) - if (input) parts.push(`${input} in`) - if (cacheRead) parts.push(`${cacheRead} cached`) - if (cacheWrite) parts.push(`${cacheWrite} cache write`) - if (output) parts.push(`${output} out`) - if (reasoning) parts.push(`${reasoning} reasoning`) - if (total) parts.push(`${total} total`) - return parts.length > 0 ? parts.join(' · ') : undefined -} diff --git a/apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts b/apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts index 8c2c4661122..9a132b10f15 100644 --- a/apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts +++ b/apps/sim/app/workspace/[workspaceId]/scheduled-tasks/utils/recurrence.ts @@ -36,12 +36,6 @@ export interface Recurrence { cron?: string } -export const DEFAULT_RECURRENCE: Recurrence = { - frequency: 'once', - weekdays: [], - end: { type: 'never' }, -} - /** Upper bound on occurrences materialized for one schedule in a single view. */ const MAX_OCCURRENCES_PER_VIEW = 500 diff --git a/apps/sim/app/workspace/[workspaceId]/upgrade/subscription-permissions.ts b/apps/sim/app/workspace/[workspaceId]/upgrade/subscription-permissions.ts index bf07d457132..681edb0490b 100644 --- a/apps/sim/app/workspace/[workspaceId]/upgrade/subscription-permissions.ts +++ b/apps/sim/app/workspace/[workspaceId]/upgrade/subscription-permissions.ts @@ -61,22 +61,3 @@ export function getSubscriptionPermissions( canViewUsageInfo, } } - -export function getVisiblePlans( - subscription: SubscriptionState, - userRole: UserRole -): ('pro' | 'team' | 'enterprise')[] { - const plans: ('pro' | 'team' | 'enterprise')[] = [] - const { isFree, isPro, isEnterprise, isOrgScoped } = subscription - const { isTeamAdmin } = userRole - - if (isFree) { - plans.push('pro', 'team', 'enterprise') - } else if (isPro && !isOrgScoped) { - plans.push('team', 'enterprise') - } else if (isOrgScoped && isTeamAdmin && !isEnterprise) { - plans.push('enterprise') - } - - return plans -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx index 7909f8bfc6f..5e5802eef4a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx @@ -115,32 +115,3 @@ export function NextError({ error, reset }: NextErrorProps) { return } - -/** - * Next.js Global Error Page Component - * Renders for application-level errors - */ -export function NextGlobalError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - useEffect(() => { - logger.error('Global workspace error:', { error }) - }, [error]) - - return ( - - - - - - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts index d9cdf9702ac..85acddb8a22 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts @@ -210,17 +210,6 @@ export const TOP_LEVEL_COMMANDS: readonly SlashCommand[] = [ { id: 'actions', label: 'Actions' }, ] as const -/** - * Maps UI command IDs to API command IDs. - * Some commands have different IDs for display vs API (e.g., "actions" -> "superagent") - */ -export function getApiCommandId(uiCommandId: string): string { - const commandMapping: Record = { - actions: 'superagent', - } - return commandMapping[uiCommandId] || uiCommandId -} - export const WEB_COMMANDS: readonly SlashCommand[] = [ { id: 'search', label: 'Search' }, { id: 'read', label: 'Read' }, @@ -230,37 +219,7 @@ export const WEB_COMMANDS: readonly SlashCommand[] = [ export const ALL_SLASH_COMMANDS: readonly SlashCommand[] = [...TOP_LEVEL_COMMANDS, ...WEB_COMMANDS] -export const ALL_COMMAND_IDS = ALL_SLASH_COMMANDS.map((cmd) => cmd.id) - -/** - * Get display label for a command ID - */ -export function getCommandDisplayLabel(commandId: string): string { - const command = ALL_SLASH_COMMANDS.find((cmd) => cmd.id === commandId) - return command?.label || commandId.charAt(0).toUpperCase() + commandId.slice(1) -} - -/** - * Threshold for considering input "near top" of viewport (in pixels) - */ -export const NEAR_TOP_THRESHOLD = 300 - /** * Scroll tolerance for mention menu positioning (in pixels) */ export const SCROLL_TOLERANCE = 8 - -/** - * Shared CSS classes for menu state text (loading, empty states) - */ -export const MENU_STATE_TEXT_CLASSES = 'px-2 py-2 text-caption text-[var(--text-muted)]' - -/** - * Calculates the next index for circular navigation (wraps around at bounds) - */ -export function getNextIndex(current: number, direction: 'up' | 'down', maxIndex: number): number { - if (direction === 'down') { - return current >= maxIndex ? 0 : current + 1 - } - return current <= 0 ? maxIndex : current - 1 -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils.ts index 3e8c4d8be5d..9ecf8397c7f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils.ts @@ -1,4 +1,3 @@ -import type { ReactNode } from 'react' import { FOLDER_CONFIGS, type MentionFolderId, @@ -123,42 +122,6 @@ export function computeMentionHighlightRanges( return ranges } -/** - * Builds React nodes with highlighted mention tokens - * @param text - Text to render - * @param contexts - Chat contexts to highlight - * @param createHighlightSpan - Function to create highlighted span element - * @returns Array of React nodes with highlighted mentions - */ -export function buildMentionHighlightNodes( - text: string, - contexts: ChatContext[], - createHighlightSpan: (token: string, key: string) => ReactNode -): ReactNode[] { - const tokens = extractContextTokens(contexts) - if (!tokens.length) return [text] - - const ranges = computeMentionHighlightRanges(text, tokens) - if (!ranges.length) return [text] - - const nodes: ReactNode[] = [] - let lastIndex = 0 - - for (const range of ranges) { - if (range.start > lastIndex) { - nodes.push(text.slice(lastIndex, range.start)) - } - nodes.push(createHighlightSpan(range.token, `mention-${range.start}-${range.end}`)) - lastIndex = range.end - } - - if (lastIndex < text.length) { - nodes.push(text.slice(lastIndex)) - } - - return nodes -} - /** * Gets the data array for a folder ID from mentionData. * Uses FOLDER_CONFIGS as the source of truth for key mapping. @@ -169,18 +132,6 @@ export function getFolderData(mentionData: MentionDataReturn, folderId: MentionF return (mentionData[config.dataKey as keyof MentionDataReturn] as any[]) || [] } -/** - * Gets the loading state for a folder ID from mentionData. - * Uses FOLDER_CONFIGS as the source of truth for key mapping. - */ -export function getFolderLoading( - mentionData: MentionDataReturn, - folderId: MentionFolderId -): boolean { - const config = FOLDER_CONFIGS[folderId] - return mentionData[config.loadingKey as keyof MentionDataReturn] as boolean -} - /** * Gets the ensure loaded function for a folder ID from mentionData. * Uses FOLDER_CONFIGS as the source of truth for key mapping. diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/node-derivation.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/node-derivation.ts index b1f9e45b49c..e361720baa0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/node-derivation.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/node-derivation.ts @@ -19,14 +19,3 @@ export function computeContainerZIndex( return depth } - -export function computeBlockZIndex( - block: Pick, - allBlocks: Record> -): number { - if (block.type === 'loop' || block.type === 'parallel') { - return computeContainerZIndex(block, allBlocks) - } - - return block.data?.parentId ? Z_INDEX.CHILD_BLOCK : Z_INDEX.ROOT_BLOCK -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts index da1c4c12591..4c2120940c0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts @@ -198,46 +198,6 @@ export function computeClampedPositionUpdates( })) } -interface ParentUpdateEntry { - blockId: string - newParentId: string - affectedEdges: Edge[] -} - -/** - * Computes parent update entries for nodes being moved into a subflow. - * Only includes "boundary edges" - edges that cross the selection boundary - * (one end inside selection, one end outside). Edges between nodes in the - * selection are preserved. - */ -export function computeParentUpdateEntries( - validNodes: Node[], - allEdges: Edge[], - targetParentId: string -): ParentUpdateEntry[] { - const movingNodeIds = new Set(validNodes.map((n) => n.id)) - - // Find edges that cross the boundary (one end inside selection, one end outside) - // Edges between nodes in the selection should stay intact - const boundaryEdges = allEdges.filter((e) => { - const sourceInSelection = movingNodeIds.has(e.source) - const targetInSelection = movingNodeIds.has(e.target) - // Only remove if exactly one end is in the selection (crosses boundary) - return sourceInSelection !== targetInSelection - }) - - // Build updates for all valid nodes - return validNodes.map((n) => { - // Only include boundary edges connected to this specific node - const edgesForThisNode = boundaryEdges.filter((e) => e.source === n.id || e.target === n.id) - return { - blockId: n.id, - newParentId: targetParentId, - affectedEdges: edgesForThisNode, - } - }) -} - /** * Resolves parent-child selection conflicts by deselecting children whose parent is also selected. */ diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index bf98272ea24..909e42a01f4 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -9,7 +9,7 @@ import { import { getScopesForService } from '@/lib/oauth/utils' import { containsReference } from '@/lib/workflows/sanitization/references' import { buildCanonicalIndex } from '@/lib/workflows/subblocks/visibility' -import type { BlockOutput, OutputFieldDefinition, SubBlockConfig } from '@/blocks/types' +import type { SubBlockConfig } from '@/blocks/types' import { getBaseModelProviders, getHostedModels, @@ -145,24 +145,6 @@ export function getSubBlocksDependingOnChange( ) } -export function resolveOutputType( - outputs: Record -): Record { - const resolvedOutputs: Record = {} - - for (const [key, outputType] of Object.entries(outputs)) { - // Handle new format: { type: 'string', description: '...' } - if (typeof outputType === 'object' && outputType !== null && 'type' in outputType) { - resolvedOutputs[key] = outputType.type as BlockOutput - } else { - // Handle old format: just the type as string, or other object formats - resolvedOutputs[key] = outputType as BlockOutput - } - } - - return resolvedOutputs -} - function getProviderFromStore(model: string): string | null { const { providers } = useProvidersStore.getState() const normalized = model.toLowerCase() diff --git a/apps/sim/components/settings/navigation.ts b/apps/sim/components/settings/navigation.ts index cb8e23ec1a6..514dc2bd7d6 100644 --- a/apps/sim/components/settings/navigation.ts +++ b/apps/sim/components/settings/navigation.ts @@ -353,13 +353,6 @@ export const ORGANIZATION_SETTINGS_GROUPS = [ { key: 'enterprise', title: 'Enterprise' }, ] as const -export const WORKSPACE_SETTINGS_GROUPS = [ - { key: 'workspace', title: 'Workspace' }, - { key: 'tools', title: 'Tools' }, - { key: 'system', title: 'System' }, - { key: 'enterprise', title: 'Enterprise' }, -] as const - export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] = [ { label: 'General', diff --git a/apps/sim/executor/constants.ts b/apps/sim/executor/constants.ts index 97194928e0b..6e905d0fc4f 100644 --- a/apps/sim/executor/constants.ts +++ b/apps/sim/executor/constants.ts @@ -169,12 +169,6 @@ export const LOOP_REFERENCE = { INDEX_PATH: 'loop.index', } as const -export const PARALLEL_REFERENCE = { - INDEX: 'index', - CURRENT_ITEM: 'currentItem', - ITEMS: 'items', -} as const - export const DEFAULTS = { BLOCK_TYPE: 'unknown', BLOCK_TITLE: 'Untitled Block', @@ -277,12 +271,6 @@ export function buildResumeUiUrl( return `${prefix}${PAUSE_RESUME.PATH.UI_RESUME}/${workflowId}/${executionId}` } -export const PARSING = { - JSON_RADIX: 10, - PREVIEW_LENGTH: 200, - PREVIEW_SUFFIX: '...', -} as const - export type FieldType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'files' | 'plain' interface ConditionConfig { @@ -349,26 +337,6 @@ export function isAnnotationOnlyBlock(blockType: string | undefined): boolean { return blockType === BlockType.NOTE } -export function supportsHandles(blockType: string | undefined): boolean { - return !isAnnotationOnlyBlock(blockType) -} - -export function getDefaultTokens() { - return { - input: DEFAULTS.TOKENS.PROMPT, - output: DEFAULTS.TOKENS.COMPLETION, - total: DEFAULTS.TOKENS.TOTAL, - } -} - -export function getDefaultCost() { - return { - input: DEFAULTS.COST.INPUT, - output: DEFAULTS.COST.OUTPUT, - total: DEFAULTS.COST.TOTAL, - } -} - export function buildReference(path: string): string { return `${REFERENCE.START}${path}${REFERENCE.END}` } @@ -377,26 +345,10 @@ export function buildLoopReference(property: string): string { return buildReference(`${REFERENCE.PREFIX.LOOP}${REFERENCE.PATH_DELIMITER}${property}`) } -export function buildParallelReference(property: string): string { - return buildReference(`${REFERENCE.PREFIX.PARALLEL}${REFERENCE.PATH_DELIMITER}${property}`) -} - -export function buildVariableReference(variableName: string): string { - return buildReference(`${REFERENCE.PREFIX.VARIABLE}${REFERENCE.PATH_DELIMITER}${variableName}`) -} - -export function buildBlockReference(blockId: string, path?: string): string { - return buildReference(path ? `${blockId}${REFERENCE.PATH_DELIMITER}${path}` : blockId) -} - export function buildLoopIndexCondition(maxIterations: number): string { return `${buildLoopReference(LOOP_REFERENCE.INDEX)} < ${maxIterations}` } -export function buildEnvVarReference(varName: string): string { - return `${REFERENCE.ENV_VAR_START}${varName}${REFERENCE.ENV_VAR_END}` -} - export function isReference(value: string): boolean { return value.startsWith(REFERENCE.START) && value.endsWith(REFERENCE.END) } @@ -465,10 +417,6 @@ export function stripCustomToolPrefix(name: string): string { : name } -export function stripMcpToolPrefix(name: string): string { - return name.startsWith(MCP.TOOL_PREFIX) ? name.slice(MCP.TOOL_PREFIX.length) : name -} - export function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } diff --git a/apps/sim/executor/handlers/shared/response-format.ts b/apps/sim/executor/handlers/shared/response-format.ts index 1d31b6ff557..9f1ad68a5ef 100644 --- a/apps/sim/executor/handlers/shared/response-format.ts +++ b/apps/sim/executor/handlers/shared/response-format.ts @@ -49,38 +49,6 @@ export function parseResponseFormat(responseFormat?: string | object): any { return undefined } -/** - * Validate and extract messages from a raw input value. - * - * Accepts a JSON string or an array. Each entry must have - * `role` (string) and `content` (string). - */ -export function resolveMessages(raw: unknown): Array<{ role: string; content: string }> { - if (!raw) { - throw new Error('Messages input is required') - } - - let messages: unknown[] - if (typeof raw === 'string') { - try { - messages = JSON.parse(raw) - } catch { - throw new Error('Messages must be a valid JSON array') - } - } else if (Array.isArray(raw)) { - messages = raw - } else { - throw new Error('Messages must be an array of {role, content} objects') - } - - return messages.map((msg: any, i: number) => { - if (!msg.role || typeof msg.content !== 'string') { - throw new Error(`Message at index ${i} must have "role" (string) and "content" (string)`) - } - return { role: String(msg.role), content: msg.content } - }) -} - /** * Try to parse the LLM response content as structured JSON and spread * the fields into the block output. Falls back to returning raw content. diff --git a/apps/sim/executor/human-in-the-loop/utils.ts b/apps/sim/executor/human-in-the-loop/utils.ts index 1b060cf833a..0b2c5467cae 100644 --- a/apps/sim/executor/human-in-the-loop/utils.ts +++ b/apps/sim/executor/human-in-the-loop/utils.ts @@ -27,18 +27,6 @@ export function generatePauseContextId( return contextId } -export function buildTriggerBlockId(nodeId: string): string { - if (nodeId.includes('__response')) { - return nodeId.replace('__response', '__trigger') - } - - if (nodeId.endsWith('_response')) { - return nodeId.replace(/_response$/, '_trigger') - } - - return `${nodeId}__trigger` -} - export function mapNodeMetadataToPauseScopes( ctx: ExecutionContext, nodeMetadata: NodeMetadataLike diff --git a/apps/sim/executor/types/loop.ts b/apps/sim/executor/types/loop.ts index eebc87e4a61..e2be8cca95a 100644 --- a/apps/sim/executor/types/loop.ts +++ b/apps/sim/executor/types/loop.ts @@ -3,7 +3,3 @@ import type { SerializedLoop } from '@/serializer/types' export interface LoopConfigWithNodes extends SerializedLoop { nodes: string[] } - -export function isLoopConfigWithNodes(config: SerializedLoop): config is LoopConfigWithNodes { - return Array.isArray((config as any).nodes) -} diff --git a/apps/sim/executor/types/parallel.ts b/apps/sim/executor/types/parallel.ts index 3ec58d7941d..6b7d279796e 100644 --- a/apps/sim/executor/types/parallel.ts +++ b/apps/sim/executor/types/parallel.ts @@ -3,9 +3,3 @@ import type { SerializedParallel } from '@/serializer/types' export interface ParallelConfigWithNodes extends SerializedParallel { nodes: string[] } - -export function isParallelConfigWithNodes( - config: SerializedParallel -): config is ParallelConfigWithNodes { - return Array.isArray((config as any).nodes) -} diff --git a/apps/sim/executor/utils/json.ts b/apps/sim/executor/utils/json.ts index 8890e7cbb88..1b3bd11ca67 100644 --- a/apps/sim/executor/utils/json.ts +++ b/apps/sim/executor/utils/json.ts @@ -1,5 +1,4 @@ import { createLogger } from '@sim/logger' -import { getErrorMessage } from '@sim/utils/errors' import { EVALUATOR } from '@/executor/constants' const logger = createLogger('JSONUtils') @@ -16,18 +15,6 @@ export function parseJSON(value: unknown, fallback: T): T { } } -export function parseJSONOrThrow(value: string): any { - try { - return JSON.parse(value.trim()) - } catch (error) { - throw new Error(`Invalid JSON: ${getErrorMessage(error, 'Parse error')}`) - } -} - -export function normalizeJSONString(value: string): string { - return value.replace(/'/g, '"') -} - export function stringifyJSON(value: any, indent?: number): string { try { return JSON.stringify(value, null, indent ?? EVALUATOR.JSON_INDENT) diff --git a/apps/sim/executor/utils/reference-validation.ts b/apps/sim/executor/utils/reference-validation.ts index 91130a19683..bef3dd45c42 100644 --- a/apps/sim/executor/utils/reference-validation.ts +++ b/apps/sim/executor/utils/reference-validation.ts @@ -1,4 +1,3 @@ -import { isLikelyReferenceSegment } from '@/lib/workflows/sanitization/references' import { REFERENCE } from '@/executor/constants' /** @@ -145,21 +144,3 @@ export function createCombinedPattern(): RegExp { 'g' ) } - -/** - * Replaces variable references with smart validation. - * Distinguishes < operator from < bracket using isLikelyReferenceSegment. - */ -export function replaceValidReferences( - template: string, - replacer: (match: string, index: number, template: string) => string -): string { - const pattern = createReferencePattern() - - return template.replace(pattern, (match, _content, index) => { - if (!isLikelyReferenceSegment(match)) { - return match - } - return replacer(match, index, template) - }) -} diff --git a/apps/sim/executor/utils/subflow-utils.ts b/apps/sim/executor/utils/subflow-utils.ts index 63ad2593824..0dc00d93466 100644 --- a/apps/sim/executor/utils/subflow-utils.ts +++ b/apps/sim/executor/utils/subflow-utils.ts @@ -39,10 +39,6 @@ export function isParallelSentinelNodeId(nodeId: string): boolean { return SubflowNodeIdCodec.isParallelSentinelNodeId(nodeId) } -export function isSentinelNodeId(nodeId: string): boolean { - return isLoopSentinelNodeId(nodeId) || isParallelSentinelNodeId(nodeId) -} - export function extractLoopIdFromSentinel(sentinelId: string): string | null { return SubflowNodeIdCodec.extractLoopIdFromSentinel(sentinelId) } diff --git a/apps/sim/hooks/queries/general-settings.ts b/apps/sim/hooks/queries/general-settings.ts index 1a24ce87383..2c3efa310ad 100644 --- a/apps/sim/hooks/queries/general-settings.ts +++ b/apps/sim/hooks/queries/general-settings.ts @@ -143,11 +143,6 @@ export function useBillingUsageNotifications(): boolean { return data?.billingUsageNotificationsEnabled ?? true } -export function useErrorNotificationsEnabled(): boolean { - const { data } = useGeneralSettings() - return data?.errorNotificationsEnabled ?? true -} - /** * The user's effective scheduling timezone: their saved preference, or the * browser-detected zone when unset. Use this wherever a task's timezone is diff --git a/apps/sim/hooks/queries/mothership-admin.ts b/apps/sim/hooks/queries/mothership-admin.ts index 1bcadf2dd74..f98fedc44af 100644 --- a/apps/sim/hooks/queries/mothership-admin.ts +++ b/apps/sim/hooks/queries/mothership-admin.ts @@ -182,28 +182,6 @@ export function useMothershipLicenses(environment: MothershipEnv) { }) } -export function useMothershipLicenseDetails( - environment: MothershipEnv, - id?: string, - name?: string -) { - return useQuery({ - queryKey: mothershipKeys.licenseDetails(environment, id, name), - queryFn: ({ signal }) => - mothershipPost( - 'licenses/details', - environment, - { - ...(id ? { id } : {}), - ...(name ? { name } : {}), - }, - signal - ), - enabled: !!(id || name), - staleTime: MOTHERSHIP_LICENSE_DETAIL_STALE_TIME, - }) -} - export function useGenerateLicense(environment: MothershipEnv) { const queryClient = useQueryClient() return useMutation({ diff --git a/apps/sim/hooks/queries/mothership-chats.ts b/apps/sim/hooks/queries/mothership-chats.ts index aae58765e98..b2dad6357c0 100644 --- a/apps/sim/hooks/queries/mothership-chats.ts +++ b/apps/sim/hooks/queries/mothership-chats.ts @@ -10,7 +10,6 @@ import { isApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import { addMothershipChatResourceContract, - createMothershipChatContract, deleteMothershipChatContract, forkMothershipChatContract, getMothershipChatContract, @@ -683,47 +682,6 @@ export function useSetMothershipChatPinned(workspaceId?: string) { }) } -async function createChat(workspaceId: string): Promise<{ id: string }> { - const { id } = await requestJson(createMothershipChatContract, { body: { workspaceId } }) - return { id } -} - -export function useCreateMothershipChat(workspaceId?: string) { - const queryClient = useQueryClient() - return useMutation({ - mutationFn: () => { - if (!workspaceId) throw new Error('workspaceId is required') - return createChat(workspaceId) - }, - onSuccess: (data) => { - if (!workspaceId) return - const existing = - queryClient.getQueryData(mothershipChatKeys.list(workspaceId)) ?? - [] - const newChat: MothershipChatMetadata = { - id: data.id, - name: 'New chat', - updatedAt: new Date(), - isActive: false, - isUnread: false, - isPinned: false, - deletedAt: null, - } - const pinnedCount = existing.findIndex((chat) => !chat.isPinned) - const insertAt = pinnedCount === -1 ? existing.length : pinnedCount - queryClient.setQueryData(mothershipChatKeys.list(workspaceId), [ - ...existing.slice(0, insertAt), - newChat, - ...existing.slice(insertAt), - ]) - }, - onSettled: () => { - if (!workspaceId) return - queryClient.invalidateQueries({ queryKey: mothershipChatKeys.list(workspaceId) }) - }, - }) -} - async function forkChat(params: { chatId: string upToMessageId: string diff --git a/apps/sim/hooks/queries/oauth/oauth-connections.ts b/apps/sim/hooks/queries/oauth/oauth-connections.ts index 25338567464..e3a5becbc0b 100644 --- a/apps/sim/hooks/queries/oauth/oauth-connections.ts +++ b/apps/sim/hooks/queries/oauth/oauth-connections.ts @@ -1,10 +1,9 @@ import { createLogger } from '@sim/logger' -import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { requestJson } from '@/lib/api/client/request' import { type ConnectedAccount, disconnectOAuthContract, - listConnectedAccountsContract, listOAuthConnectionsContract, type OAuthAccountSummary, type OAuthConnection, @@ -265,29 +264,3 @@ export function useDisconnectOAuthService() { /** Connected OAuth account for a specific provider. */ export type { ConnectedAccount } - -async function fetchConnectedAccounts( - provider: string, - signal?: AbortSignal -): Promise { - const data = await requestJson(listConnectedAccountsContract, { - query: { provider }, - signal, - }) - return data.accounts -} - -/** - * Fetches connected accounts for a specific OAuth provider. - * @param provider - The provider ID (e.g., 'slack', 'google') - * @param options - Query options including enabled flag - */ -export function useConnectedAccounts(provider: string, options?: { enabled?: boolean }) { - return useQuery({ - queryKey: oauthConnectionsKeys.account(provider), - queryFn: ({ signal }) => fetchConnectedAccounts(provider, signal), - enabled: options?.enabled ?? true, - staleTime: OAUTH_CONNECTED_ACCOUNTS_STALE_TIME, - placeholderData: keepPreviousData, - }) -} diff --git a/apps/sim/hooks/queries/organization.ts b/apps/sim/hooks/queries/organization.ts index b8d9768293b..f811e9fb54e 100644 --- a/apps/sim/hooks/queries/organization.ts +++ b/apps/sim/hooks/queries/organization.ts @@ -1,12 +1,6 @@ import { createLogger } from '@sim/logger' import { isRecordLike } from '@sim/utils/object' -import { - keepPreviousData, - type UseQueryResult, - useMutation, - useQuery, - useQueryClient, -} from '@tanstack/react-query' +import { type UseQueryResult, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { ApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import type { ContractBodyInput } from '@/lib/api/contracts' @@ -20,8 +14,6 @@ import { getMemberRemovalImpactContract, getOrganizationMemberUsageLimitContract, getOrganizationRosterContract, - listOrganizationMembersContract, - type OrganizationMembersResponse, type OrganizationMemberUsageLimitData, type OrganizationRoster, type RemovalImpactCredential, @@ -30,7 +22,6 @@ import { type RosterWorkspaceAccess, removeOrganizationMemberContract, transferOwnershipContract, - updateOrganizationContract, updateOrganizationMemberRoleContract, updateOrganizationMemberUsageLimitContract, updateOrganizationUsageLimitContract, @@ -40,8 +31,6 @@ import { type OrganizationBillingApiResponse, } from '@/lib/api/contracts/subscription' import { client } from '@/lib/auth/auth-client' -import { isEnterprise, isPaid, isTeam } from '@/lib/billing/plan-helpers' -import { hasPaidSubscriptionStatus } from '@/lib/billing/subscriptions/utils' import { workspaceCredentialKeys } from '@/hooks/queries/utils/credential-keys' import { subscriptionKeys } from '@/hooks/queries/utils/subscription-keys' import { workspaceKeys } from '@/hooks/queries/workspace' @@ -63,37 +52,8 @@ export const ORGANIZATION_MEMBER_USAGE_LIMIT_STALE_TIME = 30 * 1000 */ export const ORGANIZATION_REMOVAL_IMPACT_STALE_TIME = 0 -type OrganizationSubscriptionCandidate = { - id: string - referenceId: string - status: string - plan: string - cancelAtPeriodEnd?: boolean - periodEnd?: number | Date - trialEnd?: number | Date -} - type OrganizationBillingQueryResult = UseQueryResult -function isOrganizationSubscriptionCandidate( - value: unknown -): value is OrganizationSubscriptionCandidate { - if (!isRecordLike(value)) return false - return ( - typeof value.id === 'string' && - typeof value.referenceId === 'string' && - typeof value.status === 'string' && - typeof value.plan === 'string' && - (value.cancelAtPeriodEnd === undefined || typeof value.cancelAtPeriodEnd === 'boolean') && - (value.periodEnd === undefined || - typeof value.periodEnd === 'number' || - value.periodEnd instanceof Date) && - (value.trialEnd === undefined || - typeof value.trialEnd === 'number' || - value.trialEnd instanceof Date) - ) -} - function readNumber(value: unknown): number | undefined { if (typeof value === 'number') return value if (typeof value === 'string') { @@ -185,40 +145,6 @@ export function useMemberRemovalImpact( }) } -/** - * Fetches the current viewer's account-scoped organizations. - * - * `activeOrganization` reflects the viewer session's selected organization. It - * must not be used as the organization context for a routed workspace; those - * surfaces use the workspace host context instead. Billing data is fetched - * separately, and the Better Auth client does not accept an AbortSignal. - */ -async function fetchOrganizations(_signal?: AbortSignal) { - const [orgsResponse, activeOrgResponse] = await Promise.all([ - client.organization.list(), - client.organization.getFullOrganization(), - ]) - - return { - organizations: orgsResponse.data || [], - activeOrganization: activeOrgResponse.data, - } -} - -/** - * Reads the viewer's account organizations and account-scoped active organization. - * - * Workspace-bound consumers must use the routed workspace host context instead - * of `activeOrganization`. - */ -export function useOrganizations() { - return useQuery({ - queryKey: organizationKeys.lists(), - queryFn: ({ signal }) => fetchOrganizations(signal), - staleTime: ORGANIZATION_LIST_STALE_TIME, - }) -} - /** * Fetch a specific organization by ID. * @@ -247,53 +173,6 @@ export function useOrganization(orgId: string) { }) } -/** - * Fetch organization subscription data - */ -async function fetchOrganizationSubscription(orgId: string, _signal?: AbortSignal) { - if (!orgId) { - return null - } - - const response = await client.subscription.list({ - query: { referenceId: orgId }, - }) - - if (response.error) { - logger.error('Error fetching organization subscription', { error: response.error }) - return null - } - - // Any paid subscription attached to the org counts as its active sub. - // Priority: Enterprise > Team > Pro (matches `getHighestPrioritySubscription`). - // This intentionally includes `pro_*` plans that have been transferred - // to the org — they are pooled org-scoped subscriptions. - const rawSubscriptions: unknown = response.data - const entitled = (Array.isArray(rawSubscriptions) ? rawSubscriptions : []) - .filter(isOrganizationSubscriptionCandidate) - .filter((sub) => hasPaidSubscriptionStatus(sub.status) && isPaid(sub.plan)) - const enterpriseSubscription = entitled.find((sub) => isEnterprise(sub.plan)) - const teamSubscription = entitled.find((sub) => isTeam(sub.plan)) - const proSubscription = entitled.find((sub) => !isEnterprise(sub.plan) && !isTeam(sub.plan)) - const activeSubscription = enterpriseSubscription || teamSubscription || proSubscription - - return activeSubscription || null -} - -/** - * Hook to fetch organization subscription - */ -export function useOrganizationSubscription(orgId: string) { - return useQuery({ - queryKey: organizationKeys.subscription(orgId), - queryFn: ({ signal }) => fetchOrganizationSubscription(orgId, signal), - enabled: !!orgId, - retry: false, - staleTime: ORGANIZATION_SUBSCRIPTION_STALE_TIME, - placeholderData: keepPreviousData, - }) -} - /** * Fetch organization billing data */ @@ -330,46 +209,6 @@ export function useOrganizationBilling( }) } -/** - * Fetch organization member usage data - */ -async function fetchOrganizationMembers( - orgId: string, - signal?: AbortSignal -): Promise { - try { - return await requestJson(listOrganizationMembersContract, { - params: { id: orgId }, - query: { include: 'usage' }, - signal, - }) - } catch (error) { - if (error instanceof ApiClientError && error.status === 404) { - return { - success: true, - data: [], - total: 0, - userRole: 'member', - hasAdminAccess: false, - } - } - throw error - } -} - -/** - * Hook to fetch organization members with usage data - */ -export function useOrganizationMembers(orgId: string) { - return useQuery({ - queryKey: organizationKeys.memberUsage(orgId), - queryFn: ({ signal }) => fetchOrganizationMembers(orgId, signal), - enabled: !!orgId, - staleTime: ORGANIZATION_MEMBERS_STALE_TIME, - placeholderData: keepPreviousData, - }) -} - /** * Update organization usage limit mutation with optimistic updates */ @@ -660,30 +499,6 @@ export function useResendInvitation() { }) } -/** - * Update organization settings mutation - */ -type UpdateOrganizationParams = { - orgId: string -} & ContractBodyInput - -export function useUpdateOrganization() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ({ orgId, ...updates }: UpdateOrganizationParams) => { - return requestJson(updateOrganizationContract, { - params: { id: orgId }, - body: updates, - }) - }, - onSettled: (_data, _error, variables) => { - queryClient.invalidateQueries({ queryKey: organizationKeys.detail(variables.orgId) }) - queryClient.invalidateQueries({ queryKey: organizationKeys.lists() }) - }, - }) -} - /** * Create organization mutation */ diff --git a/apps/sim/hooks/queries/schedules.ts b/apps/sim/hooks/queries/schedules.ts index a22fe07d4fe..c2d563daaad 100644 --- a/apps/sim/hooks/queries/schedules.ts +++ b/apps/sim/hooks/queries/schedules.ts @@ -4,7 +4,6 @@ import { isApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import { deployWorkflowContract } from '@/lib/api/contracts/deployments' import { - getScheduleByIdContract, getScheduleContract, listWorkspaceSchedulesContract, reactivateScheduleContract, @@ -95,28 +94,6 @@ export function useWorkspaceSchedules(workspaceId?: string, options?: { enabled? }) } -/** - * Fetch a single workflow schedule by id — a lightweight by-id read instead of - * the whole-workspace `useWorkspaceSchedules` fetch. - */ -export function useScheduleById(scheduleId?: string) { - return useQuery({ - queryKey: scheduleKeys.byId(scheduleId ?? ''), - queryFn: async ({ signal }) => { - if (!scheduleId) throw new Error('Schedule ID required') - - const data = await requestJson(getScheduleByIdContract, { - params: { id: scheduleId }, - signal, - }) - return data.schedule - }, - enabled: Boolean(scheduleId), - staleTime: SCHEDULE_DETAIL_STALE_TIME, - placeholderData: keepPreviousData, - }) -} - /** * Hook to fetch schedule data for a workflow block */ diff --git a/apps/sim/hooks/queries/subscription.ts b/apps/sim/hooks/queries/subscription.ts index 5809f6b2eba..ad4b38dcf45 100644 --- a/apps/sim/hooks/queries/subscription.ts +++ b/apps/sim/hooks/queries/subscription.ts @@ -8,14 +8,11 @@ import { getUserBillingContract, getUserUsageLimitContract, type InvoicesApiResponse, - purchaseCreditsContract, type SubscriptionApiResponse, updateUsageLimitContract, } from '@/lib/api/contracts/subscription' -import { organizationKeys } from '@/hooks/queries/organization' import { invalidateWorkspaceUsage } from '@/hooks/queries/utils/invalidate-usage' import { subscriptionKeys } from '@/hooks/queries/utils/subscription-keys' -import { workspaceKeys } from '@/hooks/queries/workspace' export type { SubscriptionApiResponse } @@ -62,18 +59,6 @@ export function useSubscriptionData(options: UseSubscriptionDataOptions = {}) { }) } -/** - * Prefetch subscription data into a QueryClient cache. - * Use on hover to warm data before navigation. - */ -export function prefetchSubscriptionData(queryClient: QueryClient) { - queryClient.prefetchQuery({ - queryKey: subscriptionKeys.user(false), - queryFn: ({ signal }) => fetchSubscriptionData(false, signal), - staleTime: SUBSCRIPTION_DATA_STALE_TIME, - }) -} - /** * Prefetch the billing queries the Upgrade page gates on: the * organization-scoped subscription variant (`includeOrg: true`, a different @@ -260,81 +245,6 @@ export function useUpdateUsageLimit() { }) } -/** - * Upgrade subscription mutation - */ -interface UpgradeSubscriptionParams { - plan: string - orgId?: string -} - -export function useUpgradeSubscription() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ({ plan }: UpgradeSubscriptionParams) => { - return { plan } - }, - onSettled: (_data, _error, variables) => { - return Promise.all([ - queryClient.invalidateQueries({ queryKey: subscriptionKeys.users() }), - queryClient.invalidateQueries({ queryKey: subscriptionKeys.usage() }), - queryClient.invalidateQueries({ queryKey: subscriptionKeys.invoicesAll() }), - queryClient.invalidateQueries({ queryKey: workspaceKeys.lists() }), - invalidateWorkspaceUsage(queryClient), - ...(variables.orgId - ? [ - queryClient.invalidateQueries({ - queryKey: organizationKeys.billing(variables.orgId), - }), - queryClient.invalidateQueries({ - queryKey: organizationKeys.subscription(variables.orgId), - }), - ] - : []), - ]) - }, - }) -} - -/** - * Purchase credits mutation - */ -interface PurchaseCreditsParams { - amount: ContractBodyInput['amount'] - requestId: ContractBodyInput['requestId'] - orgId?: string -} - -export function usePurchaseCredits() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ({ amount, requestId }: PurchaseCreditsParams) => { - return requestJson(purchaseCreditsContract, { - body: { amount, requestId }, - }) - }, - onSettled: (_data, _error, variables) => { - return Promise.all([ - queryClient.invalidateQueries({ queryKey: subscriptionKeys.users() }), - queryClient.invalidateQueries({ queryKey: subscriptionKeys.usage() }), - invalidateWorkspaceUsage(queryClient), - ...(variables.orgId - ? [ - queryClient.invalidateQueries({ - queryKey: organizationKeys.billing(variables.orgId), - }), - queryClient.invalidateQueries({ - queryKey: organizationKeys.subscription(variables.orgId), - }), - ] - : []), - ]) - }, - }) -} - /** * Open billing portal mutation */ diff --git a/apps/sim/hooks/queries/workspace-files.ts b/apps/sim/hooks/queries/workspace-files.ts index 69c775f2dde..da45c0f0ac4 100644 --- a/apps/sim/hooks/queries/workspace-files.ts +++ b/apps/sim/hooks/queries/workspace-files.ts @@ -10,10 +10,8 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' -import { isApiClientError } from '@/lib/api/client/errors' import { requestJson } from '@/lib/api/client/request' import { fileStorageStatusContract } from '@/lib/api/contracts/storage-transfer' -import { getUsageLimitsContract } from '@/lib/api/contracts/usage-limits' import { type CreateWorkspaceFileBody, createWorkspaceFileContract, @@ -69,16 +67,6 @@ export const WORKSPACE_STORAGE_INFO_STALE_TIME = 60 * 1000 /** Cloud storage (S3/Blob) is env-driven and does not change at runtime. */ export const CLOUD_STORAGE_CONFIGURED_STALE_TIME = Number.POSITIVE_INFINITY -/** - * Storage info type - */ -interface StorageInfo { - usedBytes: number - limitBytes: number - percentUsed: number - plan?: string -} - /** * Hook to fetch a single workspace file record by ID. * Shares the `list(workspaceId, 'active')` query key with {@link useWorkspaceFiles} so no extra @@ -467,44 +455,6 @@ export function useWorkspaceFileBinary( }) } -/** - * Fetch storage info from API - */ -async function fetchStorageInfo(signal?: AbortSignal): Promise { - try { - const data = await requestJson(getUsageLimitsContract, { signal }) - - if (data.success && data.storage) { - return { - usedBytes: data.storage.usedBytes, - limitBytes: data.storage.limitBytes, - percentUsed: data.storage.percentUsed, - plan: data.usage?.plan || 'free', - } - } - - return null - } catch (error) { - if (isApiClientError(error) && error.status === 404) { - return null - } - throw error - } -} - -/** - * Hook to fetch storage info - */ -export function useStorageInfo(enabled = true) { - return useQuery({ - queryKey: workspaceFilesKeys.storageInfo(), - queryFn: ({ signal }) => fetchStorageInfo(signal), - enabled, - retry: false, // Don't retry on 404 - staleTime: WORKSPACE_STORAGE_INFO_STALE_TIME, // 1 minute - storage info doesn't change often - }) -} - async function fetchCloudStorageConfigured(signal?: AbortSignal): Promise { const data = await requestJson(fileStorageStatusContract, { signal }) return data.cloudConfigured === true diff --git a/apps/sim/hooks/queries/workspace.ts b/apps/sim/hooks/queries/workspace.ts index 490eae2cc5f..f98748e1ea6 100644 --- a/apps/sim/hooks/queries/workspace.ts +++ b/apps/sim/hooks/queries/workspace.ts @@ -399,30 +399,6 @@ export function useWorkspaceSettings(workspaceId: string) { }) } -type UpdateWorkspaceSettingsParams = { workspaceId: string } & Pick< - ContractBodyInput, - 'billedAccountUserId' -> - -/** - * Updates workspace settings (e.g., billing configuration). - * Invalidates the workspace settings cache on success. - */ -export function useUpdateWorkspaceSettings() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ({ workspaceId, ...updates }: UpdateWorkspaceSettingsParams) => { - return requestJson(updateWorkspaceContract, { params: { id: workspaceId }, body: updates }) - }, - onSettled: (_data, _error, variables) => { - queryClient.invalidateQueries({ - queryKey: workspaceKeys.settings(variables.workspaceId), - }) - }, - }) -} - /** Workspace with admin access metadata. */ export interface AdminWorkspace { id: string diff --git a/apps/sim/hooks/use-trigger-config-aggregation.ts b/apps/sim/hooks/use-trigger-config-aggregation.ts index 655e011c7f5..a5250dd4963 100644 --- a/apps/sim/hooks/use-trigger-config-aggregation.ts +++ b/apps/sim/hooks/use-trigger-config-aggregation.ts @@ -1,10 +1,7 @@ -import { createLogger } from '@sim/logger' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { getTrigger, isTriggerValid } from '@/triggers' import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants' -const logger = createLogger('useTriggerConfigAggregation') - /** * Maps old trigger config field names to new subblock IDs for backward compatibility. * This handles field name changes during the migration from modal-based configuration @@ -25,71 +22,6 @@ function mapOldFieldNameToNewSubBlockId(oldFieldName: string): string { return fieldMapping[oldFieldName] || oldFieldName } -/** - * Aggregates individual trigger field subblocks into a triggerConfig object. - * This is called on-demand when saving, not continuously. - * - * @param blockId - The block ID that has the trigger fields - * @param triggerId - The trigger ID to get the config fields from - * @returns The aggregated config object, or null if no valid config - */ - -export function useTriggerConfigAggregation( - blockId: string, - triggerId: string | undefined -): Record | null { - if (!triggerId || !blockId) { - return null - } - - if (!isTriggerValid(triggerId)) { - logger.warn(`Trigger definition not found for ID: ${triggerId}`) - return null - } - - const triggerDef = getTrigger(triggerId) - - const subBlockStore = useSubBlockStore.getState() - - const aggregatedConfig: Record = {} - let hasAnyValue = false - - triggerDef.subBlocks - .filter( - (sb) => - (sb.mode === 'trigger' || sb.mode === 'trigger-advanced') && - !SYSTEM_SUBBLOCK_IDS.includes(sb.id) - ) - .forEach((subBlock) => { - const fieldValue = subBlockStore.getValue(blockId, subBlock.id) - - let valueToUse = fieldValue - if ( - (fieldValue === null || fieldValue === undefined || fieldValue === '') && - subBlock.defaultValue !== undefined - ) { - valueToUse = subBlock.defaultValue - } - - if (valueToUse !== null && valueToUse !== undefined && valueToUse !== '') { - aggregatedConfig[subBlock.id] = valueToUse - hasAnyValue = true - } - }) - - if (!hasAnyValue) { - return null - } - - logger.debug('Aggregated trigger config fields', { - blockId, - triggerId, - aggregatedConfig, - }) - - return aggregatedConfig -} - /** * Populates individual trigger field subblocks from a triggerConfig object. * Used for backward compatibility when loading existing workflows. diff --git a/apps/sim/lib/api/contracts/admin.ts b/apps/sim/lib/api/contracts/admin.ts index a248b9dbe6c..2d4d57f0c55 100644 --- a/apps/sim/lib/api/contracts/admin.ts +++ b/apps/sim/lib/api/contracts/admin.ts @@ -1,5 +1,4 @@ import { z } from 'zod' -import { defineRouteContract } from '@/lib/api/contracts/types' import { workflowStateSchema } from '@/lib/api/contracts/workflows' import { workspacePermissionSchema } from '@/lib/api/contracts/workspaces' @@ -103,17 +102,6 @@ export const adminWorkspaceImportQuerySchema = z.object({ rootFolderName: queryStringSchema, }) -export const adminWorkspaceImportBodySchema = z.object({ - workflows: z.array( - z.object({ - content: z.union([z.string(), z.record(z.string(), z.unknown())]), - name: z.string().optional(), - folderPath: z.array(z.string()).optional(), - }), - { error: 'Invalid JSON body. Expected { workflows: [...] }' } - ), -}) - export const adminPaginationMetaSchema = z.object({ total: z.number(), limit: z.number(), @@ -212,38 +200,6 @@ export const adminFolderExportPayloadSchema = z.object({ parentId: z.string().nullable(), }) -export const adminWorkspaceExportPayloadSchema = z.object({ - version: z.literal('1.0'), - exportedAt: z.string(), - workspace: z.object({ - id: z.string(), - name: z.string(), - }), - workflows: z.array( - z.object({ - workflow: adminWorkflowExportPayloadSchema.shape.workflow, - state: adminWorkflowExportStateSchema, - }) - ), - folders: z.array(adminFolderExportPayloadSchema), -}) - -export const adminFolderFullExportPayloadSchema = z.object({ - version: z.literal('1.0'), - exportedAt: z.string(), - folder: z.object({ - id: z.string(), - name: z.string(), - }), - workflows: z.array( - z.object({ - workflow: adminWorkflowExportPayloadSchema.shape.workflow.omit({ workspaceId: true }), - state: adminWorkflowExportStateSchema, - }) - ), - folders: z.array(adminFolderExportPayloadSchema), -}) - export const adminImportResultSchema = z.object({ workflowId: z.string(), name: z.string(), @@ -283,293 +239,3 @@ export const adminDeployResultSchema = z.object({ export const adminUndeployResultSchema = z.object({ isDeployed: z.literal(false), }) - -const adminSingleResponseSchema = (schema: TSchema) => - z.object({ data: schema }) - -const adminListResponseSchema = (schema: TSchema) => - z.object({ - data: z.array(schema), - pagination: adminPaginationMetaSchema, - }) - -export const adminListWorkflowsContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workflows', - query: adminPaginationQuerySchema, - response: { - mode: 'json', - schema: adminListResponseSchema(adminWorkflowSchema), - }, -}) - -export const adminGetWorkflowContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workflows/[id]', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminWorkflowDetailSchema), - }, -}) - -export const adminDeleteWorkflowContract = defineRouteContract({ - method: 'DELETE', - path: '/api/v1/admin/workflows/[id]', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: z.object({ - success: z.literal(true), - workflowId: z.string(), - }), - }, -}) - -export const adminDeployWorkflowContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/admin/workflows/[id]/deploy', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminDeployResultSchema), - }, -}) - -export const adminUndeployWorkflowContract = defineRouteContract({ - method: 'DELETE', - path: '/api/v1/admin/workflows/[id]/deploy', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminUndeployResultSchema), - }, -}) - -export const adminListWorkflowVersionsContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workflows/[id]/versions', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema( - z.object({ - versions: z.array(adminDeploymentVersionSchema), - }) - ), - }, -}) - -export const adminActivateWorkflowVersionContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/admin/workflows/[id]/versions/[versionId]/activate', - params: adminWorkflowVersionParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema( - z.object({ - success: z.literal(true), - version: z.number(), - deployedAt: z.string(), - warnings: z.array(z.string()).optional(), - }) - ), - }, -}) - -export const adminExportWorkflowContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workflows/[id]/export', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminWorkflowExportPayloadSchema), - }, -}) - -export const adminExportWorkflowsContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/admin/workflows/export', - query: adminExportFormatQuerySchema, - body: adminExportWorkflowsBodySchema, - response: { - mode: 'binary', - }, -}) - -export const adminImportWorkflowContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/admin/workflows/import', - body: adminWorkflowImportBodySchema, - response: { - mode: 'json', - schema: adminWorkflowImportResponseSchema, - }, -}) - -export const adminListWorkspacesContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces', - query: adminPaginationQuerySchema, - response: { - mode: 'json', - schema: adminListResponseSchema(adminWorkspaceSchema), - }, -}) - -export const adminGetWorkspaceContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces/[id]', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminWorkspaceDetailSchema), - }, -}) - -export const adminListWorkspaceWorkflowsContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces/[id]/workflows', - params: adminIdParamsSchema, - query: adminPaginationQuerySchema, - response: { - mode: 'json', - schema: adminListResponseSchema(adminWorkflowSchema), - }, -}) - -export const adminDeleteWorkspaceWorkflowsContract = defineRouteContract({ - method: 'DELETE', - path: '/api/v1/admin/workspaces/[id]/workflows', - params: adminIdParamsSchema, - response: { - mode: 'json', - schema: z.object({ - success: z.literal(true), - deleted: z.number(), - }), - }, -}) - -export const adminListWorkspaceFoldersContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces/[id]/folders', - params: adminIdParamsSchema, - query: adminPaginationQuerySchema, - response: { - mode: 'json', - schema: adminListResponseSchema(adminFolderSchema), - }, -}) - -export const adminExportWorkspaceContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces/[id]/export', - params: adminIdParamsSchema, - query: adminExportFormatQuerySchema, - response: { - mode: 'binary', - }, -}) - -export const adminImportWorkspaceContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/admin/workspaces/[id]/import', - params: adminIdParamsSchema, - query: adminWorkspaceImportQuerySchema, - response: { - mode: 'json', - schema: adminWorkspaceImportResponseSchema, - }, -}) - -export const adminListWorkspaceMembersContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces/[id]/members', - params: adminIdParamsSchema, - query: adminPaginationQuerySchema, - response: { - mode: 'json', - schema: adminListResponseSchema(adminWorkspaceMemberSchema), - }, -}) - -export const adminCreateWorkspaceMemberContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/admin/workspaces/[id]/members', - params: adminIdParamsSchema, - body: adminWorkspaceMemberBodySchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema( - adminWorkspaceMemberSchema.extend({ - action: z.enum(['created', 'updated', 'already_member']), - }) - ), - }, -}) - -export const adminDeleteWorkspaceMemberContract = defineRouteContract({ - method: 'DELETE', - path: '/api/v1/admin/workspaces/[id]/members', - params: adminIdParamsSchema, - query: adminDeleteWorkspaceMemberQuerySchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema( - z.object({ - removed: z.literal(true), - userId: z.string(), - workspaceId: z.string(), - }) - ), - }, -}) - -export const adminGetWorkspaceMemberContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/workspaces/[id]/members/[memberId]', - params: adminWorkspaceMemberParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminWorkspaceMemberSchema), - }, -}) - -export const adminUpdateWorkspaceMemberContract = defineRouteContract({ - method: 'PATCH', - path: '/api/v1/admin/workspaces/[id]/members/[memberId]', - params: adminWorkspaceMemberParamsSchema, - body: adminUpdateWorkspaceMemberBodySchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema(adminWorkspaceMemberSchema), - }, -}) - -export const adminRemoveWorkspaceMemberContract = defineRouteContract({ - method: 'DELETE', - path: '/api/v1/admin/workspaces/[id]/members/[memberId]', - params: adminWorkspaceMemberParamsSchema, - response: { - mode: 'json', - schema: adminSingleResponseSchema( - z.object({ - removed: z.literal(true), - memberId: z.string(), - userId: z.string(), - workspaceId: z.string(), - }) - ), - }, -}) - -export const adminExportFolderContract = defineRouteContract({ - method: 'GET', - path: '/api/v1/admin/folders/[id]/export', - params: adminIdParamsSchema, - query: adminExportFormatQuerySchema, - response: { - mode: 'binary', - }, -}) diff --git a/apps/sim/lib/api/contracts/common.ts b/apps/sim/lib/api/contracts/common.ts index ffc7898ce4d..44cd3bc354e 100644 --- a/apps/sim/lib/api/contracts/common.ts +++ b/apps/sim/lib/api/contracts/common.ts @@ -105,20 +105,6 @@ export const getStarsContract = defineRouteContract({ }, }) -export const getStatusContract = defineRouteContract({ - method: 'GET', - path: '/api/status', - response: { - mode: 'json', - schema: z.object({ - status: z.enum(['operational', 'degraded', 'outage', 'maintenance', 'loading', 'error']), - message: z.string(), - url: z.string().url(), - lastUpdated: z.string(), - }), - }, -}) - const jobStatusSchema = z.enum(['pending', 'processing', 'completed', 'failed', 'cancelled']) const jobStatusResponseSchema = z diff --git a/apps/sim/lib/api/contracts/copilot.ts b/apps/sim/lib/api/contracts/copilot.ts index c851c6cff2c..bd26d8a4a45 100644 --- a/apps/sim/lib/api/contracts/copilot.ts +++ b/apps/sim/lib/api/contracts/copilot.ts @@ -725,22 +725,6 @@ export const revertCopilotCheckpointContract = defineRouteContract({ }, }) -export const copilotChatAbortContract = defineRouteContract({ - method: 'POST', - path: '/api/copilot/chat/abort', - body: copilotChatAbortBodySchema, - response: { - mode: 'json', - schema: z.object({ - aborted: z.boolean(), - settled: z.boolean().optional(), - // True when the stream did not settle within the grace window and the - // chat stream lock was force-broken so the chat is immediately usable. - forceReleased: z.boolean().optional(), - }), - }, -}) - export const copilotChatStreamContract = defineRouteContract({ method: 'GET', path: '/api/copilot/chat/stream', diff --git a/apps/sim/lib/api/contracts/deployments.ts b/apps/sim/lib/api/contracts/deployments.ts index 343af5e82a1..e710d9e9ae5 100644 --- a/apps/sim/lib/api/contracts/deployments.ts +++ b/apps/sim/lib/api/contracts/deployments.ts @@ -59,11 +59,6 @@ export const deploymentVersionOrActiveParamsSchema = z.object({ version: z.union([deploymentVersionPathSchema, z.literal('active')]), }) -export const deploymentVersionRouteParamsSchema = z.object({ - id: z.string().min(1, 'Invalid workflow ID'), - version: z.string().min(1, 'Invalid version'), -}) - export const updatePublicApiBodySchema = z.object({ isPublicApi: z.boolean(), }) diff --git a/apps/sim/lib/api/contracts/mcp-oauth.ts b/apps/sim/lib/api/contracts/mcp-oauth.ts index be07e026330..0661c725154 100644 --- a/apps/sim/lib/api/contracts/mcp-oauth.ts +++ b/apps/sim/lib/api/contracts/mcp-oauth.ts @@ -1,5 +1,4 @@ import { z } from 'zod' -import { defineRouteContract } from '@/lib/api/contracts/types' const mcpOauthMetadataQuerySchema = z.record(z.string(), z.string()) export type McpOauthMetadataQuery = z.input @@ -33,23 +32,3 @@ const mcpProtectedResourceMetadataSchema = z.object({ x_sim_auth: xSimAuthSchema, }) export type McpProtectedResourceMetadata = z.output - -export const mcpOauthAuthorizationServerMetadataContract = defineRouteContract({ - method: 'GET', - path: '/api/mcp/copilot/.well-known/oauth-authorization-server', - query: mcpOauthMetadataQuerySchema, - response: { - mode: 'json', - schema: mcpAuthorizationServerMetadataSchema, - }, -}) - -export const mcpOauthProtectedResourceMetadataContract = defineRouteContract({ - method: 'GET', - path: '/api/mcp/copilot/.well-known/oauth-protected-resource', - query: mcpOauthMetadataQuerySchema, - response: { - mode: 'json', - schema: mcpProtectedResourceMetadataSchema, - }, -}) diff --git a/apps/sim/lib/api/contracts/mcp.ts b/apps/sim/lib/api/contracts/mcp.ts index 53ca4e6226a..4c071657695 100644 --- a/apps/sim/lib/api/contracts/mcp.ts +++ b/apps/sim/lib/api/contracts/mcp.ts @@ -267,11 +267,6 @@ export const mcpJsonRpcMessageSchema = z }) .passthrough() -export const mcpRequestBodySchema = z.union([ - mcpJsonRpcMessageSchema, - z.array(mcpJsonRpcMessageSchema), -]) - export const mcpToolCallParamsSchema = z .object({ name: z.string().min(1), @@ -415,27 +410,6 @@ export const discoverMcpToolsContract = defineRouteContract({ }) export type DiscoverMcpToolsResponse = ContractJsonResponse -export const refreshMcpToolsContract = defineRouteContract({ - method: 'POST', - path: '/api/mcp/tools/discover', - query: mcpWorkspaceQuerySchema, - body: refreshMcpToolsBodySchema, - response: { - mode: 'json', - schema: mcpSuccessResponseSchema( - z.object({ - refreshed: z.array(z.object({ serverId: z.string(), toolCount: z.number() })), - failed: z.array(z.object({ serverId: z.string(), error: z.string() })), - summary: z.object({ - total: z.number(), - successful: z.number(), - failed: z.number(), - }), - }) - ), - }, -}) - export const listStoredMcpToolsContract = defineRouteContract({ method: 'GET', path: '/api/mcp/tools/stored', diff --git a/apps/sim/lib/api/contracts/schedules.ts b/apps/sim/lib/api/contracts/schedules.ts index 6fd4921f673..aeafa475ed3 100644 --- a/apps/sim/lib/api/contracts/schedules.ts +++ b/apps/sim/lib/api/contracts/schedules.ts @@ -203,12 +203,3 @@ export const updateScheduleContract = defineRouteContract({ schema: messageResponseSchema, }, }) - -export const executeSchedulesContract = defineRouteContract({ - method: 'GET', - path: '/api/schedules/execute', - response: { - mode: 'json', - schema: executeSchedulesResponseSchema, - }, -}) diff --git a/apps/sim/lib/api/contracts/storage-transfer.ts b/apps/sim/lib/api/contracts/storage-transfer.ts index 2eb9e3b6a50..3bfe28e8794 100644 --- a/apps/sim/lib/api/contracts/storage-transfer.ts +++ b/apps/sim/lib/api/contracts/storage-transfer.ts @@ -319,13 +319,6 @@ export const wordpressUploadContract = defineRouteContract({ response: { mode: 'json', schema: jsonResponseSchema }, }) -export const sftpListContract = defineRouteContract({ - method: 'POST', - path: '/api/tools/sftp/list', - body: sftpListBodySchema, - response: { mode: 'json', schema: jsonResponseSchema }, -}) - export const sftpDeleteContract = defineRouteContract({ method: 'POST', path: '/api/tools/sftp/delete', diff --git a/apps/sim/lib/api/contracts/tools/media/image.ts b/apps/sim/lib/api/contracts/tools/media/image.ts index 81ac09927e7..3429b861b15 100644 --- a/apps/sim/lib/api/contracts/tools/media/image.ts +++ b/apps/sim/lib/api/contracts/tools/media/image.ts @@ -43,13 +43,6 @@ export const imageToolBodySchema = z export type ImageToolBody = z.infer -export const imageProxyContract = defineRouteContract({ - method: 'GET', - path: '/api/tools/image', - query: imageProxyQuerySchema, - response: { mode: 'binary' }, -}) - export const imageToolContract = defineRouteContract({ method: 'POST', path: '/api/tools/image', diff --git a/apps/sim/lib/api/contracts/v1/copilot.ts b/apps/sim/lib/api/contracts/v1/copilot.ts index f09626e41a2..4752c61762f 100644 --- a/apps/sim/lib/api/contracts/v1/copilot.ts +++ b/apps/sim/lib/api/contracts/v1/copilot.ts @@ -1,5 +1,4 @@ import { z } from 'zod' -import { defineRouteContract } from '@/lib/api/contracts/types' import { COPILOT_REQUEST_MODES } from '@/lib/copilot/constants' export const v1CopilotChatBodySchema = z.object({ @@ -14,30 +13,3 @@ export const v1CopilotChatBodySchema = z.object({ }) export type V1CopilotChatBody = z.output - -const v1CopilotChatToolCallSchema = z.object({ - id: z.string(), - name: z.string(), - status: z.string(), - params: z.record(z.string(), z.unknown()).optional(), - // untyped-response: copilot tool result is the user-defined output of an arbitrary tool invocation - result: z.unknown().optional(), - error: z.string().optional(), - durationMs: z.number().optional(), -}) - -export const v1CopilotChatContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/copilot/chat', - body: v1CopilotChatBodySchema, - response: { - mode: 'json', - schema: z.object({ - success: z.boolean(), - content: z.string().optional(), - toolCalls: z.array(v1CopilotChatToolCallSchema).optional(), - chatId: z.string().optional(), - error: z.string().optional(), - }), - }, -}) diff --git a/apps/sim/lib/api/contracts/v1/files.ts b/apps/sim/lib/api/contracts/v1/files.ts index c196e0953cf..33ba4ba22c9 100644 --- a/apps/sim/lib/api/contracts/v1/files.ts +++ b/apps/sim/lib/api/contracts/v1/files.ts @@ -35,15 +35,6 @@ export const v1ListFilesContract = defineRouteContract({ }, }) -export const v1UploadFileContract = defineRouteContract({ - method: 'POST', - path: '/api/v1/files', - response: { - mode: 'json', - schema: v1FilesResponseSchema, - }, -}) - export const v1DownloadFileContract = defineRouteContract({ method: 'GET', path: '/api/v1/files/[fileId]', diff --git a/apps/sim/lib/api/contracts/webhooks.ts b/apps/sim/lib/api/contracts/webhooks.ts index 720a49ad3b4..5363c77f9c1 100644 --- a/apps/sim/lib/api/contracts/webhooks.ts +++ b/apps/sim/lib/api/contracts/webhooks.ts @@ -295,15 +295,3 @@ export const tiktokWebhookResponseSchema = z.union([ z.object({ ok: z.literal(true) }), z.object({ error: z.string().min(1) }), ]) - -export const tiktokWebhookContract = defineRouteContract({ - method: 'POST', - path: '/api/webhooks/tiktok', - headers: tiktokWebhookHeadersSchema, - // Body is validated after HMAC verification against the raw payload. - body: tiktokWebhookEnvelopeSchema, - response: { - mode: 'json', - schema: tiktokWebhookResponseSchema, - }, -}) diff --git a/apps/sim/lib/api/contracts/workflows.ts b/apps/sim/lib/api/contracts/workflows.ts index 25c2d286427..e6580a60954 100644 --- a/apps/sim/lib/api/contracts/workflows.ts +++ b/apps/sim/lib/api/contracts/workflows.ts @@ -509,13 +509,6 @@ export const importWorkflowAsSuperuserBodySchema = z.object({ export type ImportWorkflowAsSuperuserBody = z.input -export const importWorkflowAsSuperuserPermissiveBodySchema = z - .object({ - workflowId: z.string().optional(), - targetWorkspaceId: z.string().optional(), - }) - .passthrough() - export const importWorkflowAsSuperuserResponseSchema = z.object({ success: z.literal(true), newWorkflowId: z.string(), diff --git a/apps/sim/lib/api/contracts/workspaces.ts b/apps/sim/lib/api/contracts/workspaces.ts index 5210e774c75..6d4c64e60a1 100644 --- a/apps/sim/lib/api/contracts/workspaces.ts +++ b/apps/sim/lib/api/contracts/workspaces.ts @@ -161,14 +161,6 @@ export const workspaceMemberSchema = z.object({ export type WorkspaceMember = z.output -export const workspacePreviewBodySchema = z - .object({ - code: z - .string({ error: 'code is required' }) - .refine((code) => code.trim().length > 0, { message: 'code is required' }), - }) - .passthrough() - export const workspaceMetricsExecutionsQuerySchema = z.object({ startTime: z.string().optional(), endTime: z.string().optional(), diff --git a/apps/sim/lib/audio/extractor.ts b/apps/sim/lib/audio/extractor.ts index 47f461fc46b..6573c6f2ce7 100644 --- a/apps/sim/lib/audio/extractor.ts +++ b/apps/sim/lib/audio/extractor.ts @@ -273,42 +273,3 @@ export function isVideoFile(mimeType: string): boolean { export function isAudioFile(mimeType: string): boolean { return mimeType.startsWith('audio/') } - -/** - * Get optimal audio format for STT provider - */ -export function getOptimalFormat(provider: 'whisper' | 'deepgram' | 'elevenlabs'): { - format: 'mp3' | 'wav' | 'flac' - sampleRate: number - channels: 1 | 2 -} { - switch (provider) { - case 'whisper': - // Whisper prefers 16kHz mono - return { - format: 'mp3', - sampleRate: 16000, - channels: 1, - } - case 'deepgram': - // Deepgram works well with various formats - return { - format: 'mp3', - sampleRate: 16000, - channels: 1, - } - case 'elevenlabs': - // ElevenLabs format preferences - return { - format: 'mp3', - sampleRate: 16000, - channels: 1, - } - default: - return { - format: 'mp3', - sampleRate: 16000, - channels: 1, - } - } -} diff --git a/apps/sim/lib/billing/client/consts.ts b/apps/sim/lib/billing/client/consts.ts index 32cbec80601..0809e8e836d 100644 --- a/apps/sim/lib/billing/client/consts.ts +++ b/apps/sim/lib/billing/client/consts.ts @@ -1,8 +1,3 @@ -/** - * Number of pills to display in usage indicators. - */ -export const USAGE_PILL_COUNT = 8 - /** * Usage percentage thresholds for visual states. */ diff --git a/apps/sim/lib/billing/plan-helpers.ts b/apps/sim/lib/billing/plan-helpers.ts index 99ef6382d92..120924b3561 100644 --- a/apps/sim/lib/billing/plan-helpers.ts +++ b/apps/sim/lib/billing/plan-helpers.ts @@ -132,13 +132,6 @@ export function buildPlanName(type: 'pro' | 'team', credits: number): string { return `${type}_${credits}` } -/** - * Get the list of valid plan names for a given category. - */ -export function getValidPlanNames(type: 'pro' | 'team'): string[] { - return CREDIT_TIERS.map((t) => buildPlanName(type, t.credits)) -} - /** * Get the user-facing display name for a plan. * @example getDisplayPlanName('pro_25000') => 'Max' diff --git a/apps/sim/lib/billing/plans.ts b/apps/sim/lib/billing/plans.ts index 184093a6e7b..257e1a45a5e 100644 --- a/apps/sim/lib/billing/plans.ts +++ b/apps/sim/lib/billing/plans.ts @@ -109,14 +109,6 @@ export function getPlanByPriceId(priceId: string): BillingPlan | undefined { ) } -/** - * Get plan limits for a given plan name - */ -export function getPlanLimits(planName: string): number { - const plan = getPlanByName(planName) - return plan?.limits.cost ?? getFreeTierLimit() -} - export interface StripePlanResolution { priceId: string | undefined planFromStripe: string | null diff --git a/apps/sim/lib/blog/registry.ts b/apps/sim/lib/blog/registry.ts index 1841140a717..71235ae66b4 100644 --- a/apps/sim/lib/blog/registry.ts +++ b/apps/sim/lib/blog/registry.ts @@ -21,5 +21,3 @@ export const getAllPostMeta = blogRegistry.getAllPostMeta export const getPostBySlug = blogRegistry.getPostBySlug export const getAllTags = blogRegistry.getAllTags export const getRelatedPosts = blogRegistry.getRelatedPosts -export const getNavBlogPosts = blogRegistry.getNavPosts -export const invalidateBlogCaches = blogRegistry.invalidateCaches diff --git a/apps/sim/lib/compare/data/types.ts b/apps/sim/lib/compare/data/types.ts index 1153af7f227..03725a9a1ee 100644 --- a/apps/sim/lib/compare/data/types.ts +++ b/apps/sim/lib/compare/data/types.ts @@ -237,16 +237,6 @@ export interface CompetitorProfile { facts: ComparisonFacts } -/** A fact awaiting verification. Used as an intermediate research artifact, never shipped. */ -export function unknownFact(reason?: string): Fact { - return { - value: 'Unknown', - detail: reason, - confidence: 'unknown', - sources: [], - } -} - /** * Broad grouping for {@link SimFeature} entries. A single feature catalog * entry belongs to exactly one category, but can carry additional diff --git a/apps/sim/lib/copilot/model-visible-content.ts b/apps/sim/lib/copilot/model-visible-content.ts deleted file mode 100644 index 17039103bd2..00000000000 --- a/apps/sim/lib/copilot/model-visible-content.ts +++ /dev/null @@ -1,40 +0,0 @@ -export const COPILOT_CONTEXT_MODEL_TEXT_KEYS = [ - 'content', - 'description', - 'fileName', - 'label', - 'tableName', - 'tag', - 'text', - 'title', -] as const - -export const COPILOT_CONTEXT_ROUTING_KEYS = ['path', 'uri', 'url'] as const - -export const COPILOT_MESSAGE_DISPLAY_KEYS = [ - 'fileName', - 'label', - 'tableName', - 'text', - 'title', -] as const - -export const COPILOT_VFS_MODEL_TEXT_KEYS = [ - 'description', - 'displayName', - 'email', - 'name', - 'prompt', - 'sourceTaskName', - 'title', -] as const - -export const COPILOT_VFS_ROUTING_KEYS = ['folderPath', 'path', 'url'] as const - -export const COPILOT_USER_METADATA_MODEL_TEXT_KEYS = ['email', 'name'] as const - -export const COPILOT_DESKTOP_MODEL_TEXT_KEYS = ['running'] as const - -export function isCopilotModelTextKey(keys: readonly string[], key: string): boolean { - return keys.includes(key) -} diff --git a/apps/sim/lib/copilot/request/tools/files.ts b/apps/sim/lib/copilot/request/tools/files.ts index 518c139beee..e1248c03814 100644 --- a/apps/sim/lib/copilot/request/tools/files.ts +++ b/apps/sim/lib/copilot/request/tools/files.ts @@ -108,10 +108,6 @@ export function escapeCsvValue(value: unknown): string { return str } -export function convertRowsToCsv(rows: Record[]): string { - return convertRowsToCsvWithProvenance(rows).content -} - export function normalizeOutputWorkspaceFileName(outputPath: string): string { const segments = decodeVfsPathSegments(outputPath.trim().replace(/^\/+|\/+$/g, '')) const fileName = segments.at(-1) diff --git a/apps/sim/lib/copilot/tool-executor/executor.ts b/apps/sim/lib/copilot/tool-executor/executor.ts index 466a40fe06d..dc2489efe61 100644 --- a/apps/sim/lib/copilot/tool-executor/executor.ts +++ b/apps/sim/lib/copilot/tool-executor/executor.ts @@ -24,10 +24,6 @@ export function registerHandlers(entries: Record): void { } } -export function getRegisteredToolIds(): string[] { - return Array.from(handlerRegistry.keys()) -} - export function hasHandler(toolId: string): boolean { return handlerRegistry.has(toolId) } diff --git a/apps/sim/lib/copilot/tool-executor/router.ts b/apps/sim/lib/copilot/tool-executor/router.ts index 13ea484300a..fede5c200f9 100644 --- a/apps/sim/lib/copilot/tool-executor/router.ts +++ b/apps/sim/lib/copilot/tool-executor/router.ts @@ -1,5 +1,4 @@ import { TOOL_CATALOG, type ToolCatalogEntry } from '@/lib/copilot/generated/tool-catalog-v1' -import type { ToolCallDescriptor } from './types' export type ToolRouteTarget = ToolCatalogEntry['route'] @@ -27,10 +26,6 @@ export function isSimExecuted(toolId: string): boolean { return getToolEntry(toolId)?.route === 'sim' } -export function isGoExecuted(toolId: string): boolean { - return getToolEntry(toolId)?.route === 'go' -} - export function isClientExecuted(toolId: string): boolean { return getToolEntry(toolId)?.route === 'client' } @@ -43,26 +38,3 @@ export function isKnownTool(toolId: string): boolean { export function toolRequiresApproval(toolId: string): boolean { return getToolEntry(toolId)?.requiresApproval === true } - -interface PartitionedBatch { - sim: ToolCallDescriptor[] - go: ToolCallDescriptor[] - subagent: ToolCallDescriptor[] - client: ToolCallDescriptor[] - unknown: ToolCallDescriptor[] -} - -export function partitionToolBatch(toolCalls: ToolCallDescriptor[]): PartitionedBatch { - const result: PartitionedBatch = { sim: [], go: [], subagent: [], client: [], unknown: [] } - - for (const tc of toolCalls) { - const route = routeToolCall(tc.toolId) - if (!route) { - result.unknown.push(tc) - continue - } - result[route.route].push(tc) - } - - return result -} diff --git a/apps/sim/lib/copilot/tools/client/local-filesystem.ts b/apps/sim/lib/copilot/tools/client/local-filesystem.ts index 53a9f80c773..ba6798eab85 100644 --- a/apps/sim/lib/copilot/tools/client/local-filesystem.ts +++ b/apps/sim/lib/copilot/tools/client/local-filesystem.ts @@ -383,9 +383,3 @@ export function executeLocalFilesystemTool( } ) } - -export const userLocalVfsTestHelpers = { - mountVfsRoot, - vfsPathForUri, - localUriForVfsPath, -} diff --git a/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts b/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts index 915a2cf5a50..726693b7b59 100644 --- a/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts +++ b/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts @@ -40,7 +40,7 @@ export type FileIntentScope = { messageId?: string // When set, consumeLatestFileIntent only considers intents from this subagent // channel — the key to isolating concurrent file subagents. Omitted by callers - // that intentionally span the whole message (e.g. clearIntentsForWorkspace). + // that intentionally span the whole message. channelId?: string } @@ -66,8 +66,8 @@ function scopeMatches(intent: PendingFileIntent, scope?: FileIntentScope): boole // Channel filter for consume: when a scope carries a channelId, only the // matching file subagent's intent qualifies. No channelId => message-wide -// (legacy / main-agent) behavior. Deliberately separate from scopeMatches so -// clearIntentsForWorkspace keeps clearing every channel in a message. +// (legacy / main-agent) behavior. Deliberately separate from scopeMatches, which +// spans every channel in a message. function channelMatches(intent: PendingFileIntent, scope?: FileIntentScope): boolean { return !scope?.channelId || intent.channelId === scope.channelId } @@ -236,50 +236,3 @@ export async function consumeLatestFileIntent( } return latest } - -export async function clearIntentsForWorkspace( - workspaceId: string, - scope?: FileIntentScope -): Promise { - const redis = getRedisClient() - if (!redis) { - let cleared = 0 - for (const [key, intent] of memoryStore) { - if (intent.workspaceId === workspaceId && (!scope || scopeMatches(intent, scope))) { - memoryStore.delete(key) - cleared++ - } - } - return cleared - } - - const key = getWorkspaceRedisKey(workspaceId) - if (!scope) { - const count = await withRedisRetry( - 'count_workspace_file_intents', - workspaceId, - async (client) => client.hlen(key) - ) - await withRedisRetry('clear_workspace_file_intents', workspaceId, async (client) => { - await client.del(key) - }) - return count - } - - const entries = await withRedisRetry('read_workspace_file_intents', workspaceId, async (client) => - client.hgetall(key) - ) - const fieldsToDelete: string[] = [] - for (const [field, raw] of Object.entries(entries)) { - const parsed = parseIntent(raw) - if (parsed && scopeMatches(parsed, scope)) { - fieldsToDelete.push(field) - } - } - if (fieldsToDelete.length > 0) { - await withRedisRetry('clear_scoped_file_intents', workspaceId, async (client) => { - await client.hdel(key, ...fieldsToDelete) - }) - } - return fieldsToDelete.length -} diff --git a/apps/sim/lib/copilot/tools/shared/workflow-utils.ts b/apps/sim/lib/copilot/tools/shared/workflow-utils.ts index 07c1d8f54c8..c82e0f60bdf 100644 --- a/apps/sim/lib/copilot/tools/shared/workflow-utils.ts +++ b/apps/sim/lib/copilot/tools/shared/workflow-utils.ts @@ -31,15 +31,3 @@ export function formatNormalizedWorkflowForCopilot( if (!normalized) return null return formatWorkflowStateForCopilot(normalized, options) } - -export function normalizeWorkflowName(name?: string | null): string { - return String(name || '') - .trim() - .toLowerCase() -} - -export function extractWorkflowNames(workflows: Array<{ name?: string | null }>): string[] { - return workflows - .map((workflow) => (typeof workflow?.name === 'string' ? workflow.name : null)) - .filter((name): name is string => Boolean(name)) -} diff --git a/apps/sim/lib/core/admission/gate.ts b/apps/sim/lib/core/admission/gate.ts index f3c4866a246..af024e1c782 100644 --- a/apps/sim/lib/core/admission/gate.ts +++ b/apps/sim/lib/core/admission/gate.ts @@ -57,10 +57,3 @@ export function admissionRejectedResponse(): NextResponse { } ) } - -/** - * Returns the current gate metrics for observability. - */ -export function getAdmissionGateStatus(): { inflight: number; maxInflight: number } { - return { inflight, maxInflight: MAX_INFLIGHT } -} diff --git a/apps/sim/lib/core/execution-limits/types.ts b/apps/sim/lib/core/execution-limits/types.ts index 011bd7e4cf9..b81c817e531 100644 --- a/apps/sim/lib/core/execution-limits/types.ts +++ b/apps/sim/lib/core/execution-limits/types.ts @@ -238,13 +238,6 @@ export function getExecutionDeadlineAt(signal?: AbortSignal): Date | undefined { return deadline === undefined ? undefined : new Date(deadline) } -/** Copies a known execution deadline onto a derived signal. */ -export function preserveExecutionDeadline(source: AbortSignal, target: AbortSignal): AbortSignal { - const deadline = signalDeadlines.get(source) - if (deadline !== undefined) signalDeadlines.set(target, deadline) - return target -} - /** Combines cancellation sources and carries their earliest known execution deadline. */ export function combineExecutionAbortSignals(signals: readonly AbortSignal[]): AbortSignal { if (signals.length === 0) return new AbortController().signal diff --git a/apps/sim/lib/core/idempotency/service.ts b/apps/sim/lib/core/idempotency/service.ts index 43fa6cf9feb..25333ca9087 100644 --- a/apps/sim/lib/core/idempotency/service.ts +++ b/apps/sim/lib/core/idempotency/service.ts @@ -718,22 +718,6 @@ export const pollingIdempotency = new IdempotencyService({ storeResultBody: false, }) -/** - * Used by the internal `/api/billing/update-cost` endpoint (copilot, - * workspace-chat, MCP, mothership) to dedupe cost-recording calls. Storage - * is forced to Postgres: the operation writes AI cost to `user_stats`, - * and if Redis evicts the dedup key under memory pressure (high call - * volume) or drops it on restart, a retry would double-record usage — - * real money. DB storage fate-shares with `user_stats` and is - * eviction-proof; ~1-5ms added latency is invisible against LLM call - * latency. - */ -export const billingIdempotency = new IdempotencyService({ - namespace: 'billing', - ttlSeconds: 60 * 60, // 1 hour - forceStorage: 'database', -}) - /** * Dedupes a chat send by its client-generated `userMessageId`, so re-sending * one is safe. diff --git a/apps/sim/lib/core/rate-limiter/hosted-key/queue.ts b/apps/sim/lib/core/rate-limiter/hosted-key/queue.ts index a0803d1ae61..bab7dfc7ca3 100644 --- a/apps/sim/lib/core/rate-limiter/hosted-key/queue.ts +++ b/apps/sim/lib/core/rate-limiter/hosted-key/queue.ts @@ -203,7 +203,3 @@ export function getHostedKeyQueue(): HostedKeyQueue { } return cachedQueue } - -export function resetHostedKeyQueue(): void { - cachedQueue = null -} diff --git a/apps/sim/lib/core/utils/response-format.ts b/apps/sim/lib/core/utils/response-format.ts index 97a57d0e72c..a5d4a38f30b 100644 --- a/apps/sim/lib/core/utils/response-format.ts +++ b/apps/sim/lib/core/utils/response-format.ts @@ -113,21 +113,6 @@ export function extractFieldValues( return extractedValues } -/** - * Format extracted field values for display - * Returns formatted string representation of field values - */ -export function formatFieldValues(extractedValues: Record): string { - const formattedValues: string[] = [] - - for (const [fieldName, value] of Object.entries(extractedValues)) { - const formattedValue = typeof value === 'string' ? value : JSON.stringify(value) - formattedValues.push(formattedValue) - } - - return formattedValues.join('\n') -} - /** * Extract block ID from output ID * Handles both formats: "blockId" and "blockId_path" or "blockId.path" @@ -174,18 +159,6 @@ export function hasResponseFormatSelection(selectedOutputs: string[], blockId: s }) } -/** - * Get selected field names for a specific block from output IDs - */ -export function getSelectedFieldNames(selectedOutputs: string[], blockId: string): string[] { - return selectedOutputs - .filter((outputId) => { - const blockIdForOutput = extractBlockIdFromOutputId(outputId) - return blockIdForOutput === blockId && outputId.includes('_') - }) - .map((outputId) => extractPathFromOutputId(outputId, blockId)) -} - /** * Internal helper to traverse an object path without parsing * @param obj The object to traverse diff --git a/apps/sim/lib/core/utils/theme.ts b/apps/sim/lib/core/utils/theme.ts index 5d7101ca7ec..46035f4ce53 100644 --- a/apps/sim/lib/core/utils/theme.ts +++ b/apps/sim/lib/core/utils/theme.ts @@ -33,11 +33,3 @@ export function syncThemeToNextThemes(theme: 'system' | 'light' | 'dark') { root.classList.add(theme) } } - -/** - * Gets the current theme from next-themes localStorage - */ -export function getThemeFromNextThemes(): 'system' | 'light' | 'dark' { - if (typeof window === 'undefined') return 'system' - return (localStorage.getItem('sim-theme') as 'system' | 'light' | 'dark') || 'system' -} diff --git a/apps/sim/lib/core/utils/user-file.ts b/apps/sim/lib/core/utils/user-file.ts index abcac62da98..546c9dbc4cb 100644 --- a/apps/sim/lib/core/utils/user-file.ts +++ b/apps/sim/lib/core/utils/user-file.ts @@ -112,20 +112,3 @@ export function filterUserFileForDisplay(data: Record): Record< } return filtered } - -/** - * Extracts base64 content from either a raw base64 string or a UserFile object. - * Useful for tools that accept file input in either format. - * @returns The base64 string, or undefined if not found - */ -export function extractBase64FromFileInput( - input: string | UserFileLike | null | undefined -): string | undefined { - if (typeof input === 'string') { - return input - } - if (input?.base64) { - return input.base64 - } - return undefined -} diff --git a/apps/sim/lib/credentials/client-state.ts b/apps/sim/lib/credentials/client-state.ts index 70d52d95fa2..c1e6e3b6fee 100644 --- a/apps/sim/lib/credentials/client-state.ts +++ b/apps/sim/lib/credentials/client-state.ts @@ -4,15 +4,6 @@ export const PENDING_OAUTH_CREDENTIAL_DRAFT_KEY = 'sim.pending-oauth-credential- export const PENDING_CREDENTIAL_CREATE_REQUEST_KEY = 'sim.pending-credential-create-request' export const PENDING_CREDENTIAL_CREATE_REQUEST_EVENT = 'sim:pending-credential-create-request' -interface PendingOAuthCredentialDraft { - workspaceId: string - providerId: string - displayName: string - existingCredentialIds: string[] - existingAccountIds: string[] - requestedAt: number -} - export interface PendingCredentialCreateRequest { workspaceId: string type: 'env_personal' | 'env_workspace' @@ -29,23 +20,6 @@ function parseJson(raw: string | null): T | null { } } -export function readPendingOAuthCredentialDraft(): PendingOAuthCredentialDraft | null { - if (typeof window === 'undefined') return null - return parseJson( - window.sessionStorage.getItem(PENDING_OAUTH_CREDENTIAL_DRAFT_KEY) - ) -} - -export function writePendingOAuthCredentialDraft(payload: PendingOAuthCredentialDraft) { - if (typeof window === 'undefined') return - window.sessionStorage.setItem(PENDING_OAUTH_CREDENTIAL_DRAFT_KEY, JSON.stringify(payload)) -} - -export function clearPendingOAuthCredentialDraft() { - if (typeof window === 'undefined') return - window.sessionStorage.removeItem(PENDING_OAUTH_CREDENTIAL_DRAFT_KEY) -} - export function readPendingCredentialCreateRequest(): PendingCredentialCreateRequest | null { if (typeof window === 'undefined') return null return parseJson( diff --git a/apps/sim/lib/credentials/queries.ts b/apps/sim/lib/credentials/queries.ts index bacb9d2cda7..607145b80a7 100644 --- a/apps/sim/lib/credentials/queries.ts +++ b/apps/sim/lib/credentials/queries.ts @@ -261,22 +261,3 @@ export async function listWorkspacePrincipalCredentials(params: { return keysetPage(keys, mapped, limit) } - -/** - * A single credential scoped to a workspace, or null when it does not exist - * there. Scoping by workspace is what keeps a credential id from another tenant - * from resolving at all. - */ -export async function getWorkspaceCredential(params: { - workspaceId: string - credentialId: string -}): Promise { - const [row] = await db - .select() - .from(credential) - .where( - and(eq(credential.id, params.credentialId), eq(credential.workspaceId, params.workspaceId)) - ) - .limit(1) - return row ?? null -} diff --git a/apps/sim/lib/execution/event-buffer.ts b/apps/sim/lib/execution/event-buffer.ts index 3e75a7263c8..b555c23d2b3 100644 --- a/apps/sim/lib/execution/event-buffer.ts +++ b/apps/sim/lib/execution/event-buffer.ts @@ -628,7 +628,7 @@ export async function readExecutionMetaState( if (canUseMemoryEventBuffer()) { return readMemoryMeta(executionId) } - logger.warn('getExecutionMeta: Redis client unavailable', { executionId }) + logger.warn('readExecutionMetaState: Redis client unavailable', { executionId }) return { status: 'unavailable', error: 'Redis client unavailable' } } try { @@ -659,23 +659,6 @@ export async function readExecutionMetaState( } } -export async function getExecutionMeta(executionId: string): Promise { - const result = await readExecutionMetaState(executionId) - if (result.status === 'found') return result.meta - if (result.status === 'unavailable') { - return null - } - return null -} - -export async function readExecutionEvents( - executionId: string, - afterEventId: number -): Promise { - const result = await readExecutionEventsState(executionId, afterEventId) - return result.status === 'ok' ? result.events : [] -} - export async function readExecutionEventsState( executionId: string, afterEventId: number diff --git a/apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts b/apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts index 196d76e6503..8feee1bf9a2 100644 --- a/apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts +++ b/apps/sim/lib/execution/remote-sandbox/sandbox-spec.ts @@ -13,8 +13,6 @@ import { sandboxCliToolRecipes } from '@/lib/execution/remote-sandbox/cli-tools. */ export type SandboxLanguage = `${CodeLanguage.JavaScript}` | `${CodeLanguage.Python}` -export const SANDBOX_LANGUAGES = [CodeLanguage.JavaScript, CodeLanguage.Python] as const - export function isSandboxLanguage(value: string): value is SandboxLanguage { return value === CodeLanguage.JavaScript || value === CodeLanguage.Python } diff --git a/apps/sim/lib/folders/queries.ts b/apps/sim/lib/folders/queries.ts index dda1f63b436..6fc65721a5e 100644 --- a/apps/sim/lib/folders/queries.ts +++ b/apps/sim/lib/folders/queries.ts @@ -92,34 +92,6 @@ export async function findActiveFolder( return row ?? null } -/** - * A folder in a workspace's tree regardless of archive state. - * - * {@link findActiveFolder} answers "is this a valid destination"; this answers "does this row - * exist here at all". Delete needs the second question — `deleteFolder` reuses an already - * archived folder's own `deletedAt` so a cascade that failed partway can be retried, and - * filtering archived rows out would strand those stragglers. - */ -export async function findFolderInWorkspace( - folderId: string, - workspaceId: string, - resourceType: FolderResourceType -): Promise { - const [row] = await db - .select() - .from(folder) - .where( - and( - eq(folder.id, folderId), - eq(folder.workspaceId, workspaceId), - eq(folder.resourceType, resourceType) - ) - ) - .limit(1) - - return row ?? null -} - /** * Where a restored resource should land: its original folder when that folder is reachable, * otherwise the workspace root. diff --git a/apps/sim/lib/guardrails/pii-entities.ts b/apps/sim/lib/guardrails/pii-entities.ts index 54976c0e266..321120e276b 100644 --- a/apps/sim/lib/guardrails/pii-entities.ts +++ b/apps/sim/lib/guardrails/pii-entities.ts @@ -55,13 +55,6 @@ export const SUPPORTED_PII_ENTITIES = { export type PIIEntityType = keyof typeof SUPPORTED_PII_ENTITIES -/** Flat `{ value, label }` options for entity-type pickers, in catalog order. */ -export const PII_ENTITY_OPTIONS: ReadonlyArray<{ value: PIIEntityType; label: string }> = - Object.entries(SUPPORTED_PII_ENTITIES).map(([value, label]) => ({ - value: value as PIIEntityType, - label, - })) - /** Entity types grouped by region, for a grouped checkbox picker. */ export const PII_ENTITY_GROUPS: ReadonlyArray<{ label: string @@ -325,13 +318,6 @@ export const PII_STAGE_META: ReadonlyArray<{ }, ] -/** Recognizers that over-redact (loose, no checksum); surfaced as UI guidance. */ -export const RISKY_PII_ENTITIES: ReadonlySet = new Set([ - 'US_SSN', - 'US_BANK_NUMBER', - 'DATE_TIME', -]) - /** A fully-disabled stage policy for new drafts. */ export function emptyStagePolicy(): PiiStagePolicy { return { enabled: false, entityTypes: [], language: DEFAULT_PII_LANGUAGE, customPatterns: [] } diff --git a/apps/sim/lib/integrations/availability.server.ts b/apps/sim/lib/integrations/availability.server.ts index 8de88f590ed..a566ffccce8 100644 --- a/apps/sim/lib/integrations/availability.server.ts +++ b/apps/sim/lib/integrations/availability.server.ts @@ -16,7 +16,6 @@ export type { IntegrationAvailabilityState, } from '@/lib/integrations/availability' -let unavailableIntegrationTypes: ReadonlySet | null = null let integrationAvailabilityByType: ReadonlyMap | null = null const oauthServiceAvailability = new Map() @@ -24,20 +23,6 @@ export function getIntegrationAvailability() { return resolveIntegrationAvailability(env) } -export function getUnavailableIntegrationTypes(): ReadonlySet { - if (!unavailableIntegrationTypes) { - unavailableIntegrationTypes = new Set( - getIntegrationAvailability() - .filter( - (integration) => - integration.state === 'unavailable' || integration.state === 'misconfigured' - ) - .map((integration) => integration.type.toLowerCase()) - ) - } - return unavailableIntegrationTypes -} - function getIntegrationAvailabilityByType(): ReadonlyMap { if (!integrationAvailabilityByType) { integrationAvailabilityByType = new Map( diff --git a/apps/sim/lib/library/registry.ts b/apps/sim/lib/library/registry.ts index 6d642dc88ab..1e1fa79bba1 100644 --- a/apps/sim/lib/library/registry.ts +++ b/apps/sim/lib/library/registry.ts @@ -13,5 +13,3 @@ export const getAllPostMeta = libraryRegistry.getAllPostMeta export const getPostBySlug = libraryRegistry.getPostBySlug export const getAllTags = libraryRegistry.getAllTags export const getRelatedPosts = libraryRegistry.getRelatedPosts -export const getNavLibraryPosts = libraryRegistry.getNavPosts -export const invalidateLibraryCaches = libraryRegistry.invalidateCaches diff --git a/apps/sim/lib/logs/get-trigger-options.ts b/apps/sim/lib/logs/get-trigger-options.ts index 5ff23e804ca..b5f88ced199 100644 --- a/apps/sim/lib/logs/get-trigger-options.ts +++ b/apps/sim/lib/logs/get-trigger-options.ts @@ -10,14 +10,6 @@ export interface TriggerOption { let cachedTriggerOptions: TriggerOption[] | null = null let cachedTriggerMetadataMap: Map | null = null -/** - * Reset cache - useful for HMR in development or testing - */ -export function resetTriggerOptionsCache() { - cachedTriggerOptions = null - cachedTriggerMetadataMap = null -} - /** * Dynamically generates trigger filter options from the trigger registry and block definitions. * Results are cached after first call for performance (~98% faster on subsequent calls). diff --git a/apps/sim/lib/mcp/service.ts b/apps/sim/lib/mcp/service.ts index 9c2475a9471..7a32eb8292b 100644 --- a/apps/sim/lib/mcp/service.ts +++ b/apps/sim/lib/mcp/service.ts @@ -8,7 +8,6 @@ import { getErrorMessage } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' import { backoffWithJitter } from '@sim/utils/retry' import { and, eq, isNull, lte, or, sql } from 'drizzle-orm' -import { isTest } from '@/lib/core/config/env-flags' import { generateRequestId } from '@/lib/core/utils/request' import { McpClient } from '@/lib/mcp/client' import { mcpConnectionManager } from '@/lib/mcp/connection-manager' @@ -1226,24 +1225,3 @@ class McpService { } export const mcpService = new McpService() - -/** - * Setup process signal handlers for graceful shutdown - */ -export function setupMcpServiceCleanup() { - if (isTest) { - return - } - - const cleanup = () => { - mcpService.dispose() - } - - process.on('SIGTERM', cleanup) - process.on('SIGINT', cleanup) - - return () => { - process.removeListener('SIGTERM', cleanup) - process.removeListener('SIGINT', cleanup) - } -} diff --git a/apps/sim/lib/mcp/storage/factory.ts b/apps/sim/lib/mcp/storage/factory.ts index ad15af22fc4..cdbb51df8d1 100644 --- a/apps/sim/lib/mcp/storage/factory.ts +++ b/apps/sim/lib/mcp/storage/factory.ts @@ -40,14 +40,3 @@ export function getMcpCacheType(): 'redis' | 'memory' { const redis = getRedisClient() return redis ? 'redis' : 'memory' } - -/** - * Reset the cached adapter. - * Only use for testing purposes. - */ -export function resetMcpCacheAdapter(): void { - if (cachedAdapter) { - cachedAdapter.dispose() - cachedAdapter = null - } -} diff --git a/apps/sim/lib/mcp/workflow-tool-schema.ts b/apps/sim/lib/mcp/workflow-tool-schema.ts index 3147901000d..31b801d3f1a 100644 --- a/apps/sim/lib/mcp/workflow-tool-schema.ts +++ b/apps/sim/lib/mcp/workflow-tool-schema.ts @@ -281,23 +281,6 @@ export function getMeaningfulWorkflowDescription( return trimmed } -/** - * Generate a complete MCP tool definition from workflow metadata and input format. - */ -export function generateToolDefinition( - workflowName: string, - workflowDescription: string | undefined | null, - inputFormat: InputFormatField[], - customToolName?: string, - customDescription?: string -): McpToolDefinition { - return { - name: customToolName || sanitizeToolName(workflowName), - description: customDescription || workflowDescription || `Execute ${workflowName} workflow`, - inputSchema: generateToolInputSchema(inputFormat), - } -} - /** * Extract input format from a workflow's blocks. * Looks for any valid start block and extracts its inputFormat configuration. diff --git a/apps/sim/lib/mothership/inbox/agentmail-client.ts b/apps/sim/lib/mothership/inbox/agentmail-client.ts index 1f1abbfb51f..3f7a0dfe5e4 100644 --- a/apps/sim/lib/mothership/inbox/agentmail-client.ts +++ b/apps/sim/lib/mothership/inbox/agentmail-client.ts @@ -69,10 +69,6 @@ export async function deleteInbox(inboxId: string): Promise { }) } -export async function getInbox(inboxId: string): Promise { - return request(`/inboxes/${encodeURIComponent(inboxId)}`) -} - export async function createWebhook(opts: { url: string eventTypes: string[] diff --git a/apps/sim/lib/permission-groups/types.ts b/apps/sim/lib/permission-groups/types.ts index 3b1a9049bfe..15bdb9773f9 100644 --- a/apps/sim/lib/permission-groups/types.ts +++ b/apps/sim/lib/permission-groups/types.ts @@ -16,10 +16,6 @@ export const PERMISSION_GROUP_MEMBER_CONSTRAINTS = { groupUser: 'permission_group_member_group_user_unique', } as const -export const PERMISSION_GROUP_WORKSPACE_CONSTRAINTS = { - groupWorkspace: 'permission_group_workspace_group_workspace_unique', -} as const - export const permissionGroupConfigSchema = z.object({ allowedIntegrations: z.array(z.string()).nullable().optional(), allowedModelProviders: z.array(z.string()).nullable().optional(), diff --git a/apps/sim/lib/pptx-renderer/parser/units.ts b/apps/sim/lib/pptx-renderer/parser/units.ts index e8782fa2bf9..1dd7676db89 100644 --- a/apps/sim/lib/pptx-renderer/parser/units.ts +++ b/apps/sim/lib/pptx-renderer/parser/units.ts @@ -14,11 +14,6 @@ export function emuToPx(emu: number): number { return (emu / 914400) * 96 } -/** EMU to points. */ -export function emuToPt(emu: number): number { - return emu / 12700 -} - /** OOXML angle (60000ths of a degree) to degrees. */ export function angleToDeg(angle: number): number { return angle / 60000 @@ -29,11 +24,6 @@ export function pctToDecimal(pct: number): number { return pct / 100000 } -/** Hundredths of a point to points (used for font sizes in OOXML). */ -export function hundredthPtToPt(val: number): number { - return val / 100 -} - /** Points to pixels (at 96 DPI). */ export function ptToPx(pt: number): number { return (pt * 96) / 72 @@ -46,14 +36,3 @@ export function ptToPx(pt: number): number { export function detectUnit(value: number): 'emu' | 'point' { return Math.abs(value) > 20000 ? 'emu' : 'point' } - -/** - * Smart conversion to pixels: auto-detects whether the value is EMU or points - * and converts accordingly. - */ -export function smartToPx(value: number): number { - if (detectUnit(value) === 'emu') { - return emuToPx(value) - } - return ptToPx(value) -} diff --git a/apps/sim/lib/pptx-renderer/renderer/predefined-table-styles.ts b/apps/sim/lib/pptx-renderer/renderer/predefined-table-styles.ts index 818b9088549..a338323b9ca 100644 --- a/apps/sim/lib/pptx-renderer/renderer/predefined-table-styles.ts +++ b/apps/sim/lib/pptx-renderer/renderer/predefined-table-styles.ts @@ -783,11 +783,3 @@ export function getPredefinedTableStyle(styleId: string): SafeXmlNode | undefine cache.set(styleId, node) return node } - -/** Exported for testing: number of known predefined style UUIDs. */ -export const PREDEFINED_STYLE_COUNT = styleIdMap.size - -/** Exported for testing: all known style IDs. */ -export function getAllPredefinedStyleIds(): string[] { - return Array.from(styleIdMap.keys()) -} diff --git a/apps/sim/lib/pptx-renderer/shapes/presets.ts b/apps/sim/lib/pptx-renderer/shapes/presets.ts index e079110a452..250bc0b155c 100644 --- a/apps/sim/lib/pptx-renderer/shapes/presets.ts +++ b/apps/sim/lib/pptx-renderer/shapes/presets.ts @@ -4402,21 +4402,6 @@ presetOverlays.set('can', (w, h) => { ] }) -/** - * Get overlay paths for a preset shape (3D top faces, etc.). - * Returns empty array if the shape has no overlays. - */ -export function getPresetOverlays( - shapeType: string, - w: number, - h: number, - adjustments?: Map -): PresetOverlay[] { - const key = shapeType.toLowerCase() - const gen = presetOverlays.get(key) ?? presetOverlays.get(shapeType) - return gen ? gen(w, h, adjustments) : [] -} - // Multi-path preset shapes — complex shapes with multiple SVG paths // Each path has its own fill modifier and stroke behavior, matching OOXML spec. diff --git a/apps/sim/lib/table/cell-write.ts b/apps/sim/lib/table/cell-write.ts index cf2b4e34373..789d8702b56 100644 --- a/apps/sim/lib/table/cell-write.ts +++ b/apps/sim/lib/table/cell-write.ts @@ -24,7 +24,6 @@ import { import type { RowData, RowExecutionMetadata, - RowExecutions, TableDefinition, TableRowSecretProvenanceWrite, WorkflowGroup, @@ -411,10 +410,3 @@ export function buildOutputsByBlockId( } return map } - -/** Type-narrowing helper used by readers that can't assume `executions` is set. */ -export function readExecutions( - row: { executions?: RowExecutions } | null | undefined -): RowExecutions { - return row?.executions ?? {} -} diff --git a/apps/sim/lib/table/dispatcher.ts b/apps/sim/lib/table/dispatcher.ts index 6b2747e0e4d..9f9f200886d 100644 --- a/apps/sim/lib/table/dispatcher.ts +++ b/apps/sim/lib/table/dispatcher.ts @@ -781,18 +781,6 @@ export async function completeDispatchIfActive(dispatchId: string): Promise 0 } -export async function markDispatchCancelled(dispatchId: string): Promise { - await db - .update(tableRunDispatches) - .set({ status: 'cancelled', cancelledAt: new Date() }) - .where( - and( - eq(tableRunDispatches.id, dispatchId), - inArray(tableRunDispatches.status, [...ACTIVE_DISPATCH_STATUSES]) - ) - ) -} - /** Mark every active dispatch on this table as cancelled. Single atomic * UPDATE so the dispatcher's next iteration observes the cancel. Returns the * dispatches that were cancelled so the caller can emit per-dispatch SSE diff --git a/apps/sim/lib/table/jobs/service.ts b/apps/sim/lib/table/jobs/service.ts index 24f1ec5cf39..c5f16114f8b 100644 --- a/apps/sim/lib/table/jobs/service.ts +++ b/apps/sim/lib/table/jobs/service.ts @@ -387,25 +387,6 @@ export async function getTableJob( return job ?? null } -/** - * Stamps an export job's generated-file storage key onto its payload (`{ resultKey }` merge). - * Scoped to the still-running job so a superseded attempt can't clobber a newer run's result. - * The download route reads it; the janitor deletes the file when the terminal job is pruned. - */ -export async function setJobResultKey( - tableId: string, - jobId: string, - resultKey: string -): Promise { - await db - .update(tableJobs) - .set({ - payload: sql`coalesce(${tableJobs.payload}, '{}'::jsonb) || jsonb_build_object('resultKey', ${resultKey}::text)`, - updatedAt: new Date(), - }) - .where(ownsActiveJob(tableId, jobId)) -} - /** Stamps an export result only while the canonical workspace-scoped job is active. */ export async function setJobResultKeyInWorkspace( tableId: string, diff --git a/apps/sim/lib/table/mutation-locks.ts b/apps/sim/lib/table/mutation-locks.ts index 9a5c6e59cbd..dc38ab905a1 100644 --- a/apps/sim/lib/table/mutation-locks.ts +++ b/apps/sim/lib/table/mutation-locks.ts @@ -179,13 +179,3 @@ function patchTouchesOnlyWorkflowColumns( export function patchColumnIds(data: RowData): string[] { return Object.keys(data) } - -/** - * Escape hatch for tests and trusted system callers that legitimately invoke - * the low-level `rows/ordering.ts` primitives without a preceding assert - * (e.g. fixtures, or a call path already gated elsewhere). NOT for production - * mutation paths — those must assert so locks are enforced and violations logged. - */ -export function unsafeMutationProof(): MutationProof { - return proofFor() -} diff --git a/apps/sim/lib/table/query-builder/converters.ts b/apps/sim/lib/table/query-builder/converters.ts index 2a861155fd3..981d62294c8 100644 --- a/apps/sim/lib/table/query-builder/converters.ts +++ b/apps/sim/lib/table/query-builder/converters.ts @@ -19,7 +19,6 @@ import type { JsonValue, Predicate, Sort, - SortDirection, SortRule, SortSpec, TablePredicate, @@ -159,12 +158,6 @@ export function isTablePredicate(value: Filter | TablePredicate): value is Table return ('all' in v && Array.isArray(v.all)) || ('any' in v && Array.isArray(v.any)) } -/** Converts a single UI sort rule to a Sort object for API queries. */ -export function sortRuleToSort(rule: SortRule | null): Sort | null { - if (!rule || !rule.column) return null - return { [rule.column]: rule.direction } -} - /** Converts multiple UI sort rules to a Sort object. */ export function sortRulesToSort(rules: SortRule[]): Sort | null { if (rules.length === 0) return null @@ -179,17 +172,6 @@ export function sortRulesToSort(rules: SortRule[]): Sort | null { return Object.keys(sort).length > 0 ? sort : null } -/** Converts a Sort object back to UI sort rules. */ -export function sortToRules(sort: Sort | null): SortRule[] { - if (!sort) return [] - - return Object.entries(sort).map(([column, direction]) => ({ - id: generateShortId(), - column, - direction: normalizeSortDirection(direction), - })) -} - function toRuleValue(operator: string, value: string, keepAsText = false): JsonValue { if (operator === 'isEmpty') return { $empty: true } if (operator === 'isNotEmpty') return { $empty: false } @@ -324,10 +306,6 @@ function formatValueForBuilder(value: JsonValue): string { return String(value) } -function normalizeSortDirection(direction: string): SortDirection { - return direction === 'desc' ? 'desc' : 'asc' -} - /* ----------------------------- v2 grammar ----------------------------- */ const VALUELESS_OPS = new Set(['isEmpty', 'isNotEmpty', 'isNull', 'isNotNull']) diff --git a/apps/sim/lib/table/query-builder/use-query-builder.ts b/apps/sim/lib/table/query-builder/use-query-builder.ts index 2d86ae236fd..39fd4af3653 100644 --- a/apps/sim/lib/table/query-builder/use-query-builder.ts +++ b/apps/sim/lib/table/query-builder/use-query-builder.ts @@ -9,7 +9,6 @@ import { type FilterRule, LOGICAL_OPERATORS, SORT_DIRECTION_OPTIONS, - type SortRule, } from '@/lib/table/query-builder/constants' import type { ColumnOption } from '@/lib/table/types' @@ -77,51 +76,6 @@ export function useFilterBuilder({ } } -/** Manages sort rule state with add/remove/update operations. */ -export function useSortBuilder({ - columns, - sortRule, - setSortRule, -}: UseSortBuilderProps): UseSortBuilderReturn { - const addSort = useCallback(() => { - setSortRule({ - id: generateShortId(), - column: columns[0]?.value || '', - direction: 'asc', - }) - }, [columns, setSortRule]) - - const removeSort = useCallback(() => { - setSortRule(null) - }, [setSortRule]) - - const updateSortColumn = useCallback( - (column: string) => { - if (sortRule) { - setSortRule({ ...sortRule, column }) - } - }, - [sortRule, setSortRule] - ) - - const updateSortDirection = useCallback( - (direction: 'asc' | 'desc') => { - if (sortRule) { - setSortRule({ ...sortRule, direction }) - } - }, - [sortRule, setSortRule] - ) - - return { - sortDirectionOptions, - addSort, - removeSort, - updateSortColumn, - updateSortDirection, - } -} - export interface UseFilterBuilderProps { columns: ColumnOption[] rules: FilterRule[] @@ -138,17 +92,3 @@ export interface UseFilterBuilderReturn { updateRule: (id: string, field: keyof FilterRule, value: string) => void createDefaultRule: () => FilterRule } - -interface UseSortBuilderProps { - columns: ColumnOption[] - sortRule: SortRule | null - setSortRule: (sort: SortRule | null) => void -} - -interface UseSortBuilderReturn { - sortDirectionOptions: ColumnOption[] - addSort: () => void - removeSort: () => void - updateSortColumn: (column: string) => void - updateSortDirection: (direction: 'asc' | 'desc') => void -} diff --git a/apps/sim/lib/uploads/config.ts b/apps/sim/lib/uploads/config.ts index 489b2ee509b..e527b6f605e 100644 --- a/apps/sim/lib/uploads/config.ts +++ b/apps/sim/lib/uploads/config.ts @@ -4,11 +4,8 @@ import type { StorageConfig, StorageContext } from '@/lib/uploads/shared/types' export type { StorageConfig, StorageContext } from '@/lib/uploads/shared/types' -export const UPLOAD_DIR = '/uploads' - const storageProvider = getConfiguredStorageProviderId() -export const hasBlobConfig = storageProvider === 'azure' export const USE_BLOB_STORAGE = storageProvider === 'azure' export const USE_S3_STORAGE = storageProvider === 's3' export const USE_GCS_STORAGE = storageProvider === 'gcs' @@ -358,28 +355,3 @@ function getGcsConfig(context: StorageContext): StorageConfig { return { bucket: GCS_CONFIG.bucket } } } - -/** - * Check if a specific storage context is configured - * Returns false if the context would fall back to general config but general isn't configured - */ -export function isStorageContextConfigured(context: StorageContext): boolean { - const config = getStorageConfig(context) - - if (USE_BLOB_STORAGE) { - return !!( - config.containerName && - (config.connectionString || (config.accountName && config.accountKey)) - ) - } - - if (USE_S3_STORAGE) { - return !!(config.bucket && config.region) - } - - if (USE_GCS_STORAGE) { - return !!config.bucket - } - - return true -} diff --git a/apps/sim/lib/uploads/contexts/copilot/copilot-file-manager.ts b/apps/sim/lib/uploads/contexts/copilot/copilot-file-manager.ts index f0406e73601..ceb7eacc49b 100644 --- a/apps/sim/lib/uploads/contexts/copilot/copilot-file-manager.ts +++ b/apps/sim/lib/uploads/contexts/copilot/copilot-file-manager.ts @@ -1,11 +1,6 @@ import { createLogger } from '@sim/logger' import { getBaseUrl } from '@/lib/core/utils/urls' -import { - deleteFile, - downloadFile, - generatePresignedDownloadUrl, - uploadFile, -} from '@/lib/uploads/core/storage-service' +import { downloadFile, uploadFile } from '@/lib/uploads/core/storage-service' const logger = createLogger('CopilotFileManager') @@ -40,12 +35,6 @@ export function isSupportedFileType(mimeType: string): boolean { return SUPPORTED_FILE_TYPES.includes(mimeType.toLowerCase()) } -interface CopilotFileAttachment { - key: string - filename: string - media_type: string -} - export interface CopilotStoredFile { id: string key: string @@ -123,73 +112,3 @@ export async function downloadCopilotFile(key: string): Promise { throw error } } - -/** - * Process copilot file attachments for chat messages - * - * Downloads files from storage and validates they are supported types. - * Skips unsupported files with a warning. - * - * @param attachments Array of file attachments - * @param requestId Request identifier for logging - * @returns Array of buffers for successfully downloaded files - */ -export async function processCopilotAttachments( - attachments: CopilotFileAttachment[], - requestId: string -): Promise> { - const results: Array<{ buffer: Buffer; attachment: CopilotFileAttachment }> = [] - - for (const attachment of attachments) { - try { - if (!isSupportedFileType(attachment.media_type)) { - logger.warn(`[${requestId}] Unsupported file type: ${attachment.media_type}`) - continue - } - - const buffer = await downloadCopilotFile(attachment.key) - - results.push({ buffer, attachment }) - } catch (error) { - logger.error(`[${requestId}] Failed to process file ${attachment.filename}:`, error) - } - } - - logger.info(`Successfully processed ${results.length}/${attachments.length} attachments`, { - requestId, - }) - - return results -} - -/** - * Generate a presigned download URL for a copilot file - * - * @param key File storage key - * @param expirationSeconds Time in seconds until URL expires (default: 1 hour) - * @returns Presigned download URL - */ -export async function generateCopilotDownloadUrl( - key: string, - expirationSeconds = 3600 -): Promise { - const downloadUrl = await generatePresignedDownloadUrl(key, 'copilot', expirationSeconds) - - logger.info(`Generated copilot download URL for: ${key}`) - - return downloadUrl -} - -/** - * Delete a copilot file from storage - * - * @param key File storage key - */ -export async function deleteCopilotFile(key: string): Promise { - await deleteFile({ - key, - context: 'copilot', - }) - - logger.info(`Successfully deleted copilot file: ${key}`) -} diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts index b71a04f0111..34d8be56d6b 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-folder-manager.ts @@ -1094,92 +1094,6 @@ export async function moveWorkspaceFileItems(params: { }) } -export async function archiveWorkspaceFileFolderRecursive( - workspaceId: string, - folderId: string -): Promise { - const now = new Date() - - return db.transaction(async (tx) => { - await acquireWorkspaceFileFolderMutationLock(tx, workspaceId) - - const [folder] = await tx - .select({ id: folderTable.id }) - .from(folderTable) - .where( - and( - eq(folderTable.id, folderId), - eq(folderTable.workspaceId, workspaceId), - isFileFolder, - isNull(folderTable.deletedAt) - ) - ) - .limit(1) - - if (!folder) throw new OrchestrationError('not_found', 'Folder not found') - - const activeFolders = await tx - .select({ id: folderTable.id, parentId: folderTable.parentId }) - .from(folderTable) - .where( - and(eq(folderTable.workspaceId, workspaceId), isFileFolder, isNull(folderTable.deletedAt)) - ) - .limit(MAX_WORKSPACE_FILE_BULK_AFFECTED_ITEMS + 1) - assertBulkAffectedItemsWithinLimit(activeFolders.length) - const folderIds = [folderId, ...collectDescendantFolderIds(activeFolders, folderId)] - assertBulkAffectedItemsWithinLimit(folderIds.length) - - const affectedFiles = await tx - .select({ id: workspaceFiles.id }) - .from(workspaceFiles) - .where( - and( - inArray(workspaceFiles.folderId, folderIds), - eq(workspaceFiles.workspaceId, workspaceId), - eq(workspaceFiles.context, 'workspace'), - isNull(workspaceFiles.deletedAt) - ) - ) - .limit(MAX_WORKSPACE_FILE_BULK_AFFECTED_ITEMS + 1) - assertBulkAffectedItemsWithinLimit(folderIds.length + affectedFiles.length) - - const archivedFiles = await tx - .update(workspaceFiles) - .set({ deletedAt: now, updatedAt: now }) - .where( - and( - inArray(workspaceFiles.folderId, folderIds), - eq(workspaceFiles.workspaceId, workspaceId), - eq(workspaceFiles.context, 'workspace'), - isNull(workspaceFiles.deletedAt) - ) - ) - .returning({ id: workspaceFiles.id }) - - const archivedFolders = await tx - .update(folderTable) - .set({ deletedAt: now, updatedAt: now }) - .where( - and( - inArray(folderTable.id, folderIds), - eq(folderTable.workspaceId, workspaceId), - isFileFolder, - isNull(folderTable.deletedAt) - ) - ) - .returning({ id: folderTable.id }) - - logger.info('Archived workspace file folder recursively', { - workspaceId, - folderId, - folders: archivedFolders.length, - files: archivedFiles.length, - }) - - return { folders: archivedFolders.length, files: archivedFiles.length } - }) -} - export async function restoreWorkspaceFileFolder( workspaceId: string, folderId: string diff --git a/apps/sim/lib/uploads/utils/file-utils.ts b/apps/sim/lib/uploads/utils/file-utils.ts index 6d04e92c7ef..46a56af66ac 100644 --- a/apps/sim/lib/uploads/utils/file-utils.ts +++ b/apps/sim/lib/uploads/utils/file-utils.ts @@ -133,14 +133,6 @@ export function isVideoFileType(mimeType: string): boolean { return getContentType(mimeType) === 'video' } -/** - * Check if a MIME type is an audio or video type - */ -export function isMediaFileType(mimeType: string): boolean { - const contentType = getContentType(mimeType) - return contentType === 'audio' || contentType === 'video' -} - /** * Convert a file buffer to base64 */ diff --git a/apps/sim/lib/uploads/utils/validation.ts b/apps/sim/lib/uploads/utils/validation.ts index b400fa621a1..ce41ca20535 100644 --- a/apps/sim/lib/uploads/utils/validation.ts +++ b/apps/sim/lib/uploads/utils/validation.ts @@ -189,12 +189,6 @@ export const SUPPORTED_VIDEO_MIME_TYPES: Record `.${ext}`) export const ACCEPT_ATTRIBUTE = [...ACCEPTED_FILE_TYPES, ...ACCEPTED_FILE_EXTENSIONS].join(',') @@ -330,39 +324,6 @@ export function isSupportedExtension(extension: string): extension is SupportedD ) } -/** - * Get supported MIME types for an extension - */ -export function getSupportedMimeTypes(extension: string): string[] { - if (isSupportedExtension(extension)) { - return SUPPORTED_MIME_TYPES[extension as SupportedDocumentExtension] - } - if (SUPPORTED_AUDIO_EXTENSIONS.includes(extension as SupportedAudioExtension)) { - return SUPPORTED_AUDIO_MIME_TYPES[extension as SupportedAudioExtension] - } - if (SUPPORTED_VIDEO_EXTENSIONS.includes(extension as SupportedVideoExtension)) { - return SUPPORTED_VIDEO_MIME_TYPES[extension as SupportedVideoExtension] - } - return [] -} - -/** - * Check if file extension is a supported audio extension - */ -export function isSupportedAudioExtension(extension: string): extension is SupportedAudioExtension { - return SUPPORTED_AUDIO_EXTENSIONS.includes(extension.toLowerCase() as SupportedAudioExtension) -} - -/** - * Check if file extension is a supported video extension - */ -export function isSupportedVideoExtension(extension: string): extension is SupportedVideoExtension { - return SUPPORTED_VIDEO_EXTENSIONS.includes(extension.toLowerCase() as SupportedVideoExtension) -} - -/** - * Validate if an audio/video file type is supported for STT processing - */ const PNG_MAGIC_BYTES = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) /** @@ -399,37 +360,3 @@ export function sniffImageContentType(buffer: Buffer): string | null { } return null } - -export function validateMediaFileType( - fileName: string, - mimeType: string -): FileValidationError | null { - const raw = extractExtension(fileName) - const extension = isAlphanumericExtension(raw) ? raw : '' - - const isAudio = SUPPORTED_AUDIO_EXTENSIONS.includes(extension as SupportedAudioExtension) - const isVideo = SUPPORTED_VIDEO_EXTENSIONS.includes(extension as SupportedVideoExtension) - - if (!isAudio && !isVideo) { - return { - code: 'UNSUPPORTED_FILE_TYPE', - message: `Unsupported media file type${extension ? `: ${extension}` : ` for "${fileName}"`}. Supported audio types: ${SUPPORTED_AUDIO_EXTENSIONS.join(', ')}. Supported video types: ${SUPPORTED_VIDEO_EXTENSIONS.join(', ')}`, - supportedTypes: [...SUPPORTED_AUDIO_EXTENSIONS, ...SUPPORTED_VIDEO_EXTENSIONS], - } - } - - const baseMimeType = mimeType.split(';')[0].trim() - const allowedMimeTypes = isAudio - ? SUPPORTED_AUDIO_MIME_TYPES[extension as SupportedAudioExtension] - : SUPPORTED_VIDEO_MIME_TYPES[extension as SupportedVideoExtension] - - if (!allowedMimeTypes.includes(baseMimeType)) { - return { - code: 'MIME_TYPE_MISMATCH', - message: `MIME type ${baseMimeType} does not match file extension ${extension}. Expected: ${allowedMimeTypes.join(', ')}`, - supportedTypes: allowedMimeTypes, - } - } - - return null -} diff --git a/apps/sim/lib/workflows/autolayout/constants.ts b/apps/sim/lib/workflows/autolayout/constants.ts index f4a9356d4cb..cdbe021f04e 100644 --- a/apps/sim/lib/workflows/autolayout/constants.ts +++ b/apps/sim/lib/workflows/autolayout/constants.ts @@ -53,11 +53,6 @@ export const ROOT_PADDING_Y = 150 */ export const DEFAULT_LAYOUT_PADDING = { x: 150, y: 150 } -/** - * Margin for overlap detection - */ -export const OVERLAP_MARGIN = 30 - /** * Maximum iterations for overlap resolution */ @@ -78,24 +73,6 @@ export const AUTO_LAYOUT_EXCLUDED_TYPES = new Set([NOTE_BLOCK_TYPE]) */ export const CONTAINER_BLOCK_TYPES = new Set(['loop', 'parallel']) -/** - * Estimated height per subblock when no measured height is available. - * Used as a heuristic for new blocks that haven't been rendered yet. - */ -export const ESTIMATED_SUBBLOCK_HEIGHT = 45 - -/** - * Bottom padding added to estimated block height - */ -export const ESTIMATED_BLOCK_BOTTOM_PADDING = 20 - -/** - * Maximum estimated block height when no measurement is available. - * Prevents wildly over-estimated heights for blocks with many conditional - * subblocks (e.g. agent blocks define ~20 subblocks but only ~5 are visible). - */ -export const MAX_ESTIMATED_BLOCK_HEIGHT = 350 - /** * Default layout options */ diff --git a/apps/sim/lib/workflows/credentials/credential-extractor.ts b/apps/sim/lib/workflows/credentials/credential-extractor.ts index f1d5d661687..4683bda7a6c 100644 --- a/apps/sim/lib/workflows/credentials/credential-extractor.ts +++ b/apps/sim/lib/workflows/credentials/credential-extractor.ts @@ -2,37 +2,11 @@ import { isPlainRecord } from '@sim/utils/object' import { getToolInputParamConfigs } from '@/lib/workflows/search-replace/indexer' import { WORKFLOW_SEARCH_SUBBLOCK_RESOURCE_TYPES } from '@/lib/workflows/search-replace/resources/registry' import { setValueAtPath } from '@/lib/workflows/search-replace/value-walker' -import { - buildCanonicalIndex, - buildSubBlockValues, - evaluateSubBlockCondition, - hasAdvancedValues, - isSubBlockFeatureEnabled, - isSubBlockVisibleForMode, - type SubBlockCondition, -} from '@/lib/workflows/subblocks/visibility' import { parseStoredToolInputValue } from '@/lib/workflows/tool-input/types' import { getBlock } from '@/blocks/registry' import type { SubBlockConfig } from '@/blocks/types' -import { AuthMode } from '@/blocks/types' import type { BlockState, SubBlockState, WorkflowState } from '@/stores/workflows/workflow/types' -// Credential types based on actual patterns in the codebase -enum CredentialType { - OAUTH = 'oauth', - SECRET = 'secret', // password: true (covers API keys, bot tokens, passwords, etc.) -} - -// Type for credential requirement -export interface CredentialRequirement { - type: CredentialType - serviceId?: string // For OAuth (e.g., 'google-drive', 'slack') - label: string // Human-readable label - blockType: string // The block type that requires this - subBlockId: string // The subblock ID for reference - required: boolean -} - /** * Resource-selector types NOT cleared by the workspace rule below. Everything else the resource * registry knows about IS cleared, so the two lists can never drift apart again — the previous @@ -98,121 +72,6 @@ const WORKSPACE_SPECIFIC_FIELDS = new Set([ */ const OPAQUE_CREDENTIAL_BEARING_TYPES: ReadonlySet = new Set(['table']) -/** - * Extract required credentials from a workflow state - * This analyzes all blocks and their subblocks to identify credential requirements - */ -export function extractRequiredCredentials( - state: Partial | null | undefined -): CredentialRequirement[] { - const credentials: CredentialRequirement[] = [] - const seen = new Set() - - if (!state?.blocks) { - return credentials - } - - // Process each block - Object.values(state.blocks).forEach((block: BlockState) => { - if (!block?.type) return - - const blockConfig = getBlock(block.type) - if (!blockConfig) return - - // Add OAuth credential if block has OAuth auth mode - if (blockConfig.authMode === AuthMode.OAuth) { - const blockName = blockConfig.name || block.type - const key = `oauth-${block.type}` - - if (!seen.has(key)) { - seen.add(key) - credentials.push({ - type: CredentialType.OAUTH, - serviceId: block.type, - label: `Credential for ${blockName}`, - blockType: block.type, - subBlockId: 'oauth', - required: true, - }) - } - } - - // Process password fields (API keys, tokens, etc) - blockConfig.subBlocks?.forEach((subBlockConfig: SubBlockConfig) => { - if (!isSubBlockVisible(block, subBlockConfig)) return - if (!subBlockConfig.password) return - - const blockName = blockConfig.name || block.type - const suffix = block?.triggerMode ? ' Trigger' : '' - const fieldLabel = subBlockConfig.title || formatFieldName(subBlockConfig.id) - const key = `secret-${block.type}-${subBlockConfig.id}-${block?.triggerMode ? 'trigger' : 'default'}` - - if (!seen.has(key)) { - seen.add(key) - credentials.push({ - type: CredentialType.SECRET, - label: `${fieldLabel} for ${blockName}${suffix}`, - blockType: block.type, - subBlockId: subBlockConfig.id, - required: subBlockConfig.required !== false, - }) - } - }) - }) - - /** Helper to check visibility, respecting mode and conditions */ - function isSubBlockVisible(block: BlockState, subBlockConfig: SubBlockConfig): boolean { - if (!isSubBlockFeatureEnabled(subBlockConfig)) return false - - const values = buildSubBlockValues(block?.subBlocks || {}) - const blockConfig = getBlock(block.type) - const blockSubBlocks = blockConfig?.subBlocks || [] - const canonicalIndex = buildCanonicalIndex(blockSubBlocks) - const effectiveAdvanced = - (block?.advancedMode ?? false) || hasAdvancedValues(blockSubBlocks, values, canonicalIndex) - const canonicalModeOverrides = block.data?.canonicalModes - - if (subBlockConfig.mode === 'trigger' && !block?.triggerMode) return false - if (block?.triggerMode && subBlockConfig.mode && subBlockConfig.mode !== 'trigger') return false - - if ( - !isSubBlockVisibleForMode( - subBlockConfig, - effectiveAdvanced, - canonicalIndex, - values, - canonicalModeOverrides - ) - ) { - return false - } - - return evaluateSubBlockCondition(subBlockConfig.condition as SubBlockCondition, values) - } - - // Sort: OAuth first, then secrets, alphabetically within each type - credentials.sort((a, b) => { - if (a.type !== b.type) { - return a.type === CredentialType.OAUTH ? -1 : 1 - } - return a.label.localeCompare(b.label) - }) - - return credentials -} - -/** - * Format field name to be human-readable - */ -function formatFieldName(fieldName: string): string { - return fieldName - .replace(/[_-]/g, ' ') - .replace(/([a-z])([A-Z])/g, '$1 $2') - .split(' ') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(' ') -} - interface MutableSubBlockState extends Omit { value: unknown } @@ -452,13 +311,3 @@ export function sanitizeWorkflowForSharing( return sanitized } - -/** - * Sanitize workflow state for templates (removes credentials and workspace data) - * Wrapper for backward compatibility - */ -export function sanitizeCredentials( - state: Partial | null | undefined -): SanitizedWorkflowState { - return sanitizeWorkflowForSharing(state, { preserveEnvVars: false }) -} diff --git a/apps/sim/lib/workflows/custom-blocks/operations.ts b/apps/sim/lib/workflows/custom-blocks/operations.ts index 4056e4db2c4..b934d6428b0 100644 --- a/apps/sim/lib/workflows/custom-blocks/operations.ts +++ b/apps/sim/lib/workflows/custom-blocks/operations.ts @@ -264,12 +264,6 @@ export async function getCustomBlockWithInputsByWorkflowId( return row ? hydrateCustomBlockRow(row) : null } -/** Fetch a single custom block row by id. */ -export async function getCustomBlockById(id: string) { - const [row] = await db.select().from(customBlock).where(eq(customBlock.id, id)).limit(1) - return row ?? null -} - /** * Org + source-workspace context for manage (edit/delete) authorization. Managing * a block is gated on admin of its SOURCE workflow's workspace — the same workspace diff --git a/apps/sim/lib/workflows/dynamic-handle-topology.ts b/apps/sim/lib/workflows/dynamic-handle-topology.ts index 91e05b5cd2b..00feb4b6c0a 100644 --- a/apps/sim/lib/workflows/dynamic-handle-topology.ts +++ b/apps/sim/lib/workflows/dynamic-handle-topology.ts @@ -24,12 +24,6 @@ function parseStructuredValue(value: unknown): unknown[] | null { return Array.isArray(value) ? value : null } -export function isDynamicHandleBlockType( - type: string | undefined -): type is 'condition' | 'router_v2' { - return type === 'condition' || type === 'router_v2' -} - export function getDynamicHandleSubblockId( blockType: string | undefined ): 'conditions' | 'routes' | null { diff --git a/apps/sim/lib/workflows/executor/execution-events.ts b/apps/sim/lib/workflows/executor/execution-events.ts index 45cd90ca3a3..e13003fe1f8 100644 --- a/apps/sim/lib/workflows/executor/execution-events.ts +++ b/apps/sim/lib/workflows/executor/execution-events.ts @@ -345,17 +345,6 @@ export function encodeSSEEvent(event: ExecutionEvent): Uint8Array { return new TextEncoder().encode(formatSSEEvent(event)) } -/** - * Options for creating SSE execution callbacks - */ -interface SSECallbackOptions { - executionId: string - workflowId: string - controller: ReadableStreamDefaultController - isStreamClosed: () => boolean - setStreamClosed: () => void -} - /** * Creates execution callbacks using a provided event sink. */ @@ -554,25 +543,3 @@ export function createExecutionCallbacks(options: { onChildWorkflowInstanceReady, } } - -/** - * Creates SSE callbacks for workflow execution streaming - */ -export function createSSECallbacks(options: SSECallbackOptions) { - const { executionId, workflowId, controller, isStreamClosed, setStreamClosed } = options - - const sendEvent = (event: ExecutionEvent) => { - if (isStreamClosed()) return - try { - controller.enqueue(encodeSSEEvent(event)) - } catch { - setStreamClosed() - } - } - - return createExecutionCallbacks({ - executionId, - workflowId, - sendEvent, - }) -} diff --git a/apps/sim/lib/workflows/operations/deployment-utils.ts b/apps/sim/lib/workflows/operations/deployment-utils.ts index 76bab43f807..020576b1916 100644 --- a/apps/sim/lib/workflows/operations/deployment-utils.ts +++ b/apps/sim/lib/workflows/operations/deployment-utils.ts @@ -6,47 +6,6 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store' const logger = createLogger('DeploymentUtils') -interface InputField { - name: string - type: string -} - -/** - * Gets the input format from the Start block - * Returns an array of field definitions with name and type - */ -export function getStartBlockInputFormat(): InputField[] { - try { - const candidates = resolveStartCandidates(useWorkflowStore.getState().blocks, { - execution: 'api', - }) - - const targetCandidate = - candidates.find((candidate) => candidate.path === StartBlockPath.UNIFIED) || - candidates.find((candidate) => candidate.path === StartBlockPath.SPLIT_API) || - candidates.find((candidate) => candidate.path === StartBlockPath.SPLIT_INPUT) || - candidates.find((candidate) => candidate.path === StartBlockPath.LEGACY_STARTER) - - const targetBlock = targetCandidate?.block - - if (targetBlock) { - const inputFormat = useSubBlockStore.getState().getValue(targetBlock.id, 'inputFormat') - if (inputFormat && Array.isArray(inputFormat)) { - return inputFormat - .map((field: { name?: string; type?: string }) => ({ - name: field.name || '', - type: field.type || 'string', - })) - .filter((field) => field.name) - } - } - } catch (error) { - logger.warn('Error getting start block input format:', error) - } - - return [] -} - /** * Gets the input format example for a workflow's API deployment * Returns the -d flag with example data if inputs exist, empty string otherwise diff --git a/apps/sim/lib/workflows/schedules/utils.ts b/apps/sim/lib/workflows/schedules/utils.ts index ab30c80bb27..11791e90e8f 100644 --- a/apps/sim/lib/workflows/schedules/utils.ts +++ b/apps/sim/lib/workflows/schedules/utils.ts @@ -1,6 +1,6 @@ import { createLogger } from '@sim/logger' import { getErrorMessage, toError } from '@sim/utils/errors' -import { formatDateTime, getTimezoneAbbreviation } from '@sim/utils/formatting' +import { getTimezoneAbbreviation } from '@sim/utils/formatting' import { Cron } from 'croner' import cronstrue from 'cronstrue' @@ -487,157 +487,3 @@ const REVERSE_DAY_MAP: Record = { } export type ScheduleType = 'minutes' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'custom' - -export interface CronFormState { - scheduleType: ScheduleType - minutesInterval: string - hourlyMinute: string - dailyTime: string - weeklyDay: string - weeklyDayTime: string - monthlyDay: string - monthlyTime: string - cronExpression: string -} - -const CRON_FORM_DEFAULTS: CronFormState = { - scheduleType: 'custom', - minutesInterval: '15', - hourlyMinute: '0', - dailyTime: '09:00', - weeklyDay: 'MON', - weeklyDayTime: '09:00', - monthlyDay: '1', - monthlyTime: '09:00', - cronExpression: '', -} - -/** - * Reverse-parses a cron expression into schedule type and form field values. - * Used to pre-populate the schedule modal when editing an existing schedule. - */ -export function parseCronToScheduleType(cronExpression: string | null | undefined): CronFormState { - if (!cronExpression?.trim()) { - return { ...CRON_FORM_DEFAULTS } - } - - const parts = cronExpression.trim().split(/\s+/) - if (parts.length !== 5) { - return { ...CRON_FORM_DEFAULTS, cronExpression } - } - - const [minute, hour, dayOfMonth, month, dayOfWeek] = parts - const pad = (n: number) => String(n).padStart(2, '0') - - if ( - minute.startsWith('*/') && - hour === '*' && - dayOfMonth === '*' && - month === '*' && - dayOfWeek === '*' - ) { - const interval = Number.parseInt(minute.slice(2), 10) - if (!Number.isNaN(interval) && interval > 0) { - return { ...CRON_FORM_DEFAULTS, scheduleType: 'minutes', minutesInterval: String(interval) } - } - } - - const m = Number.parseInt(minute, 10) - const h = Number.parseInt(hour, 10) - - if ( - !Number.isNaN(m) && - hour === '*' && - dayOfMonth === '*' && - month === '*' && - dayOfWeek === '*' - ) { - return { ...CRON_FORM_DEFAULTS, scheduleType: 'hourly', hourlyMinute: String(m) } - } - - if ( - !Number.isNaN(m) && - !Number.isNaN(h) && - dayOfMonth === '*' && - month === '*' && - dayOfWeek === '*' - ) { - return { ...CRON_FORM_DEFAULTS, scheduleType: 'daily', dailyTime: `${pad(h)}:${pad(m)}` } - } - - if ( - !Number.isNaN(m) && - !Number.isNaN(h) && - dayOfMonth === '*' && - month === '*' && - dayOfWeek !== '*' - ) { - const dow = Number.parseInt(dayOfWeek, 10) - const dayName = REVERSE_DAY_MAP[dow] - if (dayName) { - return { - ...CRON_FORM_DEFAULTS, - scheduleType: 'weekly', - weeklyDay: dayName, - weeklyDayTime: `${pad(h)}:${pad(m)}`, - } - } - } - - if ( - !Number.isNaN(m) && - !Number.isNaN(h) && - dayOfMonth !== '*' && - month === '*' && - dayOfWeek === '*' - ) { - const dom = Number.parseInt(dayOfMonth, 10) - if (!Number.isNaN(dom) && dom >= 1 && dom <= 31) { - return { - ...CRON_FORM_DEFAULTS, - scheduleType: 'monthly', - monthlyDay: String(dom), - monthlyTime: `${pad(h)}:${pad(m)}`, - } - } - } - - return { ...CRON_FORM_DEFAULTS, cronExpression } -} - -/** - * Format schedule information for display - */ -export const getScheduleInfo = ( - cronExpression: string | null, - nextRunAt: string | null, - lastRanAt: string | null, - scheduleType?: string | null, - timezone?: string | null -): { - scheduleTiming: string - nextRunFormatted: string | null - lastRunFormatted: string | null -} => { - if (!nextRunAt) { - return { - scheduleTiming: 'Unknown schedule', - nextRunFormatted: null, - lastRunFormatted: null, - } - } - - let scheduleTiming = 'Unknown schedule' - - if (cronExpression) { - scheduleTiming = parseCronToHumanReadable(cronExpression, timezone || undefined) - } else if (scheduleType) { - scheduleTiming = `${scheduleType.charAt(0).toUpperCase() + scheduleType.slice(1)}` - } - - return { - scheduleTiming, - nextRunFormatted: formatDateTime(new Date(nextRunAt)), - lastRunFormatted: lastRanAt ? formatDateTime(new Date(lastRanAt)) : null, - } -} diff --git a/apps/sim/lib/workflows/search-replace/resources/resolvers.ts b/apps/sim/lib/workflows/search-replace/resources/resolvers.ts index a91091289fa..96464f3bd89 100644 --- a/apps/sim/lib/workflows/search-replace/resources/resolvers.ts +++ b/apps/sim/lib/workflows/search-replace/resources/resolvers.ts @@ -5,7 +5,6 @@ import type { WorkflowSearchResourceMeta, WorkflowSearchValuePath, } from '@/lib/workflows/search-replace/types' -import type { SelectorContext } from '@/hooks/selectors/types' /** * Which kind wins when two matches cover the same span. Exported so the @@ -67,16 +66,6 @@ export function getWorkflowSearchMatchResourceGroupKey(match: WorkflowSearchMatc ) } -export function selectorContextMatches( - left: SelectorContext | undefined, - right: SelectorContext | undefined -): boolean { - return ( - stableStringifyWorkflowSearchValue(left ?? {}) === - stableStringifyWorkflowSearchValue(right ?? {}) - ) -} - export function replacementOptionMatchesResourceMatch( option: WorkflowSearchReplacementOption, match: WorkflowSearchMatch diff --git a/apps/sim/lib/workflows/subblocks/visibility.ts b/apps/sim/lib/workflows/subblocks/visibility.ts index ef588530a03..c5fc887f086 100644 --- a/apps/sim/lib/workflows/subblocks/visibility.ts +++ b/apps/sim/lib/workflows/subblocks/visibility.ts @@ -367,21 +367,6 @@ export function reindexToolCanonicalModes( return reindexCanonicalModesByPosition(newIndexByOldIndex, overrides) } -/** - * Check if a block has any standalone advanced-only fields (not part of canonical pairs). - * These require the block-level advanced mode toggle to be visible. - */ -export function hasStandaloneAdvancedFields( - subBlocks: SubBlockConfig[], - canonicalIndex: CanonicalIndex -): boolean { - for (const subBlock of subBlocks) { - if (!isStandaloneAdvancedMode(subBlock.mode)) continue - if (!canonicalIndex.canonicalIdBySubBlockId[subBlock.id]) return true - } - return false -} - /** * True for the modes that make a field advanced-only when it is not part of a * canonical basic/advanced pair: a standalone `advanced` field, or a standalone diff --git a/apps/sim/lib/workflows/triggers/trigger-utils.ts b/apps/sim/lib/workflows/triggers/trigger-utils.ts index 0ab01bb7fda..b333ea4b12a 100644 --- a/apps/sim/lib/workflows/triggers/trigger-utils.ts +++ b/apps/sim/lib/workflows/triggers/trigger-utils.ts @@ -26,63 +26,6 @@ export function hasValidStartBlockInState(state: WorkflowState | null | undefine return !!startBlock } -interface TriggerInfo { - id: string - name: string - description: string - icon: React.ComponentType<{ className?: string }> - color: string - category: 'core' | 'integration' - enableTriggerMode?: boolean -} - -/** - * Get all blocks that can act as triggers - * This includes both dedicated trigger blocks and tools with trigger capabilities - */ -export function getAllTriggerBlocks(): TriggerInfo[] { - const allBlocks = getAllBlocks() - const triggers: TriggerInfo[] = [] - - for (const block of allBlocks) { - // Skip hidden blocks - if (block.hideFromToolbar) continue - - // Check if it's a core trigger block (category: 'triggers') - if (block.category === 'triggers') { - triggers.push({ - id: block.type, - name: block.name, - description: block.description, - icon: block.icon, - color: block.bgColor, - category: 'core', - enableTriggerMode: hasTriggerCapability(block), - }) - } - // Check if it's a tool with trigger capability (has trigger-config subblock) - else if (hasTriggerCapability(block)) { - triggers.push({ - id: block.type, - name: block.name, - description: block.description.replace(' or trigger workflows from ', ', trigger from '), - icon: block.icon, - color: block.bgColor, - category: 'integration', - enableTriggerMode: true, - }) - } - } - - // Sort: core triggers first, then integration triggers, alphabetically within each category - return triggers.sort((a, b) => { - if (a.category !== b.category) { - return a.category === 'core' ? -1 : 1 - } - return a.name.localeCompare(b.name) - }) -} - /** * Check if a block has trigger capability (contains trigger mode subblocks) */ @@ -112,16 +55,6 @@ export function getTriggersForSidebar(): BlockConfig[] { }) } -/** - * Get the proper display name for a trigger block in the UI - */ -export function getTriggerDisplayName(blockType: string): string { - const block = getBlock(blockType) - if (!block) return blockType - - return block.name -} - /** * Groups triggers by their immediate downstream blocks to identify disjoint paths */ diff --git a/apps/sim/lib/workflows/triggers/triggers.ts b/apps/sim/lib/workflows/triggers/triggers.ts index 45a8dd0e533..4bbe304c53f 100644 --- a/apps/sim/lib/workflows/triggers/triggers.ts +++ b/apps/sim/lib/workflows/triggers/triggers.ts @@ -125,10 +125,6 @@ export function classifyStartBlock(block: T): StartBlock return classifyStartBlockType(block.type, { category, triggerModeEnabled }) } -export function isLegacyStartPath(path: StartBlockPath): boolean { - return path !== StartBlockPath.UNIFIED -} - function toEntries(blocks: Record | T[]): Array<[string, T]> { if (Array.isArray(blocks)) { return blocks.map((block, index) => { diff --git a/apps/sim/lib/workspaces/naming.ts b/apps/sim/lib/workspaces/naming.ts index 02284ca7093..902d82d34bf 100644 --- a/apps/sim/lib/workspaces/naming.ts +++ b/apps/sim/lib/workspaces/naming.ts @@ -2,7 +2,6 @@ * Utility functions for generating names for workspaces and folders */ -import { randomItem } from '@sim/utils/random' import { requestJson } from '@/lib/api/client/request' import { type FolderApi, listFoldersContract } from '@/lib/api/contracts/folders' @@ -10,59 +9,6 @@ interface NameableEntity { name: string } -const WORKSPACE_NOUNS = [ - 'Pulsar', - 'Quasar', - 'Nebula', - 'Nova', - 'Cosmos', - 'Orion', - 'Vega', - 'Zenith', - 'Horizon', - 'Eclipse', - 'Aurora', - 'Photon', - 'Vertex', - 'Nexus', - 'Solaris', - 'Andromeda', - 'Phoenix', - 'Polaris', - 'Sirius', - 'Altair', - 'Meridian', - 'Titan', - 'Apex', - 'Aether', - 'Voyager', - 'Beacon', - 'Sentinel', - 'Pioneer', - 'Equinox', - 'Solstice', - 'Corona', - 'Stellar', - 'Helix', - 'Prism', - 'Axiom', - 'Boson', - 'Cygnus', - 'Draco', - 'Lyra', - 'Aquila', - 'Perseus', - 'Pegasus', - 'Triton', - 'Callisto', - 'Europa', - 'Oberon', - 'Tachyon', - 'Neutron', - 'Graviton', - 'Parallax', -] as const - /** * Generates the next incremental name for entities following pattern: "{prefix} {number}" * @@ -86,13 +32,6 @@ export function generateIncrementalName( return `${prefix} ${nextNumber}` } -/** - * Generates a random cosmos-themed workspace name - */ -export function generateWorkspaceName(): string { - return randomItem(WORKSPACE_NOUNS) -} - async function fetchWorkspaceFolders(workspaceId: string): Promise { const { folders } = await requestJson(listFoldersContract, { query: { workspaceId }, diff --git a/apps/sim/lib/workspaces/organization/utils.ts b/apps/sim/lib/workspaces/organization/utils.ts index 43f5990b29c..49c51f03bfe 100644 --- a/apps/sim/lib/workspaces/organization/utils.ts +++ b/apps/sim/lib/workspaces/organization/utils.ts @@ -77,14 +77,6 @@ export function generateSlug(name: string): string { .replace(/^-|-$/g, '') // Remove leading and trailing hyphens } -/** - * Validate organization slug format - */ -export function validateSlug(slug: string): boolean { - const slugRegex = /^[a-z0-9-_]+$/ - return slugRegex.test(slug) -} - /** * Validate email format */ diff --git a/apps/sim/providers/pi-providers.ts b/apps/sim/providers/pi-providers.ts index 1053e911ce8..363c205d921 100644 --- a/apps/sim/providers/pi-providers.ts +++ b/apps/sim/providers/pi-providers.ts @@ -6,14 +6,6 @@ import { } from '@/providers/pi-provider-configs' import type { BYOKProviderId } from '@/tools/types' -/** - * Shared provider and model bridge for the Pi model picker, executor, host SDK, - * and E2B CLI. - */ -export const PI_SUPPORTED_PROVIDER_IDS: readonly PiSupportedProvider[] = PI_PROVIDER_CONFIGS.map( - ({ id }) => id -) - const PI_PROVIDER_CONFIG_BY_ID = new Map( PI_PROVIDER_CONFIGS.map((config) => [config.id, config]) ) diff --git a/apps/sim/providers/registry.ts b/apps/sim/providers/registry.ts index 529d7c0b09f..a1d70caa046 100644 --- a/apps/sim/providers/registry.ts +++ b/apps/sim/providers/registry.ts @@ -1,5 +1,4 @@ import { createLogger } from '@sim/logger' -import { getErrorMessage } from '@sim/utils/errors' import { anthropicProvider } from '@/providers/anthropic' import { azureAnthropicProvider } from '@/providers/azure-anthropic' import { azureOpenAIProvider } from '@/providers/azure-openai' @@ -67,18 +66,3 @@ export async function getProviderExecutor( } return provider } - -export async function initializeProviders(): Promise { - for (const [id, provider] of Object.entries(providerRegistry)) { - if (provider.initialize) { - try { - await provider.initialize() - logger.info(`Initialized provider: ${id}`) - } catch (error) { - logger.error(`Failed to initialize ${id} provider`, { - error: getErrorMessage(error, 'Unknown error'), - }) - } - } - } -} diff --git a/apps/sim/scripts/pi-sandbox-packages.ts b/apps/sim/scripts/pi-sandbox-packages.ts index a6646201ccd..a4326b682d7 100644 --- a/apps/sim/scripts/pi-sandbox-packages.ts +++ b/apps/sim/scripts/pi-sandbox-packages.ts @@ -58,13 +58,6 @@ export const PI_NODE_VERSION_ASSERT = /** Fails the build loudly if the sandbox does not contain the repository's Bun version. */ export const PI_BUN_VERSION_ASSERT = `test "$(bun --version)" = "${PI_BUN_VERSION}"` -/** - * The review tools run `python3 /workspace/sim-review-tools.py` - * (`cloud-review-tools.ts:15`). E2B's `code-interpreter-v1` base ships Python, so - * only the Daytona image has to provide it explicitly. - */ -export const PI_REQUIRES_PYTHON3 = true - /** * vCPU and RAM for the Pi sandbox, shared for the same reason the package lists * are: the two providers had already drifted here. Daytona asked for 4 CPU / 8 GB diff --git a/apps/sim/stores/chat/utils.ts b/apps/sim/stores/chat/utils.ts index 037b65da220..390253b6012 100644 --- a/apps/sim/stores/chat/utils.ts +++ b/apps/sim/stores/chat/utils.ts @@ -39,14 +39,6 @@ const calculateDefaultPosition = (): ChatPosition => { return { x, y } } -/** - * Get the default chat dimensions - */ -export const getDefaultChatDimensions = () => ({ - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, -}) - /** * Calculate constrained position ensuring chat stays within bounds */ diff --git a/apps/sim/stores/folders/store.ts b/apps/sim/stores/folders/store.ts index 5f4115418fb..90506e37cf5 100644 --- a/apps/sim/stores/folders/store.ts +++ b/apps/sim/stores/folders/store.ts @@ -294,9 +294,3 @@ export const useFolderStore = create()( { name: 'folder-store' } ) ) - -export const useIsWorkflowSelected = (workflowId: string) => - useFolderStore((state) => state.selectedWorkflows.has(workflowId)) - -export const useIsFolderSelected = (folderId: string) => - useFolderStore((state) => state.selectedFolders.has(folderId)) diff --git a/apps/sim/tools/airweave/types.ts b/apps/sim/tools/airweave/types.ts index 2f3b79ac651..d2e1ab32e88 100644 --- a/apps/sim/tools/airweave/types.ts +++ b/apps/sim/tools/airweave/types.ts @@ -27,15 +27,6 @@ export const AIRWEAVE_SEARCH_RESULT_OUTPUT_PROPERTIES = { url: { type: 'string', description: 'URL to the original content', optional: true }, } as const satisfies Record -/** - * Complete search result output definition. - */ -export const AIRWEAVE_SEARCH_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Search result item with content and metadata', - properties: AIRWEAVE_SEARCH_RESULT_OUTPUT_PROPERTIES, -} - /** * Parameters for Airweave search requests. */ diff --git a/apps/sim/tools/azure_devops/utils.ts b/apps/sim/tools/azure_devops/utils.ts index b0955498554..9b59d7f5651 100644 --- a/apps/sim/tools/azure_devops/utils.ts +++ b/apps/sim/tools/azure_devops/utils.ts @@ -3,9 +3,6 @@ import type { AzureDevOpsComment, AzureDevOpsWorkItem } from '@/tools/azure_devo /** States for Azure DevOps Basic process work items (Issue, Task, Epic). */ export const AZURE_DEVOPS_BASIC_WORK_ITEM_STATES = ['To Do', 'Doing', 'Done'] as const -/** Work item types for Azure DevOps Basic process. */ -export const AZURE_DEVOPS_BASIC_WORK_ITEM_TYPES = ['Issue', 'Task', 'Epic'] as const - export type AzureDevOpsJsonPatchOp = { op: string path: string diff --git a/apps/sim/tools/calcom/types.ts b/apps/sim/tools/calcom/types.ts index def0cdee322..b7087f004cf 100644 --- a/apps/sim/tools/calcom/types.ts +++ b/apps/sim/tools/calcom/types.ts @@ -272,82 +272,6 @@ export const SCHEDULE_DATA_OUTPUT_PROPERTIES = { overrides: OVERRIDES_OUTPUT, } as const satisfies Record -/** - * Common event type data output properties - */ -export const EVENT_TYPE_DATA_OUTPUT_PROPERTIES = { - id: { type: 'number', description: 'Event type ID' }, - title: { type: 'string', description: 'Event type title' }, - slug: { type: 'string', description: 'URL-friendly slug' }, - description: { type: 'string', description: 'Event type description' }, - lengthInMinutes: { type: 'number', description: 'Duration in minutes' }, - slotInterval: { type: 'number', description: 'Minutes between available slots' }, - minimumBookingNotice: { type: 'number', description: 'Minimum advance notice in minutes' }, - beforeEventBuffer: { type: 'number', description: 'Buffer time before event in minutes' }, - afterEventBuffer: { type: 'number', description: 'Buffer time after event in minutes' }, - scheduleId: { type: 'number', description: 'Associated schedule ID' }, - disableGuests: { type: 'boolean', description: 'Whether guest invites are disabled' }, - locations: { - type: 'array', - description: 'Meeting location options', - items: { - type: 'object', - properties: { - type: { - type: 'string', - description: 'Location type (address, link, integration, phone, etc.)', - }, - address: { - type: 'string', - description: 'Physical address (for address type)', - optional: true, - }, - link: { type: 'string', description: 'Meeting URL (for link type)', optional: true }, - phone: { type: 'string', description: 'Phone number (for phone type)', optional: true }, - integration: { - type: 'string', - description: 'Integration name (for integration type)', - optional: true, - }, - public: { - type: 'boolean', - description: 'Whether location is publicly visible', - optional: true, - }, - }, - }, - }, - bookingFields: { - type: 'array', - description: 'Custom booking form fields', - items: { - type: 'object', - properties: { - type: { - type: 'string', - description: 'Field type (name, email, phone, text, select, etc.)', - }, - slug: { type: 'string', description: 'Field identifier', optional: true }, - label: { type: 'string', description: 'Field label' }, - required: { type: 'boolean', description: 'Whether field is required', optional: true }, - placeholder: { type: 'string', description: 'Placeholder text', optional: true }, - options: { - type: 'array', - description: 'Options for select/multiselect fields', - optional: true, - }, - hidden: { type: 'boolean', description: 'Whether field is hidden', optional: true }, - isDefault: { - type: 'boolean', - description: 'Whether this is a system default field', - optional: true, - }, - }, - }, - }, - metadata: { type: 'json', description: 'Custom metadata (dynamic key-value pairs)' }, -} as const satisfies Record - export interface CalcomCreateEventTypeParams { accessToken: string title: string diff --git a/apps/sim/tools/confluence/types.ts b/apps/sim/tools/confluence/types.ts index 560ee3c76d0..20d89f93a88 100644 --- a/apps/sim/tools/confluence/types.ts +++ b/apps/sim/tools/confluence/types.ts @@ -63,24 +63,6 @@ export const DETAILED_VERSION_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete detailed version object output definition. - */ -export const DETAILED_VERSION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Detailed version information', - properties: DETAILED_VERSION_OUTPUT_PROPERTIES, -} - -/** - * Complete version object output definition. - */ -export const VERSION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Version information', - properties: VERSION_OUTPUT_PROPERTIES, -} - /** * Page item properties from Confluence API v2. * Based on GET /wiki/api/v2/pages response structure. @@ -113,18 +95,6 @@ export const PAGE_OUTPUT: OutputProperty = { properties: PAGE_ITEM_PROPERTIES, } -/** - * Pages array output definition for list endpoints. - */ -export const PAGES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Confluence pages', - items: { - type: 'object', - properties: PAGE_ITEM_PROPERTIES, - }, -} - /** * Space description object properties. * Based on Confluence API v2 space description structure. @@ -162,15 +132,6 @@ export const SPACE_ITEM_PROPERTIES = { }, } as const satisfies Record -/** - * Complete space object output definition. - */ -export const SPACE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Confluence space object', - properties: SPACE_ITEM_PROPERTIES, -} - /** * Spaces array output definition for list endpoints. */ @@ -221,16 +182,6 @@ export const CONTENT_BODY_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete body object output definition for pages and blog posts. - */ -export const CONTENT_BODY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Page or blog post body content in requested format(s)', - properties: CONTENT_BODY_OUTPUT_PROPERTIES, - optional: true, -} - /** * Comment body object properties. * Based on Confluence API v2 comment body structure. @@ -447,14 +398,6 @@ export const SEARCH_RESULTS_OUTPUT: OutputProperty = { }, } -/** - * Pagination links properties for list responses. - */ -export const PAGINATION_LINKS_PROPERTIES = { - next: { type: 'string', description: 'URL to fetch the next page of results', optional: true }, - base: { type: 'string', description: 'Base URL for the API', optional: true }, -} as const satisfies Record - /** * Common timestamp output property. */ @@ -463,14 +406,6 @@ export const TIMESTAMP_OUTPUT: OutputProperty = { description: 'ISO 8601 timestamp of the operation', } -/** - * Common page ID output property. - */ -export const PAGE_ID_OUTPUT: OutputProperty = { - type: 'string', - description: 'Confluence page ID', -} - /** * Common success status output property. */ @@ -479,22 +414,6 @@ export const SUCCESS_OUTPUT: OutputProperty = { description: 'Operation success status', } -/** - * Common deleted status output property. - */ -export const DELETED_OUTPUT: OutputProperty = { - type: 'boolean', - description: 'Deletion status', -} - -/** - * Common URL output property. - */ -export const URL_OUTPUT: OutputProperty = { - type: 'string', - description: 'URL to view in Confluence', -} - export interface ConfluenceRetrieveParams { accessToken: string pageId: string diff --git a/apps/sim/tools/context_dev/types.ts b/apps/sim/tools/context_dev/types.ts index 5bf9b823aa5..e8b580c73bd 100644 --- a/apps/sim/tools/context_dev/types.ts +++ b/apps/sim/tools/context_dev/types.ts @@ -374,15 +374,6 @@ export const BRAND_OUTPUT_PROPERTIES = { 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' }, diff --git a/apps/sim/tools/docusign/types.ts b/apps/sim/tools/docusign/types.ts index e7545f3a3f4..021ee1b81ee 100644 --- a/apps/sim/tools/docusign/types.ts +++ b/apps/sim/tools/docusign/types.ts @@ -52,12 +52,6 @@ export const TEMPLATE_OUTPUT_PROPERTIES = { lastModified: { type: 'string', description: 'ISO 8601 last modified date' }, } as const satisfies Record -export const ENVELOPE_OBJECT_OUTPUT: OutputProperty = { - type: 'object', - description: 'DocuSign envelope', - properties: ENVELOPE_OUTPUT_PROPERTIES, -} - export const ENVELOPES_ARRAY_OUTPUT: OutputProperty = { type: 'array', description: 'Array of DocuSign envelopes', @@ -67,12 +61,6 @@ export const ENVELOPES_ARRAY_OUTPUT: OutputProperty = { }, } -export const RECIPIENT_OBJECT_OUTPUT: OutputProperty = { - type: 'object', - description: 'DocuSign recipient', - properties: RECIPIENT_OUTPUT_PROPERTIES, -} - export const RECIPIENTS_ARRAY_OUTPUT: OutputProperty = { type: 'array', description: 'Array of DocuSign recipients', diff --git a/apps/sim/tools/dropcontact/types.ts b/apps/sim/tools/dropcontact/types.ts index fb64e9a6a14..be385cb9793 100644 --- a/apps/sim/tools/dropcontact/types.ts +++ b/apps/sim/tools/dropcontact/types.ts @@ -15,15 +15,6 @@ export const DROPCONTACT_EMAIL_ITEM_OUTPUT_PROPERTIES = { }, } as const satisfies Record -export const DROPCONTACT_EMAILS_OUTPUT: OutputProperty = { - type: 'array', - description: 'All email addresses found for the contact', - items: { - type: 'object', - properties: DROPCONTACT_EMAIL_ITEM_OUTPUT_PROPERTIES, - }, -} - // Enrich Contact (single-contact async enrichment) export interface DropcontactEnrichContactParams extends DropcontactBaseParams { diff --git a/apps/sim/tools/firecrawl/types.ts b/apps/sim/tools/firecrawl/types.ts index 9fed6555bb5..6f39710d2d4 100644 --- a/apps/sim/tools/firecrawl/types.ts +++ b/apps/sim/tools/firecrawl/types.ts @@ -100,28 +100,6 @@ export const SEARCH_METADATA_OUTPUT: OutputProperty = { properties: SEARCH_METADATA_OUTPUT_PROPERTIES, } -/** - * Output properties for scrape tool response - * Based on POST /v2/scrape response data object - */ -export const SCRAPE_OUTPUT_PROPERTIES = { - markdown: { type: 'string', description: 'Page content converted to clean markdown format' }, - html: { type: 'string', description: 'Processed HTML content of the page', optional: true }, - rawHtml: { type: 'string', description: 'Unprocessed raw HTML content', optional: true }, - links: { - type: 'array', - description: 'Array of links found on the page', - optional: true, - items: { type: 'string', description: 'URL found on the page' }, - }, - screenshot: { - type: 'string', - description: 'Base64-encoded screenshot or URL (expires after 24 hours)', - optional: true, - }, - metadata: PAGE_METADATA_OUTPUT, -} as const satisfies Record - /** * Output properties for crawled page items * Based on GET /v2/crawl/{id} response data[] array items @@ -144,31 +122,6 @@ export const CRAWLED_PAGE_OUTPUT_PROPERTIES = { metadata: CRAWL_METADATA_OUTPUT, } as const satisfies Record -/** - * Complete crawled page output definition - */ -export const CRAWLED_PAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Crawled page data with content and metadata', - properties: CRAWLED_PAGE_OUTPUT_PROPERTIES, -} - -/** - * Output properties for crawl tool response - * Based on GET /v2/crawl/{id} response (completed status) - */ -export const CRAWL_OUTPUT_PROPERTIES = { - pages: { - type: 'array', - description: 'Array of crawled pages with their content and metadata', - items: { - type: 'object', - properties: CRAWLED_PAGE_OUTPUT_PROPERTIES, - }, - }, - total: { type: 'number', description: 'Total number of pages found during crawl' }, -} as const satisfies Record - /** * Output properties for search result items * Based on POST /v2/search response data[] array items @@ -219,73 +172,6 @@ export const SEARCH_RESULT_OUTPUT: OutputProperty = { properties: SEARCH_RESULT_OUTPUT_PROPERTIES, } -/** - * Output properties for search tool response - * Based on POST /v2/search response - */ -export const SEARCH_OUTPUT_PROPERTIES = { - data: { - type: 'array', - description: 'Array of search results with scraped content and metadata', - items: { - type: 'object', - properties: SEARCH_RESULT_OUTPUT_PROPERTIES, - }, - }, -} as const satisfies Record - -/** - * Output properties for map tool response - * Based on POST /v2/map response - */ -export const MAP_OUTPUT_PROPERTIES = { - success: { type: 'boolean', description: 'Whether the mapping operation completed successfully' }, - links: { - type: 'array', - description: 'Array of discovered URLs from the website', - items: { type: 'string', description: 'Discovered URL' }, - }, -} as const satisfies Record - -/** - * Output properties for extract tool response - * Based on GET /v2/extract/{id} response (completed status) - */ -export const EXTRACT_OUTPUT_PROPERTIES = { - success: { type: 'boolean', description: 'Whether the extraction completed successfully' }, - data: { - type: 'object', - description: 'Extracted structured data according to the provided schema or prompt', - }, -} as const satisfies Record - -/** - * Output properties for agent tool response - * Based on GET /v2/agent/{id} response (completed status) - */ -export const AGENT_OUTPUT_PROPERTIES = { - success: { type: 'boolean', description: 'Whether the agent task completed successfully' }, - status: { - type: 'string', - description: 'Current status of the agent job (processing, completed, failed)', - }, - data: { - type: 'object', - description: 'Extracted data from the agent based on the prompt and schema', - }, - expiresAt: { - type: 'string', - description: 'ISO timestamp when the results expire (24 hours after completion)', - optional: true, - }, - sources: { - type: 'array', - description: 'Array of source URLs visited and used by the agent', - optional: true, - items: { type: 'string', description: 'Source URL' }, - }, -} as const satisfies Record - // Common types interface LocationConfig { country?: string diff --git a/apps/sim/tools/github/types.ts b/apps/sim/tools/github/types.ts index 4e756fafdba..ea1474665fe 100644 --- a/apps/sim/tools/github/types.ts +++ b/apps/sim/tools/github/types.ts @@ -169,15 +169,6 @@ export const COMMIT_FILE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete commit file output definition - */ -export const COMMIT_FILE_OUTPUT = { - type: 'object', - description: 'Changed file (diff entry)', - properties: COMMIT_FILE_OUTPUT_PROPERTIES, -} as const satisfies OutputProperty - /** * Output definition for parent commit references */ @@ -187,15 +178,6 @@ export const COMMIT_PARENT_OUTPUT_PROPERTIES = { html_url: { type: 'string', description: 'Parent web URL' }, } as const satisfies Record -/** - * Complete parent commit output definition - */ -export const COMMIT_PARENT_OUTPUT = { - type: 'object', - description: 'Parent commit reference', - properties: COMMIT_PARENT_OUTPUT_PROPERTIES, -} as const satisfies OutputProperty - /** * Output definition for commit summary properties (common across list/search responses) */ @@ -220,15 +202,6 @@ export const SEARCH_REPO_OUTPUT_PROPERTIES = { description: { type: 'string', description: 'Repository description', optional: true }, } as const satisfies Record -/** - * Complete search repository output definition - */ -export const SEARCH_REPO_OUTPUT = { - type: 'object', - description: 'Repository containing the commit', - properties: SEARCH_REPO_OUTPUT_PROPERTIES, -} as const satisfies OutputProperty - /** * Extended repository output properties for V2 tools (full API response) */ @@ -373,15 +346,6 @@ export const BRANCH_OUTPUT_PROPERTIES = { protected: { type: 'boolean', description: 'Whether branch is protected' }, } as const satisfies Record -/** - * Complete branch output definition - */ -export const BRANCH_OUTPUT = { - type: 'object', - description: 'Branch object', - properties: BRANCH_OUTPUT_PROPERTIES, -} as const satisfies OutputProperty - /** * Output definition for git reference objects (created branches) */ @@ -392,15 +356,6 @@ export const GIT_REF_OUTPUT_PROPERTIES = { object: { type: 'json', description: 'Git object with type and sha' }, } as const satisfies Record -/** - * Complete git reference output definition - */ -export const GIT_REF_OUTPUT = { - type: 'object', - description: 'Git reference object', - properties: GIT_REF_OUTPUT_PROPERTIES, -} as const satisfies OutputProperty - /** * Output definition for branch protection settings */ @@ -434,15 +389,6 @@ export const BRANCH_PROTECTION_OUTPUT_PROPERTIES = { required_signatures: { type: 'json', description: 'Signature requirements', optional: true }, } as const satisfies Record -/** - * Complete branch protection output definition - */ -export const BRANCH_PROTECTION_OUTPUT = { - type: 'object', - description: 'Branch protection configuration', - properties: BRANCH_PROTECTION_OUTPUT_PROPERTIES, -} as const satisfies OutputProperty - /** * Output definition for delete branch response */ @@ -509,16 +455,6 @@ export const PROJECT_V2_OUTPUT_PROPERTIES = { shortDescription: { type: 'string', description: 'Short description', optional: true }, } as const satisfies Record -/** - * Extended output definition for V2 project objects (full API response) - */ -export const PROJECT_V2_FULL_OUTPUT_PROPERTIES = { - ...PROJECT_V2_OUTPUT_PROPERTIES, - readme: { type: 'string', description: 'Project readme', optional: true }, - createdAt: { type: 'string', description: 'Creation timestamp' }, - updatedAt: { type: 'string', description: 'Last update timestamp' }, -} as const satisfies Record - /** * Output definition for gist file objects */ diff --git a/apps/sim/tools/google/types.ts b/apps/sim/tools/google/types.ts index 50a9ac8fbb2..642cb7367dd 100644 --- a/apps/sim/tools/google/types.ts +++ b/apps/sim/tools/google/types.ts @@ -53,15 +53,6 @@ export const GOOGLE_SEARCH_RESULT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete search result item output definition - */ -export const GOOGLE_SEARCH_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'A single search result from Google Custom Search', - properties: GOOGLE_SEARCH_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for search information metadata. * @see https://developers.google.com/custom-search/v1/reference/rest/v1/Search#SearchInformation @@ -76,15 +67,6 @@ export const GOOGLE_SEARCH_INFORMATION_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete search information output definition - */ -export const GOOGLE_SEARCH_INFORMATION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Information about the search query and results', - properties: GOOGLE_SEARCH_INFORMATION_OUTPUT_PROPERTIES, -} - export interface GoogleSearchParams { query: string apiKey: string diff --git a/apps/sim/tools/incidentio/types.ts b/apps/sim/tools/incidentio/types.ts index 8557c92573d..007e1568d62 100644 --- a/apps/sim/tools/incidentio/types.ts +++ b/apps/sim/tools/incidentio/types.ts @@ -99,15 +99,6 @@ export const INCIDENTIO_INCIDENT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete incident output definition - */ -export const INCIDENTIO_INCIDENT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io incident object', - properties: INCIDENTIO_INCIDENT_OUTPUT_PROPERTIES, -} - /** * Output definition for action objects. * @see https://api-docs.incident.io/#tag/Actions @@ -129,15 +120,6 @@ export const INCIDENTIO_ACTION_OUTPUT_PROPERTIES = { completed_at: { type: 'string', description: 'When the action was completed', optional: true }, } as const satisfies Record -/** - * Complete action output definition - */ -export const INCIDENTIO_ACTION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io action object', - properties: INCIDENTIO_ACTION_OUTPUT_PROPERTIES, -} - /** * Output definition for follow-up objects. * @see https://api-docs.incident.io/#tag/Follow-ups @@ -159,15 +141,6 @@ export const INCIDENTIO_FOLLOW_UP_OUTPUT_PROPERTIES = { completed_at: { type: 'string', description: 'When the follow-up was completed', optional: true }, } as const satisfies Record -/** - * Complete follow-up output definition - */ -export const INCIDENTIO_FOLLOW_UP_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io follow-up object', - properties: INCIDENTIO_FOLLOW_UP_OUTPUT_PROPERTIES, -} - /** * Output definition for workflow objects. * @see https://api-docs.incident.io/#tag/Workflows @@ -202,15 +175,6 @@ export const INCIDENTIO_WORKFLOW_OUTPUT_PROPERTIES = { shortform: { type: 'string', description: 'Workflow shortform identifier', optional: true }, } as const satisfies Record -/** - * Complete workflow output definition - */ -export const INCIDENTIO_WORKFLOW_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io workflow object', - properties: INCIDENTIO_WORKFLOW_OUTPUT_PROPERTIES, -} - /** * Output definition for custom field objects. * @see https://api-docs.incident.io/#tag/Custom-Fields @@ -227,15 +191,6 @@ export const INCIDENTIO_CUSTOM_FIELD_OUTPUT_PROPERTIES = { updated_at: { type: 'string', description: 'When the field was last updated' }, } as const satisfies Record -/** - * Complete custom field output definition - */ -export const INCIDENTIO_CUSTOM_FIELD_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io custom field object', - properties: INCIDENTIO_CUSTOM_FIELD_OUTPUT_PROPERTIES, -} - /** * Output definition for schedule objects. * @see https://api-docs.incident.io/#tag/Schedules @@ -248,15 +203,6 @@ export const INCIDENTIO_SCHEDULE_OUTPUT_PROPERTIES = { updated_at: { type: 'string', description: 'When the schedule was last updated', optional: true }, } as const satisfies Record -/** - * Complete schedule output definition - */ -export const INCIDENTIO_SCHEDULE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io schedule object', - properties: INCIDENTIO_SCHEDULE_OUTPUT_PROPERTIES, -} - /** * Output definition for incident role objects. * @see https://api-docs.incident.io/#tag/Incident-Roles @@ -273,15 +219,6 @@ export const INCIDENTIO_INCIDENT_ROLE_OUTPUT_PROPERTIES = { updated_at: { type: 'string', description: 'When the role was last updated' }, } as const satisfies Record -/** - * Complete incident role output definition - */ -export const INCIDENTIO_INCIDENT_ROLE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Incident.io incident role object', - properties: INCIDENTIO_INCIDENT_ROLE_OUTPUT_PROPERTIES, -} - /** * Pagination output properties */ diff --git a/apps/sim/tools/intercom/types.ts b/apps/sim/tools/intercom/types.ts index 3bca7f82b3e..3cd754b48ea 100644 --- a/apps/sim/tools/intercom/types.ts +++ b/apps/sim/tools/intercom/types.ts @@ -85,15 +85,6 @@ export const INTERCOM_LIST_REFERENCE_OUTPUT_PROPERTIES = { total_count: { type: 'number', description: 'Total number of items' }, } as const satisfies Record -/** - * Complete list reference output definition - */ -export const INTERCOM_LIST_REFERENCE_OUTPUT: OutputProperty = { - type: 'object', - description: 'List reference with metadata', - properties: INTERCOM_LIST_REFERENCE_OUTPUT_PROPERTIES, -} - // Tag Output Properties /** @@ -105,27 +96,6 @@ export const INTERCOM_TAG_OUTPUT_PROPERTIES = { name: { type: 'string', description: 'Name of the tag' }, } as const satisfies Record -/** - * Complete tag output definition - */ -export const INTERCOM_TAG_OUTPUT: OutputProperty = { - type: 'object', - description: 'Intercom tag object', - properties: INTERCOM_TAG_OUTPUT_PROPERTIES, -} - -/** - * Tags array output definition for list endpoints - */ -export const INTERCOM_TAGS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of tag objects', - items: { - type: 'object', - properties: INTERCOM_TAG_OUTPUT_PROPERTIES, - }, -} - // Admin Output Properties /** @@ -175,27 +145,6 @@ export const INTERCOM_ADMIN_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete admin output definition - */ -export const INTERCOM_ADMIN_OUTPUT: OutputProperty = { - type: 'object', - description: 'Intercom admin object', - properties: INTERCOM_ADMIN_OUTPUT_PROPERTIES, -} - -/** - * Admins array output definition for list endpoints - */ -export const INTERCOM_ADMINS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of admin objects', - items: { - type: 'object', - properties: INTERCOM_ADMIN_OUTPUT_PROPERTIES, - }, -} - // Contact Output Properties /** @@ -317,27 +266,6 @@ export const INTERCOM_CONTACT_OUTPUT_PROPERTIES = { social_profiles: INTERCOM_SOCIAL_PROFILES_OUTPUT, } as const satisfies Record -/** - * Complete contact output definition - */ -export const INTERCOM_CONTACT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Intercom contact object', - properties: INTERCOM_CONTACT_OUTPUT_PROPERTIES, -} - -/** - * Contacts array output definition for list/search endpoints - */ -export const INTERCOM_CONTACTS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of contact objects', - items: { - type: 'object', - properties: INTERCOM_CONTACT_OUTPUT_PROPERTIES, - }, -} - // Company Output Properties /** @@ -460,27 +388,6 @@ export const INTERCOM_COMPANY_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete company output definition - */ -export const INTERCOM_COMPANY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Intercom company object', - properties: INTERCOM_COMPANY_OUTPUT_PROPERTIES, -} - -/** - * Companies array output definition for list endpoints - */ -export const INTERCOM_COMPANIES_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of company objects', - items: { - type: 'object', - properties: INTERCOM_COMPANY_OUTPUT_PROPERTIES, - }, -} - // Conversation Output Properties /** @@ -835,27 +742,6 @@ export const INTERCOM_CONVERSATION_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete conversation output definition - */ -export const INTERCOM_CONVERSATION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Intercom conversation object', - properties: INTERCOM_CONVERSATION_OUTPUT_PROPERTIES, -} - -/** - * Conversations array output definition for list/search endpoints - */ -export const INTERCOM_CONVERSATIONS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of conversation objects', - items: { - type: 'object', - properties: INTERCOM_CONVERSATION_OUTPUT_PROPERTIES, - }, -} - // Ticket Output Properties /** @@ -950,15 +836,6 @@ export const INTERCOM_TICKET_OUTPUT_PROPERTIES = { updated_at: { type: 'number', description: 'Unix timestamp when ticket was last updated' }, } as const satisfies Record -/** - * Complete ticket output definition - */ -export const INTERCOM_TICKET_OUTPUT: OutputProperty = { - type: 'object', - description: 'Intercom ticket object', - properties: INTERCOM_TICKET_OUTPUT_PROPERTIES, -} - // Pagination Output Properties /** @@ -985,16 +862,6 @@ export const INTERCOM_PAGES_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete pages output definition - */ -export const INTERCOM_PAGES_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pagination information', - optional: true, - properties: INTERCOM_PAGES_OUTPUT_PROPERTIES, -} - interface IntercomBaseParams { accessToken: string } diff --git a/apps/sim/tools/jina/types.ts b/apps/sim/tools/jina/types.ts index 35b48d55a67..34c94c50717 100644 --- a/apps/sim/tools/jina/types.ts +++ b/apps/sim/tools/jina/types.ts @@ -13,45 +13,6 @@ export const JINA_USAGE_OUTPUT_PROPERTIES = { tokens: { type: 'number', description: 'Number of tokens consumed by this request' }, } as const satisfies Record -/** - * Complete usage output definition - */ -export const JINA_USAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Token usage information for this request', - properties: JINA_USAGE_OUTPUT_PROPERTIES, -} - -/** - * Core data properties for Reader API responses - * Based on Jina AI Reader API response structure - */ -export const JINA_READER_DATA_OUTPUT_PROPERTIES = { - title: { type: 'string', description: 'Page title' }, - description: { type: 'string', description: 'Page meta description', optional: true }, - url: { type: 'string', description: 'The URL that was processed' }, - content: { - type: 'string', - description: 'Main content extracted from the page in markdown format', - }, - images: { - type: 'json', - description: 'Dictionary of images found on the page (image caption/name to URL)', - optional: true, - }, - links: { - type: 'json', - description: 'Dictionary of links found on the page (link text to URL)', - optional: true, - }, - usage: { - type: 'object', - description: 'Token usage information', - optional: true, - properties: JINA_USAGE_OUTPUT_PROPERTIES, - }, -} as const satisfies Record - /** * Output definition for search result items */ @@ -72,27 +33,6 @@ export const JINA_SEARCH_RESULT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete search result output definition - */ -export const JINA_SEARCH_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Search result with extracted content', - properties: JINA_SEARCH_RESULT_OUTPUT_PROPERTIES, -} - -/** - * Search results array output definition - */ -export const JINA_SEARCH_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of search results with LLM-friendly content', - items: { - type: 'object', - properties: JINA_SEARCH_RESULT_OUTPUT_PROPERTIES, - }, -} - export interface ReadUrlParams { url: string // Existing params (backward compatible) diff --git a/apps/sim/tools/jira/types.ts b/apps/sim/tools/jira/types.ts index aec32e52270..8bafc9d7e66 100644 --- a/apps/sim/tools/jira/types.ts +++ b/apps/sim/tools/jira/types.ts @@ -71,15 +71,6 @@ export const STATUS_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Status object output definition. - */ -export const STATUS_OUTPUT: OutputProperty = { - type: 'object', - description: 'Issue status', - properties: STATUS_OUTPUT_PROPERTIES, -} - /** * Issue type object properties from Jira API v3. * Based on IssueBean.fields.issuetype structure. @@ -92,15 +83,6 @@ export const ISSUE_TYPE_OUTPUT_PROPERTIES = { iconUrl: { type: 'string', description: 'URL to the issue type icon', optional: true }, } as const satisfies Record -/** - * Issue type object output definition. - */ -export const ISSUE_TYPE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Issue type', - properties: ISSUE_TYPE_OUTPUT_PROPERTIES, -} - /** * Project object properties from Jira API v3. * Based on IssueBean.fields.project structure. @@ -135,15 +117,6 @@ export const PRIORITY_OUTPUT_PROPERTIES = { iconUrl: { type: 'string', description: 'URL to the priority icon', optional: true }, } as const satisfies Record -/** - * Priority object output definition. - */ -export const PRIORITY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Issue priority', - properties: PRIORITY_OUTPUT_PROPERTIES, -} - /** * Resolution object properties from Jira API v3. * Based on IssueBean.fields.resolution structure. @@ -154,16 +127,6 @@ export const RESOLUTION_OUTPUT_PROPERTIES = { description: { type: 'string', description: 'Resolution description', optional: true }, } as const satisfies Record -/** - * Resolution object output definition. - */ -export const RESOLUTION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Issue resolution', - properties: RESOLUTION_OUTPUT_PROPERTIES, - optional: true, -} - /** * Component object properties from Jira API v3. * Based on IssueBean.fields.components structure. @@ -408,27 +371,6 @@ export const WORKLOG_ITEM_PROPERTIES = { updated: { type: 'string', description: 'ISO 8601 timestamp when the worklog was last updated' }, } as const satisfies Record -/** - * Worklog object output definition. - */ -export const WORKLOG_OUTPUT: OutputProperty = { - type: 'object', - description: 'Jira worklog object', - properties: WORKLOG_ITEM_PROPERTIES, -} - -/** - * Worklogs array output definition. - */ -export const WORKLOGS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Jira worklogs', - items: { - type: 'object', - properties: WORKLOG_ITEM_PROPERTIES, - }, -} - /** * Transition object properties from Jira API v3. * Based on GET /rest/api/3/issue/{issueIdOrKey}/transitions response. @@ -630,27 +572,6 @@ export const ISSUE_ITEM_PROPERTIES = { issueKey: { type: 'string', description: 'Issue key (e.g., PROJ-123)' }, } as const satisfies Record -/** - * Issue object output definition. - */ -export const ISSUE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Jira issue object', - properties: ISSUE_ITEM_PROPERTIES, -} - -/** - * Issues array output definition for search endpoints. - */ -export const ISSUES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Jira issues', - items: { - type: 'object', - properties: ISSUE_ITEM_PROPERTIES, - }, -} - /** * Search issue item properties (lighter than full issue for search results). * Based on POST /rest/api/3/search/jql response. @@ -740,14 +661,6 @@ export const TIMESTAMP_OUTPUT: OutputProperty = { description: 'ISO 8601 timestamp of the operation', } -/** - * Common issue key output property. - */ -export const ISSUE_KEY_OUTPUT: OutputProperty = { - type: 'string', - description: 'Jira issue key (e.g., PROJ-123)', -} - /** * Common success status output property. */ diff --git a/apps/sim/tools/jsm/types.ts b/apps/sim/tools/jsm/types.ts index e35c97c74c6..104a2eb0ecb 100644 --- a/apps/sim/tools/jsm/types.ts +++ b/apps/sim/tools/jsm/types.ts @@ -1,12 +1,5 @@ import type { ToolResponse } from '@/tools/types' -/** Reusable date output properties with ISO 8601, friendly, and epoch formats */ -export const DATE_OUTPUT_PROPERTIES = { - iso8601: { type: 'string', description: 'ISO 8601 formatted date' }, - friendly: { type: 'string', description: 'Human-readable date' }, - epochMillis: { type: 'number', description: 'Unix epoch milliseconds' }, -} as const - /** Reusable user output properties */ export const USER_OUTPUT_PROPERTIES = { accountId: { type: 'string', description: 'Atlassian account ID' }, diff --git a/apps/sim/tools/kalshi/types.ts b/apps/sim/tools/kalshi/types.ts index 206fa53bdde..0c60cacbad9 100644 --- a/apps/sim/tools/kalshi/types.ts +++ b/apps/sim/tools/kalshi/types.ts @@ -47,15 +47,6 @@ export const KALSHI_MARKET_OUTPUT_PROPERTIES = { category: { type: 'string', description: 'Market category', optional: true }, } as const satisfies Record -/** - * Complete market output definition - */ -export const KALSHI_MARKET_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi market object', - properties: KALSHI_MARKET_OUTPUT_PROPERTIES, -} - /** * Output definition for event objects. * @see https://trading-api.readme.io/reference/getevents @@ -71,15 +62,6 @@ export const KALSHI_EVENT_OUTPUT_PROPERTIES = { status: { type: 'string', description: 'Event status', optional: true }, } as const satisfies Record -/** - * Complete event output definition - */ -export const KALSHI_EVENT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi event object', - properties: KALSHI_EVENT_OUTPUT_PROPERTIES, -} - /** * Output definition for order objects. * @see https://trading-api.readme.io/reference/getorders @@ -105,15 +87,6 @@ export const KALSHI_ORDER_OUTPUT_PROPERTIES = { last_update_time: { type: 'string', description: 'Last order update time', optional: true }, } as const satisfies Record -/** - * Complete order output definition - */ -export const KALSHI_ORDER_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi order object', - properties: KALSHI_ORDER_OUTPUT_PROPERTIES, -} - /** * Output definition for position objects. * @see https://trading-api.readme.io/reference/getpositions @@ -135,15 +108,6 @@ export const KALSHI_POSITION_OUTPUT_PROPERTIES = { fees_paid: { type: 'number', description: 'Total fees paid in cents', optional: true }, } as const satisfies Record -/** - * Complete position output definition - */ -export const KALSHI_POSITION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi market position object', - properties: KALSHI_POSITION_OUTPUT_PROPERTIES, -} - /** * Output definition for event position objects. * @see https://trading-api.readme.io/reference/getpositions @@ -155,15 +119,6 @@ export const KALSHI_EVENT_POSITION_OUTPUT_PROPERTIES = { total_cost: { type: 'number', description: 'Total cost basis in cents', optional: true }, } as const satisfies Record -/** - * Complete event position output definition - */ -export const KALSHI_EVENT_POSITION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi event position object', - properties: KALSHI_EVENT_POSITION_OUTPUT_PROPERTIES, -} - /** * Output definition for fill/trade objects. * @see https://trading-api.readme.io/reference/getfills @@ -181,15 +136,6 @@ export const KALSHI_FILL_OUTPUT_PROPERTIES = { created_time: { type: 'string', description: 'Trade execution time (ISO 8601)' }, } as const satisfies Record -/** - * Complete fill output definition - */ -export const KALSHI_FILL_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi trade fill object', - properties: KALSHI_FILL_OUTPUT_PROPERTIES, -} - /** * Output definition for trade objects (public trades). * @see https://trading-api.readme.io/reference/gettrades @@ -203,15 +149,6 @@ export const KALSHI_TRADE_OUTPUT_PROPERTIES = { created_time: { type: 'string', description: 'Trade time (ISO 8601)' }, } as const satisfies Record -/** - * Complete trade output definition - */ -export const KALSHI_TRADE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi public trade object', - properties: KALSHI_TRADE_OUTPUT_PROPERTIES, -} - /** * Output definition for candlestick/OHLC objects. * @see https://trading-api.readme.io/reference/getmarketshistory @@ -226,15 +163,6 @@ export const KALSHI_CANDLESTICK_OUTPUT_PROPERTIES = { volume: { type: 'number', description: 'Volume during period' }, } as const satisfies Record -/** - * Complete candlestick output definition - */ -export const KALSHI_CANDLESTICK_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi price candlestick/OHLC data', - properties: KALSHI_CANDLESTICK_OUTPUT_PROPERTIES, -} - /** * Output definition for orderbook level objects. * @see https://trading-api.readme.io/reference/getmarketorderbook @@ -244,15 +172,6 @@ export const KALSHI_ORDERBOOK_LEVEL_OUTPUT_PROPERTIES = { quantity: { type: 'number', description: 'Quantity at this price level' }, } as const satisfies Record -/** - * Complete orderbook level output definition - */ -export const KALSHI_ORDERBOOK_LEVEL_OUTPUT: OutputProperty = { - type: 'object', - description: 'Orderbook price level', - properties: KALSHI_ORDERBOOK_LEVEL_OUTPUT_PROPERTIES, -} - /** * Output definition for series objects. * @see https://trading-api.readme.io/reference/getseries @@ -271,15 +190,6 @@ export const KALSHI_SERIES_OUTPUT_PROPERTIES = { contract_url: { type: 'string', description: 'Contract rules URL', optional: true }, } as const satisfies Record -/** - * Complete series output definition - */ -export const KALSHI_SERIES_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi series object', - properties: KALSHI_SERIES_OUTPUT_PROPERTIES, -} - /** * Output definition for balance objects. * @see https://trading-api.readme.io/reference/getbalance @@ -289,15 +199,6 @@ export const KALSHI_BALANCE_OUTPUT_PROPERTIES = { portfolio_value: { type: 'number', description: 'Total portfolio value in cents' }, } as const satisfies Record -/** - * Complete balance output definition - */ -export const KALSHI_BALANCE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Kalshi account balance', - properties: KALSHI_BALANCE_OUTPUT_PROPERTIES, -} - /** * Output definition for settlement objects. * @see https://docs.kalshi.com/api-reference/portfolio/get-settlements @@ -334,15 +235,6 @@ export const KALSHI_PAGING_OUTPUT_PROPERTIES = { cursor: { type: 'string', description: 'Cursor for fetching next page', optional: true }, } as const satisfies Record -/** - * Complete paging output definition - */ -export const KALSHI_PAGING_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pagination information', - properties: KALSHI_PAGING_OUTPUT_PROPERTIES, -} - // Base params for authenticated endpoints export interface KalshiAuthParams { keyId: string // API Key ID diff --git a/apps/sim/tools/leadmagic/types.ts b/apps/sim/tools/leadmagic/types.ts index d1836b804bf..90543fe91cd 100644 --- a/apps/sim/tools/leadmagic/types.ts +++ b/apps/sim/tools/leadmagic/types.ts @@ -1,25 +1,9 @@ -import type { OutputProperty, ToolResponse } from '@/tools/types' +import type { ToolResponse } from '@/tools/types' interface LeadMagicBaseParams { apiKey: string } -// Shared output property constants - -export const LEADMAGIC_PROFILE_OUTPUT_PROPERTIES = { - profile_url: { type: 'string', description: 'LinkedIn profile URL' }, - first_name: { type: 'string', description: 'First name' }, - last_name: { type: 'string', description: 'Last name' }, - full_name: { type: 'string', description: 'Full name' }, - professional_title: { type: 'string', description: 'Current job title', optional: true }, - bio: { type: 'string', description: 'Profile bio / summary', optional: true }, - location: { type: 'string', description: 'Location string', optional: true }, - country: { type: 'string', description: 'Country', optional: true }, - company_name: { type: 'string', description: 'Current employer', optional: true }, - company_industry: { type: 'string', description: 'Industry of current employer', optional: true }, - company_website: { type: 'string', description: 'Company website', optional: true }, -} as const satisfies Record - // Email Validation export interface LeadMagicValidateEmailParams extends LeadMagicBaseParams { diff --git a/apps/sim/tools/linear/types.ts b/apps/sim/tools/linear/types.ts index b8611210bfc..eef623776ed 100644 --- a/apps/sim/tools/linear/types.ts +++ b/apps/sim/tools/linear/types.ts @@ -295,49 +295,6 @@ export const WORKFLOW_STATE_OUTPUT_PROPERTIES = { team: TEAM_OUTPUT, } as const satisfies Record -/** - * Output definition for issue relation objects - */ -export const ISSUE_RELATION_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Relation ID' }, - type: { type: 'string', description: 'Relation type (blocks, duplicate, related)' }, - issue: ISSUE_MINIMAL_OUTPUT, - relatedIssue: ISSUE_MINIMAL_OUTPUT, -} as const satisfies Record - -/** - * Output definition for favorite objects - */ -export const FAVORITE_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Favorite ID' }, - type: { type: 'string', description: 'Favorite type (issue, project, cycle)' }, - issue: ISSUE_MINIMAL_OUTPUT, - project: PROJECT_OUTPUT, - cycle: CYCLE_OUTPUT, -} as const satisfies Record - -/** - * Output definition for project update objects - */ -export const PROJECT_UPDATE_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Project update ID' }, - body: { type: 'string', description: 'Update body (Markdown)' }, - health: { type: 'string', description: 'Project health (onTrack, atRisk, offTrack)' }, - createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' }, - user: USER_OUTPUT, -} as const satisfies Record - -/** - * Output definition for notification objects - */ -export const NOTIFICATION_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Notification ID' }, - type: { type: 'string', description: 'Notification type' }, - createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' }, - readAt: { type: 'string', description: 'Read timestamp (ISO 8601)' }, - issue: ISSUE_MINIMAL_OUTPUT, -} as const satisfies Record - /** * Output definition for customer objects */ @@ -364,30 +321,6 @@ export const CUSTOMER_OUTPUT_PROPERTIES = { archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' }, } as const satisfies Record -/** - * Output definition for customer need/request objects - */ -export const CUSTOMER_NEED_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Customer need ID' }, - body: { type: 'string', description: 'Need body/description' }, - priority: { type: 'number', description: 'Priority (0-4)' }, - createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' }, - updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' }, - archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' }, - customer: { - type: 'object', - description: 'Associated customer', - properties: { - id: { type: 'string', description: 'Customer ID' }, - name: { type: 'string', description: 'Customer name' }, - }, - }, - issue: ISSUE_MINIMAL_OUTPUT, - project: PROJECT_OUTPUT, - creator: USER_OUTPUT, - url: { type: 'string', description: 'Customer need URL' }, -} as const satisfies Record - /** * Output definition for customer status objects */ @@ -447,14 +380,6 @@ export const PROJECT_MILESTONE_OUTPUT_PROPERTIES = { archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' }, } as const satisfies Record -/** - * Output definition for nested project milestone objects - */ -export const PROJECT_MILESTONE_MINIMAL_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Project milestone ID' }, - name: { type: 'string', description: 'Milestone name' }, -} as const satisfies Record - /** * Output definition for project status objects */ diff --git a/apps/sim/tools/managed_agent/shared.ts b/apps/sim/tools/managed_agent/shared.ts index ea045474071..b291e07feca 100644 --- a/apps/sim/tools/managed_agent/shared.ts +++ b/apps/sim/tools/managed_agent/shared.ts @@ -71,17 +71,3 @@ export function resolveSessionTarget( } return { ok: true, apiKey, sessionId } } - -/** Validates the credential alone, for operations that do not target a session. */ -export function resolveApiKey( - params: Pick -): { ok: true; apiKey: string } | { ok: false; error: string } { - const apiKey = params.accessToken - if (!apiKey) { - return { - ok: false, - error: 'No Claude Platform credential is selected, or it could not be resolved.', - } - } - return { ok: true, apiKey } -} diff --git a/apps/sim/tools/mem0/types.ts b/apps/sim/tools/mem0/types.ts index 63f18988df9..5542ab9f131 100644 --- a/apps/sim/tools/mem0/types.ts +++ b/apps/sim/tools/mem0/types.ts @@ -58,15 +58,6 @@ export const ADD_MEMORY_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete add memory object output definition - */ -export const ADD_MEMORY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Queued memory processing job returned from add operation', - properties: ADD_MEMORY_OUTPUT_PROPERTIES, -} - /** * Output definition for memory objects returned by get operations. * Get responses include full memory details with timestamps and ownership info. @@ -101,15 +92,6 @@ export const MEMORY_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete memory object output definition - */ -export const MEMORY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Memory object with full details including timestamps and ownership', - properties: MEMORY_OUTPUT_PROPERTIES, -} - /** * Output definition for search result objects returned by search operations. * Search responses include similarity score in addition to memory details. diff --git a/apps/sim/tools/mistral/types.ts b/apps/sim/tools/mistral/types.ts index 641b8c7edd3..4228127ba54 100644 --- a/apps/sim/tools/mistral/types.ts +++ b/apps/sim/tools/mistral/types.ts @@ -23,15 +23,6 @@ export const MISTRAL_OCR_IMAGE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete OCR image output definition - */ -export const MISTRAL_OCR_IMAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Extracted image with bounding box', - properties: MISTRAL_OCR_IMAGE_OUTPUT_PROPERTIES, -} - /** * Output definition for page dimension objects */ @@ -86,15 +77,6 @@ export const MISTRAL_OCR_PAGE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete OCR page output definition - */ -export const MISTRAL_OCR_PAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'OCR processed page', - properties: MISTRAL_OCR_PAGE_OUTPUT_PROPERTIES, -} - /** * Output definition for usage info objects */ @@ -128,15 +110,6 @@ export const MISTRAL_PARSER_METADATA_OUTPUT_PROPERTIES = { usageInfo: MISTRAL_OCR_USAGE_OUTPUT, } as const satisfies Record -/** - * Complete parser metadata output definition - */ -export const MISTRAL_PARSER_METADATA_OUTPUT: OutputProperty = { - type: 'object', - description: 'Processing metadata', - properties: MISTRAL_PARSER_METADATA_OUTPUT_PROPERTIES, -} - export interface MistralParserInput { filePath?: string file?: RawFileInput diff --git a/apps/sim/tools/notion/types.ts b/apps/sim/tools/notion/types.ts index 2dc15428d4d..19d473e218e 100644 --- a/apps/sim/tools/notion/types.ts +++ b/apps/sim/tools/notion/types.ts @@ -190,32 +190,6 @@ export const FILE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Emoji object properties. - * @see https://developers.notion.com/reference/emoji-object - */ -export const EMOJI_OUTPUT_PROPERTIES = { - type: { type: 'string', description: 'Always "emoji" for standard emojis' }, - emoji: { type: 'string', description: 'The emoji character' }, -} as const satisfies Record - -/** - * Custom emoji object properties. - * @see https://developers.notion.com/reference/emoji-object - */ -export const CUSTOM_EMOJI_OUTPUT_PROPERTIES = { - type: { type: 'string', description: 'Always "custom_emoji"' }, - custom_emoji: { - type: 'object', - description: 'Custom emoji details', - properties: { - id: { type: 'string', description: 'Custom emoji UUID' }, - name: { type: 'string', description: 'Custom emoji name', optional: true }, - url: { type: 'string', description: 'URL to custom emoji image', optional: true }, - }, - }, -} as const satisfies Record - /** * Icon output (can be emoji, custom_emoji, or file) */ @@ -327,15 +301,6 @@ export const DATABASE_OUTPUT_PROPERTIES = { properties: { type: 'object', description: 'Database properties schema' }, } as const satisfies Record -/** - * Complete database output definition for array items - */ -export const DATABASE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Notion database object', - properties: DATABASE_OUTPUT_PROPERTIES, -} - /** * Block object properties from Notion API. * @see https://developers.notion.com/reference/block @@ -357,15 +322,6 @@ export const BLOCK_OUTPUT_PROPERTIES = { has_children: { type: 'boolean', description: 'Whether the block has nested blocks' }, } as const satisfies Record -/** - * Complete block output definition for array items - */ -export const BLOCK_OUTPUT: OutputProperty = { - type: 'object', - description: 'Notion block object', - properties: BLOCK_OUTPUT_PROPERTIES, -} - /** * Pagination output properties for list responses. * @see https://developers.notion.com/reference/intro (JSON conventions - Pagination section) diff --git a/apps/sim/tools/outlook/types.ts b/apps/sim/tools/outlook/types.ts index 4e1360b8f9f..22e9f178dc0 100644 --- a/apps/sim/tools/outlook/types.ts +++ b/apps/sim/tools/outlook/types.ts @@ -88,15 +88,6 @@ export const OUTLOOK_MESSAGE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete message output definition - */ -export const OUTLOOK_MESSAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Outlook email message', - properties: OUTLOOK_MESSAGE_OUTPUT_PROPERTIES, -} - /** * Output definition for attachment objects. * @see https://learn.microsoft.com/en-us/graph/api/resources/attachment diff --git a/apps/sim/tools/pipedrive/types.ts b/apps/sim/tools/pipedrive/types.ts index 9bcf3536b5b..fcfa944c5a6 100644 --- a/apps/sim/tools/pipedrive/types.ts +++ b/apps/sim/tools/pipedrive/types.ts @@ -56,15 +56,6 @@ export const PIPEDRIVE_LEAD_OUTPUT_PROPERTIES = { update_time: { type: 'string', description: 'When the lead was last updated (ISO 8601)' }, } as const satisfies Record -/** - * Complete lead output definition - */ -export const PIPEDRIVE_LEAD_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive lead object', - properties: PIPEDRIVE_LEAD_OUTPUT_PROPERTIES, -} - /** * Output definition for deal objects. * @see https://developers.pipedrive.com/docs/api/v1/Deals @@ -88,15 +79,6 @@ export const PIPEDRIVE_DEAL_OUTPUT_PROPERTIES = { expected_close_date: { type: 'string', description: 'Expected close date', optional: true }, } as const satisfies Record -/** - * Complete deal output definition - */ -export const PIPEDRIVE_DEAL_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive deal object', - properties: PIPEDRIVE_DEAL_OUTPUT_PROPERTIES, -} - /** * Output definition for activity objects. * @see https://developers.pipedrive.com/docs/api/v1/Activities @@ -117,15 +99,6 @@ export const PIPEDRIVE_ACTIVITY_OUTPUT_PROPERTIES = { update_time: { type: 'string', description: 'When the activity was last updated' }, } as const satisfies Record -/** - * Complete activity output definition - */ -export const PIPEDRIVE_ACTIVITY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive activity object', - properties: PIPEDRIVE_ACTIVITY_OUTPUT_PROPERTIES, -} - /** * Output definition for file objects. * @see https://developers.pipedrive.com/docs/api/v1/Files @@ -143,15 +116,6 @@ export const PIPEDRIVE_FILE_OUTPUT_PROPERTIES = { url: { type: 'string', description: 'File download URL' }, } as const satisfies Record -/** - * Complete file output definition - */ -export const PIPEDRIVE_FILE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive file object', - properties: PIPEDRIVE_FILE_OUTPUT_PROPERTIES, -} - /** * Output definition for pipeline objects. * @see https://developers.pipedrive.com/docs/api/v1/Pipelines @@ -167,15 +131,6 @@ export const PIPEDRIVE_PIPELINE_OUTPUT_PROPERTIES = { update_time: { type: 'string', description: 'When the pipeline was last updated' }, } as const satisfies Record -/** - * Complete pipeline output definition - */ -export const PIPEDRIVE_PIPELINE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive pipeline object', - properties: PIPEDRIVE_PIPELINE_OUTPUT_PROPERTIES, -} - /** * Output definition for project objects. * @see https://developers.pipedrive.com/docs/api/v1/Projects @@ -192,15 +147,6 @@ export const PIPEDRIVE_PROJECT_OUTPUT_PROPERTIES = { update_time: { type: 'string', description: 'When the project was last updated' }, } as const satisfies Record -/** - * Complete project output definition - */ -export const PIPEDRIVE_PROJECT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive project object', - properties: PIPEDRIVE_PROJECT_OUTPUT_PROPERTIES, -} - /** * Output definition for mail message objects. * @see https://developers.pipedrive.com/docs/api/v1/Mailbox @@ -235,15 +181,6 @@ export const PIPEDRIVE_MAIL_MESSAGE_OUTPUT_PROPERTIES = { org_id: { type: 'number', description: 'Associated organization ID', optional: true }, } as const satisfies Record -/** - * Complete mail message output definition - */ -export const PIPEDRIVE_MAIL_MESSAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pipedrive mail message object', - properties: PIPEDRIVE_MAIL_MESSAGE_OUTPUT_PROPERTIES, -} - /** * List metadata output properties */ diff --git a/apps/sim/tools/postgresql/types.ts b/apps/sim/tools/postgresql/types.ts index 3911125dca4..bb0811e4473 100644 --- a/apps/sim/tools/postgresql/types.ts +++ b/apps/sim/tools/postgresql/types.ts @@ -27,15 +27,6 @@ export const POSTGRES_COLUMN_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete column output definition - */ -export const POSTGRES_COLUMN_OUTPUT: OutputProperty = { - type: 'object', - description: 'PostgreSQL table column', - properties: POSTGRES_COLUMN_OUTPUT_PROPERTIES, -} - /** * Output definition for foreign key constraint objects. */ @@ -96,15 +87,6 @@ export const POSTGRES_TABLE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete table schema output definition - */ -export const POSTGRES_TABLE_OUTPUT: OutputProperty = { - type: 'object', - description: 'PostgreSQL table schema information', - properties: POSTGRES_TABLE_OUTPUT_PROPERTIES, -} - export interface PostgresConnectionConfig { host: string port: number diff --git a/apps/sim/tools/qdrant/types.ts b/apps/sim/tools/qdrant/types.ts index 84688bb20ab..d6ecfa4e966 100644 --- a/apps/sim/tools/qdrant/types.ts +++ b/apps/sim/tools/qdrant/types.ts @@ -25,15 +25,6 @@ export const POINT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete point object output definition - */ -export const POINT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Point object with ID, payload, and optional vector', - properties: POINT_OUTPUT_PROPERTIES, -} - /** * Output definition for scored point objects returned by search/query operations */ @@ -55,15 +46,6 @@ export const SCORED_POINT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete scored point object output definition - */ -export const SCORED_POINT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Scored point with ID, version, score, payload, and optional vector', - properties: SCORED_POINT_OUTPUT_PROPERTIES, -} - /** * Output definition for upsert operation result */ @@ -76,15 +58,6 @@ export const UPSERT_RESULT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete upsert result output definition - */ -export const UPSERT_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Upsert operation result with operation ID and status', - properties: UPSERT_RESULT_OUTPUT_PROPERTIES, -} - /** * Common response properties for all Qdrant operations */ diff --git a/apps/sim/tools/reddit/types.ts b/apps/sim/tools/reddit/types.ts index 3573ea80607..e4552c1be4a 100644 --- a/apps/sim/tools/reddit/types.ts +++ b/apps/sim/tools/reddit/types.ts @@ -145,47 +145,6 @@ export const POST_METADATA_OUTPUT_PROPERTIES = { permalink: { type: 'string', description: 'Reddit permalink' }, } as const satisfies Record -/** - * Complete posts array output definition - */ -export const POSTS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of posts with title, author, URL, score, comments count, and metadata', - items: { - type: 'object', - properties: POST_LISTING_OUTPUT_PROPERTIES, - }, -} - -/** - * Complete comments array output definition with nested replies - */ -export const COMMENTS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'Nested comments with author, body, score, timestamps, and replies', - items: { - type: 'object', - properties: COMMENT_WITH_REPLIES_OUTPUT_PROPERTIES, - }, -} - -/** - * Post metadata output definition for get_comments tool - */ -export const POST_METADATA_OUTPUT: OutputProperty = { - type: 'object', - description: 'Post information including ID, title, author, content, and metadata', - properties: POST_METADATA_OUTPUT_PROPERTIES, -} - -/** - * Write operation success output properties - */ -export const WRITE_SUCCESS_OUTPUT_PROPERTIES = { - success: { type: 'boolean', description: 'Whether the operation was successful' }, - message: { type: 'string', description: 'Success or error message' }, -} as const satisfies Record - /** * Submit post response data output properties */ @@ -196,15 +155,6 @@ export const SUBMIT_POST_DATA_OUTPUT_PROPERTIES = { permalink: { type: 'string', description: 'Full Reddit permalink' }, } as const satisfies Record -/** - * Submit post data output definition - */ -export const SUBMIT_POST_DATA_OUTPUT: OutputProperty = { - type: 'object', - description: 'Post data including ID, name, URL, and permalink', - properties: SUBMIT_POST_DATA_OUTPUT_PROPERTIES, -} - /** * Reply comment response data output properties */ @@ -215,15 +165,6 @@ export const REPLY_DATA_OUTPUT_PROPERTIES = { body: { type: 'string', description: 'Comment body text' }, } as const satisfies Record -/** - * Reply data output definition - */ -export const REPLY_DATA_OUTPUT: OutputProperty = { - type: 'object', - description: 'Comment data including ID, name, permalink, and body', - properties: REPLY_DATA_OUTPUT_PROPERTIES, -} - /** * Edit response data output properties */ @@ -233,15 +174,6 @@ export const EDIT_DATA_OUTPUT_PROPERTIES = { selftext: { type: 'string', description: 'Updated post text (for self posts)' }, } as const satisfies Record -/** - * Edit data output definition - */ -export const EDIT_DATA_OUTPUT: OutputProperty = { - type: 'object', - description: 'Updated content data', - properties: EDIT_DATA_OUTPUT_PROPERTIES, -} - export interface RedditPost { id: string name: string diff --git a/apps/sim/tools/salesforce/types.ts b/apps/sim/tools/salesforce/types.ts index 3ce4570639f..66efbe53d7e 100644 --- a/apps/sim/tools/salesforce/types.ts +++ b/apps/sim/tools/salesforce/types.ts @@ -97,27 +97,6 @@ export const ACCOUNT_OUTPUT_PROPERTIES = { AccountSource: { type: 'string', description: 'Source of the account record', optional: true }, } as const satisfies Record -/** - * Complete Account object output definition for single record - */ -export const ACCOUNT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Salesforce Account object', - properties: ACCOUNT_OUTPUT_PROPERTIES, -} - -/** - * Accounts array output definition for list operations - */ -export const ACCOUNTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Account objects', - items: { - type: 'object', - properties: ACCOUNT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Contact sObject * @see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_contact.htm @@ -201,27 +180,6 @@ export const CONTACT_OUTPUT_PROPERTIES = { PhotoUrl: { type: 'string', description: 'URL to contact photo', optional: true }, } as const satisfies Record -/** - * Complete Contact object output definition for single record - */ -export const CONTACT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Salesforce Contact object', - properties: CONTACT_OUTPUT_PROPERTIES, -} - -/** - * Contacts array output definition for list operations - */ -export const CONTACTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Contact objects', - items: { - type: 'object', - properties: CONTACT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Lead sObject * @see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_lead.htm @@ -309,27 +267,6 @@ export const LEAD_OUTPUT_PROPERTIES = { PhotoUrl: { type: 'string', description: 'URL to lead photo', optional: true }, } as const satisfies Record -/** - * Complete Lead object output definition for single record - */ -export const LEAD_OUTPUT: OutputProperty = { - type: 'object', - description: 'Salesforce Lead object', - properties: LEAD_OUTPUT_PROPERTIES, -} - -/** - * Leads array output definition for list operations - */ -export const LEADS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Lead objects', - items: { - type: 'object', - properties: LEAD_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Opportunity sObject * @see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_opportunity.htm @@ -411,27 +348,6 @@ export const OPPORTUNITY_OUTPUT_PROPERTIES = { ContactId: { type: 'string', description: 'ID of the primary contact', optional: true }, } as const satisfies Record -/** - * Complete Opportunity object output definition for single record - */ -export const OPPORTUNITY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Salesforce Opportunity object', - properties: OPPORTUNITY_OUTPUT_PROPERTIES, -} - -/** - * Opportunities array output definition for list operations - */ -export const OPPORTUNITIES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Opportunity objects', - items: { - type: 'object', - properties: OPPORTUNITY_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Case sObject * @see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_case.htm @@ -514,27 +430,6 @@ export const CASE_OUTPUT_PROPERTIES = { Comments: { type: 'string', description: 'Internal comments on the case', optional: true }, } as const satisfies Record -/** - * Complete Case object output definition for single record - */ -export const CASE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Salesforce Case object', - properties: CASE_OUTPUT_PROPERTIES, -} - -/** - * Cases array output definition for list operations - */ -export const CASES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Case objects', - items: { - type: 'object', - properties: CASE_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Task sObject * @see https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_task.htm @@ -617,27 +512,6 @@ export const TASK_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete Task object output definition for single record - */ -export const TASK_OUTPUT: OutputProperty = { - type: 'object', - description: 'Salesforce Task object', - properties: TASK_OUTPUT_PROPERTIES, -} - -/** - * Tasks array output definition for list operations - */ -export const TASKS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Task objects', - items: { - type: 'object', - properties: TASK_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Report list item * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_get_reportlist.htm @@ -687,18 +561,6 @@ export const REPORT_LIST_ITEM_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Reports array output definition - */ -export const REPORTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Report objects', - items: { - type: 'object', - properties: REPORT_LIST_ITEM_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Dashboard list item * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_getbasic_dashboardlist.htm @@ -714,18 +576,6 @@ export const DASHBOARD_LIST_ITEM_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Dashboards array output definition - */ -export const DASHBOARDS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Dashboard objects', - items: { - type: 'object', - properties: DASHBOARD_LIST_ITEM_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for Report Type list item * @see https://developer.salesforce.com/docs/atlas.en-us.api_analytics.meta/api_analytics/sforce_analytics_rest_api_list_reporttypes.htm @@ -741,18 +591,6 @@ export const REPORT_TYPE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Report types array output definition - */ -export const REPORT_TYPES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of Salesforce Report Type objects', - items: { - type: 'object', - properties: REPORT_TYPE_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for sObject describe field metadata * @see https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_describe.htm diff --git a/apps/sim/tools/serper/types.ts b/apps/sim/tools/serper/types.ts index d7667c2592e..4b3f167b620 100644 --- a/apps/sim/tools/serper/types.ts +++ b/apps/sim/tools/serper/types.ts @@ -19,23 +19,6 @@ export const SEARCH_PARAMETERS_OUTPUT_PROPERTIES = { num: { type: 'number', description: 'Number of results requested', optional: true }, } as const satisfies Record -/** - * Complete search parameters output definition - */ -export const SEARCH_PARAMETERS_OUTPUT: OutputProperty = { - type: 'object', - description: 'Parameters used for this search request', - properties: SEARCH_PARAMETERS_OUTPUT_PROPERTIES, -} - -/** - * Output definition for Knowledge Graph attributes - */ -export const KNOWLEDGE_GRAPH_ATTRIBUTES_OUTPUT_PROPERTIES = { - key: { type: 'string', description: 'Attribute name (e.g., "Headquarters", "CEO", "Founded")' }, - value: { type: 'string', description: 'Attribute value' }, -} as const satisfies Record - /** * Output definition for Knowledge Graph panel * Appears for entities like companies, people, places @@ -72,16 +55,6 @@ export const KNOWLEDGE_GRAPH_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete Knowledge Graph output definition - */ -export const KNOWLEDGE_GRAPH_OUTPUT: OutputProperty = { - type: 'object', - description: 'Knowledge Graph panel information for entities like companies, people, places', - optional: true, - properties: KNOWLEDGE_GRAPH_OUTPUT_PROPERTIES, -} - /** * Output definition for Answer Box / Featured Snippet * Appears for direct answer queries @@ -97,16 +70,6 @@ export const ANSWER_BOX_OUTPUT_PROPERTIES = { link: { type: 'string', description: 'URL of the source', optional: true }, } as const satisfies Record -/** - * Complete Answer Box output definition - */ -export const ANSWER_BOX_OUTPUT: OutputProperty = { - type: 'object', - description: 'Featured snippet / answer box with direct answers to queries', - optional: true, - properties: ANSWER_BOX_OUTPUT_PROPERTIES, -} - /** * Output definition for sitelinks under an organic result */ @@ -145,18 +108,6 @@ export const ORGANIC_RESULT_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete organic results array output definition - */ -export const ORGANIC_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Organic search results', - items: { - type: 'object', - properties: ORGANIC_RESULT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for People Also Ask questions */ @@ -167,19 +118,6 @@ export const PEOPLE_ALSO_ASK_OUTPUT_PROPERTIES = { link: { type: 'string', description: 'URL of the source page', optional: true }, } as const satisfies Record -/** - * Complete People Also Ask array output definition - */ -export const PEOPLE_ALSO_ASK_OUTPUT: OutputProperty = { - type: 'array', - description: 'People Also Ask questions and answers', - optional: true, - items: { - type: 'object', - properties: PEOPLE_ALSO_ASK_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for related search suggestions */ @@ -187,19 +125,6 @@ export const RELATED_SEARCH_OUTPUT_PROPERTIES = { query: { type: 'string', description: 'Suggested search query' }, } as const satisfies Record -/** - * Complete related searches array output definition - */ -export const RELATED_SEARCHES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Related search suggestions', - optional: true, - items: { - type: 'object', - properties: RELATED_SEARCH_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for top stories in news carousel */ @@ -211,19 +136,6 @@ export const TOP_STORY_OUTPUT_PROPERTIES = { imageUrl: { type: 'string', description: 'Thumbnail image URL', optional: true }, } as const satisfies Record -/** - * Complete top stories array output definition - */ -export const TOP_STORIES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Top stories carousel', - optional: true, - items: { - type: 'object', - properties: TOP_STORY_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for news search result items */ @@ -237,18 +149,6 @@ export const NEWS_RESULT_OUTPUT_PROPERTIES = { position: { type: 'number', description: 'Position in results (1-based)' }, } as const satisfies Record -/** - * Complete news results array output definition - */ -export const NEWS_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'News search results', - items: { - type: 'object', - properties: NEWS_RESULT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for image search result items */ @@ -267,18 +167,6 @@ export const IMAGE_RESULT_OUTPUT_PROPERTIES = { position: { type: 'number', description: 'Position in results (1-based)' }, } as const satisfies Record -/** - * Complete image results array output definition - */ -export const IMAGE_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Image search results', - items: { - type: 'object', - properties: IMAGE_RESULT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for video search result items */ @@ -296,18 +184,6 @@ export const VIDEO_RESULT_OUTPUT_PROPERTIES = { position: { type: 'number', description: 'Position in results (1-based)' }, } as const satisfies Record -/** - * Complete video results array output definition - */ -export const VIDEO_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Video search results', - items: { - type: 'object', - properties: VIDEO_RESULT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for places/maps search result items */ @@ -339,18 +215,6 @@ export const PLACE_RESULT_OUTPUT_PROPERTIES = { position: { type: 'number', description: 'Position in results (1-based)' }, } as const satisfies Record -/** - * Complete places results array output definition - */ -export const PLACES_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Places/maps search results', - items: { - type: 'object', - properties: PLACE_RESULT_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for shopping/product search result items */ @@ -364,18 +228,6 @@ export const SHOPPING_RESULT_OUTPUT_PROPERTIES = { position: { type: 'number', description: 'Position in results (1-based)' }, } as const satisfies Record -/** - * Complete shopping results array output definition - */ -export const SHOPPING_RESULTS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Shopping/product search results', - items: { - type: 'object', - properties: SHOPPING_RESULT_OUTPUT_PROPERTIES, - }, -} - /** * Combined search result output definition (supports all search types for legacy compatibility) * This is used when returning a unified result format across different search types @@ -395,15 +247,6 @@ export const SERPER_SEARCH_RESULT_OUTPUT_PROPERTIES = { duration: { type: 'string', description: 'Duration (videos)', optional: true }, } as const satisfies Record -/** - * Complete search result output definition - */ -export const SERPER_SEARCH_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Search result item with type-specific metadata', - properties: SERPER_SEARCH_RESULT_OUTPUT_PROPERTIES, -} - export interface SearchParams { query: string apiKey: string diff --git a/apps/sim/tools/slack/types.ts b/apps/sim/tools/slack/types.ts index 6e8739a355e..bc49cab53ca 100644 --- a/apps/sim/tools/slack/types.ts +++ b/apps/sim/tools/slack/types.ts @@ -237,28 +237,6 @@ export const MESSAGE_OUTPUT: OutputProperty = { properties: MESSAGE_OUTPUT_PROPERTIES, } -/** - * Messages array output definition for list/reader tools - */ -export const MESSAGES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of message objects', - items: { - type: 'object', - properties: MESSAGE_OUTPUT_PROPERTIES, - }, -} - -/** - * Output definition for channel topic/purpose nested objects - * Based on Slack conversation object structure - */ -export const CHANNEL_TOPIC_OUTPUT_PROPERTIES = { - value: { type: 'string', description: 'Topic or purpose text' }, - creator: { type: 'string', description: 'User ID who set it' }, - last_set: { type: 'number', description: 'Unix timestamp when last set' }, -} as const satisfies Record - /** * Output definition for channel objects * Based on Slack conversation object (https://api.slack.com/types/conversation) @@ -314,65 +292,6 @@ export const SCHEDULED_MESSAGE_OUTPUT_PROPERTIES = { text: { type: 'string', description: 'Scheduled message text', optional: true }, } as const satisfies Record -/** - * Complete channel object output definition - */ -export const CHANNEL_OUTPUT: OutputProperty = { - type: 'object', - description: 'Slack channel object', - properties: CHANNEL_OUTPUT_PROPERTIES, -} - -/** - * Channels array output definition - */ -export const CHANNELS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of channel objects', - items: { - type: 'object', - properties: CHANNEL_OUTPUT_PROPERTIES, - }, -} - -/** - * Output definition for user profile objects (nested in user) - * Based on Slack user profile object - */ -export const USER_PROFILE_OUTPUT_PROPERTIES = { - real_name: { type: 'string', description: 'Full real name' }, - real_name_normalized: { type: 'string', description: 'Normalized real name', optional: true }, - display_name: { type: 'string', description: 'Display name shown in Slack' }, - display_name_normalized: { - type: 'string', - description: 'Normalized display name', - optional: true, - }, - first_name: { type: 'string', description: 'First name', optional: true }, - last_name: { type: 'string', description: 'Last name', optional: true }, - title: { type: 'string', description: 'Job title', optional: true }, - phone: { type: 'string', description: 'Phone number', optional: true }, - skype: { type: 'string', description: 'Skype handle', optional: true }, - email: { - type: 'string', - description: 'Email address (requires users:read.email scope)', - optional: true, - }, - status_text: { type: 'string', description: 'Custom status text', optional: true }, - status_emoji: { type: 'string', description: 'Custom status emoji', optional: true }, - status_expiration: { - type: 'number', - description: 'Unix timestamp when status expires', - optional: true, - }, - image_24: { type: 'string', description: 'URL to 24px avatar', optional: true }, - image_32: { type: 'string', description: 'URL to 32px avatar', optional: true }, - image_48: { type: 'string', description: 'URL to 48px avatar', optional: true }, - image_72: { type: 'string', description: 'URL to 72px avatar', optional: true }, - image_192: { type: 'string', description: 'URL to 192px avatar', optional: true }, - image_512: { type: 'string', description: 'URL to 512px avatar', optional: true }, -} as const satisfies Record - /** * Output definition for user objects * Based on Slack user object (https://api.slack.com/types/user) @@ -474,18 +393,6 @@ export const USER_OUTPUT: OutputProperty = { properties: USER_OUTPUT_PROPERTIES, } -/** - * Users array output definition - */ -export const USERS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of user objects', - items: { - type: 'object', - properties: USER_SUMMARY_OUTPUT_PROPERTIES, - }, -} - /** * Canvas output properties */ diff --git a/apps/sim/tools/spotify/types.ts b/apps/sim/tools/spotify/types.ts index ed18abeb962..443128edde9 100644 --- a/apps/sim/tools/spotify/types.ts +++ b/apps/sim/tools/spotify/types.ts @@ -57,27 +57,6 @@ export const ALBUM_WITH_ARTISTS_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** Track properties for basic track info */ -export const TRACK_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Spotify track ID' }, - name: { type: 'string', description: 'Track name' }, - artists: { - type: 'array', - description: 'List of artists', - items: { type: 'object', properties: SIMPLIFIED_ARTIST_OUTPUT_PROPERTIES }, - }, - album: { - type: 'object', - description: 'Album information', - properties: SIMPLIFIED_ALBUM_OUTPUT_PROPERTIES, - }, - duration_ms: { type: 'number', description: 'Track duration in milliseconds' }, - explicit: { type: 'boolean', description: 'Whether the track has explicit content' }, - popularity: { type: 'number', description: 'Popularity score (0-100)' }, - preview_url: { type: 'string', description: 'URL to 30-second preview', optional: true }, - external_url: { type: 'string', description: 'Spotify URL' }, -} as const satisfies Record - /** Track properties without explicit and preview_url (for listings) */ export const TRACK_LIST_OUTPUT_PROPERTIES = { id: { type: 'string', description: 'Spotify track ID' }, @@ -148,30 +127,6 @@ export const PLAYLIST_OWNER_OUTPUT_PROPERTIES = { display_name: { type: 'string', description: 'Display name' }, } as const satisfies Record -/** Playlist list item properties */ -export const PLAYLIST_LIST_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Spotify playlist ID' }, - name: { type: 'string', description: 'Playlist name' }, - description: { type: 'string', description: 'Playlist description', optional: true }, - public: { type: 'boolean', description: 'Whether the playlist is public' }, - collaborative: { type: 'boolean', description: 'Whether the playlist is collaborative' }, - owner: { type: 'string', description: 'Owner display name' }, - total_tracks: { type: 'number', description: 'Number of tracks' }, - image_url: { type: 'string', description: 'Playlist cover image URL', optional: true }, - external_url: { type: 'string', description: 'Spotify URL' }, -} as const satisfies Record - -/** Device properties */ -export const DEVICE_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Device ID' }, - name: { type: 'string', description: 'Device name' }, - type: { type: 'string', description: 'Device type (Computer, Smartphone, etc.)' }, - volume_percent: { type: 'number', description: 'Current volume (0-100)' }, - is_active: { type: 'boolean', description: 'Whether device is active' }, - is_private_session: { type: 'boolean', description: 'Whether in private session' }, - is_restricted: { type: 'boolean', description: 'Whether device is restricted' }, -} as const satisfies Record - /** Simplified device properties (for playback state) */ export const SIMPLIFIED_DEVICE_OUTPUT_PROPERTIES = { id: { type: 'string', description: 'Device ID' }, diff --git a/apps/sim/tools/stagehand/types.ts b/apps/sim/tools/stagehand/types.ts index f42f0d923c2..9fa61e7b6d5 100644 --- a/apps/sim/tools/stagehand/types.ts +++ b/apps/sim/tools/stagehand/types.ts @@ -29,16 +29,6 @@ export const STAGEHAND_USAGE_OUTPUT_PROPERTIES = { inference_time_ms: { type: 'number', description: 'Total inference time in milliseconds' }, } as const satisfies Record -/** - * Complete usage statistics output definition - */ -export const STAGEHAND_USAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Token usage and timing statistics from agent execution', - optional: true, - properties: STAGEHAND_USAGE_OUTPUT_PROPERTIES, -} - /** * Output definition for agent action objects * Based on Stagehand AgentAction interface @@ -91,15 +81,6 @@ export const STAGEHAND_ACTION_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete agent action output definition - */ -export const STAGEHAND_ACTION_OUTPUT: OutputProperty = { - type: 'object', - description: 'Record of an action performed by the agent', - properties: STAGEHAND_ACTION_OUTPUT_PROPERTIES, -} - /** * Actions array output definition */ @@ -132,15 +113,6 @@ export const STAGEHAND_AGENT_RESULT_OUTPUT_PROPERTIES = { actions: STAGEHAND_ACTIONS_OUTPUT, } as const satisfies Record -/** - * Complete agent result output definition - */ -export const STAGEHAND_AGENT_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Complete result from the Stagehand agent execution', - properties: STAGEHAND_AGENT_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for act() method results * Based on Stagehand ActResult interface @@ -168,66 +140,6 @@ export const STAGEHAND_ACT_ACTION_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Output definition for act() result - */ -export const STAGEHAND_ACT_RESULT_OUTPUT_PROPERTIES = { - success: { - type: 'boolean', - description: 'Whether the act operation completed successfully', - }, - message: { - type: 'string', - description: 'Detailed message about the actions performed', - }, - actionDescription: { - type: 'string', - description: 'High-level description of what was done', - optional: true, - }, - actions: { - type: 'array', - description: 'List of individual actions performed', - items: { - type: 'object', - properties: STAGEHAND_ACT_ACTION_OUTPUT_PROPERTIES, - }, - }, -} as const satisfies Record - -/** - * Output definition for extract() method when called without schema - * Returns pageText or extraction string - */ -export const STAGEHAND_SIMPLE_EXTRACT_OUTPUT_PROPERTIES = { - pageText: { - type: 'string', - description: 'Raw text content of the page (when no instruction provided)', - optional: true, - }, - extraction: { - type: 'string', - description: 'Extracted content based on instruction (when no schema provided)', - optional: true, - }, -} as const satisfies Record - -/** - * Output definition for extract() method result with schema - * The actual structure depends on the user-provided schema - */ -export const STAGEHAND_EXTRACT_OUTPUT_PROPERTIES = { - data: { - type: 'object', - description: 'Extracted structured data matching the provided schema', - }, - schema: { - type: 'object', - description: 'The schema that was used for extraction', - optional: true, - }, -} as const satisfies Record - export interface StagehandExtractParams { instruction: string schema: Record diff --git a/apps/sim/tools/stripe/types.ts b/apps/sim/tools/stripe/types.ts index 88725dfa7c5..d9cc4c52e5b 100644 --- a/apps/sim/tools/stripe/types.ts +++ b/apps/sim/tools/stripe/types.ts @@ -987,26 +987,6 @@ export const CHARGE_OUTPUT_PROPERTIES = { transfer_group: { type: 'string', description: 'Transfer group', optional: true }, } as const satisfies Record -/** - * Complete Charge object output definition - */ -export const CHARGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Stripe Charge object', - properties: CHARGE_OUTPUT_PROPERTIES, -} - -/** - * Output definition for Charge metadata (summary) - */ -export const CHARGE_METADATA_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Stripe unique identifier' }, - status: { type: 'string', description: 'Current state of the resource' }, - amount: { type: 'number', description: 'Amount in smallest currency unit (e.g., cents)' }, - currency: { type: 'string', description: 'Three-letter ISO currency code (lowercase)' }, - paid: { type: 'boolean', description: 'Whether payment has been received' }, -} as const satisfies Record - /** * Output definition for Product objects * @see https://docs.stripe.com/api/products/object @@ -1052,24 +1032,6 @@ export const PRODUCT_OUTPUT_PROPERTIES = { url: { type: 'string', description: 'URL of a publicly-accessible webpage', optional: true }, } as const satisfies Record -/** - * Complete Product object output definition - */ -export const PRODUCT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Stripe Product object', - properties: PRODUCT_OUTPUT_PROPERTIES, -} - -/** - * Output definition for Product metadata (summary) - */ -export const PRODUCT_METADATA_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Stripe unique identifier' }, - name: { type: 'string', description: 'Display name' }, - active: { type: 'boolean', description: 'Whether the resource is currently active' }, -} as const satisfies Record - /** * Output definition for Price recurring object * @see https://docs.stripe.com/api/prices/object#price_object-recurring @@ -1157,29 +1119,6 @@ export const PRICE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete Price object output definition - */ -export const PRICE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Stripe Price object', - properties: PRICE_OUTPUT_PROPERTIES, -} - -/** - * Output definition for Price metadata (summary) - */ -export const PRICE_METADATA_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Stripe unique identifier' }, - product: { type: 'string', description: 'Associated product ID' }, - unit_amount: { - type: 'number', - description: 'Amount in smallest currency unit (e.g., cents)', - optional: true, - }, - currency: { type: 'string', description: 'Three-letter ISO currency code (lowercase)' }, -} as const satisfies Record - /** * Output definition for Event request object * @see https://docs.stripe.com/api/events/object#event_object-request @@ -1239,24 +1178,6 @@ export const EVENT_OUTPUT_PROPERTIES = { type: { type: 'string', description: 'Event type (e.g., invoice.created, charge.refunded)' }, } as const satisfies Record -/** - * Complete Event object output definition - */ -export const EVENT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Stripe Event object', - properties: EVENT_OUTPUT_PROPERTIES, -} - -/** - * Output definition for Event metadata (summary) - */ -export const EVENT_METADATA_OUTPUT_PROPERTIES = { - id: { type: 'string', description: 'Stripe unique identifier' }, - type: { type: 'string', description: 'Event type identifier' }, - created: { type: 'number', description: 'Unix timestamp of creation' }, -} as const satisfies Record - /** * Pagination output properties for list endpoints */ diff --git a/apps/sim/tools/stt/types.ts b/apps/sim/tools/stt/types.ts index f8674623b2e..84772327b55 100644 --- a/apps/sim/tools/stt/types.ts +++ b/apps/sim/tools/stt/types.ts @@ -24,15 +24,6 @@ export const STT_SEGMENT_OUTPUT_PROPERTIES = { confidence: { type: 'number', description: 'Confidence score (0-1)', optional: true }, } as const satisfies Record -/** - * Complete segment output definition - */ -export const STT_SEGMENT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Transcript segment with timing information', - properties: STT_SEGMENT_OUTPUT_PROPERTIES, -} - /** * Output definition for sentiment analysis results (AssemblyAI). * @see https://www.assemblyai.com/docs/audio-intelligence/sentiment-analysis diff --git a/apps/sim/tools/supabase/types.ts b/apps/sim/tools/supabase/types.ts index 40a9c4313c3..b48f2d981a5 100644 --- a/apps/sim/tools/supabase/types.ts +++ b/apps/sim/tools/supabase/types.ts @@ -41,15 +41,6 @@ export const STORAGE_FILE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete storage file object output definition - */ -export const STORAGE_FILE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Storage file object with metadata', - properties: STORAGE_FILE_OUTPUT_PROPERTIES, -} - /** * Output definition for storage bucket objects * @see https://github.com/supabase/storage-js/blob/main/src/lib/types.ts @@ -74,15 +65,6 @@ export const STORAGE_BUCKET_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete storage bucket object output definition - */ -export const STORAGE_BUCKET_OUTPUT: OutputProperty = { - type: 'object', - description: 'Storage bucket object with configuration', - properties: STORAGE_BUCKET_OUTPUT_PROPERTIES, -} - /** * Output definition for storage upload response. * The Supabase Storage REST API returns `{ Id, Key }` (Key required); Sim's @@ -159,65 +141,6 @@ export const STORAGE_DOWNLOAD_OUTPUT_PROPERTIES = { size: { type: 'number', description: 'File size in bytes' }, } as const satisfies Record -/** - * Complete download file object output definition - */ -export const STORAGE_DOWNLOAD_FILE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Downloaded file with content and metadata', - properties: STORAGE_DOWNLOAD_OUTPUT_PROPERTIES, -} - -/** - * Output definition for public URL response - */ -export const STORAGE_PUBLIC_URL_OUTPUT_PROPERTIES = { - publicUrl: { type: 'string', description: 'The public URL to access the file' }, -} as const satisfies Record - -/** - * Output definition for signed URL response - */ -export const STORAGE_SIGNED_URL_OUTPUT_PROPERTIES = { - signedUrl: { type: 'string', description: 'The temporary signed URL to access the file' }, -} as const satisfies Record - -/** - * Storage files array output definition for list operations - */ -export const STORAGE_FILES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of file objects with metadata', - items: { - type: 'object', - properties: STORAGE_FILE_OUTPUT_PROPERTIES, - }, -} - -/** - * Storage buckets array output definition for list buckets operations - */ -export const STORAGE_BUCKETS_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of bucket objects', - items: { - type: 'object', - properties: STORAGE_BUCKET_OUTPUT_PROPERTIES, - }, -} - -/** - * Storage deleted files array output definition - */ -export const STORAGE_DELETED_FILES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of deleted file objects', - items: { - type: 'object', - properties: STORAGE_DELETED_FILE_OUTPUT_PROPERTIES, - }, -} - /** * Output definition for foreign key reference in column schema */ @@ -315,18 +238,6 @@ export const INTROSPECT_TABLE_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Introspect tables array output definition - */ -export const INTROSPECT_TABLES_OUTPUT: OutputProperty = { - type: 'array', - description: 'Array of table schemas with columns, keys, and indexes', - items: { - type: 'object', - properties: INTROSPECT_TABLE_OUTPUT_PROPERTIES, - }, -} - export interface SupabaseQueryParams { apiKey: string projectId: string diff --git a/apps/sim/tools/tavily/types.ts b/apps/sim/tools/tavily/types.ts index 8ad15732e90..090f23bf420 100644 --- a/apps/sim/tools/tavily/types.ts +++ b/apps/sim/tools/tavily/types.ts @@ -21,15 +21,6 @@ export const TAVILY_SEARCH_RESULT_OUTPUT_PROPERTIES = { favicon: { type: 'string', description: 'Favicon URL for the domain', optional: true }, } as const satisfies Record -/** - * Complete search result output definition - */ -export const TAVILY_SEARCH_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Search result item', - properties: TAVILY_SEARCH_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for image items in search results */ @@ -38,15 +29,6 @@ export const TAVILY_IMAGE_OUTPUT_PROPERTIES = { description: { type: 'string', description: 'Image description', optional: true }, } as const satisfies Record -/** - * Complete image output definition - */ -export const TAVILY_IMAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Image result', - properties: TAVILY_IMAGE_OUTPUT_PROPERTIES, -} - /** * Output definition for usage statistics */ @@ -54,15 +36,6 @@ export const TAVILY_USAGE_OUTPUT_PROPERTIES = { credits: { type: 'number', description: 'Number of credits consumed' }, } as const satisfies Record -/** - * Complete usage output definition - */ -export const TAVILY_USAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Credit usage details', - properties: TAVILY_USAGE_OUTPUT_PROPERTIES, -} - /** * Output definition for extract result items */ @@ -78,15 +51,6 @@ export const TAVILY_EXTRACT_RESULT_OUTPUT_PROPERTIES = { favicon: { type: 'string', description: 'Favicon URL for the result', optional: true }, } as const satisfies Record -/** - * Complete extract result output definition - */ -export const TAVILY_EXTRACT_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Extracted content from URL', - properties: TAVILY_EXTRACT_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for failed extraction items */ @@ -95,15 +59,6 @@ export const TAVILY_FAILED_RESULT_OUTPUT_PROPERTIES = { error: { type: 'string', description: 'Error message describing why extraction failed' }, } as const satisfies Record -/** - * Complete failed result output definition - */ -export const TAVILY_FAILED_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Failed extraction result', - properties: TAVILY_FAILED_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for crawl result items */ @@ -113,15 +68,6 @@ export const TAVILY_CRAWL_RESULT_OUTPUT_PROPERTIES = { favicon: { type: 'string', description: 'Favicon URL for the result', optional: true }, } as const satisfies Record -/** - * Complete crawl result output definition - */ -export const TAVILY_CRAWL_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Crawled page result', - properties: TAVILY_CRAWL_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for map result items */ @@ -129,15 +75,6 @@ export const TAVILY_MAP_RESULT_OUTPUT_PROPERTIES = { url: { type: 'string', description: 'Discovered URL' }, } as const satisfies Record -/** - * Complete map result output definition - */ -export const TAVILY_MAP_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Mapped URL result', - properties: TAVILY_MAP_RESULT_OUTPUT_PROPERTIES, -} - interface TavilySearchResult { title: string url: string diff --git a/apps/sim/tools/tts/types.ts b/apps/sim/tools/tts/types.ts index c8dbc04f153..6d634a615cd 100644 --- a/apps/sim/tools/tts/types.ts +++ b/apps/sim/tools/tts/types.ts @@ -141,149 +141,6 @@ export interface TtsResponse { provider?: TtsProvider } -// Voice options for different providers -export const OPENAI_VOICES = { - // All voices work with all models - alloy: 'Alloy (neutral, balanced)', - ash: 'Ash (masculine, clear)', - ballad: 'Ballad (smooth, melodic)', - coral: 'Coral (warm, friendly)', - echo: 'Echo (warm, masculine)', - marin: 'Marin (soft, gentle)', - cedar: 'Cedar (deep, resonant)', - sage: 'Sage (calm, wise)', - shimmer: 'Shimmer (warm, empathetic)', - verse: 'Verse (poetic, expressive)', -} as const - -export const DEEPGRAM_VOICES = { - // Aura-1 English voices (legacy) - 'aura-asteria-en': 'Asteria (Aura-1, American, warm female)', - 'aura-luna-en': 'Luna (Aura-1, American, professional female)', - 'aura-stella-en': 'Stella (Aura-1, American, energetic female)', - 'aura-athena-en': 'Athena (Aura-1, British, sophisticated female)', - 'aura-hera-en': 'Hera (Aura-1, American, mature female)', - 'aura-orion-en': 'Orion (Aura-1, American, confident male)', - 'aura-arcas-en': 'Arcas (Aura-1, American, professional male)', - 'aura-perseus-en': 'Perseus (Aura-1, American, strong male)', - 'aura-angus-en': 'Angus (Aura-1, Irish, friendly male)', - 'aura-orpheus-en': 'Orpheus (Aura-1, American, smooth male)', - 'aura-helios-en': 'Helios (Aura-1, British, authoritative male)', - 'aura-zeus-en': 'Zeus (Aura-1, American, deep male)', - - // Aura-2 English voices - 'aura-2-arcas-en': 'Arcas (Aura-2, American male)', - 'aura-2-asteria-en': 'Asteria (Aura-2, American female)', - 'aura-2-luna-en': 'Luna (Aura-2, American female)', - 'aura-2-stella-en': 'Stella (Aura-2, American female)', - 'aura-2-athena-en': 'Athena (Aura-2, British female)', - 'aura-2-hera-en': 'Hera (Aura-2, American female)', - 'aura-2-orion-en': 'Orion (Aura-2, American male)', - 'aura-2-perseus-en': 'Perseus (Aura-2, American male)', - 'aura-2-orpheus-en': 'Orpheus (Aura-2, American male)', - 'aura-2-helios-en': 'Helios (Aura-2, British male)', - 'aura-2-zeus-en': 'Zeus (Aura-2, American male)', - 'aura-2-angus-en': 'Angus (Aura-2, Irish male)', - 'aura-2-sasha-en': 'Sasha (Aura-2, American female)', - 'aura-2-sophia-en': 'Sophia (Aura-2, American female)', - 'aura-2-oliver-en': 'Oliver (Aura-2, American male)', - 'aura-2-emma-en': 'Emma (Aura-2, American female)', - 'aura-2-jack-en': 'Jack (Aura-2, American male)', - 'aura-2-lily-en': 'Lily (Aura-2, American female)', - 'aura-2-noah-en': 'Noah (Aura-2, American male)', - 'aura-2-mia-en': 'Mia (Aura-2, American female)', - 'aura-2-william-en': 'William (Aura-2, American male)', - 'aura-2-emily-en': 'Emily (Aura-2, American female)', - 'aura-2-james-en': 'James (Aura-2, American male)', - 'aura-2-ava-en': 'Ava (Aura-2, American female)', - 'aura-2-benjamin-en': 'Benjamin (Aura-2, American male)', - 'aura-2-charlotte-en': 'Charlotte (Aura-2, American female)', - 'aura-2-lucas-en': 'Lucas (Aura-2, American male)', - 'aura-2-harper-en': 'Harper (Aura-2, American female)', - 'aura-2-henry-en': 'Henry (Aura-2, American male)', - 'aura-2-evelyn-en': 'Evelyn (Aura-2, American female)', - 'aura-2-alexander-en': 'Alexander (Aura-2, American male)', - 'aura-2-abigail-en': 'Abigail (Aura-2, American female)', - 'aura-2-michael-en': 'Michael (Aura-2, American male)', - 'aura-2-sofia-en': 'Sofia (Aura-2, American female)', - 'aura-2-daniel-en': 'Daniel (Aura-2, American male)', - 'aura-2-ella-en': 'Ella (Aura-2, American female)', - 'aura-2-matthew-en': 'Matthew (Aura-2, American male)', - 'aura-2-grace-en': 'Grace (Aura-2, American female)', - 'aura-2-jackson-en': 'Jackson (Aura-2, American male)', - 'aura-2-chloe-en': 'Chloe (Aura-2, American female)', - 'aura-2-samuel-en': 'Samuel (Aura-2, American male)', - 'aura-2-madison-en': 'Madison (Aura-2, American female)', - - // Aura-2 Spanish voices - 'aura-2-maria-es': 'Maria (Aura-2, Spanish female)', - 'aura-2-carmen-es': 'Carmen (Aura-2, Spanish female)', - 'aura-2-carlos-es': 'Carlos (Aura-2, Spanish male)', - 'aura-2-diego-es': 'Diego (Aura-2, Spanish male)', - 'aura-2-isabel-es': 'Isabel (Aura-2, Spanish female)', - 'aura-2-juan-es': 'Juan (Aura-2, Spanish male)', - 'aura-2-lucia-es': 'Lucia (Aura-2, Spanish female)', - 'aura-2-miguel-es': 'Miguel (Aura-2, Spanish male)', - 'aura-2-sofia-es': 'Sofia (Aura-2, Spanish female)', - 'aura-2-antonio-es': 'Antonio (Aura-2, Spanish male)', -} as const - -export const ELEVENLABS_MODELS = { - // V2 Models - eleven_turbo_v2_5: 'Turbo v2.5 (faster, improved)', - eleven_flash_v2_5: 'Flash v2.5 (ultra-fast, 75ms latency)', - eleven_multilingual_v2: 'Multilingual v2 (32 languages)', - eleven_turbo_v2: 'Turbo v2 (fast, good quality)', - - // V1 Models - eleven_monolingual_v1: 'Monolingual v1 (English only)', - eleven_multilingual_v1: 'Multilingual v1', -} as const - -export const CARTESIA_MODELS = { - sonic: 'Sonic (English, low latency)', - 'sonic-2': 'Sonic 2 (English, improved)', - 'sonic-turbo': 'Sonic Turbo (English, ultra-fast)', - 'sonic-3': 'Sonic 3 (English, highest quality)', - 'sonic-multilingual': 'Sonic Multilingual (100+ languages)', -} as const - -export const GOOGLE_VOICE_GENDERS = { - MALE: 'Male', - FEMALE: 'Female', - NEUTRAL: 'Neutral', -} as const - -export const GOOGLE_AUDIO_ENCODINGS = { - LINEAR16: 'LINEAR16 (uncompressed)', - MP3: 'MP3 (compressed)', - OGG_OPUS: 'OGG Opus (compressed)', - MULAW: 'MULAW (8kHz)', - ALAW: 'ALAW (8kHz)', -} as const - -export const AZURE_OUTPUT_FORMATS = { - 'riff-8khz-16bit-mono-pcm': 'PCM 8kHz 16-bit', - 'riff-24khz-16bit-mono-pcm': 'PCM 24kHz 16-bit', - 'audio-24khz-48kbitrate-mono-mp3': 'MP3 24kHz 48kbps', - 'audio-24khz-96kbitrate-mono-mp3': 'MP3 24kHz 96kbps', - 'audio-48khz-96kbitrate-mono-mp3': 'MP3 48kHz 96kbps (high quality)', -} as const - -export const PLAYHT_QUALITY_LEVELS = { - draft: 'Draft (fastest)', - standard: 'Standard (recommended)', - premium: 'Premium (best quality)', -} as const - -export const PLAYHT_OUTPUT_FORMATS = { - mp3: 'MP3', - wav: 'WAV', - ogg: 'OGG', - flac: 'FLAC', - mulaw: 'MULAW', -} as const - // Audio format MIME types export const AUDIO_MIME_TYPES: Record = { mp3: 'audio/mpeg', diff --git a/apps/sim/tools/wealthbox/utils.ts b/apps/sim/tools/wealthbox/utils.ts index 34c072ced26..694f85920cb 100644 --- a/apps/sim/tools/wealthbox/utils.ts +++ b/apps/sim/tools/wealthbox/utils.ts @@ -93,13 +93,6 @@ const validateAndBuildNoteBody = (params: WealthboxWriteParams): Record { - throw new Error( - `Failed to create Wealthbox note: ${response.status} ${response.statusText} - ${errorText}` - ) -} - // Utility function to format note response export const formatNoteResponse = (data: any): WealthboxWriteResponse => { if (!data) { diff --git a/apps/sim/tools/webflow/types.ts b/apps/sim/tools/webflow/types.ts index eff4262bb95..d5faa59b1cf 100644 --- a/apps/sim/tools/webflow/types.ts +++ b/apps/sim/tools/webflow/types.ts @@ -25,15 +25,6 @@ export const WEBFLOW_ITEM_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete item output definition - */ -export const WEBFLOW_ITEM_OUTPUT: OutputProperty = { - type: 'object', - description: 'Webflow CMS collection item', - properties: WEBFLOW_ITEM_OUTPUT_PROPERTIES, -} - /** * Output definition for list metadata. */ diff --git a/apps/sim/tools/wikipedia/types.ts b/apps/sim/tools/wikipedia/types.ts index 90954fb97c6..f09b8e97ce9 100644 --- a/apps/sim/tools/wikipedia/types.ts +++ b/apps/sim/tools/wikipedia/types.ts @@ -94,15 +94,6 @@ export const WIKIPEDIA_PAGE_SUMMARY_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete page summary output definition - */ -export const WIKIPEDIA_PAGE_SUMMARY_OUTPUT: OutputProperty = { - type: 'object', - description: 'Wikipedia page summary and metadata', - properties: WIKIPEDIA_PAGE_SUMMARY_OUTPUT_PROPERTIES, -} - /** * Output definition for search result items */ @@ -129,15 +120,6 @@ export const WIKIPEDIA_SEARCH_RESULT_OUTPUT_PROPERTIES = { url: { type: 'string', description: 'Page URL' }, } as const satisfies Record -/** - * Complete search result output definition - */ -export const WIKIPEDIA_SEARCH_RESULT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Wikipedia search result', - properties: WIKIPEDIA_SEARCH_RESULT_OUTPUT_PROPERTIES, -} - /** * Output definition for page content objects */ @@ -152,15 +134,6 @@ export const WIKIPEDIA_PAGE_CONTENT_OUTPUT_PROPERTIES = { content_format: { type: 'string', description: 'Content format (text/html)' }, } as const satisfies Record -/** - * Complete page content output definition - */ -export const WIKIPEDIA_PAGE_CONTENT_OUTPUT: OutputProperty = { - type: 'object', - description: 'Full HTML content and metadata of the Wikipedia page', - properties: WIKIPEDIA_PAGE_CONTENT_OUTPUT_PROPERTIES, -} - /** * Output definition for random page objects (subset of summary) */ @@ -192,15 +165,6 @@ export const WIKIPEDIA_RANDOM_PAGE_OUTPUT_PROPERTIES = { pageid: { type: 'number', description: 'Page ID' }, } as const satisfies Record -/** - * Complete random page output definition - */ -export const WIKIPEDIA_RANDOM_PAGE_OUTPUT: OutputProperty = { - type: 'object', - description: 'Random Wikipedia page data', - properties: WIKIPEDIA_RANDOM_PAGE_OUTPUT_PROPERTIES, -} - // Page Summary tool types export interface WikipediaPageSummaryParams { pageTitle: string diff --git a/apps/sim/tools/workday/soap.ts b/apps/sim/tools/workday/soap.ts index 96c90672165..6c716f855c5 100644 --- a/apps/sim/tools/workday/soap.ts +++ b/apps/sim/tools/workday/soap.ts @@ -192,18 +192,6 @@ export function buildServiceUrl( return `${baseUrl}/ccx/service/${tenant}/${svc.name}/${svc.version}` } -/** - * Builds the WSDL URL for a Workday SOAP service. Retained for backwards compatibility - * with any external consumers; the runtime no longer fetches the WSDL. - */ -export function buildWsdlUrl( - tenantUrl: string, - tenant: string, - service: WorkdayServiceKey -): string { - return `${buildServiceUrl(tenantUrl, tenant, service)}?wsdl` -} - const XML_ENTITIES: Record = { '&': '&', '<': '<', diff --git a/apps/sim/tools/zep/types.ts b/apps/sim/tools/zep/types.ts index 656f719753a..58048c25b83 100644 --- a/apps/sim/tools/zep/types.ts +++ b/apps/sim/tools/zep/types.ts @@ -27,15 +27,6 @@ export const THREAD_OUTPUT_PROPERTIES = { }, } as const satisfies Record -/** - * Complete thread object output definition. - */ -export const THREAD_OUTPUT: OutputProperty = { - type: 'object', - description: 'Zep thread object', - properties: THREAD_OUTPUT_PROPERTIES, -} - /** * Threads array output definition for list endpoints. * @see https://help.getzep.com/sdk-reference/thread/list-all diff --git a/apps/sim/tools/zoho/types.ts b/apps/sim/tools/zoho/types.ts index 0a7271591ef..2efd5d1ac1b 100644 --- a/apps/sim/tools/zoho/types.ts +++ b/apps/sim/tools/zoho/types.ts @@ -22,21 +22,6 @@ export interface ZohoCrmPageInfo { moreRecords: boolean } -export const ZOHO_CRM_PAGE_INFO_OUTPUT = { - type: 'object' as const, - description: 'Pagination metadata returned by Zoho CRM', - properties: { - page: { type: 'number' as const, description: 'Current page number', optional: true }, - perPage: { type: 'number' as const, description: 'Records requested per page', optional: true }, - count: { - type: 'number' as const, - description: 'Number of records in this page', - optional: true, - }, - moreRecords: { type: 'boolean' as const, description: 'Whether further pages are available' }, - }, -} - /** A CRM write result entry, as returned in the `data` array of insert/update/upsert. */ export interface ZohoCrmWriteResult { id: string | null @@ -45,25 +30,6 @@ export interface ZohoCrmWriteResult { message: string | null } -export const ZOHO_CRM_WRITE_RESULT_OUTPUT = { - type: 'array' as const, - description: 'Per-record results returned by Zoho CRM', - properties: { - id: { type: 'string' as const, description: 'Record ID', optional: true }, - code: { - type: 'string' as const, - description: 'Zoho result code (e.g. SUCCESS)', - optional: true, - }, - status: { type: 'string' as const, description: 'Result status', optional: true }, - message: { - type: 'string' as const, - description: 'Human-readable result message', - optional: true, - }, - }, -} - export interface ZohoCrmGetRecordsParams extends ZohoBaseParams { module: string recordId?: string diff --git a/apps/sim/tools/zoho/utils.ts b/apps/sim/tools/zoho/utils.ts index be49435be0d..0bbde91ebf8 100644 --- a/apps/sim/tools/zoho/utils.ts +++ b/apps/sim/tools/zoho/utils.ts @@ -1,5 +1,3 @@ -import { truncate } from '@sim/utils/string' - /** * Zoho data centers Sim supports. * @@ -27,63 +25,9 @@ export const ZOHO_DATA_CENTERS = { export type ZohoDataCenter = keyof typeof ZOHO_DATA_CENTERS -const DEFAULT_DATA_CENTER: ZohoDataCenter = 'us' - /** Zoho CRM API version pinned across every CRM tool. */ export const ZOHO_CRM_API_VERSION = 'v8' -function resolveDataCenter(dataCenter?: string): ZohoDataCenter { - const normalized = dataCenter?.trim().toLowerCase() - if (normalized && normalized in ZOHO_DATA_CENTERS) { - return normalized as ZohoDataCenter - } - return DEFAULT_DATA_CENTER -} - -/** - * Returns the CRM API base URL (including version segment) for a data center. - * Falls back to the US region when unset or unrecognized. - */ -export function getCrmBaseUrl(dataCenter?: string): string { - return `${ZOHO_DATA_CENTERS[resolveDataCenter(dataCenter)].crm}/crm/${ZOHO_CRM_API_VERSION}` -} - -/** - * Returns the Desk API base URL (including the `/api/v1` segment) for a data - * center. Falls back to the US region when unset or unrecognized. - */ -export function getDeskBaseUrl(dataCenter?: string): string { - return `${ZOHO_DATA_CENTERS[resolveDataCenter(dataCenter)].desk}/api/v1` -} - -/** - * Zoho authenticates with a bespoke scheme rather than `Bearer`. - * @see https://www.zoho.com/crm/developer/docs/api/v8/access-refresh.html - */ -export function buildZohoHeaders(accessToken: string, orgId?: string): Record { - const headers: Record = { - Authorization: `Zoho-oauthtoken ${accessToken}`, - 'Content-Type': 'application/json', - } - if (orgId?.trim()) { - headers.orgId = orgId.trim() - } - return headers -} - -/** - * Trims a required identifier and throws when it is missing or whitespace-only, - * so a blank value can never collapse into an empty URL path segment and send a - * malformed request to Zoho. - */ -export function requireZohoId(value: string | undefined, label: string): string { - const trimmed = value?.trim() - if (!trimmed) { - throw new Error(`${label} is required.`) - } - return trimmed -} - /** * Parses a JSON object supplied either as an object (from the LLM) or as a JSON * string (from a block input), and rejects anything that is not a plain object. @@ -107,17 +51,6 @@ export function parseJsonObject( return parsed as Record } -/** - * Coerces a numeric-ish param into a positive integer, clamped to `max`. - * Returns undefined when unset or unparseable so callers can omit the param. - */ -export function toPositiveInt(value: unknown, max: number): number | undefined { - if (value === undefined || value === null || value === '') return undefined - const parsed = Number(value) - if (!Number.isFinite(parsed) || parsed < 1) return undefined - return Math.min(Math.floor(parsed), max) -} - /** * Appends only the defined entries of `params` to a URL's query string, so unset * optional params never surface as empty values Zoho would reject. @@ -131,62 +64,3 @@ export function buildQuery(params: Record): const query = search.toString() return query ? `?${query}` : '' } - -/** - * Reads a Zoho response body as JSON, tolerating the empty bodies Zoho returns - * for "no matching records" — CRM answers `204 No Content` on an empty list or - * search, and Desk does the same, so a bare `response.json()` would throw on a - * perfectly successful call. - */ -export async function readZohoJson(response: Response): Promise { - if (response.status === 204) return {} - const text = await response.text() - if (!text.trim()) return {} - try { - return JSON.parse(text) - } catch { - return { message: truncate(text, 500) } - } -} - -/** - * Extracts a descriptive message from a Zoho error payload. - * - * CRM returns `{ code, message, status, details }`; Desk returns - * `{ errorCode, message }`. Both are handled, with an HTTP-status fallback. - */ -export function extractZohoErrorMessage( - data: unknown, - status: number, - defaultMessage: string -): string { - const payload = data as - | { message?: unknown; code?: unknown; errorCode?: unknown; data?: unknown } - | undefined - - const nested = Array.isArray(payload?.data) ? payload.data[0] : undefined - const record = (nested ?? payload) as - | { message?: unknown; code?: unknown; errorCode?: unknown } - | undefined - - if (record && typeof record.message === 'string' && record.message.trim()) { - const code = record.code ?? record.errorCode - const suffix = typeof code === 'string' && code.trim() ? ` [${code}]` : '' - return `Zoho API Error (${status}): ${record.message}${suffix}` - } - - switch (status) { - case 400: - return `Zoho API Error (400): Bad Request — the request was malformed or missing required parameters.` - case 401: - return `Zoho API Error (401): Unauthorized — the access token is invalid or expired. Please reconnect your Zoho account.` - case 403: - return `Zoho API Error (403): Forbidden — your Zoho account lacks permission, or the required OAuth scope was not granted.` - case 404: - return `Zoho API Error (404): Not Found — the requested record does not exist or is not visible to you.` - case 429: - return `Zoho API Error (429): Rate limit exceeded — too many API calls. Please retry later.` - default: - return `${defaultMessage} (HTTP ${status})` - } -} diff --git a/apps/sim/tools/zoom/types.ts b/apps/sim/tools/zoom/types.ts index b521b2eb5cd..abce6f218f0 100644 --- a/apps/sim/tools/zoom/types.ts +++ b/apps/sim/tools/zoom/types.ts @@ -116,15 +116,6 @@ export const MEETING_OUTPUT_PROPERTIES = { occurrences: OCCURRENCES_OUTPUT, } as const satisfies Record -/** - * Output definition for meeting object (used in create/get meeting responses) - */ -export const MEETING_OUTPUT: OutputProperty = { - type: 'object', - description: 'Meeting object with all properties', - properties: MEETING_OUTPUT_PROPERTIES, -} - /** * Meeting list item output properties (subset returned in list responses) * @see https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/meetings @@ -143,18 +134,6 @@ export const MEETING_LIST_ITEM_OUTPUT_PROPERTIES = { join_url: { type: 'string', description: 'URL for participants to join' }, } as const satisfies Record -/** - * Output definition for meetings array in list responses - */ -export const MEETINGS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'List of meetings', - items: { - type: 'object', - properties: MEETING_LIST_ITEM_OUTPUT_PROPERTIES, - }, -} - /** * Pagination output properties for meeting list endpoints * @see https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/meetings @@ -167,15 +146,6 @@ export const MEETING_PAGE_INFO_OUTPUT_PROPERTIES = { nextPageToken: { type: 'string', description: 'Token for next page of results' }, } as const satisfies Record -/** - * Complete page info output definition for meeting lists - */ -export const MEETING_PAGE_INFO_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pagination information', - properties: MEETING_PAGE_INFO_OUTPUT_PROPERTIES, -} - /** * Output definition for recording file objects * @see https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/recordingGet @@ -229,27 +199,6 @@ export const RECORDING_OUTPUT_PROPERTIES = { recording_files: RECORDING_FILES_OUTPUT, } as const satisfies Record -/** - * Complete recording object output definition - */ -export const RECORDING_OUTPUT: OutputProperty = { - type: 'object', - description: 'Recording object with all files', - properties: RECORDING_OUTPUT_PROPERTIES, -} - -/** - * Output definition for recordings array in list responses - */ -export const RECORDINGS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'List of recordings', - items: { - type: 'object', - properties: RECORDING_OUTPUT_PROPERTIES, - }, -} - /** * Pagination output properties for recording list endpoints * @see https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/recordingsList @@ -262,15 +211,6 @@ export const RECORDING_PAGE_INFO_OUTPUT_PROPERTIES = { nextPageToken: { type: 'string', description: 'Token for next page of results' }, } as const satisfies Record -/** - * Complete page info output definition for recording lists - */ -export const RECORDING_PAGE_INFO_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pagination information', - properties: RECORDING_PAGE_INFO_OUTPUT_PROPERTIES, -} - /** * Output definition for participant objects * @see https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/pastMeetingParticipants @@ -291,18 +231,6 @@ export const PARTICIPANT_OUTPUT_PROPERTIES = { status: { type: 'string', description: 'Participant status' }, } as const satisfies Record -/** - * Complete participants array output definition - */ -export const PARTICIPANTS_ARRAY_OUTPUT: OutputProperty = { - type: 'array', - description: 'List of meeting participants', - items: { - type: 'object', - properties: PARTICIPANT_OUTPUT_PROPERTIES, - }, -} - /** * Pagination output properties for participant list endpoints * @see https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/pastMeetingParticipants @@ -313,15 +241,6 @@ export const PARTICIPANT_PAGE_INFO_OUTPUT_PROPERTIES = { nextPageToken: { type: 'string', description: 'Token for next page of results' }, } as const satisfies Record -/** - * Complete page info output definition for participant lists - */ -export const PARTICIPANT_PAGE_INFO_OUTPUT: OutputProperty = { - type: 'object', - description: 'Pagination information', - properties: PARTICIPANT_PAGE_INFO_OUTPUT_PROPERTIES, -} - // Common parameters for all Zoom tools interface ZoomBaseParams { accessToken: string diff --git a/apps/sim/triggers/calendly/utils.ts b/apps/sim/triggers/calendly/utils.ts index e3d0993d4cd..bc2bc56ffaa 100644 --- a/apps/sim/triggers/calendly/utils.ts +++ b/apps/sim/triggers/calendly/utils.ts @@ -10,34 +10,6 @@ export const calendlyTriggerOptions = [ { label: 'General Webhook (All Events)', id: 'calendly_webhook' }, ] -/** - * Generate setup instructions for a specific Calendly event type - */ -export function calendlySetupInstructions(eventType: string, additionalNotes?: string): string { - const instructions = [ - 'Note: Webhooks require a paid Calendly subscription (Professional, Teams, or Enterprise plan).', - 'Important: Calendly does not provide a UI for creating webhooks. You must create them programmatically using the API. See the Calendly Developer documentation for details.', - 'Get your Calendly Personal Access Token from the Calendly dashboard under Integrations > API & Webhooks.', - 'In your workflow, add a Calendly block and select the "Create Webhook" operation.', - 'Enter your Personal Access Token in the Calendly block.', - 'Copy the Webhook URL shown above and paste it into the webhook URL field in the Create Webhook operation.', - `Select the event types to monitor. For this trigger, select ${eventType}.`, - 'Set the scope to Organization or User as needed (routing form submissions require organization scope).', - 'Run the workflow to create the webhook subscription. You can use the "List Webhooks" operation to verify it was created.', - ] - - if (additionalNotes) { - instructions.push(additionalNotes) - } - - return instructions - .map( - (instruction, index) => - `
${index === 0 ? instruction : `${index}. ${instruction}`}
` - ) - .join('') -} - /** * Shared tracking output schema */ @@ -321,21 +293,3 @@ export function buildRoutingFormOutputs(): Record { }, } as any } - -/** - * Check if a Calendly event matches the expected trigger configuration - */ -export function isCalendlyEventMatch(triggerId: string, eventType: string): boolean { - const eventMap: Record = { - calendly_invitee_created: 'invitee.created', - calendly_invitee_canceled: 'invitee.canceled', - calendly_routing_form_submitted: 'routing_form_submission.created', - } - - const expectedEvent = eventMap[triggerId] - if (!expectedEvent) { - return true // Unknown trigger or general webhook, allow through - } - - return expectedEvent === eventType -} diff --git a/apps/sim/triggers/github/utils.ts b/apps/sim/triggers/github/utils.ts index d56634d7bd1..4da55d23863 100644 --- a/apps/sim/triggers/github/utils.ts +++ b/apps/sim/triggers/github/utils.ts @@ -1,97 +1,5 @@ import { isRecordLike } from '@sim/utils/object' -/** - * Shared repository output schema - */ -export const repositoryOutputs = { - id: { - type: 'number', - description: 'Repository ID', - }, - node_id: { - type: 'string', - description: 'Repository node ID', - }, - name: { - type: 'string', - description: 'Repository name', - }, - full_name: { - type: 'string', - description: 'Repository full name (owner/repo)', - }, - private: { - type: 'boolean', - description: 'Whether the repository is private', - }, - html_url: { - type: 'string', - description: 'Repository HTML URL', - }, - description: { - type: 'string', - description: 'Repository description', - }, - fork: { - type: 'boolean', - description: 'Whether the repository is a fork', - }, - url: { - type: 'string', - description: 'Repository API URL', - }, - homepage: { - type: 'string', - description: 'Repository homepage URL', - }, - size: { - type: 'number', - description: 'Repository size in KB', - }, - stargazers_count: { - type: 'number', - description: 'Number of stars', - }, - watchers_count: { - type: 'number', - description: 'Number of watchers', - }, - language: { - type: 'string', - description: 'Primary programming language', - }, - forks_count: { - type: 'number', - description: 'Number of forks', - }, - open_issues_count: { - type: 'number', - description: 'Number of open issues', - }, - default_branch: { - type: 'string', - description: 'Default branch name', - }, - owner: { - login: { - type: 'string', - description: 'Owner username', - }, - id: { - type: 'number', - description: 'Owner ID', - }, - avatar_url: { - type: 'string', - description: 'Owner avatar URL', - }, - html_url: { - type: 'string', - description: 'Owner profile URL', - }, - }, -} as const - /** * Shared sender/user output schema */ diff --git a/apps/sim/triggers/index.ts b/apps/sim/triggers/index.ts index 2f472f7de07..b6a01a9f57a 100644 --- a/apps/sim/triggers/index.ts +++ b/apps/sim/triggers/index.ts @@ -116,20 +116,10 @@ export function getTrigger(triggerId: string): TriggerConfig { return clonedTrigger } -export function getTriggersByProvider(provider: string): TriggerConfig[] { - return Object.values(TRIGGER_REGISTRY) - .filter((trigger) => trigger.provider === provider) - .map((trigger) => getTrigger(trigger.id)) -} - export function getAllTriggers(): TriggerConfig[] { return Object.keys(TRIGGER_REGISTRY).map((triggerId) => getTrigger(triggerId)) } -export function getTriggerIds(): string[] { - return Object.keys(TRIGGER_REGISTRY) -} - export function isTriggerValid(triggerId: string): boolean { return triggerId in TRIGGER_REGISTRY }