refactor(api): extract assistant notifications slice

This commit is contained in:
2026-03-30 21:49:49 +02:00
parent fec4aa2e23
commit 18ba6fff9a
3 changed files with 938 additions and 743 deletions
@@ -0,0 +1,914 @@
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}.`,
};
},
};
}