915 lines
32 KiB
TypeScript
915 lines
32 KiB
TypeScript
import type { TRPCContext } from "../../trpc.js";
|
|
import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js";
|
|
|
|
type AssistantToolErrorResult = { error: string };
|
|
|
|
type NotificationCategory = "NOTIFICATION" | "REMINDER" | "TASK" | "APPROVAL";
|
|
type NotificationPriority = "LOW" | "NORMAL" | "HIGH" | "URGENT";
|
|
type TaskStatus = "OPEN" | "IN_PROGRESS" | "DONE" | "DISMISSED";
|
|
type DeliveryChannel = "in_app" | "email" | "both";
|
|
type ReminderRecurrence = "daily" | "weekly" | "monthly";
|
|
type BroadcastTargetType = "user" | "role" | "project" | "orgUnit" | "all";
|
|
|
|
type NotificationRecord = {
|
|
id: string;
|
|
};
|
|
|
|
type TaskRecord = {
|
|
id: string;
|
|
taskStatus?: TaskStatus | null;
|
|
};
|
|
|
|
type ReminderRecord = {
|
|
id: string;
|
|
};
|
|
|
|
type BroadcastRecord = {
|
|
id: string;
|
|
recipientCount?: number | null;
|
|
};
|
|
|
|
type NotificationsTasksDeps = {
|
|
createNotificationCaller: (ctx: TRPCContext) => {
|
|
list: (params: { unreadOnly?: boolean; limit?: number }) => Promise<unknown>;
|
|
markRead: (params: { id?: string }) => Promise<void>;
|
|
unreadCount: () => Promise<number>;
|
|
create: (params: {
|
|
userId: string;
|
|
type: string;
|
|
title: string;
|
|
body?: string;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
category?: NotificationCategory;
|
|
priority?: NotificationPriority;
|
|
link?: string;
|
|
taskStatus?: TaskStatus;
|
|
taskAction?: string;
|
|
assigneeId?: string;
|
|
dueDate?: Date;
|
|
channel?: DeliveryChannel;
|
|
senderId?: string;
|
|
}) => Promise<NotificationRecord | null>;
|
|
listTasks: (params: {
|
|
status?: TaskStatus;
|
|
includeAssigned?: boolean;
|
|
limit?: number;
|
|
}) => Promise<unknown>;
|
|
taskCounts: () => Promise<unknown>;
|
|
getTaskDetail: (params: { id: string }) => Promise<unknown>;
|
|
updateTaskStatus: (params: { id: string; status: TaskStatus }) => Promise<TaskRecord>;
|
|
executeTaskAction: (params: { id: string }) => Promise<{
|
|
task: unknown;
|
|
actionResult: { message: string };
|
|
}>;
|
|
createReminder: (params: {
|
|
title: string;
|
|
remindAt: Date;
|
|
body?: string;
|
|
recurrence?: ReminderRecurrence;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
link?: string;
|
|
}) => Promise<ReminderRecord>;
|
|
listReminders: (params: { limit?: number }) => Promise<unknown>;
|
|
updateReminder: (params: {
|
|
id: string;
|
|
title?: string;
|
|
body?: string;
|
|
remindAt?: Date;
|
|
recurrence?: ReminderRecurrence | null;
|
|
}) => Promise<ReminderRecord>;
|
|
deleteReminder: (params: { id: string }) => Promise<void>;
|
|
createTask: (params: {
|
|
userId: string;
|
|
title: string;
|
|
body?: string;
|
|
priority?: NotificationPriority;
|
|
dueDate?: Date;
|
|
taskAction?: string;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
link?: string;
|
|
channel?: DeliveryChannel;
|
|
}) => Promise<TaskRecord | null>;
|
|
assignTask: (params: { id: string; assigneeId: string }) => Promise<TaskRecord>;
|
|
createBroadcast: (params: {
|
|
title: string;
|
|
targetType: BroadcastTargetType;
|
|
body?: string;
|
|
link?: string;
|
|
category?: NotificationCategory;
|
|
priority?: NotificationPriority;
|
|
channel?: DeliveryChannel;
|
|
targetValue?: string;
|
|
scheduledAt?: Date;
|
|
taskAction?: string;
|
|
dueDate?: Date;
|
|
}) => Promise<BroadcastRecord>;
|
|
listBroadcasts: (params: { limit?: number }) => Promise<unknown>;
|
|
getBroadcastById: (params: { id: string }) => Promise<unknown>;
|
|
delete: (params: { id: string }) => Promise<void>;
|
|
};
|
|
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
|
|
parseDateTime: (value: string, fieldName: string) => Date;
|
|
parseOptionalDateTime: (value: string | undefined, fieldName: string) => Date | undefined;
|
|
toAssistantTaskNotFoundError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantTaskActionError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantTaskAssignmentError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantBroadcastNotFoundError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantReminderNotFoundError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantNotificationReadError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantNotificationDeletionError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantReminderCreationError: (
|
|
error: unknown,
|
|
) => AssistantToolErrorResult | null;
|
|
toAssistantNotificationCreationError: (
|
|
error: unknown,
|
|
context: "notification" | "task" | "broadcast",
|
|
) => AssistantToolErrorResult | null;
|
|
};
|
|
|
|
export const notificationInboxToolDefinitions: ToolDef[] = [
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "list_notifications",
|
|
description: "List recent notifications for the current user.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
unreadOnly: { type: "boolean", description: "Only show unread. Default: false" },
|
|
limit: { type: "integer", description: "Max results. Default: 20" },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "mark_notification_read",
|
|
description: "Mark one notification as read, or all unread notifications when no notificationId is provided. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
notificationId: { type: "string", description: "Notification ID. Omit to mark all unread notifications as read." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_unread_notification_count",
|
|
description: "Count unread notifications for the current user.",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "create_notification",
|
|
description: "Create a notification or task-style notification for a specific user. Manager or admin role required. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
userId: { type: "string", description: "Target user ID." },
|
|
type: { type: "string", description: "Notification type code." },
|
|
title: { type: "string", description: "Title." },
|
|
body: { type: "string", description: "Optional body text." },
|
|
entityId: { type: "string", description: "Optional linked entity ID." },
|
|
entityType: { type: "string", description: "Optional linked entity type." },
|
|
category: { type: "string", enum: ["NOTIFICATION", "REMINDER", "TASK", "APPROVAL"] },
|
|
priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"] },
|
|
link: { type: "string", description: "Optional deep link." },
|
|
taskStatus: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"] },
|
|
taskAction: { type: "string", description: "Optional machine-readable task action." },
|
|
assigneeId: { type: "string", description: "Optional assignee user ID." },
|
|
dueDate: { type: "string", format: "date-time", description: "Optional due date." },
|
|
channel: { type: "string", enum: ["in_app", "email", "both"] },
|
|
senderId: { type: "string", description: "Optional sender override." },
|
|
},
|
|
required: ["userId", "type", "title"],
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
export const notificationTaskToolDefinitions: ToolDef[] = [
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "list_tasks",
|
|
description: "List tasks and approvals for the current user via the real notification router, optionally including tasks assigned to them.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
status: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"], description: "Optional task status filter" },
|
|
includeAssigned: { type: "boolean", description: "Include tasks where the current user is assignee as well as owner. Default: true." },
|
|
limit: { type: "integer", description: "Max results. Default: 20." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_task_counts",
|
|
description: "Get open, in-progress, done, dismissed, and overdue task counts for the current user.",
|
|
parameters: { type: "object", properties: {} },
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_task_detail",
|
|
description: "Get details of a specific task/notification including linked entity information.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
taskId: { type: "string", description: "Notification/task ID" },
|
|
},
|
|
required: ["taskId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "update_task_status",
|
|
description: "Update the status of a task. Mark as IN_PROGRESS, DONE, or DISMISSED.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
taskId: { type: "string", description: "Task/notification ID" },
|
|
status: { type: "string", enum: ["OPEN", "IN_PROGRESS", "DONE", "DISMISSED"], description: "New status" },
|
|
},
|
|
required: ["taskId", "status"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "execute_task_action",
|
|
description: "Execute the machine-readable action associated with a task. For example: approve a vacation, confirm an assignment, etc. The action is encoded in the task's taskAction field.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
taskId: { type: "string", description: "Task/notification ID containing the action to execute" },
|
|
},
|
|
required: ["taskId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "create_reminder",
|
|
description: "Create a personal reminder for the current user via the real notification router. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
title: { type: "string", description: "Reminder title" },
|
|
body: { type: "string", description: "Optional details" },
|
|
remindAt: { type: "string", format: "date-time", description: "When to remind (ISO 8601 datetime)" },
|
|
recurrence: { type: "string", enum: ["daily", "weekly", "monthly"], description: "Optional recurrence pattern" },
|
|
entityId: { type: "string", description: "Optional: linked entity ID (project, resource, etc.)" },
|
|
entityType: { type: "string", description: "Optional: entity type (project, resource, vacation, etc.)" },
|
|
link: { type: "string", description: "Optional deep link." },
|
|
},
|
|
required: ["title", "remindAt"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "list_reminders",
|
|
description: "List personal reminders for the current user via the real notification router.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
limit: { type: "integer", description: "Max results. Default: 20." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "update_reminder",
|
|
description: "Update a personal reminder via the real notification router. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string", description: "Reminder notification ID." },
|
|
title: { type: "string", description: "Optional reminder title." },
|
|
body: { type: "string", description: "Optional reminder body." },
|
|
remindAt: { type: "string", format: "date-time", description: "Optional reminder timestamp." },
|
|
recurrence: { type: ["string", "null"], enum: ["daily", "weekly", "monthly", null], description: "Optional recurrence update. Use null to clear recurrence." },
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "delete_reminder",
|
|
description: "Delete a personal reminder via the real notification router. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string", description: "Reminder notification ID." },
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "create_task_for_user",
|
|
description: "Create a task for a specific user via the real notification router. Manager or admin role required. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
userId: { type: "string", description: "Target user ID" },
|
|
title: { type: "string", description: "Task title" },
|
|
body: { type: "string", description: "Task description" },
|
|
priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"], description: "Priority (default NORMAL)" },
|
|
dueDate: { type: "string", format: "date-time", description: "Optional due date (ISO 8601)" },
|
|
taskAction: { type: "string", description: "Optional machine-readable action (format: action_name:entity_id)" },
|
|
entityId: { type: "string", description: "Optional linked entity ID" },
|
|
entityType: { type: "string", description: "Optional entity type" },
|
|
link: { type: "string", description: "Optional deep link." },
|
|
channel: { type: "string", enum: ["in_app", "email", "both"], description: "Delivery channel. Default: in_app." },
|
|
},
|
|
required: ["userId", "title"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "assign_task",
|
|
description: "Assign or reassign a task to another user via the real notification router. Manager or admin role required. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string", description: "Task notification ID." },
|
|
assigneeId: { type: "string", description: "User ID to assign." },
|
|
},
|
|
required: ["id", "assigneeId"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "send_broadcast",
|
|
description: "Create and send a broadcast notification via the real notification router. Manager or admin role required. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
title: { type: "string", description: "Notification title" },
|
|
body: { type: "string", description: "Notification body" },
|
|
targetType: { type: "string", enum: ["user", "role", "project", "orgUnit", "all"], description: "Target audience type" },
|
|
targetValue: { type: "string", description: "Target value: user ID, role name (ADMIN/MANAGER/CONTROLLER/USER/VIEWER), project ID, or org unit ID" },
|
|
category: { type: "string", enum: ["NOTIFICATION", "REMINDER", "TASK", "APPROVAL"], description: "Broadcast category. Default: NOTIFICATION." },
|
|
priority: { type: "string", enum: ["LOW", "NORMAL", "HIGH", "URGENT"], description: "Priority (default NORMAL)" },
|
|
channel: { type: "string", enum: ["in_app", "email", "both"], description: "Delivery channel (default in_app)" },
|
|
link: { type: "string", description: "Optional deep-link URL" },
|
|
scheduledAt: { type: "string", format: "date-time", description: "Optional scheduled send timestamp." },
|
|
taskAction: { type: "string", description: "Optional machine-readable task action for task-like broadcasts." },
|
|
dueDate: { type: "string", format: "date-time", description: "Optional due date for task-like broadcasts." },
|
|
},
|
|
required: ["title", "targetType"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "list_broadcasts",
|
|
description: "List notification broadcasts via the real notification router. Manager or admin role required.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
limit: { type: "integer", description: "Max results. Default: 20." },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "get_broadcast_detail",
|
|
description: "Get one notification broadcast via the real notification router. Manager or admin role required.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string", description: "Broadcast ID." },
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
type: "function",
|
|
function: {
|
|
name: "delete_notification",
|
|
description: "Delete one of the current user's own notifications via the real notification router. Always confirm first.",
|
|
parameters: {
|
|
type: "object",
|
|
properties: {
|
|
id: { type: "string", description: "Notification ID." },
|
|
},
|
|
required: ["id"],
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
export function createNotificationsTasksExecutors(
|
|
deps: NotificationsTasksDeps,
|
|
): Record<string, ToolExecutor> {
|
|
return {
|
|
async list_notifications(params: { unreadOnly?: boolean; limit?: number }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
return caller.list({
|
|
...(params.unreadOnly !== undefined ? { unreadOnly: params.unreadOnly } : {}),
|
|
...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}),
|
|
});
|
|
},
|
|
|
|
async mark_notification_read(params: { notificationId?: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
try {
|
|
await caller.markRead({
|
|
...(params.notificationId !== undefined ? { id: params.notificationId } : {}),
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantNotificationReadError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
message: params.notificationId ? "Notification marked as read." : "All unread notifications marked as read.",
|
|
};
|
|
},
|
|
|
|
async get_unread_notification_count(_params: Record<string, never>, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
const count = await caller.unreadCount();
|
|
return { count };
|
|
},
|
|
|
|
async create_notification(params: {
|
|
userId: string;
|
|
type: string;
|
|
title: string;
|
|
body?: string;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
category?: NotificationCategory;
|
|
priority?: NotificationPriority;
|
|
link?: string;
|
|
taskStatus?: TaskStatus;
|
|
taskAction?: string;
|
|
assigneeId?: string;
|
|
dueDate?: string;
|
|
channel?: DeliveryChannel;
|
|
senderId?: string;
|
|
}, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
const dueDate = deps.parseOptionalDateTime(params.dueDate, "dueDate");
|
|
|
|
let notification;
|
|
try {
|
|
notification = await caller.create({
|
|
userId: params.userId,
|
|
type: params.type,
|
|
title: params.title,
|
|
...(params.body !== undefined ? { body: params.body } : {}),
|
|
...(params.entityId !== undefined ? { entityId: params.entityId } : {}),
|
|
...(params.entityType !== undefined ? { entityType: params.entityType } : {}),
|
|
...(params.category !== undefined ? { category: params.category } : {}),
|
|
...(params.priority !== undefined ? { priority: params.priority } : {}),
|
|
...(params.link !== undefined ? { link: params.link } : {}),
|
|
...(params.taskStatus !== undefined ? { taskStatus: params.taskStatus } : {}),
|
|
...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}),
|
|
...(params.assigneeId !== undefined ? { assigneeId: params.assigneeId } : {}),
|
|
...(dueDate ? { dueDate } : {}),
|
|
...(params.channel !== undefined ? { channel: params.channel } : {}),
|
|
...(params.senderId !== undefined ? { senderId: params.senderId } : {}),
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantNotificationCreationError(error, "notification");
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
notification,
|
|
notificationId: notification?.id ?? null,
|
|
message: `Created notification "${params.title}".`,
|
|
};
|
|
},
|
|
|
|
async list_tasks(params: {
|
|
status?: TaskStatus;
|
|
includeAssigned?: boolean;
|
|
limit?: number;
|
|
}, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
return caller.listTasks({
|
|
...(params.status !== undefined ? { status: params.status } : {}),
|
|
...(params.includeAssigned !== undefined ? { includeAssigned: params.includeAssigned } : {}),
|
|
...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}),
|
|
});
|
|
},
|
|
|
|
async get_task_counts(_params: Record<string, never>, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
return caller.taskCounts();
|
|
},
|
|
|
|
async get_task_detail(params: { taskId: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
try {
|
|
return await caller.getTaskDetail({ id: params.taskId });
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantTaskNotFoundError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async update_task_status(params: { taskId: string; status: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
|
|
let task;
|
|
try {
|
|
task = await caller.updateTaskStatus({
|
|
id: params.taskId,
|
|
status: params.status as TaskStatus,
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantTaskNotFoundError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
task,
|
|
message: `Task status updated to ${task.taskStatus ?? params.status}.`,
|
|
};
|
|
},
|
|
|
|
async execute_task_action(params: { taskId: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
|
|
let result;
|
|
try {
|
|
result = await caller.executeTaskAction({ id: params.taskId });
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantTaskActionError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
task: result.task,
|
|
message: result.actionResult.message,
|
|
};
|
|
},
|
|
|
|
async create_reminder(params: {
|
|
title: string;
|
|
body?: string;
|
|
remindAt: string;
|
|
recurrence?: string;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
link?: string;
|
|
}, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
|
|
if (!params.title.trim()) {
|
|
return { error: "Reminder title is required." };
|
|
}
|
|
if (params.title.length > 200) {
|
|
return { error: "Reminder title must be at most 200 characters." };
|
|
}
|
|
if (params.body !== undefined && params.body.length > 2000) {
|
|
return { error: "Reminder body must be at most 2000 characters." };
|
|
}
|
|
if (
|
|
params.recurrence !== undefined
|
|
&& !["daily", "weekly", "monthly"].includes(params.recurrence)
|
|
) {
|
|
return {
|
|
error: `Invalid recurrence: ${params.recurrence}. Valid values: daily, weekly, monthly.`,
|
|
};
|
|
}
|
|
|
|
const remindAt = deps.parseDateTime(params.remindAt, "remindAt");
|
|
|
|
let reminder;
|
|
try {
|
|
reminder = await caller.createReminder({
|
|
title: params.title,
|
|
remindAt,
|
|
...(params.body !== undefined ? { body: params.body } : {}),
|
|
...(params.recurrence !== undefined ? { recurrence: params.recurrence as ReminderRecurrence } : {}),
|
|
...(params.entityId !== undefined ? { entityId: params.entityId } : {}),
|
|
...(params.entityType !== undefined ? { entityType: params.entityType } : {}),
|
|
...(params.link !== undefined ? { link: params.link } : {}),
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantReminderCreationError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
reminder,
|
|
reminderId: reminder.id,
|
|
message: `Reminder "${params.title}" created.`,
|
|
};
|
|
},
|
|
|
|
async list_reminders(params: { limit?: number }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
return caller.listReminders({
|
|
...(params.limit !== undefined ? { limit: Math.min(params.limit, 100) } : {}),
|
|
});
|
|
},
|
|
|
|
async update_reminder(params: {
|
|
id: string;
|
|
title?: string;
|
|
body?: string;
|
|
remindAt?: string;
|
|
recurrence?: ReminderRecurrence | null;
|
|
}, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
const remindAt = deps.parseOptionalDateTime(params.remindAt, "remindAt");
|
|
|
|
let reminder;
|
|
try {
|
|
reminder = await caller.updateReminder({
|
|
id: params.id,
|
|
...(params.title !== undefined ? { title: params.title } : {}),
|
|
...(params.body !== undefined ? { body: params.body } : {}),
|
|
...(remindAt ? { remindAt } : {}),
|
|
...(params.recurrence !== undefined ? { recurrence: params.recurrence } : {}),
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantReminderNotFoundError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
reminder,
|
|
reminderId: reminder.id,
|
|
message: `Updated reminder ${params.id}.`,
|
|
};
|
|
},
|
|
|
|
async delete_reminder(params: { id: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
try {
|
|
await caller.deleteReminder({ id: params.id });
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantReminderNotFoundError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
id: params.id,
|
|
message: `Deleted reminder ${params.id}.`,
|
|
};
|
|
},
|
|
|
|
async create_task_for_user(params: {
|
|
userId: string;
|
|
title: string;
|
|
body?: string;
|
|
priority?: string;
|
|
dueDate?: string;
|
|
taskAction?: string;
|
|
entityId?: string;
|
|
entityType?: string;
|
|
link?: string;
|
|
channel?: DeliveryChannel;
|
|
}, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
const dueDate = deps.parseOptionalDateTime(params.dueDate, "dueDate");
|
|
|
|
let task;
|
|
try {
|
|
task = await caller.createTask({
|
|
userId: params.userId,
|
|
title: params.title,
|
|
...(params.body !== undefined ? { body: params.body } : {}),
|
|
...(params.priority !== undefined ? { priority: params.priority as NotificationPriority } : {}),
|
|
...(dueDate ? { dueDate } : {}),
|
|
...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}),
|
|
...(params.entityId !== undefined ? { entityId: params.entityId } : {}),
|
|
...(params.entityType !== undefined ? { entityType: params.entityType } : {}),
|
|
...(params.link !== undefined ? { link: params.link } : {}),
|
|
...(params.channel !== undefined ? { channel: params.channel } : {}),
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantNotificationCreationError(error, "task");
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
task,
|
|
taskId: task?.id ?? null,
|
|
message: `Created task "${params.title}" for ${params.userId}.`,
|
|
};
|
|
},
|
|
|
|
async assign_task(params: { id: string; assigneeId: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
|
|
let task;
|
|
try {
|
|
task = await caller.assignTask(params);
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantTaskAssignmentError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
task,
|
|
taskId: task.id,
|
|
message: `Assigned task ${params.id} to ${params.assigneeId}.`,
|
|
};
|
|
},
|
|
|
|
async send_broadcast(params: {
|
|
title: string;
|
|
body?: string;
|
|
targetType: string;
|
|
targetValue?: string;
|
|
category?: NotificationCategory;
|
|
priority?: string;
|
|
channel?: string;
|
|
link?: string;
|
|
scheduledAt?: string;
|
|
taskAction?: string;
|
|
dueDate?: string;
|
|
}, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
const scheduledAt = deps.parseOptionalDateTime(params.scheduledAt, "scheduledAt");
|
|
const dueDate = deps.parseOptionalDateTime(params.dueDate, "dueDate");
|
|
|
|
let broadcast;
|
|
try {
|
|
broadcast = await caller.createBroadcast({
|
|
title: params.title,
|
|
targetType: params.targetType as BroadcastTargetType,
|
|
...(params.body !== undefined ? { body: params.body } : {}),
|
|
...(params.link !== undefined ? { link: params.link } : {}),
|
|
...(params.category !== undefined ? { category: params.category } : {}),
|
|
...(params.priority !== undefined ? { priority: params.priority as NotificationPriority } : {}),
|
|
...(params.channel !== undefined ? { channel: params.channel as DeliveryChannel } : {}),
|
|
...(params.targetValue !== undefined ? { targetValue: params.targetValue } : {}),
|
|
...(scheduledAt ? { scheduledAt } : {}),
|
|
...(params.taskAction !== undefined ? { taskAction: params.taskAction } : {}),
|
|
...(dueDate ? { dueDate } : {}),
|
|
});
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantNotificationCreationError(error, "broadcast");
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
broadcast,
|
|
broadcastId: broadcast.id,
|
|
recipientCount: broadcast.recipientCount ?? 0,
|
|
message: `Broadcast "${params.title}" created.`,
|
|
};
|
|
},
|
|
|
|
async list_broadcasts(params: { limit?: number }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
return caller.listBroadcasts({
|
|
...(params.limit !== undefined ? { limit: Math.min(params.limit, 50) } : {}),
|
|
});
|
|
},
|
|
|
|
async get_broadcast_detail(params: { id: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
try {
|
|
return await caller.getBroadcastById({ id: params.id });
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantBroadcastNotFoundError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async delete_notification(params: { id: string }, ctx: ToolContext) {
|
|
const caller = deps.createNotificationCaller(deps.createScopedCallerContext(ctx));
|
|
try {
|
|
await caller.delete({ id: params.id });
|
|
} catch (error) {
|
|
const mapped = deps.toAssistantNotificationDeletionError(error);
|
|
if (mapped) {
|
|
return mapped;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
return {
|
|
__action: "invalidate" as const,
|
|
scope: ["notification"],
|
|
success: true,
|
|
id: params.id,
|
|
message: `Deleted notification ${params.id}.`,
|
|
};
|
|
},
|
|
};
|
|
}
|