Files
CapaKraken/packages/api/src/router/assistant-tools/clients-org-units.ts
T

319 lines
9.7 KiB
TypeScript

import type { TRPCContext } from "../../trpc.js";
import {
CreateClientSchema,
CreateOrgUnitSchema,
UpdateClientSchema,
UpdateOrgUnitSchema,
} from "@capakraken/shared";
import { z } from "zod";
import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js";
type AssistantToolErrorResult = { error: string };
type ClientsOrgUnitsDeps = {
createClientCaller: (ctx: TRPCContext) => {
create: (params: z.input<typeof CreateClientSchema>) => Promise<{
id: string;
name: string;
}>;
update: (params: {
id: string;
data: z.input<typeof UpdateClientSchema>;
}) => Promise<{
id: string;
name: string;
}>;
getById: (params: { id: string }) => Promise<{ name: string }>;
delete: (params: { id: string }) => Promise<unknown>;
};
createOrgUnitCaller: (ctx: TRPCContext) => {
create: (params: z.input<typeof CreateOrgUnitSchema>) => Promise<{
id: string;
name: string;
}>;
update: (params: {
id: string;
data: z.input<typeof UpdateOrgUnitSchema>;
}) => Promise<{
id: string;
name: string;
}>;
};
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
toAssistantClientMutationError: (
error: unknown,
action?: "create" | "update" | "delete",
) => AssistantToolErrorResult | null;
toAssistantOrgUnitMutationError: (
error: unknown,
) => AssistantToolErrorResult | null;
};
export const clientMutationToolDefinitions: ToolDef[] = [
{
type: "function",
function: {
name: "create_client",
description: "Create a new client. Requires manager or admin role. Always confirm first.",
parameters: {
type: "object",
properties: {
name: { type: "string", description: "Client name" },
code: { type: "string", description: "Client code" },
parentId: { type: "string", description: "Optional parent client ID" },
sortOrder: { type: "integer", description: "Sort order. Default: 0" },
tags: { type: "array", items: { type: "string" }, description: "Optional client tags" },
},
required: ["name"],
},
},
},
{
type: "function",
function: {
name: "update_client",
description: "Update a client. Requires manager or admin role. Always confirm first.",
parameters: {
type: "object",
properties: {
id: { type: "string", description: "Client ID" },
name: { type: "string", description: "New name" },
code: { type: "string", description: "New code" },
sortOrder: { type: "integer", description: "New sort order" },
isActive: { type: "boolean", description: "Set active state" },
parentId: { type: "string", description: "Parent client ID; use null to clear" },
tags: { type: "array", items: { type: "string" }, description: "Replacement client tags" },
},
required: ["id"],
},
},
},
{
type: "function",
function: {
name: "delete_client",
description: "Delete a client. Requires admin role. Always confirm first.",
parameters: {
type: "object",
properties: {
id: { type: "string", description: "Client ID" },
},
required: ["id"],
},
},
},
];
export const orgUnitMutationToolDefinitions: ToolDef[] = [
{
type: "function",
function: {
name: "create_org_unit",
description: "Create a new organizational unit. Admin role required. Always confirm first.",
parameters: {
type: "object",
properties: {
name: { type: "string", description: "Org unit name" },
shortName: { type: "string", description: "Short name/code" },
level: { type: "integer", description: "Level (5, 6, or 7)" },
parentId: { type: "string", description: "Parent org unit ID (optional)" },
sortOrder: { type: "integer", description: "Sort order. Default: 0" },
},
required: ["name", "level"],
},
},
},
{
type: "function",
function: {
name: "update_org_unit",
description: "Update an organizational unit. Admin role required. Always confirm first.",
parameters: {
type: "object",
properties: {
id: { type: "string", description: "Org unit ID" },
name: { type: "string", description: "New name" },
shortName: { type: "string", description: "New short name" },
sortOrder: { type: "integer", description: "New sort order" },
isActive: { type: "boolean", description: "Set active state" },
parentId: { type: "string", description: "Parent org unit ID; use null to clear" },
},
required: ["id"],
},
},
},
];
export function createClientsOrgUnitsExecutors(
deps: ClientsOrgUnitsDeps,
): Record<string, ToolExecutor> {
return {
async create_client(params: {
name: string;
code?: string;
parentId?: string;
sortOrder?: number;
tags?: string[];
}, ctx: ToolContext) {
const caller = deps.createClientCaller(deps.createScopedCallerContext(ctx));
let client;
try {
client = await caller.create(CreateClientSchema.parse(params));
} catch (error) {
const mapped = deps.toAssistantClientMutationError(error);
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate" as const,
scope: ["client"],
success: true,
message: `Created client: ${client.name}`,
clientId: client.id,
client,
};
},
async update_client(params: {
id: string;
name?: string;
code?: string | null;
sortOrder?: number;
isActive?: boolean;
parentId?: string | null;
tags?: string[];
}, ctx: ToolContext) {
const caller = deps.createClientCaller(deps.createScopedCallerContext(ctx));
const data = UpdateClientSchema.parse({
...(params.name !== undefined ? { name: params.name } : {}),
...(params.code !== undefined ? { code: params.code } : {}),
...(params.sortOrder !== undefined ? { sortOrder: params.sortOrder } : {}),
...(params.isActive !== undefined ? { isActive: params.isActive } : {}),
...(params.parentId !== undefined ? { parentId: params.parentId } : {}),
...(params.tags !== undefined ? { tags: params.tags } : {}),
});
if (Object.keys(data).length === 0) {
return { error: "No fields to update" };
}
let client;
try {
client = await caller.update({ id: params.id, data });
} catch (error) {
const mapped = deps.toAssistantClientMutationError(error);
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate" as const,
scope: ["client"],
success: true,
message: `Updated client: ${client.name}`,
clientId: client.id,
client,
};
},
async delete_client(params: { id: string }, ctx: ToolContext) {
const caller = deps.createClientCaller(deps.createScopedCallerContext(ctx));
let client;
try {
client = await caller.getById({ id: params.id });
await caller.delete({ id: params.id });
} catch (error) {
const mapped = deps.toAssistantClientMutationError(error, "delete");
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate" as const,
scope: ["client"],
success: true,
message: `Deleted client: ${client.name}`,
};
},
async create_org_unit(params: {
name: string;
shortName?: string;
level: number;
parentId?: string;
sortOrder?: number;
}, ctx: ToolContext) {
const caller = deps.createOrgUnitCaller(deps.createScopedCallerContext(ctx));
let orgUnit;
try {
orgUnit = await caller.create(CreateOrgUnitSchema.parse(params));
} catch (error) {
const mapped = deps.toAssistantOrgUnitMutationError(error);
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate" as const,
scope: ["orgUnit"],
success: true,
message: `Created org unit: ${orgUnit.name}`,
orgUnitId: orgUnit.id,
orgUnit,
};
},
async update_org_unit(params: {
id: string;
name?: string;
shortName?: string | null;
sortOrder?: number;
isActive?: boolean;
parentId?: string | null;
}, ctx: ToolContext) {
const caller = deps.createOrgUnitCaller(deps.createScopedCallerContext(ctx));
const data = UpdateOrgUnitSchema.parse({
...(params.name !== undefined ? { name: params.name } : {}),
...(params.shortName !== undefined ? { shortName: params.shortName } : {}),
...(params.sortOrder !== undefined ? { sortOrder: params.sortOrder } : {}),
...(params.isActive !== undefined ? { isActive: params.isActive } : {}),
...(params.parentId !== undefined ? { parentId: params.parentId } : {}),
});
if (Object.keys(data).length === 0) {
return { error: "No fields to update" };
}
let orgUnit;
try {
orgUnit = await caller.update({ id: params.id, data });
} catch (error) {
const mapped = deps.toAssistantOrgUnitMutationError(error);
if (mapped) {
return mapped;
}
throw error;
}
return {
__action: "invalidate" as const,
scope: ["orgUnit"],
success: true,
message: `Updated org unit: ${orgUnit.name}`,
orgUnitId: orgUnit.id,
orgUnit,
};
},
};
}