d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
167 lines
6.1 KiB
TypeScript
167 lines
6.1 KiB
TypeScript
import { db } from '@sim/db'
|
|
import { workflowSchedule } from '@sim/db/schema'
|
|
import { and, eq, isNull } from 'drizzle-orm'
|
|
import type { ExecutionContext, ToolCallResult } from '@/lib/copilot/request/types'
|
|
import { type MothershipResource, MothershipResourceType } from '@/lib/copilot/resources/types'
|
|
import { canonicalWorkspaceFilePath } from '@/lib/copilot/vfs/path-utils'
|
|
import { getKnowledgeBaseById } from '@/lib/knowledge/service'
|
|
import { getLogById } from '@/lib/logs/service'
|
|
import { getTableById } from '@/lib/table/service'
|
|
import {
|
|
getWorkspaceFile,
|
|
resolveWorkspaceFileReference,
|
|
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
|
import { getWorkflowById } from '@/lib/workflows/utils'
|
|
import type { OpenResourceItem, OpenResourceParams, ValidOpenResourceParams } from './param-types'
|
|
|
|
const VALID_OPEN_RESOURCE_TYPES = new Set(Object.values(MothershipResourceType))
|
|
|
|
async function resolveResource(
|
|
item: ValidOpenResourceParams,
|
|
context: ExecutionContext
|
|
): Promise<MothershipResource | { error: string }> {
|
|
const resourceType = item.type
|
|
let resourceId = item.id ?? ''
|
|
let title: string = resourceType
|
|
|
|
if (resourceType === 'file') {
|
|
if (!context.workspaceId)
|
|
return { error: 'Opening a workspace file requires workspace context.' }
|
|
const fileRef = item.path || item.id || ''
|
|
const record = item.path
|
|
? await resolveWorkspaceFileReference(context.workspaceId, item.path)
|
|
: item.id
|
|
? await getWorkspaceFile(context.workspaceId, item.id)
|
|
: null
|
|
if (!record) return { error: `No workspace file found for "${fileRef}".` }
|
|
resourceId = record.id
|
|
title = record.name
|
|
return {
|
|
type: resourceType,
|
|
id: resourceId,
|
|
title,
|
|
path: canonicalWorkspaceFilePath({ folderPath: record.folderPath, name: record.name }),
|
|
}
|
|
}
|
|
if (resourceType === 'workflow') {
|
|
if (!item.id) return { error: 'workflow resources require `id`.' }
|
|
const wf = await getWorkflowById(item.id)
|
|
if (!wf) return { error: `No workflow with id "${item.id}".` }
|
|
if (context.workspaceId && wf.workspaceId !== context.workspaceId)
|
|
return { error: `Workflow not found in the current workspace.` }
|
|
resourceId = wf.id
|
|
title = wf.name
|
|
}
|
|
if (resourceType === 'table') {
|
|
if (!item.id) return { error: 'table resources require `id`.' }
|
|
const tbl = await getTableById(item.id)
|
|
if (!tbl) return { error: `No table with id "${item.id}".` }
|
|
if (context.workspaceId && tbl.workspaceId !== context.workspaceId)
|
|
return { error: `Table not found in the current workspace.` }
|
|
resourceId = tbl.id
|
|
title = tbl.name
|
|
}
|
|
if (resourceType === 'knowledgebase') {
|
|
if (!item.id) return { error: 'knowledgebase resources require `id`.' }
|
|
const kb = await getKnowledgeBaseById(item.id)
|
|
if (!kb) return { error: `No knowledge base with id "${item.id}".` }
|
|
if (context.workspaceId && kb.workspaceId !== context.workspaceId)
|
|
return { error: `Knowledge base not found in the current workspace.` }
|
|
resourceId = kb.id
|
|
title = kb.name
|
|
}
|
|
if (resourceType === 'log') {
|
|
if (!item.id) return { error: 'log resources require `id`.' }
|
|
const logRecord = await getLogById(item.id)
|
|
if (!logRecord) return { error: `No log with id "${item.id}".` }
|
|
if (context.workspaceId && logRecord.workspaceId !== context.workspaceId)
|
|
return { error: `Log not found in the current workspace.` }
|
|
resourceId = logRecord.id
|
|
const workflowName = logRecord.workflowName ?? 'Unknown Workflow'
|
|
const timestamp = logRecord.startedAt.toLocaleString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
title = `${workflowName} — ${timestamp}`
|
|
}
|
|
if (resourceType === 'scheduledtask') {
|
|
if (!item.id) return { error: 'scheduledtask resources require `id`.' }
|
|
if (!context.workspaceId)
|
|
return { error: 'Opening a scheduled task requires workspace context.' }
|
|
const [schedule] = await db
|
|
.select({ id: workflowSchedule.id, jobTitle: workflowSchedule.jobTitle })
|
|
.from(workflowSchedule)
|
|
.where(
|
|
and(
|
|
eq(workflowSchedule.id, item.id),
|
|
eq(workflowSchedule.sourceWorkspaceId, context.workspaceId),
|
|
eq(workflowSchedule.sourceType, 'job'),
|
|
isNull(workflowSchedule.archivedAt)
|
|
)
|
|
)
|
|
.limit(1)
|
|
if (!schedule) return { error: `No scheduled task with id "${item.id}".` }
|
|
resourceId = schedule.id
|
|
title = schedule.jobTitle || 'Scheduled Task'
|
|
}
|
|
|
|
return { type: resourceType, id: resourceId, title }
|
|
}
|
|
|
|
export async function executeOpenResource(
|
|
rawParams: Record<string, unknown>,
|
|
context: ExecutionContext
|
|
): Promise<ToolCallResult> {
|
|
const params = rawParams as OpenResourceParams
|
|
|
|
const items: OpenResourceItem[] =
|
|
params.resources ??
|
|
(params.type && (params.id || params.path)
|
|
? [{ type: params.type, id: params.id, path: params.path }]
|
|
: [])
|
|
|
|
if (items.length === 0) {
|
|
return { success: false, error: 'resources array is required' }
|
|
}
|
|
|
|
const resources: MothershipResource[] = []
|
|
const errors: string[] = []
|
|
|
|
for (const item of items) {
|
|
const validated = validateOpenResourceItem(item)
|
|
if (!validated.success) {
|
|
errors.push(validated.error)
|
|
continue
|
|
}
|
|
const result = await resolveResource(validated.params, context)
|
|
if ('error' in result) {
|
|
errors.push(result.error)
|
|
} else {
|
|
resources.push(result)
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: resources.length > 0,
|
|
output: { opened: resources.length, errors },
|
|
resources,
|
|
}
|
|
}
|
|
|
|
function validateOpenResourceItem(
|
|
item: OpenResourceItem
|
|
): { success: true; params: ValidOpenResourceParams } | { success: false; error: string } {
|
|
if (!item.type) {
|
|
return { success: false, error: 'type is required' }
|
|
}
|
|
if (!VALID_OPEN_RESOURCE_TYPES.has(item.type)) {
|
|
return { success: false, error: `Invalid resource type: ${item.type}` }
|
|
}
|
|
if (!item.id && !(item.type === 'file' && item.path)) {
|
|
return { success: false, error: `${item.type} resources require \`id\`` }
|
|
}
|
|
return { success: true, params: { type: item.type, id: item.id, path: item.path } }
|
|
}
|