From 3d4b9f06657b5a8b421bf4b0b8601528a2a5d432 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 9 Sep 2025 17:18:08 -0700 Subject: [PATCH] feat(mcp): added support for mcp servers (#1296) * update infra and remove railway * feat(mcp): add mcp support * consolidate mcp utils * UI improvements, more MCP stuff * cleanup placeholders * reran migrations * general improvements * fix server side mcp exec * more improvements, fixed search in environment settings tab * persist subblock values for mcp block * style fixes * udpdate all text-primary to text-muted-foreground for visibility in dark mode * Revert "update infra and remove railway" This reverts commit dbf2b153b8f96808e7bb7e5f86f7e8624e3c12dd. * make MCP servers workspace-scoped * cleanup & remove unused dep * consolidated utils, DRY * added tests * better error messages, confirmed that permissions works correctly * additional improvements * remove extraneous comments * reran migrations * lint * style changes * fix: prevent config mutation in MCP client URL retry logic Fixed an issue where the MCP client was mutating the shared configuration object's URL during retry attempts. This could cause configuration corruption if the same config object was reused elsewhere. * resolve PR comments * ack PR comments --- apps/sim/app/(auth)/reset-password/page.tsx | 2 +- apps/sim/app/api/__test-utils__/setup.ts | 5 - apps/sim/app/api/__test-utils__/utils.ts | 18 - .../app/api/auth/oauth/connections/route.ts | 3 +- .../app/api/auth/oauth/credentials/route.ts | 3 +- .../app/api/auth/oauth/disconnect/route.ts | 3 +- .../api/auth/oauth/microsoft/file/route.ts | 3 +- .../api/auth/oauth/microsoft/files/route.ts | 3 +- apps/sim/app/api/auth/oauth/token/route.ts | 5 +- .../api/auth/oauth/wealthbox/item/route.ts | 3 +- .../api/auth/oauth/wealthbox/items/route.ts | 3 +- apps/sim/app/api/billing/update-cost/route.ts | 4 +- .../sim/app/api/chat/[subdomain]/otp/route.ts | 5 +- apps/sim/app/api/chat/[subdomain]/route.ts | 5 +- apps/sim/app/api/chat/utils.test.ts | 23 +- apps/sim/app/api/chat/utils.ts | 4 +- apps/sim/app/api/copilot/chat/route.test.ts | 1 + apps/sim/app/api/environment/route.ts | 6 +- apps/sim/app/api/function/execute/route.ts | 4 +- apps/sim/app/api/help/route.ts | 3 +- apps/sim/app/api/jobs/[jobId]/route.ts | 3 +- .../documents/[documentId]/chunks/route.ts | 8 +- .../[id]/documents/[documentId]/route.ts | 7 +- apps/sim/app/api/knowledge/[id]/route.ts | 7 +- apps/sim/app/api/knowledge/route.ts | 5 +- .../app/api/knowledge/search/route.test.ts | 4 + apps/sim/app/api/knowledge/search/route.ts | 3 +- apps/sim/app/api/logs/[id]/route.ts | 3 +- apps/sim/app/api/logs/route.ts | 36 +- .../app/api/mcp/servers/[id]/refresh/route.ts | 99 + apps/sim/app/api/mcp/servers/[id]/route.ts | 92 + apps/sim/app/api/mcp/servers/route.ts | 166 + .../api/mcp/servers/test-connection/route.ts | 209 + apps/sim/app/api/mcp/tools/discover/route.ts | 122 + apps/sim/app/api/mcp/tools/execute/route.ts | 252 + apps/sim/app/api/memory/[id]/route.ts | 7 +- apps/sim/app/api/memory/route.ts | 5 +- apps/sim/app/api/providers/route.ts | 5 +- apps/sim/app/api/proxy/image/route.ts | 3 +- apps/sim/app/api/proxy/route.ts | 5 +- apps/sim/app/api/schedules/[id]/route.ts | 6 +- .../app/api/schedules/[id]/status/route.ts | 3 +- apps/sim/app/api/schedules/execute/route.ts | 5 +- apps/sim/app/api/schedules/route.ts | 6 +- apps/sim/app/api/templates/[id]/route.ts | 7 +- apps/sim/app/api/templates/[id]/star/route.ts | 7 +- apps/sim/app/api/templates/[id]/use/route.ts | 3 +- apps/sim/app/api/templates/route.ts | 5 +- apps/sim/app/api/tools/custom/route.ts | 7 +- apps/sim/app/api/tools/drive/file/route.ts | 13 +- apps/sim/app/api/tools/drive/files/route.ts | 11 +- apps/sim/app/api/tools/gmail/label/route.ts | 3 +- apps/sim/app/api/tools/gmail/labels/route.ts | 4 +- .../tools/google_calendar/calendars/route.ts | 4 +- .../app/api/tools/linear/projects/route.ts | 3 +- apps/sim/app/api/tools/linear/teams/route.ts | 3 +- .../api/tools/microsoft-teams/chats/route.ts | 1 - .../api/tools/microsoft-teams/teams/route.ts | 3 +- .../app/api/tools/outlook/folders/route.ts | 3 +- .../sim/app/api/tools/slack/channels/route.ts | 3 +- apps/sim/app/api/tools/thinking/route.ts | 3 +- .../sim/app/api/tools/wealthbox/item/route.ts | 3 +- .../app/api/tools/wealthbox/items/route.ts | 3 +- .../app/api/users/me/api-keys/[id]/route.ts | 3 +- apps/sim/app/api/users/me/profile/route.ts | 5 +- apps/sim/app/api/users/me/settings/route.ts | 5 +- .../users/me/settings/unsubscribe/route.ts | 5 +- apps/sim/app/api/wand-generate/route.ts | 3 +- apps/sim/app/api/webhooks/[id]/route.ts | 7 +- apps/sim/app/api/webhooks/route.ts | 5 +- apps/sim/app/api/webhooks/test/route.ts | 3 +- .../app/api/webhooks/trigger/[path]/route.ts | 5 +- .../api/workflows/[id]/autolayout/route.ts | 3 +- .../api/workflows/[id]/chat/status/route.ts | 3 +- .../api/workflows/[id]/deploy/route.test.ts | 1 + .../app/api/workflows/[id]/deploy/route.ts | 8 +- .../app/api/workflows/[id]/deployed/route.ts | 3 +- .../app/api/workflows/[id]/duplicate/route.ts | 4 +- .../api/workflows/[id]/execute/route.test.ts | 1 + .../app/api/workflows/[id]/execute/route.ts | 6 +- apps/sim/app/api/workflows/[id]/log/route.ts | 3 +- .../[id]/revert-to-deployed/route.ts | 4 +- apps/sim/app/api/workflows/[id]/route.ts | 7 +- .../sim/app/api/workflows/[id]/state/route.ts | 3 +- .../app/api/workflows/[id]/status/route.ts | 3 +- .../app/api/workflows/[id]/variables/route.ts | 5 +- apps/sim/app/api/workflows/[id]/yaml/route.ts | 3 +- .../app/api/workflows/public/[id]/route.ts | 3 +- apps/sim/app/api/workflows/route.ts | 6 +- .../app/api/workflows/yaml/convert/route.ts | 3 +- .../app/api/workflows/yaml/export/route.ts | 3 +- .../api/workspaces/[id]/environment/route.ts | 8 +- apps/sim/app/api/workspaces/route.ts | 1 - apps/sim/app/api/yaml/autolayout/route.ts | 3 +- apps/sim/app/api/yaml/diff/create/route.ts | 3 +- apps/sim/app/api/yaml/diff/merge/route.ts | 3 +- apps/sim/app/api/yaml/generate/route.ts | 3 +- apps/sim/app/api/yaml/health/route.ts | 3 +- apps/sim/app/api/yaml/parse/route.ts | 3 +- apps/sim/app/api/yaml/to-workflow/route.ts | 3 +- .../chat/components/auth/email/email-auth.tsx | 4 +- apps/sim/app/unsubscribe/unsubscribe.tsx | 15 +- .../components/document-loading.tsx | 2 +- .../edit-chunk-modal/edit-chunk-modal.tsx | 2 +- .../knowledge-base-loading.tsx | 2 +- .../components/tag-input/tag-input.tsx | 4 +- .../[workspaceId]/knowledge/loading.tsx | 2 +- .../components/filters/components/folder.tsx | 4 +- .../components/filters/components/level.tsx | 4 +- .../filters/components/timeline.tsx | 4 +- .../components/filters/components/trigger.tsx | 6 +- .../filters/components/workflow.tsx | 6 +- .../chat-deploy/components/auth-selector.tsx | 2 +- .../components/deploy-form/deploy-form.tsx | 2 +- .../example-command/example-command.tsx | 4 +- .../template-modal/template-modal.tsx | 2 +- .../output-select/output-select.tsx | 2 +- .../credential-selector.tsx | 2 +- .../components/confluence-file-selector.tsx | 4 +- .../components/google-drive-picker.tsx | 4 +- .../components/jira-issue-selector.tsx | 4 +- .../components/microsoft-file-selector.tsx | 6 +- .../components/teams-message-selector.tsx | 4 +- .../components/wealthbox-file-selector.tsx | 2 +- .../sub-block/components/file-upload.tsx | 12 +- .../folder-selector/folder-selector.tsx | 2 +- .../components/sub-block/components/index.ts | 3 + .../mcp-dynamic-args/mcp-dynamic-args.tsx | 282 + .../mcp-server-modal/mcp-server-selector.tsx | 138 + .../mcp-server-modal/mcp-tool-selector.tsx | 165 + .../components/jira-project-selector.tsx | 4 +- .../mcp-server-modal/mcp-server-modal.tsx | 563 ++ .../tool-input/components/mcp-tools-list.tsx | 98 + .../components/tool-input/tool-input.tsx | 162 +- .../components/trigger-instructions.tsx | 2 +- .../webhook/components/providers/outlook.tsx | 2 +- .../webhook/components/providers/slack.tsx | 2 +- .../webhook/components/providers/telegram.tsx | 2 +- .../components/ui/instructions-section.tsx | 4 +- .../components/sub-block/sub-block.tsx | 33 + .../components/help-modal/help-modal.tsx | 2 +- .../components/api-keys/api-keys.tsx | 2 +- .../components/credentials/credentials.tsx | 10 +- .../components/environment/environment.tsx | 98 +- .../settings-modal/components/index.ts | 1 + .../settings-modal/components/mcp/mcp.tsx | 1080 +++ .../settings-navigation.tsx | 8 + .../components/team-members/team-members.tsx | 4 +- .../settings-modal/settings-modal.tsx | 7 + apps/sim/blocks/blocks/agent.test.ts | 1 + apps/sim/blocks/blocks/agent.ts | 11 +- apps/sim/blocks/blocks/mcp.ts | 101 + apps/sim/blocks/registry.ts | 2 + apps/sim/blocks/types.ts | 3 + apps/sim/components/icons.tsx | 20 + apps/sim/components/ui/env-var-dropdown.tsx | 33 +- apps/sim/components/ui/tag-dropdown.test.tsx | 2 +- .../sim/db/migrations/0087_wealthy_landau.sql | 28 + .../sim/db/migrations/meta/0087_snapshot.json | 6563 +++++++++++++++++ apps/sim/db/migrations/meta/_journal.json | 7 + apps/sim/db/schema.ts | 51 + .../executor/__test-utils__/executor-mocks.ts | 4 - .../handlers/agent/agent-handler.test.ts | 338 +- .../executor/handlers/agent/agent-handler.ts | 166 +- apps/sim/executor/handlers/api/api-handler.ts | 5 +- .../handlers/function/function-handler.ts | 5 +- .../handlers/generic/generic-handler.ts | 59 +- apps/sim/executor/index.test.ts | 61 +- apps/sim/hooks/use-mcp-server-test.ts | 144 + apps/sim/hooks/use-mcp-tools.ts | 205 + apps/sim/lib/copilot/auth.ts | 3 +- .../server/user/get-oauth-credentials.ts | 3 +- apps/sim/lib/mcp/client.ts | 597 ++ apps/sim/lib/mcp/middleware.ts | 207 + apps/sim/lib/mcp/service.ts | 586 ++ apps/sim/lib/mcp/types.ts | 271 + apps/sim/lib/mcp/url-validator.ts | 178 + apps/sim/lib/mcp/utils.ts | 142 + apps/sim/lib/sim-agent/client.ts | 3 +- apps/sim/lib/utils.ts | 7 + apps/sim/providers/types.ts | 1 + apps/sim/providers/utils.ts | 2 + apps/sim/stores/mcp-servers/store.ts | 186 + apps/sim/stores/mcp-servers/types.ts | 70 + apps/sim/stores/panel/console/store.test.ts | 10 +- apps/sim/tools/function/execute.test.ts | 35 +- apps/sim/tools/http/request.test.ts | 86 +- apps/sim/tools/index.test.ts | 263 +- apps/sim/tools/index.ts | 198 +- apps/sim/tools/utils.test.ts | 12 - apps/sim/triggers/slack/webhook.ts | 2 +- apps/sim/triggers/telegram/webhook.ts | 2 +- apps/sim/triggers/whatsapp/webhook.ts | 2 +- 193 files changed, 14250 insertions(+), 603 deletions(-) create mode 100644 apps/sim/app/api/mcp/servers/[id]/refresh/route.ts create mode 100644 apps/sim/app/api/mcp/servers/[id]/route.ts create mode 100644 apps/sim/app/api/mcp/servers/route.ts create mode 100644 apps/sim/app/api/mcp/servers/test-connection/route.ts create mode 100644 apps/sim/app/api/mcp/tools/discover/route.ts create mode 100644 apps/sim/app/api/mcp/tools/execute/route.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/mcp-dynamic-args/mcp-dynamic-args.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/mcp-server-modal/mcp-server-selector.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/mcp-server-modal/mcp-tool-selector.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/mcp-server-modal/mcp-server-modal.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/mcp-tools-list.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx create mode 100644 apps/sim/blocks/blocks/mcp.ts create mode 100644 apps/sim/db/migrations/0087_wealthy_landau.sql create mode 100644 apps/sim/db/migrations/meta/0087_snapshot.json create mode 100644 apps/sim/hooks/use-mcp-server-test.ts create mode 100644 apps/sim/hooks/use-mcp-tools.ts create mode 100644 apps/sim/lib/mcp/client.ts create mode 100644 apps/sim/lib/mcp/middleware.ts create mode 100644 apps/sim/lib/mcp/service.ts create mode 100644 apps/sim/lib/mcp/types.ts create mode 100644 apps/sim/lib/mcp/url-validator.ts create mode 100644 apps/sim/lib/mcp/utils.ts create mode 100644 apps/sim/stores/mcp-servers/store.ts create mode 100644 apps/sim/stores/mcp-servers/types.ts diff --git a/apps/sim/app/(auth)/reset-password/page.tsx b/apps/sim/app/(auth)/reset-password/page.tsx index 6ff7eefee2..56d8098be9 100644 --- a/apps/sim/app/(auth)/reset-password/page.tsx +++ b/apps/sim/app/(auth)/reset-password/page.tsx @@ -101,7 +101,7 @@ function ResetPasswordContent() {

- + Back to login

diff --git a/apps/sim/app/api/__test-utils__/setup.ts b/apps/sim/app/api/__test-utils__/setup.ts index 835dd42e6f..4c6f74e260 100644 --- a/apps/sim/app/api/__test-utils__/setup.ts +++ b/apps/sim/app/api/__test-utils__/setup.ts @@ -3,7 +3,6 @@ */ import { afterEach, beforeEach, vi } from 'vitest' -// Mock Next.js implementations vi.mock('next/headers', () => ({ cookies: () => ({ get: vi.fn().mockReturnValue({ value: 'test-session-token' }), @@ -13,7 +12,6 @@ vi.mock('next/headers', () => ({ }), })) -// Mock auth utilities vi.mock('@/lib/auth/session', () => ({ getSession: vi.fn().mockResolvedValue({ user: { @@ -24,13 +22,10 @@ vi.mock('@/lib/auth/session', () => ({ }), })) -// Configure Vitest environment beforeEach(() => { - // Clear all mocks before each test vi.clearAllMocks() }) afterEach(() => { - // Ensure all mocks are restored after each test vi.restoreAllMocks() }) diff --git a/apps/sim/app/api/__test-utils__/utils.ts b/apps/sim/app/api/__test-utils__/utils.ts index 692212a0f0..493de52b22 100644 --- a/apps/sim/app/api/__test-utils__/utils.ts +++ b/apps/sim/app/api/__test-utils__/utils.ts @@ -944,12 +944,10 @@ export interface TestSetupOptions { export function setupComprehensiveTestMocks(options: TestSetupOptions = {}) { const { auth = { authenticated: true }, database = {}, storage, authApi, features = {} } = options - // Setup basic infrastructure mocks setupCommonApiMocks() mockUuid() mockCryptoUuid() - // Setup authentication const authMocks = mockAuth(auth.user) if (auth.authenticated) { authMocks.setAuthenticated(auth.user) @@ -957,22 +955,18 @@ export function setupComprehensiveTestMocks(options: TestSetupOptions = {}) { authMocks.setUnauthenticated() } - // Setup database const dbMocks = createMockDatabase(database) - // Setup storage if needed let storageMocks if (storage) { storageMocks = createStorageProviderMocks(storage) } - // Setup auth API if needed let authApiMocks if (authApi) { authApiMocks = createAuthApiMocks(authApi) } - // Setup feature-specific mocks const featureMocks: any = {} if (features.workflowUtils) { featureMocks.workflowUtils = mockWorkflowUtils() @@ -1008,12 +1002,10 @@ export function createMockDatabase(options: MockDatabaseOptions = {}) { let selectCallCount = 0 - // Helper to create error const createDbError = (operation: string, message?: string) => { return new Error(message || `Database ${operation} error`) } - // Create chainable select mock const createSelectChain = () => ({ from: vi.fn().mockReturnThis(), leftJoin: vi.fn().mockReturnThis(), @@ -1038,7 +1030,6 @@ export function createMockDatabase(options: MockDatabaseOptions = {}) { }), }) - // Create insert chain const createInsertChain = () => ({ values: vi.fn().mockImplementation(() => ({ returning: vi.fn().mockImplementation(() => { @@ -1056,7 +1047,6 @@ export function createMockDatabase(options: MockDatabaseOptions = {}) { })), }) - // Create update chain const createUpdateChain = () => ({ set: vi.fn().mockImplementation(() => ({ where: vi.fn().mockImplementation(() => { @@ -1068,7 +1058,6 @@ export function createMockDatabase(options: MockDatabaseOptions = {}) { })), }) - // Create delete chain const createDeleteChain = () => ({ where: vi.fn().mockImplementation(() => { if (deleteOptions.throwError) { @@ -1078,7 +1067,6 @@ export function createMockDatabase(options: MockDatabaseOptions = {}) { }), }) - // Create transaction mock const createTransactionMock = () => { return vi.fn().mockImplementation(async (callback: any) => { if (transactionOptions.throwError) { @@ -1200,7 +1188,6 @@ export function setupKnowledgeMocks( mocks.generateEmbedding = vi.fn().mockResolvedValue([0.1, 0.2, 0.3]) } - // Mock the knowledge utilities vi.doMock('@/app/api/knowledge/utils', () => mocks) return mocks @@ -1218,12 +1205,10 @@ export function setupFileApiMocks( ) { const { authenticated = true, storageProvider = 's3', cloudEnabled = true } = options - // Setup basic mocks setupCommonApiMocks() mockUuid() mockCryptoUuid() - // Setup auth const authMocks = mockAuth() if (authenticated) { authMocks.setAuthenticated() @@ -1231,14 +1216,12 @@ export function setupFileApiMocks( authMocks.setUnauthenticated() } - // Setup file system mocks mockFileSystem({ writeFileSuccess: true, readFileContent: 'test content', existsResult: true, }) - // Setup storage provider mocks (this will mock @/lib/uploads) let storageMocks if (storageProvider) { storageMocks = createStorageProviderMocks({ @@ -1246,7 +1229,6 @@ export function setupFileApiMocks( isCloudEnabled: cloudEnabled, }) } else { - // If no storage provider specified, just mock the base functions vi.doMock('@/lib/uploads', () => ({ getStorageProvider: vi.fn().mockReturnValue('local'), isUsingCloudStorage: vi.fn().mockReturnValue(cloudEnabled), diff --git a/apps/sim/app/api/auth/oauth/connections/route.ts b/apps/sim/app/api/auth/oauth/connections/route.ts index 6bcb0c6b20..4cc270b1fb 100644 --- a/apps/sim/app/api/auth/oauth/connections/route.ts +++ b/apps/sim/app/api/auth/oauth/connections/route.ts @@ -3,6 +3,7 @@ import { jwtDecode } from 'jwt-decode' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { account, user } from '@/db/schema' @@ -18,7 +19,7 @@ interface GoogleIdToken { * Get all OAuth connections for the current user */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/auth/oauth/credentials/route.ts b/apps/sim/app/api/auth/oauth/credentials/route.ts index 9c942d8513..d301e76d03 100644 --- a/apps/sim/app/api/auth/oauth/credentials/route.ts +++ b/apps/sim/app/api/auth/oauth/credentials/route.ts @@ -6,6 +6,7 @@ import { createLogger } from '@/lib/logs/console/logger' import type { OAuthService } from '@/lib/oauth/oauth' import { parseProvider } from '@/lib/oauth/oauth' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { account, user, workflow } from '@/db/schema' @@ -23,7 +24,7 @@ interface GoogleIdToken { * Get credentials for a specific provider */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get query params diff --git a/apps/sim/app/api/auth/oauth/disconnect/route.ts b/apps/sim/app/api/auth/oauth/disconnect/route.ts index c2fc3cf01c..e7dc062c17 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.ts @@ -2,6 +2,7 @@ import { and, eq, like, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -13,7 +14,7 @@ const logger = createLogger('OAuthDisconnectAPI') * Disconnect an OAuth provider for the current user */ export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/auth/oauth/microsoft/file/route.ts b/apps/sim/app/api/auth/oauth/microsoft/file/route.ts index 1242c850a2..6028242f73 100644 --- a/apps/sim/app/api/auth/oauth/microsoft/file/route.ts +++ b/apps/sim/app/api/auth/oauth/microsoft/file/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -14,7 +15,7 @@ const logger = createLogger('MicrosoftFileAPI') * Get a single file from Microsoft OneDrive */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session const session = await getSession() diff --git a/apps/sim/app/api/auth/oauth/microsoft/files/route.ts b/apps/sim/app/api/auth/oauth/microsoft/files/route.ts index ebcbc79703..8ce7e39b6e 100644 --- a/apps/sim/app/api/auth/oauth/microsoft/files/route.ts +++ b/apps/sim/app/api/auth/oauth/microsoft/files/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -14,7 +15,7 @@ const logger = createLogger('MicrosoftFilesAPI') * Get Excel files from Microsoft OneDrive */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) // Generate a short request ID for correlation + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index f6416ef010..d69bd7d4b7 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -2,6 +2,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { checkHybridAuth } from '@/lib/auth/hybrid' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { getCredential, refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' @@ -14,7 +15,7 @@ const logger = createLogger('OAuthTokenAPI') * and workflow-based authentication (for server-side requests) */ export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() logger.info(`[${requestId}] OAuth token API POST request received`) @@ -59,7 +60,7 @@ export async function POST(request: NextRequest) { * Get the access token for a specific credential */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) // Short request ID for correlation + const requestId = generateRequestId() try { // Get the credential ID from the query params diff --git a/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts b/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts index 227b0e09a8..d691fd5c7e 100644 --- a/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts +++ b/apps/sim/app/api/auth/oauth/wealthbox/item/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -14,7 +15,7 @@ const logger = createLogger('WealthboxItemAPI') * Get a single item (note, contact, task) from Wealthbox */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts b/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts index 3fc90e16ba..92511bc1a2 100644 --- a/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts +++ b/apps/sim/app/api/auth/oauth/wealthbox/items/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -14,7 +15,7 @@ const logger = createLogger('WealthboxItemsAPI') * Get items (notes, contacts, tasks) from Wealthbox */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/billing/update-cost/route.ts b/apps/sim/app/api/billing/update-cost/route.ts index 0eab65f85b..137f5b164a 100644 --- a/apps/sim/app/api/billing/update-cost/route.ts +++ b/apps/sim/app/api/billing/update-cost/route.ts @@ -1,10 +1,10 @@ -import crypto from 'crypto' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { checkInternalApiKey } from '@/lib/copilot/utils' import { isBillingEnabled } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { userStats } from '@/db/schema' import { calculateCost } from '@/providers/utils' @@ -25,7 +25,7 @@ const UpdateCostSchema = z.object({ * Update user cost based on token usage with internal API key auth */ export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() try { diff --git a/apps/sim/app/api/chat/[subdomain]/otp/route.ts b/apps/sim/app/api/chat/[subdomain]/otp/route.ts index b7063ee882..a4185b1470 100644 --- a/apps/sim/app/api/chat/[subdomain]/otp/route.ts +++ b/apps/sim/app/api/chat/[subdomain]/otp/route.ts @@ -5,6 +5,7 @@ import { renderOTPEmail } from '@/components/emails/render-email' import { sendEmail } from '@/lib/email/mailer' import { createLogger } from '@/lib/logs/console/logger' import { getRedisClient, markMessageAsProcessed, releaseLock } from '@/lib/redis' +import { generateRequestId } from '@/lib/utils' import { addCorsHeaders, setChatAuthCookie } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { db } from '@/db' @@ -115,7 +116,7 @@ export async function POST( { params }: { params: Promise<{ subdomain: string }> } ) { const { subdomain } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.debug(`[${requestId}] Processing OTP request for subdomain: ${subdomain}`) @@ -229,7 +230,7 @@ export async function PUT( { params }: { params: Promise<{ subdomain: string }> } ) { const { subdomain } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.debug(`[${requestId}] Verifying OTP for subdomain: ${subdomain}`) diff --git a/apps/sim/app/api/chat/[subdomain]/route.ts b/apps/sim/app/api/chat/[subdomain]/route.ts index 188b37a534..abd18a12db 100644 --- a/apps/sim/app/api/chat/[subdomain]/route.ts +++ b/apps/sim/app/api/chat/[subdomain]/route.ts @@ -1,6 +1,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { addCorsHeaders, executeWorkflowForChat, @@ -20,7 +21,7 @@ export async function POST( { params }: { params: Promise<{ subdomain: string }> } ) { const { subdomain } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.debug(`[${requestId}] Processing chat request for subdomain: ${subdomain}`) @@ -141,7 +142,7 @@ export async function GET( { params }: { params: Promise<{ subdomain: string }> } ) { const { subdomain } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.debug(`[${requestId}] Fetching chat info for subdomain: ${subdomain}`) diff --git a/apps/sim/app/api/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index 335a3ec81c..b9f756b77e 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -14,10 +14,6 @@ vi.mock('@/db', () => ({ }, })) -vi.mock('@/lib/utils', () => ({ - decryptSecret: vi.fn().mockResolvedValue({ decrypted: 'test-secret' }), -})) - vi.mock('@/lib/logs/execution/logging-session', () => ({ LoggingSession: vi.fn().mockImplementation(() => ({ safeStart: vi.fn().mockResolvedValue(undefined), @@ -38,6 +34,13 @@ vi.mock('@/stores/workflows/server-utils', () => ({ mergeSubblockState: vi.fn().mockReturnValue({}), })) +const mockDecryptSecret = vi.fn() + +vi.mock('@/lib/utils', () => ({ + decryptSecret: mockDecryptSecret, + generateRequestId: vi.fn(), +})) + describe('Chat API Utils', () => { beforeEach(() => { vi.resetModules() @@ -177,7 +180,10 @@ describe('Chat API Utils', () => { }) describe('Chat auth validation', () => { - beforeEach(() => { + beforeEach(async () => { + vi.clearAllMocks() + mockDecryptSecret.mockResolvedValue({ decrypted: 'correct-password' }) + vi.doMock('@/app/api/chat/utils', async (importOriginal) => { const original = (await importOriginal()) as any return { @@ -190,13 +196,6 @@ describe('Chat API Utils', () => { }), } }) - - // Mock decryptSecret globally for all auth tests - vi.doMock('@/lib/utils', () => ({ - decryptSecret: vi.fn((encryptedValue) => { - return Promise.resolve({ decrypted: 'correct-password' }) - }), - })) }) it.concurrent('should allow access to public chats', async () => { diff --git a/apps/sim/app/api/chat/utils.ts b/apps/sim/app/api/chat/utils.ts index e8bfb05cfa..67ab219ac4 100644 --- a/apps/sim/app/api/chat/utils.ts +++ b/apps/sim/app/api/chat/utils.ts @@ -10,7 +10,7 @@ import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans' import { hasAdminPermission } from '@/lib/permissions/utils' import { processStreamingBlockLogs } from '@/lib/tokenization' import { getEmailDomain } from '@/lib/urls/utils' -import { decryptSecret } from '@/lib/utils' +import { decryptSecret, generateRequestId } from '@/lib/utils' import { getBlock } from '@/blocks' import { db } from '@/db' import { chat, userStats, workflow } from '@/db/schema' @@ -303,7 +303,7 @@ export async function executeWorkflowForChat( input: string, conversationId?: string ): Promise { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() logger.debug( `[${requestId}] Executing workflow for chat: ${chatId}${ diff --git a/apps/sim/app/api/copilot/chat/route.test.ts b/apps/sim/app/api/copilot/chat/route.test.ts index bad11be483..e92ddf7384 100644 --- a/apps/sim/app/api/copilot/chat/route.test.ts +++ b/apps/sim/app/api/copilot/chat/route.test.ts @@ -99,6 +99,7 @@ describe('Copilot Chat API Route', () => { vi.doMock('@/lib/utils', () => ({ getRotatingApiKey: mockGetRotatingApiKey, + generateRequestId: vi.fn(() => 'test-request-id'), })) vi.doMock('@/lib/env', () => ({ diff --git a/apps/sim/app/api/environment/route.ts b/apps/sim/app/api/environment/route.ts index 31663e34b8..4f575c49d4 100644 --- a/apps/sim/app/api/environment/route.ts +++ b/apps/sim/app/api/environment/route.ts @@ -3,7 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' -import { decryptSecret, encryptSecret } from '@/lib/utils' +import { decryptSecret, encryptSecret, generateRequestId } from '@/lib/utils' import { db } from '@/db' import { environment } from '@/db/schema' import type { EnvironmentVariable } from '@/stores/settings/environment/types' @@ -15,7 +15,7 @@ const EnvVarSchema = z.object({ }) export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -72,7 +72,7 @@ export async function POST(req: NextRequest) { } export async function GET(request: Request) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index bdfa67d394..2182078b15 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -4,7 +4,7 @@ import { env, isTruthy } from '@/lib/env' import { executeInE2B } from '@/lib/execution/e2b' import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages' import { createLogger } from '@/lib/logs/console/logger' - +import { generateRequestId } from '@/lib/utils' export const dynamic = 'force-dynamic' export const runtime = 'nodejs' export const maxDuration = 60 @@ -533,7 +533,7 @@ function escapeRegExp(string: string): string { } export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() let stdout = '' let userCodeStartLine = 3 // Default value for error reporting diff --git a/apps/sim/app/api/help/route.ts b/apps/sim/app/api/help/route.ts index 1ed594fbbe..ad90a3230c 100644 --- a/apps/sim/app/api/help/route.ts +++ b/apps/sim/app/api/help/route.ts @@ -7,6 +7,7 @@ import { getFromEmailAddress } from '@/lib/email/utils' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getEmailDomain } from '@/lib/urls/utils' +import { generateRequestId } from '@/lib/utils' const logger = createLogger('HelpAPI') @@ -17,7 +18,7 @@ const helpFormSchema = z.object({ }) export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get user session diff --git a/apps/sim/app/api/jobs/[jobId]/route.ts b/apps/sim/app/api/jobs/[jobId]/route.ts index 91171d713c..8d377802d7 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.ts @@ -3,6 +3,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { createErrorResponse } from '@/app/api/workflows/utils' import { db } from '@/db' import { apiKey as apiKeyTable } from '@/db/schema' @@ -14,7 +15,7 @@ export async function GET( { params }: { params: Promise<{ jobId: string }> } ) { const { jobId: taskId } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.debug(`[${requestId}] Getting status for task: ${taskId}`) diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts index 028d302e09..9a9b50636b 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts @@ -1,9 +1,9 @@ -import crypto from 'crypto' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { batchChunkOperation, createChunk, queryChunks } from '@/lib/knowledge/chunks/service' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { getUserId } from '@/app/api/auth/oauth/utils' import { checkDocumentAccess, checkDocumentWriteAccess } from '@/app/api/knowledge/utils' import { calculateCost } from '@/providers/utils' @@ -34,7 +34,7 @@ export async function GET( req: NextRequest, { params }: { params: Promise<{ id: string; documentId: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id: knowledgeBaseId, documentId } = await params try { @@ -106,7 +106,7 @@ export async function POST( req: NextRequest, { params }: { params: Promise<{ id: string; documentId: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id: knowledgeBaseId, documentId } = await params try { @@ -229,7 +229,7 @@ export async function PATCH( req: NextRequest, { params }: { params: Promise<{ id: string; documentId: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id: knowledgeBaseId, documentId } = await params try { diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts index 43f7f051be..4bd4307d14 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts @@ -8,6 +8,7 @@ import { updateDocument, } from '@/lib/knowledge/documents/service' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { checkDocumentAccess, checkDocumentWriteAccess } from '@/app/api/knowledge/utils' const logger = createLogger('DocumentByIdAPI') @@ -36,7 +37,7 @@ export async function GET( req: NextRequest, { params }: { params: Promise<{ id: string; documentId: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id: knowledgeBaseId, documentId } = await params try { @@ -79,7 +80,7 @@ export async function PUT( req: NextRequest, { params }: { params: Promise<{ id: string; documentId: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id: knowledgeBaseId, documentId } = await params try { @@ -209,7 +210,7 @@ export async function DELETE( req: NextRequest, { params }: { params: Promise<{ id: string; documentId: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id: knowledgeBaseId, documentId } = await params try { diff --git a/apps/sim/app/api/knowledge/[id]/route.ts b/apps/sim/app/api/knowledge/[id]/route.ts index a176df4fde..38fa990ea1 100644 --- a/apps/sim/app/api/knowledge/[id]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/route.ts @@ -7,6 +7,7 @@ import { updateKnowledgeBase, } from '@/lib/knowledge/service' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { checkKnowledgeBaseAccess, checkKnowledgeBaseWriteAccess } from '@/app/api/knowledge/utils' const logger = createLogger('KnowledgeBaseByIdAPI') @@ -27,7 +28,7 @@ const UpdateKnowledgeBaseSchema = z.object({ }) export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -69,7 +70,7 @@ export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: } export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -132,7 +133,7 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id: } export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/knowledge/route.ts b/apps/sim/app/api/knowledge/route.ts index 06f42be612..31ad493600 100644 --- a/apps/sim/app/api/knowledge/route.ts +++ b/apps/sim/app/api/knowledge/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { getSession } from '@/lib/auth' import { createKnowledgeBase, getKnowledgeBases } from '@/lib/knowledge/service' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' const logger = createLogger('KnowledgeBaseAPI') @@ -29,7 +30,7 @@ const CreateKnowledgeBaseSchema = z.object({ }) export async function GET(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -54,7 +55,7 @@ export async function GET(req: NextRequest) { } export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/knowledge/search/route.test.ts b/apps/sim/app/api/knowledge/search/route.test.ts index 9c86f66cce..b980fbf214 100644 --- a/apps/sim/app/api/knowledge/search/route.test.ts +++ b/apps/sim/app/api/knowledge/search/route.test.ts @@ -34,6 +34,10 @@ vi.mock('@/lib/env', () => ({ typeof value === 'string' ? value === 'true' || value === '1' : Boolean(value), })) +vi.mock('@/lib/utils', () => ({ + generateRequestId: vi.fn(() => 'test-request-id'), +})) + vi.mock('@/lib/documents/utils', () => ({ retryWithExponentialBackoff: vi.fn().mockImplementation((fn) => fn()), })) diff --git a/apps/sim/app/api/knowledge/search/route.ts b/apps/sim/app/api/knowledge/search/route.ts index c91228fcc3..6f97c68e31 100644 --- a/apps/sim/app/api/knowledge/search/route.ts +++ b/apps/sim/app/api/knowledge/search/route.ts @@ -4,6 +4,7 @@ import { TAG_SLOTS } from '@/lib/knowledge/consts' import { getDocumentTagDefinitions } from '@/lib/knowledge/tags/service' import { createLogger } from '@/lib/logs/console/logger' import { estimateTokenCount } from '@/lib/tokenization/estimators' +import { generateRequestId } from '@/lib/utils' import { getUserId } from '@/app/api/auth/oauth/utils' import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' import { calculateCost } from '@/providers/utils' @@ -57,7 +58,7 @@ const VectorSearchSchema = z ) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await request.json() diff --git a/apps/sim/app/api/logs/[id]/route.ts b/apps/sim/app/api/logs/[id]/route.ts index 02817f1e4e..1f94fa18f6 100644 --- a/apps/sim/app/api/logs/[id]/route.ts +++ b/apps/sim/app/api/logs/[id]/route.ts @@ -2,6 +2,7 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { permissions, workflow, workflowExecutionLogs } from '@/db/schema' @@ -10,7 +11,7 @@ const logger = createLogger('LogDetailsByIdAPI') export const revalidate = 0 export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/logs/route.ts b/apps/sim/app/api/logs/route.ts index a166b713cf..cfe6fbfb60 100644 --- a/apps/sim/app/api/logs/route.ts +++ b/apps/sim/app/api/logs/route.ts @@ -3,44 +3,12 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { permissions, workflow, workflowExecutionLogs } from '@/db/schema' const logger = createLogger('LogsAPI') -// Helper function to extract block executions from trace spans -function extractBlockExecutionsFromTraceSpans(traceSpans: any[]): any[] { - const blockExecutions: any[] = [] - - function processSpan(span: any) { - if (span.blockId) { - blockExecutions.push({ - id: span.id, - blockId: span.blockId, - blockName: span.name || '', - blockType: span.type, - startedAt: span.startTime, - endedAt: span.endTime, - durationMs: span.duration || 0, - status: span.status || 'success', - errorMessage: span.output?.error || undefined, - inputData: span.input || {}, - outputData: span.output || {}, - cost: span.cost || undefined, - metadata: {}, - }) - } - - // Process children recursively - if (span.children && Array.isArray(span.children)) { - span.children.forEach(processSpan) - } - } - - traceSpans.forEach(processSpan) - return blockExecutions -} - export const revalidate = 0 const QueryParamsSchema = z.object({ @@ -58,7 +26,7 @@ const QueryParamsSchema = z.object({ }) export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts new file mode 100644 index 0000000000..3f1da8b06a --- /dev/null +++ b/apps/sim/app/api/mcp/servers/[id]/refresh/route.ts @@ -0,0 +1,99 @@ +import { and, eq, isNull } from 'drizzle-orm' +import type { NextRequest } from 'next/server' +import { createLogger } from '@/lib/logs/console/logger' +import { withMcpAuth } from '@/lib/mcp/middleware' +import { mcpService } from '@/lib/mcp/service' +import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' +import { db } from '@/db' +import { mcpServers } from '@/db/schema' + +const logger = createLogger('McpServerRefreshAPI') + +export const dynamic = 'force-dynamic' + +/** + * POST - Refresh an MCP server connection (requires any workspace permission) + */ +export const POST = withMcpAuth('read')( + async ( + request: NextRequest, + { userId, workspaceId, requestId }, + { params }: { params: { id: string } } + ) => { + const serverId = params.id + + try { + logger.info( + `[${requestId}] Refreshing MCP server: ${serverId} in workspace: ${workspaceId}`, + { + userId, + } + ) + + const [server] = await db + .select() + .from(mcpServers) + .where( + and( + eq(mcpServers.id, serverId), + eq(mcpServers.workspaceId, workspaceId), + isNull(mcpServers.deletedAt) + ) + ) + .limit(1) + + if (!server) { + return createMcpErrorResponse( + new Error('Server not found or access denied'), + 'Server not found', + 404 + ) + } + + let connectionStatus: 'connected' | 'disconnected' | 'error' = 'error' + let toolCount = 0 + let lastError: string | null = null + + try { + const tools = await mcpService.discoverServerTools(userId, serverId, workspaceId) + connectionStatus = 'connected' + toolCount = tools.length + logger.info( + `[${requestId}] Successfully connected to server ${serverId}, discovered ${toolCount} tools` + ) + } catch (error) { + connectionStatus = 'error' + lastError = error instanceof Error ? error.message : 'Connection test failed' + logger.warn(`[${requestId}] Failed to connect to server ${serverId}:`, error) + } + + const [refreshedServer] = await db + .update(mcpServers) + .set({ + lastToolsRefresh: new Date(), + connectionStatus, + lastError, + lastConnected: connectionStatus === 'connected' ? new Date() : server.lastConnected, + toolCount, + updatedAt: new Date(), + }) + .where(eq(mcpServers.id, serverId)) + .returning() + + logger.info(`[${requestId}] Successfully refreshed MCP server: ${serverId}`) + return createMcpSuccessResponse({ + status: connectionStatus, + toolCount, + lastConnected: refreshedServer?.lastConnected?.toISOString() || null, + error: lastError, + }) + } catch (error) { + logger.error(`[${requestId}] Error refreshing MCP server:`, error) + return createMcpErrorResponse( + error instanceof Error ? error : new Error('Failed to refresh MCP server'), + 'Failed to refresh MCP server', + 500 + ) + } + } +) diff --git a/apps/sim/app/api/mcp/servers/[id]/route.ts b/apps/sim/app/api/mcp/servers/[id]/route.ts new file mode 100644 index 0000000000..a60b275c1a --- /dev/null +++ b/apps/sim/app/api/mcp/servers/[id]/route.ts @@ -0,0 +1,92 @@ +import { and, eq, isNull } from 'drizzle-orm' +import type { NextRequest } from 'next/server' +import { createLogger } from '@/lib/logs/console/logger' +import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' +import { mcpService } from '@/lib/mcp/service' +import { validateMcpServerUrl } from '@/lib/mcp/url-validator' +import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' +import { db } from '@/db' +import { mcpServers } from '@/db/schema' + +const logger = createLogger('McpServerAPI') + +export const dynamic = 'force-dynamic' + +/** + * PATCH - Update an MCP server in the workspace (requires write or admin permission) + */ +export const PATCH = withMcpAuth('write')( + async ( + request: NextRequest, + { userId, workspaceId, requestId }, + { params }: { params: { id: string } } + ) => { + const serverId = params.id + + try { + const body = getParsedBody(request) || (await request.json()) + + logger.info(`[${requestId}] Updating MCP server: ${serverId} in workspace: ${workspaceId}`, { + userId, + updates: Object.keys(body).filter((k) => k !== 'workspaceId'), + }) + + // Validate URL if being updated + if ( + body.url && + (body.transport === 'http' || + body.transport === 'sse' || + body.transport === 'streamable-http') + ) { + const urlValidation = validateMcpServerUrl(body.url) + if (!urlValidation.isValid) { + return createMcpErrorResponse( + new Error(`Invalid MCP server URL: ${urlValidation.error}`), + 'Invalid server URL', + 400 + ) + } + body.url = urlValidation.normalizedUrl + } + + // Remove workspaceId from body to prevent it from being updated + const { workspaceId: _, ...updateData } = body + + const [updatedServer] = await db + .update(mcpServers) + .set({ + ...updateData, + updatedAt: new Date(), + }) + .where( + and( + eq(mcpServers.id, serverId), + eq(mcpServers.workspaceId, workspaceId), + isNull(mcpServers.deletedAt) + ) + ) + .returning() + + if (!updatedServer) { + return createMcpErrorResponse( + new Error('Server not found or access denied'), + 'Server not found', + 404 + ) + } + + // Clear MCP service cache after update + mcpService.clearCache(workspaceId) + + logger.info(`[${requestId}] Successfully updated MCP server: ${serverId}`) + return createMcpSuccessResponse({ server: updatedServer }) + } catch (error) { + logger.error(`[${requestId}] Error updating MCP server:`, error) + return createMcpErrorResponse( + error instanceof Error ? error : new Error('Failed to update MCP server'), + 'Failed to update MCP server', + 500 + ) + } + } +) diff --git a/apps/sim/app/api/mcp/servers/route.ts b/apps/sim/app/api/mcp/servers/route.ts new file mode 100644 index 0000000000..de3151c31f --- /dev/null +++ b/apps/sim/app/api/mcp/servers/route.ts @@ -0,0 +1,166 @@ +import { and, eq, isNull } from 'drizzle-orm' +import type { NextRequest } from 'next/server' +import { createLogger } from '@/lib/logs/console/logger' +import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' +import { mcpService } from '@/lib/mcp/service' +import type { McpTransport } from '@/lib/mcp/types' +import { validateMcpServerUrl } from '@/lib/mcp/url-validator' +import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' +import { db } from '@/db' +import { mcpServers } from '@/db/schema' + +const logger = createLogger('McpServersAPI') + +export const dynamic = 'force-dynamic' + +/** + * Check if transport type requires a URL + */ +function isUrlBasedTransport(transport: McpTransport): boolean { + return transport === 'http' || transport === 'sse' || transport === 'streamable-http' +} + +/** + * GET - List all registered MCP servers for the workspace + */ +export const GET = withMcpAuth('read')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + logger.info(`[${requestId}] Listing MCP servers for workspace ${workspaceId}`) + + const servers = await db + .select() + .from(mcpServers) + .where(and(eq(mcpServers.workspaceId, workspaceId), isNull(mcpServers.deletedAt))) + + logger.info( + `[${requestId}] Listed ${servers.length} MCP servers for workspace ${workspaceId}` + ) + return createMcpSuccessResponse({ servers }) + } catch (error) { + logger.error(`[${requestId}] Error listing MCP servers:`, error) + return createMcpErrorResponse( + error instanceof Error ? error : new Error('Failed to list MCP servers'), + 'Failed to list MCP servers', + 500 + ) + } + } +) + +/** + * POST - Register a new MCP server for the workspace (requires write permission) + */ +export const POST = withMcpAuth('write')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + const body = getParsedBody(request) || (await request.json()) + + logger.info(`[${requestId}] Registering new MCP server:`, { + name: body.name, + transport: body.transport, + workspaceId, + }) + + if (!body.name || !body.transport) { + return createMcpErrorResponse( + new Error('Missing required fields: name or transport'), + 'Missing required fields', + 400 + ) + } + + if (isUrlBasedTransport(body.transport) && body.url) { + const urlValidation = validateMcpServerUrl(body.url) + if (!urlValidation.isValid) { + return createMcpErrorResponse( + new Error(`Invalid MCP server URL: ${urlValidation.error}`), + 'Invalid server URL', + 400 + ) + } + body.url = urlValidation.normalizedUrl + } + + const serverId = body.id || crypto.randomUUID() + + await db + .insert(mcpServers) + .values({ + id: serverId, + workspaceId, + createdBy: userId, + name: body.name, + description: body.description, + transport: body.transport, + url: body.url, + headers: body.headers || {}, + timeout: body.timeout || 30000, + retries: body.retries || 3, + enabled: body.enabled !== false, + createdAt: new Date(), + updatedAt: new Date(), + }) + .returning() + + mcpService.clearCache(workspaceId) + + logger.info(`[${requestId}] Successfully registered MCP server: ${body.name}`) + return createMcpSuccessResponse({ serverId }, 201) + } catch (error) { + logger.error(`[${requestId}] Error registering MCP server:`, error) + return createMcpErrorResponse( + error instanceof Error ? error : new Error('Failed to register MCP server'), + 'Failed to register MCP server', + 500 + ) + } + } +) + +/** + * DELETE - Delete an MCP server from the workspace (requires admin permission) + */ +export const DELETE = withMcpAuth('admin')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + const { searchParams } = new URL(request.url) + const serverId = searchParams.get('serverId') + + if (!serverId) { + return createMcpErrorResponse( + new Error('serverId parameter is required'), + 'Missing required parameter', + 400 + ) + } + + logger.info(`[${requestId}] Deleting MCP server: ${serverId} from workspace: ${workspaceId}`) + + const [deletedServer] = await db + .delete(mcpServers) + .where(and(eq(mcpServers.id, serverId), eq(mcpServers.workspaceId, workspaceId))) + .returning() + + if (!deletedServer) { + return createMcpErrorResponse( + new Error('Server not found or access denied'), + 'Server not found', + 404 + ) + } + + mcpService.clearCache(workspaceId) + + logger.info(`[${requestId}] Successfully deleted MCP server: ${serverId}`) + return createMcpSuccessResponse({ message: `Server ${serverId} deleted successfully` }) + } catch (error) { + logger.error(`[${requestId}] Error deleting MCP server:`, error) + return createMcpErrorResponse( + error instanceof Error ? error : new Error('Failed to delete MCP server'), + 'Failed to delete MCP server', + 500 + ) + } + } +) diff --git a/apps/sim/app/api/mcp/servers/test-connection/route.ts b/apps/sim/app/api/mcp/servers/test-connection/route.ts new file mode 100644 index 0000000000..a1e479b85f --- /dev/null +++ b/apps/sim/app/api/mcp/servers/test-connection/route.ts @@ -0,0 +1,209 @@ +import type { NextRequest } from 'next/server' +import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' +import { createLogger } from '@/lib/logs/console/logger' +import { McpClient } from '@/lib/mcp/client' +import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' +import type { McpServerConfig, McpTransport } from '@/lib/mcp/types' +import { validateMcpServerUrl } from '@/lib/mcp/url-validator' +import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' + +const logger = createLogger('McpServerTestAPI') + +export const dynamic = 'force-dynamic' + +/** + * Check if transport type requires a URL + */ +function isUrlBasedTransport(transport: McpTransport): boolean { + return transport === 'http' || transport === 'sse' || transport === 'streamable-http' +} + +/** + * Resolve environment variables in strings + */ +function resolveEnvVars(value: string, envVars: Record): string { + const envMatches = value.match(/\{\{([^}]+)\}\}/g) + if (!envMatches) return value + + let resolvedValue = value + for (const match of envMatches) { + const envKey = match.slice(2, -2).trim() + const envValue = envVars[envKey] + + if (envValue === undefined) { + logger.warn(`Environment variable "${envKey}" not found in MCP server test`) + continue + } + + resolvedValue = resolvedValue.replace(match, envValue) + } + return resolvedValue +} + +interface TestConnectionRequest { + name: string + transport: McpTransport + url?: string + headers?: Record + timeout?: number + workspaceId: string +} + +interface TestConnectionResult { + success: boolean + error?: string + serverInfo?: { + name: string + version: string + } + negotiatedVersion?: string + supportedCapabilities?: string[] + toolCount?: number + warnings?: string[] +} + +/** + * POST - Test connection to an MCP server before registering it + */ +export const POST = withMcpAuth('write')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + const body: TestConnectionRequest = getParsedBody(request) || (await request.json()) + + logger.info(`[${requestId}] Testing MCP server connection:`, { + name: body.name, + transport: body.transport, + url: body.url ? `${body.url.substring(0, 50)}...` : undefined, // Partial URL for security + workspaceId, + }) + + if (!body.name || !body.transport) { + return createMcpErrorResponse( + new Error('Missing required fields: name and transport are required'), + 'Missing required fields', + 400 + ) + } + + if (isUrlBasedTransport(body.transport)) { + if (!body.url) { + return createMcpErrorResponse( + new Error('URL is required for HTTP-based transports'), + 'Missing required URL', + 400 + ) + } + + const urlValidation = validateMcpServerUrl(body.url) + if (!urlValidation.isValid) { + return createMcpErrorResponse( + new Error(`Invalid MCP server URL: ${urlValidation.error}`), + 'Invalid server URL', + 400 + ) + } + body.url = urlValidation.normalizedUrl + } + + let resolvedUrl = body.url + let resolvedHeaders = body.headers || {} + + try { + const envVars = await getEffectiveDecryptedEnv(userId, workspaceId) + + if (resolvedUrl) { + resolvedUrl = resolveEnvVars(resolvedUrl, envVars) + } + + const resolvedHeadersObj: Record = {} + for (const [key, value] of Object.entries(resolvedHeaders)) { + resolvedHeadersObj[key] = resolveEnvVars(value, envVars) + } + resolvedHeaders = resolvedHeadersObj + } catch (envError) { + logger.warn( + `[${requestId}] Failed to resolve environment variables, using raw values:`, + envError + ) + } + + const testConfig: McpServerConfig = { + id: `test-${requestId}`, + name: body.name, + transport: body.transport, + url: resolvedUrl, + headers: resolvedHeaders, + timeout: body.timeout || 10000, + retries: 1, // Only one retry for tests + enabled: true, + } + + const testSecurityPolicy = { + requireConsent: false, + auditLevel: 'none' as const, + maxToolExecutionsPerHour: 0, + } + + const result: TestConnectionResult = { success: false } + let client: McpClient | null = null + + try { + client = new McpClient(testConfig, testSecurityPolicy) + await client.connect() + + result.success = true + result.negotiatedVersion = client.getNegotiatedVersion() + + try { + const tools = await client.listTools() + result.toolCount = tools.length + } catch (toolError) { + logger.warn(`[${requestId}] Could not list tools from test server:`, toolError) + result.warnings = result.warnings || [] + result.warnings.push('Could not list tools from server') + } + + const clientVersionInfo = McpClient.getVersionInfo() + if (result.negotiatedVersion !== clientVersionInfo.preferred) { + result.warnings = result.warnings || [] + result.warnings.push( + `Server uses protocol version '${result.negotiatedVersion}' instead of preferred '${clientVersionInfo.preferred}'` + ) + } + + logger.info(`[${requestId}] MCP server test successful:`, { + name: body.name, + negotiatedVersion: result.negotiatedVersion, + toolCount: result.toolCount, + capabilities: result.supportedCapabilities, + }) + } catch (error) { + logger.warn(`[${requestId}] MCP server test failed:`, error) + + result.success = false + if (error instanceof Error) { + result.error = error.message + } else { + result.error = 'Unknown connection error' + } + } finally { + if (client) { + try { + await client.disconnect() + } catch (disconnectError) { + logger.debug(`[${requestId}] Test client disconnect error (expected):`, disconnectError) + } + } + } + + return createMcpSuccessResponse(result, result.success ? 200 : 400) + } catch (error) { + logger.error(`[${requestId}] Error testing MCP server connection:`, error) + return createMcpErrorResponse( + error instanceof Error ? error : new Error('Failed to test server connection'), + 'Failed to test server connection', + 500 + ) + } + } +) diff --git a/apps/sim/app/api/mcp/tools/discover/route.ts b/apps/sim/app/api/mcp/tools/discover/route.ts new file mode 100644 index 0000000000..8ae3dfb59a --- /dev/null +++ b/apps/sim/app/api/mcp/tools/discover/route.ts @@ -0,0 +1,122 @@ +import type { NextRequest } from 'next/server' +import { createLogger } from '@/lib/logs/console/logger' +import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' +import { mcpService } from '@/lib/mcp/service' +import type { McpToolDiscoveryResponse } from '@/lib/mcp/types' +import { categorizeError, createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' + +const logger = createLogger('McpToolDiscoveryAPI') + +export const dynamic = 'force-dynamic' + +/** + * GET - Discover all tools from user's MCP servers + */ +export const GET = withMcpAuth('read')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + const { searchParams } = new URL(request.url) + const serverId = searchParams.get('serverId') + const forceRefresh = searchParams.get('refresh') === 'true' + + logger.info(`[${requestId}] Discovering MCP tools for user ${userId}`, { + serverId, + workspaceId, + forceRefresh, + }) + + let tools + if (serverId) { + tools = await mcpService.discoverServerTools(userId, serverId, workspaceId) + } else { + tools = await mcpService.discoverTools(userId, workspaceId, forceRefresh) + } + + const byServer: Record = {} + for (const tool of tools) { + byServer[tool.serverId] = (byServer[tool.serverId] || 0) + 1 + } + + const responseData: McpToolDiscoveryResponse = { + tools, + totalCount: tools.length, + byServer, + } + + logger.info( + `[${requestId}] Discovered ${tools.length} tools from ${Object.keys(byServer).length} servers` + ) + return createMcpSuccessResponse(responseData) + } catch (error) { + logger.error(`[${requestId}] Error discovering MCP tools:`, error) + const { message, status } = categorizeError(error) + return createMcpErrorResponse(new Error(message), 'Failed to discover MCP tools', status) + } + } +) + +/** + * POST - Refresh tool discovery for specific servers + */ +export const POST = withMcpAuth('read')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + const body = getParsedBody(request) || (await request.json()) + const { serverIds } = body + + if (!Array.isArray(serverIds)) { + return createMcpErrorResponse( + new Error('serverIds must be an array'), + 'Invalid request format', + 400 + ) + } + + logger.info( + `[${requestId}] Refreshing tool discovery for user ${userId}, servers:`, + serverIds + ) + + const results = await Promise.allSettled( + serverIds.map(async (serverId: string) => { + const tools = await mcpService.discoverServerTools(userId, serverId, workspaceId) + return { serverId, toolCount: tools.length } + }) + ) + + const successes: Array<{ serverId: string; toolCount: number }> = [] + const failures: Array<{ serverId: string; error: string }> = [] + + results.forEach((result, index) => { + const serverId = serverIds[index] + if (result.status === 'fulfilled') { + successes.push(result.value) + } else { + failures.push({ + serverId, + error: result.reason instanceof Error ? result.reason.message : 'Unknown error', + }) + } + }) + + const responseData = { + refreshed: successes, + failed: failures, + summary: { + total: serverIds.length, + successful: successes.length, + failed: failures.length, + }, + } + + logger.info( + `[${requestId}] Tool discovery refresh completed: ${successes.length}/${serverIds.length} successful` + ) + return createMcpSuccessResponse(responseData) + } catch (error) { + logger.error(`[${requestId}] Error refreshing tool discovery:`, error) + const { message, status } = categorizeError(error) + return createMcpErrorResponse(new Error(message), 'Failed to refresh tool discovery', status) + } + } +) diff --git a/apps/sim/app/api/mcp/tools/execute/route.ts b/apps/sim/app/api/mcp/tools/execute/route.ts new file mode 100644 index 0000000000..d4951fe9a0 --- /dev/null +++ b/apps/sim/app/api/mcp/tools/execute/route.ts @@ -0,0 +1,252 @@ +import type { NextRequest } from 'next/server' +import { createLogger } from '@/lib/logs/console/logger' +import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' +import { mcpService } from '@/lib/mcp/service' +import type { McpTool, McpToolCall, McpToolResult } from '@/lib/mcp/types' +import { + categorizeError, + createMcpErrorResponse, + createMcpSuccessResponse, + MCP_CONSTANTS, + validateStringParam, +} from '@/lib/mcp/utils' + +const logger = createLogger('McpToolExecutionAPI') + +export const dynamic = 'force-dynamic' + +// Type definitions for improved type safety +interface SchemaProperty { + type: 'string' | 'number' | 'boolean' | 'object' | 'array' + description?: string + enum?: unknown[] + format?: string + items?: SchemaProperty + properties?: Record +} + +interface ToolExecutionResult { + success: boolean + output?: McpToolResult + error?: string +} + +/** + * Type guard to safely check if a schema property has a type field + */ +function hasType(prop: unknown): prop is SchemaProperty { + return typeof prop === 'object' && prop !== null && 'type' in prop +} + +/** + * POST - Execute a tool on an MCP server + */ +export const POST = withMcpAuth('read')( + async (request: NextRequest, { userId, workspaceId, requestId }) => { + try { + const body = getParsedBody(request) || (await request.json()) + + logger.info(`[${requestId}] MCP tool execution request received`, { + hasAuthHeader: !!request.headers.get('authorization'), + authHeaderType: request.headers.get('authorization')?.substring(0, 10), + bodyKeys: Object.keys(body), + serverId: body.serverId, + toolName: body.toolName, + hasWorkflowId: !!body.workflowId, + workflowId: body.workflowId, + userId: userId, + }) + + const { serverId, toolName, arguments: args } = body + + const serverIdValidation = validateStringParam(serverId, 'serverId') + if (!serverIdValidation.isValid) { + logger.warn(`[${requestId}] Invalid serverId: ${serverId}`) + return createMcpErrorResponse(new Error(serverIdValidation.error), 'Invalid serverId', 400) + } + + const toolNameValidation = validateStringParam(toolName, 'toolName') + if (!toolNameValidation.isValid) { + logger.warn(`[${requestId}] Invalid toolName: ${toolName}`) + return createMcpErrorResponse(new Error(toolNameValidation.error), 'Invalid toolName', 400) + } + + logger.info( + `[${requestId}] Executing tool ${toolName} on server ${serverId} for user ${userId} in workspace ${workspaceId}` + ) + + let tool = null + try { + const tools = await mcpService.discoverServerTools(userId, serverId, workspaceId) + tool = tools.find((t) => t.name === toolName) + + if (!tool) { + return createMcpErrorResponse( + new Error( + `Tool ${toolName} not found on server ${serverId}. Available tools: ${tools.map((t) => t.name).join(', ')}` + ), + 'Tool not found', + 404 + ) + } + + // Parse array arguments based on tool schema + if (tool.inputSchema?.properties) { + for (const [paramName, paramSchema] of Object.entries(tool.inputSchema.properties)) { + const schema = paramSchema as any + if ( + schema.type === 'array' && + args[paramName] !== undefined && + typeof args[paramName] === 'string' + ) { + const stringValue = args[paramName].trim() + if (stringValue) { + try { + // Try to parse as JSON first (handles ["item1", "item2"]) + const parsed = JSON.parse(stringValue) + if (Array.isArray(parsed)) { + args[paramName] = parsed + } else { + // JSON parsed but not an array, wrap in array + args[paramName] = [parsed] + } + } catch (error) { + // JSON parsing failed - treat as comma-separated if contains commas, otherwise single item + if (stringValue.includes(',')) { + args[paramName] = stringValue + .split(',') + .map((item) => item.trim()) + .filter((item) => item) + } else { + // Single item - wrap in array since schema expects array + args[paramName] = [stringValue] + } + } + } else { + // Empty string becomes empty array + args[paramName] = [] + } + } + } + } + } catch (error) { + logger.warn( + `[${requestId}] Failed to discover tools for validation, proceeding anyway:`, + error + ) + } + + if (tool) { + const validationError = validateToolArguments(tool, args) + if (validationError) { + logger.warn(`[${requestId}] Tool validation failed: ${validationError}`) + return createMcpErrorResponse( + new Error(`Invalid arguments for tool ${toolName}: ${validationError}`), + 'Invalid tool arguments', + 400 + ) + } + } + + const toolCall: McpToolCall = { + name: toolName, + arguments: args || {}, + } + + const result = await Promise.race([ + mcpService.executeTool(userId, serverId, toolCall, workspaceId), + new Promise((_, reject) => + setTimeout( + () => reject(new Error('Tool execution timeout')), + MCP_CONSTANTS.EXECUTION_TIMEOUT + ) + ), + ]) + + const transformedResult = transformToolResult(result) + + if (result.isError) { + logger.warn(`[${requestId}] Tool execution returned error for ${toolName} on ${serverId}`) + return createMcpErrorResponse( + transformedResult, + transformedResult.error || 'Tool execution failed', + 400 + ) + } + logger.info(`[${requestId}] Successfully executed tool ${toolName} on server ${serverId}`) + return createMcpSuccessResponse(transformedResult) + } catch (error) { + logger.error(`[${requestId}] Error executing MCP tool:`, error) + + const { message, status } = categorizeError(error) + return createMcpErrorResponse(new Error(message), message, status) + } + } +) + +/** + * Validate tool arguments against schema + */ +function validateToolArguments(tool: McpTool, args: Record): string | null { + if (!tool.inputSchema) { + return null // No schema to validate against + } + + const schema = tool.inputSchema + + if (schema.required && Array.isArray(schema.required)) { + for (const requiredProp of schema.required) { + if (!(requiredProp in (args || {}))) { + return `Missing required property: ${requiredProp}` + } + } + } + + if (schema.properties && args) { + for (const [propName, propSchema] of Object.entries(schema.properties)) { + const propValue = args[propName] + if (propValue !== undefined && hasType(propSchema)) { + const expectedType = propSchema.type + const actualType = typeof propValue + + if (expectedType === 'string' && actualType !== 'string') { + return `Property ${propName} must be a string` + } + if (expectedType === 'number' && actualType !== 'number') { + return `Property ${propName} must be a number` + } + if (expectedType === 'boolean' && actualType !== 'boolean') { + return `Property ${propName} must be a boolean` + } + if ( + expectedType === 'object' && + (actualType !== 'object' || propValue === null || Array.isArray(propValue)) + ) { + return `Property ${propName} must be an object` + } + if (expectedType === 'array' && !Array.isArray(propValue)) { + return `Property ${propName} must be an array` + } + } + } + } + + return null +} + +/** + * Transform MCP tool result to platform format + */ +function transformToolResult(result: McpToolResult): ToolExecutionResult { + if (result.isError) { + return { + success: false, + error: result.content?.[0]?.text || 'Tool execution failed', + } + } + + return { + success: true, + output: result, + } +} diff --git a/apps/sim/app/api/memory/[id]/route.ts b/apps/sim/app/api/memory/[id]/route.ts index e8766a9540..5887fb450a 100644 --- a/apps/sim/app/api/memory/[id]/route.ts +++ b/apps/sim/app/api/memory/[id]/route.ts @@ -1,6 +1,7 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { memory } from '@/db/schema' @@ -13,7 +14,7 @@ export const runtime = 'nodejs' * GET handler for retrieving a specific memory by ID */ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -85,7 +86,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -156,7 +157,7 @@ export async function DELETE( * PUT handler for updating a specific memory */ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/memory/route.ts b/apps/sim/app/api/memory/route.ts index a2137a3ba8..3c23fcb8d8 100644 --- a/apps/sim/app/api/memory/route.ts +++ b/apps/sim/app/api/memory/route.ts @@ -1,6 +1,7 @@ import { and, eq, isNull, like } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { memory } from '@/db/schema' @@ -18,7 +19,7 @@ export const runtime = 'nodejs' * - workflowId: Filter by workflow ID (required) */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.info(`[${requestId}] Processing memory search request`) @@ -101,7 +102,7 @@ export async function GET(request: NextRequest) { * - workflowId: ID of the workflow this memory belongs to */ export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.info(`[${requestId}] Processing memory creation request`) diff --git a/apps/sim/app/api/providers/route.ts b/apps/sim/app/api/providers/route.ts index 8aa62f7e71..ba98cfa9a2 100644 --- a/apps/sim/app/api/providers/route.ts +++ b/apps/sim/app/api/providers/route.ts @@ -1,5 +1,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import type { StreamingExecution } from '@/executor/types' import { executeProviderRequest } from '@/providers' import { getApiKey } from '@/providers/utils' @@ -12,7 +13,7 @@ export const dynamic = 'force-dynamic' * Server-side proxy for provider requests */ export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() try { @@ -36,6 +37,7 @@ export async function POST(request: NextRequest) { azureApiVersion, responseFormat, workflowId, + workspaceId, stream, messages, environmentVariables, @@ -104,6 +106,7 @@ export async function POST(request: NextRequest) { azureApiVersion, responseFormat, workflowId, + workspaceId, stream, messages, environmentVariables, diff --git a/apps/sim/app/api/proxy/image/route.ts b/apps/sim/app/api/proxy/image/route.ts index 82aa7907a1..dba9958ea9 100644 --- a/apps/sim/app/api/proxy/image/route.ts +++ b/apps/sim/app/api/proxy/image/route.ts @@ -1,6 +1,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' import { validateImageUrl } from '@/lib/security/url-validation' +import { generateRequestId } from '@/lib/utils' const logger = createLogger('ImageProxyAPI') @@ -11,7 +12,7 @@ const logger = createLogger('ImageProxyAPI') export async function GET(request: NextRequest) { const url = new URL(request.url) const imageUrl = url.searchParams.get('url') - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() if (!imageUrl) { logger.error(`[${requestId}] Missing 'url' parameter`) diff --git a/apps/sim/app/api/proxy/route.ts b/apps/sim/app/api/proxy/route.ts index a0668eb455..4d4f9bbeab 100644 --- a/apps/sim/app/api/proxy/route.ts +++ b/apps/sim/app/api/proxy/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server' import { isDev } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' import { validateProxyUrl } from '@/lib/security/url-validation' +import { generateRequestId } from '@/lib/utils' import { executeTool } from '@/tools' import { getTool, validateRequiredParametersAfterMerge } from '@/tools/utils' @@ -74,7 +75,7 @@ const createErrorResponse = (error: any, status = 500, additionalData = {}) => { export async function GET(request: Request) { const url = new URL(request.url) const targetUrl = url.searchParams.get('url') - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() if (!targetUrl) { logger.error(`[${requestId}] Missing 'url' parameter`) @@ -167,7 +168,7 @@ export async function GET(request: Request) { } export async function POST(request: Request) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = new Date() const startTimeISO = startTime.toISOString() diff --git a/apps/sim/app/api/schedules/[id]/route.ts b/apps/sim/app/api/schedules/[id]/route.ts index 1296751005..c9c5b5fc17 100644 --- a/apps/sim/app/api/schedules/[id]/route.ts +++ b/apps/sim/app/api/schedules/[id]/route.ts @@ -1,9 +1,9 @@ -import crypto from 'crypto' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { workflow, workflowSchedule } from '@/db/schema' @@ -18,7 +18,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params @@ -85,7 +85,7 @@ export async function DELETE( * Update a schedule - can be used to reactivate a disabled schedule */ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params diff --git a/apps/sim/app/api/schedules/[id]/status/route.ts b/apps/sim/app/api/schedules/[id]/status/route.ts index 54cb84aba8..7756ed2574 100644 --- a/apps/sim/app/api/schedules/[id]/status/route.ts +++ b/apps/sim/app/api/schedules/[id]/status/route.ts @@ -3,13 +3,14 @@ import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { workflow, workflowSchedule } from '@/db/schema' const logger = createLogger('ScheduleStatusAPI') export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params const scheduleId = id diff --git a/apps/sim/app/api/schedules/execute/route.ts b/apps/sim/app/api/schedules/execute/route.ts index dd03ef332e..1a00f741c0 100644 --- a/apps/sim/app/api/schedules/execute/route.ts +++ b/apps/sim/app/api/schedules/execute/route.ts @@ -15,7 +15,7 @@ import { getScheduleTimeValues, getSubBlockValue, } from '@/lib/schedules/utils' -import { decryptSecret } from '@/lib/utils' +import { decryptSecret, generateRequestId } from '@/lib/utils' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' import { updateWorkflowRunCounts } from '@/lib/workflows/utils' import { db } from '@/db' @@ -25,7 +25,6 @@ import { Serializer } from '@/serializer' import { RateLimiter } from '@/services/queue' import { mergeSubblockState } from '@/stores/workflows/server-utils' -// Add dynamic export to prevent caching export const dynamic = 'force-dynamic' const logger = createLogger('ScheduledExecuteAPI') @@ -66,7 +65,7 @@ const runningExecutions = new Set() export async function GET() { logger.info(`Scheduled execution triggered at ${new Date().toISOString()}`) - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const now = new Date() let dueSchedules: (typeof workflowSchedule.$inferSelect)[] = [] diff --git a/apps/sim/app/api/schedules/route.ts b/apps/sim/app/api/schedules/route.ts index 94f3cb7890..dc610a6d58 100644 --- a/apps/sim/app/api/schedules/route.ts +++ b/apps/sim/app/api/schedules/route.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -13,6 +12,7 @@ import { getSubBlockValue, validateCronExpression, } from '@/lib/schedules/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { workflow, workflowSchedule } from '@/db/schema' @@ -65,7 +65,7 @@ function hasValidScheduleConfig( * Get schedule information for a workflow */ export async function GET(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const url = new URL(req.url) const workflowId = url.searchParams.get('workflowId') const blockId = url.searchParams.get('blockId') @@ -165,7 +165,7 @@ export async function GET(req: NextRequest) { * Create or update a schedule for a workflow */ export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/templates/[id]/route.ts b/apps/sim/app/api/templates/[id]/route.ts index 2432b13ad0..b7e5039ae8 100644 --- a/apps/sim/app/api/templates/[id]/route.ts +++ b/apps/sim/app/api/templates/[id]/route.ts @@ -4,6 +4,7 @@ import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { hasAdminPermission } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { templates, workflow } from '@/db/schema' @@ -13,7 +14,7 @@ export const revalidate = 0 // GET /api/templates/[id] - Retrieve a single template by ID export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -77,7 +78,7 @@ const updateTemplateSchema = z.object({ // PUT /api/templates/[id] - Update a template export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -163,7 +164,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/templates/[id]/star/route.ts b/apps/sim/app/api/templates/[id]/star/route.ts index 4b2701a7c9..da281ca497 100644 --- a/apps/sim/app/api/templates/[id]/star/route.ts +++ b/apps/sim/app/api/templates/[id]/star/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { templateStars, templates } from '@/db/schema' @@ -13,7 +14,7 @@ export const revalidate = 0 // GET /api/templates/[id]/star - Check if user has starred this template export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -47,7 +48,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ // POST /api/templates/[id]/star - Add a star to the template export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -123,7 +124,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/templates/[id]/use/route.ts b/apps/sim/app/api/templates/[id]/use/route.ts index 7587ada95a..33a02976c0 100644 --- a/apps/sim/app/api/templates/[id]/use/route.ts +++ b/apps/sim/app/api/templates/[id]/use/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { templates, workflow, workflowBlocks, workflowEdges } from '@/db/schema' @@ -13,7 +14,7 @@ export const revalidate = 0 // POST /api/templates/[id]/use - Use a template (increment views and create workflow) export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/templates/route.ts b/apps/sim/app/api/templates/route.ts index ac2eb4fcec..9bc8ec9b6e 100644 --- a/apps/sim/app/api/templates/route.ts +++ b/apps/sim/app/api/templates/route.ts @@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { templateStars, templates, workflow } from '@/db/schema' @@ -82,7 +83,7 @@ const QueryParamsSchema = z.object({ // GET /api/templates - Retrieve templates export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -185,7 +186,7 @@ export async function GET(request: NextRequest) { // POST /api/templates - Create a new template export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/tools/custom/route.ts b/apps/sim/app/api/tools/custom/route.ts index 3cb39c6f14..885a697177 100644 --- a/apps/sim/app/api/tools/custom/route.ts +++ b/apps/sim/app/api/tools/custom/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { getUserId } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { customTools } from '@/db/schema' @@ -33,7 +34,7 @@ const CustomToolSchema = z.object({ // GET - Fetch all custom tools for the user export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const searchParams = request.nextUrl.searchParams const workflowId = searchParams.get('workflowId') @@ -69,7 +70,7 @@ export async function GET(request: NextRequest) { // POST - Create or update custom tools export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -162,7 +163,7 @@ export async function POST(req: NextRequest) { // DELETE - Delete a custom tool by ID export async function DELETE(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const searchParams = request.nextUrl.searchParams const toolId = searchParams.get('id') diff --git a/apps/sim/app/api/tools/drive/file/route.ts b/apps/sim/app/api/tools/drive/file/route.ts index ee0edfe8c7..71dc57f97b 100644 --- a/apps/sim/app/api/tools/drive/file/route.ts +++ b/apps/sim/app/api/tools/drive/file/route.ts @@ -1,8 +1,8 @@ import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' - export const dynamic = 'force-dynamic' const logger = createLogger('GoogleDriveFileAPI') @@ -11,11 +11,10 @@ const logger = createLogger('GoogleDriveFileAPI') * Get a single file from Google Drive */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) // Generate a short request ID for correlation + const requestId = generateRequestId() logger.info(`[${requestId}] Google Drive file request received`) try { - // Get the credential ID and file ID from the query params const { searchParams } = new URL(request.url) const credentialId = searchParams.get('credentialId') const fileId = searchParams.get('fileId') @@ -31,7 +30,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) } - // Refresh access token if needed using the utility function const accessToken = await refreshAccessTokenIfNeeded( credentialId, authz.credentialOwnerUserId, @@ -42,7 +40,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 }) } - // Fetch the file from Google Drive API logger.info(`[${requestId}] Fetching file ${fileId} from Google Drive API`) const response = await fetch( `https://www.googleapis.com/drive/v3/files/${fileId}?fields=id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,exportLinks,shortcutDetails&supportsAllDrives=true`, @@ -69,7 +66,6 @@ export async function GET(request: NextRequest) { const file = await response.json() - // In case of Google Docs, Sheets, etc., provide the export links const exportFormats: { [key: string]: string } = { 'application/vnd.google-apps.document': 'application/pdf', // Google Docs to PDF 'application/vnd.google-apps.spreadsheet': @@ -77,7 +73,6 @@ export async function GET(request: NextRequest) { 'application/vnd.google-apps.presentation': 'application/pdf', // Google Slides to PDF } - // Resolve shortcuts transparently for UI stability if ( file.mimeType === 'application/vnd.google-apps.shortcut' && file.shortcutDetails?.targetId @@ -105,20 +100,16 @@ export async function GET(request: NextRequest) { } } - // If the file is a Google Docs, Sheets, or Slides file, we need to provide the export link if (file.mimeType.startsWith('application/vnd.google-apps.')) { const format = exportFormats[file.mimeType] || 'application/pdf' if (!file.exportLinks) { - // If export links are not available in the response, try to construct one file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}/export?mimeType=${encodeURIComponent( format )}` } else { - // Use the export link from the response if available file.downloadUrl = file.exportLinks[format] } } else { - // For regular files, use the download link file.downloadUrl = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media` } diff --git a/apps/sim/app/api/tools/drive/files/route.ts b/apps/sim/app/api/tools/drive/files/route.ts index add1494952..70d08108ad 100644 --- a/apps/sim/app/api/tools/drive/files/route.ts +++ b/apps/sim/app/api/tools/drive/files/route.ts @@ -2,8 +2,8 @@ import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' - export const dynamic = 'force-dynamic' const logger = createLogger('GoogleDriveFilesAPI') @@ -12,20 +12,17 @@ const logger = createLogger('GoogleDriveFilesAPI') * Get files from Google Drive */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) // Generate a short request ID for correlation + const requestId = generateRequestId() logger.info(`[${requestId}] Google Drive files request received`) try { - // Get the session const session = await getSession() - // Check if the user is authenticated if (!session?.user?.id) { logger.warn(`[${requestId}] Unauthenticated request rejected`) return NextResponse.json({ error: 'User not authenticated' }, { status: 401 }) } - // Get the credential ID from the query params const { searchParams } = new URL(request.url) const credentialId = searchParams.get('credentialId') const mimeType = searchParams.get('mimeType') @@ -38,14 +35,12 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 }) } - // Authorize use of the credential (supports collaborator credentials via workflow) const authz = await authorizeCredentialUse(request, { credentialId: credentialId!, workflowId }) if (!authz.ok || !authz.credentialOwnerUserId) { logger.warn(`[${requestId}] Unauthorized credential access attempt`, authz) return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 }) } - // Refresh access token if needed using the utility function const accessToken = await refreshAccessTokenIfNeeded( credentialId!, authz.credentialOwnerUserId, @@ -56,7 +51,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 }) } - // Build Drive 'q' expression safely const qParts: string[] = ['trashed = false'] if (folderId) { qParts.push(`'${folderId.replace(/'/g, "\\'")}' in parents`) @@ -69,7 +63,6 @@ export async function GET(request: NextRequest) { } const q = encodeURIComponent(qParts.join(' and ')) - // Fetch files from Google Drive API with shared drives support const response = await fetch( `https://www.googleapis.com/drive/v3/files?q=${q}&supportsAllDrives=true&includeItemsFromAllDrives=true&spaces=drive&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`, { diff --git a/apps/sim/app/api/tools/gmail/label/route.ts b/apps/sim/app/api/tools/gmail/label/route.ts index 87c1acae93..2d3ae5df49 100644 --- a/apps/sim/app/api/tools/gmail/label/route.ts +++ b/apps/sim/app/api/tools/gmail/label/route.ts @@ -2,6 +2,7 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -11,7 +12,7 @@ export const dynamic = 'force-dynamic' const logger = createLogger('GmailLabelAPI') export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/tools/gmail/labels/route.ts b/apps/sim/app/api/tools/gmail/labels/route.ts index 0aa41f8ea6..aa90e28f08 100644 --- a/apps/sim/app/api/tools/gmail/labels/route.ts +++ b/apps/sim/app/api/tools/gmail/labels/route.ts @@ -2,10 +2,10 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' - export const dynamic = 'force-dynamic' const logger = createLogger('GmailLabelsAPI') @@ -19,7 +19,7 @@ interface GmailLabel { } export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/tools/google_calendar/calendars/route.ts b/apps/sim/app/api/tools/google_calendar/calendars/route.ts index 80552645ea..9fdafa9bd5 100644 --- a/apps/sim/app/api/tools/google_calendar/calendars/route.ts +++ b/apps/sim/app/api/tools/google_calendar/calendars/route.ts @@ -1,8 +1,8 @@ import { type NextRequest, NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' - export const dynamic = 'force-dynamic' const logger = createLogger('GoogleCalendarAPI') @@ -21,7 +21,7 @@ interface CalendarListItem { * Get calendars from Google Calendar */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) // Generate a short request ID for correlation + const requestId = generateRequestId() logger.info(`[${requestId}] Google Calendar calendars request received`) try { diff --git a/apps/sim/app/api/tools/linear/projects/route.ts b/apps/sim/app/api/tools/linear/projects/route.ts index ae43f4995b..72523399d5 100644 --- a/apps/sim/app/api/tools/linear/projects/route.ts +++ b/apps/sim/app/api/tools/linear/projects/route.ts @@ -3,6 +3,7 @@ import { LinearClient } from '@linear/sdk' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' @@ -19,7 +20,7 @@ export async function POST(request: Request) { return NextResponse.json({ error: 'Credential and teamId are required' }, { status: 400 }) } - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const authz = await authorizeCredentialUse(request as any, { credentialId: credential, workflowId, diff --git a/apps/sim/app/api/tools/linear/teams/route.ts b/apps/sim/app/api/tools/linear/teams/route.ts index 575004065d..c4a1c08434 100644 --- a/apps/sim/app/api/tools/linear/teams/route.ts +++ b/apps/sim/app/api/tools/linear/teams/route.ts @@ -3,6 +3,7 @@ import { LinearClient } from '@linear/sdk' import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' @@ -11,7 +12,7 @@ const logger = createLogger('LinearTeamsAPI') export async function POST(request: Request) { try { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const body = await request.json() const { credential, workflowId } = body diff --git a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts index 214b7c22ae..439db34549 100644 --- a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts @@ -115,7 +115,6 @@ const getChatDisplayName = async ( export async function POST(request: Request) { try { - const requestId = crypto.randomUUID().slice(0, 8) const body = await request.json() const { credential, workflowId } = body diff --git a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts index 48a4a32d8d..0cc35c1025 100644 --- a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' @@ -19,7 +20,7 @@ export async function POST(request: Request) { } try { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const authz = await authorizeCredentialUse(request as any, { credentialId: credential, workflowId, diff --git a/apps/sim/app/api/tools/outlook/folders/route.ts b/apps/sim/app/api/tools/outlook/folders/route.ts index 6c97c7056f..0e52c164d1 100644 --- a/apps/sim/app/api/tools/outlook/folders/route.ts +++ b/apps/sim/app/api/tools/outlook/folders/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -48,7 +49,7 @@ export async function GET(request: Request) { const accessToken = await refreshAccessTokenIfNeeded( credentialId, credentialOwnerUserId, - crypto.randomUUID().slice(0, 8) + generateRequestId() ) if (!accessToken) { diff --git a/apps/sim/app/api/tools/slack/channels/route.ts b/apps/sim/app/api/tools/slack/channels/route.ts index d4c43b5429..6f171b495b 100644 --- a/apps/sim/app/api/tools/slack/channels/route.ts +++ b/apps/sim/app/api/tools/slack/channels/route.ts @@ -1,6 +1,7 @@ import { NextResponse } from 'next/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' @@ -17,7 +18,7 @@ interface SlackChannel { export async function POST(request: Request) { try { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const body = await request.json() const { credential, workflowId } = body diff --git a/apps/sim/app/api/tools/thinking/route.ts b/apps/sim/app/api/tools/thinking/route.ts index 4c19ee806f..02fd5fff75 100644 --- a/apps/sim/app/api/tools/thinking/route.ts +++ b/apps/sim/app/api/tools/thinking/route.ts @@ -1,5 +1,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import type { ThinkingToolParams, ThinkingToolResponse } from '@/tools/thinking/types' const logger = createLogger('ThinkingToolAPI') @@ -11,7 +12,7 @@ export const dynamic = 'force-dynamic' * Simply acknowledges the thought by returning it in the output */ export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body: ThinkingToolParams = await request.json() diff --git a/apps/sim/app/api/tools/wealthbox/item/route.ts b/apps/sim/app/api/tools/wealthbox/item/route.ts index d9a7b7aaae..ee0c21108e 100644 --- a/apps/sim/app/api/tools/wealthbox/item/route.ts +++ b/apps/sim/app/api/tools/wealthbox/item/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -14,7 +15,7 @@ const logger = createLogger('WealthboxItemAPI') * Get a single item (note, contact, task) from Wealthbox */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the session diff --git a/apps/sim/app/api/tools/wealthbox/items/route.ts b/apps/sim/app/api/tools/wealthbox/items/route.ts index 318661701b..999df67bbd 100644 --- a/apps/sim/app/api/tools/wealthbox/items/route.ts +++ b/apps/sim/app/api/tools/wealthbox/items/route.ts @@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { account } from '@/db/schema' @@ -24,7 +25,7 @@ interface WealthboxItem { * Get items (notes, contacts, tasks) from Wealthbox */ export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/users/me/api-keys/[id]/route.ts b/apps/sim/app/api/users/me/api-keys/[id]/route.ts index 800fcf3853..c0e13defa0 100644 --- a/apps/sim/app/api/users/me/api-keys/[id]/route.ts +++ b/apps/sim/app/api/users/me/api-keys/[id]/route.ts @@ -2,6 +2,7 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { apiKey } from '@/db/schema' @@ -12,7 +13,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/users/me/profile/route.ts b/apps/sim/app/api/users/me/profile/route.ts index 9d1e554f18..6801376bc6 100644 --- a/apps/sim/app/api/users/me/profile/route.ts +++ b/apps/sim/app/api/users/me/profile/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { user } from '@/db/schema' @@ -27,7 +28,7 @@ interface UpdateData { export const dynamic = 'force-dynamic' export async function PATCH(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -90,7 +91,7 @@ export async function PATCH(request: NextRequest) { // GET endpoint to fetch current user profile export async function GET() { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/users/me/settings/route.ts b/apps/sim/app/api/users/me/settings/route.ts index ebcafaf1ff..658ff3c293 100644 --- a/apps/sim/app/api/users/me/settings/route.ts +++ b/apps/sim/app/api/users/me/settings/route.ts @@ -4,6 +4,7 @@ import { NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { settings } from '@/db/schema' @@ -40,7 +41,7 @@ const defaultSettings = { } export async function GET() { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -83,7 +84,7 @@ export async function GET() { } export async function PATCH(request: Request) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() diff --git a/apps/sim/app/api/users/me/settings/unsubscribe/route.ts b/apps/sim/app/api/users/me/settings/unsubscribe/route.ts index 6806a581b2..99611f1eed 100644 --- a/apps/sim/app/api/users/me/settings/unsubscribe/route.ts +++ b/apps/sim/app/api/users/me/settings/unsubscribe/route.ts @@ -9,6 +9,7 @@ import { verifyUnsubscribeToken, } from '@/lib/email/unsubscribe' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' const logger = createLogger('UnsubscribeAPI') @@ -19,7 +20,7 @@ const unsubscribeSchema = z.object({ }) export async function GET(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { searchParams } = new URL(req.url) @@ -63,7 +64,7 @@ export async function GET(req: NextRequest) { } export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await req.json() diff --git a/apps/sim/app/api/wand-generate/route.ts b/apps/sim/app/api/wand-generate/route.ts index 4cc7d160bb..9a1954e2e9 100644 --- a/apps/sim/app/api/wand-generate/route.ts +++ b/apps/sim/app/api/wand-generate/route.ts @@ -4,6 +4,7 @@ import OpenAI, { AzureOpenAI } from 'openai' import { env } from '@/lib/env' import { getCostMultiplier, isBillingEnabled } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { userStats, workflow } from '@/db/schema' import { getModelPricing } from '@/providers/utils' @@ -138,7 +139,7 @@ async function updateUserStatsForWand( } export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() logger.info(`[${requestId}] Received wand generation request`) if (!client) { diff --git a/apps/sim/app/api/webhooks/[id]/route.ts b/apps/sim/app/api/webhooks/[id]/route.ts index f9d7909013..31d465c327 100644 --- a/apps/sim/app/api/webhooks/[id]/route.ts +++ b/apps/sim/app/api/webhooks/[id]/route.ts @@ -4,6 +4,7 @@ import { getSession } from '@/lib/auth' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { getOAuthToken } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { webhook, workflow } from '@/db/schema' @@ -14,7 +15,7 @@ export const dynamic = 'force-dynamic' // Get a specific webhook export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params @@ -83,7 +84,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ // Update a webhook export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params @@ -181,7 +182,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 12fed57958..64cd2eb4a1 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -5,6 +5,7 @@ import { getSession } from '@/lib/auth' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { getOAuthToken } from '@/app/api/auth/oauth/utils' import { db } from '@/db' import { webhook, workflow } from '@/db/schema' @@ -15,7 +16,7 @@ export const dynamic = 'force-dynamic' // Get all webhooks for the current user export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const session = await getSession() @@ -108,7 +109,7 @@ export async function GET(request: NextRequest) { // Create or Update a webhook export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const userId = (await getSession())?.user?.id if (!userId) { diff --git a/apps/sim/app/api/webhooks/test/route.ts b/apps/sim/app/api/webhooks/test/route.ts index f9de9a3d0e..698a385d3c 100644 --- a/apps/sim/app/api/webhooks/test/route.ts +++ b/apps/sim/app/api/webhooks/test/route.ts @@ -1,6 +1,7 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { webhook } from '@/db/schema' @@ -9,7 +10,7 @@ const logger = createLogger('WebhookTestAPI') export const dynamic = 'force-dynamic' export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { // Get the webhook ID and provider from the query parameters diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.ts index 67d772764e..a49c0ed3d4 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.ts @@ -5,6 +5,7 @@ import { checkServerSideUsageLimits } from '@/lib/billing' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { env, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { handleSlackChallenge, handleWhatsAppVerification, @@ -27,7 +28,7 @@ export const runtime = 'nodejs' * Handles verification requests from webhook providers and confirms endpoint exists. */ export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const path = (await params).path @@ -83,7 +84,7 @@ export async function POST( request: NextRequest, { params }: { params: Promise<{ path: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() let foundWorkflow: any = null let foundWebhook: any = null diff --git a/apps/sim/app/api/workflows/[id]/autolayout/route.ts b/apps/sim/app/api/workflows/[id]/autolayout/route.ts index 53d850e405..0d8e86f0d8 100644 --- a/apps/sim/app/api/workflows/[id]/autolayout/route.ts +++ b/apps/sim/app/api/workflows/[id]/autolayout/route.ts @@ -5,6 +5,7 @@ import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' import { simAgentClient } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' @@ -48,7 +49,7 @@ type AutoLayoutRequest = z.infer * Apply autolayout to an existing workflow */ export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await params diff --git a/apps/sim/app/api/workflows/[id]/chat/status/route.ts b/apps/sim/app/api/workflows/[id]/chat/status/route.ts index 1d857969a2..137c25136c 100644 --- a/apps/sim/app/api/workflows/[id]/chat/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/chat/status/route.ts @@ -1,5 +1,6 @@ import { eq } from 'drizzle-orm' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { db } from '@/db' import { chat } from '@/db/schema' @@ -11,7 +12,7 @@ const logger = createLogger('ChatStatusAPI') */ export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) { const { id } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.debug(`[${requestId}] Checking chat deployment status for workflow: ${id}`) diff --git a/apps/sim/app/api/workflows/[id]/deploy/route.test.ts b/apps/sim/app/api/workflows/[id]/deploy/route.test.ts index 09d2491c4c..753e99fded 100644 --- a/apps/sim/app/api/workflows/[id]/deploy/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/deploy/route.test.ts @@ -12,6 +12,7 @@ describe('Workflow Deployment API Route', () => { vi.doMock('@/lib/utils', () => ({ generateApiKey: vi.fn().mockReturnValue('sim_testkeygenerated12345'), + generateRequestId: vi.fn(() => 'test-request-id'), })) vi.doMock('uuid', () => ({ diff --git a/apps/sim/app/api/workflows/[id]/deploy/route.ts b/apps/sim/app/api/workflows/[id]/deploy/route.ts index 022cd8fc81..6dfeb331f1 100644 --- a/apps/sim/app/api/workflows/[id]/deploy/route.ts +++ b/apps/sim/app/api/workflows/[id]/deploy/route.ts @@ -2,7 +2,7 @@ import { and, desc, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { createLogger } from '@/lib/logs/console/logger' -import { generateApiKey } from '@/lib/utils' +import { generateApiKey, generateRequestId } from '@/lib/utils' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { db } from '@/db' @@ -14,7 +14,7 @@ export const dynamic = 'force-dynamic' export const runtime = 'nodejs' export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -133,7 +133,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -368,7 +368,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/workflows/[id]/deployed/route.ts b/apps/sim/app/api/workflows/[id]/deployed/route.ts index 64e2a30cdd..f4662627cf 100644 --- a/apps/sim/app/api/workflows/[id]/deployed/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployed/route.ts @@ -1,6 +1,7 @@ import { eq } from 'drizzle-orm' import type { NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { db } from '@/db' @@ -18,7 +19,7 @@ function addNoCacheHeaders(response: NextResponse): NextResponse { } export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/workflows/[id]/duplicate/route.ts b/apps/sim/app/api/workflows/[id]/duplicate/route.ts index a50c501c12..8f1dc984ee 100644 --- a/apps/sim/app/api/workflows/[id]/duplicate/route.ts +++ b/apps/sim/app/api/workflows/[id]/duplicate/route.ts @@ -1,10 +1,10 @@ -import crypto from 'crypto' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema' import type { Variable } from '@/stores/panel/variables/types' @@ -23,7 +23,7 @@ const DuplicateRequestSchema = z.object({ // POST /api/workflows/[id]/duplicate - Duplicate a workflow with all its blocks, edges, and subflows export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id: sourceWorkflowId } = await params - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const session = await getSession() diff --git a/apps/sim/app/api/workflows/[id]/execute/route.test.ts b/apps/sim/app/api/workflows/[id]/execute/route.test.ts index 8ddcac0232..8d7aceb531 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.test.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.test.ts @@ -162,6 +162,7 @@ describe('Workflow Execution API Route', () => { }), isHosted: vi.fn().mockReturnValue(false), getRotatingApiKey: vi.fn().mockReturnValue('rotated-api-key'), + generateRequestId: vi.fn(() => 'test-request-id'), })) vi.doMock('@/lib/logs/execution/logging-session', () => ({ diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index 8fb483d184..e71ae5f620 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -10,7 +10,7 @@ import { getPersonalAndWorkspaceEnv } from '@/lib/environment/utils' import { createLogger } from '@/lib/logs/console/logger' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans' -import { decryptSecret } from '@/lib/utils' +import { decryptSecret, generateRequestId } from '@/lib/utils' import { loadDeployedWorkflowState } from '@/lib/workflows/db-helpers' import { createHttpResponseFromBlock, @@ -342,7 +342,7 @@ async function executeWorkflow( } export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { @@ -440,7 +440,7 @@ export async function POST( request: Request, { params }: { params: Promise<{ id: string }> } ): Promise { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const logger = createLogger('WorkflowExecuteAPI') logger.info(`[${requestId}] Raw request body: `) diff --git a/apps/sim/app/api/workflows/[id]/log/route.ts b/apps/sim/app/api/workflows/[id]/log/route.ts index f6e17443ef..e3276c2666 100644 --- a/apps/sim/app/api/workflows/[id]/log/route.ts +++ b/apps/sim/app/api/workflows/[id]/log/route.ts @@ -2,6 +2,7 @@ import type { NextRequest } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans' +import { generateRequestId } from '@/lib/utils' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -10,7 +11,7 @@ const logger = createLogger('WorkflowLogAPI') export const dynamic = 'force-dynamic' export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts b/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts index 1d329e0c18..078ff9f5e7 100644 --- a/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts +++ b/apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts @@ -1,8 +1,8 @@ -import crypto from 'crypto' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/db-helpers' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -20,7 +20,7 @@ export const runtime = 'nodejs' * Revert workflow to its deployed state by saving deployed state to normalized tables */ export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const { id } = await params try { diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index ba51a784b8..815d66cdca 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -6,6 +6,7 @@ import { verifyInternalToken } from '@/lib/auth/internal' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions, hasAdminPermission } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' import { db } from '@/db' import { apiKey as apiKeyTable, templates, workflow } from '@/db/schema' @@ -25,7 +26,7 @@ const UpdateWorkflowSchema = z.object({ * Uses hybrid approach: try normalized tables first, fallback to JSON blob */ export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await params @@ -169,7 +170,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await params @@ -306,7 +307,7 @@ export async function DELETE( * Update workflow metadata (name, description, color, folderId) */ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await params diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index 1bb1b2d592..8bef22f51b 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -4,6 +4,7 @@ import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/db-helpers' import { sanitizeAgentToolsInBlocks } from '@/lib/workflows/validation' import { db } from '@/db' @@ -112,7 +113,7 @@ const WorkflowStateSchema = z.object({ * Save complete workflow state to normalized database tables */ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await params diff --git a/apps/sim/app/api/workflows/[id]/status/route.ts b/apps/sim/app/api/workflows/[id]/status/route.ts index 73d484b39a..c213e6b508 100644 --- a/apps/sim/app/api/workflows/[id]/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/status/route.ts @@ -1,5 +1,6 @@ import type { NextRequest } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' import { hasWorkflowChanged } from '@/lib/workflows/utils' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' @@ -8,7 +9,7 @@ import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/ const logger = createLogger('WorkflowStatusAPI') export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params diff --git a/apps/sim/app/api/workflows/[id]/variables/route.ts b/apps/sim/app/api/workflows/[id]/variables/route.ts index a52767f159..91f7b2ef33 100644 --- a/apps/sim/app/api/workflows/[id]/variables/route.ts +++ b/apps/sim/app/api/workflows/[id]/variables/route.ts @@ -4,6 +4,7 @@ import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { workflow } from '@/db/schema' import type { Variable } from '@/stores/panel/variables/types' @@ -23,7 +24,7 @@ const VariablesSchema = z.object({ }) export async function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const workflowId = (await params).id try { @@ -112,7 +113,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: } export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const workflowId = (await params).id try { diff --git a/apps/sim/app/api/workflows/[id]/yaml/route.ts b/apps/sim/app/api/workflows/[id]/yaml/route.ts index 96f17d89b3..4a5c84dc7a 100644 --- a/apps/sim/app/api/workflows/[id]/yaml/route.ts +++ b/apps/sim/app/api/workflows/[id]/yaml/route.ts @@ -5,6 +5,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { simAgentClient } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { loadWorkflowFromNormalizedTables, saveWorkflowToNormalizedTables, @@ -244,7 +245,7 @@ async function upsertCustomToolsFromBlocks( * Handles copilot edits, imports, and text editor saves */ export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const { id: workflowId } = await params diff --git a/apps/sim/app/api/workflows/public/[id]/route.ts b/apps/sim/app/api/workflows/public/[id]/route.ts index b253c558cb..55dc8f1b39 100644 --- a/apps/sim/app/api/workflows/public/[id]/route.ts +++ b/apps/sim/app/api/workflows/public/[id]/route.ts @@ -1,6 +1,7 @@ import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { db } from '@/db' import { marketplace, workflow } from '@/db/schema' @@ -11,7 +12,7 @@ const logger = createLogger('PublicWorkflowAPI') export const revalidate = 3600 export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const { id } = await params diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 831eada916..eff74ee62f 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -1,9 +1,9 @@ -import crypto from 'crypto' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' +import { generateRequestId } from '@/lib/utils' import { db } from '@/db' import { workflow, workflowBlocks, workspace } from '@/db/schema' import { verifyWorkspaceMembership } from './utils' @@ -20,7 +20,7 @@ const CreateWorkflowSchema = z.object({ // GET /api/workflows - Get workflows for user (optionally filtered by workspaceId) export async function GET(request: Request) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const startTime = Date.now() const url = new URL(request.url) const workspaceId = url.searchParams.get('workspaceId') @@ -82,7 +82,7 @@ export async function GET(request: Request) { // POST /api/workflows - Create a new workflow export async function POST(req: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const session = await getSession() if (!session?.user?.id) { diff --git a/apps/sim/app/api/workflows/yaml/convert/route.ts b/apps/sim/app/api/workflows/yaml/convert/route.ts index 772306b976..3582228a20 100644 --- a/apps/sim/app/api/workflows/yaml/convert/route.ts +++ b/apps/sim/app/api/workflows/yaml/convert/route.ts @@ -1,6 +1,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' import { simAgentClient } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -9,7 +10,7 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w const logger = createLogger('WorkflowYamlAPI') export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.info(`[${requestId}] Converting workflow JSON to YAML`) diff --git a/apps/sim/app/api/workflows/yaml/export/route.ts b/apps/sim/app/api/workflows/yaml/export/route.ts index 3c1cc9bc28..1373528582 100644 --- a/apps/sim/app/api/workflows/yaml/export/route.ts +++ b/apps/sim/app/api/workflows/yaml/export/route.ts @@ -4,6 +4,7 @@ import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' import { simAgentClient } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' @@ -15,7 +16,7 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w const logger = createLogger('WorkflowYamlExportAPI') export async function GET(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const url = new URL(request.url) const workflowId = url.searchParams.get('workflowId') diff --git a/apps/sim/app/api/workspaces/[id]/environment/route.ts b/apps/sim/app/api/workspaces/[id]/environment/route.ts index c3337b3100..31aa42f980 100644 --- a/apps/sim/app/api/workspaces/[id]/environment/route.ts +++ b/apps/sim/app/api/workspaces/[id]/environment/route.ts @@ -4,7 +4,7 @@ import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' -import { decryptSecret, encryptSecret } from '@/lib/utils' +import { decryptSecret, encryptSecret, generateRequestId } from '@/lib/utils' import { db } from '@/db' import { environment, workspace, workspaceEnvironment } from '@/db/schema' @@ -19,7 +19,7 @@ const DeleteSchema = z.object({ }) export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const workspaceId = (await params).id try { @@ -102,7 +102,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ } export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const workspaceId = (await params).id try { @@ -169,7 +169,7 @@ export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() const workspaceId = (await params).id try { diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index b184ca6e86..ddb4246e74 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -1,4 +1,3 @@ -import crypto from 'crypto' import { and, desc, eq, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' diff --git a/apps/sim/app/api/yaml/autolayout/route.ts b/apps/sim/app/api/yaml/autolayout/route.ts index 7c4cb28894..ecc7b730f0 100644 --- a/apps/sim/app/api/yaml/autolayout/route.ts +++ b/apps/sim/app/api/yaml/autolayout/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -50,7 +51,7 @@ const AutoLayoutRequestSchema = z.object({ }) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await request.json() diff --git a/apps/sim/app/api/yaml/diff/create/route.ts b/apps/sim/app/api/yaml/diff/create/route.ts index 966f9238c0..627d539e13 100644 --- a/apps/sim/app/api/yaml/diff/create/route.ts +++ b/apps/sim/app/api/yaml/diff/create/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -61,7 +62,7 @@ const CreateDiffRequestSchema = z.object({ }) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() logger.info(`[${requestId}] ===== YAML DIFF CREATE API CALLED =====`) diff --git a/apps/sim/app/api/yaml/diff/merge/route.ts b/apps/sim/app/api/yaml/diff/merge/route.ts index b7da3cf7ef..4990e46bdb 100644 --- a/apps/sim/app/api/yaml/diff/merge/route.ts +++ b/apps/sim/app/api/yaml/diff/merge/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -45,7 +46,7 @@ const MergeDiffRequestSchema = z.object({ }) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await request.json() diff --git a/apps/sim/app/api/yaml/generate/route.ts b/apps/sim/app/api/yaml/generate/route.ts index c9e09fb1e9..df84ffb61d 100644 --- a/apps/sim/app/api/yaml/generate/route.ts +++ b/apps/sim/app/api/yaml/generate/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -19,7 +20,7 @@ const GenerateRequestSchema = z.object({ }) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await request.json() diff --git a/apps/sim/app/api/yaml/health/route.ts b/apps/sim/app/api/yaml/health/route.ts index c1b8fac7c1..260565b230 100644 --- a/apps/sim/app/api/yaml/health/route.ts +++ b/apps/sim/app/api/yaml/health/route.ts @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' const logger = createLogger('YamlHealthAPI') @@ -9,7 +10,7 @@ const logger = createLogger('YamlHealthAPI') const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT export async function GET() { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { logger.info(`[${requestId}] Checking YAML service health`) diff --git a/apps/sim/app/api/yaml/parse/route.ts b/apps/sim/app/api/yaml/parse/route.ts index 58e0a97bf9..fec4b5deaa 100644 --- a/apps/sim/app/api/yaml/parse/route.ts +++ b/apps/sim/app/api/yaml/parse/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -18,7 +19,7 @@ const ParseRequestSchema = z.object({ }) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await request.json() diff --git a/apps/sim/app/api/yaml/to-workflow/route.ts b/apps/sim/app/api/yaml/to-workflow/route.ts index 3b4c78a971..a31a50bca9 100644 --- a/apps/sim/app/api/yaml/to-workflow/route.ts +++ b/apps/sim/app/api/yaml/to-workflow/route.ts @@ -3,6 +3,7 @@ import { z } from 'zod' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent' +import { generateRequestId } from '@/lib/utils' import { getAllBlocks } from '@/blocks/registry' import type { BlockConfig } from '@/blocks/types' import { resolveOutputType } from '@/blocks/utils' @@ -25,7 +26,7 @@ const ConvertRequestSchema = z.object({ }) export async function POST(request: NextRequest) { - const requestId = crypto.randomUUID().slice(0, 8) + const requestId = generateRequestId() try { const body = await request.json() diff --git a/apps/sim/app/chat/components/auth/email/email-auth.tsx b/apps/sim/app/chat/components/auth/email/email-auth.tsx index 798e5a89f0..ac50633dd8 100644 --- a/apps/sim/app/chat/components/auth/email/email-auth.tsx +++ b/apps/sim/app/chat/components/auth/email/email-auth.tsx @@ -268,7 +268,7 @@ export default function EmailAuth({ type='button' onClick={handleResendOtp} disabled={isSendingOtp} - className='text-primary text-sm hover:underline disabled:opacity-50' + className='text-muted-foreground text-sm hover:underline disabled:opacity-50' > {isSendingOtp ? 'Sending...' : 'Resend code'} @@ -280,7 +280,7 @@ export default function EmailAuth({ setOtpValue('') setAuthError(null) }} - className='text-primary text-sm hover:underline' + className='text-muted-foreground text-sm hover:underline' > Change email diff --git a/apps/sim/app/unsubscribe/unsubscribe.tsx b/apps/sim/app/unsubscribe/unsubscribe.tsx index 58de6e18b0..d185f4c319 100644 --- a/apps/sim/app/unsubscribe/unsubscribe.tsx +++ b/apps/sim/app/unsubscribe/unsubscribe.tsx @@ -178,7 +178,10 @@ function UnsubscribeContent() {

Need immediate help? Email us at{' '} - + {brand.supportEmail}

@@ -258,7 +261,10 @@ function UnsubscribeContent() {

If you change your mind, you can always update your email preferences in your account settings or contact us at{' '} - + {brand.supportEmail}

@@ -371,7 +377,10 @@ function UnsubscribeContent() {

Questions? Contact us at{' '} - + {brand.supportEmail}

diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-loading.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-loading.tsx index 09cbf5b9e0..8ba5c4c3a9 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-loading.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/document-loading.tsx @@ -66,7 +66,7 @@ export function DocumentLoading({
- {isFolderSelected(folder.id) && } + {isFolderSelected(folder.id) && } ))} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx index f4e8dfe3e9..dd0c122405 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/level.tsx @@ -48,7 +48,7 @@ export default function Level() { className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > Any status - {level === 'all' && } + {level === 'all' && } @@ -66,7 +66,7 @@ export default function Level() {
{levelItem.label}
- {level === levelItem.value && } + {level === levelItem.value && } ))} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/timeline.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/timeline.tsx index 412c4c8cc7..6345ed8819 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/timeline.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/timeline.tsx @@ -39,7 +39,7 @@ export default function Timeline() { className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > All time - {timeRange === 'All time' && } + {timeRange === 'All time' && } @@ -54,7 +54,7 @@ export default function Timeline() { className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > {range} - {timeRange === range && } + {timeRange === range && } ))} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx index 7df8fe0c8e..90ea545720 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/trigger.tsx @@ -65,7 +65,7 @@ export default function Trigger() { className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > All triggers - {triggers.length === 0 && } + {triggers.length === 0 && } @@ -85,7 +85,9 @@ export default function Trigger() { )} {triggerItem.label} - {isTriggerSelected(triggerItem.value) && } + {isTriggerSelected(triggerItem.value) && ( + + )} ))} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx index 90fa03a6df..8d4c2b6935 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/filters/components/workflow.tsx @@ -95,7 +95,7 @@ export default function Workflow() { className='flex cursor-pointer items-center justify-between rounded-md px-3 py-2 font-[380] text-card-foreground text-sm hover:bg-secondary/50 focus:bg-secondary/50' > All workflows - {workflowIds.length === 0 && } + {workflowIds.length === 0 && } {!loading && workflows.length > 0 && } @@ -117,7 +117,9 @@ export default function Workflow() { /> {workflow.name} - {isWorkflowSelected(workflow.id) && } + {isWorkflowSelected(workflow.id) && ( + + )} ))} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/auth-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/auth-selector.tsx index 0d6aa59a18..a228259a1b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/auth-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/auth-selector.tsx @@ -119,7 +119,7 @@ export function AuthSelector({ {isExistingChat && !password && (
-
+
Password set
Current password is securely stored diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx index 6b83941b43..f606720929 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx @@ -170,7 +170,7 @@ export function DeployForm({ type='button' variant='ghost' size='sm' - className='h-7 gap-1 px-2 text-primary text-xs' + className='h-7 gap-1 px-2 text-muted-foreground text-xs' onClick={() => setIsCreatingKey(true)} > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command.tsx index a7c5014cc1..543e2e49aa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components/example-command/example-command.tsx @@ -156,7 +156,7 @@ export function ExampleCommand({ onClick={() => setMode('sync')} className={`h-6 min-w-[50px] px-2 py-1 text-xs transition-none ${ mode === 'sync' - ? 'border-primary bg-primary text-primary-foreground hover:border-primary hover:bg-primary hover:text-primary-foreground' + ? 'border-primary bg-primary text-muted-foreground hover:border-primary hover:bg-primary hover:text-muted-foreground' : '' }`} > @@ -168,7 +168,7 @@ export function ExampleCommand({ onClick={() => setMode('async')} className={`h-6 min-w-[50px] px-2 py-1 text-xs transition-none ${ mode === 'async' - ? 'border-primary bg-primary text-primary-foreground hover:border-primary hover:bg-primary hover:text-primary-foreground' + ? 'border-primary bg-primary text-muted-foreground hover:border-primary hover:bg-primary hover:text-muted-foreground' : '' }`} > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/template-modal/template-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/template-modal/template-modal.tsx index 60bfaa5ef5..3a1a237675 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/template-modal/template-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/template-modal/template-modal.tsx @@ -446,7 +446,7 @@ export function TemplateModal({ open, onOpenChange, workflowId }: TemplateModalP className={cn( 'flex h-8 w-8 items-center justify-center rounded-md border transition-colors hover:bg-muted', field.value === icon.value && - 'bg-primary text-primary-foreground' + 'bg-primary text-muted-foreground' )} > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx index 906324cd34..810f01fbab 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/output-select/output-select.tsx @@ -391,7 +391,7 @@ export function OutputSelect({
{output.path} {selectedOutputs.includes(output.id) && ( - + )} ))} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx index ca5e3df6b4..b9d2479861 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/credential-selector.tsx @@ -319,7 +319,7 @@ export function CredentialSelector({ {credentials.length === 0 && ( -
+
{getProviderIcon(provider)} Connect {getProviderName(provider)} account
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/confluence-file-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/confluence-file-selector.tsx index 940a55461b..d145d880e0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/confluence-file-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/confluence-file-selector.tsx @@ -541,7 +541,7 @@ export function ConfluenceFileSelector({ {credentials.length === 0 && ( -
+
Connect Confluence account
@@ -585,7 +585,7 @@ export function ConfluenceFileSelector({ href={selectedFile.webViewLink} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > Open in Confluence diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/google-drive-picker.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/google-drive-picker.tsx index 9648f5ac57..918bf24cd6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/google-drive-picker.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/google-drive-picker.tsx @@ -498,7 +498,7 @@ export function GoogleDrivePicker({ href={selectedFile.webViewLink} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-muted-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > Open in Drive @@ -509,7 +509,7 @@ export function GoogleDrivePicker({ href={`https://drive.google.com/file/d/${selectedFile.id}/view`} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-muted-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > Open in Drive diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx index 1dc84555bf..ae74918456 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx @@ -583,7 +583,7 @@ export function JiraIssueSelector({ {credentials.length === 0 && ( -
+
Connect Jira account
@@ -627,7 +627,7 @@ export function JiraIssueSelector({ href={selectedIssue.webViewLink} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > Open in Jira diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/microsoft-file-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/microsoft-file-selector.tsx index 1123a9960e..220ccfb23d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/microsoft-file-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/microsoft-file-selector.tsx @@ -931,7 +931,7 @@ export function MicrosoftFileSelector({ {credentials.length === 0 && ( -
+
{getProviderIcon(provider)} Connect {getProviderName(provider)} account
@@ -975,7 +975,7 @@ export function MicrosoftFileSelector({ href={selectedFile.webViewLink} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > @@ -992,7 +992,7 @@ export function MicrosoftFileSelector({ href={`https://graph.microsoft.com/v1.0/me/drive/items/${selectedFile.id}`} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx index 170d8360cc..9a34c984d3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/teams-message-selector.tsx @@ -828,7 +828,7 @@ export function TeamsMessageSelector({ {credentials.length === 0 && ( -
+
Connect Microsoft Teams account
@@ -870,7 +870,7 @@ export function TeamsMessageSelector({ href={selectedMessage.webViewLink} target='_blank' rel='noopener noreferrer' - className='flex items-center gap-1 text-primary text-xs hover:underline' + className='flex items-center gap-1 text-foreground text-xs hover:underline' onClick={(e) => e.stopPropagation()} > Open in Microsoft Teams diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/wealthbox-file-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/wealthbox-file-selector.tsx index 47771323b7..4cbf3a01b4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/wealthbox-file-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-selector/components/wealthbox-file-selector.tsx @@ -405,7 +405,7 @@ export function WealthboxFileSelector({ {credentials.length === 0 && ( -
+
Connect Wealthbox account
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-upload.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-upload.tsx index 18e9109292..15a15768b7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-upload.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/file-upload.tsx @@ -517,7 +517,7 @@ export function FileUpload({ data-testid='file-input-element' /> -
+
{/* File list with consistent spacing */} {(hasFiles || isUploading) && (
@@ -533,7 +533,11 @@ export function FileUpload({ <> {uploadingFiles.map(renderUploadingItem)}
- +
{uploadProgress < 100 ? 'Uploading...' : 'Upload complete!'}
@@ -545,7 +549,7 @@ export function FileUpload({ {/* Action buttons */} {(hasFiles || isUploading) && ( -
+