/** * AI Assistant Tool definitions for OpenAI Function Calling. * Each tool has a JSON schema (for the AI) and an execute function (for the server). * * Helpers, callers, and access control are extracted into: * - ./assistant-tools/helpers.ts (~1,400 lines of formatters, error converters, parsers) * - ./assistant-tools/callers.ts (35 tRPC caller factory bindings) * - ./assistant-tools/access-control.ts (role/permission gating logic) */ import { PermissionKey } from "@capakraken/shared"; import { createReadOnlyProxy } from "../lib/read-only-prisma.js"; import { logger } from "../lib/logger.js"; // ─── Extracted modules ────────────────────────────────────────────────────── import { createChargeabilityReportCaller, createComputationGraphCaller, createTimelineCaller, createAuditLogCaller, createImportExportCaller, createDispoCaller, createResourceCaller, createSettingsCaller, createSystemRoleConfigCaller, createUserCaller, createNotificationCaller, createEstimateCaller, createWebhookCaller, createCountryCaller, createHolidayCalendarCaller, createBlueprintCaller, createRoleCaller, createClientCaller, createOrgUnitCaller, createProjectCaller, createRateCardCaller, createReportCaller, createVacationCaller, createEntitlementCaller, createCommentCaller, createManagementLevelCaller, createUtilizationCategoryCaller, createCalculationRuleCaller, createEffortRuleCaller, createExperienceMultiplierCaller, createDashboardCaller, createInsightsCaller, createScenarioCaller, createAllocationCaller, createStaffingCaller, } from "./assistant-tools/callers.js"; import { assertPermission, assertAdminRole, fmtDate, formatHolidayCalendarEntry, formatHolidayCalendar, formatCountry, resolveHolidayPeriod, parseIsoDate, parseOptionalIsoDate, parseDateTime, parseOptionalDateTime, isAssistantToolErrorResult, toAssistantIndexedFieldError, toAssistantNotFoundError, toAssistantAllocationNotFoundError, toAssistantProjectNotFoundError, toAssistantTimelineMutationError, toAssistantVacationMutationError, toAssistantProjectCreationError, toAssistantDemandCreationError, toAssistantDemandFillError, toAssistantEstimateNotFoundError, toAssistantEstimateReadError, toAssistantEstimateMutationError, toAssistantEstimateCreationError, toAssistantHolidayCalendarNotFoundError, toAssistantHolidayCalendarMutationError, toAssistantHolidayEntryMutationError, toAssistantHolidayEntryNotFoundError, toAssistantRoleMutationError, toAssistantClientMutationError, toAssistantOrgUnitMutationError, toAssistantCountryMutationError, toAssistantResourceCreationError, toAssistantResourceMutationError, toAssistantProjectMutationError, toAssistantMetroCityMutationError, toAssistantVacationCreationError, toAssistantEntitlementMutationError, toAssistantUserMutationError, toAssistantUserResourceLinkError, toAssistantTotpEnableError, toAssistantWebhookNotFoundError, toAssistantWebhookMutationError, toAssistantAuditLogEntryNotFoundError, toAssistantTaskActionError, toAssistantTaskAssignmentError, toAssistantBroadcastNotFoundError, toAssistantDispoImportBatchNotFoundError, toAssistantReminderNotFoundError, toAssistantNotificationReadError, toAssistantNotificationDeletionError, toAssistantReminderCreationError, toAssistantCommentResolveError, toAssistantCommentCreationError, toAssistantNotificationCreationError, toAssistantCountryNotFoundError, toAssistantTaskNotFoundError, fmtEur, normalizeAssistantExecutionError, resolveEntityOrAssistantError, resolveProjectIdentifier, resolveResourceIdentifier, resolveResponsiblePerson, createScopedCallerContext, sanitizeWebhook, sanitizeWebhookList, parseAssistantVacationRequestType, } from "./assistant-tools/helpers.js"; import { LEGACY_MONOLITHIC_TOOL_ACCESS, getAssistantToolAccessFailure, toAssistantToolAccessError, getAvailableAssistantToolsForContext as getAvailableToolsForCtx_, } from "./assistant-tools/access-control.js"; export { canAccessAssistantTool } from "./assistant-tools/access-control.js"; import { advancedTimelineToolDefinitions, createAdvancedTimelineExecutors, } from "./assistant-tools/advanced-timeline.js"; import { allocationPlanningMutationToolDefinitions, allocationPlanningReadToolDefinitions, createAllocationPlanningExecutors, } from "./assistant-tools/allocation-planning.js"; import { settingsAdminToolDefinitions, createSettingsAdminExecutors, } from "./assistant-tools/settings-admin.js"; import { createVacationHolidayExecutors, vacationHolidayMutationToolDefinitions, vacationHolidayReadToolDefinitions, } from "./assistant-tools/vacation-holidays.js"; import { createRolesAnalyticsExecutors, rolesAnalyticsMutationToolDefinitions, rolesAnalyticsReadToolDefinitions, } from "./assistant-tools/roles-analytics.js"; import { clientMutationToolDefinitions, createClientsOrgUnitsExecutors, orgUnitMutationToolDefinitions, } from "./assistant-tools/clients-org-units.js"; import { chargeabilityComputationReadToolDefinitions, createChargeabilityComputationExecutors, } from "./assistant-tools/chargeability-computation.js"; import { configReadmodelToolDefinitions, createConfigReadmodelExecutors, } from "./assistant-tools/config-readmodels.js"; import { countryMetroAdminToolDefinitions, createCountryMetroAdminExecutors, } from "./assistant-tools/country-metro-admin.js"; import { countryReadmodelToolDefinitions, createCountryReadmodelExecutors, } from "./assistant-tools/country-readmodels.js"; import { createUserSelfServiceExecutors, userSelfServiceToolDefinitions, } from "./assistant-tools/user-self-service.js"; import { createUserAdminExecutors, userAdminToolDefinitions, } from "./assistant-tools/user-admin.js"; import { createNotificationsTasksExecutors, notificationInboxToolDefinitions, notificationTaskToolDefinitions, } from "./assistant-tools/notifications-tasks.js"; import { createEstimateExecutors, estimateMutationToolDefinitions, estimateReadToolDefinitions, } from "./assistant-tools/estimates.js"; import { createProjectExecutors, projectMutationToolDefinitions, projectReadToolDefinitions, } from "./assistant-tools/projects.js"; import { createStaffingDemandExecutors, staffingDemandMutationToolDefinitions, staffingDemandReadToolDefinitions, } from "./assistant-tools/staffing-demand.js"; import { createResourceExecutors, resourceMutationToolDefinitions, resourceReadToolDefinitions, } from "./assistant-tools/resources.js"; import { blueprintsRateCardsToolDefinitions, createBlueprintsRateCardsExecutors, } from "./assistant-tools/blueprints-rate-cards.js"; import { createDashboardInsightsReportsExecutors, dashboardInsightsReportsToolDefinitions, } from "./assistant-tools/dashboard-insights-reports.js"; import { createScenarioRateAnalysisExecutors, scenarioRateAnalysisToolDefinitions, } from "./assistant-tools/scenario-rate-analysis.js"; import { createImportExportDispoExecutors, importExportDispoToolDefinitions, } from "./assistant-tools/import-export-dispo.js"; import { commentMutationToolDefinitions, commentReadToolDefinitions, createCommentExecutors, } from "./assistant-tools/comments.js"; import { auditHistoryToolDefinitions, createAuditHistoryExecutors, } from "./assistant-tools/audit-history.js"; import { createPlanningNavigationExecutors, planningNavigationToolDefinitions, } from "./assistant-tools/planning-navigation.js"; import { createVacationEntitlementExecutors, vacationEntitlementToolDefinitions, } from "./assistant-tools/vacation-entitlements.js"; import { withToolAccess, type ToolAccessRequirements, type ToolContext, type ToolDef, type ToolExecutor, } from "./assistant-tools/shared.js"; export type { ToolContext } from "./assistant-tools/shared.js"; // ─── Mutation tool set for audit logging (EGAI 4.1.3.1 / IAAI 3.6.26) ────── export const MUTATION_TOOLS = new Set([ "import_csv_data", "update_system_settings", "clear_stored_runtime_secrets", "test_ai_connection", "test_smtp_connection", "test_gemini_connection", "update_system_role_config", "create_webhook", "update_webhook", "delete_webhook", "test_webhook", "stage_dispo_import_batch", "cancel_dispo_import_batch", "resolve_dispo_staged_record", "commit_dispo_import_batch", "create_allocation", "cancel_allocation", "update_allocation_status", "update_timeline_allocation_inline", "apply_timeline_project_shift", "quick_assign_timeline_resource", "batch_quick_assign_timeline_resources", "batch_shift_timeline_allocations", "update_resource", "deactivate_resource", "create_resource", "update_project", "create_project", "delete_project", "create_vacation", "approve_vacation", "reject_vacation", "cancel_vacation", "set_entitlement", "create_demand", "fill_demand", "generate_project_cover", "remove_project_cover", "create_role", "update_role", "delete_role", "create_client", "update_client", "delete_client", "create_org_unit", "update_org_unit", "create_country", "update_country", "create_metro_city", "update_metro_city", "delete_metro_city", "create_holiday_calendar", "update_holiday_calendar", "delete_holiday_calendar", "create_holiday_calendar_entry", "update_holiday_calendar_entry", "delete_holiday_calendar_entry", "send_broadcast", "create_task_for_user", "create_reminder", "update_task_status", "execute_task_action", "create_comment", "resolve_comment", "mark_notification_read", "save_dashboard_layout", "toggle_favorite_project", "set_column_preferences", "generate_totp_secret", "verify_and_enable_totp", "create_user", "set_user_password", "update_user_role", "update_user_name", "link_user_resource", "auto_link_users_by_email", "set_user_permissions", "reset_user_permissions", "disable_user_totp", "create_notification", "update_reminder", "delete_reminder", "delete_notification", "assign_task", "create_estimate", "clone_estimate", "update_estimate_draft", "submit_estimate_version", "approve_estimate_version", "create_estimate_revision", "create_estimate_export", "create_estimate_planning_handoff", "generate_estimate_weekly_phasing", "update_estimate_commercial_terms", ]); export const ADVANCED_ASSISTANT_TOOLS = new Set([ "find_best_project_resource", "get_timeline_entries_view", "get_timeline_holiday_overlays", "get_project_timeline_context", "preview_project_shift", "update_timeline_allocation_inline", "apply_timeline_project_shift", "quick_assign_timeline_resource", "batch_quick_assign_timeline_resources", "batch_shift_timeline_allocations", "get_chargeability_report", "get_resource_computation_graph", "get_project_computation_graph", ]); // Callers imported from ./assistant-tools/callers.ts // Helpers imported from ./assistant-tools/helpers.ts // ─── Tool Definitions ─────────────────────────────────────────────────────── export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess( [ // ── READ TOOLS ── ...resourceReadToolDefinitions, ...projectReadToolDefinitions, ...advancedTimelineToolDefinitions, ...allocationPlanningReadToolDefinitions, ...vacationHolidayReadToolDefinitions, ...vacationHolidayMutationToolDefinitions, ...rolesAnalyticsReadToolDefinitions, ...chargeabilityComputationReadToolDefinitions, ...planningNavigationToolDefinitions, // ── WRITE TOOLS ── ...allocationPlanningMutationToolDefinitions, ...resourceMutationToolDefinitions, ...projectMutationToolDefinitions, ...vacationEntitlementToolDefinitions, // ── DEMAND / STAFFING ── ...staffingDemandReadToolDefinitions, ...staffingDemandMutationToolDefinitions, // ── BLUEPRINT ── ...blueprintsRateCardsToolDefinitions, // ── ESTIMATES ── ...estimateReadToolDefinitions, ...estimateMutationToolDefinitions, // ── ROLES ── ...rolesAnalyticsMutationToolDefinitions, // ── CLIENTS ── ...clientMutationToolDefinitions, // ── ADMIN / CONFIG READ TOOLS ── ...countryReadmodelToolDefinitions, ...countryMetroAdminToolDefinitions, ...configReadmodelToolDefinitions, ...userAdminToolDefinitions, ...userSelfServiceToolDefinitions, ...notificationInboxToolDefinitions, ...dashboardInsightsReportsToolDefinitions, // ── ORG UNIT MANAGEMENT ── ...orgUnitMutationToolDefinitions, // ── TASK MANAGEMENT ── ...notificationTaskToolDefinitions, ...commentReadToolDefinitions, ...scenarioRateAnalysisToolDefinitions, ...commentMutationToolDefinitions, ...auditHistoryToolDefinitions, ...importExportDispoToolDefinitions, ...settingsAdminToolDefinitions, ], LEGACY_MONOLITHIC_TOOL_ACCESS, ); const TOOL_DEFINITIONS_BY_NAME = new Map( TOOL_DEFINITIONS.map((tool) => [tool.function.name, tool]), ); // Access control imported from ./assistant-tools/access-control.ts export function getAvailableAssistantToolsForContext( permissions: Set, userRole: string, ): ToolDef[] { return getAvailableToolsForCtx_(TOOL_DEFINITIONS, permissions, userRole); } // ─── Tool Executors ───────────────────────────────────────────────────────── const executors = { ...createResourceExecutors({ assertPermission, createResourceCaller, createRoleCaller, createCountryCaller, createOrgUnitCaller, createScopedCallerContext, resolveResourceIdentifier, resolveEntityOrAssistantError, toAssistantResourceMutationError, toAssistantResourceCreationError, }), ...createProjectExecutors({ assertPermission, createProjectCaller, createBlueprintCaller, createClientCaller, createScopedCallerContext, resolveProjectIdentifier, resolveResponsiblePerson, resolveEntityOrAssistantError, toAssistantNotFoundError, toAssistantProjectMutationError, toAssistantProjectCreationError, toAssistantProjectNotFoundError, }), ...createStaffingDemandExecutors({ assertPermission, createAllocationCaller, createStaffingCaller, createRoleCaller, createScopedCallerContext, resolveProjectIdentifier, resolveResourceIdentifier, resolveEntityOrAssistantError, parseIsoDate, parseOptionalIsoDate, fmtDate, toAssistantDemandCreationError, toAssistantDemandFillError, }), ...createAdvancedTimelineExecutors({ assertPermission, createStaffingCaller, createTimelineCaller, createScopedCallerContext, resolveProjectIdentifier, resolveResourceIdentifier, parseIsoDate, fmtDate, isAssistantToolErrorResult, toAssistantIndexedFieldError, toAssistantTimelineMutationError, }), ...createAllocationPlanningExecutors({ assertPermission, createAllocationCaller, createTimelineCaller, createScopedCallerContext, resolveProjectIdentifier, resolveResourceIdentifier, parseIsoDate, parseOptionalIsoDate, fmtDate, toAssistantAllocationNotFoundError, }), ...createVacationHolidayExecutors({ createEntitlementCaller, createVacationCaller, createHolidayCalendarCaller, createScopedCallerContext, resolveResourceIdentifier, resolveHolidayPeriod, resolveEntityOrAssistantError, assertAdminRole, fmtDate, formatHolidayCalendar, formatHolidayCalendarEntry, toAssistantHolidayCalendarMutationError, toAssistantHolidayCalendarNotFoundError, toAssistantHolidayEntryMutationError, toAssistantHolidayEntryNotFoundError, }), ...createRolesAnalyticsExecutors({ createRoleCaller, createResourceCaller, createDashboardCaller, createScopedCallerContext, resolveResourceIdentifier, toAssistantRoleMutationError, }), ...createClientsOrgUnitsExecutors({ createClientCaller, createOrgUnitCaller, createScopedCallerContext, toAssistantClientMutationError, toAssistantOrgUnitMutationError, }), ...createChargeabilityComputationExecutors({ assertPermission, createChargeabilityReportCaller, createComputationGraphCaller, createScopedCallerContext, resolveResourceIdentifier, resolveProjectIdentifier, }), ...createBlueprintsRateCardsExecutors({ createBlueprintCaller, createRateCardCaller, createScopedCallerContext, resolveResourceIdentifier, resolveEntityOrAssistantError, parseOptionalIsoDate, fmtDate, }), ...createDashboardInsightsReportsExecutors({ assertPermission, createDashboardCaller, createInsightsCaller, createReportCaller, createScopedCallerContext, }), ...createPlanningNavigationExecutors({ createEstimateCaller, createClientCaller, createOrgUnitCaller, createTimelineCaller, createScopedCallerContext, resolveProjectIdentifier, parseIsoDate, }), ...createScenarioRateAnalysisExecutors({ assertPermission, createRateCardCaller, createScenarioCaller, createInsightsCaller, createScopedCallerContext, fmtEur, }), ...createCommentExecutors({ createCommentCaller, createScopedCallerContext, toAssistantCommentCreationError, toAssistantCommentResolveError, }), ...createAuditHistoryExecutors({ createAuditLogCaller, createScopedCallerContext, }), ...createVacationEntitlementExecutors({ createVacationCaller, createEntitlementCaller, createScopedCallerContext, resolveResourceIdentifier, parseIsoDate, fmtDate, parseAssistantVacationRequestType, toAssistantVacationCreationError, toAssistantVacationMutationError, toAssistantEntitlementMutationError, }), // ── ESTIMATES ── ...createEstimateExecutors({ assertPermission, createEstimateCaller, createScopedCallerContext, resolveProjectIdentifier, toAssistantEstimateNotFoundError, toAssistantEstimateReadError, toAssistantEstimateCreationError, toAssistantEstimateMutationError, }), // ── ROLES ── // ── CLIENTS ── // ── ADMIN / CONFIG ── ...createCountryReadmodelExecutors({ createCountryCaller, createScopedCallerContext, formatCountry, toAssistantCountryNotFoundError, }), ...createCountryMetroAdminExecutors({ createCountryCaller, createScopedCallerContext, assertAdminRole, formatCountry, toAssistantCountryMutationError, toAssistantMetroCityMutationError, }), ...createConfigReadmodelExecutors({ createManagementLevelCaller, createUtilizationCategoryCaller, createCalculationRuleCaller, createEffortRuleCaller, createExperienceMultiplierCaller, createScopedCallerContext, }), ...createUserAdminExecutors({ createUserCaller, createScopedCallerContext, toAssistantUserMutationError, toAssistantUserResourceLinkError, }), ...createUserSelfServiceExecutors({ createUserCaller, createScopedCallerContext, toAssistantCurrentUserError: toAssistantUserMutationError, toAssistantTotpEnableError, }), ...createNotificationsTasksExecutors({ createNotificationCaller, createScopedCallerContext, parseDateTime, parseOptionalDateTime, toAssistantTaskNotFoundError, toAssistantTaskActionError, toAssistantTaskAssignmentError, toAssistantBroadcastNotFoundError, toAssistantReminderNotFoundError, toAssistantNotificationReadError, toAssistantNotificationDeletionError, toAssistantReminderCreationError, toAssistantNotificationCreationError, }), ...createImportExportDispoExecutors({ assertPermission, createImportExportCaller, createDispoCaller, createScopedCallerContext, toAssistantDispoImportBatchNotFoundError, }), ...createSettingsAdminExecutors({ createSettingsCaller, createSystemRoleConfigCaller, createWebhookCaller, createAuditLogCaller, createProjectCaller, createScopedCallerContext, parseIsoDate, resolveProjectIdentifier, sanitizeWebhook, sanitizeWebhookList, toAssistantWebhookNotFoundError, toAssistantWebhookMutationError, toAssistantAuditLogEntryNotFoundError, }), }; // ─── Executor ─────────────────────────────────────────────────────────────── export interface ToolAction { type: string; url?: string; scope?: string[]; description?: string; } export interface ToolResult { content: string; action?: ToolAction; data?: unknown; } export async function executeTool( name: string, args: string, ctx: ToolContext, ): Promise { const executor = executors[name as keyof typeof executors]; if (!executor) return { content: JSON.stringify({ error: `Unknown tool: ${name}` }) }; try { const toolDefinition = TOOL_DEFINITIONS_BY_NAME.get(name); const accessFailure = toolDefinition ? getAssistantToolAccessFailure(toolDefinition, ctx) : null; if (accessFailure) { throw toAssistantToolAccessError(accessFailure); } const params = JSON.parse(args); // EGAI 4.1.1.2 / IAAI 3.6.22 — read-only tools get a DB proxy that blocks writes const isMutation = MUTATION_TOOLS.has(name); const effectiveCtx = isMutation ? ctx : { ...ctx, db: createReadOnlyProxy(ctx.db) }; // Audit-log all mutation tool executions (EGAI 4.1.3.1 / IAAI 3.6.26) if (isMutation) { logger.info( { tool: name, params, userId: ctx.userId, userRole: ctx.userRole }, "AI assistant mutation tool executed", ); } const result = await executor(params, effectiveCtx); // EGAI 4.3.1.2 — validate tool result against schema if defined if (toolDefinition?.resultSchema && result != null) { const validation = toolDefinition.resultSchema.safeParse(result); if (!validation.success) { logger.warn( { tool: name, errors: validation.error.issues }, "Tool result failed schema validation — proceeding with unvalidated result", ); } } // Detect action payloads (e.g. navigation, invalidation) if (result && typeof result === "object" && "__action" in (result as Record)) { const actionResult = result as Record; const actionType = actionResult.__action as string; if (actionType === "navigate") { const url = actionResult.url as string; const desc = (actionResult.description as string | undefined) ?? url; return { content: JSON.stringify({ description: desc }), data: { description: desc }, action: { type: "navigate", url, description: desc }, }; } if (actionType === "invalidate") { const scope = actionResult.scope as string[]; // Strip __action, scope, and large data from the result sent back to the AI const { __action: _, scope: _s, coverImageUrl: _img, ...rest } = actionResult; const content = JSON.stringify(rest); return { content: content.length > 4000 ? content.slice(0, 4000) + '..."' : content, data: rest, action: { type: "invalidate", scope }, }; } } // Cap tool result size to prevent oversized OpenAI conversation payloads const content = typeof result === "string" ? result : JSON.stringify(result); return { content: content.length > 8000 ? content.slice(0, 8000) + '..."' : content, ...(typeof result === "string" ? {} : { data: result }), }; } catch (err) { const normalizedError = normalizeAssistantExecutionError(err); logger.error( { tool: name, userId: ctx.userId, userRole: ctx.userRole, error: err instanceof Error ? { message: err.message, stack: err.stack } : err, }, "AI assistant tool execution failed", ); return { content: JSON.stringify(normalizedError), data: normalizedError }; } }