diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 92b913971fd..2417e6acb58 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -91,7 +91,12 @@ export function AgentPhoneIcon(props: SVGProps) { export function CrowdStrikeIcon(props: SVGProps) { return ( - + ) { export function FirecrawlIcon(props: SVGProps) { return ( - + ) { export function SerperIcon(props: SVGProps) { return ( - + ) { export function NotionIcon(props: SVGProps) { return ( - + ) @@ -1171,27 +1176,13 @@ export function GrafanaIcon(props: SVGProps) { const gradientId = `grafana_gradient_${id}` return ( - + - + @@ -1504,7 +1495,7 @@ export function ProspeoIcon(props: SVGProps) { {...props} width='1em' height='1em' - viewBox='0 0 32 32' + viewBox='1.17 1.178 29.66 29.643' fill='none' xmlns='http://www.w3.org/2000/svg' > @@ -2029,7 +2020,9 @@ export function ConfluenceIcon(props: SVGProps) { return (

+ {landingContent.install.heading} +

+

+ {landingContent.install.intro} +

+
    + {landingContent.install.steps.map((item, index) => ( +
  1. + +
    +

    + {item.title} +

    +

    + {item.body} +

    +
    +
  2. + ))} +
+
+ + Add to {name} + +
+ +
+ + )} + + {/* Privacy & data (integration-specific) */} + {landingContent?.privacy && ( + <> +
+

+ Privacy & data +

+

+ {landingContent.privacy.body}{' '} + + Privacy Policy + + . +

+
+
+ + )} + {/* How to automate */}

= { bedrock: ['bedrock/'], cerebras: ['cerebras/'], fireworks: ['fireworks/'], + together: ['together/'], + baseten: ['baseten/'], + 'ollama-cloud': ['ollama-cloud/'], groq: ['groq/'], openrouter: ['openrouter/'], vllm: ['vllm/'], diff --git a/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts b/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts index d862fe277f1..55d1b90a958 100644 --- a/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts +++ b/apps/sim/app/.well-known/oauth-authorization-server/api/mcp/copilot/route.ts @@ -1,6 +1,6 @@ import type { NextResponse } from 'next/server' -import { createMcpAuthorizationServerMetadataResponse } from '@/lib/mcp/oauth-discovery' +import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated' export async function GET(): Promise { - return createMcpAuthorizationServerMetadataResponse() + return copilotMcpDeprecatedResponse() } diff --git a/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts b/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts index a419ebda324..55d1b90a958 100644 --- a/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts +++ b/apps/sim/app/.well-known/oauth-protected-resource/api/mcp/copilot/route.ts @@ -1,6 +1,6 @@ import type { NextResponse } from 'next/server' -import { createMcpProtectedResourceMetadataResponse } from '@/lib/mcp/oauth-discovery' +import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated' export async function GET(): Promise { - return createMcpProtectedResourceMetadataResponse() + return copilotMcpDeprecatedResponse() } 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 35858ecd138..7dcd342d662 100644 --- a/apps/sim/app/api/auth/oauth/microsoft/files/route.ts +++ b/apps/sim/app/api/auth/oauth/microsoft/files/route.ts @@ -8,13 +8,57 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { GRAPH_ID_PATTERN } from '@/tools/microsoft_excel/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('MicrosoftFilesAPI') /** - * Get Excel files from Microsoft OneDrive + * Microsoft Graph paginates `search()` results via the `@odata.nextLink` + * absolute URL in the response body. Request the largest page (`$top` caps at + * 999) and drain following nextLink, bounded by a page cap. + * See https://learn.microsoft.com/en-us/graph/paging + */ +const MICROSOFT_FILES_PAGE_SIZE = 999 +const MAX_MICROSOFT_FILES_PAGES = 20 + +interface MicrosoftGraphFile { + id: string + name?: string + mimeType?: string + webUrl?: string + size?: number + createdDateTime?: string + lastModifiedDateTime?: string + thumbnails?: Array<{ small?: { url?: string }; medium?: { url?: string } }> + createdBy?: { user?: { displayName?: string; email?: string } } +} + +/** + * The shared `/api/auth/oauth/microsoft/files` route serves both the + * `microsoft.excel` and `microsoft.word` selectors. The two are distinguished + * by the `fileType` query parameter the selector forwards (defaulting to + * `excel` for backward compatibility), which drives both the search-query + * extension hint and the server-side result filter. + */ +const FILE_TYPE_CONFIG = { + excel: { + extension: '.xlsx', + mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }, + word: { + extension: '.docx', + mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }, +} as const + +type MicrosoftFileType = keyof typeof FILE_TYPE_CONFIG + +/** + * Get Excel or Word files from Microsoft OneDrive / SharePoint. The + * `fileType` query parameter selects which Office document type to return + * (defaults to `excel`). */ export const GET = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() @@ -27,6 +71,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { query: searchParams.get('query') ?? undefined, driveId: searchParams.get('driveId') ?? undefined, workflowId: searchParams.get('workflowId') ?? undefined, + fileType: searchParams.get('fileType') ?? undefined, }) if (!parsedQuery.success) { @@ -40,6 +85,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => { const { credentialId, driveId, workflowId } = parsedQuery.data const query = parsedQuery.data.query ?? '' + const fileType: MicrosoftFileType = parsedQuery.data.fileType ?? 'excel' + const { extension, mimeType: targetMimeType } = FILE_TYPE_CONFIG[fileType] + const authz = await authorizeCredentialUse(request, { credentialId, workflowId, @@ -72,11 +120,8 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 }) } - // Build search query for Excel files - let searchQuery = '.xlsx' - if (query) { - searchQuery = `${query} .xlsx` - } + // Build search query for the requested Office document type + const searchQuery = query ? `${query} ${extension}` : extension // Build the query parameters for Microsoft Graph API const searchParams_new = new URLSearchParams() @@ -84,7 +129,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { '$select', 'id,name,mimeType,webUrl,thumbnails,createdDateTime,lastModifiedDateTime,size,createdBy' ) - searchParams_new.append('$top', '50') + searchParams_new.append('$top', String(MICROSOFT_FILES_PAGE_SIZE)) // When driveId is provided (SharePoint), search within that specific drive. // Otherwise, search the user's personal OneDrive. @@ -99,44 +144,57 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } const drivePath = driveId ? `drives/${driveId}` : 'me/drive' - const response = await fetch( - `https://graph.microsoft.com/v1.0/${drivePath}/root/search(q='${encodeURIComponent(searchQuery)}')?${searchParams_new.toString()}`, - { + const rawFiles: MicrosoftGraphFile[] = [] + let nextUrl: string | undefined = + `https://graph.microsoft.com/v1.0/${drivePath}/root/search(q='${encodeURIComponent(searchQuery)}')?${searchParams_new.toString()}` + + for (let page = 0; page < MAX_MICROSOFT_FILES_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { headers: { Authorization: `Bearer ${accessToken}`, }, + }) + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: { message: 'Unknown error' } })) + logger.error(`[${requestId}] Microsoft Graph API error`, { + status: response.status, + error: errorData.error?.message || 'Failed to fetch files from Microsoft OneDrive', + }) + return NextResponse.json( + { + error: errorData.error?.message || 'Failed to fetch files from Microsoft OneDrive', + }, + { status: response.status } + ) } - ) - if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) - logger.error(`[${requestId}] Microsoft Graph API error`, { - status: response.status, - error: errorData.error?.message || 'Failed to fetch Excel files from Microsoft OneDrive', - }) - return NextResponse.json( - { - error: errorData.error?.message || 'Failed to fetch Excel files from Microsoft OneDrive', - }, - { status: response.status } - ) - } + const data = await response.json() + rawFiles.push(...((data.value as MicrosoftGraphFile[]) || [])) + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined - const data = await response.json() - let files = data.value || [] + if (nextUrl && page === MAX_MICROSOFT_FILES_PAGES - 1) { + logger.warn( + `[${requestId}] Microsoft files search hit pagination cap; list may be incomplete`, + { fileType, pages: MAX_MICROSOFT_FILES_PAGES, collected: rawFiles.length } + ) + } + } - // Transform Microsoft Graph response to match expected format and filter for Excel files - files = files + // Transform Microsoft Graph response and filter to the requested file type + const files = rawFiles .filter( - (file: any) => - file.name?.toLowerCase().endsWith('.xlsx') || - file.mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + (file: MicrosoftGraphFile) => + file.name?.toLowerCase().endsWith(extension) || file.mimeType === targetMimeType ) - .map((file: any) => ({ + .map((file: MicrosoftGraphFile) => ({ id: file.id, name: file.name, - mimeType: - file.mimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + mimeType: file.mimeType || targetMimeType, iconLink: file.thumbnails?.[0]?.small?.url, webViewLink: file.webUrl, thumbnailLink: file.thumbnails?.[0]?.medium?.url, @@ -155,7 +213,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ files }, { status: 200 }) } catch (error) { - logger.error(`[${requestId}] Error fetching Excel files from Microsoft OneDrive`, error) + logger.error(`[${requestId}] Error fetching files from Microsoft OneDrive`, error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } }) diff --git a/apps/sim/app/api/auth/sso/register/route.test.ts b/apps/sim/app/api/auth/sso/register/route.test.ts new file mode 100644 index 00000000000..87f4cdd6744 --- /dev/null +++ b/apps/sim/app/api/auth/sso/register/route.test.ts @@ -0,0 +1,196 @@ +/** + * @vitest-environment node + */ +import { createEnvMock, createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockGetSession, + mockRegisterSSOProvider, + mockHasSSOAccess, + mockValidateUrlWithDNS, + dbState, + memberTable, + ssoProviderTable, +} = vi.hoisted(() => ({ + mockGetSession: vi.fn(), + mockRegisterSSOProvider: vi.fn(), + mockHasSSOAccess: vi.fn(), + mockValidateUrlWithDNS: vi.fn(), + dbState: { members: [] as any[], providers: [] as any[] }, + memberTable: { + userId: 'member.userId', + organizationId: 'member.organizationId', + role: 'member.role', + }, + ssoProviderTable: { + id: 'sso.id', + providerId: 'sso.providerId', + domain: 'sso.domain', + issuer: 'sso.issuer', + userId: 'sso.userId', + organizationId: 'sso.organizationId', + oidcConfig: 'sso.oidcConfig', + samlConfig: 'sso.samlConfig', + }, +})) + +function makeBuilder(rows: any[]): any { + const thenable: any = Promise.resolve(rows) + thenable.where = (condition: any) => { + const values = condition?.values + if (Array.isArray(values) && values.length > 0) { + const target = String(values[values.length - 1]).toLowerCase() + return makeBuilder(rows.filter((r) => String(r.domain ?? '').toLowerCase() === target)) + } + return makeBuilder(rows) + } + thenable.limit = () => Promise.resolve(rows) + thenable.orderBy = () => Promise.resolve(rows) + return thenable +} + +vi.mock('@sim/db', () => ({ + db: { + select: () => ({ + from: (table: unknown) => + makeBuilder(table === memberTable ? dbState.members : dbState.providers), + }), + }, + member: memberTable, + ssoProvider: ssoProviderTable, +})) + +vi.mock('@/lib/auth', () => ({ + getSession: mockGetSession, + auth: { api: { registerSSOProvider: mockRegisterSSOProvider } }, +})) + +vi.mock('@/lib/billing', () => ({ + hasSSOAccess: mockHasSSOAccess, +})) + +vi.mock('@/lib/auth/sso/domain', () => ({ + normalizeSSODomain: (input: unknown): string | null => { + if (typeof input !== 'string') return null + const value = input.trim().toLowerCase() + return /^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(value) ? value : null + }, +})) + +vi.mock('@/lib/core/security/input-validation.server', () => ({ + validateUrlWithDNS: mockValidateUrlWithDNS, + secureFetchWithPinnedIP: vi.fn(), +})) + +vi.mock('@/lib/core/config/env', () => createEnvMock({ SSO_ENABLED: 'true' })) + +import { POST } from '@/app/api/auth/sso/register/route' + +const OIDC_BODY = { + providerType: 'oidc' as const, + providerId: 'acme-oidc', + issuer: 'https://idp.acme.com', + domain: 'acme.com', + clientId: 'client-id', + clientSecret: 'client-secret', + authorizationEndpoint: 'https://idp.acme.com/authorize', + tokenEndpoint: 'https://idp.acme.com/token', + userInfoEndpoint: 'https://idp.acme.com/userinfo', + jwksEndpoint: 'https://idp.acme.com/jwks', +} + +function request(body: Record) { + return createMockRequest('POST', body) +} + +describe('POST /api/auth/sso/register', () => { + beforeEach(() => { + vi.clearAllMocks() + dbState.members = [] + dbState.providers = [] + mockGetSession.mockResolvedValue({ user: { id: 'u1' } }) + mockHasSSOAccess.mockResolvedValue(true) + mockValidateUrlWithDNS.mockResolvedValue({ isValid: true, resolvedIP: '1.2.3.4' }) + mockRegisterSSOProvider.mockResolvedValue({ providerId: 'acme-oidc' }) + }) + + it('rejects callers without an Enterprise plan', async () => { + mockHasSSOAccess.mockResolvedValue(false) + const res = await POST(request({ ...OIDC_BODY, orgId: 'org1' })) + expect(res.status).toBe(403) + expect(mockRegisterSSOProvider).not.toHaveBeenCalled() + }) + + it('rejects callers who are not an admin/owner of the target org', async () => { + dbState.members = [{ organizationId: 'org1', role: 'member' }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org1' })) + expect(res.status).toBe(403) + expect(mockRegisterSSOProvider).not.toHaveBeenCalled() + }) + + it('rejects an invalid domain', async () => { + dbState.members = [{ organizationId: 'org1', role: 'owner' }] + const res = await POST(request({ ...OIDC_BODY, domain: 'not-a-domain', orgId: 'org1' })) + expect(res.status).toBe(400) + expect(mockRegisterSSOProvider).not.toHaveBeenCalled() + }) + + it('rejects a domain already registered by another organization', async () => { + dbState.members = [{ organizationId: 'org-attacker', role: 'owner' }] + dbState.providers = [{ domain: 'acme.com', userId: 'u-victim', organizationId: 'org-victim' }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org-attacker' })) + const json = await res.json() + expect(res.status).toBe(409) + expect(json.code).toBe('SSO_DOMAIN_ALREADY_REGISTERED') + expect(mockRegisterSSOProvider).not.toHaveBeenCalled() + }) + + it('matches conflicts across casing variants', async () => { + dbState.members = [{ organizationId: 'org-attacker', role: 'owner' }] + dbState.providers = [{ domain: 'ACME.com', userId: 'u-victim', organizationId: 'org-victim' }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org-attacker' })) + expect(res.status).toBe(409) + expect(mockRegisterSSOProvider).not.toHaveBeenCalled() + }) + + it('registers when the domain is unclaimed', async () => { + dbState.members = [{ organizationId: 'org1', role: 'owner' }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org1' })) + expect(res.status).toBe(200) + expect(mockRegisterSSOProvider).toHaveBeenCalledTimes(1) + }) + + it('allows the owning tenant to update its own provider for the same domain', async () => { + dbState.members = [{ organizationId: 'org1', role: 'owner' }] + dbState.providers = [{ domain: 'acme.com', userId: 'u1', organizationId: 'org1' }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org1' })) + expect(res.status).toBe(200) + expect(mockRegisterSSOProvider).toHaveBeenCalledTimes(1) + }) + + it('lets an org admin adopt their own user-scoped provider for the same domain', async () => { + dbState.members = [{ organizationId: 'org1', role: 'owner' }] + dbState.providers = [{ domain: 'acme.com', userId: 'u1', organizationId: null }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org1' })) + expect(res.status).toBe(200) + expect(mockRegisterSSOProvider).toHaveBeenCalledTimes(1) + }) + + it("still blocks an org admin from claiming another user's user-scoped domain", async () => { + dbState.members = [{ organizationId: 'org1', role: 'owner' }] + dbState.providers = [{ domain: 'acme.com', userId: 'someone-else', organizationId: null }] + const res = await POST(request({ ...OIDC_BODY, orgId: 'org1' })) + expect(res.status).toBe(409) + expect(mockRegisterSSOProvider).not.toHaveBeenCalled() + }) + + it('normalizes the domain before persisting it', async () => { + dbState.members = [{ organizationId: 'org1', role: 'owner' }] + const res = await POST(request({ ...OIDC_BODY, domain: 'ACME.com', orgId: 'org1' })) + expect(res.status).toBe(200) + expect(mockRegisterSSOProvider).toHaveBeenCalledTimes(1) + const config = mockRegisterSSOProvider.mock.calls[0][0].body + expect(config.domain).toBe('acme.com') + }) +}) diff --git a/apps/sim/app/api/auth/sso/register/route.ts b/apps/sim/app/api/auth/sso/register/route.ts index 5b285a5bd28..d5649607044 100644 --- a/apps/sim/app/api/auth/sso/register/route.ts +++ b/apps/sim/app/api/auth/sso/register/route.ts @@ -1,11 +1,12 @@ import { db, member, ssoProvider } from '@sim/db' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' -import { and, eq } from 'drizzle-orm' +import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { ssoRegistrationContract } from '@/lib/api/contracts/auth' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { auth, getSession } from '@/lib/auth' +import { normalizeSSODomain } from '@/lib/auth/sso/domain' import { hasSSOAccess } from '@/lib/billing' import { env } from '@/lib/core/config/env' import { @@ -51,7 +52,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (!parsed.success) return parsed.response const body = parsed.data.body - const { providerId, issuer, domain, providerType, mapping, orgId } = body + const { providerId, issuer, providerType, mapping, orgId } = body if (orgId) { const [membership] = await db @@ -67,6 +68,48 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } + const domain = normalizeSSODomain(body.domain) + if (!domain) { + return NextResponse.json({ error: 'Enter a valid domain like company.com' }, { status: 400 }) + } + + const isOwnedByCaller = (provider: { + userId: string | null + organizationId: string | null + }): boolean => { + if (provider.userId === session.user.id && !provider.organizationId) return true + return orgId ? provider.organizationId === orgId : false + } + + const findDomainConflict = async () => + ( + await db + .select({ + userId: ssoProvider.userId, + organizationId: ssoProvider.organizationId, + }) + .from(ssoProvider) + .where(sql`lower(${ssoProvider.domain}) = ${domain}`) + ).find((provider) => !isOwnedByCaller(provider)) + + const domainConflictResponse = () => + NextResponse.json( + { + error: 'This domain is already registered for SSO by another organization.', + code: 'SSO_DOMAIN_ALREADY_REGISTERED', + }, + { status: 409 } + ) + + if (await findDomainConflict()) { + logger.warn('Rejected SSO registration for domain owned by another tenant', { + domain, + orgId, + userId: session.user.id, + }) + return domainConflictResponse() + } + const headers: Record = {} request.headers.forEach((value, key) => { headers[key] = value @@ -408,6 +451,15 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ), }) + if (await findDomainConflict()) { + logger.warn('Rejected SSO registration: domain was claimed during registration', { + domain, + orgId, + userId: session.user.id, + }) + return domainConflictResponse() + } + const registration = await auth.api.registerSSOProvider({ body: providerConfig, headers, diff --git a/apps/sim/app/api/billing/route.ts b/apps/sim/app/api/billing/route.ts index 1b9411647e8..2cc338abd48 100644 --- a/apps/sim/app/api/billing/route.ts +++ b/apps/sim/app/api/billing/route.ts @@ -140,7 +140,6 @@ export const GET = withRouteHandler(async (request: NextRequest) => { members: rawBillingData.members.map((m) => ({ ...m, joinedAt: m.joinedAt.toISOString(), - lastActive: m.lastActive?.toISOString() || null, })), } diff --git a/apps/sim/app/api/copilot/chat/stop/route.test.ts b/apps/sim/app/api/copilot/chat/stop/route.test.ts index bab5465507d..452131f21e1 100644 --- a/apps/sim/app/api/copilot/chat/stop/route.test.ts +++ b/apps/sim/app/api/copilot/chat/stop/route.test.ts @@ -1,79 +1,19 @@ /** * @vitest-environment node */ -import { authMockFns } from '@sim/testing' +import { authMockFns, dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { - mockSelect, - mockFrom, - mockWhereSelect, - mockLimit, - mockForUpdate, - mockUpdate, - mockSet, - mockWhereUpdate, - mockReturning, - mockPublishStatusChanged, - mockSql, - mockTransaction, -} = vi.hoisted(() => { - const mockSelect = vi.fn() - const mockFrom = vi.fn() - const mockWhereSelect = vi.fn() - const mockLimit = vi.fn() - const mockForUpdate = vi.fn() - const mockUpdate = vi.fn() - const mockSet = vi.fn() - const mockWhereUpdate = vi.fn() - const mockReturning = vi.fn() - const mockPublishStatusChanged = vi.fn() - const mockSql = vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({ - strings, - values, - })) - const mockTransaction = vi.fn( - (callback: (tx: { select: typeof mockSelect; update: typeof mockUpdate }) => unknown) => - callback({ select: mockSelect, update: mockUpdate }) - ) - - return { - mockSelect, - mockFrom, - mockWhereSelect, - mockLimit, - mockForUpdate, - mockUpdate, - mockSet, - mockWhereUpdate, - mockReturning, - mockPublishStatusChanged, - mockSql, - mockTransaction, - } -}) +vi.mock('@sim/db', () => dbChainMock) -vi.mock('@sim/db/schema', () => ({ - copilotChats: { - id: 'copilotChats.id', - userId: 'copilotChats.userId', - workspaceId: 'copilotChats.workspaceId', - messages: 'copilotChats.messages', - conversationId: 'copilotChats.conversationId', - }, -})) - -vi.mock('@sim/db', () => ({ - db: { - transaction: mockTransaction, - }, +const { mockAppendCopilotChatMessages, mockPublishStatusChanged } = vi.hoisted(() => ({ + mockAppendCopilotChatMessages: vi.fn(), + mockPublishStatusChanged: vi.fn(), })) -vi.mock('drizzle-orm', () => ({ - and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), - eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), - sql: mockSql, +vi.mock('@/lib/copilot/chat/messages-store', () => ({ + appendCopilotChatMessages: mockAppendCopilotChatMessages, })) vi.mock('@/lib/copilot/tasks', () => ({ @@ -92,39 +32,33 @@ function createRequest(body: Record) { }) } +/** + * Sequence the two in-tx reads `finalizeAssistantTurn` issues: the chat row + * (`FOR UPDATE ... LIMIT 1`) and the last-message lookup that drives dedup + * (both terminate on `.limit(1)`). + */ +function mockReads(opts: { + chat: Record | null + last?: { messageId: string; role: string } +}) { + dbChainMockFns.limit.mockResolvedValueOnce(opts.chat ? [opts.chat] : []) + dbChainMockFns.limit.mockResolvedValueOnce(opts.last ? [opts.last] : []) +} + describe('copilot chat stop route', () => { beforeEach(() => { vi.clearAllMocks() - + // Drain the once-queue (clearAllMocks/resetDbChainMock don't), then restore defaults. + dbChainMockFns.limit.mockReset() + resetDbChainMock() authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) - - mockLimit.mockResolvedValue([ - { - workspaceId: 'ws-1', - messages: [{ id: 'stream-1', role: 'user', content: 'hello' }], - conversationId: 'stream-1', - }, - ]) - mockForUpdate.mockReturnValue({ limit: mockLimit }) - mockWhereSelect.mockReturnValue({ for: mockForUpdate }) - mockFrom.mockReturnValue({ where: mockWhereSelect }) - mockSelect.mockReturnValue({ from: mockFrom }) - - mockReturning.mockResolvedValue([{ workspaceId: 'ws-1' }]) - mockWhereUpdate.mockReturnValue({ returning: mockReturning }) - mockSet.mockReturnValue({ where: mockWhereUpdate }) - mockUpdate.mockReturnValue({ set: mockSet }) }) it('returns 401 when unauthenticated', async () => { authMockFns.mockGetSession.mockResolvedValueOnce(null) const response = await POST( - createRequest({ - chatId: 'chat-1', - streamId: 'stream-1', - content: '', - }) + createRequest({ chatId: 'chat-1', streamId: 'stream-1', content: '' }) ) expect(response.status).toBe(401) @@ -132,41 +66,37 @@ describe('copilot chat stop route', () => { }) it('is a no-op when the chat is missing', async () => { - mockLimit.mockResolvedValueOnce([]) + mockReads({ chat: null }) const response = await POST( - createRequest({ - chatId: 'missing-chat', - streamId: 'stream-1', - content: '', - }) + createRequest({ chatId: 'missing-chat', streamId: 'stream-1', content: '' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ success: true }) - expect(mockUpdate).not.toHaveBeenCalled() + expect(mockAppendCopilotChatMessages).not.toHaveBeenCalled() }) it('appends a stopped assistant message even with no content', async () => { + mockReads({ + chat: { workspaceId: 'ws-1', conversationId: 'stream-1', model: null }, + last: { messageId: 'stream-1', role: 'user' }, + }) + const response = await POST( - createRequest({ - chatId: 'chat-1', - streamId: 'stream-1', - content: '', - }) + createRequest({ chatId: 'chat-1', streamId: 'stream-1', content: '' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ success: true }) - const setArg = mockSet.mock.calls[0]?.[0] - expect(setArg).toBeTruthy() + const setArg = dbChainMockFns.set.mock.calls[0]?.[0] as Record expect(setArg.conversationId).toBeNull() - expect(setArg.messages).toBeTruthy() + expect(Object.hasOwn(setArg, 'messages')).toBe(false) - const appendedPayload = JSON.parse(setArg.messages.values[1] as string) - expect(appendedPayload).toHaveLength(1) - expect(appendedPayload[0]).toMatchObject({ + expect(mockAppendCopilotChatMessages).toHaveBeenCalledTimes(1) + const [, appended] = mockAppendCopilotChatMessages.mock.calls[0] + expect(appended[0]).toMatchObject({ role: 'assistant', content: '', contentBlocks: [{ type: 'complete', status: 'cancelled' }], @@ -181,32 +111,21 @@ describe('copilot chat stop route', () => { }) it('appends a stopped assistant message if the stream marker was already cleared', async () => { - mockLimit.mockResolvedValueOnce([ - { - workspaceId: 'ws-1', - messages: [{ id: 'stream-1', role: 'user', content: 'hello' }], - conversationId: null, - }, - ]) + mockReads({ + chat: { workspaceId: 'ws-1', conversationId: null, model: null }, + last: { messageId: 'stream-1', role: 'user' }, + }) const response = await POST( - createRequest({ - chatId: 'chat-1', - streamId: 'stream-1', - content: 'partial', - }) + createRequest({ chatId: 'chat-1', streamId: 'stream-1', content: 'partial' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ success: true }) - const setArg = mockSet.mock.calls[0]?.[0] - expect(setArg.messages).toBeTruthy() - const appendedPayload = JSON.parse(setArg.messages.values[1] as string) - expect(appendedPayload[0]).toMatchObject({ - role: 'assistant', - content: 'partial', - }) + expect(mockAppendCopilotChatMessages).toHaveBeenCalledTimes(1) + const [, appended] = mockAppendCopilotChatMessages.mock.calls[0] + expect(appended[0]).toMatchObject({ role: 'assistant', content: 'partial' }) expect(mockPublishStatusChanged).toHaveBeenCalledWith({ workspaceId: 'ws-1', @@ -217,28 +136,19 @@ describe('copilot chat stop route', () => { }) it('republishes completed status when the assistant was already persisted', async () => { - mockLimit.mockResolvedValueOnce([ - { - workspaceId: 'ws-1', - messages: [ - { id: 'stream-1', role: 'user', content: 'hello' }, - { id: 'assistant-1', role: 'assistant', content: 'partial' }, - ], - conversationId: null, - }, - ]) + mockReads({ + chat: { workspaceId: 'ws-1', conversationId: null, model: null }, + last: { messageId: 'assistant-1', role: 'assistant' }, + }) const response = await POST( - createRequest({ - chatId: 'chat-1', - streamId: 'stream-1', - content: 'partial', - }) + createRequest({ chatId: 'chat-1', streamId: 'stream-1', content: 'partial' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ success: true }) - expect(mockUpdate).not.toHaveBeenCalled() + expect(mockAppendCopilotChatMessages).not.toHaveBeenCalled() + expect(dbChainMockFns.set).not.toHaveBeenCalled() expect(mockPublishStatusChanged).toHaveBeenCalledWith({ workspaceId: 'ws-1', chatId: 'chat-1', diff --git a/apps/sim/app/api/copilot/chat/update-messages/route.test.ts b/apps/sim/app/api/copilot/chat/update-messages/route.test.ts index c56415116ab..2e7dfa134c8 100644 --- a/apps/sim/app/api/copilot/chat/update-messages/route.test.ts +++ b/apps/sim/app/api/copilot/chat/update-messages/route.test.ts @@ -16,6 +16,7 @@ const { mockSet, mockUpdateWhere, mockReturning, + mockReplaceCopilotChatMessages, } = vi.hoisted(() => ({ mockSelect: vi.fn(), mockFrom: vi.fn(), @@ -25,15 +26,23 @@ const { mockSet: vi.fn(), mockUpdateWhere: vi.fn(), mockReturning: vi.fn(), + mockReplaceCopilotChatMessages: vi.fn(), })) vi.mock('@sim/db', () => ({ db: { select: mockSelect, update: mockUpdate, + transaction: async ( + cb: (tx: { update: typeof mockUpdate; select: typeof mockSelect }) => unknown + ) => cb({ update: mockUpdate, select: mockSelect }), }, })) +vi.mock('@/lib/copilot/chat/messages-store', () => ({ + replaceCopilotChatMessages: mockReplaceCopilotChatMessages, +})) + vi.mock('drizzle-orm', () => ({ and: vi.fn((...conditions: unknown[]) => ({ conditions, type: 'and' })), eq: vi.fn((field: unknown, value: unknown) => ({ field, value, type: 'eq' })), @@ -257,10 +266,13 @@ describe('Copilot Chat Update Messages API Route', () => { expect(mockSelect).toHaveBeenCalled() expect(mockUpdate).toHaveBeenCalled() - expect(mockSet).toHaveBeenCalledWith({ + expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) }) + expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith( + 'chat-123', messages, - updatedAt: expect.any(Date), - }) + { chatModel: 'gpt-4' }, + expect.anything() + ) }) it('should successfully update chat messages with optional fields', async () => { @@ -315,8 +327,10 @@ describe('Copilot Chat Update Messages API Route', () => { messageCount: 2, }) - expect(mockSet).toHaveBeenCalledWith({ - messages: [ + expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) }) + expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith( + 'chat-456', + [ { id: 'msg-1', role: 'user', @@ -345,8 +359,9 @@ describe('Copilot Chat Update Messages API Route', () => { ], }, ], - updatedAt: expect.any(Date), - }) + { chatModel: 'gpt-4' }, + expect.anything() + ) }) it('should handle empty messages array', async () => { @@ -373,10 +388,13 @@ describe('Copilot Chat Update Messages API Route', () => { messageCount: 0, }) - expect(mockSet).toHaveBeenCalledWith({ - messages: [], - updatedAt: expect.any(Date), - }) + expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) }) + expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith( + 'chat-789', + [], + { chatModel: 'gpt-4' }, + expect.anything() + ) }) it('should handle database errors during chat lookup', async () => { @@ -485,10 +503,13 @@ describe('Copilot Chat Update Messages API Route', () => { messageCount: 100, }) - expect(mockSet).toHaveBeenCalledWith({ + expect(mockSet).toHaveBeenCalledWith({ updatedAt: expect.any(Date) }) + expect(mockReplaceCopilotChatMessages).toHaveBeenCalledWith( + 'chat-large', messages, - updatedAt: expect.any(Date), - }) + { chatModel: 'gpt-4' }, + expect.anything() + ) }) it('should handle messages with both user and assistant roles', async () => { diff --git a/apps/sim/app/api/copilot/chat/update-messages/route.ts b/apps/sim/app/api/copilot/chat/update-messages/route.ts index 1b654c4930a..7c7792e3f2c 100644 --- a/apps/sim/app/api/copilot/chat/update-messages/route.ts +++ b/apps/sim/app/api/copilot/chat/update-messages/route.ts @@ -6,7 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { updateCopilotMessagesContract } from '@/lib/api/contracts/copilot' import { parseRequest } from '@/lib/api/server' import { getAccessibleCopilotChatAuth } from '@/lib/copilot/chat/lifecycle' -import { replaceCopilotChatMessages } from '@/lib/copilot/chat/messages-dual-write' +import { replaceCopilotChatMessages } from '@/lib/copilot/chat/messages-store' import { normalizeMessage, type PersistedMessage } from '@/lib/copilot/chat/persisted-message' import { authenticateCopilotRequestSessionOnly, @@ -73,9 +73,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { return createNotFoundResponse('Chat not found or unauthorized') } - // Update chat with new messages, plan artifact, and config const updateData: Record = { - messages: normalizedMessages, updatedAt: new Date(), } @@ -87,16 +85,20 @@ export const POST = withRouteHandler(async (req: NextRequest) => { updateData.config = config } - const [updated] = await db - .update(copilotChats) - .set(updateData) - .where(eq(copilotChats.id, chatId)) - .returning({ model: copilotChats.model }) - if (updated) { - await replaceCopilotChatMessages(chatId, normalizedMessages, { - chatModel: updated.model ?? null, - }) - } + await db.transaction(async (tx) => { + const [updated] = await tx + .update(copilotChats) + .set(updateData) + .where(eq(copilotChats.id, chatId)) + .returning({ model: copilotChats.model }) + if (!updated) return + await replaceCopilotChatMessages( + chatId, + normalizedMessages, + { chatModel: updated.model ?? null }, + tx + ) + }) logger.info(`[${tracker.requestId}] Successfully updated chat`, { chatId, diff --git a/apps/sim/app/api/files/parse/route.test.ts b/apps/sim/app/api/files/parse/route.test.ts index b2ab510c9f8..ccf9dd684ab 100644 --- a/apps/sim/app/api/files/parse/route.test.ts +++ b/apps/sim/app/api/files/parse/route.test.ts @@ -796,6 +796,68 @@ describe('Files Parse API - Path Traversal Security', () => { } }) + it('should not treat .. inside external URLs as path traversal', async () => { + inputValidationMockFns.mockValidateUrlWithDNS.mockResolvedValue({ + isValid: true, + resolvedIP: '203.0.113.10', + }) + inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValue( + new Response('slack file content', { + status: 200, + headers: { 'content-type': 'text/plain' }, + }) + ) + permissionsMockFns.mockGetUserEntityPermissions.mockResolvedValue('write') + + // Slack truncates long titles with a literal ellipsis, so the slug contains `..` + const slackUrl = + 'https://files.slack.com/files-pri/T08-F0B/_other__no_invitation_messages_get_sent_-_sim_on_railway...txt' + + const request = new NextRequest('http://localhost:3000/api/files/parse', { + method: 'POST', + body: JSON.stringify({ filePath: slackUrl, workspaceId: 'workspace-id' }), + }) + + const response = await POST(request) + const result = await response.json() + + expect(result.success).toBe(true) + // The URL reaching the pinned fetch proves it passed validation and routed + // to external-URL handling rather than being rejected as a local path. + expect(inputValidationMockFns.mockSecureFetchWithPinnedIP).toHaveBeenCalledWith( + slackUrl, + '203.0.113.10', + expect.any(Object) + ) + }) + + it('should still reject traversal in https URLs that look like internal serve URLs', async () => { + inputValidationMockFns.mockValidateUrlWithDNS.mockResolvedValue({ + isValid: true, + resolvedIP: '203.0.113.10', + }) + inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValue( + new Response('should never be fetched', { status: 200 }) + ) + + // Absolute https URL containing `/api/files/serve/` matches isInternalFileUrl and would + // route to handleCloudFile — so it must keep traversal protection, not be waved through + // as an external URL. + const request = new NextRequest('http://localhost:3000/api/files/parse', { + method: 'POST', + body: JSON.stringify({ + filePath: 'https://attacker.com/api/files/serve/../../../etc/passwd', + }), + }) + + const response = await POST(request) + const result = await response.json() + + expect(result.success).toBe(false) + expect(result.error).toMatch(/Access denied: path traversal detected/) + expect(inputValidationMockFns.mockSecureFetchWithPinnedIP).not.toHaveBeenCalled() + }) + it('should handle encoded path traversal attempts', async () => { const encodedMaliciousPaths = [ '/api/files/serve/%2e%2e%2f%2e%2e%2fetc%2fpasswd', // ../../../etc/passwd diff --git a/apps/sim/app/api/files/parse/route.ts b/apps/sim/app/api/files/parse/route.ts index ea4f493dd80..b925a366033 100644 --- a/apps/sim/app/api/files/parse/route.ts +++ b/apps/sim/app/api/files/parse/route.ts @@ -419,13 +419,33 @@ function assertParsedContentWithinLimit(content: string, maxBytes?: number): str } /** - * Validate file path for security - prevents null byte injection and path traversal attacks + * Validate file path for security - prevents null byte injection and path traversal attacks. + * + * External URLs (`http`/`https`) are fetched over HTTP — with SSRF protection applied + * downstream in `fetchExternalUrlToWorkspace` (DNS resolution + private/reserved IP blocking) + * — and are never resolved against the filesystem, so `..`/`~` are legal URL content and must + * not be rejected. Providers such as Slack routinely emit slugs containing a literal `...`. + * + * Internal file URLs (`/api/files/serve/...`) ARE resolved to storage keys and filesystem + * paths via `extractStorageKey`, so they keep full traversal protection. The external + * short-circuit explicitly excludes them: `parseFileSingle` routes anything matching + * `isInternalFileUrl` to `handleCloudFile` (even an absolute `https://host/api/files/serve/...`), + * so such inputs must stay subject to the `..`/`~` checks rather than being waved through as + * external URLs. Only the leading-`/` "outside allowed directory" check is relaxed for them, + * since that prefix is expected. */ function validateFilePath(filePath: string): { isValid: boolean; error?: string } { if (filePath.includes('\0')) { return { isValid: false, error: 'Invalid path: null byte detected' } } + if ( + (filePath.startsWith('http://') || filePath.startsWith('https://')) && + !isInternalFileUrl(filePath) + ) { + return { isValid: true } + } + if (filePath.includes('..')) { return { isValid: false, error: 'Access denied: path traversal detected' } } diff --git a/apps/sim/app/api/knowledge/utils.test.ts b/apps/sim/app/api/knowledge/utils.test.ts index 326bbee660f..b8297e4cf65 100644 --- a/apps/sim/app/api/knowledge/utils.test.ts +++ b/apps/sim/app/api/knowledge/utils.test.ts @@ -259,7 +259,12 @@ describe('Knowledge Utils', () => { {} ) - expect(dbOps.order).toEqual(['insert', 'updateDoc']) + // Embeddings are inserted first, then the document counter update. A + // usage_log billing insert (recordUsage) may trail after updateDoc and is + // irrelevant to this ordering invariant, so assert position rather than + // exact array equality. + expect(dbOps.order[0]).toBe('insert') + expect(dbOps.order.indexOf('updateDoc')).toBeGreaterThan(0) expect(dbOps.updatePayloads[0]).toMatchObject({ processingStatus: 'completed', diff --git a/apps/sim/app/api/logs/execution/[executionId]/route.ts b/apps/sim/app/api/logs/execution/[executionId]/route.ts index 71deb54267d..adab287bf99 100644 --- a/apps/sim/app/api/logs/execution/[executionId]/route.ts +++ b/apps/sim/app/api/logs/execution/[executionId]/route.ts @@ -13,6 +13,7 @@ import { executionIdParamsSchema } from '@/lib/api/contracts/logs' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { materializeExecutionData } from '@/lib/logs/execution/trace-store' import type { TraceSpan, WorkflowExecutionLog } from '@/lib/logs/types' const logger = createLogger('LogsByExecutionIdAPI') @@ -39,13 +40,14 @@ export const GET = withRouteHandler( .select({ id: workflowExecutionLogs.id, workflowId: workflowExecutionLogs.workflowId, + workspaceId: workflowExecutionLogs.workspaceId, executionId: workflowExecutionLogs.executionId, stateSnapshotId: workflowExecutionLogs.stateSnapshotId, trigger: workflowExecutionLogs.trigger, startedAt: workflowExecutionLogs.startedAt, endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, - cost: workflowExecutionLogs.cost, + costTotal: workflowExecutionLogs.costTotal, executionData: workflowExecutionLogs.executionData, }) .from(workflowExecutionLogs) @@ -119,7 +121,14 @@ export const GET = withRouteHandler( return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 }) } - const executionData = workflowLog.executionData as WorkflowExecutionLog['executionData'] + const executionData = (await materializeExecutionData( + workflowLog.executionData as Record | null, + { + workspaceId: workflowLog.workspaceId, + workflowId: workflowLog.workflowId, + executionId: workflowLog.executionId, + } + )) as WorkflowExecutionLog['executionData'] const traceSpans = (executionData?.traceSpans as TraceSpan[]) || [] const childSnapshotIds = new Set() const collectSnapshotIds = (spans: TraceSpan[]) => { @@ -163,7 +172,7 @@ export const GET = withRouteHandler( startedAt: workflowLog.startedAt.toISOString(), endedAt: workflowLog.endedAt?.toISOString(), totalDurationMs: workflowLog.totalDurationMs, - cost: workflowLog.cost || null, + cost: workflowLog.costTotal != null ? { total: Number(workflowLog.costTotal) } : null, }, } diff --git a/apps/sim/app/api/logs/export/route.ts b/apps/sim/app/api/logs/export/route.ts index 2c817411b68..766435eadd4 100644 --- a/apps/sim/app/api/logs/export/route.ts +++ b/apps/sim/app/api/logs/export/route.ts @@ -4,7 +4,9 @@ import { createLogger } from '@sim/logger' import { and, desc, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' +import { MATERIALIZE_CONCURRENCY, mapWithConcurrency } from '@/lib/core/utils/concurrency' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { materializeExecutionData } from '@/lib/logs/execution/trace-store' import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters' import { expandFolderIdsWithDescendants } from '@/lib/logs/folder-expansion' @@ -41,7 +43,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { startedAt: workflowExecutionLogs.startedAt, endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, - cost: workflowExecutionLogs.cost, + costTotal: workflowExecutionLogs.costTotal, executionData: workflowExecutionLogs.executionData, workflowName: sql`COALESCE(${workflow.name}, 'Deleted Workflow')`, } @@ -96,11 +98,29 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!rows.length) break - for (const r of rows as any[]) { + // Heavy execution data may live in object storage; materialize per + // row with bounded concurrency so a 1000-row page doesn't fan out + // into 1000 simultaneous reads. + const materialized = await mapWithConcurrency( + rows as any[], + MATERIALIZE_CONCURRENCY, + (r) => + materializeExecutionData(r.executionData as Record | null, { + workspaceId: params.workspaceId, + workflowId: r.workflowId, + executionId: r.executionId, + }) + ) + + for (let j = 0; j < rows.length; j++) { + const r = rows[j] as any + const ed = materialized[j] as Record + // A single malformed/unserializable row must not abort the whole CSV + // stream — derive the message/trace columns defensively and fall back + // to empty on error so the row's metadata still exports. let message = '' - let traces: any = null + let tracesJson = '' try { - const ed = (r as any).executionData if (ed) { if (ed.finalOutput) message = @@ -108,20 +128,25 @@ export const GET = withRouteHandler(async (request: NextRequest) => { ? ed.finalOutput : JSON.stringify(ed.finalOutput) if (ed.message) message = ed.message - if (ed.traceSpans) traces = ed.traceSpans + if (ed.traceSpans) tracesJson = JSON.stringify(ed.traceSpans) } - } catch {} + } catch (rowError) { + logger.warn('Skipping unserializable execution data for export row', { + executionId: r.executionId, + error: rowError instanceof Error ? rowError.message : String(rowError), + }) + } const line = [ escapeCsv(r.startedAt?.toISOString?.() || r.startedAt), escapeCsv(r.level), escapeCsv(r.workflowName), escapeCsv(r.trigger), escapeCsv(r.totalDurationMs ?? ''), - escapeCsv(r.cost?.total ?? r.cost?.value?.total ?? ''), + escapeCsv(r.costTotal ?? ''), escapeCsv(r.workflowId ?? ''), escapeCsv(r.executionId ?? ''), escapeCsv(message), - escapeCsv(traces ? JSON.stringify(traces) : ''), + escapeCsv(tracesJson), ].join(',') controller.enqueue(encoder.encode(`${line}\n`)) } diff --git a/apps/sim/app/api/logs/route.ts b/apps/sim/app/api/logs/route.ts index 89f52048b72..548383c1035 100644 --- a/apps/sim/app/api/logs/route.ts +++ b/apps/sim/app/api/logs/route.ts @@ -31,6 +31,7 @@ import { listLogsContract, type WorkflowLogSummary } from '@/lib/api/contracts/l import { parseRequest } from '@/lib/api/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { jobCostTotal } from '@/lib/logs/fetch-log-detail' import { buildFilterConditions } from '@/lib/logs/filters' import { expandFolderIdsWithDescendants } from '@/lib/logs/folder-expansion' @@ -81,7 +82,8 @@ export const GET = withRouteHandler(async (request: NextRequest) => { case 'duration': return sql`${workflowExecutionLogs.totalDurationMs}` case 'cost': - return sql`(${workflowExecutionLogs.cost}->>'total')::numeric` + // Indexed projection of the usage_log ledger (dollars); no live aggregation. + return sql`${workflowExecutionLogs.costTotal}` case 'status': return sql`${workflowExecutionLogs.status}` default: @@ -201,7 +203,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { startedAt: workflowExecutionLogs.startedAt, endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, - cost: workflowExecutionLogs.cost, + costTotal: workflowExecutionLogs.costTotal, createdAt: workflowExecutionLogs.createdAt, workflowName: workflow.name, workflowDescription: workflow.description, @@ -379,7 +381,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } : null, jobTitle: null, - cost: (log.cost as WorkflowLogSummary['cost']) ?? null, + // List cost is the cost_total projection (faithful ledger sum). Null until + // completion (running) or until the one-time legacy backfill populates it. + cost: log.costTotal != null ? { total: Number(log.costTotal) } : null, pauseSummary: { status: log.pausedStatus ?? null, total: totalPauseCount, @@ -405,7 +409,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { createdAt: log.startedAt.toISOString(), workflow: null, jobTitle: log.jobTitle ?? null, - cost: (log.cost as WorkflowLogSummary['cost']) ?? null, + cost: jobCostTotal(log.cost), pauseSummary: { status: null, total: 0, resumed: 0 }, hasPendingPause: false, } diff --git a/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts b/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts index 3f2976ff027..c54b72dfcb8 100644 --- a/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts +++ b/apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts @@ -1,12 +1,4 @@ -import type { NextRequest, NextResponse } from 'next/server' -import { mcpOauthAuthorizationServerMetadataContract } from '@/lib/api/contracts/mcp-oauth' -import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { createMcpAuthorizationServerMetadataResponse } from '@/lib/mcp/oauth-discovery' +import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated' -export const GET = withRouteHandler(async (request: NextRequest): Promise => { - const parsed = await parseRequest(mcpOauthAuthorizationServerMetadataContract, request, {}) - if (!parsed.success) return parsed.response as NextResponse - - return createMcpAuthorizationServerMetadataResponse() -}) +export const GET = withRouteHandler(async () => copilotMcpDeprecatedResponse()) diff --git a/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts b/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts index 1e17b126b31..c54b72dfcb8 100644 --- a/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts +++ b/apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts @@ -1,12 +1,4 @@ -import type { NextRequest, NextResponse } from 'next/server' -import { mcpOauthProtectedResourceMetadataContract } from '@/lib/api/contracts/mcp-oauth' -import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { createMcpProtectedResourceMetadataResponse } from '@/lib/mcp/oauth-discovery' +import { copilotMcpDeprecatedResponse } from '@/lib/mcp/copilot-deprecated' -export const GET = withRouteHandler(async (request: NextRequest): Promise => { - const parsed = await parseRequest(mcpOauthProtectedResourceMetadataContract, request, {}) - if (!parsed.success) return parsed.response as NextResponse - - return createMcpProtectedResourceMetadataResponse() -}) +export const GET = withRouteHandler(async () => copilotMcpDeprecatedResponse()) diff --git a/apps/sim/app/api/mcp/copilot/route.test.ts b/apps/sim/app/api/mcp/copilot/route.test.ts new file mode 100644 index 00000000000..7db9860fbce --- /dev/null +++ b/apps/sim/app/api/mcp/copilot/route.test.ts @@ -0,0 +1,62 @@ +/** + * Tests for the deprecated Copilot MCP route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { describe, expect, it } from 'vitest' +import { GET as authServerDiscoveryGET } from '@/app/api/mcp/copilot/.well-known/oauth-authorization-server/route' +import { GET as protectedResourceDiscoveryGET } from '@/app/api/mcp/copilot/.well-known/oauth-protected-resource/route' +import { DELETE, GET, POST } from '@/app/api/mcp/copilot/route' + +const URL = 'http://localhost:3000/api/mcp/copilot' + +describe('Deprecated Copilot MCP route', () => { + it('GET returns 410', async () => { + const response = await GET(new NextRequest(URL)) + expect(response.status).toBe(410) + }) + + it('POST returns 410 with a JSON-RPC error envelope', async () => { + const request = new NextRequest(URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize' }), + }) + const response = await POST(request) + expect(response.status).toBe(410) + + const body = (await response.json()) as { jsonrpc?: string; error?: { message?: string } } + expect(body.jsonrpc).toBe('2.0') + expect(body.error?.message).toContain('deprecated') + }) + + it('POST still returns 410 when an x-api-key header is present', async () => { + const request = new NextRequest(URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'sk-sim-copilot-test' }, + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }), + }) + const response = await POST(request) + expect(response.status).toBe(410) + }) + + it('DELETE returns 410', async () => { + const response = await DELETE(new NextRequest(URL, { method: 'DELETE' })) + expect(response.status).toBe(410) + }) + + it('copilot OAuth authorization-server discovery returns 410', async () => { + const response = await authServerDiscoveryGET( + new NextRequest(`${URL}/.well-known/oauth-authorization-server`) + ) + expect(response.status).toBe(410) + }) + + it('copilot OAuth protected-resource discovery returns 410', async () => { + const response = await protectedResourceDiscoveryGET( + new NextRequest(`${URL}/.well-known/oauth-protected-resource`) + ) + expect(response.status).toBe(410) + }) +}) diff --git a/apps/sim/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts index 2bda0f3670f..6ec1475d2ef 100644 --- a/apps/sim/app/api/mcp/copilot/route.ts +++ b/apps/sim/app/api/mcp/copilot/route.ts @@ -1,740 +1,13 @@ -import { Server } from '@modelcontextprotocol/sdk/server/index.js' -import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js' -import { - CallToolRequestSchema, - type CallToolResult, - ErrorCode, - type JSONRPCError, - ListToolsRequestSchema, - type ListToolsResult, - McpError, - type RequestId, -} from '@modelcontextprotocol/sdk/types.js' -import { createLogger } from '@sim/logger' -import { toError } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' -import { authorizeWorkflowByWorkspacePermission } from '@sim/workflow-authz' -import { type NextRequest, NextResponse } from 'next/server' -import { mcpRequestBodySchema, mcpToolCallParamsSchema } from '@/lib/api/contracts/mcp' -import { validateOAuthAccessToken } from '@/lib/auth/oauth-token' -import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' -import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context' -import { ORCHESTRATION_TIMEOUT_MS, SIM_AGENT_API_URL } from '@/lib/copilot/constants' -import { createRequestId } from '@/lib/copilot/request/http' -import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless' -import { orchestrateSubagentStream } from '@/lib/copilot/request/subagent' -import { ensureHandlersRegistered, executeTool } from '@/lib/copilot/tool-executor' -import { ensureWorkspaceAccess } from '@/lib/copilot/tools/handlers/access' -import { prepareExecutionContext } from '@/lib/copilot/tools/handlers/context' -import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/definitions' -import { env } from '@/lib/core/config/env' -import { RateLimiter } from '@/lib/core/rate-limiter' -import { getBaseUrl } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { resolveWorkflowIdForUser } from '@/lib/workflows/utils' - -const logger = createLogger('CopilotMcpAPI') -const mcpRateLimiter = new RateLimiter() -const DEFAULT_COPILOT_MODEL = 'claude-opus-4-6' +import { + copilotMcpDeprecatedJsonRpcResponse, + copilotMcpDeprecatedResponse, +} from '@/lib/mcp/copilot-deprecated' export const dynamic = 'force-dynamic' -export const runtime = 'nodejs' -export const maxDuration = 3600 - -interface CopilotKeyAuthResult { - success: boolean - userId?: string - error?: string -} - -/** - * Validates a copilot API key by forwarding it to the Go copilot service's - * `/api/validate-key` endpoint. Returns the associated userId on success. - */ -async function authenticateCopilotApiKey(apiKey: string): Promise { - try { - const internalSecret = env.INTERNAL_API_SECRET - if (!internalSecret) { - logger.error('INTERNAL_API_SECRET not configured') - return { success: false, error: 'Server configuration error' } - } - - const { fetchGo } = await import('@/lib/copilot/request/go/fetch') - const res = await fetchGo(`${SIM_AGENT_API_URL}/api/validate-key`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': internalSecret, - }, - body: JSON.stringify({ targetApiKey: apiKey }), - signal: AbortSignal.timeout(10_000), - spanName: 'sim → go /api/validate-key (mcp)', - operation: 'mcp_validate_key', - }) - - if (!res.ok) { - const body = await res.json().catch(() => null) - const upstream = (body as Record)?.message - const status = res.status - - if (status === 401 || status === 403) { - return { - success: false, - error: `Invalid Copilot API key. Generate a new key in Settings → Copilot and set it in the x-api-key header.`, - } - } - if (status === 402) { - return { - success: false, - error: `Usage limit exceeded for this Copilot API key. Upgrade your plan or wait for your quota to reset.`, - } - } - - return { - success: false, - error: String(upstream ?? 'Copilot API key validation failed'), - } - } - - const data = (await res.json()) as { ok?: boolean; userId?: string } - if (!data.ok || !data.userId) { - return { - success: false, - error: 'Invalid Copilot API key. Generate a new key in Settings → Copilot.', - } - } - - return { success: true, userId: data.userId } - } catch (error) { - logger.error('Copilot API key validation failed', { error }) - return { - success: false, - error: - 'Could not validate Copilot API key — the authentication service is temporarily unreachable. This is NOT a problem with the API key itself; please retry shortly.', - } - } -} - -/** - * MCP Server instructions that guide LLMs on how to use the Sim copilot tools. - * This is included in the initialize response to help external LLMs understand - * the workflow lifecycle and best practices. - */ -const MCP_SERVER_INSTRUCTIONS = ` -## Sim Workflow Copilot - -Sim is a workflow automation platform. Workflows are visual pipelines of connected blocks (Agent, Function, Condition, API, integrations, etc.). The Agent block is the core — an LLM with tools, memory, structured output, and knowledge bases. - -### Workflow Lifecycle (Happy Path) - -1. \`list_workspaces\` → know where to work -2. \`create_workflow(name, workspaceId)\` → get a workflowId -3. \`sim_workflow(request, workflowId)\` → plan and build in one pass -4. \`sim_test(request, workflowId)\` → verify it works -5. \`sim_deploy("deploy as api", workflowId)\` → make it accessible externally (optional) - -### Working with Existing Workflows - -When the user refers to a workflow by name or description ("the email one", "my Slack bot"): -1. Use \`sim_discovery\` to find it by functionality -2. Or use \`list_workflows\` and match by name -3. Then pass the workflowId to other tools - -### Organization - -- \`rename_workflow\` — rename a workflow -- \`move_workflow\` — move a workflow into a folder (or back to root by clearing the folder id) -- \`move_folder\` — nest a folder inside another (or move it back to root by clearing the parent id) -- \`create_folder(name, parentId)\` — create nested folder hierarchies - -### Key Rules - -- You can test workflows immediately after building — deployment is only needed for external access (API, chat, MCP). -- Tools that operate on a specific workflow such as \`sim_workflow\`, \`sim_test\`, \`sim_deploy\`, and workflow-scoped \`sim_info\` requests require \`workflowId\`. -- If the user reports errors, route through \`sim_workflow\` and ask it to reproduce, inspect logs, and fix the issue end to end. -- Variable syntax: \`\` for block outputs, \`{{ENV_VAR}}\` for env vars. -` - -type HeaderMap = Record - -function createError(id: RequestId, code: ErrorCode | number, message: string): JSONRPCError { - return { - jsonrpc: '2.0', - id, - error: { code, message }, - } -} - -function readHeader(headers: HeaderMap | undefined, name: string): string | undefined { - if (!headers) return undefined - const value = headers[name.toLowerCase()] - if (Array.isArray(value)) { - return value[0] - } - return value -} - -function buildMcpServer(abortSignal?: AbortSignal): Server { - const server = new Server( - { - name: 'sim-copilot', - version: '1.0.0', - }, - { - capabilities: { tools: {} }, - instructions: MCP_SERVER_INSTRUCTIONS, - } - ) - - server.setRequestHandler(ListToolsRequestSchema, async () => { - const directTools = DIRECT_TOOL_DEFS.map((tool) => ({ - name: tool.name, - description: tool.description, - inputSchema: tool.inputSchema, - ...(tool.annotations && { annotations: tool.annotations }), - })) - - const subagentTools = SUBAGENT_TOOL_DEFS.map((tool) => ({ - name: tool.name, - description: tool.description, - inputSchema: tool.inputSchema, - ...(tool.annotations && { annotations: tool.annotations }), - })) - - const result: ListToolsResult = { - tools: [...directTools, ...subagentTools], - } - - return result - }) - - server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { - const headers = (extra.requestInfo?.headers || {}) as HeaderMap - const apiKeyHeader = readHeader(headers, 'x-api-key') - const authorizationHeader = readHeader(headers, 'authorization') - - let authResult: CopilotKeyAuthResult = { success: false } - - if (authorizationHeader?.startsWith('Bearer ')) { - const token = authorizationHeader.slice(7) - const oauthResult = await validateOAuthAccessToken(token) - if (oauthResult.success && oauthResult.userId) { - if (!oauthResult.scopes?.includes('mcp:tools')) { - return { - content: [ - { - type: 'text' as const, - text: 'AUTHENTICATION ERROR: OAuth token is missing the required "mcp:tools" scope. Re-authorize with the correct scopes.', - }, - ], - isError: true, - } - } - authResult = { success: true, userId: oauthResult.userId } - } else { - return { - content: [ - { - type: 'text' as const, - text: `AUTHENTICATION ERROR: ${oauthResult.error ?? 'Invalid OAuth access token'} Do NOT retry — re-authorize via OAuth.`, - }, - ], - isError: true, - } - } - } else if (apiKeyHeader) { - authResult = await authenticateCopilotApiKey(apiKeyHeader) - } - - if (!authResult.success || !authResult.userId) { - const errorMsg = apiKeyHeader - ? `AUTHENTICATION ERROR: ${authResult.error} Do NOT retry — this will fail until the user fixes their Copilot API key.` - : 'AUTHENTICATION ERROR: No authentication provided. Provide a Bearer token (OAuth 2.1) or an x-api-key header. Generate a Copilot API key in Settings → Copilot.' - logger.warn('MCP copilot auth failed', { method: request.method }) - return { - content: [ - { - type: 'text' as const, - text: errorMsg, - }, - ], - isError: true, - } - } - - const rateLimitResult = await mcpRateLimiter.checkRateLimitWithSubscription( - authResult.userId, - await getHighestPrioritySubscription(authResult.userId), - 'api-endpoint', - false - ) - - if (!rateLimitResult.allowed) { - return { - content: [ - { - type: 'text' as const, - text: `RATE LIMIT: Too many requests. Please wait and retry after ${rateLimitResult.resetAt.toISOString()}.`, - }, - ], - isError: true, - } - } - - const paramsValidation = mcpToolCallParamsSchema.safeParse(request.params) - if (!paramsValidation.success) { - throw new McpError(ErrorCode.InvalidParams, 'Tool name required') - } - const params = paramsValidation.data - - const result = await handleToolsCall( - { - name: params.name, - arguments: params.arguments, - }, - authResult.userId, - abortSignal - ) - - return result - }) - - return server -} - -async function handleMcpRequestWithSdk( - request: NextRequest, - parsedBody: unknown -): Promise { - const server = buildMcpServer(request.signal) - const transport = new WebStandardStreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, - }) - - await server.connect(transport) - - try { - return await transport.handleRequest(request, { parsedBody }) - } finally { - await server.close().catch(() => {}) - await transport.close().catch(() => {}) - } -} - -export const GET = withRouteHandler(async () => { - // Return 405 to signal that server-initiated SSE notifications are not - // supported. Without this, clients like mcp-remote will repeatedly - // reconnect trying to open an SSE stream, flooding the logs with GETs. - return new NextResponse(null, { status: 405 }) -}) - -export const POST = withRouteHandler(async (request: NextRequest) => { - const hasAuth = request.headers.has('authorization') || request.headers.has('x-api-key') - - if (!hasAuth) { - const origin = getBaseUrl().replace(/\/$/, '') - const resourceMetadataUrl = `${origin}/.well-known/oauth-protected-resource/api/mcp/copilot` - return new NextResponse(JSON.stringify({ error: 'unauthorized' }), { - status: 401, - headers: { - 'WWW-Authenticate': `Bearer resource_metadata="${resourceMetadataUrl}", scope="mcp:tools"`, - 'Content-Type': 'application/json', - }, - }) - } - - try { - let parsedBody: unknown - - try { - parsedBody = await request.json() - } catch { - return NextResponse.json(createError(0, ErrorCode.ParseError, 'Invalid JSON body'), { - status: 400, - }) - } - - const bodyValidation = mcpRequestBodySchema.safeParse(parsedBody) - if (!bodyValidation.success) { - return NextResponse.json( - createError(0, ErrorCode.InvalidRequest, 'Invalid JSON-RPC message'), - { - status: 400, - } - ) - } - - return await handleMcpRequestWithSdk(request, bodyValidation.data) - } catch (error) { - if (request.signal.aborted || (error as Error)?.name === 'AbortError') { - return NextResponse.json( - createError(0, ErrorCode.ConnectionClosed, 'Client cancelled request'), - { status: 499 } - ) - } - - logger.error('Error handling MCP request', { error }) - return NextResponse.json(createError(0, ErrorCode.InternalError, 'Internal error'), { - status: 500, - }) - } -}) - -export const DELETE = withRouteHandler(async (request: NextRequest) => { - void request - return NextResponse.json(createError(0, -32000, 'Method not allowed.'), { status: 405 }) -}) - -async function handleToolsCall( - params: { name: string; arguments?: Record }, - userId: string, - abortSignal?: AbortSignal -): Promise { - const args = params.arguments || {} - - const directTool = DIRECT_TOOL_DEFS.find((tool) => tool.name === params.name) - if (directTool) { - return handleDirectToolCall(directTool, args, userId) - } - - const subagentTool = SUBAGENT_TOOL_DEFS.find((tool) => tool.name === params.name) - if (subagentTool) { - return handleSubagentToolCall(subagentTool, args, userId, abortSignal) - } - - throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${params.name}`) -} - -async function handleDirectToolCall( - toolDef: (typeof DIRECT_TOOL_DEFS)[number], - args: Record, - userId: string -): Promise { - try { - const rawWorkflowId = (args.workflowId as string) || '' - let resolvedWorkspaceId: string | undefined - if (rawWorkflowId) { - const authorization = await authorizeWorkflowByWorkspacePermission({ - workflowId: rawWorkflowId, - userId, - action: 'read', - }) - if (!authorization.allowed) { - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { success: false, error: 'Workflow not found or access denied' }, - null, - 2 - ), - }, - ], - isError: true, - } - } - resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined - } - const execContext = await prepareExecutionContext( - userId, - rawWorkflowId, - (args.chatId as string) || undefined, - { workspaceId: resolvedWorkspaceId } - ) - - const toolCall = { - id: generateId(), - name: toolDef.toolId, - status: 'pending' as const, - params: args as Record, - startTime: Date.now(), - } - - ensureHandlersRegistered() - const result = await executeTool(toolCall.name, toolCall.params || {}, execContext) - - return { - content: [ - { - type: 'text', - text: JSON.stringify(result.output ?? result, null, 2), - }, - ], - isError: !result.success, - } - } catch (error) { - logger.error('Direct tool execution failed', { tool: toolDef.name, error }) - return { - content: [ - { - type: 'text', - text: `Tool execution failed: ${toError(error).message}`, - }, - ], - isError: true, - } - } -} - -/** - * Build mode uses the main /api/mcp orchestrator instead of /api/subagent/workflow. - * The main agent still delegates workflow work to the workflow subagent inside Go; - * this helper simply uses the full headless lifecycle so build requests behave like - * the primary MCP chat flow. - */ -async function handleBuildToolCall( - args: Record, - userId: string, - abortSignal?: AbortSignal -): Promise { - try { - const requestText = (args.request as string) || JSON.stringify(args) - const workflowId = args.workflowId as string | undefined - let resolvedWorkflowName: string | undefined - let resolvedWorkspaceId: string | undefined - - const resolved = workflowId - ? await (async () => { - const authorization = await authorizeWorkflowByWorkspacePermission({ - workflowId, - userId, - action: 'read', - }) - resolvedWorkflowName = authorization.workflow?.name || undefined - resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined - return authorization.allowed - ? { - status: 'resolved' as const, - workflowId, - workflowName: resolvedWorkflowName, - } - : { - status: 'not_found' as const, - message: 'workflowId is required for build. Call create_workflow first.', - } - })() - : await resolveWorkflowIdForUser(userId) - - if (resolved.status === 'resolved') { - resolvedWorkflowName ||= resolved.workflowName - } - - if (!resolved || resolved.status !== 'resolved') { - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - success: false, - error: - resolved?.message ?? - 'workflowId is required for build. Call create_workflow first.', - }, - null, - 2 - ), - }, - ], - isError: true, - } - } - - const chatId = generateId() - const executionContext = await prepareExecutionContext(userId, resolved.workflowId, chatId, { - workspaceId: resolvedWorkspaceId, - }) - resolvedWorkspaceId = executionContext.workspaceId - let workspaceContext: string | undefined - if (resolvedWorkspaceId) { - try { - workspaceContext = await generateWorkspaceContext(resolvedWorkspaceId, userId) - } catch (error) { - logger.warn('Failed to generate workspace context for build tool call', { - workflowId: resolved.workflowId, - workspaceId: resolvedWorkspaceId, - error: toError(error).message, - }) - } - } - - const requestPayload = { - message: requestText, - workflowId: resolved.workflowId, - ...(resolvedWorkflowName ? { workflowName: resolvedWorkflowName } : {}), - ...(resolvedWorkspaceId ? { workspaceId: resolvedWorkspaceId } : {}), - ...(workspaceContext ? { workspaceContext } : {}), - userId, - model: DEFAULT_COPILOT_MODEL, - mode: 'agent', - commands: ['fast'], - messageId: generateId(), - chatId, - } - - const result = await runHeadlessCopilotLifecycle(requestPayload, { - userId, - workflowId: resolved.workflowId, - workspaceId: resolvedWorkspaceId, - chatId, - goRoute: '/api/mcp', - executionContext, - autoExecuteTools: true, - timeout: ORCHESTRATION_TIMEOUT_MS, - interactive: false, - abortSignal, - }) - - const responseData = { - success: result.success, - content: result.content, - toolCalls: result.toolCalls, - error: result.error, - } - - return { - content: [{ type: 'text', text: JSON.stringify(responseData, null, 2) }], - isError: !result.success, - } - } catch (error) { - logger.error('Build tool call failed', { error }) - return { - content: [ - { - type: 'text', - text: `Build failed: ${toError(error).message}`, - }, - ], - isError: true, - } - } -} - -async function handleSubagentToolCall( - toolDef: (typeof SUBAGENT_TOOL_DEFS)[number], - args: Record, - userId: string, - abortSignal?: AbortSignal -): Promise { - if (toolDef.agentId === 'workflow') { - return handleBuildToolCall(args, userId, abortSignal) - } - - try { - const requestText = - (args.request as string) || - (args.message as string) || - (args.error as string) || - JSON.stringify(args) - const simRequestId = createRequestId() - - const context = (args.context as Record) || {} - if (args.plan && !context.plan) { - context.plan = args.plan - } - - // Authorize user-supplied workflowId / workspaceId before forwarding downstream - const rawWorkflowId = args.workflowId as string | undefined - const rawWorkspaceId = args.workspaceId as string | undefined - let resolvedWorkflowId: string | undefined - let resolvedWorkspaceId: string | undefined - - if (rawWorkflowId) { - const authorization = await authorizeWorkflowByWorkspacePermission({ - workflowId: rawWorkflowId, - userId, - action: 'read', - }) - if (!authorization.allowed) { - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { success: false, error: 'Workflow not found or access denied' }, - null, - 2 - ), - }, - ], - isError: true, - } - } - resolvedWorkflowId = rawWorkflowId - resolvedWorkspaceId = authorization.workflow?.workspaceId || undefined - } else if (rawWorkspaceId) { - await ensureWorkspaceAccess(rawWorkspaceId, userId, 'read') - resolvedWorkspaceId = rawWorkspaceId - } - - const result = await orchestrateSubagentStream( - toolDef.agentId, - { - message: requestText, - workflowId: resolvedWorkflowId, - workspaceId: resolvedWorkspaceId, - context, - model: DEFAULT_COPILOT_MODEL, - headless: true, - source: 'mcp', - }, - { - userId, - workflowId: resolvedWorkflowId, - workspaceId: resolvedWorkspaceId, - simRequestId, - abortSignal, - } - ) - - let responseData: unknown - if (result.structuredResult) { - responseData = { - success: result.structuredResult.success ?? result.success, - type: result.structuredResult.type, - summary: result.structuredResult.summary, - data: result.structuredResult.data, - } - } else if (result.error) { - responseData = { - success: false, - error: result.error, - errors: result.errors, - } - } else { - responseData = { - success: result.success, - content: result.content, - } - } +export const GET = withRouteHandler(async () => copilotMcpDeprecatedResponse()) - return { - content: [ - { - type: 'text', - text: JSON.stringify(responseData, null, 2), - }, - ], - isError: !result.success, - } - } catch (error) { - logger.error('Subagent tool call failed', { - tool: toolDef.name, - agentId: toolDef.agentId, - error, - }) +export const POST = withRouteHandler(async () => copilotMcpDeprecatedJsonRpcResponse()) - return { - content: [ - { - type: 'text', - text: `Subagent call failed: ${toError(error).message}`, - }, - ], - isError: true, - } - } -} +export const DELETE = withRouteHandler(async () => copilotMcpDeprecatedJsonRpcResponse()) diff --git a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts index 54f4cc588ee..fec6bc6c192 100644 --- a/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts +++ b/apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts @@ -6,8 +6,8 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { forkMothershipChatContract } from '@/lib/api/contracts/mothership-tasks' import { parseRequest } from '@/lib/api/server' -import { appendCopilotChatMessages } from '@/lib/copilot/chat/messages-dual-write' -import type { PersistedMessage } from '@/lib/copilot/chat/persisted-message' +import { loadCopilotChatMessages } from '@/lib/copilot/chat/lifecycle' +import { appendCopilotChatMessages } from '@/lib/copilot/chat/messages-store' import { fetchGo } from '@/lib/copilot/request/go/fetch' import { authenticateCopilotRequestSessionOnly, @@ -50,9 +50,19 @@ export const POST = withRouteHandler( const { chatId } = parsed.data.params const { upToMessageId } = parsed.data.body - // Load parent chat and verify ownership. const [parent] = await db - .select() + .select({ + id: copilotChats.id, + userId: copilotChats.userId, + type: copilotChats.type, + workspaceId: copilotChats.workspaceId, + title: copilotChats.title, + model: copilotChats.model, + resources: copilotChats.resources, + previewYaml: copilotChats.previewYaml, + planArtifact: copilotChats.planArtifact, + config: copilotChats.config, + }) .from(copilotChats) .where(eq(copilotChats.id, chatId)) .limit(1) @@ -65,8 +75,7 @@ export const POST = withRouteHandler( await assertActiveWorkspaceAccess(parent.workspaceId, userId) } - // Find the fork point in the Sim-side messages array. - const messages = Array.isArray(parent.messages) ? (parent.messages as PersistedMessage[]) : [] + const messages = await loadCopilotChatMessages(chatId) const forkIdx = messages.findIndex((m) => m.id === upToMessageId) if (forkIdx < 0) { return createBadRequestResponse('Message not found in chat') @@ -83,32 +92,36 @@ export const POST = withRouteHandler( const title = `Fork | ${baseTitle}` const now = new Date() - const [newChat] = await db - .insert(copilotChats) - .values({ - id: newId, - userId, - workspaceId: parent.workspaceId, - type: parent.type, - title, - model: parent.model, - messages: forkedMessages, - resources: parentResources, - previewYaml: parent.previewYaml, - planArtifact: parent.planArtifact, - config: parent.config, - conversationId: null, - updatedAt: now, - lastSeenAt: now, - }) - .returning({ id: copilotChats.id, workspaceId: copilotChats.workspaceId }) + const newChat = await db.transaction(async (tx) => { + const [row] = await tx + .insert(copilotChats) + .values({ + id: newId, + userId, + workspaceId: parent.workspaceId, + type: parent.type, + title, + model: parent.model, + resources: parentResources, + previewYaml: parent.previewYaml, + planArtifact: parent.planArtifact, + config: parent.config, + conversationId: null, + updatedAt: now, + lastSeenAt: now, + }) + .returning({ id: copilotChats.id, workspaceId: copilotChats.workspaceId }) + + if (!row) return null + + await appendCopilotChatMessages(newId, forkedMessages, { chatModel: parent.model }, tx) + return row + }) if (!newChat) { return createInternalServerErrorResponse('Failed to create forked chat') } - await appendCopilotChatMessages(newId, forkedMessages, { chatModel: parent.model }) - // Clone copilot-service conversation state (messages, active_messages, memory files). // Best-effort: if the copilot service doesn't have a row for the source chat yet, skip. try { diff --git a/apps/sim/app/api/mothership/chats/route.ts b/apps/sim/app/api/mothership/chats/route.ts index 1b7157fdde5..c5610da215d 100644 --- a/apps/sim/app/api/mothership/chats/route.ts +++ b/apps/sim/app/api/mothership/chats/route.ts @@ -106,7 +106,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { type: 'mothership', title: null, model: 'claude-opus-4-6', - messages: [], updatedAt: now, lastSeenAt: now, }) diff --git a/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts b/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts index 89d2cc81cb7..ed6bcf4d5b8 100644 --- a/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts @@ -11,6 +11,7 @@ import { import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { setActiveOrganizationForCurrentSession } from '@/lib/auth/active-organization' +import { getOrgMemberLedgerByUser } from '@/lib/billing/core/organization' import { getUserUsageData } from '@/lib/billing/core/usage' import { removeExternalUserFromOrganizationWorkspaces, @@ -101,10 +102,25 @@ export const GET = withRouteHandler( const computed = await getUserUsageData(memberId) if (usageData.length > 0) { + // currentPeriodCost is only a baseline; add this member's attributed + // usage_log for the period. (getUserUsageData returns the org POOL for + // org-scoped members, so it can't supply the per-member figure.) + const memberLedger = + ( + await getOrgMemberLedgerByUser( + organizationId, + computed.billingPeriodStart && computed.billingPeriodEnd + ? { start: computed.billingPeriodStart, end: computed.billingPeriodEnd } + : null + ) + ).get(memberId) ?? 0 memberData = { ...memberData, usage: { ...usageData[0], + currentPeriodCost: ( + Number(usageData[0].currentPeriodCost ?? 0) + memberLedger + ).toString(), billingPeriodStart: computed.billingPeriodStart, billingPeriodEnd: computed.billingPeriodEnd, }, diff --git a/apps/sim/app/api/organizations/[id]/members/route.ts b/apps/sim/app/api/organizations/[id]/members/route.ts index 48a356215cf..e412a7e635f 100644 --- a/apps/sim/app/api/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/route.ts @@ -17,6 +17,7 @@ import { } from '@/lib/api/contracts/organization' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' +import { getOrgMemberLedgerByUser } from '@/lib/billing/core/organization' import { ENTITLED_SUBSCRIPTION_STATUSES } from '@/lib/billing/subscriptions/utils' import { validateSeatAvailability } from '@/lib/billing/validation/seat-management' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -139,8 +140,21 @@ export const GET = withRouteHandler( const billingPeriodStart = orgSub?.periodStart ?? null const billingPeriodEnd = orgSub?.periodEnd ?? null + // currentPeriodCost is only a baseline; add each member's attributed + // usage_log for the period (batched, one query) so the roster shows real + // usage rather than the frozen baseline. + const usageByUser = await getOrgMemberLedgerByUser( + organizationId, + billingPeriodStart && billingPeriodEnd + ? { start: billingPeriodStart, end: billingPeriodEnd } + : null + ) + const membersWithUsage = base.map((row) => ({ ...row, + currentPeriodCost: ( + Number(row.currentPeriodCost ?? 0) + (usageByUser.get(row.userId) ?? 0) + ).toString(), billingPeriodStart, billingPeriodEnd, })) diff --git a/apps/sim/app/api/providers/baseten/models/route.test.ts b/apps/sim/app/api/providers/baseten/models/route.test.ts new file mode 100644 index 00000000000..8ada3c427ea --- /dev/null +++ b/apps/sim/app/api/providers/baseten/models/route.test.ts @@ -0,0 +1,252 @@ +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockFilterBlacklistedModels, + mockIsProviderBlacklisted, + mockGetBYOKKey, + mockGetSession, + mockGetUserEntityPermissions, + mutableEnv, +} = vi.hoisted(() => ({ + mockFilterBlacklistedModels: vi.fn(), + mockIsProviderBlacklisted: vi.fn(), + mockGetBYOKKey: vi.fn(), + mockGetSession: vi.fn(), + mockGetUserEntityPermissions: vi.fn(), + mutableEnv: { BASETEN_API_KEY: undefined as string | undefined }, +})) + +vi.mock('@/lib/core/config/env', () => ({ env: mutableEnv })) + +vi.mock('@/providers/utils', () => ({ + filterBlacklistedModels: mockFilterBlacklistedModels, + isProviderBlacklisted: mockIsProviderBlacklisted, +})) + +vi.mock('@/lib/api-key/byok', () => ({ + getBYOKKey: mockGetBYOKKey, +})) + +vi.mock('@/lib/auth', () => ({ + getSession: mockGetSession, +})) + +vi.mock('@/lib/workspaces/permissions/utils', () => ({ + getUserEntityPermissions: mockGetUserEntityPermissions, +})) + +import { GET } from '@/app/api/providers/baseten/models/route' + +const BASETEN_MODELS_URL = 'https://inference.baseten.co/v1/models' + +function jsonResponse(body: unknown, init: { ok?: boolean; status?: number } = {}): Response { + const status = init.status ?? 200 + const ok = init.ok ?? (status >= 200 && status < 300) + return { + ok, + status, + statusText: ok ? 'OK' : 'Error', + json: vi.fn(async () => body), + } as unknown as Response +} + +function setEnvKey(value: string | undefined): void { + mutableEnv.BASETEN_API_KEY = value +} + +function authHeaderFromLastFetch(mockFetch: ReturnType): unknown { + const init = mockFetch.mock.calls.at(-1)?.[1] as RequestInit | undefined + return (init?.headers as Record | undefined)?.Authorization +} + +describe('GET /api/providers/baseten/models', () => { + let mockFetch: ReturnType + + beforeEach(() => { + vi.clearAllMocks() + + mockFetch = vi.fn() + vi.stubGlobal('fetch', mockFetch) + + mockIsProviderBlacklisted.mockReturnValue(false) + mockFilterBlacklistedModels.mockImplementation((models: string[]) => models) + mockGetBYOKKey.mockResolvedValue(null) + mockGetSession.mockResolvedValue(null) + mockGetUserEntityPermissions.mockResolvedValue(null) + setEnvKey(undefined) + }) + + it('returns empty models without fetching when the provider is blacklisted', async () => { + mockIsProviderBlacklisted.mockReturnValue(true) + setEnvKey('env-key') + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('returns empty models when no workspaceId and no env key are available', async () => { + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('fetches models with the env key and prefixes each id with baseten/', async () => { + setEnvKey('env-key') + mockFetch.mockResolvedValueOnce( + jsonResponse({ + data: [{ id: 'openai/gpt-oss-120b' }, { id: 'deepseek-ai/DeepSeek-V3' }], + }) + ) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + models: ['baseten/openai/gpt-oss-120b', 'baseten/deepseek-ai/DeepSeek-V3'], + }) + + expect(mockFetch).toHaveBeenCalledTimes(1) + const [url, init] = mockFetch.mock.calls[0] + expect(url).toBe(BASETEN_MODELS_URL) + expect((init.headers as Record).Authorization).toBe('Bearer env-key') + }) + + it('uses the BYOK key when workspaceId, session, and permission are present', async () => { + setEnvKey('env-key') + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockGetUserEntityPermissions.mockResolvedValue('admin') + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-key', isBYOK: true }) + mockFetch.mockResolvedValueOnce(jsonResponse({ data: [{ id: 'model-a' }] })) + + const res = await GET( + createMockRequest('GET', undefined, {}, 'http://localhost:3000/api/test?workspaceId=ws-1') + ) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['baseten/model-a'] }) + + expect(mockGetBYOKKey).toHaveBeenCalledWith('ws-1', 'baseten') + expect(authHeaderFromLastFetch(mockFetch)).toBe('Bearer byok-key') + }) + + it('falls back to the env key when there is a workspaceId but no session', async () => { + setEnvKey('env-key') + mockGetSession.mockResolvedValue(null) + mockFetch.mockResolvedValueOnce(jsonResponse({ data: [{ id: 'model-a' }] })) + + const res = await GET( + createMockRequest('GET', undefined, {}, 'http://localhost:3000/api/test?workspaceId=ws-1') + ) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['baseten/model-a'] }) + expect(mockGetBYOKKey).not.toHaveBeenCalled() + expect(authHeaderFromLastFetch(mockFetch)).toBe('Bearer env-key') + }) + + it('falls back to the env key when the user lacks workspace permission', async () => { + setEnvKey('env-key') + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockGetUserEntityPermissions.mockResolvedValue(null) + mockFetch.mockResolvedValueOnce(jsonResponse({ data: [{ id: 'model-a' }] })) + + const res = await GET( + createMockRequest('GET', undefined, {}, 'http://localhost:3000/api/test?workspaceId=ws-1') + ) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['baseten/model-a'] }) + expect(mockGetBYOKKey).not.toHaveBeenCalled() + expect(authHeaderFromLastFetch(mockFetch)).toBe('Bearer env-key') + }) + + it('returns empty models when the upstream responds 401', async () => { + setEnvKey('env-key') + mockFetch.mockResolvedValueOnce(jsonResponse({}, { ok: false, status: 401 })) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns empty models when the upstream responds 500', async () => { + setEnvKey('env-key') + mockFetch.mockResolvedValueOnce(jsonResponse({}, { ok: false, status: 500 })) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns empty models when fetch throws', async () => { + setEnvKey('env-key') + mockFetch.mockRejectedValueOnce(new Error('network down')) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns empty models when the upstream data array is empty', async () => { + setEnvKey('env-key') + mockFetch.mockResolvedValueOnce(jsonResponse({ data: [] })) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns empty models when the upstream omits the data field', async () => { + setEnvKey('env-key') + mockFetch.mockResolvedValueOnce(jsonResponse({ object: 'list' })) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('dedupes repeated model ids', async () => { + setEnvKey('env-key') + mockFetch.mockResolvedValueOnce( + jsonResponse({ + data: [{ id: 'model-a' }, { id: 'model-a' }, { id: 'model-b' }], + }) + ) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['baseten/model-a', 'baseten/model-b'] }) + }) + + it('drops models removed by the blacklist filter', async () => { + setEnvKey('env-key') + mockFilterBlacklistedModels.mockImplementation((models: string[]) => + models.filter((m) => m !== 'baseten/blocked-model') + ) + mockFetch.mockResolvedValueOnce( + jsonResponse({ + data: [{ id: 'allowed-model' }, { id: 'blocked-model' }], + }) + ) + + const res = await GET(createMockRequest('GET')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['baseten/allowed-model'] }) + }) +}) diff --git a/apps/sim/app/api/providers/baseten/models/route.ts b/apps/sim/app/api/providers/baseten/models/route.ts new file mode 100644 index 00000000000..b73a6711421 --- /dev/null +++ b/apps/sim/app/api/providers/baseten/models/route.ts @@ -0,0 +1,93 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + basetenProviderModelsQuerySchema, + basetenUpstreamResponseSchema, + providerModelsResponseSchema, +} from '@/lib/api/contracts/providers' +import { validationErrorResponse } from '@/lib/api/server' +import { getBYOKKey } from '@/lib/api-key/byok' +import { getSession } from '@/lib/auth' +import { env } from '@/lib/core/config/env' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' +import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils' + +const logger = createLogger('BasetenModelsAPI') + +export const GET = withRouteHandler(async (request: NextRequest) => { + if (isProviderBlacklisted('baseten')) { + logger.info('Baseten provider is blacklisted, returning empty models') + return NextResponse.json({ models: [] }) + } + + let apiKey: string | undefined + + const queryValidation = basetenProviderModelsQuerySchema.safeParse({ + workspaceId: request.nextUrl.searchParams.get('workspaceId') ?? undefined, + }) + if (!queryValidation.success) return validationErrorResponse(queryValidation.error) + const { workspaceId } = queryValidation.data + if (workspaceId) { + const session = await getSession() + if (session?.user?.id) { + const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId) + if (permission) { + const byokResult = await getBYOKKey(workspaceId, 'baseten') + if (byokResult) { + apiKey = byokResult.apiKey + } + } + } + } + + if (!apiKey) { + apiKey = env.BASETEN_API_KEY + } + + if (!apiKey) { + logger.info('No Baseten API key available, returning empty models') + return NextResponse.json({ models: [] }) + } + + try { + const response = await fetch('https://inference.baseten.co/v1/models', { + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + cache: 'no-store', + }) + + if (!response.ok) { + logger.warn('Failed to fetch Baseten models', { + status: response.status, + statusText: response.statusText, + }) + return NextResponse.json({ models: [] }) + } + + const data = basetenUpstreamResponseSchema.parse(await response.json()) + + const allModels: string[] = [] + for (const model of data.data ?? []) { + allModels.push(`baseten/${model.id}`) + } + + const uniqueModels = Array.from(new Set(allModels)) + const models = filterBlacklistedModels(uniqueModels) + + logger.info('Successfully fetched Baseten models', { + count: models.length, + filtered: uniqueModels.length - models.length, + }) + + return NextResponse.json(providerModelsResponseSchema.parse({ models })) + } catch (error) { + logger.error('Error fetching Baseten models', { + error: getErrorMessage(error, 'Unknown error'), + }) + return NextResponse.json({ models: [] }) + } +}) diff --git a/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts b/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts new file mode 100644 index 00000000000..fd70d1ae8b1 --- /dev/null +++ b/apps/sim/app/api/providers/ollama-cloud/models/route.test.ts @@ -0,0 +1,238 @@ +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockFilterBlacklistedModels, + mockIsProviderBlacklisted, + mockGetBYOKKey, + mockGetSession, + mockGetUserEntityPermissions, + mockFetch, +} = vi.hoisted(() => ({ + mockFilterBlacklistedModels: vi.fn(), + mockIsProviderBlacklisted: vi.fn(), + mockGetBYOKKey: vi.fn(), + mockGetSession: vi.fn(), + mockGetUserEntityPermissions: vi.fn(), + mockFetch: vi.fn(), +})) + +vi.mock('@/providers/utils', () => ({ + filterBlacklistedModels: mockFilterBlacklistedModels, + isProviderBlacklisted: mockIsProviderBlacklisted, +})) + +vi.mock('@/lib/api-key/byok', () => ({ + getBYOKKey: mockGetBYOKKey, +})) + +vi.mock('@/lib/auth', () => ({ + getSession: mockGetSession, +})) + +vi.mock('@/lib/workspaces/permissions/utils', () => ({ + getUserEntityPermissions: mockGetUserEntityPermissions, +})) + +import { GET } from '@/app/api/providers/ollama-cloud/models/route' + +const OLLAMA_CLOUD_TAGS_URL = 'https://ollama.com/api/tags' + +const okResponse = (body: unknown) => ({ + ok: true, + status: 200, + statusText: 'OK', + json: vi.fn().mockResolvedValue(body), +}) + +const errorResponse = (status: number, statusText = 'Unauthorized') => ({ + ok: false, + status, + statusText, + json: vi.fn().mockResolvedValue({}), +}) + +/** + * Builds a request whose query string carries the given workspaceId. Passing + * `undefined` omits the param entirely; passing `''` produces `?workspaceId=`. + */ +const requestWithWorkspace = (workspaceId?: string) => { + const url = new URL('http://localhost:3000/api/providers/ollama-cloud/models') + if (workspaceId !== undefined) { + url.searchParams.set('workspaceId', workspaceId) + } + return createMockRequest('GET', undefined, {}, url.toString()) +} + +const fetchAuthHeader = () => { + const init = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined + const headers = init?.headers as Record | undefined + return headers?.Authorization +} + +/** Grants a session + workspace permission so the BYOK lookup is reached. */ +const grantWorkspaceAccess = () => { + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockGetUserEntityPermissions.mockResolvedValue('admin') +} + +describe('GET /api/providers/ollama-cloud/models', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('fetch', mockFetch) + + mockIsProviderBlacklisted.mockReturnValue(false) + mockFilterBlacklistedModels.mockImplementation((models: string[]) => models) + mockGetBYOKKey.mockResolvedValue(null) + mockGetSession.mockResolvedValue(null) + mockGetUserEntityPermissions.mockResolvedValue(null) + }) + + it('returns empty models without calling fetch when the provider is blacklisted', async () => { + mockIsProviderBlacklisted.mockReturnValue(true) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('returns empty models when there is no workspaceId (BYOK only, no env fallback)', async () => { + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockFetch).not.toHaveBeenCalled() + expect(mockGetBYOKKey).not.toHaveBeenCalled() + }) + + it('returns empty models when the workspace has no stored BYOK key (never falls back to a hosted key)', async () => { + grantWorkspaceAccess() + mockGetBYOKKey.mockResolvedValue(null) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockGetBYOKKey).toHaveBeenCalledWith('ws-1', 'ollama-cloud') + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('fetches /api/tags with the BYOK key and prefixes each model name with ollama-cloud/', async () => { + grantWorkspaceAccess() + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-ollama-key' }) + mockFetch.mockResolvedValue( + okResponse({ + models: [{ name: 'gpt-oss:120b' }, { name: 'deepseek-v3.1:671b' }], + }) + ) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + models: ['ollama-cloud/gpt-oss:120b', 'ollama-cloud/deepseek-v3.1:671b'], + }) + + expect(mockFetch).toHaveBeenCalledTimes(1) + expect(mockFetch.mock.calls[0][0]).toBe(OLLAMA_CLOUD_TAGS_URL) + expect(fetchAuthHeader()).toBe('Bearer byok-ollama-key') + }) + + it('does not call getBYOKKey when there is a workspaceId but no session', async () => { + mockGetSession.mockResolvedValue(null) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockGetBYOKKey).not.toHaveBeenCalled() + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('does not call getBYOKKey when the session user lacks workspace permission', async () => { + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockGetUserEntityPermissions.mockResolvedValue(null) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockGetBYOKKey).not.toHaveBeenCalled() + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('returns empty models when the upstream fetch responds non-ok', async () => { + grantWorkspaceAccess() + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-ollama-key' }) + mockFetch.mockResolvedValue(errorResponse(401, 'Unauthorized')) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns empty models when the upstream fetch throws', async () => { + grantWorkspaceAccess() + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-ollama-key' }) + mockFetch.mockRejectedValue(new Error('network down')) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns a validation error for an empty workspaceId query param', async () => { + const res = await GET(requestWithWorkspace('')) + + expect(res.status).toBe(400) + const body = (await res.json()) as { error: string } + expect(body.error).toBe('Validation error') + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('dedupes duplicate model names from the upstream response', async () => { + grantWorkspaceAccess() + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-ollama-key' }) + mockFetch.mockResolvedValue( + okResponse({ + models: [{ name: 'gpt-oss:120b' }, { name: 'gpt-oss:120b' }, { name: 'qwen3-coder:480b' }], + }) + ) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + models: ['ollama-cloud/gpt-oss:120b', 'ollama-cloud/qwen3-coder:480b'], + }) + }) + + it('applies the blacklist filter to the deduped model list', async () => { + grantWorkspaceAccess() + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-ollama-key' }) + mockFilterBlacklistedModels.mockImplementation((models: string[]) => + models.filter((m) => !m.includes('qwen')) + ) + mockFetch.mockResolvedValue( + okResponse({ + models: [{ name: 'gpt-oss:120b' }, { name: 'qwen3-coder:480b' }], + }) + ) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['ollama-cloud/gpt-oss:120b'] }) + expect(mockFilterBlacklistedModels).toHaveBeenCalledWith([ + 'ollama-cloud/gpt-oss:120b', + 'ollama-cloud/qwen3-coder:480b', + ]) + }) +}) diff --git a/apps/sim/app/api/providers/ollama-cloud/models/route.ts b/apps/sim/app/api/providers/ollama-cloud/models/route.ts new file mode 100644 index 00000000000..bd5673e0842 --- /dev/null +++ b/apps/sim/app/api/providers/ollama-cloud/models/route.ts @@ -0,0 +1,91 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + ollamaCloudProviderModelsQuerySchema, + ollamaUpstreamResponseSchema, + providerModelsResponseSchema, +} from '@/lib/api/contracts/providers' +import { validationErrorResponse } from '@/lib/api/server' +import { getBYOKKey } from '@/lib/api-key/byok' +import { getSession } from '@/lib/auth' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' +import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils' + +const logger = createLogger('OllamaCloudModelsAPI') + +/** + * Get available Ollama Cloud models. + * + * Ollama Cloud is BYOK-only — Sim never supplies a hosted key and never bills + * usage. Models are listed only when the workspace has stored its own Ollama + * API key, which is used to authenticate against the cloud `/api/tags` endpoint. + */ +export const GET = withRouteHandler(async (request: NextRequest) => { + if (isProviderBlacklisted('ollama-cloud')) { + logger.info('Ollama Cloud provider is blacklisted, returning empty models') + return NextResponse.json({ models: [] }) + } + + const queryValidation = ollamaCloudProviderModelsQuerySchema.safeParse({ + workspaceId: request.nextUrl.searchParams.get('workspaceId') ?? undefined, + }) + if (!queryValidation.success) return validationErrorResponse(queryValidation.error) + const { workspaceId } = queryValidation.data + + let apiKey: string | undefined + if (workspaceId) { + const session = await getSession() + if (session?.user?.id) { + const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId) + if (permission) { + const byokResult = await getBYOKKey(workspaceId, 'ollama-cloud') + if (byokResult) { + apiKey = byokResult.apiKey + } + } + } + } + + if (!apiKey) { + logger.info('No Ollama Cloud API key available, returning empty models') + return NextResponse.json({ models: [] }) + } + + try { + const response = await fetch('https://ollama.com/api/tags', { + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + cache: 'no-store', + }) + + if (!response.ok) { + logger.warn('Failed to fetch Ollama Cloud models', { + status: response.status, + statusText: response.statusText, + }) + return NextResponse.json({ models: [] }) + } + + const data = ollamaUpstreamResponseSchema.parse(await response.json()) + + const allModels = data.models.map((model) => `ollama-cloud/${model.name}`) + const uniqueModels = Array.from(new Set(allModels)) + const models = filterBlacklistedModels(uniqueModels) + + logger.info('Successfully fetched Ollama Cloud models', { + count: models.length, + filtered: uniqueModels.length - models.length, + }) + + return NextResponse.json(providerModelsResponseSchema.parse({ models })) + } catch (error) { + logger.error('Error fetching Ollama Cloud models', { + error: getErrorMessage(error, 'Unknown error'), + }) + return NextResponse.json({ models: [] }) + } +}) diff --git a/apps/sim/app/api/providers/together/models/route.test.ts b/apps/sim/app/api/providers/together/models/route.test.ts new file mode 100644 index 00000000000..ae801bb7c56 --- /dev/null +++ b/apps/sim/app/api/providers/together/models/route.test.ts @@ -0,0 +1,259 @@ +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockFilterBlacklistedModels, + mockIsProviderBlacklisted, + mockGetBYOKKey, + mockGetSession, + mockGetUserEntityPermissions, + mockFetch, + mutableEnv, +} = vi.hoisted(() => ({ + mockFilterBlacklistedModels: vi.fn(), + mockIsProviderBlacklisted: vi.fn(), + mockGetBYOKKey: vi.fn(), + mockGetSession: vi.fn(), + mockGetUserEntityPermissions: vi.fn(), + mockFetch: vi.fn(), + mutableEnv: { TOGETHER_API_KEY: undefined as string | undefined }, +})) + +vi.mock('@/providers/utils', () => ({ + filterBlacklistedModels: mockFilterBlacklistedModels, + isProviderBlacklisted: mockIsProviderBlacklisted, +})) + +vi.mock('@/lib/api-key/byok', () => ({ + getBYOKKey: mockGetBYOKKey, +})) + +vi.mock('@/lib/auth', () => ({ + getSession: mockGetSession, +})) + +vi.mock('@/lib/workspaces/permissions/utils', () => ({ + getUserEntityPermissions: mockGetUserEntityPermissions, +})) + +vi.mock('@/lib/core/config/env', () => ({ + env: mutableEnv, +})) + +import { GET } from '@/app/api/providers/together/models/route' + +const TOGETHER_MODELS_URL = 'https://api.together.ai/v1/models' + +const okResponse = (body: unknown) => ({ + ok: true, + status: 200, + statusText: 'OK', + json: vi.fn().mockResolvedValue(body), +}) + +const errorResponse = (status: number, statusText = 'Unauthorized') => ({ + ok: false, + status, + statusText, + json: vi.fn().mockResolvedValue({}), +}) + +/** + * Builds a request whose query string carries the given workspaceId. Passing + * `undefined` omits the param entirely; passing `''` produces `?workspaceId=`. + */ +const requestWithWorkspace = (workspaceId?: string) => { + const url = new URL('http://localhost:3000/api/providers/together/models') + if (workspaceId !== undefined) { + url.searchParams.set('workspaceId', workspaceId) + } + return createMockRequest('GET', undefined, {}, url.toString()) +} + +const fetchAuthHeader = () => { + const init = mockFetch.mock.calls[0]?.[1] as RequestInit | undefined + const headers = init?.headers as Record | undefined + return headers?.Authorization +} + +describe('GET /api/providers/together/models', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('fetch', mockFetch) + + mutableEnv.TOGETHER_API_KEY = undefined + mockIsProviderBlacklisted.mockReturnValue(false) + mockFilterBlacklistedModels.mockImplementation((models: string[]) => models) + mockGetBYOKKey.mockResolvedValue(null) + mockGetSession.mockResolvedValue(null) + mockGetUserEntityPermissions.mockResolvedValue(null) + }) + + it('returns empty models without calling fetch when the provider is blacklisted', async () => { + mockIsProviderBlacklisted.mockReturnValue(true) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('returns empty models when there is no workspaceId and no env key', async () => { + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('fetches with the env key and prefixes each model id with together/', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockFetch.mockResolvedValue( + okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }, { id: 'Qwen/Qwen2.5-72B-Instruct-Turbo' }]) + ) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + models: ['together/moonshotai/Kimi-K2-Instruct', 'together/Qwen/Qwen2.5-72B-Instruct-Turbo'], + }) + + expect(mockFetch).toHaveBeenCalledTimes(1) + expect(mockFetch.mock.calls[0][0]).toBe(TOGETHER_MODELS_URL) + expect(fetchAuthHeader()).toBe('Bearer env-together-key') + }) + + it('uses the BYOK key when a workspace, session, and permission are present', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockGetUserEntityPermissions.mockResolvedValue('admin') + mockGetBYOKKey.mockResolvedValue({ apiKey: 'byok-together-key' }) + mockFetch.mockResolvedValue(okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }])) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['together/moonshotai/Kimi-K2-Instruct'] }) + + expect(mockGetBYOKKey).toHaveBeenCalledWith('ws-1', 'together') + expect(fetchAuthHeader()).toBe('Bearer byok-together-key') + }) + + it('falls back to the env key when a workspaceId is given but there is no session', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockGetSession.mockResolvedValue(null) + mockFetch.mockResolvedValue(okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }])) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['together/moonshotai/Kimi-K2-Instruct'] }) + expect(mockGetBYOKKey).not.toHaveBeenCalled() + expect(fetchAuthHeader()).toBe('Bearer env-together-key') + }) + + it('falls back to the env key when the session user lacks workspace permission', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockGetUserEntityPermissions.mockResolvedValue(null) + mockFetch.mockResolvedValue(okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }])) + + const res = await GET(requestWithWorkspace('ws-1')) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['together/moonshotai/Kimi-K2-Instruct'] }) + expect(mockGetBYOKKey).not.toHaveBeenCalled() + expect(fetchAuthHeader()).toBe('Bearer env-together-key') + }) + + it('returns empty models when the upstream fetch responds non-ok', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockFetch.mockResolvedValue(errorResponse(401, 'Unauthorized')) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns empty models when the upstream fetch throws', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockFetch.mockRejectedValue(new Error('network down')) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: [] }) + }) + + it('returns a validation error for an empty workspaceId query param', async () => { + const res = await GET(requestWithWorkspace('')) + + expect(res.status).toBe(400) + const body = (await res.json()) as { error: string } + expect(body.error).toBe('Validation error') + expect(mockFetch).not.toHaveBeenCalled() + }) + + it('dedupes duplicate model ids from the upstream array', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockFetch.mockResolvedValue( + okResponse([ + { id: 'moonshotai/Kimi-K2-Instruct' }, + { id: 'moonshotai/Kimi-K2-Instruct' }, + { id: 'Qwen/Qwen2.5-72B-Instruct-Turbo' }, + ]) + ) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + models: ['together/moonshotai/Kimi-K2-Instruct', 'together/Qwen/Qwen2.5-72B-Instruct-Turbo'], + }) + }) + + it('applies the blacklist filter to the deduped model list', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockFilterBlacklistedModels.mockImplementation((models: string[]) => + models.filter((m) => !m.includes('Qwen')) + ) + mockFetch.mockResolvedValue( + okResponse([{ id: 'moonshotai/Kimi-K2-Instruct' }, { id: 'Qwen/Qwen2.5-72B-Instruct-Turbo' }]) + ) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ models: ['together/moonshotai/Kimi-K2-Instruct'] }) + expect(mockFilterBlacklistedModels).toHaveBeenCalledWith([ + 'together/moonshotai/Kimi-K2-Instruct', + 'together/Qwen/Qwen2.5-72B-Instruct-Turbo', + ]) + }) + + it('filters out non-chat model types (image, embedding, rerank, etc.)', async () => { + mutableEnv.TOGETHER_API_KEY = 'env-together-key' + mockFetch.mockResolvedValue( + okResponse([ + { id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', type: 'chat' }, + { id: 'black-forest-labs/FLUX.1-schnell', type: 'image' }, + { id: 'BAAI/bge-large-en-v1.5', type: 'embedding' }, + { id: 'Salesforce/Llama-Rank-V1', type: 'rerank' }, + { id: 'openai/whisper-large-v3', type: 'transcribe' }, + ]) + ) + + const res = await GET(requestWithWorkspace()) + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + models: ['together/meta-llama/Llama-3.3-70B-Instruct-Turbo'], + }) + }) +}) diff --git a/apps/sim/app/api/providers/together/models/route.ts b/apps/sim/app/api/providers/together/models/route.ts new file mode 100644 index 00000000000..dcaa0dc0c5a --- /dev/null +++ b/apps/sim/app/api/providers/together/models/route.ts @@ -0,0 +1,105 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { + providerModelsResponseSchema, + togetherProviderModelsQuerySchema, + togetherUpstreamResponseSchema, +} from '@/lib/api/contracts/providers' +import { validationErrorResponse } from '@/lib/api/server' +import { getBYOKKey } from '@/lib/api-key/byok' +import { getSession } from '@/lib/auth' +import { env } from '@/lib/core/config/env' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' +import { filterBlacklistedModels, isProviderBlacklisted } from '@/providers/utils' + +const logger = createLogger('TogetherModelsAPI') + +/** Together's catalog includes non-text models; only chat models work with chat completions. */ +const NON_CHAT_MODEL_TYPES = new Set([ + 'image', + 'video', + 'audio', + 'transcribe', + 'embedding', + 'moderation', + 'rerank', +]) + +export const GET = withRouteHandler(async (request: NextRequest) => { + if (isProviderBlacklisted('together')) { + logger.info('Together provider is blacklisted, returning empty models') + return NextResponse.json({ models: [] }) + } + + let apiKey: string | undefined + + const queryValidation = togetherProviderModelsQuerySchema.safeParse({ + workspaceId: request.nextUrl.searchParams.get('workspaceId') ?? undefined, + }) + if (!queryValidation.success) return validationErrorResponse(queryValidation.error) + const { workspaceId } = queryValidation.data + if (workspaceId) { + const session = await getSession() + if (session?.user?.id) { + const permission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId) + if (permission) { + const byokResult = await getBYOKKey(workspaceId, 'together') + if (byokResult) { + apiKey = byokResult.apiKey + } + } + } + } + + if (!apiKey) { + apiKey = env.TOGETHER_API_KEY + } + + if (!apiKey) { + logger.info('No Together API key available, returning empty models') + return NextResponse.json({ models: [] }) + } + + try { + const response = await fetch('https://api.together.ai/v1/models', { + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + cache: 'no-store', + }) + + if (!response.ok) { + logger.warn('Failed to fetch Together models', { + status: response.status, + statusText: response.statusText, + }) + return NextResponse.json({ models: [] }) + } + + const data = togetherUpstreamResponseSchema.parse(await response.json()) + + const allModels: string[] = [] + for (const model of data) { + if (model.type && NON_CHAT_MODEL_TYPES.has(model.type)) continue + allModels.push(`together/${model.id}`) + } + + const uniqueModels = Array.from(new Set(allModels)) + const models = filterBlacklistedModels(uniqueModels) + + logger.info('Successfully fetched Together models', { + count: models.length, + filtered: uniqueModels.length - models.length, + }) + + return NextResponse.json(providerModelsResponseSchema.parse({ models })) + } catch (error) { + logger.error('Error fetching Together models', { + error: getErrorMessage(error, 'Unknown error'), + }) + return NextResponse.json({ models: [] }) + } +}) diff --git a/apps/sim/app/api/superuser/import-workflow/route.ts b/apps/sim/app/api/superuser/import-workflow/route.ts index 6d54f8f1008..8cce8c24b55 100644 --- a/apps/sim/app/api/superuser/import-workflow/route.ts +++ b/apps/sim/app/api/superuser/import-workflow/route.ts @@ -7,8 +7,8 @@ import { type NextRequest, NextResponse } from 'next/server' import { importWorkflowAsSuperuserContract } from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' -import { appendCopilotChatMessages } from '@/lib/copilot/chat/messages-dual-write' -import type { PersistedMessage } from '@/lib/copilot/chat/persisted-message' +import { loadCopilotChatMessages } from '@/lib/copilot/chat/lifecycle' +import { appendCopilotChatMessages } from '@/lib/copilot/chat/messages-store' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { verifyEffectiveSuperUser } from '@/lib/permissions/super-user' import { parseWorkflowJson } from '@/lib/workflows/operations/import-export' @@ -167,34 +167,46 @@ export const POST = withRouteHandler(async (request: NextRequest) => { // Copy copilot chats associated with the source workflow const sourceCopilotChats = await db - .select() + .select({ + id: copilotChats.id, + title: copilotChats.title, + model: copilotChats.model, + previewYaml: copilotChats.previewYaml, + planArtifact: copilotChats.planArtifact, + config: copilotChats.config, + }) .from(copilotChats) .where(eq(copilotChats.workflowId, workflowId)) let copilotChatsImported = 0 for (const chat of sourceCopilotChats) { - const [imported] = await db - .insert(copilotChats) - .values({ - userId: session.user.id, - workflowId: newWorkflowId, - title: chat.title ? `[Import] ${chat.title}` : null, - messages: chat.messages, - model: chat.model, - conversationId: null, // Don't copy conversation ID - previewYaml: chat.previewYaml, - planArtifact: chat.planArtifact, - config: chat.config, - createdAt: new Date(), - updatedAt: new Date(), - }) - .returning({ id: copilotChats.id }) - if (imported && Array.isArray(chat.messages) && chat.messages.length > 0) { - await appendCopilotChatMessages(imported.id, chat.messages as PersistedMessage[], { - chatModel: chat.model, - }) - } + const sourceMessages = await loadCopilotChatMessages(chat.id) + await db.transaction(async (tx) => { + const [imported] = await tx + .insert(copilotChats) + .values({ + userId: session.user.id, + workflowId: newWorkflowId, + title: chat.title ? `[Import] ${chat.title}` : null, + model: chat.model, + conversationId: null, // Don't copy conversation ID + previewYaml: chat.previewYaml, + planArtifact: chat.planArtifact, + config: chat.config, + createdAt: new Date(), + updatedAt: new Date(), + }) + .returning({ id: copilotChats.id }) + if (imported && sourceMessages.length > 0) { + await appendCopilotChatMessages( + imported.id, + sourceMessages, + { chatModel: chat.model }, + tx + ) + } + }) copilotChatsImported++ } diff --git a/apps/sim/app/api/table/[tableId]/export/route.ts b/apps/sim/app/api/table/[tableId]/export/route.ts index 8f9fa34b807..a9f7b002070 100644 --- a/apps/sim/app/api/table/[tableId]/export/route.ts +++ b/apps/sim/app/api/table/[tableId]/export/route.ts @@ -53,7 +53,9 @@ export const GET = withRouteHandler(async (request: NextRequest, { params }: Rou const encoder = new TextEncoder() try { if (format === 'csv') { - controller.enqueue(encoder.encode(`${toCsvRow(columns.map((c) => c.name))}\n`)) + controller.enqueue( + encoder.encode(`${toCsvRow(columns.map((c) => neutralizeCsvFormula(c.name)))}\n`) + ) } else { controller.enqueue(encoder.encode('[')) } @@ -111,10 +113,23 @@ function sanitizeFilename(name: string): string { return cleaned || 'table' } +/** + * Prefixes a single quote to values starting with a spreadsheet formula trigger + * (`=`, `+`, `-`, `@`, tab, CR), neutralizing CSV injection in Excel/Sheets. + */ +function neutralizeCsvFormula(value: string): string { + return /^[=+\-@\t\r]/.test(value) ? `'${value}` : value +} + +/** + * Serializes a cell for CSV. Only string cells are formula-neutralized; numbers, + * booleans, dates, and JSON objects can never form a trigger and pass through verbatim. + */ function formatCsvValue(value: unknown): string { if (value === null || value === undefined) return '' if (value instanceof Date) return value.toISOString() if (typeof value === 'object') return JSON.stringify(value) + if (typeof value === 'string') return neutralizeCsvFormula(value) return String(value) } diff --git a/apps/sim/app/api/tools/airtable/bases/route.ts b/apps/sim/app/api/tools/airtable/bases/route.ts index b2545df0e84..20b3b3459d7 100644 --- a/apps/sim/app/api/tools/airtable/bases/route.ts +++ b/apps/sim/app/api/tools/airtable/bases/route.ts @@ -11,6 +11,71 @@ const logger = createLogger('AirtableBasesAPI') export const dynamic = 'force-dynamic' +const AIRTABLE_MAX_BASES_PAGES = 50 + +interface AirtableBase { + id: string + name: string +} + +/** + * Lists all Airtable bases, following the `offset` continuation token the Meta + * API returns (an opaque string, passed back verbatim as `?offset=`) so the + * full set is returned. Bounded by `AIRTABLE_MAX_BASES_PAGES`; logs a warning + * rather than silently dropping bases when the cap is hit. + */ +async function fetchAllBases(accessToken: string): Promise { + const bases: AirtableBase[] = [] + let offset: string | undefined + + for (let page = 0; page < AIRTABLE_MAX_BASES_PAGES; page++) { + const url = new URL('https://api.airtable.com/v0/meta/bases') + if (offset) { + url.searchParams.set('offset', offset) + } + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new AirtableFetchError(response.status, errorData) + } + + const data = (await response.json()) as { bases?: AirtableBase[]; offset?: string } + if (Array.isArray(data.bases)) { + bases.push(...data.bases) + } + + offset = data.offset || undefined + if (!offset) { + return bases + } + + if (page === AIRTABLE_MAX_BASES_PAGES - 1) { + logger.warn('Airtable bases listing hit pagination cap; base list may be incomplete', { + pages: AIRTABLE_MAX_BASES_PAGES, + }) + } + } + + return bases +} + +class AirtableFetchError extends Error { + constructor( + readonly status: number, + readonly details: unknown + ) { + super('Failed to fetch Airtable bases') + this.name = 'AirtableFetchError' + } +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -45,27 +110,24 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://api.airtable.com/v0/meta/bases', { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Airtable bases', { - status: response.status, - error: errorData, - }) - return NextResponse.json( - { error: 'Failed to fetch Airtable bases', details: errorData }, - { status: response.status } - ) + let allBases: AirtableBase[] + try { + allBases = await fetchAllBases(accessToken) + } catch (error) { + if (error instanceof AirtableFetchError) { + logger.error('Failed to fetch Airtable bases', { + status: error.status, + error: error.details, + }) + return NextResponse.json( + { error: 'Failed to fetch Airtable bases', details: error.details }, + { status: error.status } + ) + } + throw error } - const data = await response.json() - const bases = (data.bases || []).map((base: { id: string; name: string }) => ({ + const bases = allBases.map((base) => ({ id: base.id, name: base.name, })) diff --git a/apps/sim/app/api/tools/asana/workspaces/route.ts b/apps/sim/app/api/tools/asana/workspaces/route.ts index ee66783722c..0f71376c6df 100644 --- a/apps/sim/app/api/tools/asana/workspaces/route.ts +++ b/apps/sim/app/api/tools/asana/workspaces/route.ts @@ -11,6 +11,81 @@ const logger = createLogger('AsanaWorkspacesAPI') export const dynamic = 'force-dynamic' +const ASANA_PAGE_LIMIT = 100 +const ASANA_MAX_WORKSPACES_PAGES = 50 + +interface AsanaWorkspace { + gid: string + name: string +} + +interface AsanaWorkspacesPage { + data?: AsanaWorkspace[] + next_page?: { + offset?: string + } | null +} + +/** + * Lists all Asana workspaces using `limit`/`offset` pagination, following + * `next_page.offset` (an opaque token, passed back verbatim as `?offset=`) + * until `next_page` is null so the full set is returned. Bounded by + * `ASANA_MAX_WORKSPACES_PAGES`; logs a warning rather than silently dropping + * workspaces when the cap is hit. + */ +async function fetchAllWorkspaces(accessToken: string): Promise { + const workspaces: AsanaWorkspace[] = [] + let offset: string | undefined + + for (let page = 0; page < ASANA_MAX_WORKSPACES_PAGES; page++) { + const url = new URL('https://app.asana.com/api/1.0/workspaces') + url.searchParams.set('limit', String(ASANA_PAGE_LIMIT)) + if (offset) { + url.searchParams.set('offset', offset) + } + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + }, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new AsanaFetchError(response.status, errorData) + } + + const data = (await response.json()) as AsanaWorkspacesPage + if (Array.isArray(data.data)) { + workspaces.push(...data.data) + } + + offset = data.next_page?.offset || undefined + if (!offset) { + return workspaces + } + + if (page === ASANA_MAX_WORKSPACES_PAGES - 1) { + logger.warn('Asana workspaces listing hit pagination cap; workspace list may be incomplete', { + pages: ASANA_MAX_WORKSPACES_PAGES, + }) + } + } + + return workspaces +} + +class AsanaFetchError extends Error { + constructor( + readonly status: number, + readonly details: unknown + ) { + super('Failed to fetch Asana workspaces') + this.name = 'AsanaFetchError' + } +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -42,27 +117,24 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://app.asana.com/api/1.0/workspaces', { - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - }) - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Asana workspaces', { - status: response.status, - error: errorData, - }) - return NextResponse.json( - { error: 'Failed to fetch Asana workspaces', details: errorData }, - { status: response.status } - ) + let allWorkspaces: AsanaWorkspace[] + try { + allWorkspaces = await fetchAllWorkspaces(accessToken) + } catch (error) { + if (error instanceof AsanaFetchError) { + logger.error('Failed to fetch Asana workspaces', { + status: error.status, + error: error.details, + }) + return NextResponse.json( + { error: 'Failed to fetch Asana workspaces', details: error.details }, + { status: error.status } + ) + } + throw error } - const data = await response.json() - const workspaces = (data.data || []).map((workspace: { gid: string; name: string }) => ({ + const workspaces = allWorkspaces.map((workspace) => ({ id: workspace.gid, name: workspace.name, })) diff --git a/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts b/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts index bb0bff43904..4a2aabea1c9 100644 --- a/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts +++ b/apps/sim/app/api/tools/cloudwatch/describe-log-groups/route.ts @@ -10,6 +10,12 @@ import { createCloudWatchLogsClient } from '@/app/api/tools/cloudwatch/utils' const logger = createLogger('CloudWatchDescribeLogGroups') +/** AWS DescribeLogGroups caps `limit` at 50 items per page. */ +const LOG_GROUPS_PAGE_SIZE = 50 + +/** Upper bound on pages drained to avoid unbounded loops on very large accounts. */ +const MAX_LOG_GROUPS_PAGES = 20 + export const POST = withRouteHandler(async (request: NextRequest) => { try { const auth = await checkSessionOrInternalAuth(request) @@ -33,26 +39,58 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }) try { - const command = new DescribeLogGroupsCommand({ - ...(validatedData.prefix && { logGroupNamePrefix: validatedData.prefix }), - ...(validatedData.limit !== undefined && { limit: validatedData.limit }), - }) + const totalLimit = validatedData.limit + const logGroups: { + logGroupName: string + arn: string + storedBytes: number + retentionInDays: number | undefined + creationTime: number | undefined + }[] = [] + let nextToken: string | undefined + + for (let page = 0; page < MAX_LOG_GROUPS_PAGES; page++) { + const pageLimit = + totalLimit !== undefined + ? Math.min(LOG_GROUPS_PAGE_SIZE, totalLimit - logGroups.length) + : LOG_GROUPS_PAGE_SIZE + + const command = new DescribeLogGroupsCommand({ + ...(validatedData.prefix && { logGroupNamePrefix: validatedData.prefix }), + limit: pageLimit, + ...(nextToken && { nextToken }), + }) + + const response = await client.send(command) + + for (const lg of response.logGroups ?? []) { + logGroups.push({ + logGroupName: lg.logGroupName ?? '', + arn: lg.arn ?? '', + storedBytes: lg.storedBytes ?? 0, + retentionInDays: lg.retentionInDays, + creationTime: lg.creationTime, + }) + } + + nextToken = response.nextToken + if (!nextToken) break + if (totalLimit !== undefined && logGroups.length >= totalLimit) break - const response = await client.send(command) + if (page === MAX_LOG_GROUPS_PAGES - 1) { + logger.warn( + `DescribeLogGroups hit pagination cap of ${MAX_LOG_GROUPS_PAGES} pages; log group list may be incomplete` + ) + } + } - const logGroups = (response.logGroups ?? []).map((lg) => ({ - logGroupName: lg.logGroupName ?? '', - arn: lg.arn ?? '', - storedBytes: lg.storedBytes ?? 0, - retentionInDays: lg.retentionInDays, - creationTime: lg.creationTime, - })) + const cappedLogGroups = totalLimit !== undefined ? logGroups.slice(0, totalLimit) : logGroups - logger.info(`Successfully described ${logGroups.length} log groups`) + logger.info(`Successfully described ${cappedLogGroups.length} log groups`) return NextResponse.json({ success: true, - output: { logGroups }, + output: { logGroups: cappedLogGroups }, }) } finally { client.destroy() diff --git a/apps/sim/app/api/tools/cloudwatch/utils.ts b/apps/sim/app/api/tools/cloudwatch/utils.ts index 966aa67b2b4..2984229e75d 100644 --- a/apps/sim/app/api/tools/cloudwatch/utils.ts +++ b/apps/sim/app/api/tools/cloudwatch/utils.ts @@ -5,6 +5,7 @@ import { GetQueryResultsCommand, type ResultField, } from '@aws-sdk/client-cloudwatch-logs' +import { createLogger } from '@sim/logger' import { sleep } from '@sim/utils/helpers' import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits' @@ -96,37 +97,82 @@ export async function pollQueryResults( } } +/** AWS DescribeLogStreams caps `limit` at 50 items per page. */ +const LOG_STREAMS_PAGE_SIZE = 50 + +/** Upper bound on pages drained to avoid unbounded loops on log groups with many streams. */ +const MAX_LOG_STREAMS_PAGES = 20 + +const logger = createLogger('CloudWatchUtils') + +interface DescribedLogStream { + logStreamName: string + lastEventTimestamp: number | undefined + firstEventTimestamp: number | undefined + creationTime: number | undefined + storedBytes: number +} + +/** + * Lists log streams for a log group, following `nextToken` so the complete set + * is returned rather than just the first page. Bounded by + * `MAX_LOG_STREAMS_PAGES`; logs a warning rather than silently dropping streams + * when the cap is hit. Ordering/prefix inputs are preserved across all pages. + * + * When `limit` is provided it is treated as a total result cap: draining stops + * once enough streams have been collected. When omitted, every page is drained. + */ export async function describeLogStreams( client: CloudWatchLogsClient, logGroupName: string, options?: { prefix?: string; limit?: number } -): Promise<{ - logStreams: { - logStreamName: string - lastEventTimestamp: number | undefined - firstEventTimestamp: number | undefined - creationTime: number | undefined - storedBytes: number - }[] -}> { +): Promise<{ logStreams: DescribedLogStream[] }> { const hasPrefix = Boolean(options?.prefix) - const command = new DescribeLogStreamsCommand({ - logGroupName, - ...(hasPrefix - ? { orderBy: 'LogStreamName', logStreamNamePrefix: options!.prefix } - : { orderBy: 'LastEventTime', descending: true }), - ...(options?.limit !== undefined && { limit: options.limit }), - }) + const totalLimit = options?.limit + const logStreams: DescribedLogStream[] = [] + let nextToken: string | undefined + + for (let page = 0; page < MAX_LOG_STREAMS_PAGES; page++) { + const pageLimit = + totalLimit !== undefined + ? Math.min(LOG_STREAMS_PAGE_SIZE, totalLimit - logStreams.length) + : LOG_STREAMS_PAGE_SIZE + + const command = new DescribeLogStreamsCommand({ + logGroupName, + ...(hasPrefix + ? { orderBy: 'LogStreamName', logStreamNamePrefix: options!.prefix } + : { orderBy: 'LastEventTime', descending: true }), + limit: pageLimit, + ...(nextToken && { nextToken }), + }) + + const response = await client.send(command) + + for (const ls of response.logStreams ?? []) { + logStreams.push({ + logStreamName: ls.logStreamName ?? '', + lastEventTimestamp: ls.lastEventTimestamp, + firstEventTimestamp: ls.firstEventTimestamp, + creationTime: ls.creationTime, + storedBytes: ls.storedBytes ?? 0, + }) + } + + nextToken = response.nextToken + if (!nextToken) break + if (totalLimit !== undefined && logStreams.length >= totalLimit) break + + if (page === MAX_LOG_STREAMS_PAGES - 1) { + logger.warn( + `DescribeLogStreams hit pagination cap of ${MAX_LOG_STREAMS_PAGES} pages; log stream list may be incomplete`, + { logGroupName } + ) + } + } - const response = await client.send(command) return { - logStreams: (response.logStreams ?? []).map((ls) => ({ - logStreamName: ls.logStreamName ?? '', - lastEventTimestamp: ls.lastEventTimestamp, - firstEventTimestamp: ls.firstEventTimestamp, - creationTime: ls.creationTime, - storedBytes: ls.storedBytes ?? 0, - })), + logStreams: totalLimit !== undefined ? logStreams.slice(0, totalLimit) : logStreams, } } diff --git a/apps/sim/app/api/tools/drive/files/route.ts b/apps/sim/app/api/tools/drive/files/route.ts index 773fd2ae971..4c38334b14d 100644 --- a/apps/sim/app/api/tools/drive/files/route.ts +++ b/apps/sim/app/api/tools/drive/files/route.ts @@ -7,12 +7,21 @@ import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { drainGooglePagedList, GooglePageError } from '@/lib/oauth/google-pagination' import { getScopesForService } from '@/lib/oauth/utils' import { refreshAccessTokenIfNeeded, ServiceAccountTokenError } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' const logger = createLogger('GoogleDriveFilesAPI') +const MAX_DRIVE_FILE_PAGES = 20 +const DRIVE_FILE_PAGE_SIZE = 100 + +interface DriveFilesResponse { + files?: DriveFile[] + nextPageToken?: string +} + function escapeForDriveQuery(value: string): string { return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'") } @@ -138,34 +147,56 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (query) { qParts.push(`name contains '${escapeForDriveQuery(query)}'`) } - const q = encodeURIComponent(qParts.join(' and ')) - - const response = await fetch( - `https://www.googleapis.com/drive/v3/files?q=${q}&corpora=allDrives&supportsAllDrives=true&includeItemsFromAllDrives=true&fields=files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)`, - { - headers: { - Authorization: `Bearer ${accessToken}`, + const q = qParts.join(' and ') + + let files: DriveFile[] + try { + const drained = await drainGooglePagedList({ + buildUrl: (pageToken) => { + const url = new URL('https://www.googleapis.com/drive/v3/files') + url.searchParams.set('q', q) + url.searchParams.set('corpora', 'allDrives') + url.searchParams.set('supportsAllDrives', 'true') + url.searchParams.set('includeItemsFromAllDrives', 'true') + url.searchParams.set('pageSize', String(DRIVE_FILE_PAGE_SIZE)) + url.searchParams.set( + 'fields', + 'nextPageToken,files(id,name,mimeType,iconLink,webViewLink,thumbnailLink,createdTime,modifiedTime,size,owners,parents)' + ) + if (pageToken) url.searchParams.set('pageToken', pageToken) + return url.toString() }, - } - ) - - if (!response.ok) { - const error = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) - logger.error(`[${requestId}] Google Drive API error`, { - status: response.status, - error: error.error?.message || 'Failed to fetch files from Google Drive', + fetch: (url) => + fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }), + parseError: (response) => + response.json().catch(() => ({ error: { message: 'Unknown error' } })), + getItems: (body) => body.files, + getNextPageToken: (body) => body.nextPageToken, + maxPages: MAX_DRIVE_FILE_PAGES, + label: 'Google Drive files', }) - return NextResponse.json( - { - error: error.error?.message || 'Failed to fetch files from Google Drive', - }, - { status: response.status } - ) + files = drained.items + } catch (error) { + if (error instanceof GooglePageError) { + const errorBody = error.body as { error?: { message?: string } } + logger.error(`[${requestId}] Google Drive API error`, { + status: error.status, + error: errorBody?.error?.message || 'Failed to fetch files from Google Drive', + }) + return NextResponse.json( + { + error: errorBody?.error?.message || 'Failed to fetch files from Google Drive', + }, + { status: error.status } + ) + } + throw error } - const data = await response.json() - let files: DriveFile[] = data.files || [] - if (mimeType === 'application/vnd.google-apps.spreadsheet') { files = files.filter( (file: DriveFile) => file.mimeType === 'application/vnd.google-apps.spreadsheet' diff --git a/apps/sim/app/api/tools/google_bigquery/datasets/route.ts b/apps/sim/app/api/tools/google_bigquery/datasets/route.ts index a4c97d68505..695a371e170 100644 --- a/apps/sim/app/api/tools/google_bigquery/datasets/route.ts +++ b/apps/sim/app/api/tools/google_bigquery/datasets/route.ts @@ -5,6 +5,7 @@ import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { drainGooglePagedList, GooglePageError } from '@/lib/oauth/google-pagination' import { getScopesForService } from '@/lib/oauth/utils' import { refreshAccessTokenIfNeeded, ServiceAccountTokenError } from '@/app/api/auth/oauth/utils' @@ -12,6 +13,19 @@ const logger = createLogger('GoogleBigQueryDatasetsAPI') export const dynamic = 'force-dynamic' +const MAX_DATASET_PAGES = 20 +const DATASET_PAGE_SIZE = 200 + +interface BigQueryDataset { + datasetReference: { datasetId: string; projectId: string } + friendlyName?: string +} + +interface BigQueryDatasetsResponse { + datasets?: BigQueryDataset[] + nextPageToken?: string +} + /** * POST /api/tools/google_bigquery/datasets * @@ -71,41 +85,46 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch( - `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets?maxResults=200`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - } - ) + const { items } = await drainGooglePagedList({ + buildUrl: (pageToken) => { + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets` + ) + url.searchParams.set('maxResults', String(DATASET_PAGE_SIZE)) + if (pageToken) url.searchParams.set('pageToken', pageToken) + return url.toString() + }, + fetch: (url) => + fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }), + parseError: (response) => response.json().catch(() => ({})), + getItems: (body) => body.datasets, + getNextPageToken: (body) => body.nextPageToken, + maxPages: MAX_DATASET_PAGES, + label: 'BigQuery datasets', + }) + + const datasets = items.map((ds) => ({ + datasetReference: ds.datasetReference, + friendlyName: ds.friendlyName, + })) - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) + return NextResponse.json({ datasets }) + } catch (error) { + if (error instanceof GooglePageError) { logger.error('Failed to fetch BigQuery datasets', { - status: response.status, - error: errorData, + status: error.status, + error: error.body, }) return NextResponse.json( - { error: 'Failed to fetch BigQuery datasets', details: errorData }, - { status: response.status } + { error: 'Failed to fetch BigQuery datasets', details: error.body }, + { status: error.status } ) } - - const data = await response.json() - const datasets = (data.datasets || []).map( - (ds: { - datasetReference: { datasetId: string; projectId: string } - friendlyName?: string - }) => ({ - datasetReference: ds.datasetReference, - friendlyName: ds.friendlyName, - }) - ) - - return NextResponse.json({ datasets }) - } catch (error) { if (error instanceof ServiceAccountTokenError) { return NextResponse.json({ error: error.message }, { status: 400 }) } diff --git a/apps/sim/app/api/tools/google_bigquery/tables/route.ts b/apps/sim/app/api/tools/google_bigquery/tables/route.ts index 2f6320a0fe5..af013790595 100644 --- a/apps/sim/app/api/tools/google_bigquery/tables/route.ts +++ b/apps/sim/app/api/tools/google_bigquery/tables/route.ts @@ -5,6 +5,7 @@ import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { drainGooglePagedList, GooglePageError } from '@/lib/oauth/google-pagination' import { getScopesForService } from '@/lib/oauth/utils' import { refreshAccessTokenIfNeeded, ServiceAccountTokenError } from '@/app/api/auth/oauth/utils' @@ -12,6 +13,19 @@ const logger = createLogger('GoogleBigQueryTablesAPI') export const dynamic = 'force-dynamic' +const MAX_TABLE_PAGES = 20 +const TABLE_PAGE_SIZE = 200 + +interface BigQueryTable { + tableReference: { tableId: string } + friendlyName?: string +} + +interface BigQueryTablesResponse { + tables?: BigQueryTable[] + nextPageToken?: string +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -68,38 +82,46 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch( - `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets/${encodeURIComponent(datasetId)}/tables?maxResults=200`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - } - ) + const { items } = await drainGooglePagedList({ + buildUrl: (pageToken) => { + const url = new URL( + `https://bigquery.googleapis.com/bigquery/v2/projects/${encodeURIComponent(projectId)}/datasets/${encodeURIComponent(datasetId)}/tables` + ) + url.searchParams.set('maxResults', String(TABLE_PAGE_SIZE)) + if (pageToken) url.searchParams.set('pageToken', pageToken) + return url.toString() + }, + fetch: (url) => + fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }), + parseError: (response) => response.json().catch(() => ({})), + getItems: (body) => body.tables, + getNextPageToken: (body) => body.nextPageToken, + maxPages: MAX_TABLE_PAGES, + label: 'BigQuery tables', + }) + + const tables = items.map((t) => ({ + tableReference: t.tableReference, + friendlyName: t.friendlyName, + })) - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) + return NextResponse.json({ tables }) + } catch (error) { + if (error instanceof GooglePageError) { logger.error('Failed to fetch BigQuery tables', { - status: response.status, - error: errorData, + status: error.status, + error: error.body, }) return NextResponse.json( - { error: 'Failed to fetch BigQuery tables', details: errorData }, - { status: response.status } + { error: 'Failed to fetch BigQuery tables', details: error.body }, + { status: error.status } ) } - - const data = await response.json() - const tables = (data.tables || []).map( - (t: { tableReference: { tableId: string }; friendlyName?: string }) => ({ - tableReference: t.tableReference, - friendlyName: t.friendlyName, - }) - ) - - return NextResponse.json({ tables }) - } catch (error) { if (error instanceof ServiceAccountTokenError) { return NextResponse.json({ error: error.message }, { status: 400 }) } 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 e1ac55b13ef..752cc72b229 100644 --- a/apps/sim/app/api/tools/google_calendar/calendars/route.ts +++ b/apps/sim/app/api/tools/google_calendar/calendars/route.ts @@ -5,12 +5,16 @@ import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { drainGooglePagedList, GooglePageError } from '@/lib/oauth/google-pagination' import { getScopesForService } from '@/lib/oauth/utils' import { refreshAccessTokenIfNeeded, ServiceAccountTokenError } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' const logger = createLogger('GoogleCalendarAPI') +const MAX_CALENDAR_PAGES = 20 +const CALENDAR_PAGE_SIZE = 250 + interface CalendarListItem { id: string summary: string @@ -21,6 +25,11 @@ interface CalendarListItem { foregroundColor?: string } +interface CalendarListResponse { + items?: CalendarListItem[] + nextPageToken?: string +} + /** * Get calendars from Google Calendar */ @@ -64,35 +73,50 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } logger.info(`[${requestId}] Fetching calendars from Google Calendar API`) - const calendarResponse = await fetch( - 'https://www.googleapis.com/calendar/v3/users/me/calendarList', - { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - } - ) - if (!calendarResponse.ok) { - const errorData = await calendarResponse - .text() - .then((text) => JSON.parse(text)) - .catch(() => ({ error: { message: 'Unknown error' } })) - logger.error(`[${requestId}] Google Calendar API error`, { - status: calendarResponse.status, - error: errorData.error?.message || 'Failed to fetch calendars', + let calendars: CalendarListItem[] + try { + const drained = await drainGooglePagedList({ + buildUrl: (pageToken) => { + const url = new URL('https://www.googleapis.com/calendar/v3/users/me/calendarList') + url.searchParams.set('maxResults', String(CALENDAR_PAGE_SIZE)) + if (pageToken) url.searchParams.set('pageToken', pageToken) + return url.toString() + }, + fetch: (url) => + fetch(url, { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }), + parseError: (response) => + response + .text() + .then((text) => JSON.parse(text)) + .catch(() => ({ error: { message: 'Unknown error' } })), + getItems: (body) => body.items, + getNextPageToken: (body) => body.nextPageToken, + maxPages: MAX_CALENDAR_PAGES, + label: 'Google Calendar calendars', }) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch calendars' }, - { status: calendarResponse.status } - ) + calendars = drained.items + } catch (error) { + if (error instanceof GooglePageError) { + const errorData = error.body as { error?: { message?: string } } + logger.error(`[${requestId}] Google Calendar API error`, { + status: error.status, + error: errorData?.error?.message || 'Failed to fetch calendars', + }) + return NextResponse.json( + { error: errorData?.error?.message || 'Failed to fetch calendars' }, + { status: error.status } + ) + } + throw error } - const data = await calendarResponse.json() - const calendars: CalendarListItem[] = data.items || [] - calendars.sort((a, b) => { if (a.primary && !b.primary) return -1 if (!a.primary && b.primary) return 1 diff --git a/apps/sim/app/api/tools/google_tasks/task-lists/route.ts b/apps/sim/app/api/tools/google_tasks/task-lists/route.ts index c5ee97bb234..80c5a99f598 100644 --- a/apps/sim/app/api/tools/google_tasks/task-lists/route.ts +++ b/apps/sim/app/api/tools/google_tasks/task-lists/route.ts @@ -5,6 +5,7 @@ import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { drainGooglePagedList, GooglePageError } from '@/lib/oauth/google-pagination' import { getScopesForService } from '@/lib/oauth/utils' import { refreshAccessTokenIfNeeded, ServiceAccountTokenError } from '@/app/api/auth/oauth/utils' @@ -12,6 +13,19 @@ const logger = createLogger('GoogleTasksTaskListsAPI') export const dynamic = 'force-dynamic' +const MAX_TASK_LIST_PAGES = 20 +const TASK_LIST_PAGE_SIZE = 1000 + +interface GoogleTaskList { + id: string + title: string +} + +interface GoogleTaskListsResponse { + items?: GoogleTaskList[] + nextPageToken?: string +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -56,33 +70,44 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://tasks.googleapis.com/tasks/v1/users/@me/lists', { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', + const { items } = await drainGooglePagedList({ + buildUrl: (pageToken) => { + const url = new URL('https://tasks.googleapis.com/tasks/v1/users/@me/lists') + url.searchParams.set('maxResults', String(TASK_LIST_PAGE_SIZE)) + if (pageToken) url.searchParams.set('pageToken', pageToken) + return url.toString() }, + fetch: (url) => + fetch(url, { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }), + parseError: (response) => response.json().catch(() => ({})), + getItems: (body) => body.items, + getNextPageToken: (body) => body.nextPageToken, + maxPages: MAX_TASK_LIST_PAGES, + label: 'Google Tasks task lists', }) - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Google Tasks task lists', { - status: response.status, - error: errorData, - }) - return NextResponse.json( - { error: 'Failed to fetch Google Tasks task lists', details: errorData }, - { status: response.status } - ) - } - - const data = await response.json() - const taskLists = (data.items || []).map((list: { id: string; title: string }) => ({ + const taskLists = items.map((list) => ({ id: list.id, title: list.title, })) return NextResponse.json({ taskLists }) } catch (error) { + if (error instanceof GooglePageError) { + logger.error('Failed to fetch Google Tasks task lists', { + status: error.status, + error: error.body, + }) + return NextResponse.json( + { error: 'Failed to fetch Google Tasks task lists', details: error.body }, + { status: error.status } + ) + } if (error instanceof ServiceAccountTokenError) { return NextResponse.json({ error: error.message }, { status: 400 }) } diff --git a/apps/sim/app/api/tools/jira/projects/route.ts b/apps/sim/app/api/tools/jira/projects/route.ts index 2c99f2e447a..2ee1244cb2f 100644 --- a/apps/sim/app/api/tools/jira/projects/route.ts +++ b/apps/sim/app/api/tools/jira/projects/route.ts @@ -14,6 +14,77 @@ export const dynamic = 'force-dynamic' const logger = createLogger('JiraProjectsAPI') +const JIRA_PROJECTS_PAGE_SIZE = 50 +const MAX_JIRA_PROJECTS_PAGES = 40 + +interface JiraProjectSearchPage { + values?: unknown[] + isLast?: boolean + maxResults?: number +} + +/** + * Drains the offset-paginated Jira `/project/search` endpoint, advancing + * `startAt` by the server-returned page size until `isLast === true` (or a short + * page is seen). Bounded by `MAX_JIRA_PROJECTS_PAGES`; emits a `logger.warn` and + * returns the partial set rather than looping unbounded when the cap is hit. + */ +async function fetchAllJiraProjects( + apiUrl: string, + baseParams: URLSearchParams, + accessToken: string +): Promise<{ values: unknown[]; lastResponse: Response }> { + const values: unknown[] = [] + let startAt = 0 + let lastResponse: Response + + for (let page = 0; page < MAX_JIRA_PROJECTS_PAGES; page++) { + const params = new URLSearchParams(baseParams) + params.set('startAt', String(startAt)) + params.set('maxResults', String(JIRA_PROJECTS_PAGE_SIZE)) + + const finalUrl = `${apiUrl}?${params.toString()}` + logger.info(`Fetching Jira projects from: ${finalUrl}`) + + const response = await fetch(finalUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + }, + }) + + logger.info(`Response status: ${response.status} ${response.statusText}`) + + if (!response.ok) { + return { values, lastResponse: response } + } + + const data = (await response.json()) as JiraProjectSearchPage + lastResponse = response + + const pageValues = data.values ?? [] + values.push(...pageValues) + + const pageSize = + data.maxResults && data.maxResults > 0 ? data.maxResults : JIRA_PROJECTS_PAGE_SIZE + if (data.isLast === true || pageValues.length < pageSize) { + return { values, lastResponse } + } + + startAt += pageValues.length + + if (page === MAX_JIRA_PROJECTS_PAGES - 1) { + logger.warn('Jira project search hit pagination cap; project list may be incomplete', { + pages: MAX_JIRA_PROJECTS_PAGES, + collected: values.length, + }) + } + } + + return { values, lastResponse: lastResponse! } +} + export const GET = withRouteHandler(async (request: NextRequest) => { try { const auth = await checkSessionOrInternalAuth(request) @@ -51,35 +122,28 @@ export const GET = withRouteHandler(async (request: NextRequest) => { queryParams.append('orderBy', 'name') queryParams.append('expand', 'description,lead,url,projectKeys') - const finalUrl = `${apiUrl}?${queryParams.toString()}` - logger.info(`Fetching Jira projects from: ${finalUrl}`) - - const response = await fetch(finalUrl, { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - }) - - logger.info(`Response status: ${response.status} ${response.statusText}`) + const { values, lastResponse } = await fetchAllJiraProjects(apiUrl, queryParams, accessToken) - if (!response.ok) { - const errorText = await response.text() - logger.error('Jira API error:', { status: response.status, error: errorText }) + if (!lastResponse.ok) { + const errorText = await lastResponse.text() + logger.error('Jira API error:', { status: lastResponse.status, error: errorText }) return NextResponse.json( - { error: parseAtlassianErrorMessage(response.status, response.statusText, errorText) }, - { status: response.status } + { + error: parseAtlassianErrorMessage( + lastResponse.status, + lastResponse.statusText, + errorText + ), + }, + { status: lastResponse.status } ) } - const data = await response.json() - - logger.info(`Jira API Response Status: ${response.status}`) - logger.info(`Found projects: ${data.values?.length || 0}`) + logger.info(`Jira API Response Status: ${lastResponse.status}`) + logger.info(`Found projects: ${values.length}`) const projects = - data.values?.map((project: any) => ({ + values.map((project: any) => ({ id: project.id, key: project.key, name: project.name, diff --git a/apps/sim/app/api/tools/jsm/selector-requesttypes/route.ts b/apps/sim/app/api/tools/jsm/selector-requesttypes/route.ts index 01c1682e1e0..b23b4b7c7a7 100644 --- a/apps/sim/app/api/tools/jsm/selector-requesttypes/route.ts +++ b/apps/sim/app/api/tools/jsm/selector-requesttypes/route.ts @@ -14,6 +14,72 @@ const logger = createLogger('JsmSelectorRequestTypesAPI') export const dynamic = 'force-dynamic' +const JSM_REQUEST_TYPES_PAGE_SIZE = 100 +const MAX_JSM_REQUEST_TYPES_PAGES = 50 + +interface JsmPagedResponse { + values?: T[] + isLastPage?: boolean + _links?: { next?: string } +} + +interface JsmRequestTypeValue { + id: string + name: string +} + +/** + * Drains the offset-paginated JSM `/servicedesk/{id}/requesttype` endpoint, + * advancing `start` by the number of rows actually returned until + * `isLastPage === true` (or `_links.next` is absent, or a page comes back + * empty). Advancing by the real row count — not the requested `limit` — + * prevents skipping items if the server returns a short non-final page. Bounded + * by `MAX_JSM_REQUEST_TYPES_PAGES`; emits a `logger.warn` and returns the + * partial set rather than looping unbounded when the cap is hit. + */ +async function fetchAllJsmRequestTypes( + requestTypeUrl: string, + accessToken: string +): Promise<{ values: JsmRequestTypeValue[]; lastResponse: Response }> { + const values: JsmRequestTypeValue[] = [] + let start = 0 + let lastResponse: Response + + for (let page = 0; page < MAX_JSM_REQUEST_TYPES_PAGES; page++) { + const url = `${requestTypeUrl}?start=${start}&limit=${JSM_REQUEST_TYPES_PAGE_SIZE}` + + const response = await fetch(url, { + method: 'GET', + headers: getJsmHeaders(accessToken), + }) + + if (!response.ok) { + return { values, lastResponse: response } + } + + const data = (await response.json()) as JsmPagedResponse + lastResponse = response + + const pageValues = data.values ?? [] + values.push(...pageValues) + + if (data.isLastPage === true || !data._links?.next || pageValues.length === 0) { + return { values, lastResponse } + } + + start += pageValues.length + + if (page === MAX_JSM_REQUEST_TYPES_PAGES - 1) { + logger.warn('JSM request type list hit pagination cap; list may be incomplete', { + pages: MAX_JSM_REQUEST_TYPES_PAGES, + collected: values.length, + }) + } + } + + return { values, lastResponse: lastResponse! } +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -72,28 +138,30 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } const baseUrl = getJsmApiBaseUrl(cloudIdValidation.sanitized!) - const url = `${baseUrl}/servicedesk/${serviceDeskIdValidation.sanitized}/requesttype?limit=100` + const requestTypeUrl = `${baseUrl}/servicedesk/${serviceDeskIdValidation.sanitized}/requesttype` - const response = await fetch(url, { - method: 'GET', - headers: getJsmHeaders(accessToken), - }) + const { values, lastResponse } = await fetchAllJsmRequestTypes(requestTypeUrl, accessToken) - if (!response.ok) { - const errorText = await response.text() + if (!lastResponse.ok) { + const errorText = await lastResponse.text() logger.error('JSM API error:', { - status: response.status, - statusText: response.statusText, + status: lastResponse.status, + statusText: lastResponse.statusText, error: errorText, }) return NextResponse.json( - { error: parseAtlassianErrorMessage(response.status, response.statusText, errorText) }, - { status: response.status } + { + error: parseAtlassianErrorMessage( + lastResponse.status, + lastResponse.statusText, + errorText + ), + }, + { status: lastResponse.status } ) } - const data = await response.json() - const requestTypes = (data.values || []).map((rt: { id: string; name: string }) => ({ + const requestTypes = values.map((rt) => ({ id: rt.id, name: rt.name, })) diff --git a/apps/sim/app/api/tools/jsm/selector-servicedesks/route.ts b/apps/sim/app/api/tools/jsm/selector-servicedesks/route.ts index c1efdb0f93e..786483630dd 100644 --- a/apps/sim/app/api/tools/jsm/selector-servicedesks/route.ts +++ b/apps/sim/app/api/tools/jsm/selector-servicedesks/route.ts @@ -14,6 +14,72 @@ const logger = createLogger('JsmSelectorServiceDesksAPI') export const dynamic = 'force-dynamic' +const JSM_SERVICE_DESKS_PAGE_SIZE = 100 +const MAX_JSM_SERVICE_DESKS_PAGES = 50 + +interface JsmPagedResponse { + values?: T[] + isLastPage?: boolean + _links?: { next?: string } +} + +interface JsmServiceDeskValue { + id: string + projectName: string +} + +/** + * Drains the offset-paginated JSM `/servicedesk` endpoint, advancing `start` by + * the number of rows actually returned until `isLastPage === true` (or + * `_links.next` is absent, or a page comes back empty). Advancing by the real + * row count — not the requested `limit` — prevents skipping items if the server + * returns a short non-final page. Bounded by `MAX_JSM_SERVICE_DESKS_PAGES`; + * emits a `logger.warn` and returns the partial set rather than looping + * unbounded when the cap is hit. + */ +async function fetchAllJsmServiceDesks( + baseUrl: string, + accessToken: string +): Promise<{ values: JsmServiceDeskValue[]; lastResponse: Response }> { + const values: JsmServiceDeskValue[] = [] + let start = 0 + let lastResponse: Response + + for (let page = 0; page < MAX_JSM_SERVICE_DESKS_PAGES; page++) { + const url = `${baseUrl}/servicedesk?start=${start}&limit=${JSM_SERVICE_DESKS_PAGE_SIZE}` + + const response = await fetch(url, { + method: 'GET', + headers: getJsmHeaders(accessToken), + }) + + if (!response.ok) { + return { values, lastResponse: response } + } + + const data = (await response.json()) as JsmPagedResponse + lastResponse = response + + const pageValues = data.values ?? [] + values.push(...pageValues) + + if (data.isLastPage === true || !data._links?.next || pageValues.length === 0) { + return { values, lastResponse } + } + + start += pageValues.length + + if (page === MAX_JSM_SERVICE_DESKS_PAGES - 1) { + logger.warn('JSM service desk list hit pagination cap; list may be incomplete', { + pages: MAX_JSM_SERVICE_DESKS_PAGES, + collected: values.length, + }) + } + } + + return { values, lastResponse: lastResponse! } +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -63,28 +129,29 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } const baseUrl = getJsmApiBaseUrl(cloudIdValidation.sanitized!) - const url = `${baseUrl}/servicedesk?limit=100` - const response = await fetch(url, { - method: 'GET', - headers: getJsmHeaders(accessToken), - }) + const { values, lastResponse } = await fetchAllJsmServiceDesks(baseUrl, accessToken) - if (!response.ok) { - const errorText = await response.text() + if (!lastResponse.ok) { + const errorText = await lastResponse.text() logger.error('JSM API error:', { - status: response.status, - statusText: response.statusText, + status: lastResponse.status, + statusText: lastResponse.statusText, error: errorText, }) return NextResponse.json( - { error: parseAtlassianErrorMessage(response.status, response.statusText, errorText) }, - { status: response.status } + { + error: parseAtlassianErrorMessage( + lastResponse.status, + lastResponse.statusText, + errorText + ), + }, + { status: lastResponse.status } ) } - const data = await response.json() - const serviceDesks = (data.values || []).map((sd: { id: string; projectName: string }) => ({ + const serviceDesks = values.map((sd) => ({ id: sd.id, name: sd.projectName, })) diff --git a/apps/sim/app/api/tools/linear/projects/route.ts b/apps/sim/app/api/tools/linear/projects/route.ts index e7ca9bab1a7..9b0b500e0db 100644 --- a/apps/sim/app/api/tools/linear/projects/route.ts +++ b/apps/sim/app/api/tools/linear/projects/route.ts @@ -1,4 +1,4 @@ -import type { Project } from '@linear/sdk' +import type { Project, Team } from '@linear/sdk' import { LinearClient } from '@linear/sdk' import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' @@ -13,6 +13,50 @@ export const dynamic = 'force-dynamic' const logger = createLogger('LinearProjectsAPI') +/** Linear's maximum page size for a single connection request. */ +const LINEAR_PAGE_SIZE = 250 + +/** + * Upper bound on pages to drain from a single team's projects connection. At + * 250 projects/page this covers 2,500 projects per team; the cap guards + * against runaway loops on a broken `hasNextPage` rather than a realistic + * limit. + */ +const MAX_PROJECTS_PAGES = 10 + +/** + * Drains a single team's projects connection by following + * `pageInfo.endCursor` until `hasNextPage` is false. Bounded by + * `MAX_PROJECTS_PAGES`; logs a warning if the cap is hit so a truncated list + * is visible rather than silently dropped. + */ +async function fetchAllTeamProjects(team: Team): Promise { + const projects: Project[] = [] + let after: string | undefined + + for (let page = 0; page < MAX_PROJECTS_PAGES; page++) { + const result = await team.projects({ first: LINEAR_PAGE_SIZE, after }) + projects.push(...result.nodes) + + if (!result.pageInfo.hasNextPage) { + return projects + } + after = result.pageInfo.endCursor ?? undefined + if (!after) { + return projects + } + if (page === MAX_PROJECTS_PAGES - 1) { + logger.warn('Linear projects pagination hit cap; project list may be incomplete', { + teamId: team.id, + cap: MAX_PROJECTS_PAGES, + fetched: projects.length, + }) + } + } + + return projects +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const parsed = await parseRequest(linearProjectsSelectorContract, request, {}) @@ -59,8 +103,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const perTeam = await Promise.all( teamIds.map(async (id) => { const team = await linearClient.team(id) - const result = await team.projects() - return result.nodes.map((project: Project) => ({ + const teamProjects = await fetchAllTeamProjects(team) + return teamProjects.map((project: Project) => ({ id: project.id, name: project.name, })) diff --git a/apps/sim/app/api/tools/linear/teams/route.ts b/apps/sim/app/api/tools/linear/teams/route.ts index 89b02a6e24a..f71adec6b0a 100644 --- a/apps/sim/app/api/tools/linear/teams/route.ts +++ b/apps/sim/app/api/tools/linear/teams/route.ts @@ -13,6 +13,48 @@ export const dynamic = 'force-dynamic' const logger = createLogger('LinearTeamsAPI') +/** Linear's maximum page size for a single connection request. */ +const LINEAR_PAGE_SIZE = 250 + +/** + * Upper bound on pages to drain from the teams connection. At 250 teams/page + * this covers 2,500 teams; the cap guards against runaway loops on a broken + * `hasNextPage` rather than a realistic limit. + */ +const MAX_TEAMS_PAGES = 10 + +/** + * Drains the full Linear teams connection by following + * `pageInfo.endCursor` until `hasNextPage` is false. Bounded by + * `MAX_TEAMS_PAGES`; logs a warning if the cap is hit so a truncated list is + * visible rather than silently dropped. + */ +async function fetchAllTeams(linearClient: LinearClient): Promise { + const teams: Team[] = [] + let after: string | undefined + + for (let page = 0; page < MAX_TEAMS_PAGES; page++) { + const result = await linearClient.teams({ first: LINEAR_PAGE_SIZE, after }) + teams.push(...result.nodes) + + if (!result.pageInfo.hasNextPage) { + return teams + } + after = result.pageInfo.endCursor ?? undefined + if (!after) { + return teams + } + if (page === MAX_TEAMS_PAGES - 1) { + logger.warn('Linear teams pagination hit cap; team list may be incomplete', { + cap: MAX_TEAMS_PAGES, + fetched: teams.length, + }) + } + } + + return teams +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const requestId = generateRequestId() @@ -45,8 +87,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } const linearClient = new LinearClient({ accessToken }) - const teamsResult = await linearClient.teams() - const teams = teamsResult.nodes.map((team: Team) => ({ + const allTeams = await fetchAllTeams(linearClient) + const teams = allTeams.map((team: Team) => ({ id: team.id, name: team.name, })) diff --git a/apps/sim/app/api/tools/microsoft-teams/channels/route.ts b/apps/sim/app/api/tools/microsoft-teams/channels/route.ts index 6570d5fdd42..c8bd6ddcb57 100644 --- a/apps/sim/app/api/tools/microsoft-teams/channels/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/channels/route.ts @@ -7,11 +7,25 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('TeamsChannelsAPI') +/** + * Upper bound on Microsoft Graph pages drained when listing a team's channels. + * The `teams/{id}/channels` endpoint does not support `$top`, so paging is + * driven entirely by the server via `@odata.nextLink`. The cap prevents an + * unbounded loop; hitting it is logged as a warning. + */ +const MAX_CHANNELS_PAGES = 20 + +interface GraphChannel { + id: string + displayName?: string +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const parsed = await parseRequest(microsoftChannelsSelectorContract, request, {}) @@ -52,41 +66,60 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch( - `https://graph.microsoft.com/v1.0/teams/${encodeURIComponent(teamId)}/channels`, - { + const channels: GraphChannel[] = [] + let nextPageUrl: string | undefined = + `https://graph.microsoft.com/v1.0/teams/${encodeURIComponent(teamId)}/channels` + + for (let page = 0; page < MAX_CHANNELS_PAGES; page++) { + const response = await fetch(nextPageUrl, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, + }) + + if (!response.ok) { + const errorData = await response.json() + logger.error('Microsoft Graph API error getting channels', { + status: response.status, + error: errorData, + endpoint: nextPageUrl, + }) + + if (response.status === 401) { + return NextResponse.json( + { + error: 'Authentication failed. Please reconnect your Microsoft Teams account.', + authRequired: true, + }, + { status: 401 } + ) + } + + throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) } - ) - if (!response.ok) { - const errorData = await response.json() - logger.error('Microsoft Graph API error getting channels', { - status: response.status, - error: errorData, - endpoint: `https://graph.microsoft.com/v1.0/teams/${teamId}/channels`, - }) + const data = await response.json() + if (Array.isArray(data.value)) { + channels.push(...(data.value as GraphChannel[])) + } - if (response.status === 401) { - return NextResponse.json( - { - error: 'Authentication failed. Please reconnect your Microsoft Teams account.', - authRequired: true, - }, - { status: 401 } - ) + const rawNextLink = getGraphNextPageUrl(data) + if (!rawNextLink) { + nextPageUrl = undefined + break } + nextPageUrl = assertGraphNextPageUrl(rawNextLink) - throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) + if (page === MAX_CHANNELS_PAGES - 1) { + logger.warn( + 'Hit Microsoft Graph channels pagination cap; channel list may be incomplete', + { maxPages: MAX_CHANNELS_PAGES, collected: channels.length } + ) + } } - const data = await response.json() - const channels = data.value - return NextResponse.json({ channels: channels, }) 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 832c7200141..d709bcd62e6 100644 --- a/apps/sim/app/api/tools/microsoft-teams/chats/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/chats/route.ts @@ -7,11 +7,29 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('TeamsChatsAPI') +/** + * Largest page size the `me/chats` Microsoft Graph endpoint permits via `$top`. + */ +const CHATS_PAGE_SIZE = 50 + +/** + * Upper bound on Microsoft Graph pages drained when listing the user's chats. + * Paging follows `@odata.nextLink`. The cap prevents an unbounded loop; hitting + * it is logged as a warning. + */ +const MAX_CHATS_PAGES = 20 + +interface GraphChat { + id: string + topic?: string +} + /** * Helper function to get chat members and create a meaningful name * @@ -153,39 +171,62 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: 'Could not retrieve access token' }, { status: 401 }) } - const response = await fetch('https://graph.microsoft.com/v1.0/me/chats', { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) + const rawChats: GraphChat[] = [] + let nextPageUrl: string | undefined = + `https://graph.microsoft.com/v1.0/me/chats?$top=${CHATS_PAGE_SIZE}` - if (!response.ok) { - const errorData = await response.json() - logger.error('Microsoft Graph API error getting chats', { - status: response.status, - error: errorData, - endpoint: 'https://graph.microsoft.com/v1.0/me/chats', + for (let page = 0; page < MAX_CHATS_PAGES; page++) { + const response = await fetch(nextPageUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, }) - if (response.status === 401) { - return NextResponse.json( - { - error: 'Authentication failed. Please reconnect your Microsoft Teams account.', - authRequired: true, - }, - { status: 401 } - ) + if (!response.ok) { + const errorData = await response.json() + logger.error('Microsoft Graph API error getting chats', { + status: response.status, + error: errorData, + endpoint: nextPageUrl, + }) + + if (response.status === 401) { + return NextResponse.json( + { + error: 'Authentication failed. Please reconnect your Microsoft Teams account.', + authRequired: true, + }, + { status: 401 } + ) + } + + throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) } - throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) - } + const data = await response.json() + if (Array.isArray(data.value)) { + rawChats.push(...(data.value as GraphChat[])) + } - const data = await response.json() + const rawNextLink = getGraphNextPageUrl(data) + if (!rawNextLink) { + nextPageUrl = undefined + break + } + nextPageUrl = assertGraphNextPageUrl(rawNextLink) + + if (page === MAX_CHATS_PAGES - 1) { + logger.warn('Hit Microsoft Graph chats pagination cap; chat list may be incomplete', { + maxPages: MAX_CHATS_PAGES, + collected: rawChats.length, + }) + } + } const chats = await Promise.all( - data.value.map(async (chat: any) => ({ + rawChats.map(async (chat) => ({ id: chat.id, displayName: await getChatDisplayName(chat.id, accessToken, chat.topic), })) 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 44c1d997060..990bfd282d2 100644 --- a/apps/sim/app/api/tools/microsoft-teams/teams/route.ts +++ b/apps/sim/app/api/tools/microsoft-teams/teams/route.ts @@ -6,11 +6,25 @@ import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('TeamsTeamsAPI') +/** + * Upper bound on Microsoft Graph pages drained when listing the user's joined + * teams. The `me/joinedTeams` endpoint does not support `$top`, so paging is + * driven entirely by the server via `@odata.nextLink`. The cap prevents an + * unbounded loop; hitting it is logged as a warning. + */ +const MAX_TEAMS_PAGES = 20 + +interface GraphTeam { + id: string + displayName?: string +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const parsed = await parseRequest(microsoftTeamsSelectorContract, request, {}) @@ -46,38 +60,59 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://graph.microsoft.com/v1.0/me/joinedTeams', { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) + const teams: GraphTeam[] = [] + let nextPageUrl: string | undefined = 'https://graph.microsoft.com/v1.0/me/joinedTeams' - if (!response.ok) { - const errorData = await response.json() - logger.error('Microsoft Graph API error getting teams', { - status: response.status, - error: errorData, - endpoint: 'https://graph.microsoft.com/v1.0/me/joinedTeams', + for (let page = 0; page < MAX_TEAMS_PAGES; page++) { + const response = await fetch(nextPageUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, }) - // Check for auth errors specifically - if (response.status === 401) { - return NextResponse.json( - { - error: 'Authentication failed. Please reconnect your Microsoft Teams account.', - authRequired: true, - }, - { status: 401 } - ) + if (!response.ok) { + const errorData = await response.json() + logger.error('Microsoft Graph API error getting teams', { + status: response.status, + error: errorData, + endpoint: nextPageUrl, + }) + + // Check for auth errors specifically + if (response.status === 401) { + return NextResponse.json( + { + error: 'Authentication failed. Please reconnect your Microsoft Teams account.', + authRequired: true, + }, + { status: 401 } + ) + } + + throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) } - throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) - } + const data = await response.json() + if (Array.isArray(data.value)) { + teams.push(...(data.value as GraphTeam[])) + } + + const rawNextLink = getGraphNextPageUrl(data) + if (!rawNextLink) { + nextPageUrl = undefined + break + } + nextPageUrl = assertGraphNextPageUrl(rawNextLink) - const data = await response.json() - const teams = data.value + if (page === MAX_TEAMS_PAGES - 1) { + logger.warn('Hit Microsoft Graph teams pagination cap; team list may be incomplete', { + maxPages: MAX_TEAMS_PAGES, + collected: teams.length, + }) + } + } return NextResponse.json({ teams: teams, diff --git a/apps/sim/app/api/tools/microsoft_excel/drives/route.ts b/apps/sim/app/api/tools/microsoft_excel/drives/route.ts index 2e0d1d80e43..97d921a5ea4 100644 --- a/apps/sim/app/api/tools/microsoft_excel/drives/route.ts +++ b/apps/sim/app/api/tools/microsoft_excel/drives/route.ts @@ -8,11 +8,19 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import { extractGraphError, GRAPH_ID_PATTERN } from '@/tools/microsoft_excel/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('MicrosoftExcelDrivesAPI') +/** + * Upper bound on Microsoft Graph pages drained when listing site drives. + * Each page returns up to `$top=999` drives, so this caps the result set at + * roughly 10k drives while preventing an unbounded server-side loop. + */ +const MAX_DRIVES_PAGES = 10 + interface GraphDrive { id: string name: string @@ -88,25 +96,41 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } // List all drives for the site - const url = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=id,name,driveType,webUrl` + let nextUrl: string | undefined = + `https://graph.microsoft.com/v1.0/sites/${siteId}/drives?$select=id,name,driveType,webUrl&$top=999` + + const rawDrives: GraphDrive[] = [] + for (let page = 0; page < MAX_DRIVES_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + if (!response.ok) { + const errorMessage = await extractGraphError(response) + logger.error(`[${requestId}] Microsoft Graph API error fetching drives`, { + status: response.status, + error: errorMessage, + }) + return NextResponse.json({ error: errorMessage }, { status: response.status }) + } - if (!response.ok) { - const errorMessage = await extractGraphError(response) - logger.error(`[${requestId}] Microsoft Graph API error fetching drives`, { - status: response.status, - error: errorMessage, - }) - return NextResponse.json({ error: errorMessage }, { status: response.status }) + const data = await response.json() + if (Array.isArray(data.value)) { + rawDrives.push(...data.value) + } + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + if (nextUrl && page === MAX_DRIVES_PAGES - 1) { + logger.warn( + `[${requestId}] Site drives pagination hit ${MAX_DRIVES_PAGES}-page cap; result may be incomplete` + ) + } } - const data = await response.json() - const drives = (data.value || []).map((drive: GraphDrive) => ({ + const drives = rawDrives.map((drive: GraphDrive) => ({ id: drive.id, name: drive.name, driveType: drive.driveType, diff --git a/apps/sim/app/api/tools/microsoft_planner/plans/route.ts b/apps/sim/app/api/tools/microsoft_planner/plans/route.ts index a710f845251..604f7c85b34 100644 --- a/apps/sim/app/api/tools/microsoft_planner/plans/route.ts +++ b/apps/sim/app/api/tools/microsoft_planner/plans/route.ts @@ -6,11 +6,19 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' const logger = createLogger('MicrosoftPlannerPlansAPI') export const dynamic = 'force-dynamic' +/** + * Upper bound on Microsoft Graph pages drained when listing Planner plans. + * Planner uses server-side paging (`$top` is generally ignored), so this caps + * the `@odata.nextLink` follow loop to prevent an unbounded drain. + */ +const MAX_PLANS_PAGES = 20 + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() @@ -40,25 +48,40 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://graph.microsoft.com/v1.0/me/planner/plans', { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + let nextUrl: string | undefined = 'https://graph.microsoft.com/v1.0/me/planner/plans' - if (!response.ok) { - const errorText = await response.text() - logger.error(`[${requestId}] Microsoft Graph API error:`, errorText) - return NextResponse.json( - { error: 'Failed to fetch plans from Microsoft Graph' }, - { status: response.status } - ) - } + const rawPlans: { id: string; title: string }[] = [] + for (let page = 0; page < MAX_PLANS_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + + if (!response.ok) { + const errorText = await response.text() + logger.error(`[${requestId}] Microsoft Graph API error:`, errorText) + return NextResponse.json( + { error: 'Failed to fetch plans from Microsoft Graph' }, + { status: response.status } + ) + } - const data = await response.json() - const plans = data.value || [] + const data = await response.json() + if (Array.isArray(data.value)) { + rawPlans.push(...data.value) + } + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + if (nextUrl && page === MAX_PLANS_PAGES - 1) { + logger.warn( + `[${requestId}] Planner plans pagination hit ${MAX_PLANS_PAGES}-page cap; result may be incomplete` + ) + } + } - const filteredPlans = plans.map((plan: { id: string; title: string }) => ({ + const filteredPlans = rawPlans.map((plan: { id: string; title: string }) => ({ id: plan.id, title: plan.title, })) diff --git a/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts b/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts index 94bf43e8322..b9b764089bd 100644 --- a/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts +++ b/apps/sim/app/api/tools/microsoft_planner/tasks/route.ts @@ -8,11 +8,19 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { PlannerTask } from '@/tools/microsoft_planner/types' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' const logger = createLogger('MicrosoftPlannerTasksAPI') export const dynamic = 'force-dynamic' +/** + * Upper bound on Microsoft Graph pages drained when listing a plan's tasks. + * Planner uses server-side paging (`$top` is generally ignored), so this caps + * the `@odata.nextLink` follow loop to prevent an unbounded drain. + */ +const MAX_TASKS_PAGES = 20 + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() @@ -48,28 +56,41 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch( - `https://graph.microsoft.com/v1.0/planner/plans/${planIdValidation.sanitized}/tasks`, - { + let nextUrl: string | undefined = + `https://graph.microsoft.com/v1.0/planner/plans/${planIdValidation.sanitized}/tasks` + + const rawTasks: PlannerTask[] = [] + for (let page = 0; page < MAX_TASKS_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { headers: { Authorization: `Bearer ${accessToken}`, }, + }) + + if (!response.ok) { + const errorText = await response.text() + logger.error(`[${requestId}] Microsoft Graph API error:`, errorText) + return NextResponse.json( + { error: 'Failed to fetch tasks from Microsoft Graph' }, + { status: response.status } + ) } - ) - if (!response.ok) { - const errorText = await response.text() - logger.error(`[${requestId}] Microsoft Graph API error:`, errorText) - return NextResponse.json( - { error: 'Failed to fetch tasks from Microsoft Graph' }, - { status: response.status } - ) - } + const data = await response.json() + if (Array.isArray(data.value)) { + rawTasks.push(...data.value) + } - const data = await response.json() - const tasks = data.value || [] + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + if (nextUrl && page === MAX_TASKS_PAGES - 1) { + logger.warn( + `[${requestId}] Planner tasks pagination hit ${MAX_TASKS_PAGES}-page cap; result may be incomplete` + ) + } + } - const filteredTasks = tasks.map((task: PlannerTask) => ({ + const filteredTasks = rawTasks.map((task: PlannerTask) => ({ id: task.id, title: task.title, planId: task.planId, diff --git a/apps/sim/app/api/tools/monday/boards/route.ts b/apps/sim/app/api/tools/monday/boards/route.ts index d20634b0111..e5d3dc5fadc 100644 --- a/apps/sim/app/api/tools/monday/boards/route.ts +++ b/apps/sim/app/api/tools/monday/boards/route.ts @@ -11,18 +11,29 @@ export const dynamic = 'force-dynamic' const logger = createLogger('MondayBoardsAPI') +/** + * Monday's GraphQL `boards(limit: N, page: P, state: active)` has no cursor: + * `page` starts at 1 and you stop once a page returns fewer than `limit` items + * (or an empty page). We request the largest page (`MONDAY_BOARDS_LIMIT`) and + * bound the drain with `MAX_MONDAY_PAGES`. + */ +const MONDAY_BOARDS_LIMIT = 100 +const MAX_MONDAY_PAGES = 50 + interface MondayGraphQLError { message?: string } +interface MondayBoard { + id: string + name: string +} + interface MondayBoardsResponse { errors?: MondayGraphQLError[] error_message?: string data?: { - boards?: Array<{ - id: string - name: string - }> + boards?: MondayBoard[] } } @@ -57,34 +68,68 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://api.monday.com/v2', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: accessToken, - 'API-Version': '2024-10', - }, - body: JSON.stringify({ - query: '{ boards(limit: 100, state: active) { id name } }', - }), - }) + const allBoards: MondayBoard[] = [] + let page = 1 - const data = (await response.json()) as MondayBoardsResponse + for (; page <= MAX_MONDAY_PAGES; page++) { + const response = await fetch('https://api.monday.com/v2', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: accessToken, + 'API-Version': '2024-10', + }, + body: JSON.stringify({ + query: `{ boards(limit: ${MONDAY_BOARDS_LIMIT}, page: ${page}, state: active) { id name } }`, + }), + }) - if (data.errors?.length) { - logger.error('Monday.com API error', { errors: data.errors }) - return NextResponse.json( - { error: data.errors[0].message || 'Monday.com API error' }, - { status: 500 } - ) - } + if (!response.ok) { + const details = await response.text().catch(() => '') + logger.error('Monday.com API HTTP error', { + status: response.status, + statusText: response.statusText, + details, + }) + return NextResponse.json( + { error: `Monday.com API error: ${response.status} ${response.statusText}` }, + { status: 500 } + ) + } + + const data = (await response.json()) as MondayBoardsResponse + + if (data.errors?.length) { + logger.error('Monday.com API error', { errors: data.errors }) + return NextResponse.json( + { error: data.errors[0].message || 'Monday.com API error' }, + { status: 500 } + ) + } + + if (data.error_message) { + logger.error('Monday.com API error', { error_message: data.error_message }) + return NextResponse.json({ error: data.error_message }, { status: 500 }) + } + + const pageBoards = data.data?.boards || [] + allBoards.push(...pageBoards) + + if (pageBoards.length < MONDAY_BOARDS_LIMIT) { + break + } - if (data.error_message) { - logger.error('Monday.com API error', { error_message: data.error_message }) - return NextResponse.json({ error: data.error_message }, { status: 500 }) + if (page === MAX_MONDAY_PAGES) { + logger.warn( + 'Monday boards pagination hit MAX_MONDAY_PAGES cap; board list may be incomplete', + { + maxPages: MAX_MONDAY_PAGES, + } + ) + } } - const boards = (data.data?.boards || []).map((board) => ({ + const boards = allBoards.map((board) => ({ id: board.id, name: board.name, })) diff --git a/apps/sim/app/api/tools/notion/databases/route.ts b/apps/sim/app/api/tools/notion/databases/route.ts index 6ab772afa99..c3f844495d9 100644 --- a/apps/sim/app/api/tools/notion/databases/route.ts +++ b/apps/sim/app/api/tools/notion/databases/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { notionDatabasesSelectorContract } from '@/lib/api/contracts/selectors' import { parseRequest } from '@/lib/api/server' @@ -12,6 +13,16 @@ const logger = createLogger('NotionDatabasesAPI') export const dynamic = 'force-dynamic' +const NOTION_PAGE_SIZE = 100 + +/** + * Notion's `POST /v1/search` returns at most `page_size` results per call and + * exposes `has_more`/`next_cursor` for pagination. This caps the number of + * pages drained so a tenant with a very large workspace cannot make this route + * loop unbounded. With `NOTION_PAGE_SIZE` of 100 this covers up to 2,000 items. + */ +const MAX_DATABASE_PAGES = 20 + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -43,33 +54,55 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://api.notion.com/v1/search', { - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - 'Notion-Version': '2022-06-28', - }, - body: JSON.stringify({ - filter: { value: 'database', property: 'object' }, - page_size: 100, - }), - }) + const results: Record[] = [] + let startCursor: string | undefined - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Notion databases', { - status: response.status, - error: errorData, + for (let page = 0; page < MAX_DATABASE_PAGES; page++) { + const response = await fetch('https://api.notion.com/v1/search', { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + }, + body: JSON.stringify({ + filter: { value: 'database', property: 'object' }, + page_size: NOTION_PAGE_SIZE, + ...(startCursor ? { start_cursor: startCursor } : {}), + }), }) - return NextResponse.json( - { error: 'Failed to fetch Notion databases', details: errorData }, - { status: response.status } - ) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + logger.error('Failed to fetch Notion databases', { + status: response.status, + error: errorData, + }) + return NextResponse.json( + { error: 'Failed to fetch Notion databases', details: errorData }, + { status: response.status } + ) + } + + const data = await response.json() + if (Array.isArray(data.results)) { + results.push(...(data.results as Record[])) + } + + if (!data.has_more || !data.next_cursor) { + break + } + startCursor = data.next_cursor as string + + if (page === MAX_DATABASE_PAGES - 1) { + logger.warn('Notion databases search hit pagination cap; results may be incomplete', { + maxPages: MAX_DATABASE_PAGES, + fetched: results.length, + }) + } } - const data = await response.json() - const databases = (data.results || []).map((db: Record) => ({ + const databases = results.map((db) => ({ id: db.id as string, name: extractTitleFromItem(db), })) @@ -78,7 +111,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } catch (error) { logger.error('Error processing Notion databases request:', error) return NextResponse.json( - { error: 'Failed to retrieve Notion databases', details: (error as Error).message }, + { error: 'Failed to retrieve Notion databases', details: getErrorMessage(error) }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/notion/pages/route.ts b/apps/sim/app/api/tools/notion/pages/route.ts index 419193fdc7c..e48eadf8a41 100644 --- a/apps/sim/app/api/tools/notion/pages/route.ts +++ b/apps/sim/app/api/tools/notion/pages/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { notionPagesSelectorContract } from '@/lib/api/contracts/selectors' import { parseRequest } from '@/lib/api/server' @@ -12,6 +13,16 @@ const logger = createLogger('NotionPagesAPI') export const dynamic = 'force-dynamic' +const NOTION_PAGE_SIZE = 100 + +/** + * Notion's `POST /v1/search` returns at most `page_size` results per call and + * exposes `has_more`/`next_cursor` for pagination. This caps the number of + * pages drained so a tenant with a very large workspace cannot make this route + * loop unbounded. With `NOTION_PAGE_SIZE` of 100 this covers up to 2,000 items. + */ +const MAX_NOTION_PAGES = 20 + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -43,33 +54,55 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://api.notion.com/v1/search', { - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - 'Notion-Version': '2022-06-28', - }, - body: JSON.stringify({ - filter: { value: 'page', property: 'object' }, - page_size: 100, - }), - }) + const results: Record[] = [] + let startCursor: string | undefined - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Notion pages', { - status: response.status, - error: errorData, + for (let page = 0; page < MAX_NOTION_PAGES; page++) { + const response = await fetch('https://api.notion.com/v1/search', { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'Notion-Version': '2022-06-28', + }, + body: JSON.stringify({ + filter: { value: 'page', property: 'object' }, + page_size: NOTION_PAGE_SIZE, + ...(startCursor ? { start_cursor: startCursor } : {}), + }), }) - return NextResponse.json( - { error: 'Failed to fetch Notion pages', details: errorData }, - { status: response.status } - ) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + logger.error('Failed to fetch Notion pages', { + status: response.status, + error: errorData, + }) + return NextResponse.json( + { error: 'Failed to fetch Notion pages', details: errorData }, + { status: response.status } + ) + } + + const data = await response.json() + if (Array.isArray(data.results)) { + results.push(...(data.results as Record[])) + } + + if (!data.has_more || !data.next_cursor) { + break + } + startCursor = data.next_cursor as string + + if (page === MAX_NOTION_PAGES - 1) { + logger.warn('Notion pages search hit pagination cap; results may be incomplete', { + maxPages: MAX_NOTION_PAGES, + fetched: results.length, + }) + } } - const data = await response.json() - const pages = (data.results || []).map((page: Record) => ({ + const pages = results.map((page) => ({ id: page.id as string, name: extractTitleFromItem(page), })) @@ -78,7 +111,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } catch (error) { logger.error('Error processing Notion pages request:', error) return NextResponse.json( - { error: 'Failed to retrieve Notion pages', details: (error as Error).message }, + { error: 'Failed to retrieve Notion pages', details: getErrorMessage(error) }, { status: 500 } ) } diff --git a/apps/sim/app/api/tools/onedrive/files/route.ts b/apps/sim/app/api/tools/onedrive/files/route.ts index 4b3b7273608..5bf2a580ac0 100644 --- a/apps/sim/app/api/tools/onedrive/files/route.ts +++ b/apps/sim/app/api/tools/onedrive/files/route.ts @@ -8,11 +8,21 @@ import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { MicrosoftGraphDriveItem } from '@/tools/onedrive/types' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('OneDriveFilesAPI') +/** + * Microsoft Graph paginates drive item collections via the `@odata.nextLink` + * absolute URL in the response body. Request the largest page (`$top` caps at + * 999) and drain following nextLink, bounded by a page cap. + * See https://learn.microsoft.com/en-us/graph/paging + */ +const ONEDRIVE_FILES_PAGE_SIZE = 999 +const MAX_ONEDRIVE_FILES_PAGES = 20 + /** * Get files (not folders) from Microsoft OneDrive */ @@ -71,7 +81,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { '$select', 'id,name,file,webUrl,size,createdDateTime,lastModifiedDateTime,createdBy,thumbnails' ) - searchParams_new.append('$top', '50') + searchParams_new.append('$top', String(ONEDRIVE_FILES_PAGE_SIZE)) url = `https://graph.microsoft.com/v1.0/me/drive/root/search(q='${encodeURIComponent(query)}')?${searchParams_new.toString()}` } else { const searchParams_new = new URLSearchParams() @@ -79,34 +89,53 @@ export const GET = withRouteHandler(async (request: NextRequest) => { '$select', 'id,name,file,folder,webUrl,size,createdDateTime,lastModifiedDateTime,createdBy,thumbnails' ) - searchParams_new.append('$top', '50') + searchParams_new.append('$top', String(ONEDRIVE_FILES_PAGE_SIZE)) url = `https://graph.microsoft.com/v1.0/me/drive/root/children?${searchParams_new.toString()}` } logger.info(`[${requestId}] Fetching files from Microsoft Graph`, { url }) - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + const rawItems: MicrosoftGraphDriveItem[] = [] + let nextUrl: string | undefined = url - if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) - logger.error(`[${requestId}] Microsoft Graph API error`, { - status: response.status, - error: errorData.error?.message || 'Failed to fetch files from OneDrive', + for (let page = 0; page < MAX_ONEDRIVE_FILES_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, }) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch files from OneDrive' }, - { status: response.status } - ) + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: { message: 'Unknown error' } })) + logger.error(`[${requestId}] Microsoft Graph API error`, { + status: response.status, + error: errorData.error?.message || 'Failed to fetch files from OneDrive', + }) + return NextResponse.json( + { error: errorData.error?.message || 'Failed to fetch files from OneDrive' }, + { status: response.status } + ) + } + + const data = await response.json() + rawItems.push(...((data.value as MicrosoftGraphDriveItem[]) || [])) + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + + if (nextUrl && page === MAX_ONEDRIVE_FILES_PAGES - 1) { + logger.warn(`[${requestId}] OneDrive files hit pagination cap; list may be incomplete`, { + pages: MAX_ONEDRIVE_FILES_PAGES, + collected: rawItems.length, + }) + } } - const data = await response.json() - logger.info(`[${requestId}] Received ${data.value?.length || 0} items from Microsoft Graph`) + logger.info(`[${requestId}] Received ${rawItems.length} items from Microsoft Graph`) - const files = (data.value || []) + const files = rawItems .filter((item: MicrosoftGraphDriveItem) => !!item.file && !item.folder) .map((file: MicrosoftGraphDriveItem) => ({ id: file.id, @@ -129,7 +158,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { })) logger.info(`[${requestId}] Returning ${files.length} files`, { - totalItems: data.value?.length || 0, + totalItems: rawItems.length, }) return NextResponse.json({ files }, { status: 200 }) diff --git a/apps/sim/app/api/tools/onedrive/folders/route.ts b/apps/sim/app/api/tools/onedrive/folders/route.ts index 4c65c4190f6..bcfd9273c2e 100644 --- a/apps/sim/app/api/tools/onedrive/folders/route.ts +++ b/apps/sim/app/api/tools/onedrive/folders/route.ts @@ -8,11 +8,21 @@ import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { MicrosoftGraphDriveItem } from '@/tools/onedrive/types' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('OneDriveFoldersAPI') +/** + * Microsoft Graph paginates drive item collections via the `@odata.nextLink` + * absolute URL in the response body. Request the largest page (`$top` caps at + * 999) and drain following nextLink, bounded by a page cap. + * See https://learn.microsoft.com/en-us/graph/paging + */ +const ONEDRIVE_FOLDERS_PAGE_SIZE = 999 +const MAX_ONEDRIVE_FOLDERS_PAGES = 20 + /** * Get folders from Microsoft OneDrive */ @@ -60,28 +70,47 @@ export const GET = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 }) } - let url = `https://graph.microsoft.com/v1.0/me/drive/root/children?$filter=folder ne null&$select=id,name,folder,webUrl,createdDateTime,lastModifiedDateTime&$top=50` + let url = `https://graph.microsoft.com/v1.0/me/drive/root/children?$filter=folder ne null&$select=id,name,folder,webUrl,createdDateTime,lastModifiedDateTime&$top=${ONEDRIVE_FOLDERS_PAGE_SIZE}` if (query) { url += `&$search="${encodeURIComponent(query)}"` } - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + const rawItems: MicrosoftGraphDriveItem[] = [] + let nextUrl: string | undefined = url - if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch folders from OneDrive' }, - { status: response.status } - ) + for (let page = 0; page < MAX_ONEDRIVE_FOLDERS_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: { message: 'Unknown error' } })) + return NextResponse.json( + { error: errorData.error?.message || 'Failed to fetch folders from OneDrive' }, + { status: response.status } + ) + } + + const data = await response.json() + rawItems.push(...((data.value as MicrosoftGraphDriveItem[]) || [])) + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + + if (nextUrl && page === MAX_ONEDRIVE_FOLDERS_PAGES - 1) { + logger.warn(`[${requestId}] OneDrive folders hit pagination cap; list may be incomplete`, { + pages: MAX_ONEDRIVE_FOLDERS_PAGES, + collected: rawItems.length, + }) + } } - const data = await response.json() - const folders = (data.value || []) + const folders = rawItems .filter((item: MicrosoftGraphDriveItem) => item.folder) .map((folder: MicrosoftGraphDriveItem) => ({ id: folder.id, diff --git a/apps/sim/app/api/tools/onepassword/utils.test.ts b/apps/sim/app/api/tools/onepassword/utils.test.ts new file mode 100644 index 00000000000..504ed46b050 --- /dev/null +++ b/apps/sim/app/api/tools/onepassword/utils.test.ts @@ -0,0 +1,108 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockDnsLookup, hostedFlag } = vi.hoisted(() => ({ + mockDnsLookup: vi.fn(), + hostedFlag: { value: false }, +})) + +vi.mock('@/lib/core/config/feature-flags', () => ({ + get isHosted() { + return hostedFlag.value + }, +})) + +vi.mock('dns/promises', () => ({ + default: { lookup: mockDnsLookup }, +})) + +import { validateConnectServerUrl } from '@/app/api/tools/onepassword/utils' + +describe('validateConnectServerUrl', () => { + beforeEach(() => { + vi.clearAllMocks() + hostedFlag.value = false + }) + + it('rejects a non-URL string', async () => { + await expect(validateConnectServerUrl('not a url')).rejects.toThrow('is not a valid URL') + }) + + describe('hosted deployment', () => { + beforeEach(() => { + hostedFlag.value = true + }) + + it.each([ + ['loopback', 'http://127.0.0.1:8080'], + ['RFC1918 10.x', 'http://10.0.0.5'], + ['RFC1918 192.168.x', 'http://192.168.1.1:8443'], + ['RFC1918 172.16.x', 'http://172.16.0.9'], + ['link-local metadata', 'http://169.254.169.254'], + ['IPv4-mapped IPv6 private', 'http://[::ffff:10.0.0.1]'], + ['IPv6 loopback', 'http://[::1]'], + ])('blocks %s', async (_label, url) => { + await expect(validateConnectServerUrl(url)).rejects.toThrow( + 'cannot point to a private or reserved IP address' + ) + }) + + it('allows a public IP literal', async () => { + await expect(validateConnectServerUrl('https://8.8.8.8')).resolves.toBe('8.8.8.8') + }) + + it('blocks a hostname that resolves to a private IP', async () => { + mockDnsLookup.mockResolvedValue({ address: '10.1.2.3', family: 4 }) + await expect(validateConnectServerUrl('https://connect.internal')).rejects.toThrow( + 'cannot point to a private or reserved IP address' + ) + }) + + it('allows a hostname that resolves to a public IP', async () => { + mockDnsLookup.mockResolvedValue({ address: '93.184.216.34', family: 4 }) + await expect(validateConnectServerUrl('https://connect.example.com')).resolves.toBe( + '93.184.216.34' + ) + }) + }) + + describe('self-hosted deployment', () => { + beforeEach(() => { + hostedFlag.value = false + }) + + it.each([ + ['loopback', 'http://127.0.0.1:8080', '127.0.0.1'], + ['RFC1918 10.x', 'http://10.0.0.5', '10.0.0.5'], + ['RFC1918 192.168.x', 'http://192.168.1.1:8443', '192.168.1.1'], + ])('allows %s (private Connect server)', async (_label, url, expected) => { + await expect(validateConnectServerUrl(url)).resolves.toBe(expected) + }) + + it('still blocks link-local metadata', async () => { + await expect(validateConnectServerUrl('http://169.254.169.254')).rejects.toThrow( + 'cannot point to a link-local address' + ) + }) + + it('still blocks IPv6 link-local', async () => { + await expect(validateConnectServerUrl('http://[fe80::1]')).rejects.toThrow( + 'cannot point to a link-local address' + ) + }) + + it('allows a hostname that resolves to a private IP', async () => { + mockDnsLookup.mockResolvedValue({ address: '10.1.2.3', family: 4 }) + await expect(validateConnectServerUrl('https://connect.internal')).resolves.toBe('10.1.2.3') + }) + }) + + it('rejects when DNS resolution fails', async () => { + mockDnsLookup.mockRejectedValue(new Error('ENOTFOUND')) + await expect(validateConnectServerUrl('https://nope.invalid')).rejects.toThrow( + 'could not be resolved' + ) + }) +}) diff --git a/apps/sim/app/api/tools/onepassword/utils.ts b/apps/sim/app/api/tools/onepassword/utils.ts index 94babba28f3..4dcee716966 100644 --- a/apps/sim/app/api/tools/onepassword/utils.ts +++ b/apps/sim/app/api/tools/onepassword/utils.ts @@ -12,7 +12,11 @@ import type { import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import * as ipaddr from 'ipaddr.js' -import { secureFetchWithPinnedIP } from '@/lib/core/security/input-validation.server' +import { isHosted } from '@/lib/core/config/feature-flags' +import { + isPrivateOrReservedIP, + secureFetchWithPinnedIP, +} from '@/lib/core/security/input-validation.server' /** Connect-format field type strings returned by normalization. */ type ConnectFieldType = @@ -246,12 +250,44 @@ export async function createOnePasswordClient(serviceAccountToken: string) { const connectLogger = createLogger('OnePasswordConnect') /** - * Validates that a Connect server URL does not target cloud metadata endpoints. - * Allows private IPs and localhost since 1Password Connect is designed to be self-hosted. - * Returns the resolved IP for DNS pinning to prevent TOCTOU rebinding. - * @throws Error if the URL is invalid, points to a link-local address, or DNS fails. + * Enforces the SSRF policy for a resolved Connect server IP. + * + * On the hosted service, all private and reserved IPs are blocked — a tenant has + * no legitimate reason to point Connect at the platform's internal network. On + * self-hosted deployments only link-local (cloud metadata) is blocked, since the + * operator controls both the workflows and the network and Connect servers + * legitimately live on private (RFC1918) addresses. + * + * @throws Error if the IP is not permitted under the active policy. */ -async function validateConnectServerUrl(serverUrl: string): Promise { +function assertConnectIpAllowed(ip: string, hostname: string): void { + if (isHosted) { + if (isPrivateOrReservedIP(ip)) { + connectLogger.warn('1Password Connect server URL resolves to a private or reserved IP', { + hostname, + resolvedIP: ip, + }) + throw new Error('1Password server URL cannot point to a private or reserved IP address') + } + return + } + + if (ipaddr.isValid(ip) && ipaddr.process(ip).range() === 'linkLocal') { + connectLogger.warn('1Password Connect server URL resolves to a link-local IP', { + hostname, + resolvedIP: ip, + }) + throw new Error('1Password server URL cannot point to a link-local address') + } +} + +/** + * Validates a Connect server URL against the SSRF policy and returns the resolved + * IP for DNS pinning to prevent TOCTOU rebinding. See {@link assertConnectIpAllowed} + * for the hosted vs. self-hosted policy. + * @throws Error if the URL is invalid, fails the IP policy, or DNS fails. + */ +export async function validateConnectServerUrl(serverUrl: string): Promise { let hostname: string try { hostname = new URL(serverUrl).hostname @@ -263,31 +299,23 @@ async function validateConnectServerUrl(serverUrl: string): Promise { hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname if (ipaddr.isValid(clean)) { - const addr = ipaddr.process(clean) - if (addr.range() === 'linkLocal') { - throw new Error('1Password server URL cannot point to a link-local address') - } + assertConnectIpAllowed(clean, clean) return clean } + let address: string try { - const { address } = await dns.lookup(clean, { verbatim: true }) - if (ipaddr.isValid(address) && ipaddr.process(address).range() === 'linkLocal') { - connectLogger.warn('1Password Connect server URL resolves to link-local IP', { - hostname: clean, - resolvedIP: address, - }) - throw new Error('1Password server URL resolves to a link-local address') - } - return address + ;({ address } = await dns.lookup(clean, { verbatim: true })) } catch (error) { - if (error instanceof Error && error.message.startsWith('1Password')) throw error connectLogger.warn('DNS lookup failed for 1Password Connect server URL', { hostname: clean, error: toError(error).message, }) throw new Error('1Password server URL hostname could not be resolved') } + + assertConnectIpAllowed(address, clean) + return address } /** Minimal response shape used by all connectRequest callers. */ diff --git a/apps/sim/app/api/tools/outlook/folders/route.ts b/apps/sim/app/api/tools/outlook/folders/route.ts index 2cd0addcd85..8ae9d3e9e21 100644 --- a/apps/sim/app/api/tools/outlook/folders/route.ts +++ b/apps/sim/app/api/tools/outlook/folders/route.ts @@ -8,11 +8,21 @@ import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('OutlookFoldersAPI') +/** + * Microsoft Graph paginates `mailFolders` via the `@odata.nextLink` absolute + * URL in the response body (default page size is ~10). Bound the drain so a + * pathological account can't loop unbounded; `$top` is capped at 999 by Graph. + * See https://learn.microsoft.com/en-us/graph/paging + */ +const OUTLOOK_FOLDERS_PAGE_SIZE = 999 +const MAX_OUTLOOK_FOLDERS_PAGES = 20 + interface OutlookFolder { id: string displayName: string @@ -65,37 +75,53 @@ export const GET = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://graph.microsoft.com/v1.0/me/mailFolders', { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) + const folders: OutlookFolder[] = [] + let nextUrl: string | undefined = + `https://graph.microsoft.com/v1.0/me/mailFolders?$top=${OUTLOOK_FOLDERS_PAGE_SIZE}` - if (!response.ok) { - const errorData = await response.json() - logger.error('Microsoft Graph API error getting folders', { - status: response.status, - error: errorData, - endpoint: 'https://graph.microsoft.com/v1.0/me/mailFolders', + for (let page = 0; page < MAX_OUTLOOK_FOLDERS_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, }) - if (response.status === 401) { - return NextResponse.json( - { - error: 'Authentication failed. Please reconnect your Outlook account.', - authRequired: true, - }, - { status: 401 } - ) + if (!response.ok) { + const errorData = await response.json() + logger.error('Microsoft Graph API error getting folders', { + status: response.status, + error: errorData, + endpoint: nextUrl, + }) + + if (response.status === 401) { + return NextResponse.json( + { + error: 'Authentication failed. Please reconnect your Outlook account.', + authRequired: true, + }, + { status: 401 } + ) + } + + throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) } - throw new Error(`Microsoft Graph API error: ${JSON.stringify(errorData)}`) - } + const data = await response.json() + folders.push(...((data.value as OutlookFolder[]) || [])) - const data = await response.json() - const folders = data.value || [] + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + + if (nextUrl && page === MAX_OUTLOOK_FOLDERS_PAGES - 1) { + logger.warn('Outlook mailFolders hit pagination cap; folder list may be incomplete', { + pages: MAX_OUTLOOK_FOLDERS_PAGES, + collected: folders.length, + }) + } + } const transformedFolders = folders.map((folder: OutlookFolder) => ({ id: folder.id, diff --git a/apps/sim/app/api/tools/pipedrive/pipelines/route.ts b/apps/sim/app/api/tools/pipedrive/pipelines/route.ts index 8e3900fe113..707a9ff9ee4 100644 --- a/apps/sim/app/api/tools/pipedrive/pipelines/route.ts +++ b/apps/sim/app/api/tools/pipedrive/pipelines/route.ts @@ -11,6 +11,86 @@ const logger = createLogger('PipedrivePipelinesAPI') export const dynamic = 'force-dynamic' +const PIPEDRIVE_PAGE_LIMIT = 500 +const PIPEDRIVE_MAX_PIPELINES_PAGES = 50 + +interface PipedrivePipeline { + id: number + name: string +} + +interface PipedrivePipelinesPage { + data?: PipedrivePipeline[] + additional_data?: { + pagination?: { + more_items_in_collection?: boolean + next_start?: number + } + } +} + +/** + * Lists all Pipedrive pipelines using v1 offset pagination (`start`/`limit`), + * following `additional_data.pagination.next_start` while + * `more_items_in_collection` is true so the full set is returned. Bounded by + * `PIPEDRIVE_MAX_PIPELINES_PAGES`; logs a warning rather than silently dropping + * pipelines when the cap is hit. + */ +async function fetchAllPipelines(accessToken: string): Promise { + const pipelines: PipedrivePipeline[] = [] + let start = 0 + + for (let page = 0; page < PIPEDRIVE_MAX_PIPELINES_PAGES; page++) { + const url = new URL('https://api.pipedrive.com/v1/pipelines') + url.searchParams.set('start', String(start)) + url.searchParams.set('limit', String(PIPEDRIVE_PAGE_LIMIT)) + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new PipedriveFetchError(response.status, errorData) + } + + const data = (await response.json()) as PipedrivePipelinesPage + if (Array.isArray(data.data)) { + pipelines.push(...data.data) + } + + const pagination = data.additional_data?.pagination + if (!pagination?.more_items_in_collection || typeof pagination.next_start !== 'number') { + return pipelines + } + start = pagination.next_start + + if (page === PIPEDRIVE_MAX_PIPELINES_PAGES - 1) { + logger.warn( + 'Pipedrive pipelines listing hit pagination cap; pipeline list may be incomplete', + { + pages: PIPEDRIVE_MAX_PIPELINES_PAGES, + } + ) + } + } + + return pipelines +} + +class PipedriveFetchError extends Error { + constructor( + readonly status: number, + readonly details: unknown + ) { + super('Failed to fetch Pipedrive pipelines') + this.name = 'PipedriveFetchError' + } +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -42,27 +122,24 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch('https://api.pipedrive.com/v1/pipelines', { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Pipedrive pipelines', { - status: response.status, - error: errorData, - }) - return NextResponse.json( - { error: 'Failed to fetch Pipedrive pipelines', details: errorData }, - { status: response.status } - ) + let allPipelines: PipedrivePipeline[] + try { + allPipelines = await fetchAllPipelines(accessToken) + } catch (error) { + if (error instanceof PipedriveFetchError) { + logger.error('Failed to fetch Pipedrive pipelines', { + status: error.status, + error: error.details, + }) + return NextResponse.json( + { error: 'Failed to fetch Pipedrive pipelines', details: error.details }, + { status: error.status } + ) + } + throw error } - const data = await response.json() - const pipelines = (data.data || []).map((pipeline: { id: number; name: string }) => ({ + const pipelines = allPipelines.map((pipeline) => ({ id: String(pipeline.id), name: pipeline.name, })) diff --git a/apps/sim/app/api/tools/sharepoint/lists/route.ts b/apps/sim/app/api/tools/sharepoint/lists/route.ts index 109b4106784..a3970a6f043 100644 --- a/apps/sim/app/api/tools/sharepoint/lists/route.ts +++ b/apps/sim/app/api/tools/sharepoint/lists/route.ts @@ -7,11 +7,19 @@ import { validateSharePointSiteId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('SharePointListsAPI') +/** + * Upper bound on Microsoft Graph pages drained when listing SharePoint lists. + * Each page returns up to `$top=999` lists, so this caps the result set at + * roughly 10k lists while preventing an unbounded server-side loop. + */ +const MAX_LISTS_PAGES = 10 + interface SharePointList { id: string displayName: string @@ -60,24 +68,42 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const url = `https://graph.microsoft.com/v1.0/sites/${siteIdValidation.sanitized}/lists?$select=id,displayName,description,webUrl&$expand=list($select=hidden)&$top=100` + let nextUrl: string | undefined = + `https://graph.microsoft.com/v1.0/sites/${siteIdValidation.sanitized}/lists?$select=id,displayName,description,webUrl&$expand=list($select=hidden)&$top=999` - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + const rawLists: SharePointList[] = [] + for (let page = 0; page < MAX_LISTS_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) - if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch lists from SharePoint' }, - { status: response.status } - ) + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: { message: 'Unknown error' } })) + return NextResponse.json( + { error: errorData.error?.message || 'Failed to fetch lists from SharePoint' }, + { status: response.status } + ) + } + + const data = await response.json() + if (Array.isArray(data.value)) { + rawLists.push(...data.value) + } + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + if (nextUrl && page === MAX_LISTS_PAGES - 1) { + logger.warn( + `[${requestId}] SharePoint lists pagination hit ${MAX_LISTS_PAGES}-page cap; result may be incomplete` + ) + } } - const data = await response.json() - const lists = (data.value || []) + const lists = rawLists .filter((list: SharePointList) => list.list?.hidden !== true) .map((list: SharePointList) => ({ id: list.id, diff --git a/apps/sim/app/api/tools/sharepoint/sites/route.ts b/apps/sim/app/api/tools/sharepoint/sites/route.ts index 4ca0c58cdeb..fc8db948c7d 100644 --- a/apps/sim/app/api/tools/sharepoint/sites/route.ts +++ b/apps/sim/app/api/tools/sharepoint/sites/route.ts @@ -7,11 +7,19 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' import type { SharepointSite } from '@/tools/sharepoint/types' +import { assertGraphNextPageUrl, getGraphNextPageUrl } from '@/tools/sharepoint/utils' export const dynamic = 'force-dynamic' const logger = createLogger('SharePointSitesAPI') +/** + * Upper bound on Microsoft Graph pages drained when listing SharePoint sites. + * Each page returns up to `$top=999` sites, so this caps the result set at + * roughly 10k sites while preventing an unbounded server-side loop. + */ +const MAX_SITES_PAGES = 10 + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() @@ -45,24 +53,42 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } const searchQuery = query || '*' - const url = `https://graph.microsoft.com/v1.0/sites?search=${encodeURIComponent(searchQuery)}&$select=id,name,displayName,webUrl,createdDateTime,lastModifiedDateTime&$top=50` + let nextUrl: string | undefined = + `https://graph.microsoft.com/v1.0/sites?search=${encodeURIComponent(searchQuery)}&$select=id,name,displayName,webUrl,createdDateTime,lastModifiedDateTime&$top=999` - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) + const rawSites: SharepointSite[] = [] + for (let page = 0; page < MAX_SITES_PAGES && nextUrl; page++) { + const response = await fetch(nextUrl, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) - if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch sites from SharePoint' }, - { status: response.status } - ) + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: { message: 'Unknown error' } })) + return NextResponse.json( + { error: errorData.error?.message || 'Failed to fetch sites from SharePoint' }, + { status: response.status } + ) + } + + const data = await response.json() + if (Array.isArray(data.value)) { + rawSites.push(...data.value) + } + + const nextLink = getGraphNextPageUrl(data) + nextUrl = nextLink ? assertGraphNextPageUrl(nextLink) : undefined + if (nextUrl && page === MAX_SITES_PAGES - 1) { + logger.warn( + `[${requestId}] SharePoint sites pagination hit ${MAX_SITES_PAGES}-page cap; result may be incomplete` + ) + } } - const data = await response.json() - const sites = (data.value || []).map((site: SharepointSite) => ({ + const sites = rawSites.map((site: SharepointSite) => ({ id: site.id, name: site.displayName || site.name, mimeType: 'application/vnd.microsoft.graph.site', diff --git a/apps/sim/app/api/tools/slack/users/route.ts b/apps/sim/app/api/tools/slack/users/route.ts index d9360d9b7c4..cb1e69569f4 100644 --- a/apps/sim/app/api/tools/slack/users/route.ts +++ b/apps/sim/app/api/tools/slack/users/route.ts @@ -12,6 +12,9 @@ export const dynamic = 'force-dynamic' const logger = createLogger('SlackUsersAPI') +const SLACK_PAGE_LIMIT = 200 +const SLACK_MAX_USER_PAGES = 10 + interface SlackUser { id: string name: string @@ -20,6 +23,11 @@ interface SlackUser { is_bot: boolean } +interface SlackUsersResult { + members: SlackUser[] + truncated: boolean +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const requestId = generateRequestId() @@ -86,6 +94,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } const data = await fetchSlackUsers(accessToken) + if (data.truncated) { + logger.warn('users.list hit pagination cap; user list may be incomplete') + } const users = (data.members || []) .filter((user: SlackUser) => !user.deleted && !user.is_bot) @@ -134,27 +145,53 @@ async function fetchSlackUser(accessToken: string, userId: string) { return data } -async function fetchSlackUsers(accessToken: string) { - const url = new URL('https://slack.com/api/users.list') - url.searchParams.append('limit', '200') +/** + * Lists Slack workspace members, following `response_metadata.next_cursor` so + * the full set is returned. Bounded by `SLACK_MAX_USER_PAGES`; sets `truncated` + * rather than silently dropping members when the cap is hit. + */ +async function fetchSlackUsers(accessToken: string): Promise { + const members: SlackUser[] = [] + let cursor: string | undefined + let truncated = false + + for (let page = 0; page < SLACK_MAX_USER_PAGES; page++) { + const url = new URL('https://slack.com/api/users.list') + url.searchParams.append('limit', String(SLACK_PAGE_LIMIT)) + if (cursor) { + url.searchParams.append('cursor', cursor) + } - const response = await fetch(url.toString(), { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) + const response = await fetch(url.toString(), { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }) - if (!response.ok) { - throw new Error(`Slack API error: ${response.status} ${response.statusText}`) - } + if (!response.ok) { + throw new Error(`Slack API error: ${response.status} ${response.statusText}`) + } - const data = await response.json() + const data = await response.json() - if (!data.ok) { - throw new Error(data.error || 'Failed to fetch users') + if (!data.ok) { + throw new Error(data.error || 'Failed to fetch users') + } + + if (Array.isArray(data.members)) { + members.push(...data.members) + } + + cursor = data.response_metadata?.next_cursor?.trim() || undefined + if (!cursor) { + return { members, truncated } + } + if (page === SLACK_MAX_USER_PAGES - 1) { + truncated = true + } } - return data + return { members, truncated } } diff --git a/apps/sim/app/api/tools/wealthbox/items/route.ts b/apps/sim/app/api/tools/wealthbox/items/route.ts index 00ce1aab98a..a10c4672eb3 100644 --- a/apps/sim/app/api/tools/wealthbox/items/route.ts +++ b/apps/sim/app/api/tools/wealthbox/items/route.ts @@ -12,6 +12,18 @@ export const dynamic = 'force-dynamic' const logger = createLogger('WealthboxItemsAPI') +/** + * Wealthbox `GET /v1/contacts` paginates with `?page=` / `?per_page=`, starting + * at page 1. Wealthbox documents no `per_page` maximum and its contacts response + * carries no pagination `meta`, so termination relies on the short-page check: + * we stop once a page returns fewer items than `WEALTHBOX_PAGE_SIZE` (the + * `meta.total_pages` / `meta.current_page` check is a defensive fallback for if + * Wealthbox ever adds that block). Bounded by `MAX_WEALTHBOX_PAGES` so a runaway + * response can't loop forever. + */ +const WEALTHBOX_PAGE_SIZE = 50 +const MAX_WEALTHBOX_PAGES = 50 + interface WealthboxItem { id: string name: string @@ -21,6 +33,15 @@ interface WealthboxItem { updatedAt: string } +interface WealthboxContactsPage { + contacts?: Array> + meta?: { + total_count?: number + total_pages?: number + current_page?: number + } +} + /** * Get items (notes, contacts, tasks) from Wealthbox */ @@ -69,63 +90,83 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } const endpoint = endpoints[type as keyof typeof endpoints] - const url = new URL(`https://api.crmworkspace.com/v1/${endpoint}`) - logger.info(`[${requestId}] Fetching ${type}s from Wealthbox`, { endpoint, - url: url.toString(), hasQuery: !!query.trim(), }) - const response = await fetch(url.toString(), { - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }) + const allContacts: Array> = [] + let page = 1 - if (!response.ok) { - const errorText = await response.text() - logger.error( - `[${requestId}] Wealthbox API error: ${response.status} ${response.statusText}`, - { - error: errorText, - endpoint, - url: url.toString(), - } - ) - return NextResponse.json( - { error: `Failed to fetch ${type}s from Wealthbox` }, - { status: response.status } - ) - } + for (; page <= MAX_WEALTHBOX_PAGES; page++) { + const url = new URL(`https://api.crmworkspace.com/v1/${endpoint}`) + url.searchParams.set('per_page', String(WEALTHBOX_PAGE_SIZE)) + url.searchParams.set('page', String(page)) - const data = (await response.json()) as { contacts?: Array> } & Record< - string, - unknown - > + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }) - logger.info(`[${requestId}] Wealthbox API raw response`, { - type, - status: response.status, - dataKeys: Object.keys(data || {}), - hasContacts: !!data.contacts, - dataStructure: typeof data === 'object' ? Object.keys(data) : 'not an object', - }) + if (!response.ok) { + const errorText = await response.text() + logger.error( + `[${requestId}] Wealthbox API error: ${response.status} ${response.statusText}`, + { + error: errorText, + endpoint, + url: url.toString(), + } + ) + return NextResponse.json( + { error: `Failed to fetch ${type}s from Wealthbox` }, + { status: response.status } + ) + } - let items: WealthboxItem[] = [] + const data = (await response.json()) as WealthboxContactsPage - if (type === 'contact') { const contacts = data.contacts || [] if (!Array.isArray(contacts)) { logger.warn(`[${requestId}] Contacts is not an array`, { contacts, dataType: typeof contacts, }) - return NextResponse.json({ items: [] }, { status: 200 }) + break + } + + allContacts.push(...contacts) + + const totalPages = data.meta?.total_pages + const currentPage = data.meta?.current_page ?? page + const reachedLastByMeta = + typeof totalPages === 'number' && totalPages > 0 && currentPage >= totalPages + const reachedLastByCount = contacts.length < WEALTHBOX_PAGE_SIZE + + if (reachedLastByMeta || reachedLastByCount) { + break } - items = contacts.map((item) => { + if (page === MAX_WEALTHBOX_PAGES) { + logger.warn( + `[${requestId}] Wealthbox pagination hit MAX_WEALTHBOX_PAGES cap; contact list may be incomplete`, + { endpoint, maxPages: MAX_WEALTHBOX_PAGES } + ) + } + } + + logger.info(`[${requestId}] Wealthbox API drained`, { + type, + pagesFetched: Math.min(page, MAX_WEALTHBOX_PAGES), + totalContacts: allContacts.length, + }) + + let items: WealthboxItem[] = [] + + if (type === 'contact') { + items = allContacts.map((item) => { const firstName = typeof item.first_name === 'string' ? item.first_name : '' const lastName = typeof item.last_name === 'string' ? item.last_name : '' return { diff --git a/apps/sim/app/api/tools/webflow/items/route.ts b/apps/sim/app/api/tools/webflow/items/route.ts index 1ed9884e0fb..4feb0f40417 100644 --- a/apps/sim/app/api/tools/webflow/items/route.ts +++ b/apps/sim/app/api/tools/webflow/items/route.ts @@ -12,6 +12,9 @@ const logger = createLogger('WebflowItemsAPI') export const dynamic = 'force-dynamic' +const WEBFLOW_PAGE_LIMIT = 100 +const WEBFLOW_MAX_ITEMS_PAGES = 50 + interface WebflowItem { id: string fieldData?: { @@ -21,6 +24,74 @@ interface WebflowItem { } } +interface WebflowItemsPage { + items?: WebflowItem[] + pagination?: { + total?: number + limit?: number + offset?: number + } +} + +/** + * Lists all items in a Webflow collection using `offset`/`limit` pagination + * (limit capped at 100), advancing the numeric `offset` until the accumulated + * count reaches `pagination.total` so the full set is returned. Bounded by + * `WEBFLOW_MAX_ITEMS_PAGES`; logs a warning rather than silently dropping items + * when the cap is hit. + */ +async function fetchAllItems(accessToken: string, collectionId: string): Promise { + const items: WebflowItem[] = [] + let offset = 0 + + for (let page = 0; page < WEBFLOW_MAX_ITEMS_PAGES; page++) { + const url = new URL(`https://api.webflow.com/v2/collections/${collectionId}/items`) + url.searchParams.set('limit', String(WEBFLOW_PAGE_LIMIT)) + url.searchParams.set('offset', String(offset)) + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${accessToken}`, + accept: 'application/json', + }, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new WebflowFetchError(response.status, errorData) + } + + const data = (await response.json()) as WebflowItemsPage + const pageItems = data.items || [] + items.push(...pageItems) + + const total = data.pagination?.total + offset += pageItems.length + if (pageItems.length === 0 || (typeof total === 'number' && items.length >= total)) { + return items + } + + if (page === WEBFLOW_MAX_ITEMS_PAGES - 1) { + logger.warn('Webflow items listing hit pagination cap; item list may be incomplete', { + collectionId, + pages: WEBFLOW_MAX_ITEMS_PAGES, + }) + } + } + + return items +} + +class WebflowFetchError extends Error { + constructor( + readonly status: number, + readonly details: unknown + ) { + super('Failed to fetch Webflow items') + this.name = 'WebflowFetchError' + } +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const requestId = generateRequestId() @@ -61,32 +132,24 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch( - `https://api.webflow.com/v2/collections/${collectionId}/items?limit=100`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - accept: 'application/json', - }, + let items: WebflowItem[] + try { + items = await fetchAllItems(accessToken, collectionId) + } catch (error) { + if (error instanceof WebflowFetchError) { + logger.error('Failed to fetch Webflow items', { + status: error.status, + error: error.details, + collectionId, + }) + return NextResponse.json( + { error: 'Failed to fetch Webflow items', details: error.details }, + { status: error.status } + ) } - ) - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Webflow items', { - status: response.status, - error: errorData, - collectionId, - }) - return NextResponse.json( - { error: 'Failed to fetch Webflow items', details: errorData }, - { status: response.status } - ) + throw error } - const data = (await response.json()) as { items?: WebflowItem[] } - const items = data.items || [] - let formattedItems = items.map((item) => { const fieldData = item.fieldData || {} const name = fieldData.name || fieldData.title || fieldData.slug || item.id diff --git a/apps/sim/app/api/tools/zoom/meetings/route.ts b/apps/sim/app/api/tools/zoom/meetings/route.ts index 36edf498789..53e78f408ca 100644 --- a/apps/sim/app/api/tools/zoom/meetings/route.ts +++ b/apps/sim/app/api/tools/zoom/meetings/route.ts @@ -11,6 +11,24 @@ const logger = createLogger('ZoomMeetingsAPI') export const dynamic = 'force-dynamic' +/** + * Zoom `GET /v2/users/me/meetings` returns `next_page_token`, which is passed + * back as `?next_page_token=` until it comes back as an empty string. `page_size` + * max is 300. Bounded by `MAX_ZOOM_PAGES` so a runaway response can't loop forever. + */ +const ZOOM_PAGE_SIZE = 300 +const MAX_ZOOM_PAGES = 50 + +interface ZoomMeeting { + id: number + topic: string +} + +interface ZoomMeetingsPage { + meetings?: ZoomMeeting[] + next_page_token?: string +} + export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() try { @@ -42,30 +60,57 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const response = await fetch( - 'https://api.zoom.us/v2/users/me/meetings?page_size=300&type=scheduled', - { + const allMeetings: ZoomMeeting[] = [] + let nextPageToken = '' + + for (let page = 0; page < MAX_ZOOM_PAGES; page++) { + const url = new URL('https://api.zoom.us/v2/users/me/meetings') + url.searchParams.set('page_size', String(ZOOM_PAGE_SIZE)) + url.searchParams.set('type', 'scheduled') + if (nextPageToken) { + url.searchParams.set('next_page_token', nextPageToken) + } + + const response = await fetch(url.toString(), { headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + logger.error('Failed to fetch Zoom meetings', { + status: response.status, + error: errorData, + }) + return NextResponse.json( + { error: 'Failed to fetch Zoom meetings', details: errorData }, + { status: response.status } + ) } - ) - if (!response.ok) { - const errorData = await response.json().catch(() => ({})) - logger.error('Failed to fetch Zoom meetings', { - status: response.status, - error: errorData, - }) - return NextResponse.json( - { error: 'Failed to fetch Zoom meetings', details: errorData }, - { status: response.status } - ) + const data = (await response.json()) as ZoomMeetingsPage + if (Array.isArray(data.meetings)) { + allMeetings.push(...data.meetings) + } + + nextPageToken = data.next_page_token?.trim() || '' + if (!nextPageToken) { + break + } + + if (page === MAX_ZOOM_PAGES - 1) { + logger.warn( + 'Zoom meetings pagination hit MAX_ZOOM_PAGES cap; meeting list may be incomplete', + { + maxPages: MAX_ZOOM_PAGES, + } + ) + } } - const data = await response.json() - const meetings = (data.meetings || []).map((meeting: { id: number; topic: string }) => ({ + const meetings = allMeetings.map((meeting) => ({ id: String(meeting.id), name: meeting.topic, })) diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts index 4dadf2cf93e..7a33e113133 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/members/[memberId]/route.ts @@ -35,6 +35,7 @@ import { adminV1UpdateOrganizationMemberContract, } from '@/lib/api/contracts/v1/admin' import { parseRequest } from '@/lib/api/server' +import { getOrgMemberLedgerByUser } from '@/lib/billing/core/organization' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -86,7 +87,6 @@ export const GET = withRouteHandler( userEmail: user.email, currentPeriodCost: userStats.currentPeriodCost, currentUsageLimit: userStats.currentUsageLimit, - lastActive: userStats.lastActive, billingBlocked: userStats.billingBlocked, }) .from(member) @@ -99,6 +99,10 @@ export const GET = withRouteHandler( return notFoundResponse('Member') } + // currentPeriodCost is only a baseline; add this member's attributed + // usage_log for the org's period so admin shows real current usage. + const ledgerByUser = await getOrgMemberLedgerByUser(organizationId) + const data: AdminMemberDetail = { id: memberData.id, userId: memberData.userId, @@ -107,9 +111,10 @@ export const GET = withRouteHandler( createdAt: memberData.createdAt.toISOString(), userName: memberData.userName, userEmail: memberData.userEmail, - currentPeriodCost: memberData.currentPeriodCost ?? '0', + currentPeriodCost: ( + Number(memberData.currentPeriodCost ?? 0) + (ledgerByUser.get(memberData.userId) ?? 0) + ).toString(), currentUsageLimit: memberData.currentUsageLimit, - lastActive: memberData.lastActive?.toISOString() ?? null, billingBlocked: memberData.billingBlocked ?? false, } diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts index 0c5b50fefd6..5c4466d182d 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/members/route.ts @@ -37,6 +37,7 @@ import { adminV1ListOrganizationMembersContract, } from '@/lib/api/contracts/v1/admin' import { parseRequest } from '@/lib/api/server' +import { getOrgMemberLedgerByUser } from '@/lib/billing/core/organization' import { addUserToOrganization } from '@/lib/billing/organizations/membership' import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -96,7 +97,6 @@ export const GET = withRouteHandler( userEmail: user.email, currentPeriodCost: userStats.currentPeriodCost, currentUsageLimit: userStats.currentUsageLimit, - lastActive: userStats.lastActive, billingBlocked: userStats.billingBlocked, }) .from(member) @@ -109,6 +109,11 @@ export const GET = withRouteHandler( ]) const total = countResult[0].count + + // currentPeriodCost is only a baseline; add each member's attributed + // usage_log for the org's period so admin shows real current usage. + const usageByUser = await getOrgMemberLedgerByUser(organizationId) + const data: AdminMemberDetail[] = membersData.map((m) => ({ id: m.id, userId: m.userId, @@ -117,9 +122,10 @@ export const GET = withRouteHandler( createdAt: m.createdAt.toISOString(), userName: m.userName, userEmail: m.userEmail, - currentPeriodCost: m.currentPeriodCost ?? '0', + currentPeriodCost: ( + Number(m.currentPeriodCost ?? 0) + (usageByUser.get(m.userId) ?? 0) + ).toString(), currentUsageLimit: m.currentUsageLimit, - lastActive: m.lastActive?.toISOString() ?? null, billingBlocked: m.billingBlocked ?? false, })) diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts index 6c015190408..921131e2409 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/seats/route.ts @@ -51,16 +51,6 @@ export const GET = withRouteHandler( subscriptionPlan: analytics.subscriptionPlan, canAddSeats: analytics.canAddSeats, utilizationRate: analytics.utilizationRate, - activeMembers: analytics.activeMembers, - inactiveMembers: analytics.inactiveMembers, - memberActivity: analytics.memberActivity.map((m) => ({ - userId: m.userId, - userName: m.userName, - userEmail: m.userEmail, - role: m.role, - joinedAt: m.joinedAt.toISOString(), - lastActive: m.lastActive?.toISOString() ?? null, - })), } logger.info(`Admin API: Retrieved seat analytics for organization ${organizationId}`) diff --git a/apps/sim/app/api/v1/admin/types.ts b/apps/sim/app/api/v1/admin/types.ts index 3cdbfc46d27..eb3ce167e0a 100644 --- a/apps/sim/app/api/v1/admin/types.ts +++ b/apps/sim/app/api/v1/admin/types.ts @@ -543,7 +543,6 @@ export interface AdminMemberDetail extends AdminMember { // Billing/usage info from userStats currentPeriodCost: string currentUsageLimit: string | null - lastActive: string | null billingBlocked: boolean } @@ -574,28 +573,15 @@ interface AdminUserBilling { userEmail: string stripeCustomerId: string | null // Usage stats - totalManualExecutions: number - totalApiCalls: number - totalWebhookTriggers: number - totalScheduledExecutions: number - totalChatExecutions: number - totalMcpExecutions: number - totalA2aExecutions: number - totalTokensUsed: number - totalCost: string currentUsageLimit: string | null currentPeriodCost: string lastPeriodCost: string | null billedOverageThisPeriod: string storageUsedBytes: number - lastActive: string | null billingBlocked: boolean - // Copilot usage - totalCopilotCost: string + // Copilot usage (active per-period baselines) currentPeriodCopilotCost: string lastPeriodCopilotCost: string | null - totalCopilotTokens: number - totalCopilotCalls: number } export interface AdminUserBillingWithSubscription extends AdminUserBilling { @@ -643,16 +629,6 @@ export interface AdminSeatAnalytics { subscriptionPlan: string canAddSeats: boolean utilizationRate: number - activeMembers: number - inactiveMembers: number - memberActivity: Array<{ - userId: string - userName: string - userEmail: string - role: string - joinedAt: string - lastActive: string | null - }> } export interface AdminDeploymentVersion { diff --git a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts index 7c080a4e6e5..85a5f69b63a 100644 --- a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts +++ b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts @@ -29,6 +29,7 @@ import { } from '@/lib/api/contracts/v1/admin' import { parseRequest } from '@/lib/api/server' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' +import { getUserUsageData } from '@/lib/billing/core/usage' import { isOrgScopedSubscription } from '@/lib/billing/subscriptions/utils' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { withAdminAuthParams } from '@/app/api/v1/admin/middleware' @@ -78,6 +79,11 @@ export const GET = withRouteHandler( const [stats] = await db.select().from(userStats).where(eq(userStats.userId, userId)).limit(1) + // currentPeriodCost is now only a baseline; canonical current-period usage + // (baseline + attributed usage_log, refresh-adjusted) comes from the same + // helper users see, so admin reflects real usage instead of a stale 0. + const usage = await getUserUsageData(userId) + const memberOrgs = await db .select({ organizationId: member.organizationId, @@ -107,27 +113,14 @@ export const GET = withRouteHandler( userName: userData.name, userEmail: userData.email, stripeCustomerId: userData.stripeCustomerId, - totalManualExecutions: stats?.totalManualExecutions ?? 0, - totalApiCalls: stats?.totalApiCalls ?? 0, - totalWebhookTriggers: stats?.totalWebhookTriggers ?? 0, - totalScheduledExecutions: stats?.totalScheduledExecutions ?? 0, - totalChatExecutions: stats?.totalChatExecutions ?? 0, - totalMcpExecutions: stats?.totalMcpExecutions ?? 0, - totalA2aExecutions: stats?.totalA2aExecutions ?? 0, - totalTokensUsed: stats?.totalTokensUsed ?? 0, - totalCost: stats?.totalCost ?? '0', currentUsageLimit: stats?.currentUsageLimit ?? null, - currentPeriodCost: stats?.currentPeriodCost ?? '0', + currentPeriodCost: usage.currentUsage.toString(), lastPeriodCost: stats?.lastPeriodCost ?? null, billedOverageThisPeriod: stats?.billedOverageThisPeriod ?? '0', storageUsedBytes: stats?.storageUsedBytes ?? 0, - lastActive: stats?.lastActive?.toISOString() ?? null, billingBlocked: stats?.billingBlocked ?? false, - totalCopilotCost: stats?.totalCopilotCost ?? '0', currentPeriodCopilotCost: stats?.currentPeriodCopilotCost ?? '0', lastPeriodCopilotCost: stats?.lastPeriodCopilotCost ?? null, - totalCopilotTokens: stats?.totalCopilotTokens ?? 0, - totalCopilotCalls: stats?.totalCopilotCalls ?? 0, subscriptions: subscriptions.map(toAdminSubscription), organizationMemberships: memberOrgs.map((m) => ({ organizationId: m.organizationId, diff --git a/apps/sim/app/api/v1/copilot/chat/route.test.ts b/apps/sim/app/api/v1/copilot/chat/route.test.ts new file mode 100644 index 00000000000..fef0a7eec88 --- /dev/null +++ b/apps/sim/app/api/v1/copilot/chat/route.test.ts @@ -0,0 +1,26 @@ +/** + * Tests for the deprecated v1 copilot chat API route + * + * @vitest-environment node + */ +import { NextRequest } from 'next/server' +import { describe, expect, it } from 'vitest' +import { POST } from '@/app/api/v1/copilot/chat/route' + +const URL = 'http://localhost:3000/api/v1/copilot/chat' + +describe('Deprecated v1 copilot chat route', () => { + it('POST returns 410 with a success:false error body', async () => { + const request = new NextRequest(URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'x-api-key': 'sk-test' }, + body: JSON.stringify({ message: 'hello' }), + }) + const response = await POST(request) + expect(response.status).toBe(410) + + const body = (await response.json()) as { success?: boolean; error?: string } + expect(body.success).toBe(false) + expect(body.error).toContain('deprecated') + }) +}) diff --git a/apps/sim/app/api/v1/copilot/chat/route.ts b/apps/sim/app/api/v1/copilot/chat/route.ts index 6eaa1424b77..ac3759d96fc 100644 --- a/apps/sim/app/api/v1/copilot/chat/route.ts +++ b/apps/sim/app/api/v1/copilot/chat/route.ts @@ -1,151 +1,18 @@ -import { createLogger } from '@sim/logger' -import { toError } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' -import { type NextRequest, NextResponse } from 'next/server' -import { v1CopilotChatContract } from '@/lib/api/contracts/v1/copilot' -import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' -import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless' +import { NextResponse } from 'next/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { getWorkflowById, resolveWorkflowIdForUser } from '@/lib/workflows/utils' -import { authenticateRequest } from '@/app/api/v1/middleware' - -export const maxDuration = 3600 - -const logger = createLogger('CopilotHeadlessAPI') -const DEFAULT_COPILOT_MODEL = 'claude-opus-4-6' /** * POST /api/v1/copilot/chat - * Headless copilot endpoint for server-side orchestration. * - * workflowId is optional - if not provided: - * - If workflowName is provided, finds that workflow - * - If exactly one workflow is available, uses that workflow as context - * - Otherwise requires workflowId or workflowName to disambiguate + * Deprecated: the v1 headless copilot chat API has been removed. The endpoint + * returns 410 Gone for all callers. */ -export const POST = withRouteHandler(async (req: NextRequest) => { - let messageId: string | undefined - const authorized = await authenticateRequest(req, 'copilot-chat') - if (authorized instanceof NextResponse) { - return authorized - } - const { userId, rateLimit } = authorized - const auth = { - authenticated: true as const, - userId, - keyType: rateLimit.keyType, - workspaceId: rateLimit.workspaceId, - } - - try { - const parsedRequest = await parseRequest( - v1CopilotChatContract, - req, - {}, - { - validationErrorResponse: (error) => - NextResponse.json( - { - success: false, - error: getValidationErrorMessage(error, 'Invalid request'), - details: error.issues, - }, - { status: 400 } - ), - invalidJsonResponse: () => - NextResponse.json({ success: false, error: 'Invalid request' }, { status: 400 }), - } - ) - if (!parsedRequest.success) return parsedRequest.response - - const parsed = parsedRequest.data.body - const selectedModel = parsed.model || DEFAULT_COPILOT_MODEL - - // Resolve workflow ID - const resolved = await resolveWorkflowIdForUser( - auth.userId, - parsed.workflowId, - parsed.workflowName, - auth.keyType === 'workspace' ? auth.workspaceId : undefined - ) - if (resolved.status !== 'resolved') { - return NextResponse.json( - { - success: false, - error: resolved.message, - }, - { status: 400 } - ) - } - - if (auth.keyType === 'workspace' && auth.workspaceId) { - const workflow = await getWorkflowById(resolved.workflowId) - if (!workflow?.workspaceId || workflow.workspaceId !== auth.workspaceId) { - return NextResponse.json( - { success: false, error: 'API key is not authorized for this workspace' }, - { status: 403 } - ) - } - } - - // Transform mode to transport mode (same as client API) - // build and agent both map to 'agent' on the backend - const effectiveMode = parsed.mode === 'agent' ? 'build' : parsed.mode - const transportMode = effectiveMode === 'build' ? 'agent' : effectiveMode - - // Always generate a chatId - required for artifacts system to work with subagents - const chatId = parsed.chatId || generateId() - - messageId = generateId() - logger.info( - messageId - ? `Received headless copilot chat start request [messageId:${messageId}]` - : 'Received headless copilot chat start request', - { - workflowId: resolved.workflowId, - workflowName: parsed.workflowName, - chatId, - mode: transportMode, - autoExecuteTools: parsed.autoExecuteTools, - timeout: parsed.timeout, - } - ) - const requestPayload = { - message: parsed.message, - workflowId: resolved.workflowId, - userId: auth.userId, - model: selectedModel, - mode: transportMode, - messageId, - chatId, - } - - const result = await runHeadlessCopilotLifecycle(requestPayload, { - userId: auth.userId, - workflowId: resolved.workflowId, - chatId, - goRoute: '/api/mcp', - autoExecuteTools: parsed.autoExecuteTools, - timeout: parsed.timeout, - interactive: false, - }) - - return NextResponse.json({ - success: result.success, - content: result.content, - toolCalls: result.toolCalls, - chatId: result.chatId || chatId, - error: result.error, - }) - } catch (error) { - logger.error( - messageId - ? `Headless copilot request failed [messageId:${messageId}]` - : 'Headless copilot request failed', - { - error: toError(error).message, - } - ) - return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }) - } -}) +export const POST = withRouteHandler(async () => + NextResponse.json( + { + success: false, + error: 'The v1 copilot chat API has been deprecated and is no longer available.', + }, + { status: 410, headers: { 'Cache-Control': 'no-store' } } + ) +) diff --git a/apps/sim/app/api/v1/logs/[id]/route.ts b/apps/sim/app/api/v1/logs/[id]/route.ts index c32acfd444c..9858a1b4e24 100644 --- a/apps/sim/app/api/v1/logs/[id]/route.ts +++ b/apps/sim/app/api/v1/logs/[id]/route.ts @@ -7,6 +7,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { v1GetLogContract } from '@/lib/api/contracts/v1/logs' import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { materializeExecutionData } from '@/lib/logs/execution/trace-store' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, @@ -50,7 +51,7 @@ export const GET = withRouteHandler( endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, executionData: workflowExecutionLogs.executionData, - cost: workflowExecutionLogs.cost, + costTotal: workflowExecutionLogs.costTotal, files: workflowExecutionLogs.files, createdAt: workflowExecutionLogs.createdAt, workflowName: workflow.name, @@ -101,8 +102,15 @@ export const GET = withRouteHandler( totalDurationMs: log.totalDurationMs, files: log.files || undefined, workflow: workflowSummary, - executionData: log.executionData as any, - cost: log.cost as any, + executionData: (await materializeExecutionData( + log.executionData as Record | null, + { + workspaceId: log.workspaceId, + workflowId: log.workflowId, + executionId: log.executionId, + } + )) as any, + cost: log.costTotal != null ? { total: Number(log.costTotal) } : null, createdAt: log.createdAt.toISOString(), } diff --git a/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts b/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts index e7503ecb071..eefad39bb80 100644 --- a/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts +++ b/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts @@ -70,7 +70,9 @@ export const GET = withRouteHandler( startedAt: workflowLog.startedAt.toISOString(), endedAt: workflowLog.endedAt?.toISOString(), totalDurationMs: workflowLog.totalDurationMs, - cost: workflowLog.cost || null, + // Sourced from the cost_total projection of the usage_log ledger + // (the deprecated cost jsonb column was dropped). + cost: workflowLog.costTotal != null ? { total: Number(workflowLog.costTotal) } : null, }, } diff --git a/apps/sim/app/api/v1/logs/filters.ts b/apps/sim/app/api/v1/logs/filters.ts index 4f9bdbdfc6c..0e409e4d53f 100644 --- a/apps/sim/app/api/v1/logs/filters.ts +++ b/apps/sim/app/api/v1/logs/filters.ts @@ -84,18 +84,19 @@ export function buildLogFilters(filters: LogFilters): SQL { conditions.push(lte(workflowExecutionLogs.totalDurationMs, filters.maxDurationMs)) } - // Cost filters + // Cost filters — indexed projection of the usage_log ledger (dollars). if (filters.minCost !== undefined) { - conditions.push(sql`(${workflowExecutionLogs.cost}->>'total')::numeric >= ${filters.minCost}`) + conditions.push(sql`${workflowExecutionLogs.costTotal} >= ${filters.minCost}`) } if (filters.maxCost !== undefined) { - conditions.push(sql`(${workflowExecutionLogs.cost}->>'total')::numeric <= ${filters.maxCost}`) + conditions.push(sql`${workflowExecutionLogs.costTotal} <= ${filters.maxCost}`) } - // Model filter + // Model filter — uses the models_used projection (includes zero-cost/BYOK + // models, which the usage_log ledger drops), preserving prior behavior. if (filters.model) { - conditions.push(sql`${workflowExecutionLogs.cost}->>'models' ? ${filters.model}`) + conditions.push(sql`${workflowExecutionLogs.modelsUsed} @> ARRAY[${filters.model}]::text[]`) } // Combine all conditions with AND diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index 0f8f7b31b82..c85031e3cc4 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -6,7 +6,9 @@ import { and, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1ListLogsContract } from '@/lib/api/contracts/v1/logs' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' +import { MATERIALIZE_CONCURRENCY, mapWithConcurrency } from '@/lib/core/utils/concurrency' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { materializeExecutionData } from '@/lib/logs/execution/trace-store' import { buildLogFilters, getOrderBy } from '@/app/api/v1/logs/filters' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { @@ -103,6 +105,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { .select({ id: workflowExecutionLogs.id, workflowId: workflowExecutionLogs.workflowId, + workspaceId: workflowExecutionLogs.workspaceId, executionId: workflowExecutionLogs.executionId, deploymentVersionId: workflowExecutionLogs.deploymentVersionId, level: workflowExecutionLogs.level, @@ -110,7 +113,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { startedAt: workflowExecutionLogs.startedAt, endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, - cost: workflowExecutionLogs.cost, + costTotal: workflowExecutionLogs.costTotal, files: workflowExecutionLogs.files, executionData: params.details === 'full' ? workflowExecutionLogs.executionData : sql`null`, workflowName: workflow.name, @@ -144,7 +147,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => { }) } - const formattedLogs = data.map((log) => { + // Only materialize externalized execution data when the response actually + // needs it (details=full + finalOutput/traceSpans requested). + const needsMaterialize = + params.details === 'full' && (params.includeFinalOutput || params.includeTraceSpans) + + const buildBase = (log: (typeof data)[number]) => { const result: any = { id: log.id, workflowId: log.workflowId, @@ -155,7 +163,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { startedAt: log.startedAt.toISOString(), endedAt: log.endedAt?.toISOString() || null, totalDurationMs: log.totalDurationMs, - cost: log.cost ? { total: (log.cost as any).total } : null, + cost: log.costTotal != null ? { total: Number(log.costTotal) } : null, files: log.files || null, } @@ -166,24 +174,36 @@ export const GET = withRouteHandler(async (request: NextRequest) => { description: log.workflowDescription, deleted: !log.workflowName, } - - if (log.cost) { - result.cost = log.cost - } - - if (log.executionData) { - const execData = log.executionData as any - if (params.includeFinalOutput && execData.finalOutput) { - result.finalOutput = execData.finalOutput - } - if (params.includeTraceSpans && execData.traceSpans) { - result.traceSpans = execData.traceSpans - } - } } return result - }) + } + + // Only run the bounded-concurrency materialization when the response actually + // needs object-storage reads; otherwise a plain synchronous map avoids the + // per-row worker/promise overhead. + const formattedLogs = needsMaterialize + ? await mapWithConcurrency(data, MATERIALIZE_CONCURRENCY, async (log) => { + const result = buildBase(log) + if (log.executionData) { + const execData = (await materializeExecutionData( + log.executionData as Record | null, + { + workspaceId: log.workspaceId, + workflowId: log.workflowId, + executionId: log.executionId, + } + )) as any + if (params.includeFinalOutput && execData.finalOutput) { + result.finalOutput = execData.finalOutput + } + if (params.includeTraceSpans && execData.traceSpans) { + result.traceSpans = execData.traceSpans + } + } + return result + }) + : data.map(buildBase) const limits = await getUserLimits(userId) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 4740909c2f5..80f34950ed2 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -27,7 +27,10 @@ import { } from '@/lib/webhooks/provider-subscriptions' import { getProviderHandler } from '@/lib/webhooks/providers' import { mergeNonUserFields } from '@/lib/webhooks/utils' -import { syncWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' +import { + findConflictingWebhookPathOwner, + syncWebhooksForCredentialSet, +} from '@/lib/webhooks/utils.server' import { extractCredentialSetId, isCredentialSetValue } from '@/executor/constants' const logger = createLogger('WebhooksAPI') @@ -330,21 +333,31 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } if (!targetWebhookId) { - const existingByPath = await db - .select({ id: webhook.id, workflowId: webhook.workflowId }) + const conflictingOwner = await findConflictingWebhookPathOwner({ + path: finalPath, + workflowId, + }) + if (conflictingOwner) { + logger.warn(`[${requestId}] Webhook path conflict: ${finalPath}`) + return NextResponse.json( + { error: 'Webhook path already exists.', code: 'PATH_EXISTS' }, + { status: 409 } + ) + } + + const ownExisting = await db + .select({ id: webhook.id }) .from(webhook) - .where(and(eq(webhook.path, finalPath), isNull(webhook.archivedAt))) - .limit(1) - if (existingByPath.length > 0) { - // If a webhook with the same path exists but belongs to a different workflow, return an error - if (existingByPath[0].workflowId !== workflowId) { - logger.warn(`[${requestId}] Webhook path conflict: ${finalPath}`) - return NextResponse.json( - { error: 'Webhook path already exists.', code: 'PATH_EXISTS' }, - { status: 409 } + .where( + and( + eq(webhook.path, finalPath), + eq(webhook.workflowId, workflowId), + isNull(webhook.archivedAt) ) - } - targetWebhookId = existingByPath[0].id + ) + .limit(1) + if (ownExisting.length > 0) { + targetWebhookId = ownExisting[0].id } } diff --git a/apps/sim/app/api/workflows/[id]/executions/[executionId]/route.ts b/apps/sim/app/api/workflows/[id]/executions/[executionId]/route.ts index 6efff82a7cc..fbbdb0abb5f 100644 --- a/apps/sim/app/api/workflows/[id]/executions/[executionId]/route.ts +++ b/apps/sim/app/api/workflows/[id]/executions/[executionId]/route.ts @@ -9,6 +9,7 @@ import { } from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { materializeExecutionData } from '@/lib/logs/execution/trace-store' import { validateWorkflowAccess } from '@/app/api/workflows/middleware' import type { PausePoint } from '@/executor/types' @@ -117,6 +118,7 @@ export const GET = withRouteHandler( .select({ executionId: workflowExecutionLogs.executionId, workflowId: workflowExecutionLogs.workflowId, + workspaceId: workflowExecutionLogs.workspaceId, status: workflowExecutionLogs.status, level: workflowExecutionLogs.level, trigger: workflowExecutionLogs.trigger, @@ -124,7 +126,7 @@ export const GET = withRouteHandler( endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, executionData: workflowExecutionLogs.executionData, - cost: workflowExecutionLogs.cost, + costTotal: workflowExecutionLogs.costTotal, }) .from(workflowExecutionLogs) .where( @@ -177,13 +179,20 @@ export const GET = withRouteHandler( } } - const cost = logRow.cost - ? { total: Number((logRow.cost as { total?: number }).total ?? 0) } - : null + const cost = logRow.costTotal != null ? { total: Number(logRow.costTotal) } : null - const error = status === 'failed' ? extractError(logRow.executionData) : null + // Heavy execution data may live in object storage; resolve the pointer + // before reading error / finalOutput / traceSpans (no-op for inline rows). + const executionData = (await materializeExecutionData( + logRow.executionData as Record | null, + { + workspaceId: logRow.workspaceId, + workflowId: logRow.workflowId, + executionId: logRow.executionId, + } + )) as ExecutionDataShape | undefined - const executionData = logRow.executionData as ExecutionDataShape | undefined + const error = status === 'failed' ? extractError(executionData) : null const finalOutput = includeOutput && status === 'completed' && executionData diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options-bar/resource-options-bar.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options-bar/resource-options-bar.tsx index 94d1ed103f9..a675a9cc580 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options-bar/resource-options-bar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-options-bar/resource-options-bar.tsx @@ -1,4 +1,4 @@ -import { memo, type ReactNode } from 'react' +import { memo, type ReactNode, useState } from 'react' import * as PopoverPrimitive from '@radix-ui/react-popover' import { ArrowDown, @@ -14,12 +14,15 @@ import { Search, X, } from '@/components/emcn' +import { POPOVER_ANIMATION_CLASSES } from '@/components/emcn/components/chip-date-picker/chip-date-picker' import { cn } from '@/lib/core/utils/cn' const SEARCH_ICON = ( ) +const RESOURCE_MENU_EDGE_OFFSET = 6 + type SortDirection = 'asc' | 'desc' export interface ColumnOption { @@ -73,6 +76,10 @@ interface ResourceOptionsBarProps { filterActive?: boolean filterTags?: FilterTag[] extras?: ReactNode + /** Right-aligned slot. Unlike `extras` (which sits with the left controls), + * `trailing` is pushed to the far right via `justify-between` — used for the + * table's run/stop control opposite the left-aligned filter/sort. */ + trailing?: ReactNode } export const ResourceOptionsBar = memo(function ResourceOptionsBar({ @@ -83,9 +90,24 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({ filterActive, filterTags, extras, + trailing, }: ResourceOptionsBarProps) { + /** + * Coordinates the Filter popover and Sort menu as a single menu bar: clicking + * one while the other is open switches to it in a single click. Functional + * updates make the close→open ordering race-proof, so whichever menu the click + * targets wins regardless of which `onOpenChange` fires first. + */ + const [openMenu, setOpenMenu] = useState<'filter' | 'sort' | null>(null) + const hasContent = - search || sort || filter || onFilterToggle || extras || (filterTags && filterTags.length > 0) + search || + sort || + filter || + onFilterToggle || + extras || + trailing || + (filterTags && filterTags.length > 0) if (!hasContent) return null return ( @@ -123,26 +145,52 @@ export const ResourceOptionsBar = memo(function ResourceOptionsBar({ Filter ) : filter ? ( - - - - + + setOpenMenu((current) => (open ? 'filter' : current === 'filter' ? null : current)) + } + > + +
+ + + + {sort && ( + + setOpenMenu((current) => + open ? 'sort' : current === 'sort' ? null : current + ) + } + /> + )} +
+
{filter}
) : null} - {sort && } + {sort && (onFilterToggle || !filter) && }

+ {trailing &&
{trailing}
}
) @@ -200,11 +248,19 @@ const SearchSection = memo(function SearchSection({ search }: { search: SearchCo ) }) -const SortDropdown = memo(function SortDropdown({ config }: { config: SortConfig }) { +interface SortDropdownProps { + config: SortConfig + /** Controlled open state — omit for standalone (uncontrolled) usage. */ + open?: boolean + /** Controlled open-change handler, paired with {@link SortDropdownProps.open}. */ + onOpenChange?: (open: boolean) => void +} + +const SortDropdown = memo(function SortDropdown({ config, open, onOpenChange }: SortDropdownProps) { const { options, active, onSort, onClear } = config return ( - + + )} + + { + if (value !== 'all' && value !== 'enabled' && value !== 'disabled') return + setEnabledFilter(value) setCurrentPage(1) setSelectedDocuments(new Set()) setIsSelectAllMode(false) }} - overlayContent={ - {enabledDisplayLabel} - } - showAllOption - allOptionLabel='All' - size='sm' - className='h-[32px] w-full rounded-md' + align='start' + fullWidth + flush /> - {enabledFilter.length > 0 && ( - - )} - + ), - [enabledFilter, enabledDisplayLabel, tagDefinitions, tagFilterEntries] + [enabledFilter, tagDefinitions, tagFilterEntries] ) const connectorBadges = @@ -975,15 +966,12 @@ export function KnowledgeBase({ const filterTags: FilterTag[] = useMemo( () => [ - ...(enabledFilter.length > 0 + ...(enabledFilter !== 'all' ? [ { - label: - enabledFilter.length === 1 - ? `Status: ${enabledFilter[0] === 'enabled' ? 'Enabled' : 'Disabled'}` - : 'Status: 2 selected', + label: `Status: ${enabledFilter === 'enabled' ? 'Enabled' : 'Disabled'}`, onRemove: () => { - setEnabledFilter([]) + setEnabledFilter('all') setCurrentPage(1) setSelectedDocuments(new Set()) setIsSelectAllMode(false) @@ -1129,7 +1117,7 @@ export function KnowledgeBase({ const emptyMessage = searchQuery ? 'No documents found' - : enabledFilter.length > 0 || activeTagFilters.length > 0 + : enabledFilter !== 'all' || activeTagFilters.length > 0 ? 'Nothing matches your filter' : undefined @@ -1441,6 +1429,18 @@ export function KnowledgeBase({ ) } +/** + * Sizes the filter popover to its content with pure CSS `max-content` (clamped to + * `[280, 420]`). Because the padding box is part of `max-content`, the `p-3` + * inset is preserved on every edge — there is no separate measured/animated outer + * layer that can disagree by a few pixels and clip the right padding. The width + * still adapts to the active filters; it just resizes instantly rather than + * animating. + */ +function AutoWidthPanel({ children }: { children: ReactNode }) { + return
{children}
+} + interface TagFilterEntry { id: string tagName: string @@ -1467,13 +1467,97 @@ interface TagFilterSectionProps { onChange: (entries: TagFilterEntry[]) => void } +interface TagFilterValueControlProps { + entry: TagFilterEntry + onChange: (patch: Partial) => void +} + /** - * Tag filter section rendered inside the combined filter popover + * Renders the value input for a knowledge base tag filter row. + */ +function TagFilterValueControl({ entry, onChange }: TagFilterValueControlProps) { + const isBetween = entry.operator === 'between' + + if (entry.fieldType === 'date') { + if (isBetween) { + return ( +
+ onChange({ value })} + placeholder='From' + fullWidth + flush + /> + to + onChange({ valueTo: value })} + placeholder='To' + fullWidth + flush + /> +
+ ) + } + + return ( + onChange({ value })} + placeholder='Select date' + fullWidth + flush + /> + ) + } + + if (isBetween) { + return ( +
+ onChange({ value: event.target.value })} + placeholder='From' + /> + to + onChange({ valueTo: event.target.value })} + placeholder='To' + /> +
+ ) + } + + return ( + onChange({ value: event.target.value })} + placeholder={ + entry.fieldType === 'boolean' + ? 'true or false' + : entry.fieldType === 'number' + ? 'Enter number' + : 'Enter value' + } + /> + ) +} + +/** + * Tag filter section rendered inside the combined filter popover. */ function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectionProps) { const activeCount = entries.filter((f) => f.tagSlot && f.value.trim()).length - const tagOptions: ComboboxOption[] = tagDefinitions.map((t) => ({ + const tagOptions: ChipDropdownOption[] = tagDefinitions.map((t) => ({ value: t.displayName, label: t.displayName, })) @@ -1483,6 +1567,21 @@ function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectio [entries] ) + const scrollRef = useRef(null) + const prevCountRef = useRef(filtersToShow.length) + + useEffect(() => { + if (filtersToShow.length > prevCountRef.current) { + const el = scrollRef.current + if (el) { + requestAnimationFrame(() => { + el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }) + }) + } + } + prevCountRef.current = filtersToShow.length + }, [filtersToShow.length]) + const updateEntry = (id: string, patch: Partial) => { const existing = filtersToShow.find((e) => e.id === id) if (!existing) return @@ -1516,130 +1615,94 @@ function TagFilterSection({ tagDefinitions, entries, onChange }: TagFilterSectio if (tagDefinitions.length === 0) return null return ( -
-
- - Filter by tags - -
- {activeCount > 0 && ( - - )} - -
+ )}
-
- {filtersToShow.map((entry) => { +
+ {filtersToShow.map((entry, index) => { const operators = getOperatorsForFieldType(entry.fieldType) - const operatorOptions: ComboboxOption[] = operators.map((op) => ({ + const operatorOptions: ChipDropdownOption[] = operators.map((op) => ({ value: op.value, label: op.label, })) - const isBetween = entry.operator === 'between' return ( -
-
- +
+ {index > 0 && ( +
+ + and + +
+
+ )} +
+
+ handleTagChange(entry.id, value)} + placeholder='Select tag' + align='start' + matchTriggerWidth={false} + contentClassName='max-h-[240px] overflow-y-auto' + className='max-w-[150px]' + flush + /> + {entry.tagSlot && ( + updateEntry(entry.id, { operator: value, valueTo: '' })} + placeholder='Operator' + align='start' + matchTriggerWidth={false} + flush + /> + )} +
- handleTagChange(entry.id, v)} - placeholder='Select tag' - /> - {entry.tagSlot && ( - <> - - updateEntry(entry.id, { operator: v, valueTo: '' })} - placeholder='Select operator' - /> - - - {entry.fieldType === 'date' ? ( - isBetween ? ( -
- updateEntry(entry.id, { value: v })} - placeholder='From' - /> - to - updateEntry(entry.id, { valueTo: v })} - placeholder='To' - /> -
- ) : ( - updateEntry(entry.id, { value: v })} - placeholder='Select date' - /> - ) - ) : isBetween ? ( -
- updateEntry(entry.id, { value: e.target.value })} - placeholder='From' - className='h-[28px] text-caption' - /> - to - updateEntry(entry.id, { valueTo: e.target.value })} - placeholder='To' - className='h-[28px] text-caption' - /> -
- ) : ( - updateEntry(entry.id, { value: e.target.value })} - placeholder={ - entry.fieldType === 'boolean' - ? 'true or false' - : entry.fieldType === 'number' - ? 'Enter number' - : 'Enter value' - } - className='h-[28px] text-caption' - /> - )} - + updateEntry(entry.id, patch)} + /> )}
) })}
+ +
) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx index b802e4b9924..14bbed274f0 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx @@ -3,8 +3,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { useParams, useRouter } from 'next/navigation' -import type { ComboboxOption } from '@/components/emcn' -import { Combobox, Tooltip } from '@/components/emcn' +import type { ChipDropdownOption, ChipMultiSelectOption } from '@/components/emcn' +import { Button, ChipDropdown, ChipMultiSelect, Tooltip } from '@/components/emcn' import { Database } from '@/components/emcn/icons' import type { KnowledgeBaseData } from '@/lib/knowledge/types' import type { @@ -58,6 +58,20 @@ const COLUMNS: ResourceColumn[] = [ const DATABASE_ICON = +const CONNECTOR_FILTER_OPTIONS: ChipDropdownOption[] = [ + { value: 'all', label: 'All' }, + { value: 'connected', label: 'With connectors' }, + { value: 'unconnected', label: 'Without connectors' }, +] + +const CONTENT_FILTER_OPTIONS: ChipDropdownOption[] = [ + { value: 'all', label: 'All' }, + { value: 'has-docs', label: 'Has documents' }, + { value: 'empty', label: 'Empty' }, +] + +const FILTER_SECTION_LABEL_CLASS = 'text-[var(--text-muted)] text-small' + function connectorCell(connectorTypes?: string[]): ResourceCell { if (!connectorTypes || connectorTypes.length === 0) { return { label: EMPTY_CELL_PLACEHOLDER } @@ -409,28 +423,7 @@ export function Knowledge() { [activeSort] ) - const connectorDisplayLabel = useMemo(() => { - if (connectorFilter.length === 0) return 'All' - if (connectorFilter.length === 1) - return connectorFilter[0] === 'connected' ? 'With connectors' : 'Without connectors' - return `${connectorFilter.length} selected` - }, [connectorFilter]) - - const contentDisplayLabel = useMemo(() => { - if (contentFilter.length === 0) return 'All' - if (contentFilter.length === 1) - return contentFilter[0] === 'has-docs' ? 'Has documents' : 'Empty' - return `${contentFilter.length} selected` - }, [contentFilter]) - - const ownerDisplayLabel = useMemo(() => { - if (ownerFilter.length === 0) return 'All' - if (ownerFilter.length === 1) - return members?.find((m) => m.userId === ownerFilter[0])?.name ?? '1 member' - return `${ownerFilter.length} members` - }, [ownerFilter, members]) - - const memberOptions: ComboboxOption[] = useMemo( + const memberOptions: ChipMultiSelectOption[] = useMemo( () => (members ?? []).map((m) => ({ value: m.userId, @@ -451,95 +444,83 @@ export function Knowledge() { [members] ) - const hasActiveFilters = - connectorFilter.length > 0 || contentFilter.length > 0 || ownerFilter.length > 0 - const filterContent = useMemo( () => ( -
-
- Connectors - {connectorDisplayLabel} - } - showAllOption - allOptionLabel='All' - size='sm' - className='h-[32px] w-full rounded-md' +
+
+
+ Connectors + {connectorFilter.length > 0 && ( + + )} +
+ setConnectorFilter(value === 'all' ? [] : [value])} + align='start' + fullWidth + flush />
-
- Content - {contentDisplayLabel} - } - showAllOption - allOptionLabel='All' - size='sm' - className='h-[32px] w-full rounded-md' +
+
+ Content + {contentFilter.length > 0 && ( + + )} +
+ setContentFilter(value === 'all' ? [] : [value])} + align='start' + fullWidth + flush />
{memberOptions.length > 0 && ( -
- Owner - +
+ Owner + {ownerFilter.length > 0 && ( + + )} +
+ {ownerDisplayLabel} - } + values={ownerFilter} + onChange={setOwnerFilter} + allLabel='All' searchable searchPlaceholder='Search members...' - showAllOption - allOptionLabel='All' - size='sm' - className='h-[32px] w-full rounded-md' + align='start' + fullWidth + flush />
)} - {hasActiveFilters && ( - - )}
), - [ - connectorFilter, - contentFilter, - ownerFilter, - memberOptions, - connectorDisplayLabel, - contentDisplayLabel, - ownerDisplayLabel, - hasActiveFilters, - ] + [connectorFilter, contentFilter, ownerFilter, memberOptions] ) const filterTags: FilterTag[] = useMemo(() => { diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx index af7241ac38f..f61fa8bc600 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx @@ -55,6 +55,13 @@ const MIN_BAR_PCT = 0.5 interface TraceViewProps { traceSpans: TraceSpan[] + /** + * Authoritative, multiplier-inclusive run cost (dollars) from the persisted + * execution log. When provided it drives the header credit chip so the Trace + * tab and the Overview cost breakdown can never show different totals. Falls + * back to the root span's own cost only when absent (e.g. live previews). + */ + runCostDollars?: number } interface FlatSpanEntry { @@ -706,8 +713,10 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa if (cacheRead) metaEntries.push({ label: 'Cache read', value: cacheRead }) if (cacheWrite) metaEntries.push({ label: 'Cache write', value: cacheWrite }) if (reasoning) metaEntries.push({ label: 'Reasoning tokens', value: reasoning }) - const costTotal = formatCostAmount(span.cost?.total) - if (costTotal) metaEntries.push({ label: 'Cost', value: costTotal }) + // Per-span cost is intentionally not shown: cost lives only in the usage_log + // ledger (the authoritative, multiplier-inclusive run total drives the header + // chip). Persisted spans are cost-stripped, so a per-span row would render on + // live runs but vanish on reload — show one consistent total instead. if (span.errorType) metaEntries.push({ label: 'Error type', value: span.errorType }) if (span.iterationIndex !== undefined) metaEntries.push({ label: 'Iteration', value: String(span.iterationIndex + 1) }) @@ -808,7 +817,7 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa * in a way that mirrors the executor's internal structure so investigators can * follow block-by-block and segment-by-segment what happened and why. */ -export const TraceView = memo(function TraceView({ traceSpans }: TraceViewProps) { +export const TraceView = memo(function TraceView({ traceSpans, runCostDollars }: TraceViewProps) { const treeRef = useRef(null) const [searchQuery, setSearchQuery] = useState('') const [treePaneWidth, setTreePaneWidth] = useState(DEFAULT_TREE_PANE_WIDTH) @@ -1017,7 +1026,7 @@ export const TraceView = memo(function TraceView({ traceSpans }: TraceViewProps) {blockCount} {blockCount === 1 ? 'span' : 'spans'} {(() => { - const rootCost = formatCostAmount(normalizedSpans[0]?.cost?.total) + const rootCost = formatCostAmount(runCostDollars ?? normalizedSpans[0]?.cost?.total) return rootCost ? ( {rootCost} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx index 1eb7d8ba81d..549a7e35e41 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/log-details.tsx @@ -25,6 +25,7 @@ import { } from '@/components/emcn' import type { WorkflowLogRow } from '@/lib/api/contracts/logs' import { BASE_EXECUTION_CHARGE } from '@/lib/billing/constants' +import { apportionCredits, dollarsToCredits } from '@/lib/billing/credits/conversion' import { cn } from '@/lib/core/utils/cn' import { handleKeyboardActivation } from '@/lib/core/utils/keyboard' import { filterHiddenOutputKeys } from '@/lib/logs/execution/trace-spans/trace-spans' @@ -50,6 +51,16 @@ import { formatCost } from '@/providers/utils' import { useLogDetailsUIStore } from '@/stores/logs/store' import { MAX_LOG_DETAILS_WIDTH_RATIO, MIN_LOG_DETAILS_WIDTH } from '@/stores/logs/utils' +/** + * Renders an already-apportioned integer credit value. `dollars` is only used + * to distinguish a genuine zero ("0 credits") from a sub-credit charge that + * rounded down to zero ("<1 credit"); the credit figure itself is authoritative. + */ +function creditLabel(credits: number, dollars: number): string { + if (credits <= 0) return dollars > 0 ? '<1 credit' : '0 credits' + return `${credits.toLocaleString()} ${credits === 1 ? 'credit' : 'credits'}` +} + export const WorkflowOutputSection = memo( function WorkflowOutputSection({ output }: { output: Record }) { const contentRef = useRef(null) @@ -327,6 +338,54 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP return { input: raw } as Record }, [log.executionData]) + // Cost breakdown, sourced solely from the usage_log ledger (single source of + // truth). Line items (Base Run / per-model / per-integration) get integer + // credits apportioned with a single round at the total so rows always + // reconcile (never round-then-sum, which drifts). Pre-ledger runs that only + // have the cost_total projection show the total alone — no itemization, no + // parallel jsonb reconstruction. + const costBreakdown = useMemo((): { + rows: Array<{ key: string; label: string; credits: number; dollars: number }> + totalCredits: number + totalDollars: number + tokens: { input: number; output: number } + } | null => { + const ledger = log.costLedger + if (ledger && ledger.items.length > 0) { + const credits = apportionCredits( + ledger.items.map((item, i) => ({ key: String(i), dollars: item.cost })) + ) + const rows = ledger.items.map((item, i) => ({ + key: String(i), + label: + item.category === 'fixed' && item.description === 'execution_fee' + ? 'Base Run' + : item.description, + credits: credits[String(i)] ?? 0, + dollars: item.cost, + })) + return { + rows, + totalCredits: dollarsToCredits(ledger.total), + totalDollars: ledger.total, + tokens: { + input: ledger.items.reduce((s, it) => s + (it.inputTokens ?? 0), 0), + output: ledger.items.reduce((s, it) => s + (it.outputTokens ?? 0), 0), + }, + } + } + + // Total-only (pre-ledger runs with just the cost_total projection). + const total = log.cost?.total + if (total == null) return null + return { + rows: [], + totalCredits: dollarsToCredits(total), + totalDollars: total, + tokens: { input: 0, output: 0 }, + } + }, [log.costLedger, log.cost]) + const formattedTimestamp = formatDate(log.createdAt) const logStatus = getDisplayStatus(log.status) @@ -530,67 +589,39 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP {log.files && log.files.length > 0 && } {/* Cost Breakdown */} - {hasCostInfo && ( + {hasCostInfo && costBreakdown && (
-
- - Base Run - - - {formatCost(BASE_EXECUTION_CHARGE)} - -
-
- - Model Input - - - {formatCost(log.cost?.input || 0)} - -
-
- - Model Output - - - {formatCost(log.cost?.output || 0)} - -
- {(() => { - const models = (log.cost as Record)?.models as - | Record - | undefined - const totalToolCost = models - ? Object.values(models).reduce((sum, m) => sum + (m?.toolCost || 0), 0) - : 0 - return totalToolCost > 0 ? ( -
- - Tool Usage - - - {formatCost(totalToolCost)} - -
- ) : null - })()} + {costBreakdown.rows.map((row) => ( +
+ + {row.label} + + + {creditLabel(row.credits, row.dollars)} + +
+ ))}
Total - {formatCost(log.cost?.total || 0)} - -
-
- - Tokens - - - {log.cost?.tokens?.input || log.cost?.tokens?.prompt || 0} in ·{' '} - {log.cost?.tokens?.output || log.cost?.tokens?.completion || 0} out + {creditLabel(costBreakdown.totalCredits, costBreakdown.totalDollars)}
+ {(costBreakdown.tokens.input > 0 || costBreakdown.tokens.output > 0) && ( +
+ + Tokens + + + {costBreakdown.tokens.input} in · {costBreakdown.tokens.output} out + +
+ )}

Total includes a {formatCost(BASE_EXECUTION_CHARGE)} base charge plus model and @@ -609,7 +640,7 @@ export function LogDetailsContent({ log, onActiveTabChange }: LogDetailsContentP className='mt-3 min-h-0 flex-1 overflow-hidden focus-visible:outline-none' > {traceSpans?.length ? ( - + ) : log.executionData ? (

diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts index 4f142b0c67d..09ead88ac8d 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-details/utils.ts @@ -1,6 +1,6 @@ import type React from 'react' import { AgentSkillsIcon, WorkflowIcon } from '@/components/icons' -import { dollarsToCredits } from '@/lib/billing/credits/conversion' +import { formatCreditCost } from '@/lib/billing/credits/conversion' import type { TraceSpan } from '@/lib/logs/types' import { LoopTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/loop/loop-config' import { ParallelTool } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/parallel/parallel-config' @@ -117,10 +117,7 @@ export function getDisplayName(span: TraceSpan): string { } export function formatCostAmount(value: number | undefined): string | undefined { - if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) return undefined - const credits = dollarsToCredits(value) - if (credits <= 0) return '<1 credit' - return `${credits.toLocaleString('en-US')} ${credits === 1 ? 'credit' : 'credits'}` + return formatCreditCost(value, { emptyForZeroOrLess: true }) } export function formatTokensSummary(tokens: TraceSpan['tokens']): string | undefined { diff --git a/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx b/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx index f2563a2b37c..0622fafa858 100644 --- a/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx +++ b/apps/sim/app/workspace/[workspaceId]/providers/provider-models-loader.tsx @@ -5,10 +5,13 @@ import { createLogger } from '@sim/logger' import { useParams } from 'next/navigation' import { useProviderModels } from '@/hooks/queries/providers' import { + updateBasetenProviderModels, updateFireworksProviderModels, updateLiteLLMProviderModels, + updateOllamaCloudProviderModels, updateOllamaProviderModels, updateOpenRouterProviderModels, + updateTogetherProviderModels, updateVLLMProviderModels, } from '@/providers/utils' import { type ProviderName, useProvidersStore } from '@/stores/providers' @@ -31,6 +34,8 @@ function useSyncProvider(provider: ProviderName, workspaceId?: string) { try { if (provider === 'ollama') { updateOllamaProviderModels(data.models) + } else if (provider === 'ollama-cloud') { + void updateOllamaCloudProviderModels(data.models) } else if (provider === 'vllm') { updateVLLMProviderModels(data.models) } else if (provider === 'litellm') { @@ -42,6 +47,10 @@ function useSyncProvider(provider: ProviderName, workspaceId?: string) { } } else if (provider === 'fireworks') { void updateFireworksProviderModels(data.models) + } else if (provider === 'together') { + void updateTogetherProviderModels(data.models) + } else if (provider === 'baseten') { + void updateBasetenProviderModels(data.models) } } catch (syncError) { logger.warn(`Failed to sync provider definitions for ${provider}`, syncError as Error) @@ -63,9 +72,12 @@ export function ProviderModelsLoader() { useSyncProvider('base') useSyncProvider('ollama') + useSyncProvider('ollama-cloud', workspaceId) useSyncProvider('vllm') useSyncProvider('litellm') useSyncProvider('openrouter') useSyncProvider('fireworks', workspaceId) + useSyncProvider('together', workspaceId) + useSyncProvider('baseten', workspaceId) return null } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx index 80a1e46849d..27d71cb19a2 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/byok/byok.tsx @@ -17,8 +17,10 @@ import { } from '@/components/emcn' import { AnthropicIcon, + BasetenIcon, BrandfetchIcon, ExaAIIcon, + FindymailIcon, FirecrawlIcon, FireworksIcon, GeminiIcon, @@ -28,11 +30,15 @@ import { JinaAIIcon, LinkupIcon, MistralIcon, + OllamaIcon, OpenAIIcon, ParallelIcon, PeopleDataLabsIcon, PerplexityIcon, + ProspeoIcon, SerperIcon, + TogetherIcon, + WizaIcon, } from '@/components/icons' import { Input } from '@/components/ui' import { @@ -87,6 +93,27 @@ const PROVIDERS: { description: 'LLM calls', placeholder: 'Enter your Fireworks API key', }, + { + id: 'together', + name: 'Together AI', + icon: TogetherIcon, + description: 'LLM calls', + placeholder: 'Enter your Together AI API key', + }, + { + id: 'baseten', + name: 'Baseten', + icon: BasetenIcon, + description: 'LLM calls', + placeholder: 'Enter your Baseten API key', + }, + { + id: 'ollama-cloud', + name: 'Ollama Cloud', + icon: OllamaIcon, + description: 'LLM calls', + placeholder: 'Enter your Ollama API key', + }, { id: 'falai', name: 'Fal.ai', @@ -171,6 +198,27 @@ const PROVIDERS: { description: 'Person and company enrichment, search, and identity', placeholder: 'Enter your People Data Labs API key', }, + { + id: 'findymail', + name: 'Findymail', + icon: FindymailIcon, + description: 'Email finder, verification, and phone lookup', + placeholder: 'Enter your Findymail API key', + }, + { + id: 'prospeo', + name: 'Prospeo', + icon: ProspeoIcon, + description: 'Person and company enrichment and search', + placeholder: 'Enter your Prospeo API key', + }, + { + id: 'wiza', + name: 'Wiza', + icon: WizaIcon, + description: 'Prospect search, individual reveal, and company enrichment', + placeholder: 'Enter your Wiza API key', + }, ] export function BYOK() { diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx index 4905edb3035..d7651bd3c64 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx @@ -53,8 +53,8 @@ export function ColumnConfigSidebar(props: ColumnConfigSidebarProps) { role='dialog' aria-label='Configure column' className={cn( - 'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] shadow-overlay transition-transform duration-200 ease-out', - open ? 'translate-x-0' : 'translate-x-full' + 'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] transition-transform duration-200 ease-out', + open ? 'translate-x-0 shadow-overlay' : 'translate-x-full' )} > {props.config && ( diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx index 2e021797858..257c66861af 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichment-config.tsx @@ -3,7 +3,6 @@ import { useState } from 'react' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' -import { X } from 'lucide-react' import { Badge, Button, @@ -15,7 +14,7 @@ import { Switch, toast, } from '@/components/emcn' -import { ArrowLeft } from '@/components/emcn/icons' +import { ArrowLeft, X } from '@/components/emcn/icons' import type { AddWorkflowGroupBodyInput } from '@/lib/api/contracts/tables' import { cn } from '@/lib/core/utils/cn' import type { ColumnDefinition, WorkflowGroup, WorkflowGroupOutput } from '@/lib/table' diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx index 3594d0ca029..a2575b4b43f 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/enrichments-sidebar/enrichments-sidebar.tsx @@ -1,9 +1,8 @@ 'use client' import { useState } from 'react' -import { X } from 'lucide-react' -import { Button, Input } from '@/components/emcn' -import { Search } from '@/components/emcn/icons' +import { Input } from '@/components/emcn' +import { Search, X } from '@/components/emcn/icons' import { cn } from '@/lib/core/utils/cn' import type { ColumnDefinition, WorkflowGroup } from '@/lib/table' import { ALL_ENRICHMENTS } from '@/enrichments' @@ -33,8 +32,8 @@ export function EnrichmentsSidebar({ open, ...rest }: EnrichmentsSidebarProps) { role='dialog' aria-label='Enrichments' className={cn( - 'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] shadow-overlay transition-transform duration-200 ease-out', - open ? 'translate-x-0' : 'translate-x-full' + 'absolute top-0 right-0 bottom-0 z-[var(--z-modal)] flex w-[400px] flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--bg)] transition-transform duration-200 ease-out', + open ? 'translate-x-0 shadow-overlay' : 'translate-x-full' )} > {open && } @@ -75,15 +74,14 @@ function EnrichmentsSidebarBody({

Enrichment

- +

@@ -121,15 +119,14 @@ function EnrichmentsSidebarBody({

Enrichments

- +
@@ -155,11 +152,10 @@ function EnrichmentsSidebarBody({ const Icon = enrichment.icon return (
  • - +
  • ) })} diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx index bd683c62515..12f5bb10fb8 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.tsx @@ -12,7 +12,7 @@ import { } from '@/components/emcn' import { ChevronDown, Plus } from '@/components/emcn/icons' import type { Filter, FilterRule } from '@/lib/table' -import { COMPARISON_OPERATORS } from '@/lib/table/query-builder/constants' +import { COMPARISON_OPERATORS, VALUELESS_OPERATORS } from '@/lib/table/query-builder/constants' import { filterRulesToFilter, filterToRules } from '@/lib/table/query-builder/converters' const OPERATOR_LABELS = Object.fromEntries( @@ -71,7 +71,9 @@ export function TableFilter({ columns, filter, onApply, onClose }: TableFilterPr }, []) const handleApply = useCallback(() => { - const validRules = rulesRef.current.filter((r) => r.column && r.value) + const validRules = rulesRef.current.filter( + (r) => r.column && (r.value || VALUELESS_OPERATORS.has(r.operator)) + ) onApply(filterRulesToFilter(validRules)) }, [onApply]) @@ -197,16 +199,20 @@ const FilterRuleRow = memo(function FilterRuleRow({ - onUpdate(rule.id, 'value', e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') onApply() - }} - placeholder='Enter a value' - className='h-[28px] flex-1 rounded-[5px] border border-[var(--border)] bg-transparent px-2 text-[var(--text-secondary)] text-xs outline-none placeholder:text-[var(--text-subtle)] hover-hover:border-[var(--border-1)] focus:border-[var(--border-1)]' - /> + {VALUELESS_OPERATORS.has(rule.operator) ? ( +
    + ) : ( + onUpdate(rule.id, 'value', e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') onApply() + }} + placeholder='Enter a value' + className='h-[28px] flex-1 rounded-[5px] border border-[var(--border)] bg-transparent px-2 text-[var(--text-secondary)] text-xs outline-none placeholder:text-[var(--text-subtle)] hover-hover:border-[var(--border-1)] focus:border-[var(--border-1)]' + /> + )} {props.config && ( diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx index 631c0f15ef6..db3e38672f4 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx @@ -478,14 +478,14 @@ export function Table({ } /> )} - {/* Sort + filter render in both modes. In embedded (mothership) mode there's - no ResourceHeader, so the run/stop control rides in the options bar's - `extras` slot — keeping the bar populated whether or not a run is live. */} + {/* Sort + filter render in both modes (left-aligned). In embedded (mothership) + mode there's no ResourceHeader, so the run/stop control rides in the options + bar's right-aligned `trailing` slot — opposite the left-aligned filter/sort. */} setFilterOpen((prev) => !prev)} filterActive={filterOpen || !!queryOptions.filter} - extras={ + trailing={ embedded && selection.totalRunning > 0 ? ( (null) const { data: workspaceCredentials } = useWorkspaceCredentials({ workspaceId, enabled: isOpen }) useRegisterGlobalCommands([ @@ -246,10 +249,7 @@ export function WorkflowSearchReplace() { ) useEffect(() => { - if (!isOpen) { - usePanelEditorSearchStore.getState().setActiveSearchTarget(null) - return - } + if (!isOpen) return searchInputRef.current?.focus() searchInputRef.current?.select() }, [isOpen]) @@ -309,10 +309,7 @@ export function WorkflowSearchReplace() { return [] }, [activeMatch, hydratedMatches]) - const eligibleMatchIds = useMemo( - () => replaceAllTargetMatches.map((match) => match.id), - [replaceAllTargetMatches] - ) + const eligibleMatchIds = replaceAllTargetMatches.map((match) => match.id) const controlTargetMatches = activeMatch ? [activeMatch] : [] const usesResourceReplacement = controlTargetMatches.some(isConstrainedResourceMatch) const resourceReplacementContextKey = @@ -322,23 +319,20 @@ export function WorkflowSearchReplace() { const replacement = resourceReplacementContextKey ? (resourceReplacementByContext[resourceReplacementContextKey] ?? '') : textReplacement - const handleReplacementChange = useCallback( - (nextReplacement: string) => { - if (!resourceReplacementContextKey) { - setReplacement(nextReplacement) - return - } + const handleReplacementChange = (nextReplacement: string) => { + if (!resourceReplacementContextKey) { + setReplacement(nextReplacement) + return + } - setResourceReplacementByContext((current) => ({ - ...current, - [resourceReplacementContextKey]: nextReplacement, - })) - }, - [resourceReplacementContextKey, setReplacement] - ) - const compatibleResourceOptions = useMemo( - () => getCompatibleResourceReplacementOptions(controlTargetMatches, resourceOptions), - [controlTargetMatches, resourceOptions] + setResourceReplacementByContext((current) => ({ + ...current, + [resourceReplacementContextKey]: nextReplacement, + })) + } + const compatibleResourceOptions = getCompatibleResourceReplacementOptions( + controlTargetMatches, + resourceOptions ) const hasReplacement = replacement.trim().length > 0 const activeReplacementIssue = activeMatch @@ -357,34 +351,51 @@ export function WorkflowSearchReplace() { }) : 'No replaceable matches.' - const applySubflowUpdate = useCallback( - (update: WorkflowSearchReplaceSubflowUpdate) => { - if (update.fieldId === WORKFLOW_SEARCH_SUBFLOW_FIELD_IDS.iterations) { - if (typeof update.nextValue !== 'number') return - collaborativeUpdateIterationCount(update.blockId, update.blockType, update.nextValue) - return - } + const applySubflowUpdate = (update: WorkflowSearchReplaceSubflowUpdate) => { + if (update.fieldId === WORKFLOW_SEARCH_SUBFLOW_FIELD_IDS.iterations) { + if (typeof update.nextValue !== 'number') return + collaborativeUpdateIterationCount(update.blockId, update.blockType, update.nextValue) + return + } - collaborativeUpdateIterationCollection( - update.blockId, - update.blockType, - String(update.nextValue) - ) - }, - [collaborativeUpdateIterationCollection, collaborativeUpdateIterationCount] - ) + collaborativeUpdateIterationCollection( + update.blockId, + update.blockType, + String(update.nextValue) + ) + } useEffect(() => { - if (!isOpen) return + if (!isOpen) { + prevIsOpenRef.current = false + usePanelEditorSearchStore.getState().setActiveSearchTarget(null) + return + } + + const justOpened = !prevIsOpenRef.current + prevIsOpenRef.current = true + const queryChanged = prevQueryRef.current !== query + prevQueryRef.current = query if (hydratedMatches.length === 0) { + afterReplaceIndexRef.current = null if (activeMatchId) setActiveMatchId(null) usePanelEditorSearchStore.getState().setActiveSearchTarget(null) return } if (!activeMatchId || !hydratedMatches.some((match) => match.id === activeMatchId)) { - handleSelectMatch(hydratedMatches[0].id) + const replaceIndex = afterReplaceIndexRef.current + afterReplaceIndexRef.current = null + + if (queryChanged || justOpened) { + handleSelectMatch(hydratedMatches[0].id) + } else if (replaceIndex !== null) { + handleSelectMatch(hydratedMatches[Math.min(replaceIndex, hydratedMatches.length - 1)].id) + } else { + setActiveMatchId(null) + usePanelEditorSearchStore.getState().setActiveSearchTarget(null) + } return } @@ -399,14 +410,19 @@ export function WorkflowSearchReplace() { const handleMoveActiveMatch = (delta: number) => { if (hydratedMatches.length === 0) return - const currentIndex = activeMatchIndex >= 0 ? activeMatchIndex : 0 - const nextIndex = (currentIndex + delta + hydratedMatches.length) % hydratedMatches.length + if (activeMatchIndex < 0) { + handleSelectMatch(hydratedMatches[delta > 0 ? 0 : hydratedMatches.length - 1].id) + return + } + const nextIndex = (activeMatchIndex + delta + hydratedMatches.length) % hydratedMatches.length handleSelectMatch(hydratedMatches[nextIndex].id) } - const handleApply = (matchIds: string[]) => { + const handleApply = (matchIds: string[], replaceActiveIndex?: number) => { if (!workflowId || isApplying || searchReadOnly) return + if (replaceActiveIndex !== undefined) afterReplaceIndexRef.current = replaceActiveIndex setIsApplying(true) + let committed = false try { const selectedIds = new Set(matchIds) @@ -491,14 +507,16 @@ export function WorkflowSearchReplace() { toast({ message: `Replaced ${replacedCount} field${replacedCount === 1 ? '' : 's'}.`, }) + committed = true } finally { setIsApplying(false) + if (!committed) afterReplaceIndexRef.current = null } } const handleReplaceActive = () => { if (!activeMatch) return - handleApply([activeMatch.id]) + handleApply([activeMatch.id], activeMatchIndex) } const handleReplaceAll = () => { @@ -508,7 +526,9 @@ export function WorkflowSearchReplace() { const matchCountLabel = hydratedMatches.length === 0 ? 'No results' - : `${activeMatchIndex >= 0 ? activeMatchIndex + 1 : 1} of ${hydratedMatches.length}` + : activeMatchIndex >= 0 + ? `${activeMatchIndex + 1} of ${hydratedMatches.length}` + : `0 of ${hydratedMatches.length}` return (
    { const startedAt = formatLogDate(row.startedAt) + // Heavy execution data may live in object storage; resolve the pointer so + // retry deliveries get finalOutput/traceSpans (no-op for inline rows). + const executionData = await materializeExecutionData( + isRecord(row.executionData) ? row.executionData : {}, + { workspaceId: row.workspaceId, workflowId: row.workflowId, executionId: row.executionId } + ) + return { id: row.id, workflowId: row.workflowId, @@ -561,8 +548,9 @@ function normalizeWorkflowExecutionLog( endedAt: formatLogDate(row.endedAt, startedAt), totalDurationMs: row.totalDurationMs ?? 0, files: normalizeLogFiles(row.files), - executionData: isRecord(row.executionData) ? row.executionData : {}, - cost: normalizeLogCost(row.cost), + executionData: executionData as WorkflowExecutionLog['executionData'], + // cost_total projection of the usage_log ledger (not the deprecated jsonb). + cost: row.costTotal != null ? { total: Number(row.costTotal) } : undefined, createdAt: formatLogDate(row.createdAt, startedAt), } } @@ -580,7 +568,7 @@ async function buildRetryLog(params: NotificationDeliveryParams): Promise = { { label: 'Delete Agent', id: 'cursor_delete_agent' }, { label: 'List Artifacts', id: 'cursor_list_artifacts' }, { label: 'Download Artifact', id: 'cursor_download_artifact' }, + { label: 'List Models', id: 'cursor_list_models' }, + { label: 'List Repositories', id: 'cursor_list_repositories' }, + { label: 'Get API Key Info', id: 'cursor_get_api_key_info' }, ], value: () => 'cursor_launch_agent', }, @@ -182,6 +185,9 @@ export const CursorBlock: BlockConfig = { 'cursor_delete_agent', 'cursor_list_artifacts', 'cursor_download_artifact', + 'cursor_list_models', + 'cursor_list_repositories', + 'cursor_get_api_key_info', ], config: { tool: (params) => params.operation || 'cursor_launch_agent', @@ -235,6 +241,9 @@ export const CursorV2Block: BlockConfig = { 'cursor_delete_agent_v2', 'cursor_list_artifacts_v2', 'cursor_download_artifact_v2', + 'cursor_list_models_v2', + 'cursor_list_repositories_v2', + 'cursor_get_api_key_info_v2', ], config: { tool: createVersionedToolSelector({ @@ -252,12 +261,20 @@ export const CursorV2Block: BlockConfig = { source: { type: 'json', description: 'Agent source repository info' }, target: { type: 'json', description: 'Agent target branch/PR info' }, summary: { type: 'string', description: 'Agent summary' }, - createdAt: { type: 'string', description: 'Agent creation timestamp' }, + createdAt: { type: 'string', description: 'Creation timestamp (agent or API key)' }, agents: { type: 'json', description: 'Array of agent objects (list operation)' }, nextCursor: { type: 'string', description: 'Pagination cursor (list operation)' }, messages: { type: 'json', description: 'Conversation messages (get conversation operation)' }, artifacts: { type: 'json', description: 'List of artifact files (list artifacts operation)' }, file: { type: 'file', description: 'Downloaded artifact file (download artifact operation)' }, + models: { type: 'json', description: 'Available model names (list models operation)' }, + repositories: { + type: 'json', + description: + 'Accessible repositories [{owner, name, repository}] (list repositories operation)', + }, + apiKeyName: { type: 'string', description: 'API key name (api key info operation)' }, + userEmail: { type: 'string', description: 'Key owner email (api key info operation)' }, }, } diff --git a/apps/sim/blocks/blocks/devin.ts b/apps/sim/blocks/blocks/devin.ts index fdce079c506..a021e11c092 100644 --- a/apps/sim/blocks/blocks/devin.ts +++ b/apps/sim/blocks/blocks/devin.ts @@ -2,6 +2,14 @@ import { DevinIcon } from '@/components/icons' import type { BlockConfig, BlockMeta } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' +const SESSION_OBJECT_OPERATIONS = [ + 'create_session', + 'get_session', + 'send_message', + 'archive_session', + 'terminate_session', +] as const + export const DevinBlock: BlockConfig = { type: 'devin', name: 'Devin', @@ -31,6 +39,13 @@ export const DevinBlock: BlockConfig = { { label: 'Get Session', id: 'get_session' }, { label: 'List Sessions', id: 'list_sessions' }, { label: 'Send Message', id: 'send_message' }, + { label: 'List Session Messages', id: 'list_session_messages' }, + { label: 'List Session Attachments', id: 'list_session_attachments' }, + { label: 'Get Session Tags', id: 'get_session_tags' }, + { label: 'Append Session Tags', id: 'append_session_tags' }, + { label: 'Replace Session Tags', id: 'replace_session_tags' }, + { label: 'Archive Session', id: 'archive_session' }, + { label: 'Terminate Session', id: 'terminate_session' }, ], value: () => 'create_session', }, @@ -42,6 +57,13 @@ export const DevinBlock: BlockConfig = { password: true, required: true, }, + { + id: 'orgId', + title: 'Organization ID', + type: 'short-input', + placeholder: 'Enter your Devin organization ID (org-...)', + required: true, + }, { id: 'prompt', title: 'Prompt', @@ -85,16 +107,19 @@ RULES: title: 'Tags', type: 'short-input', placeholder: 'Comma-separated tags', - condition: { field: 'operation', value: 'create_session' }, - mode: 'advanced', + required: { field: 'operation', value: ['append_session_tags', 'replace_session_tags'] }, + condition: { + field: 'operation', + value: ['create_session', 'append_session_tags', 'replace_session_tags'], + }, }, { id: 'sessionId', title: 'Session ID', type: 'short-input', placeholder: 'Enter session ID', - required: { field: 'operation', value: ['get_session', 'send_message'] }, - condition: { field: 'operation', value: ['get_session', 'send_message'] }, + required: { field: 'operation', value: ['create_session', 'list_sessions'], not: true }, + condition: { field: 'operation', value: ['create_session', 'list_sessions'], not: true }, }, { id: 'message', @@ -108,8 +133,28 @@ RULES: id: 'limit', title: 'Limit', type: 'short-input', - placeholder: 'Number of sessions (1-200, default: 100)', - condition: { field: 'operation', value: 'list_sessions' }, + placeholder: 'Max results (1-200, default: 100)', + condition: { field: 'operation', value: ['list_sessions', 'list_session_messages'] }, + mode: 'advanced', + }, + { + id: 'after', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Pagination cursor from a previous response', + condition: { field: 'operation', value: ['list_sessions', 'list_session_messages'] }, + mode: 'advanced', + }, + { + id: 'terminateArchive', + title: 'Archive Instead of Terminate', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + condition: { field: 'operation', value: 'terminate_session' }, mode: 'advanced', }, ], @@ -119,15 +164,30 @@ RULES: 'devin_get_session', 'devin_list_sessions', 'devin_send_message', + 'devin_list_session_messages', + 'devin_list_session_attachments', + 'devin_get_session_tags', + 'devin_append_session_tags', + 'devin_replace_session_tags', + 'devin_archive_session', + 'devin_terminate_session', ], config: { tool: (params) => `devin_${params.operation}`, params: (params) => { if (params.maxAcuLimit != null && params.maxAcuLimit !== '') { - params.maxAcuLimit = Number(params.maxAcuLimit) + const parsed = Number(params.maxAcuLimit) + params.maxAcuLimit = Number.isFinite(parsed) ? parsed : undefined } if (params.limit != null && params.limit !== '') { - params.limit = Number(params.limit) + const parsed = Number(params.limit) + params.limit = Number.isFinite(parsed) ? parsed : undefined + } + if (params.terminateArchive != null && params.terminateArchive !== '') { + params.archive = + typeof params.terminateArchive === 'boolean' + ? params.terminateArchive + : params.terminateArchive === 'true' } return params }, @@ -138,52 +198,122 @@ RULES: sessionId: { type: 'string', description: 'Session ID' }, message: { type: 'string', description: 'Message to send to the session' }, apiKey: { type: 'string', description: 'Devin API key' }, + orgId: { type: 'string', description: 'Devin organization ID' }, playbookId: { type: 'string', description: 'Playbook ID to guide the session' }, maxAcuLimit: { type: 'number', description: 'Maximum ACU limit' }, - tags: { type: 'string', description: 'Comma-separated tags' }, - limit: { type: 'number', description: 'Number of sessions to return' }, + tags: { type: 'string', description: 'Tags (comma-separated string or array of strings)' }, + limit: { type: 'number', description: 'Maximum number of results to return' }, + after: { type: 'string', description: 'Pagination cursor for the next page' }, + terminateArchive: { + type: 'string', + description: 'Whether to archive instead of terminate the session', + }, }, outputs: { - sessionId: { type: 'string', description: 'Session identifier' }, - url: { type: 'string', description: 'URL to view the session in Devin UI' }, + sessionId: { + type: 'string', + description: 'Session identifier', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, + url: { + type: 'string', + description: 'URL to view the session in Devin UI', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, status: { type: 'string', description: 'Session status (new, claimed, running, exit, error, suspended, resuming)', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, }, statusDetail: { type: 'string', description: 'Detailed status (working, waiting_for_user, finished, etc.)', - condition: { field: 'operation', value: 'list_sessions', not: true }, + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, + title: { + type: 'string', + description: 'Session title', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, + createdAt: { + type: 'number', + description: 'Creation timestamp (Unix)', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, + updatedAt: { + type: 'number', + description: 'Last updated timestamp (Unix)', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, }, - title: { type: 'string', description: 'Session title' }, - createdAt: { type: 'number', description: 'Creation timestamp (Unix)' }, - updatedAt: { type: 'number', description: 'Last updated timestamp (Unix)' }, acusConsumed: { type: 'number', description: 'ACUs consumed', - condition: { field: 'operation', value: 'list_sessions', not: true }, + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, + tags: { + type: 'json', + description: 'Session tags (array of strings)', + condition: { + field: 'operation', + value: [ + ...SESSION_OBJECT_OPERATIONS, + 'get_session_tags', + 'append_session_tags', + 'replace_session_tags', + ], + }, }, - tags: { type: 'json', description: 'Session tags' }, pullRequests: { type: 'json', - description: 'Pull requests created during the session', - condition: { field: 'operation', value: 'list_sessions', not: true }, + description: 'Pull requests created during the session ([{pr_url, pr_state}])', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, }, structuredOutput: { type: 'json', description: 'Structured output from the session', - condition: { field: 'operation', value: 'list_sessions', not: true }, + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, }, playbookId: { type: 'string', description: 'Associated playbook ID', - condition: { field: 'operation', value: 'list_sessions', not: true }, + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, + }, + isArchived: { + type: 'boolean', + description: 'Whether the session is archived', + condition: { field: 'operation', value: [...SESSION_OBJECT_OPERATIONS] }, }, sessions: { type: 'json', - description: 'List of sessions', + description: + 'List of sessions ([{sessionId, url, status, statusDetail, title, tags, acusConsumed, pullRequests, playbookId, isArchived, ...}])', condition: { field: 'operation', value: 'list_sessions' }, }, + messages: { + type: 'json', + description: 'Messages in the session ([{eventId, source, message, createdAt}])', + condition: { field: 'operation', value: 'list_session_messages' }, + }, + attachments: { + type: 'json', + description: 'Session attachments ([{attachmentId, name, url, source, contentType}])', + condition: { field: 'operation', value: 'list_session_attachments' }, + }, + endCursor: { + type: 'string', + description: 'Pagination cursor for the next page, or null if last page', + condition: { field: 'operation', value: ['list_sessions', 'list_session_messages'] }, + }, + hasNextPage: { + type: 'boolean', + description: 'Whether more results are available', + condition: { field: 'operation', value: ['list_sessions', 'list_session_messages'] }, + }, + total: { + type: 'number', + description: 'Total number of results, if provided', + condition: { field: 'operation', value: ['list_sessions', 'list_session_messages'] }, + }, }, } diff --git a/apps/sim/blocks/blocks/findymail.ts b/apps/sim/blocks/blocks/findymail.ts index c1a65f49902..8d0e2fa27e1 100644 --- a/apps/sim/blocks/blocks/findymail.ts +++ b/apps/sim/blocks/blocks/findymail.ts @@ -213,7 +213,7 @@ export const FindymailBlock: BlockConfig = { placeholder: 'e.g. React, TypeScript, Node.js', }, }, - // API Key + // API Key — hidden on hosted Sim for operations with hosted-key support { id: 'apiKey', title: 'API Key', @@ -221,6 +221,18 @@ export const FindymailBlock: BlockConfig = { required: true, placeholder: 'Enter your Findymail API key', password: true, + hideWhenHosted: true, + condition: { field: 'operation', value: 'findymail_get_credits', not: true }, + }, + // API Key — always required for the credit-balance lookup (no hosted key) + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + required: true, + placeholder: 'Enter your Findymail API key', + password: true, + condition: { field: 'operation', value: 'findymail_get_credits' }, }, ], tools: { diff --git a/apps/sim/blocks/blocks/google_sheets.ts b/apps/sim/blocks/blocks/google_sheets.ts index bc731fb8ec4..4d77288c099 100644 --- a/apps/sim/blocks/blocks/google_sheets.ts +++ b/apps/sim/blocks/blocks/google_sheets.ts @@ -460,9 +460,15 @@ Return ONLY the range string - no sheet name, no explanations, no quotes.`, type: 'dropdown', options: [ { label: 'Contains', id: 'contains' }, + { label: 'Does Not Contain', id: 'not_contains' }, { label: 'Exact Match', id: 'exact' }, + { label: 'Not Equal To', id: 'not_equals' }, { label: 'Starts With', id: 'starts_with' }, { label: 'Ends With', id: 'ends_with' }, + { label: 'Greater Than', id: 'gt' }, + { label: 'Greater Than or Equal', id: 'gte' }, + { label: 'Less Than', id: 'lt' }, + { label: 'Less Than or Equal', id: 'lte' }, ], condition: { field: 'operation', value: 'read' }, mode: 'advanced', @@ -501,7 +507,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, { label: 'User Entered (Parse formulas)', id: 'USER_ENTERED' }, { label: "Raw (Don't parse formulas)", id: 'RAW' }, ], - condition: { field: 'operation', value: ['write', 'batch_update'] }, + condition: { field: 'operation', value: ['write', 'update', 'batch_update'] }, }, // Update-specific Fields { @@ -894,11 +900,15 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, type: 'string', description: 'Destination spreadsheet ID for copy', }, - filterColumn: { type: 'string', description: 'Column header name to filter on' }, + filterColumn: { + type: 'string', + description: 'Column header name to filter the read rows on (within the read range)', + }, filterValue: { type: 'string', description: 'Value to match against the filter column' }, filterMatchType: { type: 'string', - description: 'Match type: contains, exact, starts_with, or ends_with', + description: + 'Match type: contains, not_contains, exact, not_equals, starts_with, ends_with, gt, gte, lt, or lte', }, }, outputs: { @@ -918,6 +928,12 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, description: 'Cell values as 2D array', condition: { field: 'operation', value: 'read' }, }, + filter: { + type: 'json', + description: + 'Filter summary (present only when a filter was requested): applied, column, matchType, columnFound, matchedRows, totalRows', + condition: { field: 'operation', value: 'read' }, + }, // Write/Update/Append outputs updatedRange: { type: 'string', diff --git a/apps/sim/blocks/blocks/greptile.ts b/apps/sim/blocks/blocks/greptile.ts index f0fa2f1667e..83de267de4d 100644 --- a/apps/sim/blocks/blocks/greptile.ts +++ b/apps/sim/blocks/blocks/greptile.ts @@ -1,5 +1,5 @@ import { GreptileIcon } from '@/components/icons' -import type { BlockConfig, BlockMeta } from '@/blocks/types' +import type { BlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' import type { GreptileResponse } from '@/tools/greptile/types' @@ -12,8 +12,9 @@ export const GreptileBlock: BlockConfig = { 'Query and search codebases using natural language with Greptile. Get AI-generated answers about your code, find relevant files, and understand complex codebases.', docsLink: 'https://docs.sim.ai/tools/greptile', category: 'tools', - integrationType: IntegrationType.DevOps, - bgColor: '#FFFFFF', + integrationType: IntegrationType.DeveloperTools, + tags: ['version-control', 'knowledge-base'], + bgColor: '#5DE195', icon: GreptileIcon, subBlocks: [ { @@ -88,7 +89,7 @@ export const GreptileBlock: BlockConfig = { // type: 'switch', // condition: { field: 'operation', value: 'greptile_search' }, // }, - // Index operation inputs + // Index & Status shared inputs { id: 'remote', title: 'Git Remote', @@ -98,14 +99,14 @@ export const GreptileBlock: BlockConfig = { { label: 'GitLab', id: 'gitlab' }, ], value: () => 'github', - condition: { field: 'operation', value: 'greptile_index_repo' }, + condition: { field: 'operation', value: ['greptile_index_repo', 'greptile_status'] }, }, { id: 'repository', title: 'Repository', type: 'short-input', placeholder: 'owner/repo', - condition: { field: 'operation', value: 'greptile_index_repo' }, + condition: { field: 'operation', value: ['greptile_index_repo', 'greptile_status'] }, required: true, }, { @@ -113,9 +114,10 @@ export const GreptileBlock: BlockConfig = { title: 'Branch', type: 'short-input', placeholder: 'main', - condition: { field: 'operation', value: 'greptile_index_repo' }, + condition: { field: 'operation', value: ['greptile_index_repo', 'greptile_status'] }, required: true, }, + // Index-only inputs { id: 'reload', title: 'Force Re-index', @@ -128,34 +130,6 @@ export const GreptileBlock: BlockConfig = { type: 'switch', condition: { field: 'operation', value: 'greptile_index_repo' }, }, - // Status operation inputs - { - id: 'remote', - title: 'Git Remote', - type: 'dropdown', - options: [ - { label: 'GitHub', id: 'github' }, - { label: 'GitLab', id: 'gitlab' }, - ], - value: () => 'github', - condition: { field: 'operation', value: 'greptile_status' }, - }, - { - id: 'repository', - title: 'Repository', - type: 'short-input', - placeholder: 'owner/repo', - condition: { field: 'operation', value: 'greptile_status' }, - required: true, - }, - { - id: 'branch', - title: 'Branch', - type: 'short-input', - placeholder: 'main', - condition: { field: 'operation', value: 'greptile_status' }, - required: true, - }, // API Keys (common) { id: 'apiKey', @@ -222,74 +196,3 @@ export const GreptileBlock: BlockConfig = { sha: { type: 'string', description: 'Git commit SHA' }, }, } - -export const GreptileBlockMeta = { - tags: ['version-control', 'knowledge-base'], - templates: [ - { - icon: GreptileIcon, - title: 'Greptile code search', - prompt: - 'Create a workflow exposed as a chat endpoint that accepts natural-language code questions, runs them against a Greptile-indexed repository, and returns the AI-generated answer with file and line citations so engineers get grounded answers about the codebase.', - modules: ['agent', 'workflows'], - category: 'engineering', - tags: ['engineering', 'research', 'devops'], - }, - { - icon: GreptileIcon, - title: 'Greptile-backed PR reviewer', - prompt: - 'Build a workflow that runs when a GitHub pull request is opened, fetches the diff, asks Greptile how each changed function is used elsewhere in the codebase, and posts an architectural review comment highlighting impact, downstream callers, and risky patterns.', - modules: ['agent', 'workflows'], - category: 'engineering', - tags: ['engineering', 'devops', 'automation'], - alsoIntegrations: ['github'], - }, - { - icon: GreptileIcon, - title: 'Onboarding doc generator', - prompt: - 'Create a workflow that takes a repository name, indexes it with Greptile if not already indexed, then queries Greptile for the architecture overview, key modules, and entry points, and writes a polished onboarding document file new engineers can read on day one.', - modules: ['agent', 'files', 'workflows'], - category: 'engineering', - tags: ['engineering', 'content', 'team'], - }, - { - icon: GreptileIcon, - title: 'Repository index orchestrator', - prompt: - 'Create a scheduled workflow that runs nightly, lists every repository in a table, submits any newly created or updated repositories to Greptile for indexing, polls until indexing completes, and updates the table with index status and completion time.', - modules: ['scheduled', 'tables', 'agent', 'workflows'], - category: 'engineering', - tags: ['engineering', 'devops', 'automation'], - }, - { - icon: GreptileIcon, - title: 'Code search audit log', - prompt: - 'Build a workflow that wraps Greptile queries with structured logging — capturing question, repository, top-matched files, and answer — into a tables-backed audit log so leadership can see what knowledge the team is searching for and where docs are weakest.', - modules: ['tables', 'agent', 'workflows'], - category: 'engineering', - tags: ['engineering', 'analysis', 'enterprise'], - }, - { - icon: GreptileIcon, - title: 'Incident root-cause assistant', - prompt: - 'Create a workflow triggered during an incident that takes the failing endpoint or stack frame, asks Greptile where the relevant code lives and what changed most recently, and posts a structured root-cause hypothesis with file links to the incident Slack channel.', - modules: ['agent', 'workflows'], - category: 'engineering', - tags: ['engineering', 'devops', 'analysis'], - alsoIntegrations: ['slack'], - }, - { - icon: GreptileIcon, - title: 'Greptile onboarding repo indexer', - prompt: - 'Build a workflow that on a new repository indexes it with Greptile, then runs a set of architecture questions to generate a codebase overview — entry points, key services, and conventions — and writes the guide to a file for new engineers.', - modules: ['agent', 'files', 'workflows'], - category: 'engineering', - tags: ['engineering', 'content', 'automation'], - }, - ], -} as const satisfies BlockMeta diff --git a/apps/sim/blocks/blocks/prospeo.ts b/apps/sim/blocks/blocks/prospeo.ts index eb3bb6f3626..8464aab03a2 100644 --- a/apps/sim/blocks/blocks/prospeo.ts +++ b/apps/sim/blocks/blocks/prospeo.ts @@ -301,7 +301,7 @@ export const ProspeoBlock: BlockConfig = { condition: { field: 'operation', value: 'prospeo_search_suggestions' }, }, - // API Key (always last) + // API Key — hidden on hosted Sim for operations with hosted-key support { id: 'apiKey', title: 'API Key', @@ -309,6 +309,25 @@ export const ProspeoBlock: BlockConfig = { required: true, placeholder: 'Enter your Prospeo API key', password: true, + hideWhenHosted: true, + condition: { + field: 'operation', + value: ['prospeo_search_suggestions', 'prospeo_account_information'], + not: true, + }, + }, + // API Key — always required for the free account/suggestion lookups (no hosted key) + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + required: true, + placeholder: 'Enter your Prospeo API key', + password: true, + condition: { + field: 'operation', + value: ['prospeo_search_suggestions', 'prospeo_account_information'], + }, }, ], tools: { diff --git a/apps/sim/blocks/blocks/quiver.ts b/apps/sim/blocks/blocks/quiver.ts index 86158609ac0..d1be1b79a47 100644 --- a/apps/sim/blocks/blocks/quiver.ts +++ b/apps/sim/blocks/blocks/quiver.ts @@ -1,5 +1,5 @@ import { QuiverIcon } from '@/components/icons' -import type { BlockConfig, BlockMeta } from '@/blocks/types' +import type { BlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' import { normalizeFileInput } from '@/blocks/utils' import type { QuiverSvgResponse } from '@/tools/quiver/types' @@ -12,8 +12,9 @@ export const QuiverBlock: BlockConfig = { 'Generate SVG images from text prompts or vectorize raster images into SVGs using QuiverAI. Supports reference images, style instructions, and multiple output generation.', docsLink: 'https://docs.sim.ai/tools/quiver', category: 'tools', - integrationType: IntegrationType.Marketing, - bgColor: '#000000', + integrationType: IntegrationType.Design, + tags: ['image-generation'], + bgColor: '#FFFFFF', icon: QuiverIcon, authMode: AuthMode.ApiKey, subBlocks: [ @@ -236,76 +237,3 @@ export const QuiverBlock: BlockConfig = { }, }, } - -export const QuiverBlockMeta = { - tags: ['image-generation'], - templates: [ - { - icon: QuiverIcon, - title: 'Quiver congressional trades watcher', - prompt: - 'Build a scheduled workflow that monitors Quiver Quantitative for new congressional trades on tracked tickers, scores notability, and posts a Slack alert with the trade details.', - modules: ['scheduled', 'agent', 'workflows'], - category: 'operations', - tags: ['finance', 'monitoring'], - alsoIntegrations: ['slack'], - }, - { - icon: QuiverIcon, - title: 'Quiver insider signal aggregator', - prompt: - 'Create a scheduled workflow that pulls Quiver insider trades and lobbying activity for tracked tickers, aggregates a signal score, and writes a research table for the portfolio review.', - modules: ['scheduled', 'tables', 'agent', 'workflows'], - category: 'operations', - tags: ['finance', 'analysis'], - }, - { - icon: QuiverIcon, - title: 'Quiver lobbying tracker', - prompt: - 'Build a workflow that watches Quiver lobbying spend on tracked sectors, summarizes new filings, and emails a digest to portfolio managers weekly.', - modules: ['scheduled', 'agent', 'workflows'], - category: 'operations', - tags: ['finance', 'research'], - alsoIntegrations: ['gmail'], - }, - { - icon: QuiverIcon, - title: 'Quiver patent watcher', - prompt: - 'Create a scheduled workflow that monitors Quiver patent filings for tracked companies, scores relevance to thesis, and writes notable filings to a research log.', - modules: ['scheduled', 'tables', 'agent', 'workflows'], - category: 'operations', - tags: ['finance', 'research'], - }, - { - icon: QuiverIcon, - title: 'Quiver government-contract alerter', - prompt: - 'Build a workflow that watches Quiver for new government-contract awards on tracked tickers, posts a Slack alert with award size and agency, and writes the contract to a tracking table.', - modules: ['scheduled', 'tables', 'agent', 'workflows'], - category: 'operations', - tags: ['finance', 'monitoring'], - alsoIntegrations: ['slack'], - }, - { - icon: QuiverIcon, - title: 'Quiver Wallstreetbets pulse', - prompt: - 'Create a scheduled daily workflow that pulls Quiver WallStreetBets mentions and sentiment for tracked tickers, captures spike events, and posts notable surges to Slack.', - modules: ['scheduled', 'agent', 'workflows'], - category: 'operations', - tags: ['finance', 'monitoring'], - alsoIntegrations: ['slack'], - }, - { - icon: QuiverIcon, - title: 'Quiver corporate-flights watcher', - prompt: - 'Build a scheduled workflow that monitors Quiver corporate jet flight data, flags unusual destination patterns for tracked tickers, and writes findings to a research file.', - modules: ['scheduled', 'agent', 'files', 'workflows'], - category: 'operations', - tags: ['finance', 'research'], - }, - ], -} as const satisfies BlockMeta diff --git a/apps/sim/blocks/blocks/table.ts b/apps/sim/blocks/blocks/table.ts index afdcdc5b2eb..80dba4a7015 100644 --- a/apps/sim/blocks/blocks/table.ts +++ b/apps/sim/blocks/blocks/table.ts @@ -379,6 +379,10 @@ IMPORTANT: Reference the table schema to know which columns exist and their type - **$in**: In array - {"column": {"$in": ["value1", "value2"]}} - **$nin**: Not in array - {"column": {"$nin": ["value1", "value2"]}} - **$contains**: String contains - {"column": {"$contains": "text"}} +- **$ncontains**: Does not contain (matches empty cells) - {"column": {"$ncontains": "text"}} +- **$startsWith**: Starts with - {"column": {"$startsWith": "text"}} +- **$endsWith**: Ends with - {"column": {"$endsWith": "text"}} +- **$empty**: Is empty (true) or non-empty (false) - {"column": {"$empty": true}} ### EXAMPLES @@ -467,6 +471,10 @@ IMPORTANT: Reference the table schema to know which columns exist and their type - **$in**: In array - {"column": {"$in": ["value1", "value2"]}} - **$nin**: Not in array - {"column": {"$nin": ["value1", "value2"]}} - **$contains**: String contains - {"column": {"$contains": "text"}} +- **$ncontains**: Does not contain (matches empty cells) - {"column": {"$ncontains": "text"}} +- **$startsWith**: Starts with - {"column": {"$startsWith": "text"}} +- **$endsWith**: Ends with - {"column": {"$endsWith": "text"}} +- **$empty**: Is empty (true) or non-empty (false) - {"column": {"$empty": true}} ### EXAMPLES diff --git a/apps/sim/blocks/blocks/wiza.ts b/apps/sim/blocks/blocks/wiza.ts index d7958d7f7a1..8c5f0d3b82f 100644 --- a/apps/sim/blocks/blocks/wiza.ts +++ b/apps/sim/blocks/blocks/wiza.ts @@ -23,8 +23,7 @@ export const WizaBlock: BlockConfig = { options: [ { label: 'Prospect Search', id: 'prospect_search' }, { label: 'Company Enrichment', id: 'company_enrichment' }, - { label: 'Start Individual Reveal', id: 'start_individual_reveal' }, - { label: 'Get Individual Reveal', id: 'get_individual_reveal' }, + { label: 'Individual Reveal', id: 'individual_reveal' }, { label: 'Get Credits', id: 'get_credits' }, ], value: () => 'prospect_search', @@ -36,6 +35,17 @@ export const WizaBlock: BlockConfig = { placeholder: 'Enter your Wiza API key', password: true, required: true, + hideWhenHosted: true, + condition: { field: 'operation', value: 'get_credits', not: true }, + }, + { + id: 'apiKey', + title: 'Wiza API Key', + type: 'short-input', + placeholder: 'Enter your Wiza API key', + password: true, + required: true, + condition: { field: 'operation', value: 'get_credits' }, }, // Prospect Search @@ -286,7 +296,7 @@ Return ONLY the JSON object - no explanations, no extra text.`, mode: 'advanced', }, - // Start Individual Reveal + // Individual Reveal { id: 'enrichment_level', title: 'Enrichment Level', @@ -297,85 +307,66 @@ Return ONLY the JSON object - no explanations, no extra text.`, { label: 'Phone', id: 'phone' }, { label: 'Full', id: 'full' }, ], - value: () => 'partial', - condition: { field: 'operation', value: 'start_individual_reveal' }, - required: { field: 'operation', value: 'start_individual_reveal' }, + value: () => 'full', + condition: { field: 'operation', value: 'individual_reveal' }, + required: { field: 'operation', value: 'individual_reveal' }, }, { id: 'profile_url', title: 'LinkedIn Profile URL', type: 'short-input', placeholder: 'https://linkedin.com/in/johndoe', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, }, { id: 'full_name', title: 'Full Name', type: 'short-input', placeholder: 'John Doe', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, }, { id: 'company', title: 'Company', type: 'short-input', placeholder: 'Wiza', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, }, { id: 'domain', title: 'Company Domain', type: 'short-input', placeholder: 'wiza.co', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, }, { id: 'email', title: 'Email', type: 'short-input', placeholder: 'john@wiza.co', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, }, { id: 'accept_work', title: 'Accept Work Emails', type: 'switch', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, mode: 'advanced', }, { id: 'accept_personal', title: 'Accept Personal Emails', type: 'switch', - condition: { field: 'operation', value: 'start_individual_reveal' }, - mode: 'advanced', - }, - { - id: 'callback_url', - title: 'Callback URL', - type: 'short-input', - placeholder: 'https://example.com/wiza-callback', - condition: { field: 'operation', value: 'start_individual_reveal' }, + condition: { field: 'operation', value: 'individual_reveal' }, mode: 'advanced', }, - - // Get Individual Reveal - { - id: 'id', - title: 'Reveal ID', - type: 'short-input', - placeholder: 'Reveal ID returned from Start Individual Reveal', - condition: { field: 'operation', value: 'get_individual_reveal' }, - required: { field: 'operation', value: 'get_individual_reveal' }, - }, ], tools: { access: [ 'wiza_prospect_search', 'wiza_company_enrichment', - 'wiza_start_individual_reveal', - 'wiza_get_individual_reveal', + 'wiza_individual_reveal', 'wiza_get_credits', ], config: { @@ -385,10 +376,8 @@ Return ONLY the JSON object - no explanations, no extra text.`, return 'wiza_prospect_search' case 'company_enrichment': return 'wiza_company_enrichment' - case 'start_individual_reveal': - return 'wiza_start_individual_reveal' - case 'get_individual_reveal': - return 'wiza_get_individual_reveal' + case 'individual_reveal': + return 'wiza_individual_reveal' case 'get_credits': return 'wiza_get_credits' default: @@ -478,8 +467,6 @@ Return ONLY the JSON object - no explanations, no extra text.`, email: { type: 'string', description: 'Email address' }, accept_work: { type: 'boolean', description: 'Whether to accept work emails' }, accept_personal: { type: 'boolean', description: 'Whether to accept personal emails' }, - callback_url: { type: 'string', description: 'Callback URL' }, - id: { type: 'string', description: 'Individual reveal ID' }, }, outputs: { @@ -494,46 +481,44 @@ Return ONLY the JSON object - no explanations, no extra text.`, }, id: { type: 'number', - description: 'Reveal ID (start_individual_reveal, get_individual_reveal)', + description: 'Reveal ID (individual_reveal)', }, status: { type: 'string', - description: - 'Reveal status (start_individual_reveal, get_individual_reveal): queued | resolving | finished | failed', + description: 'Reveal status (individual_reveal): queued | resolving | finished | failed', }, is_complete: { type: 'boolean', - description: - 'Whether the reveal has completed (start_individual_reveal, get_individual_reveal)', + description: 'Whether the reveal has completed (individual_reveal)', }, - name: { type: 'string', description: 'Full name (get_individual_reveal)' }, - company: { type: 'string', description: 'Company name (get_individual_reveal)' }, + name: { type: 'string', description: 'Full name (individual_reveal)' }, + company: { type: 'string', description: 'Company name (individual_reveal)' }, enrichment_level: { type: 'string', - description: 'Enrichment level used (get_individual_reveal)', + description: 'Enrichment level used (individual_reveal)', }, - linkedin_profile_url: { type: 'string', description: 'LinkedIn URL (get_individual_reveal)' }, - title: { type: 'string', description: 'Job title (get_individual_reveal)' }, - location: { type: 'string', description: 'Location (get_individual_reveal)' }, - email: { type: 'string', description: 'Primary email (get_individual_reveal)' }, - email_type: { type: 'string', description: 'Primary email type (get_individual_reveal)' }, + linkedin_profile_url: { type: 'string', description: 'LinkedIn URL (individual_reveal)' }, + title: { type: 'string', description: 'Job title (individual_reveal)' }, + location: { type: 'string', description: 'Location (individual_reveal)' }, + email: { type: 'string', description: 'Primary email (individual_reveal)' }, + email_type: { type: 'string', description: 'Primary email type (individual_reveal)' }, email_status: { type: 'string', - description: 'Primary email status: valid | risky | unfound (get_individual_reveal)', + description: 'Primary email status: valid | risky | unfound (individual_reveal)', }, emails: { type: 'json', - description: 'All emails found (get_individual_reveal): [{email, email_type, email_status}]', + description: 'All emails found (individual_reveal): [{email, email_type, email_status}]', }, - mobile_phone: { type: 'string', description: 'Mobile phone (get_individual_reveal)' }, - phone_number: { type: 'string', description: 'Direct/office phone (get_individual_reveal)' }, + mobile_phone: { type: 'string', description: 'Mobile phone (individual_reveal)' }, + phone_number: { type: 'string', description: 'Direct/office phone (individual_reveal)' }, phone_status: { type: 'string', - description: 'Phone status: found | unfound (get_individual_reveal)', + description: 'Phone status: found | unfound (individual_reveal)', }, phones: { type: 'json', - description: 'All phones found (get_individual_reveal): [{number, pretty_number, type}]', + description: 'All phones found (individual_reveal): [{number, pretty_number, type}]', }, company_name: { type: 'string', @@ -541,41 +526,41 @@ Return ONLY the JSON object - no explanations, no extra text.`, }, company_domain: { type: 'string', - description: 'Company domain (company_enrichment, get_individual_reveal)', + description: 'Company domain (company_enrichment, individual_reveal)', }, domain: { type: 'string', description: 'Domain (company_enrichment)' }, company_industry: { type: 'string', - description: 'Industry (company_enrichment, get_individual_reveal)', + description: 'Industry (company_enrichment, individual_reveal)', }, company_size: { type: 'number', - description: 'Employee count (company_enrichment, get_individual_reveal)', + description: 'Employee count (company_enrichment, individual_reveal)', }, company_size_range: { type: 'string', - description: 'Headcount range (company_enrichment, get_individual_reveal)', + description: 'Headcount range (company_enrichment, individual_reveal)', }, company_founded: { type: 'number', - description: 'Year founded (company_enrichment, get_individual_reveal)', + description: 'Year founded (company_enrichment, individual_reveal)', }, company_revenue_range: { type: 'string', description: 'Revenue range (company_enrichment)', }, - company_revenue: { type: 'string', description: 'Revenue (get_individual_reveal)' }, + company_revenue: { type: 'string', description: 'Revenue (individual_reveal)' }, company_funding: { type: 'string', - description: 'Total funding (company_enrichment, get_individual_reveal)', + description: 'Total funding (company_enrichment, individual_reveal)', }, company_type: { type: 'string', - description: 'Company type (company_enrichment, get_individual_reveal)', + description: 'Company type (company_enrichment, individual_reveal)', }, company_description: { type: 'string', - description: 'Company description (company_enrichment, get_individual_reveal)', + description: 'Company description (company_enrichment, individual_reveal)', }, company_ticker: { type: 'string', description: 'Stock ticker (company_enrichment)' }, company_last_funding_round: { @@ -592,40 +577,40 @@ Return ONLY the JSON object - no explanations, no extra text.`, }, company_location: { type: 'string', - description: 'Full location string (company_enrichment, get_individual_reveal)', + description: 'Full location string (company_enrichment, individual_reveal)', }, company_twitter: { type: 'string', description: 'Twitter URL (company_enrichment)' }, company_facebook: { type: 'string', description: 'Facebook URL (company_enrichment)' }, company_linkedin: { type: 'string', - description: 'LinkedIn URL (company_enrichment, get_individual_reveal)', + description: 'LinkedIn URL (company_enrichment, individual_reveal)', }, company_linkedin_id: { type: 'string', description: 'LinkedIn ID (company_enrichment)' }, company_street: { type: 'string', - description: 'Street address (company_enrichment, get_individual_reveal)', + description: 'Street address (company_enrichment, individual_reveal)', }, company_locality: { type: 'string', - description: 'City (company_enrichment, get_individual_reveal)', + description: 'City (company_enrichment, individual_reveal)', }, company_region: { type: 'string', - description: 'State/region (company_enrichment, get_individual_reveal)', + description: 'State/region (company_enrichment, individual_reveal)', }, company_postal_code: { type: 'string', - description: 'Postal code (company_enrichment, get_individual_reveal)', + description: 'Postal code (company_enrichment, individual_reveal)', }, company_country: { type: 'string', - description: 'Country (company_enrichment, get_individual_reveal)', + description: 'Country (company_enrichment, individual_reveal)', }, - company_subindustry: { type: 'string', description: 'Subindustry (get_individual_reveal)' }, + company_subindustry: { type: 'string', description: 'Subindustry (individual_reveal)' }, credits: { type: 'json', description: - 'Credits deducted — company_enrichment: { api_credits: { total, company_credits } }; get_individual_reveal: { api_credits: { total, email_credits, phone_credits, scrape_credits } }', + 'Credits deducted — company_enrichment: { api_credits: { total, company_credits } }; individual_reveal: { api_credits: { total, email_credits, phone_credits, scrape_credits } }', }, email_credits: { type: 'json', diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index 4a17b845263..4b1d0b556ed 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -50,18 +50,24 @@ export function getModelOptions() { const providersState = useProvidersStore.getState() const baseModels = providersState.providers.base.models const ollamaModels = providersState.providers.ollama.models + const ollamaCloudModels = providersState.providers['ollama-cloud'].models const vllmModels = providersState.providers.vllm.models const litellmModels = providersState.providers.litellm.models const openrouterModels = providersState.providers.openrouter.models const fireworksModels = providersState.providers.fireworks.models + const togetherModels = providersState.providers.together.models + const basetenModels = providersState.providers.baseten.models const allModels = Array.from( new Set([ ...baseModels, ...ollamaModels, + ...ollamaCloudModels, ...vllmModels, ...litellmModels, ...openrouterModels, ...fireworksModels, + ...togetherModels, + ...basetenModels, ]) ) diff --git a/apps/sim/components/emcn/components/calendar/calendar.tsx b/apps/sim/components/emcn/components/calendar/calendar.tsx new file mode 100644 index 00000000000..82fada85647 --- /dev/null +++ b/apps/sim/components/emcn/components/calendar/calendar.tsx @@ -0,0 +1,213 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' +import { ChevronLeft, ChevronRight } from 'lucide-react' +import { cn } from '@/lib/core/utils/cn' + +const MONTHS = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +] as const + +const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] as const + +function getDaysInMonth(year: number, month: number): number { + return new Date(year, month + 1, 0).getDate() +} + +function getFirstDayOfMonth(year: number, month: number): number { + return new Date(year, month, 1).getDay() +} + +function pad2(value: number): string { + return value.toString().padStart(2, '0') +} + +/** + * Serializes a calendar cell to the `YYYY-MM-DD` wire format used across the + * date filters. Built from local Y/M/D parts so there is no UTC offset shift. + */ +function toDateString(year: number, month: number, day: number): string { + return `${year}-${pad2(month + 1)}-${pad2(day)}` +} + +/** + * Parses a `YYYY-MM-DD` string (or `Date`) into a local `Date`. `YYYY-MM-DD` + * is parsed as local time to avoid the off-by-one day that `new Date('2026-05-08')` + * (UTC midnight) produces in negative-offset timezones. + */ +export function parseDateValue(value: string | Date | undefined): Date | null { + if (!value) return null + if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value + if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { + const [year, month, day] = value.split('-').map(Number) + return new Date(year, month - 1, day) + } + const parsed = new Date(value) + return Number.isNaN(parsed.getTime()) ? null : parsed +} + +/** + * Human-readable label for a date value (e.g. `May 8, 2026`). Returns an empty + * string when there is no parseable date — callers fall back to a placeholder. + */ +export function formatDateLabel(value: string | Date | undefined): string { + const date = parseDateValue(value) + if (!date) return '' + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) +} + +function isSameDay(a: Date, b: Date): boolean { + return ( + a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() && + a.getDate() === b.getDate() + ) +} + +export interface CalendarProps { + /** Selected date as a `YYYY-MM-DD` string or `Date`. */ + value?: string | Date + /** Called with the picked date in `YYYY-MM-DD` format. */ + onChange?: (value: string) => void + /** Forwarded to the root grid container. */ + className?: string +} + +/** + * Single-month date grid aligned with the chip family — `rounded-lg` day cells, + * `--surface-active` hover, and a `primary`-chip fill on the selected day. Pair + * it with {@link ChipDatePicker} for a chip trigger, or embed it directly. + * + * @example + * + */ +export function Calendar({ value, onChange, className }: CalendarProps) { + const selected = useMemo(() => parseDateValue(value), [value]) + const today = useMemo(() => new Date(), []) + + const [view, setView] = useState(() => { + const base = selected ?? today + return { month: base.getMonth(), year: base.getFullYear() } + }) + + useEffect(() => { + if (selected) { + setView({ month: selected.getMonth(), year: selected.getFullYear() }) + } + }, [selected]) + + const cells = useMemo<(number | null)[]>(() => { + const leading = getFirstDayOfMonth(view.year, view.month) + const total = getDaysInMonth(view.year, view.month) + const result: (number | null)[] = [] + for (let i = 0; i < leading; i++) result.push(null) + for (let day = 1; day <= total; day++) result.push(day) + return result + }, [view]) + + const goToPrevMonth = () => + setView((prev) => + prev.month === 0 + ? { month: 11, year: prev.year - 1 } + : { month: prev.month - 1, year: prev.year } + ) + + const goToNextMonth = () => + setView((prev) => + prev.month === 11 + ? { month: 0, year: prev.year + 1 } + : { month: prev.month + 1, year: prev.year } + ) + + const selectDay = (day: number) => onChange?.(toDateString(view.year, view.month, day)) + + const goToToday = () => { + setView({ month: today.getMonth(), year: today.getFullYear() }) + onChange?.(toDateString(today.getFullYear(), today.getMonth(), today.getDate())) + } + + return ( +
    +
    + + + {MONTHS[view.month]} {view.year} + + +
    + +
    + {WEEKDAYS.map((weekday) => ( +
    + {weekday} +
    + ))} +
    + +
    + {cells.map((day, index) => { + if (day === null) return
    + + const cellDate = new Date(view.year, view.month, day) + const isSelected = selected ? isSameDay(cellDate, selected) : false + const isToday = isSameDay(cellDate, today) + + return ( +
    + +
    + ) + })} +
    + + +
    + ) +} diff --git a/apps/sim/components/emcn/components/chip-date-picker/chip-date-picker.tsx b/apps/sim/components/emcn/components/chip-date-picker/chip-date-picker.tsx new file mode 100644 index 00000000000..72e725de458 --- /dev/null +++ b/apps/sim/components/emcn/components/chip-date-picker/chip-date-picker.tsx @@ -0,0 +1,110 @@ +'use client' + +import { forwardRef, useState } from 'react' +import * as PopoverPrimitive from '@radix-ui/react-popover' +import { Calendar, formatDateLabel } from '@/components/emcn/components/calendar/calendar' +import { chipVariants, TRIGGER_BORDER_CLASS } from '@/components/emcn/components/chip/chip' +import { ChevronDown } from '@/components/emcn/icons' +import { cn } from '@/lib/core/utils/cn' + +export const POPOVER_ANIMATION_CLASSES = + 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=open]:animate-in motion-reduce:animate-none' + +export interface ChipDatePickerProps { + /** Selected date as a `YYYY-MM-DD` string. */ + value?: string + /** Called with the picked date in `YYYY-MM-DD` format. */ + onChange?: (value: string) => void + /** Shown in the trigger when no date is selected. */ + placeholder?: string + /** Aligns the calendar popover relative to the trigger. */ + align?: 'start' | 'center' | 'end' + /** Disables the trigger. */ + disabled?: boolean + /** Stretch the trigger to fill its container (mirrors `Chip`'s `fullWidth`). */ + fullWidth?: boolean + /** Removes the default `mx-0.5` cluster margin (mirrors `Chip`'s `flush`). */ + flush?: boolean + /** Forwarded class for the trigger button. */ + className?: string +} + +/** + * Date counterpart to {@link ChipDropdown} — a chip-styled trigger that opens a + * {@link Calendar} in a popover. The trigger reuses `chipVariants` (filled + + * border) and the owned chevron for visual parity with the other chip controls. + * + * @example + * + */ +const ChipDatePicker = forwardRef(function ChipDatePicker( + { + value, + onChange, + placeholder = 'Select date', + align = 'start', + disabled, + fullWidth, + flush, + className, + }, + ref +) { + const [open, setOpen] = useState(false) + const label = formatDateLabel(value) + + return ( + + + + + + + { + onChange?.(next) + setOpen(false) + }} + /> + + + + ) +}) + +ChipDatePicker.displayName = 'ChipDatePicker' + +export { ChipDatePicker } diff --git a/apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx b/apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx index 5c1d2cb94e8..bdf43feb326 100644 --- a/apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx +++ b/apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx @@ -2,7 +2,7 @@ import { type ComponentType, forwardRef, type ReactNode } from 'react' import type { VariantProps } from 'class-variance-authority' -import { chipVariants } from '@/components/emcn/components/chip/chip' +import { chipVariants, TRIGGER_BORDER_CLASS } from '@/components/emcn/components/chip/chip' import { DropdownMenu, DropdownMenuContent, @@ -68,17 +68,6 @@ interface ChipDropdownProps extends VariantProps { className?: string } -/** - * Variants that read as flat surfaces — `ghost` is transparent, `filled` is a - * muted surface tint. We add a 1px border on these so the chip reads as an - * interactive form control. - * - * `primary` ships with a solid contrast background and `destructive` already - * carries its own red border via {@link chipVariants}; both are skipped here - * to avoid stacking a second border on top. - */ -const TRIGGER_BORDER_CLASS = 'border border-[var(--border-1)]' - /** * Dropdown counterpart to {@link Chip} — a 30px pill that opens a menu of * options and reports the selected value via `onChange`. @@ -112,6 +101,7 @@ const ChipDropdown = forwardRef(function C variant = 'filled', active, fullWidth, + flush, }, ref ) { @@ -149,7 +139,7 @@ const ChipDropdown = forwardRef(function C type='button' disabled={disabled} className={cn( - chipVariants({ variant, active, fullWidth }), + chipVariants({ variant, active, fullWidth, flush }), hasTriggerBorder && TRIGGER_BORDER_CLASS, className )} diff --git a/apps/sim/components/emcn/components/chip-multi-select/chip-multi-select.tsx b/apps/sim/components/emcn/components/chip-multi-select/chip-multi-select.tsx new file mode 100644 index 00000000000..9701e6167bb --- /dev/null +++ b/apps/sim/components/emcn/components/chip-multi-select/chip-multi-select.tsx @@ -0,0 +1,202 @@ +'use client' + +import { type ComponentType, forwardRef, type ReactNode, useMemo, useState } from 'react' +import type { VariantProps } from 'class-variance-authority' +import { chipVariants, TRIGGER_BORDER_CLASS } from '@/components/emcn/components/chip/chip' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSearchInput, + DropdownMenuTrigger, +} from '@/components/emcn/components/dropdown-menu/dropdown-menu' +import { Check, ChevronDown } from '@/components/emcn/icons' +import { cn } from '@/lib/core/utils/cn' + +type ChipIcon = ComponentType<{ className?: string }> + +/** + * Single option rendered inside a {@link ChipMultiSelect}. + */ +interface ChipMultiSelectOption { + /** Stable identifier toggled in/out of the selected `values`. */ + value: string + /** Visual label rendered in the menu and (when only one is picked) the trigger. */ + label: string + /** Optional leading icon component rendered inside the menu item. */ + icon?: ChipIcon + /** Pre-rendered leading element (e.g. an avatar) — takes precedence over `icon`. */ + iconElement?: ReactNode +} + +interface ChipMultiSelectProps + extends Pick, 'fullWidth' | 'flush'> { + /** Currently selected values. Empty array reads as "all" / no filter. */ + values: string[] + /** Called with the next selected values when an option is toggled. */ + onChange: (values: string[]) => void + /** Options to render in the menu. */ + options: ReadonlyArray + /** Label shown in the trigger and as the reset row when nothing is selected. */ + allLabel?: string + /** Renders a search field at the top of the menu that filters options by label. */ + searchable?: boolean + /** Placeholder for the search field. */ + searchPlaceholder?: string + /** Aligns the menu relative to the trigger. */ + align?: 'start' | 'center' | 'end' + /** + * Whether the menu width matches the trigger's (default `true`). Set `false` + * to let the menu size to its widest item instead. + */ + matchTriggerWidth?: boolean + /** Forwarded to the inner `DropdownMenuContent`. */ + contentClassName?: string + /** Disables the trigger. */ + disabled?: boolean + /** Forwarded class for the trigger button. */ + className?: string +} + +/** + * Multi-select counterpart to {@link ChipDropdown} — a chip trigger that opens a + * menu of toggleable options with a leading "all" reset row and an optional + * search field. The menu stays open across selections; a trailing check marks + * each active option, matching `ChipDropdown`'s single-select affordance. + * + * @example + * + */ +const ChipMultiSelect = forwardRef( + function ChipMultiSelect( + { + values, + onChange, + options, + allLabel = 'All', + searchable = false, + searchPlaceholder = 'Search...', + align = 'start', + matchTriggerWidth = true, + contentClassName, + disabled, + fullWidth, + flush, + className, + }, + ref + ) { + const [open, setOpen] = useState(false) + const [search, setSearch] = useState('') + + const displayLabel = + values.length === 0 + ? allLabel + : values.length === 1 + ? (options.find((o) => o.value === values[0])?.label ?? allLabel) + : `${values.length} selected` + + const filteredOptions = useMemo(() => { + const query = search.trim().toLowerCase() + if (!searchable || !query) return options + return options.filter((option) => option.label.toLowerCase().includes(query)) + }, [options, searchable, search]) + + const toggle = (value: string) => { + onChange(values.includes(value) ? values.filter((v) => v !== value) : [...values, value]) + } + + return ( + { + setOpen(next) + if (!next) setSearch('') + }} + > + + + + event.preventDefault() : undefined} + className={cn( + matchTriggerWidth && 'w-[var(--radix-dropdown-menu-trigger-width)] max-w-none', + contentClassName + )} + > + {searchable && ( + setSearch(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Escape') setOpen(false) + }} + /> + )} + { + event.preventDefault() + onChange([]) + }} + > + {allLabel} + {values.length === 0 ? : null} + + {filteredOptions.map((option) => { + const isSelected = values.includes(option.value) + const OptionIcon = option.icon + return ( + { + event.preventDefault() + toggle(option.value) + }} + > + {option.iconElement ?? (OptionIcon ? : null)} + {option.label} + {isSelected ? : null} + + ) + })} + + + ) + } +) + +ChipMultiSelect.displayName = 'ChipMultiSelect' + +export { ChipMultiSelect } +export type { ChipMultiSelectOption, ChipMultiSelectProps } diff --git a/apps/sim/components/emcn/components/chip/chip.tsx b/apps/sim/components/emcn/components/chip/chip.tsx index 54366ee371e..3d6eb2d27aa 100644 --- a/apps/sim/components/emcn/components/chip/chip.tsx +++ b/apps/sim/components/emcn/components/chip/chip.tsx @@ -139,5 +139,12 @@ const ChipLink = forwardRef(function ChipLink( ) }) +/** + * 1px border applied to `filled` and `ghost` chip triggers to read as + * interactive form controls rather than static pills. Omitted on `primary` and + * `destructive` variants which already carry their own border styling. + */ +export const TRIGGER_BORDER_CLASS = 'border border-[var(--border-1)]' + export { Chip, ChipLink, chipVariants } export type { ChipLinkProps, ChipProps } diff --git a/apps/sim/components/emcn/components/index.ts b/apps/sim/components/emcn/components/index.ts index a7e5e1e7300..12b95fdd63c 100644 --- a/apps/sim/components/emcn/components/index.ts +++ b/apps/sim/components/emcn/components/index.ts @@ -15,6 +15,7 @@ export { buttonGroupItemVariants, buttonGroupVariants, } from './button-group/button-group' +export { Calendar, type CalendarProps, formatDateLabel, parseDateValue } from './calendar/calendar' export { Callout, calloutVariants } from './callout/callout' export { Checkbox, @@ -22,6 +23,7 @@ export { checkboxVariants, } from './checkbox/checkbox' export { Chip, ChipLink, type ChipLinkProps, type ChipProps, chipVariants } from './chip/chip' +export { ChipDatePicker, type ChipDatePickerProps } from './chip-date-picker/chip-date-picker' export { ChipDropdown, type ChipDropdownOption, @@ -40,6 +42,11 @@ export { type ChipModalHeaderProps, type ChipModalProps, } from './chip-modal/chip-modal' +export { + ChipMultiSelect, + type ChipMultiSelectOption, + type ChipMultiSelectProps, +} from './chip-multi-select/chip-multi-select' export { ChipSwitch, type ChipSwitchOption, diff --git a/apps/sim/components/emcn/components/input/input.tsx b/apps/sim/components/emcn/components/input/input.tsx index b216d67bb52..bdf162f3d64 100644 --- a/apps/sim/components/emcn/components/input/input.tsx +++ b/apps/sim/components/emcn/components/input/input.tsx @@ -31,6 +31,12 @@ const inputVariants = cva( variants: { variant: { default: '', + /** + * Aligns the input with the chip family — a 30px `rounded-lg` filled + * surface that sits flush next to {@link ChipDropdown} / {@link ChipDatePicker} + * in form rows (e.g. tag-filter values). + */ + chip: 'h-[30px] rounded-lg text-[var(--text-body)] focus-visible:border-[var(--border-focus)] dark:bg-[var(--surface-4)]', }, }, defaultVariants: { diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 92b913971fd..e2c0e8946a6 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -91,7 +91,12 @@ export function AgentPhoneIcon(props: SVGProps) { export function CrowdStrikeIcon(props: SVGProps) { return ( - + ) { export function FirecrawlIcon(props: SVGProps) { return ( - + ) { export function SerperIcon(props: SVGProps) { return ( - + ) { export function NotionIcon(props: SVGProps) { return ( - + ) @@ -1171,27 +1176,13 @@ export function GrafanaIcon(props: SVGProps) { const gradientId = `grafana_gradient_${id}` return ( - + - + @@ -1504,7 +1495,7 @@ export function ProspeoIcon(props: SVGProps) { {...props} width='1em' height='1em' - viewBox='0 0 32 32' + viewBox='1.17 1.178 29.66 29.643' fill='none' xmlns='http://www.w3.org/2000/svg' > @@ -2029,7 +2020,9 @@ export function ConfluenceIcon(props: SVGProps) { return (

    ( + getCredits: (params: P, output: Record) => number +): ToolHostingConfig

    { + return { + envKeyPrefix: FINDYMAIL_API_KEY_PREFIX, + apiKeyParam: 'apiKey', + byokProviderId: 'findymail', + pricing: { + type: 'custom', + getCost: (params, output) => { + const credits = getCredits(params, output) + return { cost: credits * FINDYMAIL_CREDIT_USD, metadata: { credits } } + }, + }, + rateLimit: { + mode: 'per_request', + requestsPerMinute: 60, + }, + } +} diff --git a/apps/sim/tools/findymail/lookup_technologies.ts b/apps/sim/tools/findymail/lookup_technologies.ts index b7fb2af8d1d..e7360f36151 100644 --- a/apps/sim/tools/findymail/lookup_technologies.ts +++ b/apps/sim/tools/findymail/lookup_technologies.ts @@ -1,3 +1,4 @@ +import { findymailHosting } from '@/tools/findymail/hosting' import type { FindymailLookupTechnologiesParams, FindymailLookupTechnologiesResponse, @@ -15,6 +16,11 @@ export const lookupTechnologiesTool: ToolConfig< 'Get the technology stack for a company by domain. Optionally filter by technology names. 1 finder credit if technologies are found, free otherwise.', version: '1.0.0', + hosting: findymailHosting((_params, output) => { + // 1 finder credit when a technology stack is returned, free otherwise. + return Array.isArray(output.technologies) && output.technologies.length > 0 ? 1 : 0 + }), + params: { domain: { type: 'string', diff --git a/apps/sim/tools/findymail/reverse_email_lookup.ts b/apps/sim/tools/findymail/reverse_email_lookup.ts index 48a0c42ca18..447d112b696 100644 --- a/apps/sim/tools/findymail/reverse_email_lookup.ts +++ b/apps/sim/tools/findymail/reverse_email_lookup.ts @@ -1,3 +1,4 @@ +import { findymailHosting } from '@/tools/findymail/hosting' import type { FindymailReverseEmailLookupParams, FindymailReverseEmailLookupResponse, @@ -14,6 +15,13 @@ export const reverseEmailLookupTool: ToolConfig< 'Find a business profile from an email address. Uses 1 finder credit if a profile is found, 2 credits if returning full profile data.', version: '1.0.0', + hosting: findymailHosting((params, output) => { + const found = Boolean(output.email || output.linkedin_url || output.fullName) + if (!found) return 0 + // 1 credit for a match, 2 when full profile enrichment is requested. + return params.with_profile ? 2 : 1 + }), + params: { email: { type: 'string', diff --git a/apps/sim/tools/findymail/search_technologies.ts b/apps/sim/tools/findymail/search_technologies.ts index a4f7bcadcc5..64d56b785a3 100644 --- a/apps/sim/tools/findymail/search_technologies.ts +++ b/apps/sim/tools/findymail/search_technologies.ts @@ -1,3 +1,4 @@ +import { findymailHosting } from '@/tools/findymail/hosting' import type { FindymailSearchTechnologiesParams, FindymailSearchTechnologiesResponse, @@ -15,6 +16,11 @@ export const searchTechnologiesTool: ToolConfig< 'Search the technology catalog by name. Returns up to 25 technologies. Free endpoint, rate limited to 10 requests per minute.', version: '1.0.0', + hosting: findymailHosting(() => { + // Free catalog search — consumes no Findymail credits. + return 0 + }), + params: { q: { type: 'string', diff --git a/apps/sim/tools/findymail/verify_email.ts b/apps/sim/tools/findymail/verify_email.ts index c239e296cd0..30463e1fe6c 100644 --- a/apps/sim/tools/findymail/verify_email.ts +++ b/apps/sim/tools/findymail/verify_email.ts @@ -1,3 +1,4 @@ +import { findymailHosting } from '@/tools/findymail/hosting' import type { FindymailVerifyEmailParams, FindymailVerifyEmailResponse, @@ -11,6 +12,11 @@ export const verifyEmailTool: ToolConfig(() => { + // Each verification consumes one verifier credit, billed at the finder-credit rate. + return 1 + }), + params: { email: { type: 'string', diff --git a/apps/sim/tools/google_sheets/append.ts b/apps/sim/tools/google_sheets/append.ts index b3cdfb06b91..7f290a2ce3b 100644 --- a/apps/sim/tools/google_sheets/append.ts +++ b/apps/sim/tools/google_sheets/append.ts @@ -69,14 +69,14 @@ export const appendTool: ToolConfig { + it('passes values through unchanged when no filter column is provided', () => { + const result = filterSheetRows(VALUES, {}) + expect(result.applied).toBe(false) + expect(result.values).toBe(VALUES) + expect(result.totalRows).toBe(3) + }) + + it('passes through when filterValue is empty', () => { + const result = filterSheetRows(VALUES, { filterColumn: 'Status', filterValue: '' }) + expect(result.applied).toBe(false) + expect(result.values).toBe(VALUES) + }) + + it('defaults to case-insensitive contains', () => { + const result = filterSheetRows(VALUES, { filterColumn: 'status', filterValue: 'active' }) + expect(result.applied).toBe(true) + expect(result.columnFound).toBe(true) + expect(result.values).toEqual([VALUES[0], VALUES[1], VALUES[3]]) + expect(result.matchedRows).toBe(2) + }) + + it('matches column names case-insensitively and trims whitespace', () => { + const result = filterSheetRows(VALUES, { + filterColumn: ' EMAIL ', + filterValue: 'example.com', + }) + expect(result.columnFound).toBe(true) + expect(result.matchedRows).toBe(2) + }) + + it('supports exact and not_equals', () => { + expect( + filterSheetRows(VALUES, { + filterColumn: 'Status', + filterValue: 'Active', + filterMatchType: 'exact', + }).matchedRows + ).toBe(2) + expect( + filterSheetRows(VALUES, { + filterColumn: 'Status', + filterValue: 'Active', + filterMatchType: 'not_equals', + }).matchedRows + ).toBe(1) + }) + + it('supports starts_with, ends_with, and not_contains', () => { + expect( + filterSheetRows(VALUES, { + filterColumn: 'Email', + filterValue: 'bob', + filterMatchType: 'starts_with', + }).matchedRows + ).toBe(1) + expect( + filterSheetRows(VALUES, { + filterColumn: 'Email', + filterValue: '.com', + filterMatchType: 'ends_with', + }).matchedRows + ).toBe(3) + expect( + filterSheetRows(VALUES, { + filterColumn: 'Email', + filterValue: 'example.com', + filterMatchType: 'not_contains', + }).matchedRows + ).toBe(1) + }) + + it('compares numerically for ordering operators (not substring)', () => { + const gt = filterSheetRows(VALUES, { + filterColumn: 'Score', + filterValue: '50', + filterMatchType: 'gt', + }) + expect(gt.matchedRows).toBe(1) + expect(gt.values).toEqual([VALUES[0], VALUES[1]]) + + expect( + filterSheetRows(VALUES, { + filterColumn: 'Score', + filterValue: '40', + filterMatchType: 'gte', + }).matchedRows + ).toBe(2) + expect( + filterSheetRows(VALUES, { + filterColumn: 'Score', + filterValue: '40', + filterMatchType: 'lt', + }).matchedRows + ).toBe(1) + expect( + filterSheetRows(VALUES, { + filterColumn: 'Score', + filterValue: '40', + filterMatchType: 'lte', + }).matchedRows + ).toBe(2) + }) + + it('orders negative numbers correctly', () => { + const temps: unknown[][] = [ + ['City', 'Temp'], + ['A', '-5'], + ['B', '0'], + ['C', '-12'], + ['D', '3'], + ] + expect( + filterSheetRows(temps, { filterColumn: 'Temp', filterValue: '-5', filterMatchType: 'gte' }) + .matchedRows + ).toBe(3) + expect( + filterSheetRows(temps, { filterColumn: 'Temp', filterValue: '0', filterMatchType: 'lt' }) + .matchedRows + ).toBe(2) + }) + + it('excludes blank and non-numeric cells from numeric comparisons', () => { + const scores: unknown[][] = [ + ['Name', 'Score'], + ['Alice', '90'], + ['Bob', ''], + ['Carol', 'N/A'], + ['Dan', '60'], + ] + const result = filterSheetRows(scores, { + filterColumn: 'Score', + filterValue: '50', + filterMatchType: 'gt', + }) + expect(result.matchedRows).toBe(2) + expect(result.values).toEqual([scores[0], scores[1], scores[4]]) + }) + + it('falls back to lexicographic ordering when values are not numeric (ISO dates)', () => { + const dated: unknown[][] = [ + ['Task', 'Due'], + ['A', '2026-01-15'], + ['B', '2026-03-01'], + ['C', '2025-12-31'], + ] + const result = filterSheetRows(dated, { + filterColumn: 'Due', + filterValue: '2026-01-01', + filterMatchType: 'gte', + }) + expect(result.matchedRows).toBe(2) + }) + + it('reports columnFound=false and leaves values unchanged when the column is missing', () => { + const result = filterSheetRows(VALUES, { + filterColumn: 'Nonexistent', + filterValue: 'x', + }) + expect(result.applied).toBe(false) + expect(result.columnFound).toBe(false) + expect(result.matchedRows).toBe(0) + expect(result.values).toBe(VALUES) + expect(result.totalRows).toBe(3) + }) + + it('handles a header-only sheet and reports the column as found when it exists', () => { + const headerOnly: unknown[][] = [['Name', 'Status']] + const result = filterSheetRows(headerOnly, { filterColumn: 'Status', filterValue: 'Active' }) + expect(result.applied).toBe(false) + expect(result.columnFound).toBe(true) + expect(result.matchedRows).toBe(0) + expect(result.totalRows).toBe(0) + expect(result.values).toBe(headerOnly) + }) + + it('reports columnFound=false for a header-only sheet when the column is absent', () => { + const headerOnly: unknown[][] = [['Name', 'Status']] + const result = filterSheetRows(headerOnly, { filterColumn: 'Nonexistent', filterValue: 'x' }) + expect(result.applied).toBe(false) + expect(result.columnFound).toBe(false) + expect(result.matchedRows).toBe(0) + expect(result.values).toBe(headerOnly) + }) + + it('reports columnFound=false for an empty values array', () => { + const empty: unknown[][] = [] + const result = filterSheetRows(empty, { filterColumn: 'Status', filterValue: 'Active' }) + expect(result.applied).toBe(false) + expect(result.columnFound).toBe(false) + expect(result.matchedRows).toBe(0) + expect(result.totalRows).toBe(0) + }) + + it('treats missing cells as empty strings', () => { + const sparse: unknown[][] = [['Name', 'Status'], ['Alice'], ['Bob', 'Active']] + const result = filterSheetRows(sparse, { + filterColumn: 'Status', + filterValue: 'Active', + filterMatchType: 'exact', + }) + expect(result.matchedRows).toBe(1) + expect(result.values).toEqual([sparse[0], sparse[2]]) + }) + + it('always retains the header row in filtered output', () => { + const result = filterSheetRows(VALUES, { + filterColumn: 'Status', + filterValue: 'no-match', + filterMatchType: 'exact', + }) + expect(result.values).toEqual([VALUES[0]]) + expect(result.matchedRows).toBe(0) + }) +}) diff --git a/apps/sim/tools/google_sheets/filter.ts b/apps/sim/tools/google_sheets/filter.ts new file mode 100644 index 00000000000..ea4caa460a9 --- /dev/null +++ b/apps/sim/tools/google_sheets/filter.ts @@ -0,0 +1,152 @@ +/** + * Client-side row filtering for Google Sheets read results. + * + * The Google Sheets REST API (`spreadsheets.values.get`) has no server-side + * content filtering — `DataFilter` selects only by A1 range, grid range, or + * developer metadata, never by cell value. Filtering by cell content must + * therefore happen after values are fetched, over the window of rows the read + * returned (e.g. the default `A1:Z1000`), not the entire sheet. + */ + +/** + * Supported ways to compare a cell against the filter value. Text operators are + * case-insensitive. The ordering operators (`gt`/`gte`/`lt`/`lte`) compare + * numerically when both operands parse as finite numbers, fall back to + * case-insensitive lexicographic comparison when both are non-numeric (which + * orders ISO dates correctly), and never match when one side is numeric and the + * other is not (the values are not comparable). + */ +export type SheetFilterMatchType = + | 'contains' + | 'not_contains' + | 'exact' + | 'not_equals' + | 'starts_with' + | 'ends_with' + | 'gt' + | 'gte' + | 'lt' + | 'lte' + +export interface SheetFilterOptions { + filterColumn?: string + filterValue?: string + filterMatchType?: SheetFilterMatchType +} + +export interface SheetFilterResult { + /** The (possibly filtered) values, always including the header row when present. */ + values: unknown[][] + /** Whether row filtering was actually applied to the data rows. */ + applied: boolean + /** Whether the requested filter column was found in the header row. */ + columnFound: boolean + /** Number of data rows (excluding the header) that matched the filter. */ + matchedRows: number + /** Total number of data rows (excluding the header) that were considered. */ + totalRows: number +} + +const DEFAULT_MATCH_TYPE: SheetFilterMatchType = 'contains' + +/** + * Parses a cell string as a finite number, or returns null when it is blank or + * non-numeric so callers can fall back to lexicographic comparison. + */ +function asFiniteNumber(value: string): number | null { + if (value.trim() === '') return null + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : null +} + +/** Case-insensitive lexicographic comparison returning -1, 0, or 1. */ +function compareLexicographic(cell: string, target: string): number { + return Math.sign(cell.toLowerCase().localeCompare(target.toLowerCase())) +} + +/** Evaluates a single cell against the filter target for the given match type. */ +function matchesCell(cell: string, target: string, matchType: SheetFilterMatchType): boolean { + switch (matchType) { + case 'gt': + case 'gte': + case 'lt': + case 'lte': { + const cellNum = asFiniteNumber(cell) + const targetNum = asFiniteNumber(target) + let cmp: number + if (cellNum !== null && targetNum !== null) { + cmp = Math.sign(cellNum - targetNum) + } else if (cellNum === null && targetNum === null) { + cmp = compareLexicographic(cell, target) + } else { + return false + } + if (matchType === 'gt') return cmp > 0 + if (matchType === 'gte') return cmp >= 0 + if (matchType === 'lt') return cmp < 0 + return cmp <= 0 + } + case 'exact': + return cell.toLowerCase() === target.toLowerCase() + case 'not_equals': + return cell.toLowerCase() !== target.toLowerCase() + case 'starts_with': + return cell.toLowerCase().startsWith(target.toLowerCase()) + case 'ends_with': + return cell.toLowerCase().endsWith(target.toLowerCase()) + case 'not_contains': + return !cell.toLowerCase().includes(target.toLowerCase()) + default: + return cell.toLowerCase().includes(target.toLowerCase()) + } +} + +/** + * Filters a 2D values array (header row + data rows) by matching a single column + * against a target value. Returns the original values untouched when no filter + * is requested, when there are no data rows, or when the column is not found — + * the `applied`/`columnFound` flags let callers distinguish "no match possible" + * from "everything matched". + */ +export function filterSheetRows( + values: unknown[][], + options: SheetFilterOptions +): SheetFilterResult { + const { filterColumn, filterValue, filterMatchType } = options + const totalRows = Math.max(values.length - 1, 0) + + if (!filterColumn || filterValue === undefined || filterValue === '') { + return { values, applied: false, columnFound: true, matchedRows: totalRows, totalRows } + } + + const headers = values[0] ?? [] + const normalizedColumn = filterColumn.trim().toLowerCase() + const columnIndex = headers.findIndex( + (header) => String(header).trim().toLowerCase() === normalizedColumn + ) + const columnFound = columnIndex !== -1 + + // No data rows to evaluate (empty or header-only sheet): nothing matched, but + // still report whether the requested column actually exists in the header. + if (values.length <= 1) { + return { values, applied: false, columnFound, matchedRows: 0, totalRows: 0 } + } + + // Column not found: leave rows untouched and report zero matches, not totalRows. + if (!columnFound) { + return { values, applied: false, columnFound: false, matchedRows: 0, totalRows } + } + + const matchType = filterMatchType ?? DEFAULT_MATCH_TYPE + const matched = values + .slice(1) + .filter((row) => matchesCell(String(row[columnIndex] ?? ''), filterValue, matchType)) + + return { + values: [values[0], ...matched], + applied: true, + columnFound: true, + matchedRows: matched.length, + totalRows, + } +} diff --git a/apps/sim/tools/google_sheets/read.test.ts b/apps/sim/tools/google_sheets/read.test.ts new file mode 100644 index 00000000000..829969bcdbc --- /dev/null +++ b/apps/sim/tools/google_sheets/read.test.ts @@ -0,0 +1,97 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { readV2Tool } from '@/tools/google_sheets/read' +import type { GoogleSheetsV2ToolParams } from '@/tools/google_sheets/types' + +const SPREADSHEET_ID = 'abc123' +const URL = `https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/Sheet1!A1:Z1000` + +const SHEET_DATA = { + range: 'Sheet1!A1:D4', + values: [ + ['Name', 'Status'], + ['Alice', 'Active'], + ['Bob', 'Closed'], + ], +} + +function mockResponse(body: unknown, url = URL): Response { + // double-cast-allowed: lightweight Response stub for transformResponse unit test + return { url, json: async () => body } as unknown as Response +} + +const baseParams: GoogleSheetsV2ToolParams = { + accessToken: 'token', + spreadsheetId: SPREADSHEET_ID, + sheetName: 'Sheet1', +} + +describe('readV2Tool.transformResponse', () => { + it('returns values untouched and omits the filter field when no filter is requested', async () => { + const result = await readV2Tool.transformResponse!(mockResponse(SHEET_DATA), baseParams) + + expect(result.output.values).toEqual(SHEET_DATA.values) + expect('filter' in result.output).toBe(false) + expect(result.output.range).toBe(SHEET_DATA.range) + expect(result.output.metadata.spreadsheetId).toBe(SPREADSHEET_ID) + }) + + it('omits the filter field when filterColumn is set but filterValue is empty', async () => { + const result = await readV2Tool.transformResponse!(mockResponse(SHEET_DATA), { + ...baseParams, + filterColumn: 'Status', + filterValue: '', + }) + + expect('filter' in result.output).toBe(false) + expect(result.output.values).toEqual(SHEET_DATA.values) + }) + + it('filters rows and reports filter metadata when a filter is applied', async () => { + const result = await readV2Tool.transformResponse!(mockResponse(SHEET_DATA), { + ...baseParams, + filterColumn: 'Status', + filterValue: 'Active', + filterMatchType: 'exact', + }) + + expect(result.output.values).toEqual([ + ['Name', 'Status'], + ['Alice', 'Active'], + ]) + expect(result.output.filter).toEqual({ + applied: true, + column: 'Status', + matchType: 'exact', + columnFound: true, + matchedRows: 1, + totalRows: 2, + }) + }) + + it('leaves values unchanged and reports columnFound=false when the column is missing', async () => { + const result = await readV2Tool.transformResponse!(mockResponse(SHEET_DATA), { + ...baseParams, + filterColumn: 'Nonexistent', + filterValue: 'x', + }) + + expect(result.output.values).toEqual(SHEET_DATA.values) + expect(result.output.filter?.columnFound).toBe(false) + expect(result.output.filter?.applied).toBe(false) + expect(result.output.filter?.matchedRows).toBe(0) + }) + + it('handles a response with no values array', async () => { + const result = await readV2Tool.transformResponse!(mockResponse({ range: 'Sheet1!A1' }), { + ...baseParams, + filterColumn: 'Status', + filterValue: 'Active', + }) + + expect(result.output.values).toEqual([]) + expect(result.output.filter?.applied).toBe(false) + }) +}) diff --git a/apps/sim/tools/google_sheets/read.ts b/apps/sim/tools/google_sheets/read.ts index 74948847275..c3b8bfa994e 100644 --- a/apps/sim/tools/google_sheets/read.ts +++ b/apps/sim/tools/google_sheets/read.ts @@ -1,3 +1,4 @@ +import { filterSheetRows } from '@/tools/google_sheets/filter' import type { GoogleSheetsReadResponse, GoogleSheetsToolParams, @@ -53,7 +54,7 @@ export const readTool: ToolConfig 1) { - const headers = values[0] as string[] - const columnIndex = headers.findIndex( - (h) => String(h).toLowerCase() === params.filterColumn!.toLowerCase() - ) + const filterRequested = + Boolean(params?.filterColumn) && + params?.filterValue !== undefined && + params?.filterValue !== '' - if (columnIndex !== -1) { - const matchType = params.filterMatchType ?? 'contains' - const filterVal = params.filterValue.toLowerCase() - - const filteredRows = values.slice(1).filter((row) => { - const cellValue = String(row[columnIndex] ?? '').toLowerCase() - switch (matchType) { - case 'exact': - return cellValue === filterVal - case 'starts_with': - return cellValue.startsWith(filterVal) - case 'ends_with': - return cellValue.endsWith(filterVal) - default: - return cellValue.includes(filterVal) - } - }) - - // Return header row + matching rows - values = [values[0], ...filteredRows] - } - } + const filterResult = filterSheetRows(rawValues, { + filterColumn: params?.filterColumn, + filterValue: params?.filterValue, + filterMatchType: params?.filterMatchType, + }) return { success: true, output: { sheetName: params?.sheetName ?? '', range: data.range ?? '', - values, + values: filterResult.values, metadata: { spreadsheetId: metadata.spreadsheetId, spreadsheetUrl: metadata.spreadsheetUrl, }, + ...(filterRequested + ? { + filter: { + applied: filterResult.applied, + column: params?.filterColumn ?? '', + matchType: params?.filterMatchType ?? 'contains', + columnFound: filterResult.columnFound, + matchedRows: filterResult.matchedRows, + totalRows: filterResult.totalRows, + }, + } + : {}), }, } }, diff --git a/apps/sim/tools/google_sheets/types.ts b/apps/sim/tools/google_sheets/types.ts index 8379d78ebe9..119a71e305d 100644 --- a/apps/sim/tools/google_sheets/types.ts +++ b/apps/sim/tools/google_sheets/types.ts @@ -1,6 +1,7 @@ import type { GoogleSheetsV2DeleteRowsResponse } from '@/tools/google_sheets/delete_rows' import type { GoogleSheetsV2DeleteSheetResponse } from '@/tools/google_sheets/delete_sheet' import type { GoogleSheetsV2DeleteSpreadsheetResponse } from '@/tools/google_sheets/delete_spreadsheet' +import type { SheetFilterMatchType } from '@/tools/google_sheets/filter' import type { ToolResponse } from '@/tools/types' interface GoogleSheetsRange { @@ -81,12 +82,28 @@ export type GoogleSheetsResponse = // V2 Types - with explicit sheetName parameter +export interface GoogleSheetsFilterInfo { + /** Whether row filtering was actually applied to the data rows. */ + applied: boolean + /** The column header the filter targeted. */ + column: string + /** The match type used for the comparison. */ + matchType: SheetFilterMatchType + /** Whether the requested filter column was found in the header row. */ + columnFound: boolean + /** Number of data rows (excluding the header) that matched the filter. */ + matchedRows: number + /** Total number of data rows (excluding the header) that were considered. */ + totalRows: number +} + export interface GoogleSheetsV2ReadResponse extends ToolResponse { output: { sheetName: string range: string values: any[][] metadata: GoogleSheetsMetadata + filter?: GoogleSheetsFilterInfo } } @@ -134,7 +151,7 @@ export interface GoogleSheetsV2ToolParams { majorDimension?: 'ROWS' | 'COLUMNS' filterColumn?: string filterValue?: string - filterMatchType?: 'contains' | 'exact' | 'starts_with' | 'ends_with' + filterMatchType?: SheetFilterMatchType } export type GoogleSheetsV2Response = diff --git a/apps/sim/tools/google_sheets/update.ts b/apps/sim/tools/google_sheets/update.ts index fb088c60c77..f6900e2bc83 100644 --- a/apps/sim/tools/google_sheets/update.ts +++ b/apps/sim/tools/google_sheets/update.ts @@ -63,7 +63,7 @@ export const updateTool: ToolConfig((_params, output) => { + // Prospeo reports the exact credits spent for the batch in total_cost. + if (typeof output.total_cost !== 'number') { + throw new Error('Prospeo bulk enrich company response missing total_cost') + } + return output.total_cost + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/prospeo/bulk_enrich_person.ts b/apps/sim/tools/prospeo/bulk_enrich_person.ts index b6d095e9526..e594e9b7cb4 100644 --- a/apps/sim/tools/prospeo/bulk_enrich_person.ts +++ b/apps/sim/tools/prospeo/bulk_enrich_person.ts @@ -1,3 +1,4 @@ +import { prospeoHosting } from '@/tools/prospeo/hosting' import { extractProspeoError, type ProspeoBulkEnrichPersonParams, @@ -15,6 +16,14 @@ export const bulkEnrichPersonTool: ToolConfig< description: 'Enrich up to 50 person records at once.', version: '1.0.0', + hosting: prospeoHosting((_params, output) => { + // Prospeo reports the exact credits spent for the batch in total_cost. + if (typeof output.total_cost !== 'number') { + throw new Error('Prospeo bulk enrich person response missing total_cost') + } + return output.total_cost + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/prospeo/enrich_company.ts b/apps/sim/tools/prospeo/enrich_company.ts index ca4e1ed9b99..7f0b141e3d3 100644 --- a/apps/sim/tools/prospeo/enrich_company.ts +++ b/apps/sim/tools/prospeo/enrich_company.ts @@ -1,3 +1,4 @@ +import { prospeoHosting } from '@/tools/prospeo/hosting' import { extractProspeoError, type ProspeoEnrichCompanyParams, @@ -14,6 +15,12 @@ export const enrichCompanyTool: ToolConfig< description: 'Enrich a company with complete B2B data.', version: '1.0.0', + hosting: prospeoHosting((_params, output) => { + // 1 credit per company match; no charge on a no-match or repeat enrichment. + if (output.free_enrichment === true) return 0 + return output.company ? 1 : 0 + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/prospeo/enrich_person.ts b/apps/sim/tools/prospeo/enrich_person.ts index 44c72a26007..e072dc6bbbf 100644 --- a/apps/sim/tools/prospeo/enrich_person.ts +++ b/apps/sim/tools/prospeo/enrich_person.ts @@ -1,3 +1,4 @@ +import { prospeoHosting } from '@/tools/prospeo/hosting' import { extractProspeoError, type ProspeoEnrichPersonParams, @@ -12,6 +13,16 @@ export const enrichPersonTool: ToolConfig((_params, output) => { + // No charge on a no-match or a repeat enrichment. + if (output.free_enrichment === true) return 0 + const person = output.person as Record | null + if (!person) return 0 + // 10 credits when a mobile is revealed, otherwise 1 for the person match. + const mobile = person.mobile as { revealed?: boolean } | undefined + return mobile?.revealed ? 10 : 1 + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/prospeo/hosting.ts b/apps/sim/tools/prospeo/hosting.ts new file mode 100644 index 00000000000..c5d97b98475 --- /dev/null +++ b/apps/sim/tools/prospeo/hosting.ts @@ -0,0 +1,43 @@ +import type { ToolHostingConfig } from '@/tools/types' + +/** + * Env var prefix for Prospeo hosted keys. Provide keys as + * `PROSPEO_API_KEY_COUNT` plus `PROSPEO_API_KEY_1..N`. + */ +export const PROSPEO_API_KEY_PREFIX = 'PROSPEO_API_KEY' + +/** + * Dollar cost of a single Prospeo credit. + * + * Prospeo charges per match: 1 credit per person/company match, 10 credits when + * a mobile is revealed, and never on a no-match or a repeat enrichment. Based on + * the $39/month Starter plan (1,000 credits ≈ $0.039/credit) — https://prospeo.io/pricing. + */ +export const PROSPEO_CREDIT_USD = 0.039 + +/** + * Build a Prospeo `hosting` config. `getCredits` returns the number of Prospeo + * credits the call consumed, derived from the tool's output (prefer the + * API-reported `total_cost` for bulk endpoints; otherwise compute from the + * `free`/`free_enrichment` flag and the match). + */ +export function prospeoHosting

    ( + getCredits: (params: P, output: Record) => number +): ToolHostingConfig

    { + return { + envKeyPrefix: PROSPEO_API_KEY_PREFIX, + apiKeyParam: 'apiKey', + byokProviderId: 'prospeo', + pricing: { + type: 'custom', + getCost: (params, output) => { + const credits = getCredits(params, output) + return { cost: credits * PROSPEO_CREDIT_USD, metadata: { credits } } + }, + }, + rateLimit: { + mode: 'per_request', + requestsPerMinute: 60, + }, + } +} diff --git a/apps/sim/tools/prospeo/search_company.ts b/apps/sim/tools/prospeo/search_company.ts index f4e8a1c0b52..e16ecaf83c1 100644 --- a/apps/sim/tools/prospeo/search_company.ts +++ b/apps/sim/tools/prospeo/search_company.ts @@ -1,3 +1,4 @@ +import { prospeoHosting } from '@/tools/prospeo/hosting' import { extractProspeoError, PROSPEO_PAGINATION_OUTPUT, @@ -16,6 +17,13 @@ export const searchCompanyTool: ToolConfig< description: 'Search for companies using 20+ filters to build account lists.', version: '1.0.0', + hosting: prospeoHosting((_params, output) => { + // 1 credit per page that returns at least one result; free on 30-day dedup. + if (output.free === true) return 0 + const results = output.results + return Array.isArray(results) && results.length > 0 ? 1 : 0 + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/prospeo/search_person.ts b/apps/sim/tools/prospeo/search_person.ts index c8415ceec9e..883b11e73dc 100644 --- a/apps/sim/tools/prospeo/search_person.ts +++ b/apps/sim/tools/prospeo/search_person.ts @@ -1,3 +1,4 @@ +import { prospeoHosting } from '@/tools/prospeo/hosting' import { extractProspeoError, PROSPEO_PAGINATION_OUTPUT, @@ -14,6 +15,13 @@ export const searchPersonTool: ToolConfig((_params, output) => { + // 1 credit per page that returns at least one result; free on 30-day dedup. + if (output.free === true) return 0 + const results = output.results + return Array.isArray(results) && results.length > 0 ? 1 : 0 + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 3070576fa42..2bf90f1e477 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -435,6 +435,8 @@ import { cursorDownloadArtifactV2Tool, cursorGetAgentTool, cursorGetAgentV2Tool, + cursorGetApiKeyInfoTool, + cursorGetApiKeyInfoV2Tool, cursorGetConversationTool, cursorGetConversationV2Tool, cursorLaunchAgentTool, @@ -443,6 +445,10 @@ import { cursorListAgentsV2Tool, cursorListArtifactsTool, cursorListArtifactsV2Tool, + cursorListModelsTool, + cursorListModelsV2Tool, + cursorListRepositoriesTool, + cursorListRepositoriesV2Tool, cursorStopAgentTool, cursorStopAgentV2Tool, } from '@/tools/cursor' @@ -487,10 +493,17 @@ import { datadogSubmitMetricsTool, } from '@/tools/datadog' import { + devinAppendSessionTagsTool, + devinArchiveSessionTool, devinCreateSessionTool, + devinGetSessionTagsTool, devinGetSessionTool, + devinListSessionAttachmentsTool, + devinListSessionMessagesTool, devinListSessionsTool, + devinReplaceSessionTagsTool, devinSendMessageTool, + devinTerminateSessionTool, } from '@/tools/devin' import { discordAddReactionTool, @@ -3053,9 +3066,8 @@ import { import { wizaCompanyEnrichmentTool, wizaGetCreditsTool, - wizaGetIndividualRevealTool, + wizaIndividualRevealTool, wizaProspectSearchTool, - wizaStartIndividualRevealTool, } from '@/tools/wiza' import { wordpressCreateCategoryTool, @@ -4028,6 +4040,13 @@ export const tools: Record = { devin_get_session: devinGetSessionTool, devin_list_sessions: devinListSessionsTool, devin_send_message: devinSendMessageTool, + devin_list_session_messages: devinListSessionMessagesTool, + devin_list_session_attachments: devinListSessionAttachmentsTool, + devin_get_session_tags: devinGetSessionTagsTool, + devin_append_session_tags: devinAppendSessionTagsTool, + devin_replace_session_tags: devinReplaceSessionTagsTool, + devin_archive_session: devinArchiveSessionTool, + devin_terminate_session: devinTerminateSessionTool, dagster_delete_run: dagsterDeleteRunTool, dagster_get_run: dagsterGetRunTool, dagster_get_run_logs: dagsterGetRunLogsTool, @@ -4823,6 +4842,12 @@ export const tools: Record = { cursor_download_artifact_v2: cursorDownloadArtifactV2Tool, cursor_list_artifacts: cursorListArtifactsTool, cursor_list_artifacts_v2: cursorListArtifactsV2Tool, + cursor_list_models: cursorListModelsTool, + cursor_list_models_v2: cursorListModelsV2Tool, + cursor_list_repositories: cursorListRepositoriesTool, + cursor_list_repositories_v2: cursorListRepositoriesV2Tool, + cursor_get_api_key_info: cursorGetApiKeyInfoTool, + cursor_get_api_key_info_v2: cursorGetApiKeyInfoV2Tool, trello_list_lists: trelloListListsTool, trello_list_cards: trelloListCardsTool, trello_create_card: trelloCreateCardTool, @@ -5436,9 +5461,8 @@ export const tools: Record = { wikipedia_random: wikipediaRandomPageTool, wiza_company_enrichment: wizaCompanyEnrichmentTool, wiza_get_credits: wizaGetCreditsTool, - wiza_get_individual_reveal: wizaGetIndividualRevealTool, + wiza_individual_reveal: wizaIndividualRevealTool, wiza_prospect_search: wizaProspectSearchTool, - wiza_start_individual_reveal: wizaStartIndividualRevealTool, wordpress_create_post: wordpressCreatePostTool, wordpress_update_post: wordpressUpdatePostTool, wordpress_delete_post: wordpressDeletePostTool, diff --git a/apps/sim/tools/table/delete_rows_by_filter.ts b/apps/sim/tools/table/delete_rows_by_filter.ts index a31fb1bbaaa..64fbcc35de5 100644 --- a/apps/sim/tools/table/delete_rows_by_filter.ts +++ b/apps/sim/tools/table/delete_rows_by_filter.ts @@ -29,7 +29,8 @@ export const tableDeleteRowsByFilterTool: ToolConfig< filter: { type: 'object', required: true, - description: 'Filter criteria using operators like $eq, $ne, $gt, $lt, $contains, $in, etc.', + description: + 'Filter criteria using operators like $eq, $ne, $gt, $lt, $contains, $ncontains, $startsWith, $endsWith, $in, $nin, $empty, etc.', visibility: 'user-or-llm', }, limit: { diff --git a/apps/sim/tools/table/query_rows.ts b/apps/sim/tools/table/query_rows.ts index db22eda30d2..74af432238e 100644 --- a/apps/sim/tools/table/query_rows.ts +++ b/apps/sim/tools/table/query_rows.ts @@ -26,7 +26,7 @@ export const tableQueryRowsTool: ToolConfig> = PerRequestPricing * Adding more keys only requires updating the count and adding the new env var — * no code changes needed. */ -interface ToolHostingConfig

    > { +export interface ToolHostingConfig

    > { /** Optional predicate for tools where hosted keys only apply to some parameter combinations. */ enabled?: (params: P) => boolean /** diff --git a/apps/sim/tools/wiza/company_enrichment.ts b/apps/sim/tools/wiza/company_enrichment.ts index 70bfdfec5be..39089404f1c 100644 --- a/apps/sim/tools/wiza/company_enrichment.ts +++ b/apps/sim/tools/wiza/company_enrichment.ts @@ -1,4 +1,5 @@ import type { ToolConfig } from '@/tools/types' +import { wizaHosting } from '@/tools/wiza/hosting' import type { WizaCompanyEnrichmentParams, WizaCompanyEnrichmentResponse } from '@/tools/wiza/types' export const wizaCompanyEnrichmentTool: ToolConfig< @@ -11,6 +12,11 @@ export const wizaCompanyEnrichmentTool: ToolConfig< 'Enrich a company by name, domain, LinkedIn ID, or LinkedIn slug with detailed firmographic data', version: '1.0.0', + hosting: wizaHosting((_params, output) => { + // 2 API credits per successful company match; no charge on a no-match. + return output.company_name || output.company_domain || output.domain ? 2 : 0 + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/wiza/get_individual_reveal.ts b/apps/sim/tools/wiza/get_individual_reveal.ts deleted file mode 100644 index 7c3d4ec5434..00000000000 --- a/apps/sim/tools/wiza/get_individual_reveal.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { ToolConfig } from '@/tools/types' -import type { - WizaGetIndividualRevealParams, - WizaGetIndividualRevealResponse, -} from '@/tools/wiza/types' - -export const wizaGetIndividualRevealTool: ToolConfig< - WizaGetIndividualRevealParams, - WizaGetIndividualRevealResponse -> = { - id: 'wiza_get_individual_reveal', - name: 'Wiza Get Individual Reveal', - description: 'Retrieve the status and enriched data for an individual reveal by ID', - version: '1.0.0', - - params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Wiza API key', - }, - id: { - type: 'string', - required: true, - visibility: 'user-or-llm', - description: 'Individual reveal ID returned from Start Individual Reveal', - }, - }, - - request: { - url: (params: WizaGetIndividualRevealParams) => - `https://wiza.co/api/individual_reveals/${encodeURIComponent(String(params.id).trim())}`, - method: 'GET', - headers: (params: WizaGetIndividualRevealParams) => ({ - Authorization: `Bearer ${params.apiKey}`, - 'Content-Type': 'application/json', - }), - }, - - transformResponse: async (response: Response) => { - if (!response.ok) { - const errorText = await response.text() - throw new Error(`Wiza API error: ${response.status} - ${errorText}`) - } - - const json = await response.json() - const d = json.data ?? {} - const emails = Array.isArray(d.emails) ? d.emails : [] - const phones = Array.isArray(d.phones) ? d.phones : [] - - return { - success: true, - output: { - id: d.id ?? null, - status: d.status ?? null, - is_complete: d.is_complete ?? null, - name: d.name ?? null, - company: d.company ?? null, - enrichment_level: d.enrichment_level ?? null, - linkedin_profile_url: d.linkedin_profile_url ?? null, - title: d.title ?? null, - location: d.location ?? null, - email: d.email ?? null, - email_type: d.email_type ?? null, - email_status: d.email_status ?? null, - emails: emails.map((e: Record) => ({ - email: (e.email as string) ?? null, - email_type: (e.email_type as string) ?? null, - email_status: (e.email_status as string) ?? null, - })), - mobile_phone: d.mobile_phone ?? null, - phone_number: d.phone_number ?? null, - phone_status: d.phone_status ?? null, - phones: phones.map((p: Record) => ({ - number: (p.number as string) ?? null, - pretty_number: (p.pretty_number as string) ?? null, - type: (p.type as string) ?? null, - })), - company_size: d.company_size ?? null, - company_size_range: d.company_size_range ?? null, - company_type: d.company_type ?? null, - company_domain: d.company_domain ?? null, - company_locality: d.company_locality ?? null, - company_region: d.company_region ?? null, - company_country: d.company_country ?? null, - company_street: d.company_street ?? null, - company_postal_code: d.company_postal_code ?? null, - company_founded: d.company_founded ?? null, - company_funding: d.company_funding ?? null, - company_revenue: d.company_revenue ?? null, - company_industry: d.company_industry ?? null, - company_subindustry: d.company_subindustry ?? null, - company_linkedin: d.company_linkedin ?? null, - company_location: d.company_location ?? null, - company_description: d.company_description ?? null, - credits: d.credits ?? null, - }, - } - }, - - outputs: { - id: { type: 'number', description: 'Reveal ID', optional: true }, - status: { - type: 'string', - description: 'queued | resolving | finished | failed', - optional: true, - }, - is_complete: { - type: 'boolean', - description: 'Whether the reveal has completed', - optional: true, - }, - name: { type: 'string', description: 'Full name', optional: true }, - company: { type: 'string', description: 'Company name', optional: true }, - enrichment_level: { type: 'string', description: 'Enrichment level used', optional: true }, - linkedin_profile_url: { type: 'string', description: 'LinkedIn URL', optional: true }, - title: { type: 'string', description: 'Job title', optional: true }, - location: { type: 'string', description: 'Location', optional: true }, - email: { type: 'string', description: 'Primary email', optional: true }, - email_type: { type: 'string', description: 'Email type', optional: true }, - email_status: { type: 'string', description: 'valid | risky | unfound', optional: true }, - emails: { - type: 'array', - description: 'All emails found', - optional: true, - items: { - type: 'object', - properties: { - email: { type: 'string' }, - email_type: { type: 'string' }, - email_status: { type: 'string' }, - }, - }, - }, - mobile_phone: { type: 'string', description: 'Mobile phone', optional: true }, - phone_number: { type: 'string', description: 'Direct/office phone', optional: true }, - phone_status: { type: 'string', description: 'found | unfound', optional: true }, - phones: { - type: 'array', - description: 'All phones found', - optional: true, - items: { - type: 'object', - properties: { - number: { type: 'string' }, - pretty_number: { type: 'string' }, - type: { type: 'string' }, - }, - }, - }, - company_size: { type: 'number', description: 'Employee count', optional: true }, - company_size_range: { type: 'string', description: 'Headcount range', optional: true }, - company_type: { type: 'string', description: 'Company type', optional: true }, - company_domain: { type: 'string', description: 'Company domain', optional: true }, - company_locality: { type: 'string', description: 'City', optional: true }, - company_region: { type: 'string', description: 'State/region', optional: true }, - company_country: { type: 'string', description: 'Country', optional: true }, - company_street: { type: 'string', description: 'Street', optional: true }, - company_postal_code: { type: 'string', description: 'Postal code', optional: true }, - company_founded: { type: 'number', description: 'Year founded', optional: true }, - company_funding: { type: 'string', description: 'Funding total', optional: true }, - company_revenue: { type: 'string', description: 'Revenue', optional: true }, - company_industry: { type: 'string', description: 'Industry', optional: true }, - company_subindustry: { type: 'string', description: 'Subindustry', optional: true }, - company_linkedin: { type: 'string', description: 'Company LinkedIn URL', optional: true }, - company_location: { type: 'string', description: 'Full company location', optional: true }, - company_description: { type: 'string', description: 'Company description', optional: true }, - credits: { - type: 'json', - description: - 'Credits deducted for this reveal (api_credits: { total, email_credits, phone_credits, scrape_credits })', - optional: true, - }, - }, -} diff --git a/apps/sim/tools/wiza/hosting.ts b/apps/sim/tools/wiza/hosting.ts new file mode 100644 index 00000000000..43fe367ae34 --- /dev/null +++ b/apps/sim/tools/wiza/hosting.ts @@ -0,0 +1,42 @@ +import type { ToolHostingConfig } from '@/tools/types' + +/** + * Env var prefix for Wiza hosted keys. Provide keys as `WIZA_API_KEY_COUNT` + * plus `WIZA_API_KEY_1..N`. + */ +export const WIZA_API_KEY_PREFIX = 'WIZA_API_KEY' + +/** + * Dollar cost of a single Wiza API credit. + * + * Wiza meters API usage in credits at a documented $0.025/credit (2,000-credit + * minimum) — https://help.wiza.co/en/articles/13551713-how-to-purchase-api-credits. + * Credits are deducted only when data is successfully returned: 2 credits per + * valid email, 5 credits per phone, 2 credits per company enrichment. + */ +export const WIZA_CREDIT_USD = 0.025 + +/** + * Build a Wiza `hosting` config. `getCredits` returns the number of Wiza API + * credits the call consumed, derived from the tool's output. + */ +export function wizaHosting

    ( + getCredits: (params: P, output: Record) => number +): ToolHostingConfig

    { + return { + envKeyPrefix: WIZA_API_KEY_PREFIX, + apiKeyParam: 'apiKey', + byokProviderId: 'wiza', + pricing: { + type: 'custom', + getCost: (params, output) => { + const credits = getCredits(params, output) + return { cost: credits * WIZA_CREDIT_USD, metadata: { credits } } + }, + }, + rateLimit: { + mode: 'per_request', + requestsPerMinute: 60, + }, + } +} diff --git a/apps/sim/tools/wiza/index.ts b/apps/sim/tools/wiza/index.ts index 21df6dec38d..289503b4da1 100644 --- a/apps/sim/tools/wiza/index.ts +++ b/apps/sim/tools/wiza/index.ts @@ -1,6 +1,5 @@ export { wizaCompanyEnrichmentTool } from './company_enrichment' export { wizaGetCreditsTool } from './get_credits' -export { wizaGetIndividualRevealTool } from './get_individual_reveal' +export { wizaIndividualRevealTool } from './individual_reveal' export { wizaProspectSearchTool } from './prospect_search' -export { wizaStartIndividualRevealTool } from './start_individual_reveal' export type * from './types' diff --git a/apps/sim/tools/wiza/individual_reveal.ts b/apps/sim/tools/wiza/individual_reveal.ts new file mode 100644 index 00000000000..291d1791f67 --- /dev/null +++ b/apps/sim/tools/wiza/individual_reveal.ts @@ -0,0 +1,330 @@ +import { sleep } from '@sim/utils/helpers' +import type { ToolConfig } from '@/tools/types' +import { wizaHosting } from '@/tools/wiza/hosting' +import type { + WizaIndividualRevealData, + WizaIndividualRevealParams, + WizaIndividualRevealResponse, +} from '@/tools/wiza/types' + +const POLL_INTERVAL_MS = 2000 +const MAX_POLL_TIME_MS = 120000 +/** Tolerate brief Wiza outages while polling before giving up on an already-started reveal. */ +const MAX_CONSECUTIVE_POLL_ERRORS = 3 + +/** Whether a reveal payload has reached a terminal state and no longer needs polling. */ +function isTerminalReveal(d: { status?: string | null; is_complete?: boolean | null }): boolean { + return d.status === 'finished' || d.status === 'failed' || d.is_complete === true +} + +/** Map a Wiza individual-reveal payload (`data` object) to the tool output shape. */ +function mapRevealData(d: Record): WizaIndividualRevealData { + const emails = Array.isArray(d.emails) ? (d.emails as Record[]) : [] + const phones = Array.isArray(d.phones) ? (d.phones as Record[]) : [] + return { + id: (d.id as number) ?? null, + status: (d.status as string) ?? null, + is_complete: (d.is_complete as boolean) ?? null, + name: (d.name as string) ?? null, + company: (d.company as string) ?? null, + enrichment_level: (d.enrichment_level as string) ?? null, + linkedin_profile_url: (d.linkedin_profile_url as string) ?? null, + title: (d.title as string) ?? null, + location: (d.location as string) ?? null, + email: (d.email as string) ?? null, + email_type: (d.email_type as string) ?? null, + email_status: (d.email_status as string) ?? null, + emails: emails.map((e) => ({ + email: (e.email as string) ?? null, + email_type: (e.email_type as string) ?? null, + email_status: (e.email_status as string) ?? null, + })), + mobile_phone: (d.mobile_phone as string) ?? null, + phone_number: (d.phone_number as string) ?? null, + phone_status: (d.phone_status as string) ?? null, + phones: phones.map((p) => ({ + number: (p.number as string) ?? null, + pretty_number: (p.pretty_number as string) ?? null, + type: (p.type as string) ?? null, + })), + company_size: (d.company_size as number) ?? null, + company_size_range: (d.company_size_range as string) ?? null, + company_type: (d.company_type as string) ?? null, + company_domain: (d.company_domain as string) ?? null, + company_locality: (d.company_locality as string) ?? null, + company_region: (d.company_region as string) ?? null, + company_country: (d.company_country as string) ?? null, + company_street: (d.company_street as string) ?? null, + company_postal_code: (d.company_postal_code as string) ?? null, + company_founded: (d.company_founded as number) ?? null, + company_funding: (d.company_funding as string) ?? null, + company_revenue: (d.company_revenue as string) ?? null, + company_industry: (d.company_industry as string) ?? null, + company_subindustry: (d.company_subindustry as string) ?? null, + company_linkedin: (d.company_linkedin as string) ?? null, + company_location: (d.company_location as string) ?? null, + company_description: (d.company_description as string) ?? null, + credits: (d.credits as Record) ?? null, + } +} + +export const wizaIndividualRevealTool: ToolConfig< + WizaIndividualRevealParams, + WizaIndividualRevealResponse +> = { + id: 'wiza_individual_reveal', + name: 'Wiza Individual Reveal', + description: + 'Reveal a contact via LinkedIn URL, name + company/domain, or email. Starts the reveal and polls until it resolves. Uses 2 credits per valid email and 5 credits per phone, charged only on success.', + version: '1.0.0', + + hosting: wizaHosting((_params, output) => { + let credits = 0 + const emails = Array.isArray(output.emails) + ? (output.emails as { email_status?: string }[]) + : [] + const emailValid = + output.email_status === 'valid' || emails.some((e) => e.email_status === 'valid') + // 2 credits when at least one valid email is returned. + if (emailValid) credits += 2 + const phones = Array.isArray(output.phones) ? output.phones : [] + const phoneFound = Boolean(output.mobile_phone || output.phone_number || phones.length > 0) + // 5 credits when at least one phone is returned. + if (phoneFound) credits += 5 + return credits + }), + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Wiza API key', + }, + enrichment_level: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Enrichment depth: none, partial, phone, or full', + }, + profile_url: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'LinkedIn profile URL (e.g., https://linkedin.com/in/johndoe)', + }, + full_name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Full name (used with company or domain)', + }, + company: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Company name (used with full_name)', + }, + domain: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Company domain (used with full_name)', + }, + email: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email address (use alone or with other identifiers)', + }, + accept_work: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to accept work emails (email_options)', + }, + accept_personal: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to accept personal emails (email_options)', + }, + }, + + request: { + url: 'https://wiza.co/api/individual_reveals', + method: 'POST', + headers: (params: WizaIndividualRevealParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: WizaIndividualRevealParams) => { + const individual: Record = {} + if (params.profile_url) individual.profile_url = params.profile_url + if (params.full_name) individual.full_name = params.full_name + if (params.company) individual.company = params.company + if (params.domain) individual.domain = params.domain + if (params.email) individual.email = params.email + + const body: Record = { + individual_reveal: individual, + enrichment_level: params.enrichment_level, + } + + if (params.accept_work !== undefined || params.accept_personal !== undefined) { + const emailOptions: Record = {} + if (params.accept_work !== undefined) emailOptions.accept_work = params.accept_work + if (params.accept_personal !== undefined) { + emailOptions.accept_personal = params.accept_personal + } + body.email_options = emailOptions + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Wiza API error: ${response.status} - ${errorText}`) + } + const json = await response.json() + return { + success: true, + output: mapRevealData(json.data ?? {}), + } + }, + + postProcess: async (result, params) => { + if (!result.success) return result + + // Wiza can resolve synchronously (e.g. a cache hit) — the initial POST payload is + // already mapped, so skip polling when it is terminal. + if (isTerminalReveal(result.output)) { + return { success: result.output.status !== 'failed', output: result.output } + } + + const revealId = result.output.id + if (revealId == null) { + // Return an explicit failure rather than throwing: a thrown error here is swallowed + // by the executor and masked as the queued (incomplete) success result. + return { + success: false, + error: 'Wiza individual reveal did not return an id', + output: result.output, + } + } + + let elapsedTime = 0 + let consecutiveErrors = 0 + while (elapsedTime < MAX_POLL_TIME_MS) { + await sleep(POLL_INTERVAL_MS) + elapsedTime += POLL_INTERVAL_MS + + const statusResponse = await fetch( + `https://wiza.co/api/individual_reveals/${encodeURIComponent(String(revealId))}`, + { + headers: { + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }, + } + ) + + if (!statusResponse.ok) { + // The reveal is already started (and billed by Wiza), so tolerate brief outages and + // retry rather than aborting the whole window on a single transient 5xx/429. + consecutiveErrors += 1 + if (consecutiveErrors >= MAX_CONSECUTIVE_POLL_ERRORS) { + const errorText = await statusResponse.text().catch(() => '') + return { + success: false, + error: `Wiza API error: ${statusResponse.status} - ${errorText}`, + output: result.output, + } + } + continue + } + consecutiveErrors = 0 + + const json = await statusResponse.json() + const data = json.data ?? {} + + if (isTerminalReveal(data)) { + return { + success: data.status !== 'failed', + output: mapRevealData(data), + } + } + } + + return { + success: false, + error: 'Wiza individual reveal did not complete within the polling window', + output: result.output, + } + }, + + outputs: { + id: { type: 'number', description: 'Reveal ID' }, + status: { type: 'string', description: 'queued | resolving | finished | failed' }, + is_complete: { type: 'boolean', description: 'Whether the reveal has completed' }, + name: { type: 'string', description: 'Full name', optional: true }, + company: { type: 'string', description: 'Company name', optional: true }, + enrichment_level: { type: 'string', description: 'Enrichment level used', optional: true }, + linkedin_profile_url: { type: 'string', description: 'LinkedIn URL', optional: true }, + title: { type: 'string', description: 'Job title', optional: true }, + location: { type: 'string', description: 'Location', optional: true }, + email: { type: 'string', description: 'Primary email', optional: true }, + email_type: { type: 'string', description: 'Email type', optional: true }, + email_status: { type: 'string', description: 'valid | risky | unfound', optional: true }, + emails: { + type: 'array', + description: 'All emails found', + optional: true, + items: { + type: 'object', + properties: { + email: { type: 'string' }, + email_type: { type: 'string' }, + email_status: { type: 'string' }, + }, + }, + }, + mobile_phone: { type: 'string', description: 'Mobile phone', optional: true }, + phone_number: { type: 'string', description: 'Direct/office phone', optional: true }, + phone_status: { type: 'string', description: 'found | unfound', optional: true }, + phones: { + type: 'array', + description: 'All phones found', + optional: true, + items: { + type: 'object', + properties: { + number: { type: 'string' }, + pretty_number: { type: 'string' }, + type: { type: 'string' }, + }, + }, + }, + company_size: { type: 'number', description: 'Employee count', optional: true }, + company_size_range: { type: 'string', description: 'Headcount range', optional: true }, + company_type: { type: 'string', description: 'Company type', optional: true }, + company_domain: { type: 'string', description: 'Company domain', optional: true }, + company_locality: { type: 'string', description: 'City', optional: true }, + company_region: { type: 'string', description: 'State/region', optional: true }, + company_country: { type: 'string', description: 'Country', optional: true }, + company_street: { type: 'string', description: 'Street', optional: true }, + company_postal_code: { type: 'string', description: 'Postal code', optional: true }, + company_founded: { type: 'number', description: 'Year founded', optional: true }, + company_funding: { type: 'string', description: 'Funding total', optional: true }, + company_revenue: { type: 'string', description: 'Revenue', optional: true }, + company_industry: { type: 'string', description: 'Industry', optional: true }, + company_subindustry: { type: 'string', description: 'Subindustry', optional: true }, + company_linkedin: { type: 'string', description: 'Company LinkedIn URL', optional: true }, + company_location: { type: 'string', description: 'Full company location', optional: true }, + company_description: { type: 'string', description: 'Company description', optional: true }, + credits: { type: 'json', description: 'Credits consumed by the reveal', optional: true }, + }, +} diff --git a/apps/sim/tools/wiza/prospect_search.ts b/apps/sim/tools/wiza/prospect_search.ts index c507f0cbb61..fb460b39b63 100644 --- a/apps/sim/tools/wiza/prospect_search.ts +++ b/apps/sim/tools/wiza/prospect_search.ts @@ -1,4 +1,5 @@ import type { ToolConfig } from '@/tools/types' +import { wizaHosting } from '@/tools/wiza/hosting' import type { WizaProspectSearchParams, WizaProspectSearchResponse } from '@/tools/wiza/types' export const wizaProspectSearchTool: ToolConfig< @@ -10,6 +11,12 @@ export const wizaProspectSearchTool: ToolConfig< description: "Search Wiza's database of prospects using person, company, and financial filters", version: '1.0.0', + hosting: wizaHosting(() => { + // Prospect search returns profiles without contact data and consumes no credits; + // Wiza charges only on reveal/enrichment. + return 0 + }), + params: { apiKey: { type: 'string', diff --git a/apps/sim/tools/wiza/start_individual_reveal.ts b/apps/sim/tools/wiza/start_individual_reveal.ts deleted file mode 100644 index 902c51f8723..00000000000 --- a/apps/sim/tools/wiza/start_individual_reveal.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { ToolConfig } from '@/tools/types' -import type { - WizaStartIndividualRevealParams, - WizaStartIndividualRevealResponse, -} from '@/tools/wiza/types' - -export const wizaStartIndividualRevealTool: ToolConfig< - WizaStartIndividualRevealParams, - WizaStartIndividualRevealResponse -> = { - id: 'wiza_start_individual_reveal', - name: 'Wiza Start Individual Reveal', - description: - 'Start an individual reveal to enrich a contact via LinkedIn URL, name+company, or email', - version: '1.0.0', - - params: { - apiKey: { - type: 'string', - required: true, - visibility: 'user-only', - description: 'Wiza API key', - }, - enrichment_level: { - type: 'string', - required: true, - visibility: 'user-or-llm', - description: 'Enrichment depth: none, partial, phone, or full', - }, - profile_url: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'LinkedIn profile URL (e.g., https://linkedin.com/in/johndoe)', - }, - full_name: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'Full name (used with company or domain)', - }, - company: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'Company name (used with full_name)', - }, - domain: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'Company domain (used with full_name)', - }, - email: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'Email address (use alone or with other identifiers)', - }, - accept_work: { - type: 'boolean', - required: false, - visibility: 'user-or-llm', - description: 'Whether to accept work emails (email_options)', - }, - accept_personal: { - type: 'boolean', - required: false, - visibility: 'user-or-llm', - description: 'Whether to accept personal emails (email_options)', - }, - callback_url: { - type: 'string', - required: false, - visibility: 'user-or-llm', - description: 'Optional URL to receive a callback with the reveal update', - }, - }, - - request: { - url: 'https://wiza.co/api/individual_reveals', - method: 'POST', - headers: (params: WizaStartIndividualRevealParams) => ({ - Authorization: `Bearer ${params.apiKey}`, - 'Content-Type': 'application/json', - }), - body: (params: WizaStartIndividualRevealParams) => { - const individual: Record = {} - if (params.profile_url) individual.profile_url = params.profile_url - if (params.full_name) individual.full_name = params.full_name - if (params.company) individual.company = params.company - if (params.domain) individual.domain = params.domain - if (params.email) individual.email = params.email - - const body: Record = { - individual_reveal: individual, - enrichment_level: params.enrichment_level, - } - - if (params.accept_work !== undefined || params.accept_personal !== undefined) { - const emailOptions: Record = {} - if (params.accept_work !== undefined) emailOptions.accept_work = params.accept_work - if (params.accept_personal !== undefined) { - emailOptions.accept_personal = params.accept_personal - } - body.email_options = emailOptions - } - - if (params.callback_url) body.callback_url = params.callback_url - - return body - }, - }, - - transformResponse: async (response: Response) => { - if (!response.ok) { - const errorText = await response.text() - throw new Error(`Wiza API error: ${response.status} - ${errorText}`) - } - - const json = await response.json() - const d = json.data ?? {} - - return { - success: true, - output: { - id: d.id ?? null, - status: d.status ?? null, - is_complete: d.is_complete ?? null, - }, - } - }, - - outputs: { - id: { - type: 'number', - description: 'Individual reveal ID (use with Get Individual Reveal)', - optional: true, - }, - status: { - type: 'string', - description: 'Reveal status: queued, resolving, finished, or failed', - optional: true, - }, - is_complete: { - type: 'boolean', - description: 'Whether the reveal has completed', - optional: true, - }, - }, -} diff --git a/apps/sim/tools/wiza/types.ts b/apps/sim/tools/wiza/types.ts index 00db0a23563..603a3fa933f 100644 --- a/apps/sim/tools/wiza/types.ts +++ b/apps/sim/tools/wiza/types.ts @@ -105,7 +105,7 @@ export interface WizaCompanyEnrichmentResponse extends ToolResponse { } } -export interface WizaStartIndividualRevealParams { +export interface WizaIndividualRevealParams { apiKey: string enrichment_level: 'none' | 'partial' | 'phone' | 'full' profile_url?: string @@ -115,10 +115,9 @@ export interface WizaStartIndividualRevealParams { email?: string accept_work?: boolean accept_personal?: boolean - callback_url?: string } -interface WizaIndividualRevealData { +export interface WizaIndividualRevealData { id: number | null status: string | null is_complete: boolean | null @@ -164,20 +163,7 @@ interface WizaIndividualRevealData { credits: Record | null } -export interface WizaStartIndividualRevealResponse extends ToolResponse { - output: { - id: number | null - status: string | null - is_complete: boolean | null - } -} - -export interface WizaGetIndividualRevealParams { - apiKey: string - id: string -} - -export interface WizaGetIndividualRevealResponse extends ToolResponse { +export interface WizaIndividualRevealResponse extends ToolResponse { output: WizaIndividualRevealData } @@ -185,5 +171,4 @@ export type WizaResponse = | WizaGetCreditsResponse | WizaProspectSearchResponse | WizaCompanyEnrichmentResponse - | WizaStartIndividualRevealResponse - | WizaGetIndividualRevealResponse + | WizaIndividualRevealResponse diff --git a/packages/db/migrations/0219_amused_leo.sql b/packages/db/migrations/0219_amused_leo.sql index c32c029caca..36dcb651ebb 100644 --- a/packages/db/migrations/0219_amused_leo.sql +++ b/packages/db/migrations/0219_amused_leo.sql @@ -1,4 +1,4 @@ -ALTER TABLE "copilot_messages" ADD COLUMN "seq" integer;--> statement-breakpoint +ALTER TABLE "copilot_messages" ADD COLUMN IF NOT EXISTS "seq" integer;--> statement-breakpoint WITH ordered AS ( SELECT c."id" AS chat_id, elem.value->>'id' AS message_id, elem.ord AS ord FROM "copilot_chats" c @@ -16,4 +16,4 @@ ranked AS ( UPDATE "copilot_messages" m SET "seq" = r.seq FROM ranked r WHERE m."chat_id" = r.chat_id AND m."message_id" = r.message_id;--> statement-breakpoint -CREATE INDEX "copilot_messages_chat_seq_idx" ON "copilot_messages" USING btree ("chat_id","seq") WHERE "copilot_messages"."deleted_at" IS NULL; \ No newline at end of file +CREATE INDEX IF NOT EXISTS "copilot_messages_chat_seq_idx" ON "copilot_messages" USING btree ("chat_id","seq") WHERE "copilot_messages"."deleted_at" IS NULL; \ No newline at end of file diff --git a/packages/db/migrations/0220_early_hellion.sql b/packages/db/migrations/0220_early_hellion.sql new file mode 100644 index 00000000000..558f964c2d9 --- /dev/null +++ b/packages/db/migrations/0220_early_hellion.sql @@ -0,0 +1,36 @@ +ALTER TABLE "workflow_execution_logs" ADD COLUMN IF NOT EXISTS "cost_total" numeric;--> statement-breakpoint +ALTER TABLE "workflow_execution_logs" ADD COLUMN IF NOT EXISTS "models_used" text[];--> statement-breakpoint +COMMIT;--> statement-breakpoint +ALTER TYPE "public"."usage_log_category" ADD VALUE IF NOT EXISTS 'tool';--> statement-breakpoint +CREATE OR REPLACE PROCEDURE backfill_wel_cost_total_0220() LANGUAGE plpgsql AS $$ +DECLARE + updated integer; +BEGIN + LOOP + WITH candidates AS ( + SELECT id FROM workflow_execution_logs + WHERE cost_total IS NULL + AND cost ? 'total' + AND (cost->>'total') ~ '^-?[0-9]+(\.[0-9]+)?$' + LIMIT 5000 + ) + UPDATE workflow_execution_logs wel + SET cost_total = NULLIF(wel.cost->>'total', '')::numeric, + models_used = CASE + WHEN jsonb_typeof(wel.cost->'models') = 'object' + THEN ARRAY(SELECT jsonb_object_keys(wel.cost->'models')) + ELSE wel.models_used + END + FROM candidates + WHERE wel.id = candidates.id; + GET DIAGNOSTICS updated = ROW_COUNT; + EXIT WHEN updated = 0; + COMMIT; + END LOOP; +END; +$$;--> statement-breakpoint +CALL backfill_wel_cost_total_0220();--> statement-breakpoint +DROP PROCEDURE backfill_wel_cost_total_0220();--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "usage_log_execution_id_idx" ON "usage_log" USING btree ("execution_id");--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "workflow_execution_logs_workspace_cost_total_idx" ON "workflow_execution_logs" USING btree ("workspace_id","cost_total");--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "workflow_execution_logs_models_used_idx" ON "workflow_execution_logs" USING gin ("models_used"); diff --git a/packages/db/migrations/0221_secret_hannibal_king.sql b/packages/db/migrations/0221_secret_hannibal_king.sql new file mode 100644 index 00000000000..ecda911cf0b --- /dev/null +++ b/packages/db/migrations/0221_secret_hannibal_king.sql @@ -0,0 +1 @@ +DROP INDEX "idx_webhook_on_workflow_id_block_id"; \ No newline at end of file diff --git a/packages/db/migrations/meta/0220_snapshot.json b/packages/db/migrations/meta/0220_snapshot.json new file mode 100644 index 00000000000..64c6073d85c --- /dev/null +++ b/packages/db/migrations/meta/0220_snapshot.json @@ -0,0 +1,17582 @@ +{ + "id": "10027d61-99fa-4082-99a8-27be35fc3d8a", + "prevId": "9c04ddff-9332-4201-be6f-98fbf006874a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.a2a_agent": { + "name": "a2a_agent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "authentication": { + "name": "authentication", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "signatures": { + "name": "signatures", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "a2a_agent_workflow_id_idx": { + "name": "a2a_agent_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_created_by_idx": { + "name": "a2a_agent_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_workflow_unique": { + "name": "a2a_agent_workspace_workflow_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"a2a_agent\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_archived_at_idx": { + "name": "a2a_agent_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_archived_partial_idx": { + "name": "a2a_agent_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"a2a_agent\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_agent_workspace_id_workspace_id_fk": { + "name": "a2a_agent_workspace_id_workspace_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_workflow_id_workflow_id_fk": { + "name": "a2a_agent_workflow_id_workflow_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_created_by_user_id_fk": { + "name": "a2a_agent_created_by_user_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_push_notification_config": { + "name": "a2a_push_notification_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_schemes": { + "name": "auth_schemes", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "auth_credentials": { + "name": "auth_credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "a2a_push_notification_config_task_unique": { + "name": "a2a_push_notification_config_task_unique", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_push_notification_config_task_id_a2a_task_id_fk": { + "name": "a2a_push_notification_config_task_id_a2a_task_id_fk", + "tableFrom": "a2a_push_notification_config", + "tableTo": "a2a_task", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_task": { + "name": "a2a_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "a2a_task_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'submitted'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "artifacts": { + "name": "artifacts", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "a2a_task_agent_id_idx": { + "name": "a2a_task_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_session_id_idx": { + "name": "a2a_task_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_status_idx": { + "name": "a2a_task_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_execution_id_idx": { + "name": "a2a_task_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_created_at_idx": { + "name": "a2a_task_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_task_agent_id_a2a_agent_id_fk": { + "name": "a2a_task_agent_id_a2a_agent_id_fk", + "tableFrom": "a2a_task", + "tableTo": "a2a_agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set": { + "name": "credential_set", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_created_by_idx": { + "name": "credential_set_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_org_name_unique": { + "name": "credential_set_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_provider_id_idx": { + "name": "credential_set_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_organization_id_organization_id_fk": { + "name": "credential_set_organization_id_organization_id_fk", + "tableFrom": "credential_set", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_created_by_user_id_fk": { + "name": "credential_set_created_by_user_id_fk", + "tableFrom": "credential_set", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_invitation": { + "name": "credential_set_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "accepted_by_user_id": { + "name": "accepted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_invitation_set_id_idx": { + "name": "credential_set_invitation_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_token_idx": { + "name": "credential_set_invitation_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_status_idx": { + "name": "credential_set_invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_expires_at_idx": { + "name": "credential_set_invitation_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_invitation_credential_set_id_credential_set_id_fk": { + "name": "credential_set_invitation_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_invited_by_user_id_fk": { + "name": "credential_set_invitation_invited_by_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_accepted_by_user_id_user_id_fk": { + "name": "credential_set_invitation_accepted_by_user_id_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["accepted_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_set_invitation_token_unique": { + "name": "credential_set_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_member": { + "name": "credential_set_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_member_user_id_idx": { + "name": "credential_set_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_unique": { + "name": "credential_set_member_unique", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_status_idx": { + "name": "credential_set_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_member_credential_set_id_credential_set_id_fk": { + "name": "credential_set_member_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_user_id_user_id_fk": { + "name": "credential_set_member_user_id_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_invited_by_user_id_fk": { + "name": "credential_set_member_invited_by_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form": { + "name": "form", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "show_branding": { + "name": "show_branding", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "form_identifier_idx": { + "name": "form_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"form\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_workflow_id_idx": { + "name": "form_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_user_id_idx": { + "name": "form_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_archived_at_partial_idx": { + "name": "form_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"form\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_workflow_id_workflow_id_fk": { + "name": "form_workflow_id_workflow_id_fk", + "tableFrom": "form", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "form_user_id_user_id_fk": { + "name": "form_user_id_user_id_fk", + "tableFrom": "form", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_access_token_access_token_idx": { + "name": "oauth_access_token_access_token_idx", + "columns": [ + { + "expression": "access_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_token_idx": { + "name": "oauth_access_token_refresh_token_idx", + "columns": [ + { + "expression": "refresh_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_application_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_access_token_unique": { + "name": "oauth_access_token_access_token_unique", + "nullsNotDistinct": false, + "columns": ["access_token"] + }, + "oauth_access_token_refresh_token_unique": { + "name": "oauth_access_token_refresh_token_unique", + "nullsNotDistinct": false, + "columns": ["refresh_token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_application": { + "name": "oauth_application", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_urls": { + "name": "redirect_urls", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_application_client_id_idx": { + "name": "oauth_application_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_application_user_id_user_id_fk": { + "name": "oauth_application_user_id_user_id_fk", + "tableFrom": "oauth_application", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_application_client_id_unique": { + "name": "oauth_application_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_user_client_idx": { + "name": "oauth_consent_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_application_client_id_fk": { + "name": "oauth_consent_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "auto_add_new_members": { + "name": "auto_add_new_members", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_name_unique": { + "name": "permission_group_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_auto_add_unique": { + "name": "permission_group_workspace_auto_add_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "auto_add_new_members = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_id_workspace_id_fk", + "tableFrom": "permission_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_workspace_user_unique": { + "name": "permission_group_member_workspace_user_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_workspace_id_workspace_id_fk": { + "name": "permission_group_member_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_creators": { + "name": "template_creators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "template_creator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_image_url": { + "name": "profile_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_creators_reference_idx": { + "name": "template_creators_reference_idx", + "columns": [ + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_reference_id_idx": { + "name": "template_creators_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_created_by_idx": { + "name": "template_creators_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_creators_created_by_user_id_fk": { + "name": "template_creators_created_by_user_id_fk", + "tableFrom": "template_creators", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_stars": { + "name": "template_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_stars_user_id_idx": { + "name": "template_stars_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_id_idx": { + "name": "template_stars_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_idx": { + "name": "template_stars_user_template_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_user_idx": { + "name": "template_stars_template_user_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_starred_at_idx": { + "name": "template_stars_starred_at_idx", + "columns": [ + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_starred_at_idx": { + "name": "template_stars_template_starred_at_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_unique": { + "name": "template_stars_user_template_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_stars_user_id_user_id_fk": { + "name": "template_stars_user_id_user_id_fk", + "tableFrom": "template_stars", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "template_stars_template_id_templates_id_fk": { + "name": "template_stars_template_id_templates_id_fk", + "tableFrom": "template_stars", + "tableTo": "templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "template_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "required_credentials": { + "name": "required_credentials", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "og_image_url": { + "name": "og_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "templates_status_idx": { + "name": "templates_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_creator_id_idx": { + "name": "templates_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_views_idx": { + "name": "templates_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_stars_idx": { + "name": "templates_stars_idx", + "columns": [ + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_views_idx": { + "name": "templates_status_views_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_stars_idx": { + "name": "templates_status_stars_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_created_at_idx": { + "name": "templates_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_updated_at_idx": { + "name": "templates_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workflow_id_workflow_id_fk": { + "name": "templates_workflow_id_workflow_id_fk", + "tableFrom": "templates", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "templates_creator_id_template_creators_id_fk": { + "name": "templates_creator_id_template_creators_id_fk", + "tableFrom": "templates", + "tableTo": "template_creators", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_a2a_executions": { + "name": "total_a2a_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_table_id_idx": { + "name": "user_table_rows_table_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_data_gin_idx": { + "name": "user_table_rows_data_gin_idx", + "columns": [ + { + "expression": "data", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id": { + "name": "idx_webhook_on_workflow_id_block_id", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_credential_set_id_idx": { + "name": "webhook_credential_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_credential_set_id_credential_set_id_fk": { + "name": "webhook_credential_set_id_credential_set_id_fk", + "tableFrom": "webhook", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_provider_unique": { + "name": "workspace_byok_provider_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_byok_workspace_idx": { + "name": "workspace_byok_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_delivery": { + "name": "workspace_notification_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "notification_delivery_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_notification_delivery_subscription_id_idx": { + "name": "workspace_notification_delivery_subscription_id_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_execution_id_idx": { + "name": "workspace_notification_delivery_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_status_idx": { + "name": "workspace_notification_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_next_attempt_idx": { + "name": "workspace_notification_delivery_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk": { + "name": "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workspace_notification_subscription", + "columnsFrom": ["subscription_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_delivery_workflow_id_workflow_id_fk": { + "name": "workspace_notification_delivery_workflow_id_workflow_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_subscription": { + "name": "workspace_notification_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_ids": { + "name": "workflow_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "all_workflows": { + "name": "all_workflows", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "level_filter": { + "name": "level_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['info', 'error']::text[]" + }, + "trigger_filter": { + "name": "trigger_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['api', 'webhook', 'schedule', 'manual', 'chat']::text[]" + }, + "include_final_output": { + "name": "include_final_output", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_trace_spans": { + "name": "include_trace_spans", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_rate_limits": { + "name": "include_rate_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_usage_data": { + "name": "include_usage_data", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "webhook_config": { + "name": "webhook_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email_recipients": { + "name": "email_recipients", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "slack_config": { + "name": "slack_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "alert_config": { + "name": "alert_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_alert_at": { + "name": "last_alert_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_notification_workspace_id_idx": { + "name": "workspace_notification_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_active_idx": { + "name": "workspace_notification_active_idx", + "columns": [ + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_type_idx": { + "name": "workspace_notification_type_idx", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_subscription_workspace_id_workspace_id_fk": { + "name": "workspace_notification_subscription_workspace_id_workspace_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_subscription_created_by_user_id_fk": { + "name": "workspace_notification_subscription_created_by_user_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.a2a_task_status": { + "name": "a2a_task_status", + "schema": "public", + "values": [ + "submitted", + "working", + "input-required", + "completed", + "failed", + "canceled", + "rejected", + "auth-required", + "unknown" + ] + }, + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_set_invitation_status": { + "name": "credential_set_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "expired", "cancelled"] + }, + "public.credential_set_member_status": { + "name": "credential_set_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": ["oauth", "env_workspace", "env_personal", "service_account"] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.notification_delivery_status": { + "name": "notification_delivery_status", + "schema": "public", + "values": ["pending", "in_progress", "success", "failed"] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": ["webhook", "email", "slack"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.template_creator_type": { + "name": "template_creator_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.template_status": { + "name": "template_status", + "schema": "public", + "values": ["pending", "approved", "rejected"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/0221_snapshot.json b/packages/db/migrations/meta/0221_snapshot.json new file mode 100644 index 00000000000..56036c9cb01 --- /dev/null +++ b/packages/db/migrations/meta/0221_snapshot.json @@ -0,0 +1,17561 @@ +{ + "id": "3660eb7f-6a5c-4409-abc0-cc9a404cfdf6", + "prevId": "10027d61-99fa-4082-99a8-27be35fc3d8a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.a2a_agent": { + "name": "a2a_agent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "authentication": { + "name": "authentication", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "signatures": { + "name": "signatures", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "a2a_agent_workflow_id_idx": { + "name": "a2a_agent_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_created_by_idx": { + "name": "a2a_agent_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_workflow_unique": { + "name": "a2a_agent_workspace_workflow_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"a2a_agent\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_archived_at_idx": { + "name": "a2a_agent_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_archived_partial_idx": { + "name": "a2a_agent_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"a2a_agent\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_agent_workspace_id_workspace_id_fk": { + "name": "a2a_agent_workspace_id_workspace_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_workflow_id_workflow_id_fk": { + "name": "a2a_agent_workflow_id_workflow_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_created_by_user_id_fk": { + "name": "a2a_agent_created_by_user_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_push_notification_config": { + "name": "a2a_push_notification_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_schemes": { + "name": "auth_schemes", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "auth_credentials": { + "name": "auth_credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "a2a_push_notification_config_task_unique": { + "name": "a2a_push_notification_config_task_unique", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_push_notification_config_task_id_a2a_task_id_fk": { + "name": "a2a_push_notification_config_task_id_a2a_task_id_fk", + "tableFrom": "a2a_push_notification_config", + "tableTo": "a2a_task", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_task": { + "name": "a2a_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "a2a_task_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'submitted'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "artifacts": { + "name": "artifacts", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "a2a_task_agent_id_idx": { + "name": "a2a_task_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_session_id_idx": { + "name": "a2a_task_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_status_idx": { + "name": "a2a_task_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_execution_id_idx": { + "name": "a2a_task_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_created_at_idx": { + "name": "a2a_task_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_task_agent_id_a2a_agent_id_fk": { + "name": "a2a_task_agent_id_a2a_agent_id_fk", + "tableFrom": "a2a_task", + "tableTo": "a2a_agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set": { + "name": "credential_set", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_created_by_idx": { + "name": "credential_set_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_org_name_unique": { + "name": "credential_set_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_provider_id_idx": { + "name": "credential_set_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_organization_id_organization_id_fk": { + "name": "credential_set_organization_id_organization_id_fk", + "tableFrom": "credential_set", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_created_by_user_id_fk": { + "name": "credential_set_created_by_user_id_fk", + "tableFrom": "credential_set", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_invitation": { + "name": "credential_set_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "accepted_by_user_id": { + "name": "accepted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_invitation_set_id_idx": { + "name": "credential_set_invitation_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_token_idx": { + "name": "credential_set_invitation_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_status_idx": { + "name": "credential_set_invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_expires_at_idx": { + "name": "credential_set_invitation_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_invitation_credential_set_id_credential_set_id_fk": { + "name": "credential_set_invitation_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_invited_by_user_id_fk": { + "name": "credential_set_invitation_invited_by_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_accepted_by_user_id_user_id_fk": { + "name": "credential_set_invitation_accepted_by_user_id_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["accepted_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_set_invitation_token_unique": { + "name": "credential_set_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_member": { + "name": "credential_set_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_member_user_id_idx": { + "name": "credential_set_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_unique": { + "name": "credential_set_member_unique", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_status_idx": { + "name": "credential_set_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_member_credential_set_id_credential_set_id_fk": { + "name": "credential_set_member_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_user_id_user_id_fk": { + "name": "credential_set_member_user_id_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_invited_by_user_id_fk": { + "name": "credential_set_member_invited_by_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form": { + "name": "form", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "show_branding": { + "name": "show_branding", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "form_identifier_idx": { + "name": "form_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"form\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_workflow_id_idx": { + "name": "form_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_user_id_idx": { + "name": "form_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_archived_at_partial_idx": { + "name": "form_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"form\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_workflow_id_workflow_id_fk": { + "name": "form_workflow_id_workflow_id_fk", + "tableFrom": "form", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "form_user_id_user_id_fk": { + "name": "form_user_id_user_id_fk", + "tableFrom": "form", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_access_token_access_token_idx": { + "name": "oauth_access_token_access_token_idx", + "columns": [ + { + "expression": "access_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_token_idx": { + "name": "oauth_access_token_refresh_token_idx", + "columns": [ + { + "expression": "refresh_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_application_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_access_token_unique": { + "name": "oauth_access_token_access_token_unique", + "nullsNotDistinct": false, + "columns": ["access_token"] + }, + "oauth_access_token_refresh_token_unique": { + "name": "oauth_access_token_refresh_token_unique", + "nullsNotDistinct": false, + "columns": ["refresh_token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_application": { + "name": "oauth_application", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_urls": { + "name": "redirect_urls", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_application_client_id_idx": { + "name": "oauth_application_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_application_user_id_user_id_fk": { + "name": "oauth_application_user_id_user_id_fk", + "tableFrom": "oauth_application", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_application_client_id_unique": { + "name": "oauth_application_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_user_client_idx": { + "name": "oauth_consent_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_application_client_id_fk": { + "name": "oauth_consent_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "auto_add_new_members": { + "name": "auto_add_new_members", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_name_unique": { + "name": "permission_group_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_auto_add_unique": { + "name": "permission_group_workspace_auto_add_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "auto_add_new_members = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_id_workspace_id_fk", + "tableFrom": "permission_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_workspace_user_unique": { + "name": "permission_group_member_workspace_user_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_workspace_id_workspace_id_fk": { + "name": "permission_group_member_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_creators": { + "name": "template_creators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "template_creator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_image_url": { + "name": "profile_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_creators_reference_idx": { + "name": "template_creators_reference_idx", + "columns": [ + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_reference_id_idx": { + "name": "template_creators_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_created_by_idx": { + "name": "template_creators_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_creators_created_by_user_id_fk": { + "name": "template_creators_created_by_user_id_fk", + "tableFrom": "template_creators", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_stars": { + "name": "template_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_stars_user_id_idx": { + "name": "template_stars_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_id_idx": { + "name": "template_stars_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_idx": { + "name": "template_stars_user_template_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_user_idx": { + "name": "template_stars_template_user_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_starred_at_idx": { + "name": "template_stars_starred_at_idx", + "columns": [ + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_starred_at_idx": { + "name": "template_stars_template_starred_at_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_unique": { + "name": "template_stars_user_template_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_stars_user_id_user_id_fk": { + "name": "template_stars_user_id_user_id_fk", + "tableFrom": "template_stars", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "template_stars_template_id_templates_id_fk": { + "name": "template_stars_template_id_templates_id_fk", + "tableFrom": "template_stars", + "tableTo": "templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "template_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "required_credentials": { + "name": "required_credentials", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "og_image_url": { + "name": "og_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "templates_status_idx": { + "name": "templates_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_creator_id_idx": { + "name": "templates_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_views_idx": { + "name": "templates_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_stars_idx": { + "name": "templates_stars_idx", + "columns": [ + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_views_idx": { + "name": "templates_status_views_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_stars_idx": { + "name": "templates_status_stars_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_created_at_idx": { + "name": "templates_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_updated_at_idx": { + "name": "templates_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workflow_id_workflow_id_fk": { + "name": "templates_workflow_id_workflow_id_fk", + "tableFrom": "templates", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "templates_creator_id_template_creators_id_fk": { + "name": "templates_creator_id_template_creators_id_fk", + "tableFrom": "templates", + "tableTo": "template_creators", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_a2a_executions": { + "name": "total_a2a_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_table_id_idx": { + "name": "user_table_rows_table_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_data_gin_idx": { + "name": "user_table_rows_data_gin_idx", + "columns": [ + { + "expression": "data", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_credential_set_id_idx": { + "name": "webhook_credential_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_credential_set_id_credential_set_id_fk": { + "name": "webhook_credential_set_id_credential_set_id_fk", + "tableFrom": "webhook", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_provider_unique": { + "name": "workspace_byok_provider_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_byok_workspace_idx": { + "name": "workspace_byok_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_delivery": { + "name": "workspace_notification_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "notification_delivery_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_notification_delivery_subscription_id_idx": { + "name": "workspace_notification_delivery_subscription_id_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_execution_id_idx": { + "name": "workspace_notification_delivery_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_status_idx": { + "name": "workspace_notification_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_next_attempt_idx": { + "name": "workspace_notification_delivery_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk": { + "name": "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workspace_notification_subscription", + "columnsFrom": ["subscription_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_delivery_workflow_id_workflow_id_fk": { + "name": "workspace_notification_delivery_workflow_id_workflow_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_subscription": { + "name": "workspace_notification_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_ids": { + "name": "workflow_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "all_workflows": { + "name": "all_workflows", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "level_filter": { + "name": "level_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['info', 'error']::text[]" + }, + "trigger_filter": { + "name": "trigger_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['api', 'webhook', 'schedule', 'manual', 'chat']::text[]" + }, + "include_final_output": { + "name": "include_final_output", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_trace_spans": { + "name": "include_trace_spans", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_rate_limits": { + "name": "include_rate_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_usage_data": { + "name": "include_usage_data", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "webhook_config": { + "name": "webhook_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email_recipients": { + "name": "email_recipients", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "slack_config": { + "name": "slack_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "alert_config": { + "name": "alert_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_alert_at": { + "name": "last_alert_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_notification_workspace_id_idx": { + "name": "workspace_notification_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_active_idx": { + "name": "workspace_notification_active_idx", + "columns": [ + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_type_idx": { + "name": "workspace_notification_type_idx", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_subscription_workspace_id_workspace_id_fk": { + "name": "workspace_notification_subscription_workspace_id_workspace_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_subscription_created_by_user_id_fk": { + "name": "workspace_notification_subscription_created_by_user_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.a2a_task_status": { + "name": "a2a_task_status", + "schema": "public", + "values": [ + "submitted", + "working", + "input-required", + "completed", + "failed", + "canceled", + "rejected", + "auth-required", + "unknown" + ] + }, + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_set_invitation_status": { + "name": "credential_set_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "expired", "cancelled"] + }, + "public.credential_set_member_status": { + "name": "credential_set_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": ["oauth", "env_workspace", "env_personal", "service_account"] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.notification_delivery_status": { + "name": "notification_delivery_status", + "schema": "public", + "values": ["pending", "in_progress", "success", "failed"] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": ["webhook", "email", "slack"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.template_creator_type": { + "name": "template_creator_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.template_status": { + "name": "template_status", + "schema": "public", + "values": ["pending", "approved", "rejected"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 9ac105fb2e4..864b641e53a 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1534,6 +1534,20 @@ "when": 1780079220753, "tag": "0219_amused_leo", "breakpoints": true + }, + { + "idx": 220, + "version": "7", + "when": 1780081787541, + "tag": "0220_early_hellion", + "breakpoints": true + }, + { + "idx": 221, + "version": "7", + "when": 1780111474238, + "tag": "0221_secret_hannibal_king", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 18fdc5cb7a5..6abf08dbeeb 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -329,8 +329,25 @@ export const workflowExecutionLogs = pgTable( endedAt: timestamp('ended_at'), totalDurationMs: integer('total_duration_ms'), + /** + * Heavy trace data (traceSpans, finalOutput, workflowInput, executionState) + * is externalized to object storage; this column then holds a slim payload: + * a `traceStoreRef` (__simLargeValueRef) pointer to the stored object plus + * inline markers (hasTraceSpans, traceSpanCount, environment, trigger, + * truncation flags). It also still holds the FULL payload inline for legacy + * / not-yet-backfilled rows, for the storage-write-failure fallback, and for + * job_execution_logs. Required — not droppable. Read it via + * `materializeExecutionData`, which resolves the pointer. + */ executionData: jsonb('execution_data').notNull().default('{}'), + /** @deprecated Not written/read; cost lives in usage_log + the `cost_total` projection. Drop in a follow-up PR after the `cost_total` backfill. */ cost: jsonb('cost'), + // Faithful, write-once projection of the run's usage_log ledger sum (dollars). + // Backs list cost display/filter/sort without live aggregation; never an + // independently-computed value (cost_total == SUM(usage_log) for the run). + costTotal: decimal('cost_total'), + // Model names used by the run (incl. zero-cost/BYOK), for the v1 model filter. + modelsUsed: text('models_used').array(), files: jsonb('files'), // File metadata for execution files createdAt: timestamp('created_at').notNull().defaultNow(), }, @@ -356,6 +373,11 @@ export const workflowExecutionLogs = pgTable( table.workspaceId, table.startedAt ), + workspaceCostTotalIdx: index('workflow_execution_logs_workspace_cost_total_idx').on( + table.workspaceId, + table.costTotal + ), + modelsUsedIdx: index('workflow_execution_logs_models_used_idx').using('gin', table.modelsUsed), workspaceEndedAtIdIdx: index('workflow_execution_logs_workspace_ended_at_id_idx').on( table.workspaceId, sql`date_trunc('milliseconds', ${table.endedAt})`, @@ -725,11 +747,6 @@ export const webhook = pgTable( pathIdx: uniqueIndex('path_deployment_unique') .on(table.path, table.deploymentVersionId) .where(sql`${table.archivedAt} IS NULL`), - // Optimize queries for webhooks by workflow and block - workflowBlockIdx: index('idx_webhook_on_workflow_id_block_id').on( - table.workflowId, - table.blockId - ), workflowDeploymentIdx: index('webhook_workflow_deployment_idx').on( table.workflowId, table.deploymentVersionId @@ -881,39 +898,33 @@ export const userStats = pgTable('user_stats', { .notNull() .references(() => user.id, { onDelete: 'cascade' }) .unique(), // One record per user - /** - * Deprecated former usage hot-path counters. - * - * These used to be incremented from execution/API/trigger/chat/MCP/A2A - * billing paths on every usage event. New usage reporting must derive - * these dimensions from `usage_log` instead of writing this row. - */ + // Retired usage hot-path counters: no writers/readers; derive from usage_log. + // Drop via DROP COLUMN in a follow-up migration. + /** @deprecated Retired usage counter; derive from usage_log. */ totalManualExecutions: integer('total_manual_executions').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalApiCalls: integer('total_api_calls').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalWebhookTriggers: integer('total_webhook_triggers').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalScheduledExecutions: integer('total_scheduled_executions').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalChatExecutions: integer('total_chat_executions').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalMcpExecutions: integer('total_mcp_executions').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalA2aExecutions: integer('total_a2a_executions').notNull().default(0), + /** @deprecated Retired usage counter; derive from usage_log. */ totalTokensUsed: bigint('total_tokens_used', { mode: 'number' }).notNull().default(0), - /** - * Deprecated former usage hot-path cost aggregate. - * - * `recordUsage` now appends attributed rows to `usage_log`; this column is - * retained only for legacy/admin reporting until consumers move to ledger - * aggregations. - */ + /** @deprecated Not written (recordUsage appends to usage_log); legacy/admin reads only. Move readers to ledger aggregation. */ totalCost: decimal('total_cost').notNull().default('0'), currentUsageLimit: decimal('current_usage_limit').default(DEFAULT_FREE_CREDITS.toString()), // Default $5 (1,000 credits) for free plan, null for team/enterprise usageLimitUpdatedAt: timestamp('usage_limit_updated_at').defaultNow(), /** - * Deprecated former usage hot-path current-period aggregate. - * - * Keep only as the pre-shift baseline for the active billing period. - * Canonical current-period usage is `currentPeriodCost` baseline plus - * attributed `usage_log` rows for the same billing entity and period. + * Active per-period baseline (not a per-usage hot-path counter). Current usage + * = this baseline + attributed usage_log rows for the period; reset at rollover. */ - currentPeriodCost: decimal('current_period_cost').notNull().default('0'), // Usage in current billing period + currentPeriodCost: decimal('current_period_cost').notNull().default('0'), lastPeriodCost: decimal('last_period_cost').default('0'), // Usage from previous billing period /** * Threshold/final billing tracker. @@ -933,25 +944,21 @@ export const userStats = pgTable('user_stats', { * overage collection. It is not a per-usage aggregate counter. */ creditBalance: decimal('credit_balance').notNull().default('0'), - /** - * Deprecated former Copilot hot-path cost/counter aggregates. - * - * Copilot/MCP Copilot usage should be reported from `usage_log` going - * forward. Current/last period Copilot columns remain as legacy reset - * trackers until those consumers are migrated. - */ + /** @deprecated Not written; report Copilot cost from usage_log. Legacy/admin reads only. */ totalCopilotCost: decimal('total_copilot_cost').notNull().default('0'), + /** Active per-period Copilot baseline; reset at rollover (not a per-usage counter). */ currentPeriodCopilotCost: decimal('current_period_copilot_cost').notNull().default('0'), + /** Previous-period Copilot cost; set at rollover. */ lastPeriodCopilotCost: decimal('last_period_copilot_cost').default('0'), + /** @deprecated Not written; report Copilot tokens from usage_log. Legacy/admin reads only. */ totalCopilotTokens: bigint('total_copilot_tokens', { mode: 'number' }).notNull().default(0), + /** @deprecated Not written; report Copilot calls from usage_log. Legacy/admin reads only. */ totalCopilotCalls: integer('total_copilot_calls').notNull().default(0), - /** - * Deprecated former MCP Copilot hot-path aggregates. - * - * New MCP Copilot billing usage should be reported from `usage_log`. - */ + /** @deprecated Not written; report MCP Copilot calls from usage_log. Legacy/admin reads only. */ totalMcpCopilotCalls: integer('total_mcp_copilot_calls').notNull().default(0), + /** @deprecated Not written; report MCP Copilot cost from usage_log. Legacy/admin reads only. */ totalMcpCopilotCost: decimal('total_mcp_copilot_cost').notNull().default('0'), + /** @deprecated No writer (never incremented or reset). MCP copilot usage lives in usage_log (source 'mcp_copilot'); read it from there, not this column. */ currentPeriodMcpCopilotCost: decimal('current_period_mcp_copilot_cost').notNull().default('0'), /** * Storage upload/delete hot-path tracker for personal plans. @@ -960,13 +967,7 @@ export const userStats = pgTable('user_stats', { * org-scoped storage writes update `organization.storageUsedBytes`. */ storageUsedBytes: bigint('storage_used_bytes', { mode: 'number' }).notNull().default(0), - /** - * Deprecated former execution hot-path activity timestamp. - * - * Successful workflow execution no longer updates `user_stats`; this column - * is retained only for legacy/admin reporting until replaced by an activity - * source that does not contend on this row. - */ + /** @deprecated Not updated by execution (no user_stats write on completion); legacy/admin reads only. */ lastActive: timestamp('last_active').notNull().defaultNow(), billingBlocked: boolean('billing_blocked').notNull().default(false), billingBlockedReason: billingBlockedReasonEnum('billing_blocked_reason'), @@ -2647,7 +2648,7 @@ export const auditLog = pgTable( }) ) -export const usageLogCategoryEnum = pgEnum('usage_log_category', ['model', 'fixed']) +export const usageLogCategoryEnum = pgEnum('usage_log_category', ['model', 'fixed', 'tool']) export const usageLogSourceEnum = pgEnum('usage_log_source', [ 'workflow', 'wand', @@ -2717,6 +2718,7 @@ export const usageLog = pgTable( table.workspaceId, table.createdAt ), + executionIdIdx: index('usage_log_execution_id_idx').on(table.executionId), }) ) diff --git a/packages/db/scripts/migrate.ts b/packages/db/scripts/migrate.ts index d8449a5775a..9d967a9db7c 100644 --- a/packages/db/scripts/migrate.ts +++ b/packages/db/scripts/migrate.ts @@ -2,6 +2,33 @@ import { drizzle } from 'drizzle-orm/postgres-js' import { migrate } from 'drizzle-orm/postgres-js/migrator' import postgres from 'postgres' +/** + * Concurrent-index convention (avoid write-blocking index builds on large tables) + * -------------------------------------------------------------------------------- + * drizzle-kit emits plain `CREATE INDEX`, which takes a SHARE lock and blocks all + * writes for the build duration — on a big, write-hot table (e.g. + * workflow_execution_logs, usage_log) that stalls every in-flight workflow + * completion for minutes. drizzle wraps each migration in a transaction, and + * `CREATE INDEX CONCURRENTLY` cannot run inside a transaction block. + * + * So, after generating a migration that adds an index on a large/hot table, edit + * the generated SQL to end drizzle's transaction first, then build concurrently + * and idempotently: + * + * COMMIT;--> statement-breakpoint + * CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_name" ON "table" (...); + * + * Notes: + * - Put the `COMMIT` breakpoint AFTER all transactional DDL (ALTER TABLE/TYPE) + * in the file and only the concurrent CREATE INDEX statements below it. + * - Use `IF NOT EXISTS` (and make sibling DDL idempotent, e.g. + * `ADD COLUMN IF NOT EXISTS`, `ADD VALUE IF NOT EXISTS`) so a re-run after a + * failed CONCURRENTLY build is safe — fresh DBs and re-applies both work. + * - CONCURRENTLY only takes a SHARE UPDATE EXCLUSIVE lock (allows reads/writes). + * - Always validate on staging before prod; a failed CONCURRENTLY build can + * leave an INVALID index that must be dropped and rebuilt. + */ + const url = process.env.DATABASE_URL if (!url) { console.error('ERROR: Missing DATABASE_URL environment variable.') @@ -12,6 +39,8 @@ if (!url) { const client = postgres(url, { max: 1, connect_timeout: 10 }) try { + // statement_timeout=0: index builds (esp. CONCURRENTLY on large tables) can run + // far longer than the app default; a migration must never be killed mid-build. await client`SET statement_timeout = 0` await migrate(drizzle(client), { migrationsFolder: './migrations' }) console.log('Migrations applied successfully.') diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 5b321a818d0..6f63a524433 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 758, - zodRoutes: 758, + totalRoutes: 761, + zodRoutes: 761, nonZodRoutes: 0, } as const @@ -83,6 +83,14 @@ const INDIRECT_ZOD_ROUTES = new Set([ // that closes the popup, so the JSON-mode contract framework doesn't fit. // Validation is enforced via state lookup + session-vs-row userId match. 'apps/sim/app/api/mcp/oauth/callback/route.ts', + // Deprecated Copilot MCP surface: these routes are gated to always return + // 410 Gone and consume no client-supplied input. + 'apps/sim/app/api/mcp/copilot/route.ts', + 'apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts', + 'apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts', + // Deprecated v1 headless copilot chat API: gated to always return 410 Gone + // and consumes no client-supplied input. + 'apps/sim/app/api/v1/copilot/chat/route.ts', ]) /** @@ -110,7 +118,6 @@ const RAW_JSON_BASELINE_ROUTES = new Set([ 'apps/sim/app/api/invitations/[id]/route.ts', 'apps/sim/app/api/knowledge/[id]/documents/route.ts', 'apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts', - 'apps/sim/app/api/mcp/copilot/route.ts', 'apps/sim/app/api/mcp/serve/[serverId]/route.ts', 'apps/sim/app/api/mcp/servers/route.ts', 'apps/sim/app/api/mcp/servers/[id]/route.ts', diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 84d9d762bb2..8751fc4ee0e 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -1,9 +1,9 @@ #!/usr/bin/env ts-node import fs from 'fs' import path from 'path' -import { fileURLToPath } from 'url' -import { glob } from 'glob' +import { fileURLToPath, pathToFileURL } from 'url' import { isVersionedType, stripVersionSuffix } from '@sim/utils/string' +import { glob } from 'glob' import type { BlockCategory } from '../apps/sim/blocks/types' import { IntegrationType } from '../apps/sim/blocks/types' @@ -195,6 +195,7 @@ interface IntegrationEntry { category: BlockCategory integrationType: IntegrationType tags?: string[] + landingContent?: Record } /** @@ -679,6 +680,19 @@ async function writeIntegrationsJson(iconMapping: Record): Promi const triggerRegistry = await buildTriggerRegistry() const { desc: toolDescMap, name: toolNameMap } = await buildToolDescriptionMap() + + // Hand-authored, integration-specific landing content (install walkthrough, + // privacy blurb), keyed by slug. Imported as pure data — its only import is + // type-only and erased at runtime — and baked into the entries below so the + // landing page reads a single source instead of augmenting at render time. + const landingContentModule = await import( + pathToFileURL(path.join(LANDING_INTEGRATIONS_DATA_PATH, 'landing-content.ts')).href + ) + const landingContentMap = (landingContentModule.INTEGRATION_LANDING_CONTENT ?? {}) as Record< + string, + Record + > + const integrations: IntegrationEntry[] = [] const seenBaseTypes = new Set() const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort() @@ -790,6 +804,7 @@ async function writeIntegrationsJson(iconMapping: Record): Promi category: 'tools', integrationType, ...(config.tags ? { tags: config.tags } : {}), + ...(landingContentMap[slug] ? { landingContent: landingContentMap[slug] } : {}), }) } }