import { SystemRole } from "@capakraken/shared"; import type { TRPCContext } from "../../trpc.js"; import { withToolAccess, type ToolContext, type ToolDef, type ToolExecutor } from "./shared.js"; type AssistantToolErrorResult = { error: string }; type BlueprintRecord = { id: string; name: string; fieldDefs: unknown; rolePresets: unknown; }; type ResolvedResource = { id: string; }; type BlueprintsRateCardsDeps = { createBlueprintCaller: (ctx: TRPCContext) => { listSummaries: () => Promise>; getByIdentifier: (params: { identifier: string }) => Promise; }; createRateCardCaller: (ctx: TRPCContext) => { list: (params: { isActive: boolean; search?: string; }) => Promise>; resolveBestRate: (params: { resourceId?: string; roleName?: string; date?: Date; }) => Promise; }; createScopedCallerContext: (ctx: ToolContext) => TRPCContext; resolveResourceIdentifier: ( ctx: ToolContext, identifier: string, ) => Promise; resolveEntityOrAssistantError: ( resolve: () => Promise, notFoundMessage: string, ) => Promise; parseOptionalIsoDate: ( value: string | undefined, fieldName: string, ) => Date | undefined; fmtDate: (value: Date | null | undefined) => string | null; }; export const blueprintsRateCardsToolDefinitions: ToolDef[] = withToolAccess([ { type: "function", function: { name: "list_blueprints", description: "List available project blueprints with their field definitions.", parameters: { type: "object", properties: {} }, }, }, { type: "function", function: { name: "get_blueprint", description: "Get detailed blueprint with all field definitions and role presets.", parameters: { type: "object", properties: { identifier: { type: "string", description: "Blueprint ID or name (partial match)" }, }, required: ["identifier"], }, }, }, { type: "function", function: { name: "list_rate_cards", description: "List rate cards with their effective dates and line items.", parameters: { type: "object", properties: { query: { type: "string", description: "Search by name" }, limit: { type: "integer", description: "Max results. Default: 20" }, }, }, }, }, { type: "function", function: { name: "resolve_rate", description: "Look up the applicable rate for a resource, role, or management level from rate cards.", parameters: { type: "object", properties: { resourceId: { type: "string", description: "Resource ID or name" }, roleName: { type: "string", description: "Role name" }, date: { type: "string", description: "Date to check rate for (YYYY-MM-DD). Default: today" }, }, }, }, }, ], { list_blueprints: { requiresPlanningRead: true, }, get_blueprint: { requiresPlanningRead: true, }, list_rate_cards: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER], requiresCostView: true, }, resolve_rate: { allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER], requiresCostView: true, }, }); export function createBlueprintsRateCardsExecutors( deps: BlueprintsRateCardsDeps, ): Record { return { async list_blueprints(_params: Record, ctx: ToolContext) { const caller = deps.createBlueprintCaller(deps.createScopedCallerContext(ctx)); const blueprints = await caller.listSummaries(); return blueprints.map((blueprint) => ({ id: blueprint.id, name: blueprint.name, projectCount: blueprint._count.projects, })); }, async get_blueprint(params: { identifier: string }, ctx: ToolContext) { const caller = deps.createBlueprintCaller(deps.createScopedCallerContext(ctx)); const blueprint = await deps.resolveEntityOrAssistantError( () => caller.getByIdentifier({ identifier: params.identifier }), `Blueprint not found: ${params.identifier}`, ); if ("error" in blueprint) { return blueprint; } return { id: blueprint.id, name: blueprint.name, fieldDefs: blueprint.fieldDefs, rolePresets: blueprint.rolePresets, }; }, async list_rate_cards( params: { query?: string; limit?: number }, ctx: ToolContext, ) { const caller = deps.createRateCardCaller(deps.createScopedCallerContext(ctx)); const cards = await caller.list({ isActive: true, ...(params.query ? { search: params.query } : {}), }); return cards .map((card) => ({ id: card.id, name: card.name, effectiveFrom: deps.fmtDate(card.effectiveFrom), effectiveTo: deps.fmtDate(card.effectiveTo), lineCount: card._count.lines, })) .slice(0, Math.min(params.limit ?? 20, 50)); }, async resolve_rate( params: { resourceId?: string; roleName?: string; date?: string }, ctx: ToolContext, ) { const caller = deps.createRateCardCaller(deps.createScopedCallerContext(ctx)); const date = deps.parseOptionalIsoDate(params.date, "date"); if (params.resourceId) { const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId); if ("error" in resource) { return resource; } return caller.resolveBestRate({ resourceId: resource.id, ...(date ? { date } : {}), }); } return caller.resolveBestRate({ ...(params.roleName ? { roleName: params.roleName } : {}), ...(date ? { date } : {}), }); }, }; }