mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-31 01:37:58 -05:00
Basic ss tes
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { db } from '@sim/db'
|
||||
import { copilotChats } from '@sim/db/schema'
|
||||
import { copilotChats, permissions, workflow } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, desc, eq } from 'drizzle-orm'
|
||||
import { and, asc, desc, eq, inArray, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
@@ -40,7 +40,8 @@ const ChatMessageSchema = z.object({
|
||||
message: z.string().min(1, 'Message is required'),
|
||||
userMessageId: z.string().optional(), // ID from frontend for the user message
|
||||
chatId: z.string().optional(),
|
||||
workflowId: z.string().min(1, 'Workflow ID is required'),
|
||||
workflowId: z.string().optional(),
|
||||
workflowName: z.string().optional(),
|
||||
model: z.enum(COPILOT_MODEL_IDS).optional().default('claude-4.5-opus'),
|
||||
mode: z.enum(COPILOT_REQUEST_MODES).optional().default('agent'),
|
||||
prefetch: z.boolean().optional(),
|
||||
@@ -78,6 +79,54 @@ const ChatMessageSchema = z.object({
|
||||
commands: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
async function resolveWorkflowId(
|
||||
userId: string,
|
||||
workflowId?: string,
|
||||
workflowName?: string
|
||||
): Promise<{ workflowId: string; workflowName?: string } | null> {
|
||||
// If workflowId provided, use it directly
|
||||
if (workflowId) {
|
||||
return { workflowId }
|
||||
}
|
||||
|
||||
// Get user's accessible workflows
|
||||
const workspaceIds = await db
|
||||
.select({ entityId: permissions.entityId })
|
||||
.from(permissions)
|
||||
.where(and(eq(permissions.userId, userId), eq(permissions.entityType, 'workspace')))
|
||||
|
||||
const workspaceIdList = workspaceIds.map((row) => row.entityId)
|
||||
|
||||
const workflowConditions = [eq(workflow.userId, userId)]
|
||||
if (workspaceIdList.length > 0) {
|
||||
workflowConditions.push(inArray(workflow.workspaceId, workspaceIdList))
|
||||
}
|
||||
|
||||
const workflows = await db
|
||||
.select()
|
||||
.from(workflow)
|
||||
.where(or(...workflowConditions))
|
||||
.orderBy(asc(workflow.sortOrder), asc(workflow.createdAt), asc(workflow.id))
|
||||
|
||||
if (workflows.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If workflowName provided, find matching workflow
|
||||
if (workflowName) {
|
||||
const match = workflows.find(
|
||||
(w) => String(w.name || '').trim().toLowerCase() === workflowName.toLowerCase()
|
||||
)
|
||||
if (match) {
|
||||
return { workflowId: match.id, workflowName: match.name || undefined }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Default to first workflow
|
||||
return { workflowId: workflows[0].id, workflowName: workflows[0].name || undefined }
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/copilot/chat
|
||||
* Send messages to sim agent and handle chat persistence
|
||||
@@ -100,7 +149,8 @@ export async function POST(req: NextRequest) {
|
||||
message,
|
||||
userMessageId,
|
||||
chatId,
|
||||
workflowId,
|
||||
workflowId: providedWorkflowId,
|
||||
workflowName,
|
||||
model,
|
||||
mode,
|
||||
prefetch,
|
||||
@@ -113,6 +163,16 @@ export async function POST(req: NextRequest) {
|
||||
contexts,
|
||||
commands,
|
||||
} = ChatMessageSchema.parse(body)
|
||||
|
||||
// Resolve workflowId - if not provided, use first workflow or find by name
|
||||
const resolved = await resolveWorkflowId(authenticatedUserId, providedWorkflowId, workflowName)
|
||||
if (!resolved) {
|
||||
return createBadRequestResponse(
|
||||
'No workflows found. Create a workflow first or provide a valid workflowId.'
|
||||
)
|
||||
}
|
||||
const workflowId = resolved.workflowId
|
||||
|
||||
// Ensure we have a consistent user message ID for this request
|
||||
const userMessageIdToUse = userMessageId || crypto.randomUUID()
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { db } from '@sim/db'
|
||||
import { permissions, workflow } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, asc, eq, inArray, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { authenticateV1Request } from '@/app/api/v1/auth'
|
||||
@@ -10,17 +13,71 @@ const logger = createLogger('CopilotHeadlessAPI')
|
||||
|
||||
const RequestSchema = z.object({
|
||||
message: z.string().min(1, 'message is required'),
|
||||
workflowId: z.string().min(1, 'workflowId is required'),
|
||||
workflowId: z.string().optional(),
|
||||
workflowName: z.string().optional(),
|
||||
chatId: z.string().optional(),
|
||||
mode: z.enum(['agent', 'ask', 'plan']).optional().default('agent'),
|
||||
mode: z.enum(['agent', 'ask', 'plan', 'fast']).optional().default('fast'),
|
||||
model: z.string().optional(),
|
||||
autoExecuteTools: z.boolean().optional().default(true),
|
||||
timeout: z.number().optional().default(300000),
|
||||
})
|
||||
|
||||
async function resolveWorkflowId(
|
||||
userId: string,
|
||||
workflowId?: string,
|
||||
workflowName?: string
|
||||
): Promise<{ workflowId: string; workflowName?: string } | null> {
|
||||
// If workflowId provided, use it directly
|
||||
if (workflowId) {
|
||||
return { workflowId }
|
||||
}
|
||||
|
||||
// Get user's accessible workflows
|
||||
const workspaceIds = await db
|
||||
.select({ entityId: permissions.entityId })
|
||||
.from(permissions)
|
||||
.where(and(eq(permissions.userId, userId), eq(permissions.entityType, 'workspace')))
|
||||
|
||||
const workspaceIdList = workspaceIds.map((row) => row.entityId)
|
||||
|
||||
const workflowConditions = [eq(workflow.userId, userId)]
|
||||
if (workspaceIdList.length > 0) {
|
||||
workflowConditions.push(inArray(workflow.workspaceId, workspaceIdList))
|
||||
}
|
||||
|
||||
const workflows = await db
|
||||
.select()
|
||||
.from(workflow)
|
||||
.where(or(...workflowConditions))
|
||||
.orderBy(asc(workflow.sortOrder), asc(workflow.createdAt), asc(workflow.id))
|
||||
|
||||
if (workflows.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If workflowName provided, find matching workflow
|
||||
if (workflowName) {
|
||||
const match = workflows.find(
|
||||
(w) => String(w.name || '').trim().toLowerCase() === workflowName.toLowerCase()
|
||||
)
|
||||
if (match) {
|
||||
return { workflowId: match.id, workflowName: match.name || undefined }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Default to first workflow
|
||||
return { workflowId: workflows[0].id, workflowName: workflows[0].name || undefined }
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/copilot/chat
|
||||
* Headless copilot endpoint for server-side orchestration.
|
||||
*
|
||||
* workflowId is optional - if not provided:
|
||||
* - If workflowName is provided, finds that workflow
|
||||
* - Otherwise uses the user's first workflow as context
|
||||
* - The copilot can still operate on any workflow using list_user_workflows
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const auth = await authenticateV1Request(req)
|
||||
@@ -34,9 +91,18 @@ export async function POST(req: NextRequest) {
|
||||
const defaults = getCopilotModel('chat')
|
||||
const selectedModel = parsed.model || defaults.model
|
||||
|
||||
// Resolve workflow ID
|
||||
const resolved = await resolveWorkflowId(auth.userId, parsed.workflowId, parsed.workflowName)
|
||||
if (!resolved) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'No workflows found. Create a workflow first or provide a valid workflowId.' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const requestPayload = {
|
||||
message: parsed.message,
|
||||
workflowId: parsed.workflowId,
|
||||
workflowId: resolved.workflowId,
|
||||
userId: auth.userId,
|
||||
stream: true,
|
||||
streamToolCalls: true,
|
||||
@@ -44,12 +110,13 @@ export async function POST(req: NextRequest) {
|
||||
mode: parsed.mode,
|
||||
messageId: crypto.randomUUID(),
|
||||
version: SIM_AGENT_VERSION,
|
||||
headless: true, // Enable cross-workflow operations via workflowId params
|
||||
...(parsed.chatId ? { chatId: parsed.chatId } : {}),
|
||||
}
|
||||
|
||||
const result = await orchestrateCopilotStream(requestPayload, {
|
||||
userId: auth.userId,
|
||||
workflowId: parsed.workflowId,
|
||||
workflowId: resolved.workflowId,
|
||||
chatId: parsed.chatId,
|
||||
autoExecuteTools: parsed.autoExecuteTools,
|
||||
timeout: parsed.timeout,
|
||||
|
||||
@@ -302,7 +302,10 @@ async function executeGetUserWorkflow(
|
||||
return { success: false, error: 'workflowId is required' }
|
||||
}
|
||||
|
||||
await ensureWorkflowAccess(workflowId, context.userId)
|
||||
const { workflow: workflowRecord, workspaceId } = await ensureWorkflowAccess(
|
||||
workflowId,
|
||||
context.userId
|
||||
)
|
||||
|
||||
const normalized = await loadWorkflowFromNormalizedTables(workflowId)
|
||||
if (!normalized) {
|
||||
@@ -318,7 +321,16 @@ async function executeGetUserWorkflow(
|
||||
const sanitized = sanitizeForCopilot(workflowState)
|
||||
const userWorkflow = JSON.stringify(sanitized, null, 2)
|
||||
|
||||
return { success: true, output: { userWorkflow } }
|
||||
// Return workflow ID so copilot can use it for subsequent tool calls
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
workflowId,
|
||||
workflowName: workflowRecord.name || '',
|
||||
workspaceId,
|
||||
userWorkflow,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
@@ -371,7 +383,16 @@ async function executeGetWorkflowFromName(
|
||||
const sanitized = sanitizeForCopilot(workflowState)
|
||||
const userWorkflow = JSON.stringify(sanitized, null, 2)
|
||||
|
||||
return { success: true, output: { userWorkflow } }
|
||||
// Return workflow ID and workspaceId so copilot can use them for subsequent tool calls
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
workflowId: match.id,
|
||||
workflowName: match.name || '',
|
||||
workspaceId: match.workspaceId,
|
||||
userWorkflow,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
@@ -396,11 +417,18 @@ async function executeListUserWorkflows(context: ExecutionContext): Promise<Tool
|
||||
.where(or(...workflowConditions))
|
||||
.orderBy(asc(workflow.sortOrder), asc(workflow.createdAt), asc(workflow.id))
|
||||
|
||||
// Return both names (for backward compatibility) and full workflow info with IDs
|
||||
const names = workflows
|
||||
.map((w) => (typeof w.name === 'string' ? w.name : null))
|
||||
.filter((n): n is string => Boolean(n))
|
||||
|
||||
return { success: true, output: { workflow_names: names } }
|
||||
const workflowList = workflows.map((w) => ({
|
||||
workflowId: w.id,
|
||||
workflowName: w.name || '',
|
||||
workspaceId: w.workspaceId,
|
||||
}))
|
||||
|
||||
return { success: true, output: { workflow_names: names, workflows: workflowList } }
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user