diff --git a/apps/api/package.json b/apps/api/package.json index c510425c68..acf8fe92c4 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -4,9 +4,9 @@ "version": "0.0.1", "author": "", "dependencies": { - "@ai-sdk/anthropic": "^2.0.53", - "@ai-sdk/groq": "^2.0.32", - "@ai-sdk/openai": "^2.0.65", + "@ai-sdk/anthropic": "^3.0.75", + "@ai-sdk/groq": "^3.0.38", + "@ai-sdk/openai": "^3.0.62", "@aws-sdk/client-acm": "^3.948.0", "@aws-sdk/client-api-gateway": "^3.948.0", "@aws-sdk/client-apigatewayv2": "^3.948.0", @@ -88,7 +88,7 @@ "@upstash/redis": "^1.34.2", "@upstash/vector": "^1.2.2", "adm-zip": "^0.5.16", - "ai": "^5.0.60", + "ai": "^6.0.175", "archiver": "^7.0.1", "axios": "^1.12.2", "better-auth": "^1.4.22", diff --git a/apps/api/src/assistant-chat/assistant-chat.controller.ts b/apps/api/src/assistant-chat/assistant-chat.controller.ts index bb7b680381..8c9c30f7df 100644 --- a/apps/api/src/assistant-chat/assistant-chat.controller.ts +++ b/apps/api/src/assistant-chat/assistant-chat.controller.ts @@ -127,7 +127,7 @@ Important: const result = streamText({ model: openai('gpt-5'), system: systemPrompt, - messages: convertToModelMessages(messages), + messages: await convertToModelMessages(messages), tools, stopWhen: stepCountIs(5), }); diff --git a/apps/api/src/policies/policies.controller.ts b/apps/api/src/policies/policies.controller.ts index 67a4587c40..0eab46dc33 100644 --- a/apps/api/src/policies/policies.controller.ts +++ b/apps/api/src/policies/policies.controller.ts @@ -1321,7 +1321,7 @@ Keep responses helpful and focused on the policy editing task.`; const result = streamText({ model: openai('gpt-5.5'), system: systemPrompt, - messages: convertToModelMessages(messages), + messages: await convertToModelMessages(messages), }); return result.pipeTextStreamToResponse(res); diff --git a/apps/app/package.json b/apps/app/package.json index ac2bc13498..3103e15404 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -3,12 +3,14 @@ "version": "0.1.0", "type": "module", "dependencies": { - "@ai-sdk/anthropic": "^3.0.0", - "@ai-sdk/groq": "^3.0.0", - "@ai-sdk/openai": "^3.0.0", - "@ai-sdk/provider": "^3.0.0", - "@ai-sdk/react": "^3.0.0", - "@ai-sdk/rsc": "^2.0.0", + "@ai-sdk/anthropic": "^3.0.75", + "@ai-sdk/gateway": "^3.0.110", + "@ai-sdk/google": "^3.0.68", + "@ai-sdk/groq": "^3.0.38", + "@ai-sdk/openai": "^3.0.62", + "@ai-sdk/provider": "^3.0.10", + "@ai-sdk/react": "^3.0.177", + "@ai-sdk/rsc": "^2.0.175", "@aws-sdk/client-ec2": "^3.948.0", "@aws-sdk/client-lambda": "^3.948.0", "@aws-sdk/client-s3": "3.1013.0", @@ -84,7 +86,7 @@ "@vercel/sandbox": "^0.0.21", "@vercel/sdk": "^1.7.1", "@xyflow/react": "^12.10.0", - "ai": "^6.0.116", + "ai": "^6.0.175", "ai-elements": "^1.6.1", "axios": "^1.9.0", "better-auth": "^1.4.22", diff --git a/apps/app/src/app/(app)/[orgId]/components/OnboardingTracker.tsx b/apps/app/src/app/(app)/[orgId]/components/OnboardingTracker.tsx index 9ae8559985..f2c5dd82d1 100644 --- a/apps/app/src/app/(app)/[orgId]/components/OnboardingTracker.tsx +++ b/apps/app/src/app/(app)/[orgId]/components/OnboardingTracker.tsx @@ -3,7 +3,7 @@ import { Button } from '@trycompai/ui/button'; import { Card, CardContent } from '@trycompai/ui/card'; import type { Onboarding } from '@db'; -import { useRealtimeRun } from '@trigger.dev/react-hooks'; +import { useRun } from '@trigger.dev/react-hooks'; import { AnimatePresence, motion } from 'framer-motion'; import { AlertTriangle, @@ -26,9 +26,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; const ONBOARDING_STEPS = [ - { key: 'vendors', label: 'Researching Vendors', order: 1 }, - { key: 'risk', label: 'Creating Risks', order: 2 }, - { key: 'policies', label: 'Tailoring Policies', order: 3 }, + { key: 'policies', label: 'Tailoring Policies', order: 1 }, + { key: 'vendors', label: 'Creating Vendors', order: 2 }, + { key: 'risk', label: 'Creating Risks', order: 3 }, + { key: 'linkage', label: 'Linking to Controls', order: 4 }, + { key: 'vendorMitigations', label: 'Assessing Vendors', order: 5 }, + { key: 'riskMitigations', label: 'Assessing Risks', order: 6 }, ] as const; const IN_PROGRESS_STATUSES = [ @@ -60,11 +63,13 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => const [isPoliciesExpanded, setIsPoliciesExpanded] = useState(false); const [isVendorsExpanded, setIsVendorsExpanded] = useState(false); const [isRisksExpanded, setIsRisksExpanded] = useState(false); + const spinnerStyle = useMemo(() => ({ + animation: 'spin 1s linear infinite', + animationDelay: `${-(Date.now() % 1000)}ms`, + }), []); - // useRealtimeRun will automatically get the token from TriggerProvider context - // This gives us real-time updates including metadata changes - const { run, error } = useRealtimeRun(triggerJobId || '', { - enabled: !!triggerJobId, + const { run, error } = useRun(triggerJobId || '', { + refreshInterval: 1000, }); const dismissKey = triggerJobId ? `onboarding-tracker-dismissed:${triggerJobId}` : null; @@ -95,12 +100,30 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => } }, [dismissKey]); - // Auto-minimize when completed + // Auto-minimize when completed AND all background work is done. + // The main task completes before policies/mitigations finish (they + // run as fire-and-forget children), so also check the counters. useEffect(() => { - if (run?.status === 'COMPLETED' && !isMinimized) { + if (run?.status !== 'COMPLETED' || isMinimized) return; + const meta = run?.metadata as Record | undefined; + if (!meta) return; + + const policiesTotal = (meta.policiesTotal as number) || 0; + const policiesCompleted = (meta.policiesCompleted as number) || 0; + const policiesDone = policiesTotal === 0 || policiesCompleted >= policiesTotal; + + const vendorsTotal = (meta.vendorsTotal as number) || 0; + const vendorsCompleted = (meta.vendorsCompleted as number) || 0; + const vendorsDone = vendorsTotal === 0 || vendorsCompleted >= vendorsTotal; + + const risksTotal = (meta.risksTotal as number) || 0; + const risksCompleted = (meta.risksCompleted as number) || 0; + const risksDone = risksTotal === 0 || risksCompleted >= risksTotal; + + if (policiesDone && vendorsDone && risksDone) { setIsMinimized(true); } - }, [run?.status, isMinimized]); + }, [run?.status, run?.metadata, isMinimized]); // Extract step completion from metadata (real-time updates) const stepStatus = useMemo(() => { @@ -109,6 +132,9 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => vendors: false, risk: false, policies: false, + linkage: false, + vendorMitigations: false, + riskMitigations: false, currentStep: null, vendorsTotal: 0, vendorsCompleted: 0, @@ -161,10 +187,20 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => (meta[statusKey] as 'queued' | 'pending' | 'processing' | 'completed') || 'queued'; }); + const vTotal = (meta.vendorsTotal as number) || 0; + const vCompleted = (meta.vendorsCompleted as number) || 0; + const rTotal = (meta.risksTotal as number) || 0; + const rCompleted = (meta.risksCompleted as number) || 0; + const pTotal = (meta.policiesTotal as number) || 0; + const pCompleted = (meta.policiesCompleted as number) || 0; + return { vendors: meta.vendors === true, risk: meta.risk === true, - policies: meta.policies === true, + policies: pTotal > 0 && pCompleted >= pTotal, + linkage: meta.linkage === true, + vendorMitigations: vTotal > 0 && vCompleted >= vTotal, + riskMitigations: rTotal > 0 && rCompleted >= rTotal, currentStep: (meta.currentStep as string) || null, vendorsTotal: (meta.vendorsTotal as number) || 0, vendorsCompleted: (meta.vendorsCompleted as number) || 0, @@ -201,12 +237,11 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => const stepKey = currentStep.key; - // Expand current step if it has items to show - if (stepKey === 'vendors' && stepStatus.vendorsTotal > 0) { + if (stepKey === 'vendorMitigations' && stepStatus.vendorsTotal > 0) { setIsVendorsExpanded(true); setIsRisksExpanded(false); setIsPoliciesExpanded(false); - } else if (stepKey === 'risk' && stepStatus.risksTotal > 0) { + } else if (stepKey === 'riskMitigations' && stepStatus.risksTotal > 0) { setIsVendorsExpanded(false); setIsRisksExpanded(true); setIsPoliciesExpanded(false); @@ -214,6 +249,10 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => setIsVendorsExpanded(false); setIsRisksExpanded(false); setIsPoliciesExpanded(true); + } else { + setIsVendorsExpanded(false); + setIsRisksExpanded(false); + setIsPoliciesExpanded(false); } }, [currentStep?.key, stepStatus.vendorsTotal, stepStatus.risksTotal, stepStatus.policiesTotal]); @@ -395,7 +434,7 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => if (!run && !error) { return (
- +

Initializing...

Checking onboarding status

@@ -433,7 +472,22 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => const friendlyStatus = getFriendlyStatusName(run.status); - switch (run.status) { + // When the main task is COMPLETED but child tasks (policies, mitigations) + // are still running, show the progress view instead of "Setup Complete". + const hasBackgroundWork = (() => { + if (run.status !== 'COMPLETED') return false; + const meta = run.metadata as Record | undefined; + if (!meta) return false; + const pt = (meta.policiesTotal as number) || 0; + const pc = (meta.policiesCompleted as number) || 0; + const vt = (meta.vendorsTotal as number) || 0; + const vc = (meta.vendorsCompleted as number) || 0; + const rt = (meta.risksTotal as number) || 0; + const rc = (meta.risksCompleted as number) || 0; + return (pt > 0 && pc < pt) || (vt > 0 && vc < vt) || (rt > 0 && rc < rt); + })(); + + switch (hasBackgroundWork ? 'EXECUTING' : run.status) { case 'WAITING': case 'QUEUED': case 'EXECUTING': @@ -472,151 +526,56 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) =>
{ONBOARDING_STEPS.map((step) => { const isCurrent = currentStep?.key === step.key; - const isVendorsStep = step.key === 'vendors'; - const isRisksStep = step.key === 'risk'; - const isPoliciesStep = step.key === 'policies'; - - // Determine completion based on unique counts, not raw metadata totals - const vendorsCompleted = - uniqueVendorsCounts.total > 0 && - uniqueVendorsCounts.completed >= uniqueVendorsCounts.total; - const risksCompleted = - stepStatus.risksTotal > 0 && stepStatus.risksCompleted >= stepStatus.risksTotal; - const policiesCompleted = - stepStatus.policiesTotal > 0 && - stepStatus.policiesCompleted >= stepStatus.policiesTotal; - - const isCompleted = - (isVendorsStep && vendorsCompleted) || - (isRisksStep && risksCompleted) || - (isPoliciesStep && policiesCompleted); - - // Check if any items are actively being processed (not just queued) - const vendorsProcessing = Object.values(stepStatus.vendorsStatus || {}).some( - (status) => status === 'processing' || status === 'assessing', - ); - const risksProcessing = Object.values(stepStatus.risksStatus || {}).some( - (status) => status === 'processing' || status === 'assessing', + const isCompleted = stepStatus[step.key as keyof typeof stepStatus] === true; + + const isProcessing = !isCompleted && ( + (step.key === 'policies' && Object.values(stepStatus.policiesStatus).some((s) => s === 'processing')) || + (step.key === 'vendorMitigations' && Object.values(stepStatus.vendorsStatus).some((s) => s === 'processing' || s === 'assessing')) || + (step.key === 'riskMitigations' && Object.values(stepStatus.risksStatus).some((s) => s === 'processing' || s === 'assessing')) ); - const policiesProcessing = Object.values(stepStatus.policiesStatus || {}).some( - (status) => status === 'processing', + + const stepIcon = isCompleted ? ( + + ) : isCurrent || isProcessing ? ( + + ) : ( +
); - // Show spinner if actively processing, even if not the current step - const isActivelyProcessing = - (isVendorsStep && vendorsProcessing) || - (isRisksStep && risksProcessing) || - (isPoliciesStep && policiesProcessing); - - const vendorsQueued = - stepStatus.vendorsCompleted < stepStatus.vendorsTotal && - stepStatus.vendorsTotal > 0 && - !vendorsProcessing; - const risksQueued = - stepStatus.risksCompleted < stepStatus.risksTotal && - stepStatus.risksTotal > 0 && - !risksProcessing; - const policiesQueued = - stepStatus.policiesCompleted < stepStatus.policiesTotal && - stepStatus.policiesTotal > 0 && - !policiesProcessing; - - // Vendors step with expandable dropdown - if (isVendorsStep && stepStatus.vendorsTotal > 0) { + const stepTextClass = `text-sm ${ + isCompleted ? 'text-primary' : isCurrent || isProcessing ? 'text-primary font-medium' : 'text-muted-foreground' + }`; + + // Expandable step with per-entity items + if (step.key === 'vendorMitigations' && stepStatus.vendorsTotal > 0) { return (
- - - {/* Expanded vendor list */} {isVendorsExpanded && uniqueVendorsInfo.length > 0 && ( - +
{uniqueVendorsInfo.map((vendor) => { - const vendorStatus = stepStatus.vendorsStatus[vendor.id] || 'pending'; - const isVendorCompleted = vendorStatus === 'completed'; - const isVendorProcessing = vendorStatus === 'processing'; - const isVendorQueued = vendorStatus === 'pending'; - + const status = stepStatus.vendorsStatus[vendor.id] || 'pending'; + const done = status === 'completed'; + const active = status === 'processing' || status === 'assessing'; const content = ( <> - {isVendorCompleted ? ( - - ) : isVendorProcessing ? ( - - ) : isVendorQueued ? ( - - ) : ( -
- )} - - {vendor.name} - + {done ? : active ? : } + {vendor.name} ); - return (
- {isVendorCompleted && orgId ? ( - - {content} - - ) : ( - content - )} + {done && orgId ? {content} : content}
); })} @@ -627,99 +586,35 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => ); } - // Risks step with expandable dropdown - if (isRisksStep && stepStatus.risksTotal > 0) { + if (step.key === 'riskMitigations' && stepStatus.risksTotal > 0) { return (
- - - {/* Expanded risk list */} {isRisksExpanded && stepStatus.risksInfo.length > 0 && ( - +
{stepStatus.risksInfo.map((risk) => { - const riskStatus = stepStatus.risksStatus[risk.id] || 'pending'; - const isRiskCompleted = riskStatus === 'completed'; - const isRiskProcessing = riskStatus === 'processing'; - + const status = stepStatus.risksStatus[risk.id] || 'pending'; + const done = status === 'completed'; + const active = status === 'processing' || status === 'assessing'; const content = ( <> - {isRiskCompleted ? ( - - ) : isRiskProcessing ? ( - - ) : ( -
- )} - - {risk.name} - + {done ? : active ? :
} + {risk.name} ); - return (
- {isRiskCompleted && orgId ? ( - - {content} - - ) : ( - content - )} + {done && orgId ? {content} : content}
); })} @@ -730,104 +625,36 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => ); } - if (isPoliciesStep && stepStatus.policiesTotal > 0) { - // Policies step with expandable dropdown + if (step.key === 'policies' && stepStatus.policiesTotal > 0) { return (
- - - {/* Expanded policy list */} {isPoliciesExpanded && stepStatus.policiesInfo.length > 0 && ( - +
{stepStatus.policiesInfo.map((policy) => { - const policyStatus = stepStatus.policiesStatus[policy.id] || 'queued'; - const isPolicyCompleted = policyStatus === 'completed'; - const isPolicyProcessing = policyStatus === 'processing'; - const isPolicyQueued = - policyStatus === 'queued' || policyStatus === 'pending'; - + const status = stepStatus.policiesStatus[policy.id] || 'queued'; + const done = status === 'completed'; + const processing = status === 'processing'; + const queued = status === 'queued' || status === 'pending'; const content = ( <> - {isPolicyCompleted ? ( - - ) : isPolicyProcessing ? ( - - ) : isPolicyQueued ? ( - - ) : ( -
- )} - - {policy.name} - + {done ? : processing ? : queued ? :
} + {policy.name} ); - return (
- {isPolicyCompleted && orgId ? ( - - {content} - - ) : ( - content - )} + {done && orgId ? {content} : content}
); })} @@ -838,29 +665,17 @@ export const OnboardingTracker = ({ onboarding }: { onboarding: Onboarding }) => ); } - // Regular step + // Simple step row (creation, linkage) + const count = step.key === 'vendors' ? uniqueVendorsCounts.total + : step.key === 'risk' ? stepStatus.risksTotal + : null; return (
- {isCompleted ? ( - - ) : isCurrent ? ( - - ) : policiesQueued ? ( - - ) : ( -
+ {stepIcon} + {step.label} + {count !== null && count > 0 && ( + {count} )} - - {step.label} -
); })} diff --git a/apps/app/src/app/(app)/[orgId]/overview/components/ToDoOverview.tsx b/apps/app/src/app/(app)/[orgId]/overview/components/ToDoOverview.tsx index 3a5da51221..7645fca14b 100644 --- a/apps/app/src/app/(app)/[orgId]/overview/components/ToDoOverview.tsx +++ b/apps/app/src/app/(app)/[orgId]/overview/components/ToDoOverview.tsx @@ -5,7 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@trycompai/ui/card'; import { ScrollArea } from '@trycompai/ui/scroll-area'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@trycompai/ui/tabs'; import { Policy, Task } from '@db'; -import { useRealtimeRun } from '@trigger.dev/react-hooks'; +import { useRun } from '@trigger.dev/react-hooks'; import { ArrowRight, CheckCircle2, @@ -49,8 +49,8 @@ export function ToDoOverview({ const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); - const { run: onboardingRun } = useRealtimeRun(onboardingTriggerJobId || '', { - enabled: !!onboardingTriggerJobId, + const { run: onboardingRun } = useRun(onboardingTriggerJobId || '', { + refreshInterval: 1000, }); const IN_PROGRESS_STATUSES = [ diff --git a/apps/app/src/app/(app)/[orgId]/policies/(overview)/hooks/use-policy-onboarding-status.ts b/apps/app/src/app/(app)/[orgId]/policies/(overview)/hooks/use-policy-onboarding-status.ts index 9ecf5944f8..b3f8e203d6 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/(overview)/hooks/use-policy-onboarding-status.ts +++ b/apps/app/src/app/(app)/[orgId]/policies/(overview)/hooks/use-policy-onboarding-status.ts @@ -1,6 +1,6 @@ 'use client'; -import { useRealtimeRun } from '@trigger.dev/react-hooks'; +import { useRun } from '@trigger.dev/react-hooks'; import { useMemo } from 'react'; import type { PolicyTailoringStatus } from '../../all/components/policy-tailoring-context'; @@ -20,8 +20,8 @@ export function usePolicyOnboardingStatus( onboardingRunId: string | null | undefined, ) { const shouldSubscribe = Boolean(onboardingRunId); - const { run } = useRealtimeRun(shouldSubscribe ? onboardingRunId! : '', { - enabled: shouldSubscribe, + const { run } = useRun(shouldSubscribe ? onboardingRunId! : '', { + refreshInterval: 1000, }); const itemStatuses = useMemo>(() => { diff --git a/apps/app/src/app/(app)/[orgId]/policies/all/components/policies-table.tsx b/apps/app/src/app/(app)/[orgId]/policies/all/components/policies-table.tsx index 81bd08465a..a86d1deaf4 100644 --- a/apps/app/src/app/(app)/[orgId]/policies/all/components/policies-table.tsx +++ b/apps/app/src/app/(app)/[orgId]/policies/all/components/policies-table.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useRealtimeRun } from '@trigger.dev/react-hooks'; +import { useRun } from '@trigger.dev/react-hooks'; import { Download, Loader2 } from 'lucide-react'; import * as React from 'react'; import { toast } from 'sonner'; @@ -31,8 +31,8 @@ export function PoliciesTable({ promises, onboardingRunId }: PoliciesTableProps) const orgId = params.orgId as string; const shouldSubscribeToRun = Boolean(onboardingRunId); - const { run } = useRealtimeRun(shouldSubscribeToRun ? onboardingRunId! : '', { - enabled: shouldSubscribeToRun, + const { run } = useRun(shouldSubscribeToRun ? onboardingRunId! : '', { + refreshInterval: 1000, }); const policyStatuses = React.useMemo(() => { diff --git a/apps/app/src/app/(app)/[orgId]/risk/(overview)/hooks/use-onboarding-status.ts b/apps/app/src/app/(app)/[orgId]/risk/(overview)/hooks/use-onboarding-status.ts index 5b53b1975f..d3661d0831 100644 --- a/apps/app/src/app/(app)/[orgId]/risk/(overview)/hooks/use-onboarding-status.ts +++ b/apps/app/src/app/(app)/[orgId]/risk/(overview)/hooks/use-onboarding-status.ts @@ -1,6 +1,6 @@ 'use client'; -import { useRealtimeRun } from '@trigger.dev/react-hooks'; +import { useRun } from '@trigger.dev/react-hooks'; import { useMemo } from 'react'; export type OnboardingItemStatus = 'pending' | 'processing' | 'created' | 'assessing' | 'completed'; @@ -15,8 +15,8 @@ export function useOnboardingStatus( itemType: 'risks' | 'vendors', ) { const shouldSubscribe = Boolean(onboardingRunId); - const { run } = useRealtimeRun(shouldSubscribe ? onboardingRunId! : '', { - enabled: shouldSubscribe, + const { run } = useRun(shouldSubscribe ? onboardingRunId! : '', { + refreshInterval: 1000, }); const itemStatuses = useMemo>(() => { diff --git a/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/automation/[automationId]/tools/gateway.ts b/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/automation/[automationId]/tools/gateway.ts index 744e1b1441..49706fd9f1 100644 --- a/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/automation/[automationId]/tools/gateway.ts +++ b/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/automation/[automationId]/tools/gateway.ts @@ -1,6 +1,6 @@ import { createGatewayProvider } from '@ai-sdk/gateway'; import type { OpenAIResponsesProviderOptions } from '@ai-sdk/openai'; -import type { LanguageModelV2 } from '@ai-sdk/provider'; +import type { LanguageModelV3 } from '@ai-sdk/provider'; import type { JSONValue } from 'ai'; export async function getAvailableModels() { @@ -10,7 +10,7 @@ export async function getAvailableModels() { } export interface ModelOptions { - model: LanguageModelV2; + model: LanguageModelV3; providerOptions?: Record>; headers?: Record; } diff --git a/apps/app/src/app/(app)/setup/go/[id]/components/onboarding-status.tsx b/apps/app/src/app/(app)/setup/go/[id]/components/onboarding-status.tsx index e2aaf4f39d..28edd653d6 100644 --- a/apps/app/src/app/(app)/setup/go/[id]/components/onboarding-status.tsx +++ b/apps/app/src/app/(app)/setup/go/[id]/components/onboarding-status.tsx @@ -16,8 +16,13 @@ export function OnboardingStatus({ runId }: { runId: string }) { useEffect(() => { if (run?.status === 'COMPLETED') { router.replace('/'); + return; } - }, [run?.status, router]); + const meta = run?.metadata as Record | undefined; + if (meta?.readyForDashboard === true) { + router.replace('/'); + } + }, [run?.status, run?.metadata, router]); return (
diff --git a/apps/app/src/lib/embedding/run-linkage.ts b/apps/app/src/lib/embedding/run-linkage.ts index b1954be0a1..138cca6f7c 100644 --- a/apps/app/src/lib/embedding/run-linkage.ts +++ b/apps/app/src/lib/embedding/run-linkage.ts @@ -109,19 +109,10 @@ const AUTONOMOUS_FINAL_TOP_K = 8; const AUTONOMOUS_MIN_RERANK_SCORE = 5; const AUTONOMOUS_MIN_LINKS_FLOOR = 3; -// How many risks/vendors to match concurrently in the bulk onboarding path. -// Each iteration makes 1 vector query (Upstash) + 1 OpenAI rerank call + 1 -// Prisma update — typical wall-clock per iteration is 3–10 seconds (the -// rerank LLM call dominates). With 32 in-flight at once a 20-entity -// onboarding finishes in roughly one batch, well within gpt-5-mini / -// Upstash rate limits. -// -// NOTE: this is in-process concurrency on a single trigger.dev task. The -// natural next step (true fan-out per entity using `task.batchTrigger`) -// would unlock trigger.dev's queue-level concurrency (50), but requires -// passing the embedded-task metadata to children rather than rebuilding -// taskById per child. Filed as a follow-up. -const MATCH_CONCURRENCY = 32; +// Risk and vendor matching run in parallel, so this limit applies to +// EACH side independently. Keep it at 16 so both sides combined stay +// under ~32 concurrent LLM rerank calls. +const MATCH_CONCURRENCY = 16; async function mapWithConcurrency( items: T[], @@ -604,14 +595,22 @@ export async function runLinkage({ let suggestions: RunLinkageOutput['suggestions']; - // Risk matching — fan out with bounded concurrency. Each iteration is one - // vector query + (rerank | DB update). Order doesn't matter here; we sum - // riskLinks at the end and emit `current` based on completion count. + // Emit initial matching phases before the parallel fan-out so the UI shows + // both phases starting at once. if (risks.length > 0) { onPhase?.({ name: 'matching-risks', current: 0, total: risks.length }); } + if (vendors.length > 0) { + onPhase?.({ name: 'matching-vendors', current: 0, total: vendors.length }); + } + + // Risk + vendor matching run in parallel — they write to separate DB + // tables (Risk.tasks vs Vendor.tasks) and the shared taskById is read-only. let completedRisks = 0; - const riskOutcomes = await mapWithConcurrency(risks, MATCH_CONCURRENCY, async (risk) => { + let completedVendors = 0; + + const [riskOutcomes, vendorOutcomes] = await Promise.all([ + mapWithConcurrency(risks, MATCH_CONCURRENCY, async (risk) => { const similar = await findSimilarTasks({ organizationId, queryText: riskQueryText(risk), @@ -687,23 +686,8 @@ export async function runLinkage({ completedRisks += 1; onPhase?.({ name: 'matching-risks', current: completedRisks, total: risks.length }); return { count, perEntitySuggestions }; - }); - const riskLinks = riskOutcomes.reduce((sum, r) => sum + r.count, 0); - // suggestionsOnly endpoints always pass a single riskId, so at most one - // outcome carries suggestions — pick the first non-null. - for (const r of riskOutcomes) { - if (r.perEntitySuggestions) { - suggestions = r.perEntitySuggestions; - break; - } - } - - // Vendor matching — same pattern. - if (vendors.length > 0) { - onPhase?.({ name: 'matching-vendors', current: 0, total: vendors.length }); - } - let completedVendors = 0; - const vendorOutcomes = await mapWithConcurrency(vendors, MATCH_CONCURRENCY, async (vendor) => { + }), + mapWithConcurrency(vendors, MATCH_CONCURRENCY, async (vendor) => { const similar = await findSimilarTasks({ organizationId, queryText: vendorQueryText(vendor), @@ -751,8 +735,17 @@ export async function runLinkage({ completedVendors += 1; onPhase?.({ name: 'matching-vendors', current: completedVendors, total: vendors.length }); return { count, perEntitySuggestions }; - }); + }), + ]); + + const riskLinks = riskOutcomes.reduce((sum, r) => sum + r.count, 0); const vendorLinks = vendorOutcomes.reduce((sum, v) => sum + v.count, 0); + for (const r of riskOutcomes) { + if (r.perEntitySuggestions) { + suggestions = r.perEntitySuggestions; + break; + } + } if (!suggestions) { for (const v of vendorOutcomes) { if (v.perEntitySuggestions) { diff --git a/apps/app/src/lib/rerank-suggestions.ts b/apps/app/src/lib/rerank-suggestions.ts index 742d74a64c..1dad5df7c6 100644 --- a/apps/app/src/lib/rerank-suggestions.ts +++ b/apps/app/src/lib/rerank-suggestions.ts @@ -1,4 +1,4 @@ -import { openai } from '@ai-sdk/openai'; +import { createGatewayProvider } from '@ai-sdk/gateway'; import { generateObject, jsonSchema } from 'ai'; /** @@ -39,7 +39,11 @@ export interface RerankedCandidate { rerankScore: number; } -const RERANK_MODEL = 'gpt-5-mini'; +const gateway = createGatewayProvider({ + baseURL: process.env.AI_GATEWAY_BASE_URL, +}); + +const RERANK_MODEL = 'google/gemini-3.1-flash-lite-preview' as const; const SYSTEM_PROMPT = `You are a GRC analyst evaluating which compliance tasks would meaningfully reduce a specific risk or vendor exposure. @@ -105,7 +109,7 @@ export async function rerankSuggestions({ .join('\n'); const result = await generateObject({ - model: openai(RERANK_MODEL), + model: gateway(RERANK_MODEL), system: SYSTEM_PROMPT, prompt: userPrompt, schema: rerankSchema, diff --git a/apps/app/src/trigger/lib/prompts.ts b/apps/app/src/trigger/lib/prompts.ts index 1372487a22..a18332193c 100644 --- a/apps/app/src/trigger/lib/prompts.ts +++ b/apps/app/src/trigger/lib/prompts.ts @@ -1,6 +1,24 @@ import { FrameworkEditorFramework, FrameworkEditorPolicyTemplate } from '@db'; import { logger } from '@trigger.dev/sdk'; +const FRAMEWORK_MATCHERS: Array<{ flag: string; test: (name: string) => boolean }> = [ + { flag: 'soc2', test: (n) => /soc\s*2/i.test(n) || n.includes('soc') }, + { flag: 'hipaa', test: (n) => n.includes('hipaa') }, + { flag: 'pipeda', test: (n) => n.includes('pipeda') }, + { flag: 'gdpr', test: (n) => n.includes('gdpr') }, + { flag: 'iso27001', test: (n) => /iso\s*27001/i.test(n) }, + { flag: 'pci', test: (n) => /pci/i.test(n) }, + { flag: 'nist', test: (n) => /nist/i.test(n) }, + { flag: 'ccpa', test: (n) => n.includes('ccpa') }, +]; + +function buildFrameworkFlags(frameworks: FrameworkEditorFramework[]): string { + return FRAMEWORK_MATCHERS.map(({ flag, test }) => { + const active = frameworks.some((f) => test(f.name.toLowerCase())); + return ` - ${flag} is ${active ? 'true' : 'false'}`; + }).join('\n'); +} + export const generatePrompt = ({ policyTemplate, contextHub, @@ -29,10 +47,7 @@ export const generatePrompt = ({ frameworks.length > 0 ? frameworks.map((f) => `${f.name} v${f.version}`).join(', ') : 'None explicitly selected'; - const hasHIPAA = frameworks.some((f) => f.name.toLowerCase().includes('hipaa')); - const hasSOC2 = frameworks.some( - (f) => /soc\s*2/i.test(f.name) || f.name.toLowerCase().includes('soc'), - ); + const frameworkFlags = buildFrameworkFlags(frameworks); return ` Company: ${companyName} (${companyWebsite}) @@ -67,10 +82,9 @@ Required rules (keep this simple): - Do NOT copy instruction cue lines (e.g., "Add a HIPAA checklist...", "State that...", "Clarify that..."). Convert such cues into real policy language, and then remove the cue line entirely. If a cue precedes bullet points, keep the bullets but delete the cue line. 3) Handlebars-style conditionals - - The template may contain conditional blocks using {{#if var}}...{{/if}} syntax (e.g., {{#if soc2}}, {{#if hipaa}}). + - The template may contain conditional blocks using {{#if var}}...{{/if}} syntax (e.g., {{#if soc2}}, {{#if hipaa}}, {{#if pipeda}}). - Evaluate these using the selected frameworks: - - soc2 is ${hasSOC2 ? 'true' : 'false'} - - hipaa is ${hasHIPAA ? 'true' : 'false'} +${frameworkFlags} - If the condition is true: keep only the inner content and remove the {{#if}}/{{/if}} markers. - If the condition is false: remove the entire block including its content. - For any other unknown {{#if X}} variables: assume false and remove the block. diff --git a/apps/app/src/trigger/tasks/onboarding/generate-risk-mitigation.ts b/apps/app/src/trigger/tasks/onboarding/generate-risk-mitigation.ts index b56993af63..733847323b 100644 --- a/apps/app/src/trigger/tasks/onboarding/generate-risk-mitigation.ts +++ b/apps/app/src/trigger/tasks/onboarding/generate-risk-mitigation.ts @@ -1,5 +1,5 @@ import { RiskStatus, db } from '@db/server'; -import { logger, metadata, queue, tags, task } from '@trigger.dev/sdk'; +import { logger, metadata, queue, tags, task, tasks } from '@trigger.dev/sdk'; import axios from 'axios'; import { createRiskMitigationComment, @@ -114,7 +114,8 @@ export const generateRiskMitigationsForOrg = task({ const policies = policyRows.map((p) => ({ name: p.name, description: p.description })); - await generateRiskMitigation.batchTrigger( + await tasks.batchTriggerAndWait( + 'generate-risk-mitigation', risks.map((r) => ({ payload: { organizationId, @@ -122,7 +123,7 @@ export const generateRiskMitigationsForOrg = task({ authorId: author?.id, policies, }, - concurrencyKey: `${organizationId}:${r.id}`, + options: { concurrencyKey: `${organizationId}:${r.id}` }, })), ); diff --git a/apps/app/src/trigger/tasks/onboarding/generate-vendor-mitigation.ts b/apps/app/src/trigger/tasks/onboarding/generate-vendor-mitigation.ts index 38c91d44d0..8e16b540ae 100644 --- a/apps/app/src/trigger/tasks/onboarding/generate-vendor-mitigation.ts +++ b/apps/app/src/trigger/tasks/onboarding/generate-vendor-mitigation.ts @@ -1,5 +1,5 @@ import { VendorStatus, db } from '@db/server'; -import { logger, metadata, queue, tags, task } from '@trigger.dev/sdk'; +import { logger, metadata, queue, tags, task, tasks } from '@trigger.dev/sdk'; import axios from 'axios'; import { createVendorRiskComment, @@ -116,7 +116,8 @@ export const generateVendorMitigationsForOrg = task({ const policies = policyRows.map((p) => ({ name: p.name, description: p.description })); - await generateVendorMitigation.batchTrigger( + await tasks.batchTriggerAndWait( + 'generate-vendor-mitigation', vendors.map((v) => ({ payload: { organizationId, @@ -124,7 +125,7 @@ export const generateVendorMitigationsForOrg = task({ authorId: author?.id, policies, }, - concurrencyKey: `${organizationId}:${v.id}`, + options: { concurrencyKey: `${organizationId}:${v.id}` }, })), ); diff --git a/apps/app/src/trigger/tasks/onboarding/onboard-organization-helpers.ts b/apps/app/src/trigger/tasks/onboarding/onboard-organization-helpers.ts index c333bdba01..cc6289d01a 100644 --- a/apps/app/src/trigger/tasks/onboarding/onboard-organization-helpers.ts +++ b/apps/app/src/trigger/tasks/onboarding/onboard-organization-helpers.ts @@ -1,4 +1,4 @@ -import { openai } from '@ai-sdk/openai'; +import { createGatewayProvider } from '@ai-sdk/gateway'; import { Departments, FrameworkEditorFramework, @@ -14,6 +14,11 @@ import { import { db } from '@db/server'; import { logger, metadata, tasks } from '@trigger.dev/sdk'; import { generateObject, jsonSchema } from 'ai'; + +const gateway = createGatewayProvider({ + baseURL: process.env.AI_GATEWAY_BASE_URL, +}); +const ONBOARDING_MODEL = 'google/gemini-3-flash' as const; import axios from 'axios'; import { z } from 'zod'; import type { researchVendor } from '../scrape/research'; @@ -496,7 +501,7 @@ export async function extractVendorsFromContext( const customVendorNameSet = new Set(customVendors.map((v) => v.name.toLowerCase())); const { object } = await generateObject({ - model: openai('gpt-4.1-mini'), + model: gateway(ONBOARDING_MODEL), schema: jsonSchema({ type: 'object', properties: { @@ -664,7 +669,7 @@ Citations (write one sentence per item, in order): ${formatCitationsBlock(citations)}`; const result = await generateObject({ - model: openai('gpt-5-mini'), + model: gateway(ONBOARDING_MODEL), system: RISK_MITIGATION_PROMPT, prompt: userPrompt, schema: sentencesSchema, @@ -935,29 +940,27 @@ async function triggerVendorRiskAssessmentsViaApi(params: { * Triggers research tasks for created vendors */ export async function triggerVendorResearch(vendors: any[]): Promise { - for (const vendor of vendors) { + const researchable = vendors.filter((vendor) => { const website = (vendor.website ?? '').toString().trim(); if (!website) { logger.info(`Skipping research for vendor ${vendor.name} (no website)`); - continue; + return false; } - - // Ensure it's a valid absolute URL; don't let one bad vendor break the whole onboarding. try { // eslint-disable-next-line no-new new URL(website); + return true; } catch { logger.warn(`Skipping research for vendor ${vendor.name} (invalid website URL)`, { website, }); - continue; + return false; } + }); - try { - // `scoreContext` chains the research run into score-vendor-risk - // when GlobalVendors finishes saving, so the per-org Vendor row - // gets a posture-grounded score instead of the conservative - // (possible × moderate) default the extraction pass set. + const results = await Promise.allSettled( + researchable.map(async (vendor) => { + const website = (vendor.website ?? '').toString().trim(); const handle = await tasks.trigger('research-vendor', { website, scoreContext: @@ -966,12 +969,16 @@ export async function triggerVendorResearch(vendors: any[]): Promise { : undefined, }); logger.info(`Triggered research for vendor ${vendor.name} with handle ${handle.id}`); - } catch (error) { + }), + ); + + for (const [i, result] of results.entries()) { + if (result.status === 'rejected') { + const vendor = researchable[i]; logger.error('Failed to trigger vendor research task', { vendorId: vendor.id, vendorName: vendor.name, - website, - error: error instanceof Error ? error.message : String(error), + error: result.reason instanceof Error ? result.reason.message : String(result.reason), }); } } @@ -1031,7 +1038,7 @@ Citations (write one sentence per item, in order): ${formatCitationsBlock(citations)}`; const result = await generateObject({ - model: openai('gpt-5-mini'), + model: gateway(ONBOARDING_MODEL), system: RISK_MITIGATION_PROMPT, prompt: userPrompt, schema: sentencesSchema, @@ -1103,7 +1110,7 @@ export async function extractRisksFromContext( existingRisks: { title: string }[], ): Promise { const { object } = await generateObject({ - model: openai('gpt-4.1-mini'), + model: gateway(ONBOARDING_MODEL), schema: jsonSchema({ type: 'object', properties: { @@ -1310,7 +1317,8 @@ export async function triggerPolicyUpdates( metadata.set(`policy_${policy.id}_status`, 'queued'); }); - await updatePolicy.batchTriggerAndWait( + await tasks.batchTrigger( + 'update-policy', policies.map((policy) => ({ payload: { organizationId, @@ -1318,7 +1326,7 @@ export async function triggerPolicyUpdates( contextHub: questionsAndAnswers.map((c) => `${c.question}\n${c.answer}`).join('\n'), frameworks, }, - concurrencyKey: organizationId, + options: { concurrencyKey: organizationId }, })), ); } @@ -1363,17 +1371,14 @@ export async function createVendors( triggeredCount: vendorsForRiskAssessment.length, }); - // TODO: Un-comment this when UI part is ready - await triggerVendorRiskAssessmentsViaApi({ + // Fire-and-forget: risk assessments + research are side effects that + // don't need to block the main onboarding flow. + void triggerVendorRiskAssessmentsViaApi({ organizationId, vendors: vendorsForRiskAssessment, - // Onboarding should NOT force expensive research if GlobalVendors already has data. - // If data is missing, the API/Trigger pipeline will still do research. withResearch: false, }); - - // Trigger background research for each vendor (best-effort) - await triggerVendorResearch(createdVendors); + void triggerVendorResearch(createdVendors); return createdVendors; } diff --git a/apps/app/src/trigger/tasks/onboarding/onboard-organization.ts b/apps/app/src/trigger/tasks/onboarding/onboard-organization.ts index caa3bd1813..478e05b475 100644 --- a/apps/app/src/trigger/tasks/onboarding/onboard-organization.ts +++ b/apps/app/src/trigger/tasks/onboarding/onboard-organization.ts @@ -27,7 +27,7 @@ export const onboardOrganization = task({ await tags.add([`org:${payload.organizationId}`]); // Initialize metadata for real-time tracking - metadata.set('currentStep', 'Researching Vendors...'); + metadata.set('currentStep', 'Tailoring Policies...'); metadata.set('vendors', false); metadata.set('risk', false); metadata.set('policies', false); @@ -61,11 +61,18 @@ export const onboardOrganization = task({ metadata.set('policiesInfo', []); } - const frameworkInstances = await db.frameworkInstance.findMany({ - where: { - organizationId: payload.organizationId, - }, - }); + const [frameworkInstances, owner] = await Promise.all([ + db.frameworkInstance.findMany({ + where: { organizationId: payload.organizationId }, + }), + db.member.findFirst({ + where: { + organizationId: payload.organizationId, + role: { contains: 'owner' }, + deactivated: false, + }, + }), + ]); const frameworks = await db.frameworkEditorFramework.findMany({ where: { @@ -77,114 +84,88 @@ export const onboardOrganization = task({ }, }); - // Get owner - const owner = await db.member.findFirst({ - where: { - organizationId: payload.organizationId, - role: { - contains: 'owner', - }, - deactivated: false, - }, - }); - if (!owner) { logger.error(`Owner not found for organization ${payload.organizationId}`); throw new Error(`Owner not found for organization ${payload.organizationId}`); } - // Update owner to also be an employee - await db.member.update({ - where: { - id: owner.id, - }, - data: { - role: 'owner,employee', - }, - }); - - // Assign owner to all tasks - await db.task.updateMany({ - where: { - organizationId: payload.organizationId, - }, - data: { - assigneeId: owner.id, - }, - }); - - // Update tasks to be quarterly - await db.task.updateMany({ - where: { - organizationId: payload.organizationId, - }, - data: { - frequency: 'quarterly', - }, - }); - - // Extract vendors first so we can show them immediately - const vendorData = await extractVendorsFromContext(questionsAndAnswers); - - // Track vendors immediately as "pending" before creation - if (vendorData.length > 0) { - metadata.set('vendorsTotal', vendorData.length); - metadata.set('vendorsCompleted', 0); - metadata.set('vendorsRemaining', vendorData.length); - // Use temporary IDs based on index until we have real IDs - metadata.set( - 'vendorsInfo', - vendorData.map((v, index) => ({ id: `temp_${index}`, name: v.vendor_name })), - ); - // Mark all as pending initially - vendorData.forEach((_, index) => { - metadata.set(`vendor_temp_${index}_status`, 'pending'); - }); - } - - // Create vendors (pass extracted data to avoid re-extraction) - // Tracking is handled inside createVendors -> createVendorsFromData - const vendors = await createVendors(questionsAndAnswers, payload.organizationId, vendorData); - - // Update tracking with real vendor IDs (tracking during creation uses temp IDs) - if (vendors.length > 0) { - metadata.set( - 'vendorsInfo', - vendors.map((v) => ({ id: v.id, name: v.name })), - ); - // Mark all created vendors as "assessing" since they need mitigation - vendors.forEach((vendor) => { - metadata.set(`vendor_${vendor.id}_status`, 'assessing'); - }); - } - - // Mark vendors step as complete in metadata (real-time) - metadata.set('vendors', true); - metadata.set('currentStep', 'Creating Risks...'); - - // Create risks (tracking is handled inside createRisks) - const risks = await createRisks( - questionsAndAnswers, - payload.organizationId, - organization.name, - ); + await Promise.all([ + db.member.update({ + where: { id: owner.id }, + data: { role: 'owner,employee' }, + }), + db.task.updateMany({ + where: { organizationId: payload.organizationId }, + data: { assigneeId: owner.id, frequency: 'quarterly' }, + }), + ]); - // Mark all created risks as "assessing" since they need mitigation - if (risks.length > 0) { - risks.forEach((risk) => { - metadata.set(`risk_${risk.id}_status`, 'assessing'); - }); - } + // Policies only need frameworks + Q&A — start them immediately so they + // drain while vendors/risks/linkage run. Fire-and-forget; per-policy + // progress tracked via child metadata (policy_${id}_status). + const policyCount = policyList.length; + metadata.set('currentStep', `Tailoring Policies... (0/${policyCount})`); + await updateOrganizationPolicies(payload.organizationId, questionsAndAnswers, frameworks); - // Mark risks step as complete in metadata (real-time) - metadata.set('risk', true); + // Extract vendors + risks in parallel (both are independent LLM calls). + metadata.set('currentStep', 'Creating Vendors...'); + + const [vendors, risks] = await Promise.all([ + (async () => { + const vendorData = await extractVendorsFromContext(questionsAndAnswers); + if (vendorData.length > 0) { + metadata.set('vendorsTotal', vendorData.length); + metadata.set('vendorsCompleted', 0); + metadata.set('vendorsRemaining', vendorData.length); + metadata.set( + 'vendorsInfo', + vendorData.map((v, index) => ({ id: `temp_${index}`, name: v.vendor_name })), + ); + vendorData.forEach((_, index) => { + metadata.set(`vendor_temp_${index}_status`, 'pending'); + }); + } + const created = await createVendors( + questionsAndAnswers, + payload.organizationId, + vendorData, + ); + if (created.length > 0) { + metadata.set( + 'vendorsInfo', + created.map((v) => ({ id: v.id, name: v.name })), + ); + created.forEach((vendor) => { + metadata.set(`vendor_${vendor.id}_status`, 'assessing'); + }); + } + metadata.set('vendors', true); + metadata.set('currentStep', 'Creating Risks...'); + return created; + })(), + (async () => { + metadata.set('currentStep', 'Creating Risks...'); + const created = await createRisks( + questionsAndAnswers, + payload.organizationId, + organization.name, + ); + if (created.length > 0) { + created.forEach((risk) => { + metadata.set(`risk_${risk.id}_status`, 'assessing'); + }); + } + metadata.set('risk', true); + return created; + })(), + ]); // Auto-link risks + vendors to existing tasks BEFORE mitigation generation // runs, so the AI prompt for both risks AND vendors sees the linked // tasks/controls and produces grounded plans. Fan-out for both happens // after this gate. Fails-soft: a timeout/error degrades to today's // behavior. (ENG-221 + Cubic findings #7 / #26.) - metadata.set('currentStep', 'Linking risks to tasks...'); + metadata.set('currentStep', 'Linking to Controls...'); try { await tasks.triggerAndWait( 'link-risks-and-vendors-to-work', @@ -198,40 +179,34 @@ export const onboardOrganization = task({ }); } - // Get policy count for the step message - const policyCount = policyList.length; - metadata.set('currentStep', `Tailoring Policies... (0/${policyCount})`); - - // Fan-out vendor + risk mitigations now that linkage has populated the - // grounding context for both kinds of entities. Done in parallel — - // each fan-out task itself batchTriggers per-entity children. - await Promise.all([ - tasks.trigger( - 'generate-vendor-mitigations-for-org', - { organizationId: payload.organizationId }, - ), - tasks.trigger( - 'generate-risk-mitigations-for-org', - { organizationId: payload.organizationId }, - ), - ]); - - // Update policies with progress tracking - await updateOrganizationPolicies(payload.organizationId, questionsAndAnswers, frameworks); - - // Mark policies step as complete in metadata (real-time) - metadata.set('policies', true); - metadata.set('currentStep', 'Finalizing...'); - - // Mark onboarding as completed in metadata - metadata.set('completed', true); - - // Mark onboarding as completed in database + // Redirect the user to the dashboard now — policies, vendors, and + // risks are created. The task stays alive so child metadata writes + // (mitigation progress) keep landing on the root run. + metadata.set('readyForDashboard', true); await db.onboarding.update({ where: { organizationId: payload.organizationId }, data: { triggerJobCompleted: true }, }); + // Fan-out vendor + risk mitigations. triggerAndWait keeps this task + // alive so metadata.root stays writable for child tasks. Sequential + // because Trigger.dev doesn't support parallel waits, but both + // fan-outs use batchTriggerAndWait internally so their children + // run with full queue concurrency. + metadata.set('currentStep', 'Assessing Vendors...'); + await tasks.triggerAndWait( + 'generate-vendor-mitigations-for-org', + { organizationId: payload.organizationId }, + ); + metadata.set('currentStep', 'Assessing Risks...'); + await tasks.triggerAndWait( + 'generate-risk-mitigations-for-org', + { organizationId: payload.organizationId }, + ); + + metadata.set('currentStep', 'Finalizing...'); + metadata.set('completed', true); + logger.info(`Created ${vendors.length} vendors`); logger.info(`Onboarding completed for organization ${payload.organizationId}`); } catch (error) { diff --git a/apps/app/src/trigger/tasks/onboarding/process-policy-template.ts b/apps/app/src/trigger/tasks/onboarding/process-policy-template.ts new file mode 100644 index 0000000000..4296331e0b --- /dev/null +++ b/apps/app/src/trigger/tasks/onboarding/process-policy-template.ts @@ -0,0 +1,199 @@ +type JsonNode = Record; + +const PLACEHOLDER_QUESTIONS: Array<[string, RegExp]> = [ + ['COMPANYINFO', /describe your company/i], + ['INDUSTRY', /what industry/i], + ['EMPLOYEES', /how many employees/i], + ['DEVICES', /what devices/i], + ['SOFTWARE', /what software/i], + ['LOCATION', /how does your team work/i], + ['CRITICAL', /where do you host/i], + ['DATA', /what type(s)? of data/i], + ['GEO', /where is your data/i], +]; + +const FRAMEWORK_MATCHERS: Array<[string, (n: string) => boolean]> = [ + ['soc2', (n) => /soc\s*2/i.test(n) || n.includes('soc')], + ['hipaa', (n) => n.includes('hipaa')], + ['pipeda', (n) => n.includes('pipeda')], + ['gdpr', (n) => n.includes('gdpr')], + ['iso27001', (n) => /iso\s*27001/i.test(n)], + ['pci', (n) => /pci/i.test(n)], + ['nist', (n) => /nist/i.test(n)], + ['ccpa', (n) => n.includes('ccpa')], +]; + +export function buildVariables({ + companyName, + contextHub, +}: { + companyName: string; + contextHub: string; +}): Record { + const vars: Record = { COMPANY: companyName }; + const lines = contextHub.split('\n'); + + for (let i = 0; i < lines.length - 1; i++) { + for (const [key, pattern] of PLACEHOLDER_QUESTIONS) { + if (!vars[key] && pattern.test(lines[i])) { + vars[key] = lines[i + 1]?.trim() || 'N/A'; + } + } + } + + return vars; +} + +export function buildFlags( + frameworks: Array<{ name: string }>, +): Record { + const flags: Record = {}; + for (const [flag, test] of FRAMEWORK_MATCHERS) { + flags[flag] = frameworks.some((f) => test(f.name.toLowerCase())); + } + return flags; +} + +function extractText(node: JsonNode): string { + if (typeof node.text === 'string') return node.text; + if (Array.isArray(node.content)) { + return (node.content as JsonNode[]).map(extractText).join(''); + } + return ''; +} + +function replacePlaceholdersInText(text: string, vars: Record): string { + return text.replace(/\{\{(\w+)\}\}/g, (match, key: string) => vars[key] ?? 'N/A'); +} + +function processInlineConditionals(text: string, flags: Record): string { + return text.replace( + /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, + (_match, flag: string, inner: string) => (flags[flag] ? inner : ''), + ); +} + +function processTextNode(node: JsonNode, vars: Record, flags: Record): JsonNode | null { + if (typeof node.text !== 'string') return node; + + let text = node.text; + text = processInlineConditionals(text, flags); + text = replacePlaceholdersInText(text, vars); + + if (!text) return null; + return { ...node, text }; +} + +function processNode(node: JsonNode, vars: Record, flags: Record): JsonNode | null { + if (node.type === 'text') { + return processTextNode(node, vars, flags); + } + + if (!Array.isArray(node.content)) return node; + + const processed = processContentArray(node.content as JsonNode[], vars, flags); + if (processed.length === 0 && node.type !== 'document') return null; + + return { ...node, content: processed }; +} + +/** + * Walks a TipTap content array, evaluates {{#if}}…{{/if}} blocks that + * span multiple sibling nodes, replaces {{PLACEHOLDER}} values, and + * returns the cleaned array. Fully deterministic — no LLM calls. + */ +export function processContentArray( + nodes: JsonNode[], + vars: Record, + flags: Record, +): JsonNode[] { + const result: JsonNode[] = []; + let skipDepth = 0; + + for (const node of nodes) { + const text = extractText(node); + + const openMatch = text.match(/\{\{#if\s+(\w+)\}\}/); + const closeMatch = text.includes('{{/if}}'); + const hasOnlyMarker = /^\s*\{\{#if\s+\w+\}\}\s*$/.test(text) || + /^\s*\{\{\/if\}\}\s*$/.test(text); + + if (openMatch && closeMatch) { + if (skipDepth === 0) { + const processed = processNode(node, vars, flags); + if (processed) result.push(processed); + } + continue; + } + + if (openMatch) { + if (skipDepth > 0) { + skipDepth++; + continue; + } + const flag = openMatch[1]; + const isTrue = flags[flag] ?? false; + if (!isTrue) { + skipDepth++; + } else if (!hasOnlyMarker) { + const processed = processNode(node, vars, flags); + if (processed) result.push(processed); + } + continue; + } + + if (closeMatch) { + if (skipDepth > 0) { + skipDepth--; + } else if (!hasOnlyMarker) { + const processed = processNode(node, vars, flags); + if (processed) result.push(processed); + } + continue; + } + + if (skipDepth > 0) continue; + + const processed = processNode(node, vars, flags); + if (processed) result.push(processed); + } + + return result; +} + +/** + * Processes a full TipTap document: replaces handlebars placeholders with + * real values and evaluates conditional blocks based on framework flags. + * Returns the processed content array (inner nodes of the document). + */ +export function processTemplate({ + content, + companyName, + contextHub, + frameworks, +}: { + content: unknown; + companyName: string; + contextHub: string; + frameworks: Array<{ name: string }>; +}): JsonNode[] { + const vars = buildVariables({ companyName, contextHub }); + const flags = buildFlags(frameworks); + + let nodes: JsonNode[]; + if ( + content && + typeof content === 'object' && + 'type' in (content as JsonNode) && + (content as JsonNode).type === 'doc' && + Array.isArray((content as JsonNode).content) + ) { + nodes = (content as JsonNode).content as JsonNode[]; + } else if (Array.isArray(content)) { + nodes = content as JsonNode[]; + } else { + return []; + } + + return processContentArray(nodes, vars, flags); +} diff --git a/apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts b/apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts index 899b02c074..6024a1f890 100644 --- a/apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts +++ b/apps/app/src/trigger/tasks/onboarding/update-policies-helpers.ts @@ -1,362 +1,85 @@ -import { openai } from '@ai-sdk/openai'; +import { createGatewayProvider } from '@ai-sdk/gateway'; import { db, FrameworkEditorFramework, FrameworkEditorPolicyTemplate, type Policy } from '@db/server'; import type { JSONContent } from '@tiptap/react'; import { logger } from '@trigger.dev/sdk'; -import { generateObject, NoObjectGeneratedError } from 'ai'; +import { generateObject } from 'ai'; import { z } from 'zod'; -import { generatePrompt } from '../../lib/prompts'; - -// Sanitization utilities -const PLACEHOLDER_REGEX = /<<\s*TO\s*REVIEW\s*>>/gi; - -function extractText(node: Record): string { - const text = node && typeof node['text'] === 'string' ? (node['text'] as string) : ''; - const content = Array.isArray((node as any)?.content) - ? ((node as any).content as Record[]) - : null; - if (content && content.length > 0) { - return content.map(extractText).join(''); - } - return text || ''; -} - -function sanitizeNodePlaceholders(node: Record): Record { - const cloned: Record = { ...node }; - if (typeof cloned['text'] === 'string') { - const replaced = (cloned['text'] as string) - .replace(PLACEHOLDER_REGEX, '') - .replace(/\s{2,}/g, ' ') - .trim(); - cloned['text'] = replaced; - } - const content = Array.isArray((cloned as any).content) - ? ((cloned as any).content as Record[]) - : null; - if (content) { - (cloned as any).content = content.map(sanitizeNodePlaceholders); - } - return cloned; -} +import { processTemplate } from './process-policy-template'; -function shouldRemoveAuditorArtifactsHeading(headingText: string): boolean { - const lower = headingText.trim().toLowerCase(); - // Match variations: artefacts/artifacts and with/without "evidence" - return lower.includes('auditor') && (lower.includes('artefact') || lower.includes('artifact')); -} - -function removeAuditorArtifactsSection( - content: Record[], -): Record[] { - const result: Record[] = []; - let i = 0; - while (i < content.length) { - const node = content[i] as Record; - const nodeType = typeof node['type'] === 'string' ? (node['type'] as string) : ''; - if (nodeType === 'heading') { - const headingText = extractText(node); - if (shouldRemoveAuditorArtifactsHeading(headingText)) { - // Skip this heading and subsequent nodes until next heading or end - i += 1; - while (i < content.length) { - const nextNode = content[i] as Record; - const nextType = typeof nextNode['type'] === 'string' ? (nextNode['type'] as string) : ''; - if (nextType === 'heading') break; - i += 1; - } - continue; - } - } - result.push(sanitizeNodePlaceholders(node)); - i += 1; - } - return result; -} +const gateway = createGatewayProvider({ + baseURL: process.env.AI_GATEWAY_BASE_URL, +}); -function sanitizeDocument(document: { type: 'document'; content: Record[] }) { - const content = Array.isArray(document.content) ? document.content : []; - const withoutAuditorArtifacts = removeAuditorArtifactsSection(content); - return { - type: 'document' as const, - content: withoutAuditorArtifacts, - }; -} +const CUE_LINE_PATTERN = + /^(State that|Clarify that|Add a |Include a |Specify |List |Note that|Require that|Describe |Define )/; -/** - * Extract text from a heading node - */ -function extractHeadingText(node: Record): string { - const type = typeof node['type'] === 'string' ? (node['type'] as string) : ''; - if (type !== 'heading') return ''; - return extractText(node).trim(); -} +type JsonNode = Record; /** - * Get allowed top-level heading titles from the original/template content - * We consider headings with level 1 or 2 as top-level anchors for section boundaries. + * Finds text nodes containing instruction cue lines (e.g. "State that...", + * "Define..."). Returns an array of { path, text } entries where path is + * the index chain to reach the text node in the content tree. */ -function getAllowedTopLevelHeadings(originalContent: Record[]): string[] { - const allowed: string[] = []; - for (const node of originalContent) { - const type = typeof node['type'] === 'string' ? (node['type'] as string) : ''; - if (type === 'heading') { - const level = (node as any)?.attrs?.level; - if (typeof level === 'number' && level >= 1 && level <= 2) { - const text = extractHeadingText(node); - if (text) allowed.push(text.toLowerCase()); - } +function findCueLines( + nodes: JsonNode[], + path: number[] = [], +): Array<{ path: number[]; text: string }> { + const results: Array<{ path: number[]; text: string }> = []; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.type === 'text' && typeof node.text === 'string' && CUE_LINE_PATTERN.test(node.text)) { + results.push({ path: [...path, i], text: node.text }); } - } - return allowed; -} - -/** - * Remove sections that should not exist (Table of Contents, Mapping sections) and - * drop any new top-level sections not present in the original/template headings. - */ -function alignToTemplateStructure( - updated: { type: 'document'; content: Record[] }, - originalContent: Record[], -): { type: 'document'; content: Record[] } { - const allowedTopHeadings = getAllowedTopLevelHeadings(originalContent); - if (allowedTopHeadings.length === 0) { - // Nothing to enforce; return as-is - return updated; - } - - const isForbiddenHeading = (headingText: string): boolean => { - const lower = headingText.toLowerCase(); - if (lower.includes('table of contents')) return true; - if (lower.includes('mapping') && lower.includes('soc')) return true; // e.g., SOC 2 mappings - return false; - }; - - const result: Record[] = []; - let i = 0; - const content = Array.isArray(updated.content) ? updated.content : []; - - while (i < content.length) { - const node = content[i] as Record; - const nodeType = typeof node['type'] === 'string' ? (node['type'] as string) : ''; - - if (nodeType === 'heading') { - const level = (node as any)?.attrs?.level; - const headingText = extractHeadingText(node); - - // Skip forbidden sections entirely - if (isForbiddenHeading(headingText)) { - i += 1; - while (i < content.length) { - const nextNode = content[i] as Record; - const nextType = typeof nextNode['type'] === 'string' ? (nextNode['type'] as string) : ''; - if (nextType === 'heading') break; - i += 1; - } - continue; - } - - // Enforce allowed top-level headings - if (typeof level === 'number' && level >= 1 && level <= 2) { - const normalized = headingText.toLowerCase(); - if (!allowedTopHeadings.includes(normalized)) { - // Drop this new top-level section and its content until next heading - i += 1; - while (i < content.length) { - const nextNode = content[i] as Record; - const nextType = - typeof nextNode['type'] === 'string' ? (nextNode['type'] as string) : ''; - if (nextType === 'heading') break; - i += 1; - } - continue; - } - } + if (Array.isArray(node.content)) { + results.push(...findCueLines(node.content as JsonNode[], [...path, i])); } - - // Keep node (with placeholder sanitization already applied earlier) - result.push(node); - i += 1; } - - return { type: 'document', content: result }; + return results; } -/** - * AI reconciliation step: ensure the draft keeps the same top-level section structure - * as the original template while using the new content where headings match. - * - Preserve the order and heading levels from the original. - * - For each top-level heading in the original, use the draft section content if present - * (matched by heading text, case-insensitive); otherwise keep the original section content. - * - Do not introduce new top-level sections, TOC, or mapping sections. - */ -export async function reconcileFormatWithTemplate( - originalContent: Record[], - draft: { type: 'document'; content: Record[] }, -): Promise<{ type: 'document'; content: Record[] }> { - try { - const { object } = await generateObject({ - model: openai('gpt-5-mini'), - output: 'no-schema', - system: `You are an expert policy editor. -Given an ORIGINAL policy TipTap JSON and a DRAFT TipTap JSON, produce a FINAL TipTap JSON that: -- Preserves the ORIGINAL top-level section structure (order and presence of titles) and visual presentation of titles. -- VISUAL CONSISTENCY: For each ORIGINAL top-level title, match its visual style in the FINAL exactly: - - If the ORIGINAL uses a heading, keep the same heading level in the FINAL. - - If the ORIGINAL uses a bold paragraph as the title, use a bold paragraph for that title in the FINAL (single text node with a bold mark). - - After each title, ensure at least one paragraph node exists (may be empty if content is not provided). -- CONTENT SELECTION: For each ORIGINAL title, prefer the DRAFT's corresponding section content when the title text matches (case-insensitive). If no matching DRAFT section exists, keep the ORIGINAL section content. -- COMPLETENESS: Include every ORIGINAL top-level title exactly once and in the same order as the ORIGINAL. Do not omit any original section, even if the DRAFT lacks content for it (in that case, keep the ORIGINAL section or include an empty paragraph placeholder under the title). -- PROHIBITIONS: Do not add new top-level sections. Do not include a Table of Contents. Do not add framework mapping sections unless they already exist in the ORIGINAL. -- OUTPUT FORMAT: Valid TipTap JSON with root {"type":"document","content":[...]}.`, - prompt: `ORIGINAL (TipTap JSON):\n${JSON.stringify({ type: 'document', content: originalContent })}\n\nDRAFT (TipTap JSON):\n${JSON.stringify(draft)}\n\nReturn ONLY the FINAL TipTap JSON document with type "document" and a "content" array. -Follow the structure rules above strictly.`, - }); - const parsed = object as { type?: string; content?: unknown }; - if (parsed?.type !== 'document' || !Array.isArray(parsed?.content)) { - throw new Error('AI response did not match expected TipTap document structure'); - } - return { type: 'document' as const, content: parsed.content as Record[] }; - } catch (error) { - logger.error('AI reconcile format step failed; falling back to deterministic alignment', { - error: error instanceof Error ? error.message : String(error), - }); - return draft; +function setTextAtPath(nodes: JsonNode[], path: number[], newText: string): void { + let current: JsonNode[] = nodes; + for (let i = 0; i < path.length - 1; i++) { + const node = current[path[i]]; + current = node.content as JsonNode[]; } + const target = current[path[path.length - 1]]; + target.text = newText; } /** - * AI format checker: returns whether DRAFT conforms to ORIGINAL's format + * Rewrites instruction cue lines into direct policy language using a + * targeted LLM call. Only fires when cue lines are detected — most + * policies skip this entirely. */ -export async function aiCheckFormatWithTemplate( - originalContent: Record[], - draft: { type: 'document'; content: Record[] }, -): Promise<{ isConforming: boolean; reasons: string[] }> { +async function refineCueLines( + content: JsonNode[], + policyName: string, +): Promise { + const cueLines = findCueLines(content); + if (cueLines.length === 0) return content; + try { const { object } = await generateObject({ - model: openai('gpt-5-mini'), - system: `You are validating policy layout. -Compare ORIGINAL vs DRAFT (TipTap JSON). Determine if DRAFT conforms to ORIGINAL format: -- Same top-level section titles present and in the same order -- Title visual style matches (heading level vs bold paragraph) -- No new top-level sections added; no Table of Contents; no framework mapping sections if not in ORIGINAL -- After every title there is at least one paragraph node -Return JSON { isConforming: boolean, reasons: string[] }. -`, - prompt: `ORIGINAL:\n${JSON.stringify({ type: 'document', content: originalContent })}\n\nDRAFT:\n${JSON.stringify(draft)}\n\nRespond only with the JSON object.`, + model: gateway('anthropic/claude-sonnet-4.6'), + system: `You rewrite policy template instructions into direct, professional policy language. Each input is an instruction (e.g. "State that...", "Define..."). Return the equivalent text as it should appear in a published security policy — authoritative, concise, no instructional phrasing.`, + prompt: `Policy: "${policyName}"\n\nRewrite each instruction:\n${cueLines.map((c, i) => `${i + 1}. ${c.text}`).join('\n')}`, schema: z.object({ - isConforming: z.boolean(), - reasons: z.array(z.string()).default([]), + rewrites: z.array(z.string()).length(cueLines.length), }), }); - return object; - } catch (error) { - logger.error('AI format check failed, defaulting to not conforming', { - error: error instanceof Error ? error.message : String(error), - }); - return { isConforming: false, reasons: ['checker_failed'] }; - } -} - -/** - * VISUAL LAYOUT ENFORCEMENT - * Make the draft visually match the template with respect to section title presentation: - * - If the template uses a heading (level 1/2) for a title, ensure the draft uses the same heading level for that title - * - If the template uses a bold paragraph as a title, ensure the draft does the same (single text node, bold mark) - * - After each title, ensure at least one paragraph node exists - */ -function isBoldParagraphTitle(node: Record): boolean { - if ((node as any)?.type !== 'paragraph') return false; - const content = Array.isArray((node as any)?.content) ? ((node as any).content as any[]) : []; - if (content.length !== 1) return false; - const t = content[0]; - if (!t || t.type !== 'text' || typeof t.text !== 'string') return false; - const marks = Array.isArray(t.marks) ? (t.marks as any[]) : []; - return marks.some((m) => m?.type === 'bold'); -} - -function toBoldTitleParagraph(text: string): Record { - return { - type: 'paragraph', - content: [ - { - type: 'text', - text, - marks: [{ type: 'bold' }], - }, - ], - } as Record; -} -type TitlePattern = { kind: 'heading'; level: number } | { kind: 'boldParagraph' }; - -function getTitlePatternMap(original: Record[]): Map { - const map = new Map(); - for (const node of original) { - const type = (node as any)?.type as string; - if (type === 'heading') { - const level = (node as any)?.attrs?.level; - const text = extractHeadingText(node); - if (text && typeof level === 'number') { - map.set(text.trim().toLowerCase(), { kind: 'heading', level }); - } - } else if (isBoldParagraphTitle(node)) { - const text = extractText(node); - if (text) { - map.set(text.trim().toLowerCase(), { kind: 'boldParagraph' }); - } + for (let i = 0; i < cueLines.length; i++) { + setTextAtPath(content, cueLines[i].path, object.rewrites[i]); } - } - return map; -} - -export function enforceVisualLayoutWithTemplate( - original: Record[], - draft: { type: 'document'; content: Record[] }, -): { type: 'document'; content: Record[] } { - const content = Array.isArray(draft.content) ? draft.content : []; - const patternMap = getTitlePatternMap(original); - if (patternMap.size === 0) return draft; - - const out: Record[] = []; - - for (let i = 0; i < content.length; i += 1) { - const node = content[i] as Record; - const type = (node as any)?.type as string; - let pushed = false; - - if (type === 'heading' || isBoldParagraphTitle(node)) { - const titleText = (type === 'heading' ? extractHeadingText(node) : extractText(node)).trim(); - const key = titleText.toLowerCase(); - const pattern = titleText ? patternMap.get(key) : undefined; - - if (pattern) { - if (pattern.kind === 'heading') { - out.push({ - type: 'heading', - attrs: { level: pattern.level }, - content: [{ type: 'text', text: titleText }], - }); - pushed = true; - } else if (pattern.kind === 'boldParagraph') { - out.push(toBoldTitleParagraph(titleText)); - pushed = true; - } - - if (pushed) { - // Ensure at least one paragraph follows a title - const next = content[i + 1] as Record | undefined; - const nextType = (next as any)?.type as string | undefined; - if (!next || nextType === 'heading') { - out.push({ type: 'paragraph', content: [] }); - } - continue; - } - } - } - - out.push(node); + } catch (err) { + logger.warn('Cue line refinement failed; keeping original text', { + policyName, + error: err instanceof Error ? err.message : String(err), + }); } - return { type: 'document', content: out }; + return content; } // Types @@ -430,80 +153,6 @@ export async function fetchOrganizationAndPolicy( return { organization, policy, policyTemplate }; } -/** - * Generates the prompt for policy content generation - */ -export async function generatePolicyPrompt( - policyTemplate: FrameworkEditorPolicyTemplate, - contextHub: string, - organization: OrganizationData, - frameworks: FrameworkEditorFramework[], -): Promise { - return generatePrompt({ - contextHub, - policyTemplate, - companyName: organization.name ?? 'Company', - companyWebsite: organization.website ?? 'https://company.com', - frameworks, - }); -} - -/** - * Generates policy content using AI with TipTap JSON schema - */ -export async function generatePolicyContent(prompt: string): Promise<{ - type: 'document'; - content: Record[]; -}> { - try { - const { object } = await generateObject({ - model: openai('gpt-5-mini'), - output: 'no-schema', - system: `You are an expert at writing security policies. Generate content directly as TipTap JSON format. - -TipTap JSON structure: -- Root: {"type": "document", "content": [array of nodes]} -- Paragraphs: {"type": "paragraph", "content": [text nodes]} -- Headings: {"type": "heading", "attrs": {"level": 1-6}, "content": [text nodes]} -- Lists: {"type": "orderedList"/"bulletList", "content": [listItem nodes]} -- List items: {"type": "listItem", "content": [paragraph nodes]} -- Text: {"type": "text", "text": "content", "marks": [formatting]} -- Bold: {"type": "bold"} in marks array -- Italic: {"type": "italic"} in marks array - -IMPORTANT: Follow ALL formatting instructions in the prompt, implementing them as proper TipTap JSON structures. -Return a JSON object with exactly this shape: {"type": "document", "content": [array of TipTap nodes]}`, - prompt: `Generate a SOC 2 compliant security policy as a complete TipTap JSON document. - -INSTRUCTIONS TO IMPLEMENT IN TIPTAP JSON: -${prompt.replace(/\\n/g, '\n')} - -Return the complete TipTap document following ALL the above requirements using proper TipTap JSON structure.`, - }); - - const parsed = object as { type?: string; content?: unknown }; - if (parsed?.type !== 'document' || !Array.isArray(parsed?.content)) { - throw new Error('AI response did not match expected TipTap document structure'); - } - - return { type: 'document' as const, content: parsed.content as Record[] }; - } catch (aiError) { - logger.error(`Error generating AI content: ${aiError}`); - - if (NoObjectGeneratedError.isInstance(aiError)) { - logger.error( - `NoObjectGeneratedError: ${JSON.stringify({ - cause: aiError.cause, - text: aiError.text, - response: aiError.response, - usage: aiError.usage, - })}`, - ); - } - throw aiError; - } -} - /** * Updates policy content in the database with versioning support. * Creates a new version 1 and sets it as the current (published) version. @@ -610,19 +259,21 @@ export async function updatePolicyInDatabase( export async function processPolicyUpdate(params: UpdatePolicyParams): Promise { const { organizationId, policyId, contextHub, frameworks, memberId } = params; - // Fetch organization and policy data const { organization, policyTemplate } = await fetchOrganizationAndPolicy( organizationId, policyId, ); - // Generate prompt for AI - const prompt = await generatePolicyPrompt(policyTemplate, contextHub, organization, frameworks); + const processedContent = processTemplate({ + content: policyTemplate.content, + companyName: organization.name ?? 'Company', + contextHub, + frameworks, + }); - // Generate new policy content - const updatedContent = await generatePolicyContent(prompt); + const refinedContent = await refineCueLines(processedContent, policyTemplate.name); + const updatedContent = { type: 'document' as const, content: refinedContent }; - // Update policy in database with versioning support await updatePolicyInDatabase(policyId, updatedContent.content, memberId); return { diff --git a/apps/app/src/trigger/tasks/onboarding/update-policy.ts b/apps/app/src/trigger/tasks/onboarding/update-policy.ts index f17bcbafde..da53708a0e 100644 --- a/apps/app/src/trigger/tasks/onboarding/update-policy.ts +++ b/apps/app/src/trigger/tasks/onboarding/update-policy.ts @@ -2,21 +2,7 @@ import { logger, metadata, queue, schemaTask } from '@trigger.dev/sdk'; import { z } from 'zod'; import { processPolicyUpdate } from './update-policies-helpers'; -if (!process.env.OPENAI_API_KEY) { - throw new Error('OPENAI_API_KEY is not set'); -} - -// v4: define queue ahead of time. -// concurrencyLimit is intentionally low so a 30+ policy onboarding fan-out -// can't hog all of dev's 25-slot env budget. Each policy update takes ~20-40s -// (LLM call + DB writes), while risk + vendor mitigations take ~8-12s. With -// no cap the slower policies fill every slot first and the user-visible -// mitigation tasks sit queued for minutes. Capping at 5 leaves ≥20 slots -// for the faster, latency-sensitive mitigation fan-out to finish quickly, -// then policies drain through. Total wall time is ~the same, but the user -// sees their treatment plans populate immediately instead of waiting for -// every policy to finish first. -export const updatePolicyQueue = queue({ name: 'update-policy', concurrencyLimit: 5 }); +export const updatePolicyQueue = queue({ name: 'update-policy', concurrencyLimit: 15 }); export const updatePolicy = schemaTask({ id: 'update-policy', diff --git a/apps/app/trigger.config.ts b/apps/app/trigger.config.ts index 1a41b7888e..a08ad804da 100644 --- a/apps/app/trigger.config.ts +++ b/apps/app/trigger.config.ts @@ -1,6 +1,5 @@ import { puppeteer } from '@trigger.dev/build/extensions/puppeteer'; import { defineConfig } from '@trigger.dev/sdk'; -import { caBundleExtension } from './caBundleExtension'; import { prismaExtension } from './customPrismaExtension'; export default defineConfig({ @@ -15,7 +14,6 @@ export default defineConfig({ maxDuration: 300, // 5 minutes build: { extensions: [ - caBundleExtension(), prismaExtension({ version: '7.6.0', dbPackageVersion: '^2.0.0', diff --git a/bun.lock b/bun.lock index a31aac1737..390869b3ea 100644 --- a/bun.lock +++ b/bun.lock @@ -71,9 +71,9 @@ "name": "@trycompai/api", "version": "0.0.1", "dependencies": { - "@ai-sdk/anthropic": "^2.0.53", - "@ai-sdk/groq": "^2.0.32", - "@ai-sdk/openai": "^2.0.65", + "@ai-sdk/anthropic": "^3.0.75", + "@ai-sdk/groq": "^3.0.38", + "@ai-sdk/openai": "^3.0.62", "@aws-sdk/client-acm": "^3.948.0", "@aws-sdk/client-api-gateway": "^3.948.0", "@aws-sdk/client-apigatewayv2": "^3.948.0", @@ -155,7 +155,7 @@ "@upstash/redis": "^1.34.2", "@upstash/vector": "^1.2.2", "adm-zip": "^0.5.16", - "ai": "^5.0.60", + "ai": "^6.0.175", "archiver": "^7.0.1", "axios": "^1.12.2", "better-auth": "^1.4.22", @@ -219,12 +219,14 @@ "name": "@trycompai/app", "version": "0.1.0", "dependencies": { - "@ai-sdk/anthropic": "^3.0.0", - "@ai-sdk/groq": "^3.0.0", - "@ai-sdk/openai": "^3.0.0", - "@ai-sdk/provider": "^3.0.0", - "@ai-sdk/react": "^3.0.0", - "@ai-sdk/rsc": "^2.0.0", + "@ai-sdk/anthropic": "^3.0.75", + "@ai-sdk/gateway": "^3.0.110", + "@ai-sdk/google": "^3.0.68", + "@ai-sdk/groq": "^3.0.38", + "@ai-sdk/openai": "^3.0.62", + "@ai-sdk/provider": "^3.0.10", + "@ai-sdk/react": "^3.0.177", + "@ai-sdk/rsc": "^2.0.175", "@aws-sdk/client-ec2": "^3.948.0", "@aws-sdk/client-lambda": "^3.948.0", "@aws-sdk/client-s3": "3.1013.0", @@ -300,7 +302,7 @@ "@vercel/sandbox": "^0.0.21", "@vercel/sdk": "^1.7.1", "@xyflow/react": "^12.10.0", - "ai": "^6.0.116", + "ai": "^6.0.175", "ai-elements": "^1.6.1", "axios": "^1.9.0", "better-auth": "^1.4.22", @@ -538,7 +540,7 @@ }, "packages/db": { "name": "@trycompai/db", - "version": "2.1.1", + "version": "2.3.0", "bin": { "comp-prisma-postinstall": "./dist/postinstall.js", }, @@ -650,12 +652,12 @@ "name": "@trycompai/integrations", "version": "1.0.0", "dependencies": { - "@ai-sdk/openai": "^2.0.0", + "@ai-sdk/openai": "^3.0.62", "@aws-sdk/client-securityhub": "^3.0.0", "@aws-sdk/client-sts": "^3.0.0", "@azure/identity": "^4.10.0", "@trycompai/app": "workspace:*", - "ai": "^5.0.0", + "ai": "^6.0.175", "jsonwebtoken": "^9.0.2", "node-fetch": "^2.6.7", "react": "^19.0.0", @@ -736,7 +738,7 @@ "@tiptap/suggestion": "3.22.1", "@uidotdev/usehooks": "^2.4.1", "@xyflow/react": "^12.9.3", - "ai": "^5.0.101", + "ai": "^6.0.175", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -809,7 +811,7 @@ "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@3.0.99", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.79", "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-d/WsYOlqjQeEwTewawjrlhoWfHt3q1vRT5/XdFJ6U+KYd/3HnAlrA3rg0+T7xMk98XmctaILJb45Ct/8zrGxSA=="], - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.79", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-K0U09FPDO1kmLPjRLXFcNSvmnKHJBMARCb8r3Ulw7wU6/+Zh9djWcFDiPPNsklg6yAezcdLTcYPszgWJJ6iOTA=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.75", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5AV3CKwaOJFdGXhihVgvRLNrjwRn2Xmy71YygT8DYOA+5zTx93Seg2QSIS8b3tJxzZ7X4H84pEtrE8VZKBCZGA=="], "@ai-sdk/azure": ["@ai-sdk/azure@2.0.108", "", { "dependencies": { "@ai-sdk/openai": "2.0.106", "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/F+lx3glCDiqJfqkZP9IOCubYlWABX2Jg9Yzm/JIxZR5qHfo9rsLwS4zVtghbELVbEjxakaFlDT/c6uTBj0uug=="], @@ -817,17 +819,17 @@ "@ai-sdk/deepseek": ["@ai-sdk/deepseek@1.0.39", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5TXw7Pm0+/YL2WdnZpXBgruPayhqBgBMNDL95V14Sf4MQz+RmNMhansvK8Fv9Dcgp3Y0p7EasNsPWYJOfj0zoA=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.86", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-pP9F5G7C5sqZtAwquFB+g50lVS/s4Wf/ll2WSm0ODk0Iix27trVgBDpFK5CBletcQXSDlAvSQQi25nBodYLt3g=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sbv8+1L9/BRKydn8dMNwoMQKupA4iLJ9N+yvxgW6wMQ/94UepDf3FeYWMj/dLdzolAHZ6izRUP4s5WqQkmJ2Zg=="], - "@ai-sdk/google": ["@ai-sdk/google@2.0.72", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BjDY6l+rV4CmHKjZe4H0uRXW3M2o+g7PaYM8oFpW+9PP1qKNEybnJ6//Si7BSf6DT+86dKARrtEl09lxSSaMaA=="], + "@ai-sdk/google": ["@ai-sdk/google@3.0.68", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjQSuUmwStn7R0RDGl9I8kriY+xjmschzy5JN4eHPPEOdca2gS6zLc+oi8jhRiCqqROkk3U12Q9M8rmQw7gmbQ=="], "@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.137", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.79", "@ai-sdk/google": "2.0.72", "@ai-sdk/openai-compatible": "1.0.39", "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-vDtCmwMy4CzVsv3PESmkE96qDSqnsArDDEc22eggujZI/WxmIeKa+8vyUYjJUx9HZLOCPo7HhYDXjH0R2mcM+Q=="], - "@ai-sdk/groq": ["@ai-sdk/groq@2.0.40", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-1EL8D1tyjOKjCFUt8XspDoA6zxDcalMsLR2O56ji8QklWsAPaf4TuMJAvf5x5KDrkuJaSAjk94KvPH5hOX+VNQ=="], + "@ai-sdk/groq": ["@ai-sdk/groq@3.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mzn+KYeROVHFZnAr3qNX+eZ4Un4BFykOcs8XDH8LLzdfgrW6fxQkdiZyww0asYGjIYaa16dkyVtglp4GV6BeUQ=="], "@ai-sdk/mistral": ["@ai-sdk/mistral@2.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oBR9nJQ8TRFU0JIIXF+0cFTo8VVEreA1V8AMD3c77BJj/1NUSBLrhyqAbX9k7YAtztvZHUdFcm3+vK8KIx0sUQ=="], - "@ai-sdk/openai": ["@ai-sdk/openai@2.0.106", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EFC0rpo1wfe4HIz5KZCE72edP2J7fOeR7wPXzjCDljaTRB1wectKDIKRLowpU4F0mbcJ+XScAsoYNPK/Z20aVQ=="], + "@ai-sdk/openai": ["@ai-sdk/openai@3.0.62", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Oy74Bztik2X25wZD9HRd83BAXOKcRvrfgz9gvVGqKj68yegf447NiElPbB6TSVb8zyiY9wv1GSGywMCxnnoF9g=="], "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.39", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-001hdQPPXxYBWrz5d+eAmBVYmwzsB+guIey1DFXi1ZEE5H3j7fRrhPpX55MdM9Fle2DS7WZ8b3qkumCIWE92YQ=="], @@ -3109,7 +3111,7 @@ "@vercel/analytics": ["@vercel/analytics@1.6.1", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg=="], - "@vercel/oidc": ["@vercel/oidc@2.0.2", "", { "dependencies": { "@types/ms": "2.1.0", "ms": "2.1.3" } }, "sha512-59PBFx3T+k5hLTEWa3ggiMpGRz1OVvl9eN8SUai+A43IsqiOuAe7qPBf+cray/Fj6mkgnxm/D7IAtjc8zSHi7g=="], + "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], "@vercel/sandbox": ["@vercel/sandbox@0.0.21", "", { "dependencies": { "@vercel/oidc": "^2.0.2", "async-retry": "1.3.3", "jsonlines": "0.1.1", "ms": "2.1.3", "tar-stream": "3.1.7", "undici": "^7.16.0", "zod": "3.24.4" } }, "sha512-j6nAUQyuw6znVaZGd7yI0nab1EWhEtIZPnTXvpDatJ2SObodYZOz5hGoLjCopAuhQBC7vOngbZ1bwP6HxtB5+g=="], @@ -6683,9 +6685,13 @@ "@actions/http-client/undici": ["undici@6.25.0", "", {}, "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.79", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-K0U09FPDO1kmLPjRLXFcNSvmnKHJBMARCb8r3Ulw7wU6/+Zh9djWcFDiPPNsklg6yAezcdLTcYPszgWJJ6iOTA=="], + "@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], + "@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], + + "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@2.0.106", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EFC0rpo1wfe4HIz5KZCE72edP2J7fOeR7wPXzjCDljaTRB1wectKDIKRLowpU4F0mbcJ+XScAsoYNPK/Z20aVQ=="], "@ai-sdk/azure/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], @@ -6693,19 +6699,21 @@ "@ai-sdk/deepseek/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@ai-sdk/gateway/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], + "@ai-sdk/gateway/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], - "@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], + "@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], - "@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], + "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.79", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-K0U09FPDO1kmLPjRLXFcNSvmnKHJBMARCb8r3Ulw7wU6/+Zh9djWcFDiPPNsklg6yAezcdLTcYPszgWJJ6iOTA=="], + + "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.72", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BjDY6l+rV4CmHKjZe4H0uRXW3M2o+g7PaYM8oFpW+9PP1qKNEybnJ6//Si7BSf6DT+86dKARrtEl09lxSSaMaA=="], "@ai-sdk/google-vertex/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@ai-sdk/groq/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], + "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], "@ai-sdk/mistral/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@ai-sdk/openai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], + "@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], @@ -6775,6 +6783,14 @@ "@browserbasehq/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], + "@browserbasehq/stagehand/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.79", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-K0U09FPDO1kmLPjRLXFcNSvmnKHJBMARCb8r3Ulw7wU6/+Zh9djWcFDiPPNsklg6yAezcdLTcYPszgWJJ6iOTA=="], + + "@browserbasehq/stagehand/@ai-sdk/google": ["@ai-sdk/google@2.0.72", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BjDY6l+rV4CmHKjZe4H0uRXW3M2o+g7PaYM8oFpW+9PP1qKNEybnJ6//Si7BSf6DT+86dKARrtEl09lxSSaMaA=="], + + "@browserbasehq/stagehand/@ai-sdk/groq": ["@ai-sdk/groq@2.0.40", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-1EL8D1tyjOKjCFUt8XspDoA6zxDcalMsLR2O56ji8QklWsAPaf4TuMJAvf5x5KDrkuJaSAjk94KvPH5hOX+VNQ=="], + + "@browserbasehq/stagehand/@ai-sdk/openai": ["@ai-sdk/openai@2.0.106", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EFC0rpo1wfe4HIz5KZCE72edP2J7fOeR7wPXzjCDljaTRB1wectKDIKRLowpU4F0mbcJ+XScAsoYNPK/Z20aVQ=="], + "@browserbasehq/stagehand/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], "@browserbasehq/stagehand/@browserbasehq/sdk": ["@browserbasehq/sdk@2.10.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-pOL4yW8P8AI2+N5y6zEP6XXKqIXtYyKunr1JXppqQDOyKLxxvZEDqQCHJXWUzqgx3R1tGWpn7m9AjXN7MeYInA=="], @@ -7271,11 +7287,7 @@ "@trycompai/api/@react-email/render": ["@react-email/render@2.0.8", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-5udvVr3U/WuGJZfLdLBOhkzrqRWd2Q5ZYmF7ppcy7FzWcwgshdqLMNqJOXcVzAXJXg/2bm7D+WGJzTtZOZMQnQ=="], - "@trycompai/app/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.74", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Xew9rfz9WWhDSyF8rNhjT/XWOWelNfJrMlmG0Ahw210hStisRpQZ1s+7VeI9JTJOZ5y5tXqBi5kfPwYnCfyRTA=="], - - "@trycompai/app/@ai-sdk/groq": ["@ai-sdk/groq@3.0.38", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mzn+KYeROVHFZnAr3qNX+eZ4Un4BFykOcs8XDH8LLzdfgrW6fxQkdiZyww0asYGjIYaa16dkyVtglp4GV6BeUQ=="], - - "@trycompai/app/@ai-sdk/openai": ["@ai-sdk/openai@3.0.60", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-vZKqbDSCF1T65gDMG2ILJ2NAEpG45U2VtvEve1Fv/WRLu2i5TPUqxjlGKm+5a7Dd57Yr6CGePxrJhsQJC8LJ3A=="], + "@trycompai/api/ai": ["ai@6.0.175", "", { "dependencies": { "@ai-sdk/gateway": "3.0.110", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6fFFHzbh6FIZnYc31V6osOxq25ABJYCShfG0O6ajHiA4FB/DgnPi1mP8cO5aAU3HNSbQHiMazdlh9bIsp97mVA=="], "@trycompai/app/@mendable/firecrawl-js": ["@mendable/firecrawl-js@1.29.3", "", { "dependencies": { "axios": "^1.11.0", "typescript-event-target": "^1.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.0" } }, "sha512-+uvDktesJmVtiwxMtimq+3f5bKlsan4T7TokxOI7DbxFkApwrRNss5GYEXbInveMTz8LpGth/9Ch5BTwCqrpfA=="], @@ -7305,8 +7317,12 @@ "@trycompai/framework-editor-cli/@types/node": ["@types/node@22.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q=="], + "@trycompai/integrations/ai": ["ai@6.0.175", "", { "dependencies": { "@ai-sdk/gateway": "3.0.110", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6fFFHzbh6FIZnYc31V6osOxq25ABJYCShfG0O6ajHiA4FB/DgnPi1mP8cO5aAU3HNSbQHiMazdlh9bIsp97mVA=="], + "@trycompai/portal/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@trycompai/ui/ai": ["ai@6.0.175", "", { "dependencies": { "@ai-sdk/gateway": "3.0.110", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6fFFHzbh6FIZnYc31V6osOxq25ABJYCShfG0O6ajHiA4FB/DgnPi1mP8cO5aAU3HNSbQHiMazdlh9bIsp97mVA=="], + "@trycompai/ui/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], "@ts-morph/common/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], @@ -7323,6 +7339,8 @@ "@uploadthing/shared/effect": ["effect@3.17.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-dpt0ONUn3zzAuul6k4nC/coTTw27AL5nhkORXgTi6NfMPzqWYa1M05oKmOMTxpVSTKepqXVcW9vIwkuaaqx9zA=="], + "@vercel/sandbox/@vercel/oidc": ["@vercel/oidc@2.0.2", "", { "dependencies": { "@types/ms": "2.1.0", "ms": "2.1.3" } }, "sha512-59PBFx3T+k5hLTEWa3ggiMpGRz1OVvl9eN8SUai+A43IsqiOuAe7qPBf+cray/Fj6mkgnxm/D7IAtjc8zSHi7g=="], + "@vercel/sandbox/tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], "@vercel/sandbox/zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="], @@ -7333,6 +7351,8 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.86", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@ai-sdk/provider-utils": "3.0.25", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-pP9F5G7C5sqZtAwquFB+g50lVS/s4Wf/ll2WSm0ODk0Iix27trVgBDpFK5CBletcQXSDlAvSQQi25nBodYLt3g=="], + "ai/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], @@ -8431,13 +8451,19 @@ "zod-error/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@ai-sdk/react/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ai-sdk/react/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sbv8+1L9/BRKydn8dMNwoMQKupA4iLJ9N+yvxgW6wMQ/94UepDf3FeYWMj/dLdzolAHZ6izRUP4s5WqQkmJ2Zg=="], + "@ai-sdk/gateway/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ai-sdk/rsc/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ai-sdk/rsc/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sbv8+1L9/BRKydn8dMNwoMQKupA4iLJ9N+yvxgW6wMQ/94UepDf3FeYWMj/dLdzolAHZ6izRUP4s5WqQkmJ2Zg=="], + "@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/react/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@ai-sdk/rsc/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], @@ -8871,16 +8897,10 @@ "@trigger.dev/core/socket.io/engine.io": ["engine.io@6.5.5", "", { "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1" } }, "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA=="], - "@trycompai/app/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], - - "@trycompai/app/@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], - - "@trycompai/app/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], + "@trycompai/api/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], "@trycompai/app/@mendable/firecrawl-js/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@trycompai/app/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.110", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.26", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-sbv8+1L9/BRKydn8dMNwoMQKupA4iLJ9N+yvxgW6wMQ/94UepDf3FeYWMj/dLdzolAHZ6izRUP4s5WqQkmJ2Zg=="], - "@trycompai/app/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], "@trycompai/app/resend/@react-email/render": ["@react-email/render@1.1.2", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3", "react-promise-suspense": "^0.3.4" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw=="], @@ -8893,6 +8913,10 @@ "@trycompai/framework-editor/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "@trycompai/integrations/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], + + "@trycompai/ui/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ=="], + "@trycompai/ui/shiki/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], "@trycompai/ui/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], @@ -8913,6 +8937,8 @@ "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "app-builder-lib/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], @@ -9539,10 +9565,6 @@ "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@ai-sdk/react/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], - - "@ai-sdk/rsc/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], - "@angular-devkit/schematics/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], "@angular-devkit/schematics/ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -9755,15 +9777,13 @@ "@trigger.dev/core/socket.io/engine.io/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], - "@trycompai/app/@ai-sdk/anthropic/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@trycompai/api/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@trycompai/app/@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@trycompai/app/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@trycompai/app/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@trycompai/app/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="], + "@trycompai/integrations/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@trycompai/app/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@trycompai/ui/ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/packages/integrations/package.json b/packages/integrations/package.json index cb36cf72e1..b8e4ba161d 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -2,12 +2,12 @@ "name": "@trycompai/integrations", "version": "1.0.0", "dependencies": { - "@ai-sdk/openai": "^2.0.0", + "@ai-sdk/openai": "^3.0.62", "@aws-sdk/client-securityhub": "^3.0.0", "@aws-sdk/client-sts": "^3.0.0", "@azure/identity": "^4.10.0", "@trycompai/app": "workspace:*", - "ai": "^5.0.0", + "ai": "^6.0.175", "jsonwebtoken": "^9.0.2", "node-fetch": "^2.6.7", "react": "^19.0.0", diff --git a/packages/ui/package.json b/packages/ui/package.json index 811106279a..0b9da0cfd6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -8,6 +8,7 @@ "tailwind-merge" ], "dependencies": { + "@floating-ui/dom": "^1.6.0", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-avatar": "1.1.10", @@ -36,7 +37,6 @@ "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-controllable-state": "^1.2.2", "@radix-ui/react-use-is-hydrated": "^0.1.0", - "@floating-ui/dom": "^1.6.0", "@tailwindcss/typography": "^0.5.16", "@tiptap/extension-bold": "3.22.1", "@tiptap/extension-code-block-lowlight": "3.22.1", @@ -54,7 +54,7 @@ "@tiptap/suggestion": "3.22.1", "@uidotdev/usehooks": "^2.4.1", "@xyflow/react": "^12.9.3", - "ai": "^5.0.101", + "ai": "^6.0.175", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1",