Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/docs/content/docs/en/integrations/airtable.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Write new records to an Airtable table
| `baseId` | string | Yes | Airtable base ID \(starts with "app", e.g., "appXXXXXXXXXXXXXX"\) |
| `tableId` | string | Yes | Table ID \(starts with "tbl"\) or table name |
Comment thread
icecrasher321 marked this conversation as resolved.
| `records` | json | Yes | Array of records to create, each with a `fields` object |
| `typecast` | boolean | No | When true, Airtable automatically converts string values to the field type |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prueba

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prueba2

#### Output

Expand All @@ -166,6 +167,7 @@ Update an existing record in an Airtable table by ID
| `tableId` | string | Yes | Table ID \(starts with "tbl"\) or table name |
| `recordId` | string | Yes | Record ID to update \(starts with "rec", e.g., "recXXXXXXXXXXXXXX"\) |
| `fields` | json | Yes | An object containing the field names and their new values |
| `typecast` | boolean | No | When true, Airtable automatically converts string values to the field type |

#### Output

Expand All @@ -190,6 +192,7 @@ Update multiple existing records in an Airtable table
| `baseId` | string | Yes | Airtable base ID \(starts with "app", e.g., "appXXXXXXXXXXXXXX"\) |
| `tableId` | string | Yes | Table ID \(starts with "tbl"\) or table name |
| `records` | json | Yes | Array of records to update, each with an `id` and a `fields` object |
| `typecast` | boolean | No | When true, Airtable automatically converts string values to the field type |

#### Output

Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/chat/manage/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export const PATCH = withRouteHandler(
const deployResult = await performFullDeploy({
workflowId: existingChat[0].workflowId,
userId: session.user.id,
request,
Comment thread
icecrasher321 marked this conversation as resolved.
})

if (!deployResult.success) {
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/cron/renew-subscriptions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { verifyCronAuth } from '@/lib/auth/internal'
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
import { runDetached } from '@/lib/core/utils/background'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { deliverableWebhookPredicate } from '@/lib/webhooks/delivery-predicate'
import { getCredentialOwner, getNotificationUrl } from '@/lib/webhooks/provider-subscription-utils'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

Expand Down Expand Up @@ -96,7 +97,7 @@ async function renewExpiringSubscriptions(): Promise<{
.from(webhookTable)
.where(
and(
eq(webhookTable.isActive, true),
deliverableWebhookPredicate(webhookTable, 'active_only'),
or(
eq(webhookTable.provider, 'microsoft-teams'),
eq(webhookTable.provider, 'microsoftteams')
Expand Down
11 changes: 9 additions & 2 deletions apps/sim/app/api/mcp/serve/[serverId]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ describe('MCP Serve Route', () => {
},
])
.mockResolvedValueOnce([{ toolName: 'tool_a', workflowId: 'wf-1' }])
.mockResolvedValueOnce([{ isDeployed: true, workspaceId: 'ws-1' }])
.mockResolvedValueOnce([
{ isDeployed: true, workspaceId: 'ws-1', deploymentVersionId: 'deployment-1' },
])

hybridAuthMockFns.mockCheckHybridAuth.mockResolvedValueOnce({
success: true,
Expand Down Expand Up @@ -656,7 +658,9 @@ describe('MCP Serve Route', () => {
},
])
.mockResolvedValueOnce([{ toolName: 'tool_a', workflowId: 'wf-1' }])
.mockResolvedValueOnce([{ isDeployed: true, workspaceId: 'ws-1' }])
.mockResolvedValueOnce([
{ isDeployed: true, workspaceId: 'ws-1', deploymentVersionId: 'deployment-1' },
])
fetchMock.mockResolvedValueOnce(
new Response(
JSON.stringify({
Expand Down Expand Up @@ -689,6 +693,9 @@ describe('MCP Serve Route', () => {
const fetchOptions = fetchMock.mock.calls[0][1] as RequestInit
const headers = fetchOptions.headers as Record<string, string>
expect(headers['X-Sim-MCP-Tool-Call']).toBe('true')
expect(JSON.parse(fetchOptions.body as string)).toMatchObject({
deploymentVersionId: 'deployment-1',
})
})

it('preserves downstream attributed usage admission rejections', async () => {
Expand Down
22 changes: 20 additions & 2 deletions apps/sim/app/api/mcp/serve/[serverId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import {
type Tool,
} from '@modelcontextprotocol/sdk/types.js'
import { db } from '@sim/db'
import { workflow, workflowMcpServer, workflowMcpTool, workspace } from '@sim/db/schema'
import {
workflow,
workflowDeploymentVersion,
workflowMcpServer,
workflowMcpTool,
workspace,
} from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, asc, eq, gt, isNull, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
Expand Down Expand Up @@ -751,8 +757,19 @@ async function handleToolsCall(
}

const [wf] = await db
.select({ isDeployed: workflow.isDeployed, workspaceId: workflow.workspaceId })
.select({
isDeployed: workflow.isDeployed,
workspaceId: workflow.workspaceId,
deploymentVersionId: workflowDeploymentVersion.id,
})
.from(workflow)
.leftJoin(
workflowDeploymentVersion,
and(
eq(workflowDeploymentVersion.workflowId, workflow.id),
eq(workflowDeploymentVersion.isActive, true)
)
)
Comment thread
icecrasher321 marked this conversation as resolved.
.where(and(eq(workflow.id, tool.workflowId), isNull(workflow.archivedAt)))
.limit(1)
Comment thread
icecrasher321 marked this conversation as resolved.
const abortedAfterWorkflowLookup = callerAbortedJsonRpcResponse(id, abortSignal)
Expand Down Expand Up @@ -813,6 +830,7 @@ async function handleToolsCall(
input: params.arguments || {},
triggerType: 'mcp',
includeFileBase64: false,
...(wf.deploymentVersionId ? { deploymentVersionId: wf.deploymentVersionId } : {}),
})
assertKnownSizeWithinLimit(
Buffer.byteLength(workflowRequestBody, 'utf-8'),
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/schedules/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ async function claimWorkflowSchedules(queuedAt: Date, limit: number) {
lastQueuedAt: workflowSchedule.lastQueuedAt,
timezone: workflowSchedule.timezone,
deploymentVersionId: workflowSchedule.deploymentVersionId,
deploymentOperationId: workflowSchedule.deploymentOperationId,
sourceType: workflowSchedule.sourceType,
})

Expand Down Expand Up @@ -864,6 +865,7 @@ async function processScheduleItem(
workspaceId,
billingAttribution,
deploymentVersionId: schedule.deploymentVersionId || undefined,
deploymentOperationId: schedule.deploymentOperationId || undefined,
cronExpression: schedule.cronExpression || undefined,
timezone: schedule.timezone || undefined,
lastRanAt: schedule.lastRanAt?.toISOString(),
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/tools/deployments/deploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
const result = await performFullDeploy({
workflowId,
userId: auth.userId,
workflowName: access.workflow.name || undefined,
versionName: name,
versionDescription: description ?? undefined,
requestId,
request,
})

if (!result.success) {
Expand All @@ -69,9 +67,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
success: true,
output: {
workflowId,
isDeployed: true,
isDeployed: Boolean(result.activeDeployment),
deployedAt: result.deployedAt?.toISOString() ?? null,
version: result.version,
activeDeployment: result.activeDeployment,
latestDeploymentAttempt: result.latestDeploymentAttempt,
warnings: result.warnings ?? [],
},
})
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/tools/deployments/promote/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
workflowId,
version,
userId: auth.userId,
workflow: access.workflow as Record<string, unknown>,
requestId,
request,
})

if (!result.success) {
Expand All @@ -69,9 +67,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
success: true,
output: {
workflowId,
isDeployed: true,
isDeployed: Boolean(result.activeDeployment),
deployedAt: result.deployedAt?.toISOString() ?? null,
version,
activeDeployment: result.activeDeployment,
latestDeploymentAttempt: result.latestDeploymentAttempt,
warnings: result.warnings ?? [],
},
})
Expand Down
20 changes: 20 additions & 0 deletions apps/sim/app/api/tools/deployments/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ describe('POST /api/tools/deployments/deploy', () => {
success: true,
deployedAt: new Date('2026-06-12T00:00:00Z'),
version: 4,
activeDeployment: {
deploymentVersionId: 'dv-4',
version: 4,
deployedAt: '2026-06-12T00:00:00.000Z',
},
})
})

Expand Down Expand Up @@ -157,6 +162,11 @@ describe('POST /api/tools/deployments/deploy', () => {
isDeployed: true,
deployedAt: '2026-06-12T00:00:00.000Z',
version: 4,
activeDeployment: {
deploymentVersionId: 'dv-4',
version: 4,
deployedAt: '2026-06-12T00:00:00.000Z',
},
warnings: [],
},
})
Expand Down Expand Up @@ -236,6 +246,11 @@ describe('POST /api/tools/deployments/promote', () => {
mockPerformActivateVersion.mockResolvedValue({
success: true,
deployedAt: new Date('2026-06-12T00:00:00Z'),
activeDeployment: {
deploymentVersionId: 'dv-3',
version: 3,
deployedAt: '2026-06-12T00:00:00.000Z',
},
})
})

Expand All @@ -255,6 +270,11 @@ describe('POST /api/tools/deployments/promote', () => {
isDeployed: true,
deployedAt: '2026-06-12T00:00:00.000Z',
version: 3,
activeDeployment: {
deploymentVersionId: 'dv-3',
version: 3,
deployedAt: '2026-06-12T00:00:00.000Z',
},
warnings: [],
})
})
Expand Down
12 changes: 0 additions & 12 deletions apps/sim/app/api/v1/admin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,18 +629,6 @@ export interface AdminDeploymentVersion {
deployedByName: string | null
}

export interface AdminDeployResult {
isDeployed: boolean
version: number
deployedAt: string
warnings?: string[]
}

export interface AdminUndeployResult {
isDeployed: boolean
warnings?: string[]
}

// =============================================================================
// Audit Log Types
// =============================================================================
Expand Down
23 changes: 14 additions & 9 deletions apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createLogger } from '@sim/logger'
import { getActiveWorkflowRecord } from '@sim/platform-authz/workflow'
import {
type AdminV1DeployResult,
type AdminV1UndeployResult,
adminV1DeployWorkflowContract,
adminV1UndeployWorkflowContract,
} from '@/lib/api/contracts/v1/admin'
Expand All @@ -15,7 +17,6 @@ import {
notFoundResponse,
singleResponse,
} from '@/app/api/v1/admin/responses'
import type { AdminDeployResult, AdminUndeployResult } from '@/app/api/v1/admin/types'

const logger = createLogger('AdminWorkflowDeployAPI')
export const maxDuration = 120
Expand Down Expand Up @@ -51,9 +52,7 @@ export const POST = withRouteHandler(
const result = await performFullDeploy({
workflowId,
userId: workflowRecord.userId,
workflowName: workflowRecord.name,
requestId,
request,
actorId: 'admin-api',
})

Expand All @@ -63,13 +62,19 @@ export const POST = withRouteHandler(
return internalErrorResponse(result.error || 'Failed to deploy workflow')
}

logger.info(`[${requestId}] Admin API: Deployed workflow ${workflowId} as v${result.version}`)
const isDeployed = Boolean(result.activeDeployment)
const attemptActivated = result.latestDeploymentAttempt?.status === 'active'
logger.info(
`[${requestId}] Admin API: Deployment ${attemptActivated ? 'activated' : 'accepted'} for workflow ${workflowId}`
)

const response: AdminDeployResult = {
isDeployed: true,
version: result.version!,
deployedAt: result.deployedAt!.toISOString(),
const response: AdminV1DeployResult = {
isDeployed,
version: result.version ?? null,
deployedAt: result.deployedAt?.toISOString() ?? null,
warnings: result.warnings,
activeDeployment: result.activeDeployment,
latestDeploymentAttempt: result.latestDeploymentAttempt,
}

return singleResponse(response)
Expand Down Expand Up @@ -108,7 +113,7 @@ export const DELETE = withRouteHandler(

logger.info(`Admin API: Undeployed workflow ${workflowId}`)

const response: AdminUndeployResult = {
const response: AdminV1UndeployResult = {
isDeployed: false,
warnings: result.warnings,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ export const POST = withRouteHandler(
workflowId,
version: versionNum,
userId: workflowRecord.userId,
workflow: workflowRecord as Record<string, unknown>,
requestId,
request,
actorId: 'admin-api',
})

Expand All @@ -53,14 +51,16 @@ export const POST = withRouteHandler(
}

logger.info(
`[${requestId}] Admin API: Activated version ${versionNum} for workflow ${workflowId}`
`[${requestId}] Admin API: ${result.latestDeploymentAttempt?.status === 'active' ? 'Activated' : 'Accepted activation for'} version ${versionNum} on workflow ${workflowId}`
)

return singleResponse({
success: true,
version: versionNum,
deployedAt: result.deployedAt!.toISOString(),
deployedAt: result.deployedAt?.toISOString() ?? null,
warnings: result.warnings,
activeDeployment: result.activeDeployment,
latestDeploymentAttempt: result.latestDeploymentAttempt,
})
} catch (error) {
logger.error(
Expand Down
29 changes: 23 additions & 6 deletions apps/sim/app/api/v1/workflows/[id]/deploy/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ describe('POST /api/v1/workflows/[id]/deploy', () => {
deployedAt: new Date('2026-06-12T00:00:00Z'),
version: 4,
warnings: undefined,
activeDeployment: {
deploymentVersionId: 'dv-4',
version: 4,
deployedAt: '2026-06-12T00:00:00.000Z',
},
latestDeploymentAttempt: {
id: 'op-1',
deploymentVersionId: 'dv-4',
version: 4,
action: 'deploy',
status: 'active',
readiness: { webhooks: 'ready', schedules: 'ready', mcp: 'ready' },
requestedAt: '2026-06-12T00:00:00.000Z',
activatedAt: '2026-06-12T00:00:00.000Z',
error: null,
},
})
})

Expand Down Expand Up @@ -163,6 +179,8 @@ describe('POST /api/v1/workflows/[id]/deploy', () => {
deployedAt: '2026-06-12T00:00:00.000Z',
version: 4,
warnings: [],
activeDeployment: expect.objectContaining({ deploymentVersionId: 'dv-4', version: 4 }),
latestDeploymentAttempt: expect.objectContaining({ id: 'op-1', status: 'active' }),
})
})

Expand All @@ -179,12 +197,11 @@ describe('POST /api/v1/workflows/[id]/deploy', () => {
versionDescription: 'Fixes the agent prompt',
})
)
expect(mockCaptureServerEvent).toHaveBeenCalledWith(
'user-1',
'workflow_deployed',
expect.objectContaining({ workflow_id: WORKFLOW_ID }),
expect.anything()
)
/**
* The workflow_deployed analytics event is emitted by the activation
* side effects in the deployment outbox, not by this route.
*/
expect(mockCaptureServerEvent).not.toHaveBeenCalled()
})

it('maps validation failures from the orchestration to 400', async () => {
Expand Down
Loading
Loading