1ff5c3377c
The read-only proxy previously wrapped model delegates to block writes, but left client-level raw/escape hatches ($transaction, $executeRaw, $executeRawUnsafe, $queryRawUnsafe, $runCommandRaw) intact. A read-tool could smuggle DML via raw SQL, or open an interactive $transaction whose tx-scoped client (unproxied by construction) accepts writes. - read-only-prisma: block $transaction, $executeRaw, $executeRawUnsafe, $queryRawUnsafe, $runCommandRaw at the client level. Template-tagged $queryRaw stays allowed (read-only by API contract). - assistant-tools: add create_estimate to MUTATION_TOOLS — it uses $transaction internally and was previously bypassing the proxy only because $transaction wasn't blocked. - shared: document isReadOnly flag on ToolContext so any scoped tRPC caller a tool spawns keeps the proxied client. - helpers: note the runtime wrap at assistant-tools.ts:739 is authoritative; forwarding ctx.db verbatim is correct. - tests: cover model writes, raw escapes, and the allowed $queryRaw path (7 cases, all pass). - loosen one estimate-detail test that compared the exact db instance (fails once that instance is a proxy; the assertion's intent is the estimate id). Covers EGAI 4.1.1.2 / IAAI 3.6.22. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
811 lines
24 KiB
TypeScript
811 lines
24 KiB
TypeScript
/**
|
|
* 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<PermissionKey>,
|
|
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<ToolResult> {
|
|
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<string, unknown>)) {
|
|
const actionResult = result as Record<string, unknown>;
|
|
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 };
|
|
}
|
|
}
|