Compare commits

..

6 Commits

Author SHA1 Message Date
waleed
91ec8ef2a6 fix(kbtags): added time to date tag, improved ui ux throughout the kb 2026-01-30 20:37:48 -08:00
Vikhyath Mondreti
cf2f1abcaf fix(executor): condition inside parallel (#3094)
* fix(executor): condition inside parallel

* remove comments
2026-01-30 18:47:39 -08:00
Waleed
4109feecf6 feat(invitations): added invitations query hook, migrated all tool files to use absolute imports (#3092)
* feat(invitations): added invitations query hook, migrated all tool files to use absolute imports

* ack PR comments

* remove dead import

* remove unused hook
2026-01-30 18:39:23 -08:00
Waleed
37d5e01f5f fix(mcp): increase timeout from 1m to 10m (#3093) 2026-01-30 17:51:05 -08:00
Vikhyath Mondreti
2d799b3272 fix(billing): plan should be detected from stripe subscription object (#3090)
* fix(billing): plan should be detected from stripe subscription object

* fix typing
2026-01-30 17:01:16 -08:00
Waleed
92403e0594 fix(editor): advanced toggle respects user edit permissions (#3089) 2026-01-30 15:22:46 -08:00
556 changed files with 3229 additions and 1400 deletions

View File

@@ -39,8 +39,18 @@ export function SocialLoginButtons({
setIsGithubLoading(true)
try {
await client.signIn.social({ provider: 'github', callbackURL })
} catch (_err: unknown) {
// Error handling is done silently - user will see the OAuth error page if needed
} catch (err: any) {
let errorMessage = 'Failed to sign in with GitHub'
if (err.message?.includes('account exists')) {
errorMessage = 'An account with this email already exists. Please sign in instead.'
} else if (err.message?.includes('cancelled')) {
errorMessage = 'GitHub sign in was cancelled. Please try again.'
} else if (err.message?.includes('network')) {
errorMessage = 'Network error. Please check your connection and try again.'
} else if (err.message?.includes('rate limit')) {
errorMessage = 'Too many attempts. Please try again later.'
}
} finally {
setIsGithubLoading(false)
}
@@ -52,8 +62,18 @@ export function SocialLoginButtons({
setIsGoogleLoading(true)
try {
await client.signIn.social({ provider: 'google', callbackURL })
} catch (_err: unknown) {
// Error handling is done silently - user will see the OAuth error page if needed
} catch (err: any) {
let errorMessage = 'Failed to sign in with Google'
if (err.message?.includes('account exists')) {
errorMessage = 'An account with this email already exists. Please sign in instead.'
} else if (err.message?.includes('cancelled')) {
errorMessage = 'Google sign in was cancelled. Please try again.'
} else if (err.message?.includes('network')) {
errorMessage = 'Network error. Please check your connection and try again.'
} else if (err.message?.includes('rate limit')) {
errorMessage = 'Too many attempts. Please try again later.'
}
} finally {
setIsGoogleLoading(false)
}

View File

@@ -158,7 +158,7 @@ export default function LoginPage({
return () => {
window.removeEventListener('keydown', handleKeyDown)
}
}, [forgotPasswordOpen])
}, [forgotPasswordEmail, forgotPasswordOpen])
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newEmail = e.target.value

View File

@@ -3,7 +3,7 @@
import { useEffect, useState } from 'react'
import { createLogger } from '@sim/logger'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import { useRouter, useSearchParams } from 'next/navigation'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
@@ -52,6 +52,7 @@ const validateCallbackUrl = (url: string): boolean => {
}
export default function SSOForm() {
const router = useRouter()
const searchParams = useSearchParams()
const [isLoading, setIsLoading] = useState(false)
const [email, setEmail] = useState('')

View File

@@ -196,7 +196,7 @@ export function useVerification({
return () => clearTimeout(timeoutId)
}
}, [otp, email, isLoading, isVerified, verifyCode])
}, [otp, email, isLoading, isVerified])
useEffect(() => {
if (typeof window !== 'undefined') {
@@ -220,7 +220,7 @@ export function useVerification({
handleRedirect()
}
}
}, [isEmailVerificationEnabled, router, isInviteFlow, redirectUrl, refetchSession])
}, [isEmailVerificationEnabled, router, isInviteFlow, redirectUrl])
return {
otp,

View File

@@ -118,7 +118,7 @@ export function DotPattern({
<stop offset='100%' stopColor='currentColor' stopOpacity='0' />
</radialGradient>
</defs>
{dots.map((dot, _index) => (
{dots.map((dot, index) => (
<circle
key={`${dot.x}-${dot.y}`}
cx={dot.x}

View File

@@ -106,8 +106,9 @@ export function LandingFlow({
proOptions={{ hideAttribution: true }}
fitView={false}
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
onInit={() => {
onInit={(instance) => {
setRfReady(true)
// Expose limited viewport API for outer timeline to pan smoothly
viewportApiRef.current = {
panTo: (x: number, y: number, options?: { duration?: number }) => {
setViewport({ x, y, zoom: 1 }, { duration: options?.duration ?? 0 })

View File

@@ -140,7 +140,7 @@ export default function Hero() {
*/
const [rfNodes, setRfNodes] = React.useState<Node[]>([])
const [rfEdges, setRfEdges] = React.useState<Edge[]>([])
const [groupBox] = React.useState<LandingGroupData | null>(null)
const [groupBox, setGroupBox] = React.useState<LandingGroupData | null>(null)
const [worldWidth, setWorldWidth] = React.useState<number>(1000)
const viewportApiRef = React.useRef<LandingViewportApi | null>(null)

View File

@@ -149,7 +149,7 @@ export function extractAgentContent(executeResult: {
if (typeof executeResult.output === 'object' && executeResult.output !== null) {
const keys = Object.keys(executeResult.output)
// Skip empty objects or objects with only undefined values
if (keys.length > 0 && keys.some((k) => executeResult.output?.[k] !== undefined)) {
if (keys.length > 0 && keys.some((k) => executeResult.output![k] !== undefined)) {
return JSON.stringify(executeResult.output)
}
}

View File

@@ -19,7 +19,7 @@ interface GoogleIdToken {
/**
* Get all OAuth connections for the current user
*/
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
try {

View File

@@ -177,6 +177,7 @@ describe('OAuth Token API Routes', () => {
const { POST } = await import('@/app/api/auth/oauth/token/route')
const response = await POST(req)
const data = await response.json()
expect(response.status).toBe(403)
})

View File

@@ -1,4 +1,4 @@
import crypto from 'node:crypto'
import crypto from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'

View File

@@ -8,7 +8,7 @@ const logger = createLogger('TrelloAuthorize')
export const dynamic = 'force-dynamic'
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const session = await getSession()
if (!session?.user?.id) {

View File

@@ -3,7 +3,7 @@ import { getBaseUrl } from '@/lib/core/utils/urls'
export const dynamic = 'force-dynamic'
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
const baseUrl = getBaseUrl()
return new NextResponse(

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { chat, verification } from '@sim/db/schema'
import { createLogger } from '@sim/logger'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { chat, workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'

View File

@@ -42,7 +42,7 @@ const chatSchema = z.object({
.default([]),
})
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const session = await getSession()

View File

@@ -61,7 +61,7 @@ export async function POST(req: NextRequest) {
{ success: true, key: { id: data?.id || 'new', apiKey: data.apiKey } },
{ status: 201 }
)
} catch (_error) {
} catch (error) {
return NextResponse.json({ error: 'Failed to generate copilot API key' }, { status: 500 })
}
}

View File

@@ -3,7 +3,7 @@ import { getSession } from '@/lib/auth'
import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/copilot/constants'
import { env } from '@/lib/core/config/env'
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const session = await getSession()
if (!session?.user?.id) {
@@ -49,7 +49,7 @@ export async function GET(_request: NextRequest) {
})
return NextResponse.json({ keys }, { status: 200 })
} catch (_error) {
} catch (error) {
return NextResponse.json({ error: 'Failed to get keys' }, { status: 500 })
}
}
@@ -89,7 +89,7 @@ export async function DELETE(request: NextRequest) {
}
return NextResponse.json({ success: true }, { status: 200 })
} catch (_error) {
} catch (error) {
return NextResponse.json({ error: 'Failed to delete key' }, { status: 500 })
}
}

View File

@@ -494,6 +494,20 @@ export async function POST(req: NextRequest) {
// If streaming is requested, forward the stream and update chat later
if (stream && simAgentResponse.body) {
// Create user message to save
const userMessage = {
id: userMessageIdToUse, // Consistent ID used for request and persistence
role: 'user',
content: message,
timestamp: new Date().toISOString(),
...(fileAttachments && fileAttachments.length > 0 && { fileAttachments }),
...(Array.isArray(contexts) && contexts.length > 0 && { contexts }),
...(Array.isArray(contexts) &&
contexts.length > 0 && {
contentBlocks: [{ type: 'contexts', contexts: contexts as any, timestamp: Date.now() }],
}),
}
// Create a pass-through stream that captures the response
const transformedStream = new ReadableStream({
async start(controller) {
@@ -501,11 +515,14 @@ export async function POST(req: NextRequest) {
let assistantContent = ''
const toolCalls: any[] = []
let buffer = ''
const isFirstDone = true
let responseIdFromStart: string | undefined
let responseIdFromDone: string | undefined
// Track tool call progress to identify a safe done event
const announcedToolCallIds = new Set<string>()
const startedToolExecutionIds = new Set<string>()
const completedToolExecutionIds = new Set<string>()
let lastDoneResponseId: string | undefined
let lastSafeDoneResponseId: string | undefined
// Send chatId as first event
@@ -547,15 +564,9 @@ export async function POST(req: NextRequest) {
}
// Forward the sim agent stream and capture assistant response
const reader = simAgentResponse.body?.getReader()
const reader = simAgentResponse.body!.getReader()
const decoder = new TextDecoder()
if (!reader) {
logger.error(`[${tracker.requestId}] Failed to get reader from response body`)
controller.close()
return
}
try {
while (true) {
const { done, value } = await reader.read()
@@ -636,11 +647,15 @@ export async function POST(req: NextRequest) {
break
case 'start':
if (event.data?.responseId) {
responseIdFromStart = event.data.responseId
}
break
case 'done':
if (event.data?.responseId) {
responseIdFromDone = event.data.responseId
lastDoneResponseId = responseIdFromDone
// Mark this done as safe only if no tool call is currently in progress or pending
const announced = announcedToolCallIds.size
@@ -675,7 +690,7 @@ export async function POST(req: NextRequest) {
`data: ${JSON.stringify({ type: 'content', data: formatted })}\n\n`
)
)
} catch (_enqueueErr) {
} catch (enqueueErr) {
reader.cancel()
break
}
@@ -684,7 +699,7 @@ export async function POST(req: NextRequest) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\n\n`)
)
} catch (_enqueueErr) {
} catch (enqueueErr) {
reader.cancel()
break
}
@@ -694,7 +709,7 @@ export async function POST(req: NextRequest) {
// Forward original event to client
try {
controller.enqueue(encoder.encode(`data: ${jsonStr}\n\n`))
} catch (_enqueueErr) {
} catch (enqueueErr) {
reader.cancel()
break
}
@@ -752,17 +767,17 @@ export async function POST(req: NextRequest) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\n\n`)
)
} catch (_enqueueErr) {
} catch (enqueueErr) {
reader.cancel()
}
} else {
try {
controller.enqueue(encoder.encode(`data: ${jsonStr}\n\n`))
} catch (_enqueueErr) {
} catch (enqueueErr) {
reader.cancel()
}
}
} catch (_e) {
} catch (e) {
logger.warn(`[${tracker.requestId}] Failed to parse final buffer: "${buffer}"`)
}
}

View File

@@ -113,7 +113,7 @@ export async function POST(request: NextRequest) {
)
}
const _result = await stateResponse.json()
const result = await stateResponse.json()
logger.info(
`[${tracker.requestId}] Successfully reverted workflow ${checkpoint.workflowId} to checkpoint ${checkpointId}`
)

View File

@@ -63,7 +63,7 @@ export async function POST(req: NextRequest) {
let parsedWorkflowState
try {
parsedWorkflowState = JSON.parse(workflowState)
} catch (_error) {
} catch (error) {
return createBadRequestResponse('Invalid workflow state JSON')
}

View File

@@ -121,6 +121,8 @@ export async function POST(req: NextRequest) {
return createBadRequestResponse('Failed to update tool call status or tool call not found')
}
const duration = tracker.getDuration()
return NextResponse.json({
success: true,
message: message || `Tool call ${toolCallId} has been ${status.toLowerCase()}`,

View File

@@ -111,7 +111,7 @@ export async function POST(req: NextRequest) {
* GET /api/copilot/feedback
* Get all feedback records (for analytics)
*/
export async function GET(_req: NextRequest) {
export async function GET(req: NextRequest) {
const tracker = createRequestTracker()
try {

View File

@@ -5,6 +5,7 @@ import {
authenticateCopilotRequestSessionOnly,
createBadRequestResponse,
createInternalServerErrorResponse,
createRequestTracker,
createUnauthorizedResponse,
} from '@/lib/copilot/request-helpers'
import { env } from '@/lib/core/config/env'
@@ -18,6 +19,7 @@ const BodySchema = z.object({
})
export async function POST(req: NextRequest) {
const tracker = createRequestTracker()
try {
const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly()
if (!isAuthenticated || !userId) {
@@ -60,7 +62,7 @@ export async function POST(req: NextRequest) {
}
return NextResponse.json({ success: true })
} catch (_error) {
} catch (error) {
return createInternalServerErrorResponse('Failed to forward copilot stats')
}
}

View File

@@ -34,7 +34,7 @@ const DEFAULT_ENABLED_MODELS: Record<CopilotModelId, boolean> = {
}
// GET - Fetch user's enabled models
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const session = await getSession()

View File

@@ -46,7 +46,7 @@ async function hasPermission(userId: string, profile: any): Promise<boolean> {
}
// GET /api/creators/[id] - Get a specific creator profile
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -137,7 +137,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
// DELETE /api/creators/[id] - Delete a creator profile
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()

View File

@@ -12,7 +12,7 @@ const logger = createLogger('CreatorVerificationAPI')
export const revalidate = 0
// POST /api/creators/[id]/verify - Verify a creator (super users only)
export async function POST(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -62,7 +62,7 @@ export async function POST(_request: NextRequest, { params }: { params: Promise<
// DELETE /api/creators/[id]/verify - Unverify a creator (super users only)
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()

View File

@@ -30,6 +30,8 @@ const CreateCreatorProfileSchema = z.object({
// GET /api/creators - Get creator profiles for current user
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
try {
const session = await getSession()

View File

@@ -37,7 +37,7 @@ async function getCredentialSetWithAccess(credentialSetId: string, userId: strin
}
export async function POST(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; invitationId: string }> }
) {
const session = await getSession()

View File

@@ -41,7 +41,7 @@ async function getCredentialSetWithAccess(credentialSetId: string, userId: strin
return { set, role: membership.role }
}
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {

View File

@@ -33,7 +33,7 @@ async function getCredentialSetWithAccess(credentialSetId: string, userId: strin
return { set, role: membership.role }
}
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {

View File

@@ -43,7 +43,7 @@ async function getCredentialSetWithAccess(credentialSetId: string, userId: strin
return { set, role: membership.role }
}
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {
@@ -141,7 +141,7 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
}
}
export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {

View File

@@ -13,7 +13,7 @@ import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
const logger = createLogger('CredentialSetInviteToken')
export async function GET(_req: NextRequest, { params }: { params: Promise<{ token: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ token: string }> }) {
const { token } = await params
const [invitation] = await db
@@ -61,7 +61,7 @@ export async function GET(_req: NextRequest, { params }: { params: Promise<{ tok
})
}
export async function POST(_req: NextRequest, { params }: { params: Promise<{ token: string }> }) {
export async function POST(req: NextRequest, { params }: { params: Promise<{ token: string }> }) {
const { token } = await params
const session = await getSession()

View File

@@ -72,7 +72,7 @@ export async function POST(req: NextRequest) {
}
}
export async function GET(_request: Request) {
export async function GET(request: Request) {
const requestId = generateRequestId()
try {

View File

@@ -162,7 +162,7 @@ async function verifyWorkspaceFileAccess(
cloudKey: string,
userId: string,
customConfig?: StorageConfig,
_isLocal?: boolean
isLocal?: boolean
): Promise<boolean> {
try {
// Priority 1: Check database (most reliable, works for both local and cloud)
@@ -228,7 +228,7 @@ async function verifyWorkspaceFileAccess(
async function verifyExecutionFileAccess(
cloudKey: string,
userId: string,
_customConfig?: StorageConfig
customConfig?: StorageConfig
): Promise<boolean> {
const parts = cloudKey.split('/')
@@ -493,7 +493,7 @@ async function verifyRegularFileAccess(
cloudKey: string,
userId: string,
customConfig?: StorageConfig,
_isLocal?: boolean
isLocal?: boolean
): Promise<boolean> {
try {
// Priority 1: Check if this might be a workspace file (check database)

View File

@@ -1,4 +1,4 @@
import path from 'node:path'
import path from 'path'
/**
* Tests for file parse API route
*

View File

@@ -1,7 +1,7 @@
import { Buffer } from 'node:buffer'
import { createHash } from 'node:crypto'
import fsPromises, { readFile } from 'node:fs/promises'
import path from 'node:path'
import { Buffer } from 'buffer'
import { createHash } from 'crypto'
import fsPromises, { readFile } from 'fs/promises'
import path from 'path'
import { createLogger } from '@sim/logger'
import binaryExtensionsList from 'binary-extensions'
import { type NextRequest, NextResponse } from 'next/server'
@@ -899,7 +899,7 @@ Please use a PDF viewer for best results.`
* Create error message for PDF parsing failure and make it more readable
*/
function createPdfFailureMessage(
_pageCount: number,
pageCount: number,
size: number,
path: string,
error: string

View File

@@ -131,7 +131,7 @@ describe('File Serve API Route', () => {
expect(disposition).toContain('filename=')
expect(disposition).toContain('test-file.txt')
const fs = await import('node:fs/promises')
const fs = await import('fs/promises')
expect(fs.readFile).toHaveBeenCalled()
})
@@ -196,7 +196,7 @@ describe('File Serve API Route', () => {
expect(response.status).toBe(200)
const fs = await import('node:fs/promises')
const fs = await import('fs/promises')
expect(fs.readFile).toHaveBeenCalledWith('/test/uploads/nested/path/file.txt')
})

View File

@@ -1,4 +1,4 @@
import { readFile } from 'node:fs/promises'
import { readFile } from 'fs/promises'
import { createLogger } from '@sim/logger'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'

View File

@@ -1,5 +1,5 @@
import { existsSync } from 'node:fs'
import { join, resolve, sep } from 'node:path'
import { existsSync } from 'fs'
import { join, resolve, sep } from 'path'
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { UPLOAD_DIR } from '@/lib/uploads/config'
@@ -119,7 +119,7 @@ export function extractFilename(path: string): string {
return filename
}
function _sanitizeFilename(filename: string): string {
function sanitizeFilename(filename: string): string {
if (!filename || typeof filename !== 'string') {
throw new Error('Invalid filename provided')
}

View File

@@ -321,7 +321,7 @@ describe('Individual Folder API Route', () => {
await PUT(req, { params })
expect(capturedUpdates).not.toBeNull()
expect(capturedUpdates?.name).toBe('Folder With Spaces')
expect(capturedUpdates!.name).toBe('Folder With Spaces')
})
it('should handle database errors gracefully', async () => {

View File

@@ -106,7 +106,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
// DELETE - Delete a folder and all its contents
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {

View File

@@ -551,7 +551,7 @@ describe('Folders API Route', () => {
await POST(req)
expect(capturedValues).not.toBeNull()
expect(capturedValues?.name).toBe('Test Folder With Spaces')
expect(capturedValues!.name).toBe('Test Folder With Spaces')
})
it('should use default color when not provided', async () => {
@@ -591,7 +591,7 @@ describe('Folders API Route', () => {
await POST(req)
expect(capturedValues).not.toBeNull()
expect(capturedValues?.color).toBe('#6B7280')
expect(capturedValues!.color).toBe('#6B7280')
})
})
})

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { form, workflow, workflowBlocks } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
@@ -235,11 +235,28 @@ export async function POST(
// For forms, we don't stream back - we wait for completion and return success
// Consume the stream to wait for completion
const reader = stream.getReader()
let lastOutput: any = null
try {
while (true) {
const { done } = await reader.read()
const { done, value } = await reader.read()
if (done) break
// Parse SSE data if present
const text = new TextDecoder().decode(value)
const lines = text.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.slice(6))
if (data.type === 'complete' || data.output) {
lastOutput = data.output || data
}
} catch {
// Ignore parse errors
}
}
}
}
} finally {
reader.releaseLock()

View File

@@ -62,7 +62,7 @@ const updateFormSchema = z.object({
isActive: z.boolean().optional(),
})
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
const session = await getSession()
@@ -201,7 +201,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
}
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {

View File

@@ -64,7 +64,7 @@ const formSchema = z.object({
showBranding: z.boolean().optional().default(true),
})
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const session = await getSession()

View File

@@ -63,7 +63,7 @@ describe('Form API Utils', () => {
it.concurrent('should validate tokens with password hash', async () => {
const { validateAuthToken } = await import('@/lib/core/security/deployment')
const crypto = await import('node:crypto')
const crypto = await import('crypto')
const formId = 'test-form-id'
const encryptedPassword = 'encrypted-password-value'

View File

@@ -292,7 +292,7 @@ function formatE2BError(
*/
function createUserFriendlyErrorMessage(
enhanced: EnhancedError,
_requestId: string,
requestId: string,
userCode?: string
): string {
let errorMessage = enhanced.message

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
@@ -14,7 +14,7 @@ const UpdateChunkSchema = z.object({
})
export async function GET(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; documentId: string; chunkId: string }> }
) {
const requestId = randomUUID().slice(0, 8)
@@ -134,7 +134,7 @@ export async function PUT(
}
export async function DELETE(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; documentId: string; chunkId: string }> }
) {
const requestId = randomUUID().slice(0, 8)

View File

@@ -47,7 +47,7 @@ const UpdateDocumentSchema = z.object({
})
export async function GET(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; documentId: string }> }
) {
const requestId = generateRequestId()
@@ -123,6 +123,8 @@ export async function PUT(
try {
const validatedData = UpdateDocumentSchema.parse(body)
const updateData: any = {}
if (validatedData.markFailedDueToTimeout) {
const doc = accessCheck.document
@@ -218,7 +220,7 @@ export async function PUT(
}
export async function DELETE(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; documentId: string }> }
) {
const requestId = generateRequestId()

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
@@ -31,7 +31,7 @@ const BulkTagDefinitionsSchema = z.object({
// GET /api/knowledge/[id]/documents/[documentId]/tag-definitions - Get tag definitions for a document
export async function GET(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; documentId: string }> }
) {
const requestId = randomUUID().slice(0, 8)

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
@@ -11,7 +11,7 @@ const logger = createLogger('TagDefinitionAPI')
// DELETE /api/knowledge/[id]/tag-definitions/[tagId] - Delete a tag definition
export async function DELETE(
_req: NextRequest,
req: NextRequest,
{ params }: { params: Promise<{ id: string; tagId: string }> }
) {
const requestId = randomUUID().slice(0, 8)

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
@@ -10,7 +10,7 @@ export const dynamic = 'force-dynamic'
const logger = createLogger('TagUsageAPI')
// GET /api/knowledge/[id]/tag-usage - Get usage statistics for all tag definitions
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = randomUUID().slice(0, 8)
const { id: knowledgeBaseId } = await params

View File

@@ -202,7 +202,6 @@ describe('Knowledge Search Utils', () => {
)
expect(result).toEqual([0.1, 0.2, 0.3])
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -233,7 +232,6 @@ describe('Knowledge Search Utils', () => {
)
expect(result).toEqual([0.1, 0.2, 0.3])
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -262,7 +260,6 @@ describe('Knowledge Search Utils', () => {
expect.any(Object)
)
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -292,7 +289,6 @@ describe('Knowledge Search Utils', () => {
expect.any(Object)
)
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -325,7 +321,6 @@ describe('Knowledge Search Utils', () => {
await expect(generateSearchEmbedding('test query')).rejects.toThrow('Embedding API failed')
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -346,7 +341,6 @@ describe('Knowledge Search Utils', () => {
await expect(generateSearchEmbedding('test query')).rejects.toThrow('Embedding API failed')
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -380,7 +374,6 @@ describe('Knowledge Search Utils', () => {
})
)
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
@@ -413,7 +406,6 @@ describe('Knowledge Search Utils', () => {
})
)
// Clean up
Object.keys(env).forEach((key) => delete (env as any)[key])
})
})
@@ -427,4 +419,97 @@ describe('Knowledge Search Utils', () => {
expect(result).toEqual({})
})
})
describe('Date Filter Format Handling', () => {
it('should accept date-only format (YYYY-MM-DD) in structured filters', () => {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator: 'eq',
value: '2024-01-15',
}
expect(filter.value).toMatch(/^\d{4}-\d{2}-\d{2}$/)
expect(filter.fieldType).toBe('date')
})
it('should accept ISO 8601 timestamp format in structured filters', () => {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator: 'eq',
value: '2024-01-15T14:30:00',
}
expect(filter.value).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
expect(filter.fieldType).toBe('date')
})
it('should accept ISO 8601 timestamp with UTC timezone in structured filters', () => {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator: 'gte',
value: '2024-01-15T14:30:00Z',
}
expect(filter.value).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)
expect(filter.fieldType).toBe('date')
})
it('should accept ISO 8601 timestamp with timezone offset in structured filters', () => {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator: 'lt',
value: '2024-01-15T14:30:00+05:00',
}
expect(filter.value).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/)
expect(filter.fieldType).toBe('date')
})
it('should support all date comparison operators', () => {
const operators = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between']
const validDateValue = '2024-01-15'
for (const operator of operators) {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator,
value: validDateValue,
}
expect(filter.operator).toBe(operator)
}
})
it('should support between operator with date range', () => {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator: 'between',
value: '2024-01-01',
valueTo: '2024-12-31',
}
expect(filter.operator).toBe('between')
expect(filter.value).toBe('2024-01-01')
expect(filter.valueTo).toBe('2024-12-31')
})
it('should support between operator with timestamp range', () => {
const filter = {
tagSlot: 'date1',
fieldType: 'date',
operator: 'between',
value: '2024-01-01T00:00:00',
valueTo: '2024-12-31T23:59:59',
}
expect(filter.operator).toBe('between')
expect(filter.value).toMatch(/T\d{2}:\d{2}:\d{2}$/)
expect(filter.valueTo).toMatch(/T\d{2}:\d{2}:\d{2}$/)
})
})
})

View File

@@ -203,39 +203,74 @@ function buildFilterCondition(filter: StructuredFilter, embeddingTable: any) {
}
}
// Handle date operators - expects YYYY-MM-DD format from frontend
// Handle date operators - accepts YYYY-MM-DD or ISO 8601 timestamp
if (fieldType === 'date') {
const dateStr = String(value)
// Validate YYYY-MM-DD format
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
logger.debug(`[getStructuredTagFilters] Invalid date format: ${dateStr}, expected YYYY-MM-DD`)
const dateOnlyRegex = /^\d{4}-\d{2}-\d{2}$/
const datetimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/
// Validate format - accept date-only or timestamp
const isDateOnly = dateOnlyRegex.test(dateStr)
const isTimestamp = datetimeRegex.test(dateStr)
if (!isDateOnly && !isTimestamp) {
logger.debug(
`[getStructuredTagFilters] Invalid date format: ${dateStr}, expected YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss`
)
return null
}
// Use date comparison for date-only values, timestamp comparison for timestamps
const castType = isDateOnly ? '::date' : '::timestamp'
switch (operator) {
case 'eq':
return sql`${column}::date = ${dateStr}::date`
return isDateOnly
? sql`${column}::date = ${dateStr}::date`
: sql`${column}::timestamp = ${dateStr}::timestamp`
case 'neq':
return sql`${column}::date != ${dateStr}::date`
return isDateOnly
? sql`${column}::date != ${dateStr}::date`
: sql`${column}::timestamp != ${dateStr}::timestamp`
case 'gt':
return sql`${column}::date > ${dateStr}::date`
return isDateOnly
? sql`${column}::date > ${dateStr}::date`
: sql`${column}::timestamp > ${dateStr}::timestamp`
case 'gte':
return sql`${column}::date >= ${dateStr}::date`
return isDateOnly
? sql`${column}::date >= ${dateStr}::date`
: sql`${column}::timestamp >= ${dateStr}::timestamp`
case 'lt':
return sql`${column}::date < ${dateStr}::date`
return isDateOnly
? sql`${column}::date < ${dateStr}::date`
: sql`${column}::timestamp < ${dateStr}::timestamp`
case 'lte':
return sql`${column}::date <= ${dateStr}::date`
return isDateOnly
? sql`${column}::date <= ${dateStr}::date`
: sql`${column}::timestamp <= ${dateStr}::timestamp`
case 'between':
if (valueTo !== undefined) {
const dateStrTo = String(valueTo)
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStrTo)) {
return sql`${column}::date = ${dateStr}::date`
const isToDateOnly = dateOnlyRegex.test(dateStrTo)
const isToTimestamp = datetimeRegex.test(dateStrTo)
if (!isToDateOnly && !isToTimestamp) {
return isDateOnly
? sql`${column}::date = ${dateStr}::date`
: sql`${column}::timestamp = ${dateStr}::timestamp`
}
return sql`${column}::date >= ${dateStr}::date AND ${column}::date <= ${dateStrTo}::date`
// Use date comparison if both are date-only, otherwise use timestamp
if (isDateOnly && isToDateOnly) {
return sql`${column}::date >= ${dateStr}::date AND ${column}::date <= ${dateStrTo}::date`
}
return sql`${column}::timestamp >= ${dateStr}::timestamp AND ${column}::timestamp <= ${dateStrTo}::timestamp`
}
return sql`${column}::date = ${dateStr}::date`
return isDateOnly
? sql`${column}::date = ${dateStr}::date`
: sql`${column}::timestamp = ${dateStr}::timestamp`
default:
return sql`${column}::date = ${dateStr}::date`
return isDateOnly
? sql`${column}::date = ${dateStr}::date`
: sql`${column}::timestamp = ${dateStr}::timestamp`
}
}
@@ -269,7 +304,7 @@ function getStructuredTagFilters(filters: StructuredFilter[], embeddingTable: an
if (!filtersBySlot.has(slot)) {
filtersBySlot.set(slot, [])
}
filtersBySlot.get(slot)?.push(filter)
filtersBySlot.get(slot)!.push(filter)
}
// Build conditions: OR within same slot, AND across different slots

View File

@@ -63,7 +63,7 @@ async function getServer(serverId: string) {
return server
}
export async function GET(_request: NextRequest, { params }: { params: Promise<RouteParams> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<RouteParams> }) {
const { serverId } = await params
try {
@@ -264,7 +264,7 @@ async function handleToolsCall(
method: 'POST',
headers,
body: JSON.stringify({ input: params.arguments || {}, triggerType: 'mcp' }),
signal: AbortSignal.timeout(300000), // 5 minute timeout
signal: AbortSignal.timeout(600000), // 10 minute timeout
})
const executeResult = await response.json()

View File

@@ -140,7 +140,7 @@ async function syncToolSchemasToWorkflows(
}
export const POST = withMcpAuth<{ id: string }>('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
async (request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
const { id: serverId } = await params
try {

View File

@@ -19,7 +19,7 @@ export const dynamic = 'force-dynamic'
* GET - List all registered MCP servers for the workspace
*/
export const GET = withMcpAuth('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }) => {
async (request: NextRequest, { userId, workspaceId, requestId }) => {
try {
logger.info(`[${requestId}] Listing MCP servers for workspace ${workspaceId}`)

View File

@@ -12,7 +12,7 @@ const logger = createLogger('McpStoredToolsAPI')
export const dynamic = 'force-dynamic'
export const GET = withMcpAuth('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }) => {
async (request: NextRequest, { userId, workspaceId, requestId }) => {
try {
logger.info(`[${requestId}] Fetching stored MCP tools for workspace ${workspaceId}`)

View File

@@ -18,7 +18,7 @@ interface RouteParams {
* GET - Get a specific workflow MCP server with its tools
*/
export const GET = withMcpAuth<RouteParams>('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
async (request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
try {
const { id: serverId } = await params
@@ -127,7 +127,7 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
* DELETE - Delete a workflow MCP server and all its tools
*/
export const DELETE = withMcpAuth<RouteParams>('admin')(
async (_request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
async (request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
try {
const { id: serverId } = await params

View File

@@ -20,7 +20,7 @@ interface RouteParams {
* GET - Get a specific tool
*/
export const GET = withMcpAuth<RouteParams>('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
async (request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
try {
const { id: serverId, toolId } = await params
@@ -131,7 +131,7 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
* DELETE - Remove a tool from an MCP server
*/
export const DELETE = withMcpAuth<RouteParams>('write')(
async (_request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
async (request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
try {
const { id: serverId, toolId } = await params

View File

@@ -20,7 +20,7 @@ interface RouteParams {
* GET - List all tools for a workflow MCP server
*/
export const GET = withMcpAuth<RouteParams>('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
async (request: NextRequest, { userId, workspaceId, requestId }, { params }) => {
try {
const { id: serverId } = await params

View File

@@ -16,7 +16,7 @@ export const dynamic = 'force-dynamic'
* GET - List all workflow MCP servers for the workspace
*/
export const GET = withMcpAuth('read')(
async (_request: NextRequest, { userId, workspaceId, requestId }) => {
async (request: NextRequest, { userId, workspaceId, requestId }) => {
try {
logger.info(`[${requestId}] Listing workflow MCP servers for workspace ${workspaceId}`)

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import {
invitation,

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import {
invitation,
@@ -42,7 +42,7 @@ interface WorkspaceInvitation {
* GET /api/organizations/[id]/invitations
* Get all pending invitations for an organization
*/
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
try {
const session = await getSession()

View File

@@ -239,7 +239,7 @@ export async function PUT(
* Remove member from organization
*/
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string; memberId: string }> }
) {
try {

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { invitation, member, organization, user, userStats } from '@sim/db/schema'
import { createLogger } from '@sim/logger'

View File

@@ -32,7 +32,7 @@ async function getPermissionGroupWithAccess(groupId: string, userId: string) {
return { group, role: membership.role }
}
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {

View File

@@ -70,7 +70,7 @@ async function getPermissionGroupWithAccess(groupId: string, userId: string) {
return { group, role: membership.role }
}
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {
@@ -195,7 +195,7 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
}
}
export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await getSession()
if (!session?.user?.id) {

View File

@@ -5,7 +5,7 @@ export async function GET() {
try {
const allModels = Object.keys(getBaseModelProviders())
return NextResponse.json({ models: allModels })
} catch (_error) {
} catch (error) {
return NextResponse.json({ models: [], error: 'Failed to fetch models' }, { status: 500 })
}
}

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { generateRequestId } from '@/lib/core/utils/request'

View File

@@ -14,7 +14,7 @@ export const revalidate = 0
/**
* POST /api/templates/[id]/approve - Approve a template (super users only)
*/
export async function POST(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params

View File

@@ -14,7 +14,7 @@ export const revalidate = 0
/**
* POST /api/templates/[id]/reject - Reject a template (super users only)
*/
export async function POST(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params

View File

@@ -16,7 +16,7 @@ const logger = createLogger('TemplateByIdAPI')
export const revalidate = 0
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -234,7 +234,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
// DELETE /api/templates/[id] - Delete a template
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()

View File

@@ -13,7 +13,7 @@ export const dynamic = 'force-dynamic'
export const revalidate = 0
// GET /api/templates/[id]/star - Check if user has starred this template
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -47,7 +47,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{
}
// POST /api/templates/[id]/star - Add a star to the template
export async function POST(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -120,7 +120,7 @@ export async function POST(_request: NextRequest, { params }: { params: Promise<
// DELETE /api/templates/[id]/star - Remove a star from the template
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()

View File

@@ -136,7 +136,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
// Use a transaction for template updates and deployment version
const _result = await db.transaction(async (tx) => {
const result = await db.transaction(async (tx) => {
// Prepare template update data
const updateData: any = {
views: sql`${templates.views} + 1`,

View File

@@ -20,6 +20,7 @@ export async function GET(request: NextRequest) {
const requestId = generateRequestId()
try {
const url = new URL(request.url)
const hasApiKey = !!request.headers.get('x-api-key')
// Check internal API key authentication
@@ -125,7 +126,7 @@ export async function GET(request: NextRequest) {
}
// Add a helpful OPTIONS handler for CORS preflight
export async function OPTIONS(_request: NextRequest) {
export async function OPTIONS(request: NextRequest) {
const requestId = generateRequestId()
logger.info(`[${requestId}] OPTIONS request received for /api/templates/approved/sanitized`)

View File

@@ -9,7 +9,7 @@ import type {
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createA2AClient, isTerminalState } from '@/lib/a2a/utils'
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -50,12 +50,14 @@ export async function POST(request: NextRequest) {
let taskId = validatedData.taskId
let contextId: string | undefined
let state: TaskState = 'working'
let content = ''
let artifacts: Artifact[] = []
let history: Message[] = []
for await (const event of stream) {
if (event.kind === 'message') {
const msg = event as Message
content = extractTextContent(msg)
taskId = msg.taskId || taskId
contextId = msg.contextId || contextId
state = 'completed'
@@ -66,6 +68,10 @@ export async function POST(request: NextRequest) {
state = task.status.state
artifacts = task.artifacts || []
history = task.history || []
const lastAgentMessage = history.filter((m) => m.role === 'agent').pop()
if (lastAgentMessage) {
content = extractTextContent(lastAgentMessage)
}
} else if ('status' in event) {
const statusEvent = event as TaskStatusUpdateEvent
state = statusEvent.status.state

View File

@@ -82,7 +82,7 @@ describe('Custom Tools API Routes', () => {
mockSelect.mockReturnValue({ from: mockFrom })
mockFrom.mockReturnValue({ where: mockWhere })
mockWhere.mockImplementation((_condition) => {
mockWhere.mockImplementation((condition) => {
const queryBuilder = {
orderBy: mockOrderBy,
limit: mockLimit,
@@ -90,7 +90,7 @@ describe('Custom Tools API Routes', () => {
resolve(sampleTools)
return queryBuilder
},
catch: (_reject: (error: Error) => void) => queryBuilder,
catch: (reject: (error: Error) => void) => queryBuilder,
}
return queryBuilder
})
@@ -101,7 +101,7 @@ describe('Custom Tools API Routes', () => {
resolve(sampleTools)
return queryBuilder
},
catch: (_reject: (error: Error) => void) => queryBuilder,
catch: (reject: (error: Error) => void) => queryBuilder,
}
return queryBuilder
})
@@ -131,12 +131,12 @@ describe('Custom Tools API Routes', () => {
resolve(sampleTools)
return queryBuilder
},
catch: (_reject: (error: Error) => void) => queryBuilder,
catch: (reject: (error: Error) => void) => queryBuilder,
}
return queryBuilder
})
const txMockWhere = vi.fn().mockImplementation((_condition) => {
const txMockWhere = vi.fn().mockImplementation((condition) => {
const queryBuilder = {
orderBy: txMockOrderBy,
limit: mockLimit,
@@ -144,7 +144,7 @@ describe('Custom Tools API Routes', () => {
resolve(sampleTools)
return queryBuilder
},
catch: (_reject: (error: Error) => void) => queryBuilder,
catch: (reject: (error: Error) => void) => queryBuilder,
}
return queryBuilder
})
@@ -274,14 +274,14 @@ describe('Custom Tools API Routes', () => {
mockLimit.mockResolvedValueOnce([{ workspaceId: 'workspace-123' }])
mockWhere.mockImplementationOnce((_condition) => {
mockWhere.mockImplementationOnce((condition) => {
const queryBuilder = {
limit: mockLimit,
then: (resolve: (value: typeof sampleTools) => void) => {
resolve(sampleTools)
return queryBuilder
},
catch: (_reject: (error: Error) => void) => queryBuilder,
catch: (reject: (error: Error) => void) => queryBuilder,
}
return queryBuilder
})

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,6 +1,7 @@
import { createLogger } from '@sim/logger'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
export const dynamic = 'force-dynamic'
@@ -19,6 +20,7 @@ export async function POST(request: Request) {
}
try {
const requestId = generateRequestId()
const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { createLogger } from '@sim/logger'

View File

@@ -1,13 +1,9 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import {
createMongoDBConnection,
sanitizeCollectionName,
validateFilter,
} from '@/app/api/tools/mongodb/utils'
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
const logger = createLogger('MongoDBDeleteAPI')
@@ -73,7 +69,7 @@ export async function POST(request: NextRequest) {
let filterDoc
try {
filterDoc = JSON.parse(params.filter)
} catch (_error) {
} catch (error) {
logger.warn(`[${requestId}] Invalid filter JSON: ${params.filter}`)
return NextResponse.json({ error: 'Invalid JSON format in filter' }, { status: 400 })
}

View File

@@ -1,13 +1,9 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import {
createMongoDBConnection,
sanitizeCollectionName,
validatePipeline,
} from '@/app/api/tools/mongodb/utils'
import { createMongoDBConnection, sanitizeCollectionName, validatePipeline } from '../utils'
const logger = createLogger('MongoDBExecuteAPI')

View File

@@ -1,9 +1,9 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createMongoDBConnection, sanitizeCollectionName } from '@/app/api/tools/mongodb/utils'
import { createMongoDBConnection, sanitizeCollectionName } from '../utils'
const logger = createLogger('MongoDBInsertAPI')

View File

@@ -1,9 +1,9 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createMongoDBConnection, executeIntrospect } from '@/app/api/tools/mongodb/utils'
import { createMongoDBConnection, executeIntrospect } from '../utils'
const logger = createLogger('MongoDBIntrospectAPI')

View File

@@ -1,13 +1,9 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import {
createMongoDBConnection,
sanitizeCollectionName,
validateFilter,
} from '@/app/api/tools/mongodb/utils'
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
const logger = createLogger('MongoDBQueryAPI')
@@ -87,7 +83,7 @@ export async function POST(request: NextRequest) {
if (params.sort?.trim()) {
try {
sortCriteria = JSON.parse(params.sort)
} catch (_error) {
} catch (error) {
logger.warn(`[${requestId}] Invalid sort JSON: ${params.sort}`)
return NextResponse.json({ error: 'Invalid JSON format in sort criteria' }, { status: 400 })
}

View File

@@ -1,13 +1,9 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import {
createMongoDBConnection,
sanitizeCollectionName,
validateFilter,
} from '@/app/api/tools/mongodb/utils'
import { createMongoDBConnection, sanitizeCollectionName, validateFilter } from '../utils'
const logger = createLogger('MongoDBUpdateAPI')
@@ -94,7 +90,7 @@ export async function POST(request: NextRequest) {
try {
filterDoc = JSON.parse(params.filter)
updateDoc = JSON.parse(params.update)
} catch (_error) {
} catch (error) {
logger.warn(`[${requestId}] Invalid JSON in filter or update`)
return NextResponse.json(
{ error: 'Invalid JSON format in filter or update' },

View File

@@ -72,7 +72,7 @@ export function validateFilter(filter: string): { isValid: boolean; error?: stri
}
return { isValid: true }
} catch (_error) {
} catch (error) {
return {
isValid: false,
error: 'Invalid JSON format in filter',
@@ -113,7 +113,7 @@ export function validatePipeline(pipeline: string): { isValid: boolean; error?:
}
return { isValid: true }
} catch (_error) {
} catch (error) {
return {
isValid: false,
error: 'Invalid JSON format in pipeline',

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
@@ -29,7 +29,7 @@ const UpdateSchema = z.object({
throw new Error('Data must be a JSON object')
}
return parsed
} catch (_e) {
} catch (e) {
throw new Error('Invalid JSON format in data field')
}
}),

View File

@@ -271,7 +271,7 @@ export async function executeIntrospect(
unique: row.NON_UNIQUE === 0,
})
}
indexMap.get(indexName)?.columns.push(row.COLUMN_NAME)
indexMap.get(indexName)!.columns.push(row.COLUMN_NAME)
}
const indexes = Array.from(indexMap.values())

View File

@@ -1,4 +1,4 @@
import { randomUUID } from 'node:crypto'
import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

Some files were not shown because too many files have changed in this diff Show More