import type { Client, GetAsyncTaskResponse } from "@notionhq/client" import * as z from "zod" import { notionAccount, notionAction, notionOutput } from "../lib" const MEETING_NOTE_BASE_INPUT_SCHEMA = { language: z .enum([ "auto", "en", "zh-CN", "zh-TW", "es", "fr", "de", "ja", "ko", "pt", "ru", "th", "vi", "id", "da", "fi", "no", "nl", "it", "sv", "ar", "he", "pl", ]) .optional(), options: z .strictObject({ kickoff_summary: z.boolean().optional() }) .optional(), title: z.string().optional(), } const MEETING_NOTE_INPUT_SCHEMA = z.union([ z.strictObject({ ...MEETING_NOTE_BASE_INPUT_SCHEMA, parent: z.strictObject({ page_id: z.string().min(1), type: z.literal("page_id"), }), source: z.strictObject({ file_upload_id: z.string().min(1), type: z.literal("file_upload"), }), }), z.strictObject({ ...MEETING_NOTE_BASE_INPUT_SCHEMA, source: z.strictObject({ block_id: z.string().min(1), type: z.literal("block"), }), }), ]) const CREATE_PAGE_PARENT_SCHEMA = z.union([ z.strictObject({ page_id: z.string().min(1), type: z.literal("page_id").optional(), }), z.strictObject({ database_id: z.string().min(1), type: z.literal("database_id").optional(), }), z.strictObject({ data_source_id: z.string().min(1), type: z.literal("data_source_id").optional(), }), z.strictObject({ type: z.literal("workspace").optional(), workspace: z.literal(true), }), ]) const PAGE_POSITION_SCHEMA = z.union([ z.strictObject({ after_block: z.strictObject({ id: z.string().min(1) }), type: z.literal("after_block"), }), z.strictObject({ type: z.literal("page_start") }), z.strictObject({ type: z.literal("page_end") }), ]) const CREATE_PAGE_TEMPLATE_SCHEMA = z.union([ z.strictObject({ type: z.literal("none") }), z.strictObject({ timezone: z.string().optional(), type: z.literal("default"), }), z.strictObject({ template_id: z.string().min(1), timezone: z.string().optional(), type: z.literal("template_id"), }), ]) const MOVE_PAGE_PARENT_SCHEMA = z.union([ z.strictObject({ page_id: z.string().min(1), type: z.literal("page_id").optional(), }), z.strictObject({ data_source_id: z.string().min(1), type: z.literal("data_source_id").optional(), }), ]) const BLOCK_TYPE_SCHEMA = z.enum([ "embed", "bookmark", "image", "video", "pdf", "file", "audio", "code", "equation", "divider", "breadcrumb", "tab", "table_of_contents", "link_to_page", "table_row", "heading_1", "heading_2", "heading_3", "heading_4", "paragraph", "bulleted_list_item", "numbered_list_item", "quote", "to_do", "toggle", "template", "callout", "synced_block", "table", "column", ]) const BLOCK_CONTENT_KEYS = BLOCK_TYPE_SCHEMA.options const UPDATE_PAGE_TEMPLATE_SCHEMA = z.union([ z.strictObject({ timezone: z.string().optional(), type: z.literal("default"), }), z.strictObject({ template_id: z.string().min(1), timezone: z.string().optional(), type: z.literal("template_id"), }), ]) const UPDATE_MARKDOWN_TYPE_SCHEMA = z.enum([ "insert_content", "replace_content_range", "update_content", "replace_content", ]) /** Current response union missing from Notion SDK 5.26's create-page alias. */ type CurrentCreatePageResponse = | Awaited> | GetAsyncTaskResponse /** Current response union missing from Notion SDK 5.26's markdown alias. */ type CurrentUpdatePageMarkdownResponse = | Awaited> | GetAsyncTaskResponse /** Retrieves the current state and result of a Notion async task. */ export const getNotionAsyncTask = notionAction< Client["asyncTasks"]["retrieve"] >({ account: notionAccount.authenticated, allowedKeys: ["task_id"], call: async (client, input) => await client.asyncTasks.retrieve(input), description: "Retrieves the current state and result of an async Notion task.", name: "Get Notion async task", replaySafety: "safe", output: notionOutput.entity("async_task", [ "id", "status_url", "created_time", "operation", "status", ]), requiredKeys: ["task_id"], }) /** Retrieves a Notion block. */ export const getNotionBlock = notionAction({ account: notionAccount.readContent, allowedKeys: ["block_id"], call: async (client, input) => await client.blocks.retrieve(input), description: "Retrieves one block by ID.", name: "Get Notion block", replaySafety: "safe", output: notionOutput.entity("block"), requiredKeys: ["block_id"], }) /** Lists the direct children of a Notion block. */ export const listNotionBlockChildren = notionAction< Client["blocks"]["children"]["list"] >({ account: notionAccount.readContent, allowedKeys: ["block_id", "start_cursor", "page_size"], call: async (client, input) => await client.blocks.children.list(input), description: "Lists the direct children of a block with cursor pagination.", name: "List Notion block children", replaySafety: "safe", output: notionOutput.list("block"), requiredKeys: ["block_id"], }) /** Updates a Notion block using the current block payload variants. */ export const updateNotionBlock = notionAction< Client["blocks"]["update"], "archived" >({ account: notionAccount.updateContent, allowedKeys: [ "block_id", "embed", "type", "in_trash", "bookmark", "image", "video", "pdf", "file", "audio", "code", "equation", "divider", "breadcrumb", "tab", "table_of_contents", "link_to_page", "table_row", "heading_1", "heading_2", "heading_3", "heading_4", "paragraph", "bulleted_list_item", "numbered_list_item", "quote", "to_do", "toggle", "template", "callout", "synced_block", "table", "column", ], call: async (client, input) => await client.blocks.update(input), description: "Updates a block or moves it to or from the trash.", inputSchema: z.looseObject({ type: BLOCK_TYPE_SCHEMA.optional() }), name: "Update Notion block", replaySafety: "unsafe", output: notionOutput.entity("block"), refine: (input) => { const contentKeys = BLOCK_CONTENT_KEYS.filter( (key) => input[key] !== undefined, ) return ( contentKeys.length <= 1 && (input.type === undefined || contentKeys[0] === input.type) ) }, requiredKeys: ["block_id"], }) /** Moves a Notion block to the trash. */ export const deleteNotionBlock = notionAction({ account: notionAccount.updateContent, allowedKeys: ["block_id"], call: async (client, input) => await client.blocks.delete(input), description: "Moves one block to the trash.", name: "Delete Notion block", replaySafety: "unsafe", output: notionOutput.entity("block"), requiredKeys: ["block_id"], }) /** Appends child blocks at a supported position. */ export const appendNotionBlockChildren = notionAction< Client["blocks"]["children"]["append"], "after" >({ account: notionAccount.insertContent, allowedKeys: ["block_id", "children", "position"], call: async (client, input) => await client.blocks.children.append(input), description: "Appends child blocks to a block at an optional position.", name: "Append Notion block children", replaySafety: "unsafe", output: notionOutput.list("block"), requiredKeys: ["block_id", "children"], }) /** Creates a Notion meeting note. */ export const createNotionMeetingNote = notionAction< Client["blocks"]["meetingNotes"]["create"] >({ account: notionAccount.createMeetingNote, allowedKeys: ["title", "language", "options", "source", "parent"], call: async (client, input) => await client.blocks.meetingNotes.create(input), description: "Creates a meeting note from a supported source.", inputSchema: MEETING_NOTE_INPUT_SCHEMA, name: "Create Notion meeting note", replaySafety: "unsafe", output: notionOutput.entity("block"), requiredKeys: ["source"], }) /** Queries Notion meeting notes. */ export const queryNotionMeetingNotes = notionAction< Client["blocks"]["meetingNotes"]["query"] >({ account: notionAccount.readContent, allowedKeys: ["filter", "sort", "limit"], call: async (client, input) => await client.blocks.meetingNotes.query(input), description: "Queries meeting notes with provider filters and sorting.", name: "Query Notion meeting notes", replaySafety: "safe", output: notionOutput.fields("results", "has_more"), refine: (input) => input.limit === undefined || (Number.isInteger(input.limit) && input.limit >= 1 && input.limit <= 50), }) /** Creates a Notion page. */ export const createNotionPage = notionAction< Client["pages"]["create"], never, CurrentCreatePageResponse >({ account: notionAccount.insertContent, allowedKeys: [ "filter_properties", "parent", "properties", "icon", "cover", "content", "children", "markdown", "allow_async", "template", "position", ], call: async (client, input) => await client.pages.create(input), description: "Creates a page with properties and optional initial content.", inputSchema: z.looseObject({ parent: CREATE_PAGE_PARENT_SCHEMA.optional(), position: PAGE_POSITION_SCHEMA.optional(), template: CREATE_PAGE_TEMPLATE_SCHEMA.optional(), }), name: "Create Notion page", replaySafety: "unsafe", output: notionOutput.oneOf( { object: "page", requiredKeys: ["id"] }, { object: "async_task", requiredKeys: ["id", "status_url", "created_time", "operation", "status"], }, ), refine: (input) => !( (input.markdown !== undefined && (input.content !== undefined || input.children !== undefined)) || (input.template !== undefined && input.children !== undefined) ), }) /** Retrieves a Notion page. */ export const getNotionPage = notionAction({ account: notionAccount.readContent, allowedKeys: ["page_id", "filter_properties"], call: async (client, input) => await client.pages.retrieve(input), description: "Retrieves one page and its visible properties.", name: "Get Notion page", replaySafety: "safe", output: notionOutput.entity("page"), requiredKeys: ["page_id"], }) /** Retrieves one paginated Notion page property. */ export const getNotionPageProperty = notionAction< Client["pages"]["properties"]["retrieve"] >({ account: notionAccount.readContent, allowedKeys: ["page_id", "property_id", "start_cursor", "page_size"], call: async (client, input) => await client.pages.properties.retrieve(input), description: "Retrieves one page property, including paginated values.", name: "Get Notion page property", replaySafety: "safe", output: notionOutput.entity(["property_item", "list"], ["object"]), requiredKeys: ["page_id", "property_id"], }) /** Retrieves a Notion page as markdown. */ export const getNotionPageMarkdown = notionAction< Client["pages"]["retrieveMarkdown"] >({ account: notionAccount.readContent, allowedKeys: ["page_id", "include_transcript"], call: async (client, input) => await client.pages.retrieveMarkdown(input), description: "Retrieves page content as markdown with optional transcript text.", name: "Get Notion page markdown", replaySafety: "safe", output: notionOutput.entity("page_markdown", [ "id", "markdown", "truncated", "unknown_block_ids", ]), requiredKeys: ["page_id"], }) /** Updates properties or lifecycle state for a Notion page. */ export const updateNotionPage = notionAction< Client["pages"]["update"], "archived" | "is_archived" >({ account: notionAccount.updateContent, allowedKeys: [ "page_id", "filter_properties", "properties", "icon", "cover", "is_locked", "template", "erase_content", "in_trash", ], call: async (client, input) => await client.pages.update(input), description: "Updates a page's properties, icon, cover, lock, or trash state.", inputSchema: z.looseObject({ template: UPDATE_PAGE_TEMPLATE_SCHEMA.optional(), }), name: "Update Notion page", replaySafety: "unsafe", output: notionOutput.entity("page"), requiredKeys: ["page_id"], }) /** Moves a Notion page beneath a page or data source. */ export const moveNotionPage = notionAction({ account: notionAccount.updateContent, allowedKeys: ["page_id", "parent"], call: async (client, input) => await client.pages.move(input), description: "Moves a page to another page or data source parent.", inputSchema: z.strictObject({ page_id: z.string().min(1), parent: MOVE_PAGE_PARENT_SCHEMA, }), name: "Move Notion page", replaySafety: "unsafe", output: notionOutput.entity("page"), requiredKeys: ["page_id", "parent"], }) /** Applies a current markdown update operation to a Notion page. */ export const updateNotionPageMarkdown = notionAction< Client["pages"]["updateMarkdown"], never, CurrentUpdatePageMarkdownResponse >({ account: notionAccount.updateContent, allowedKeys: [ "page_id", "allow_async", "type", "insert_content", "replace_content_range", "update_content", "replace_content", ], call: async (client, input) => await client.pages.updateMarkdown(input), description: "Inserts, updates, or replaces page content using markdown.", inputSchema: z.looseObject({ type: UPDATE_MARKDOWN_TYPE_SCHEMA }), name: "Update Notion page markdown", replaySafety: "unsafe", output: notionOutput.oneOf( { object: "page_markdown", requiredKeys: ["id", "markdown", "truncated", "unknown_block_ids"], }, { object: "async_task", requiredKeys: ["id", "status_url", "created_time", "operation", "status"], }, ), refine: (input) => { const variantKeys = [ "insert_content", "replace_content_range", "update_content", "replace_content", ] as const const hasOnlyVariant = (variant: (typeof variantKeys)[number]) => variantKeys.every( (candidate) => (candidate === variant) === Object.hasOwn(input, candidate), ) switch (input.type) { case "insert_content": return ( hasOnlyVariant("insert_content") && !( input.insert_content.after !== undefined && input.insert_content.position !== undefined ) ) case "replace_content_range": return hasOnlyVariant("replace_content_range") case "update_content": return hasOnlyVariant("update_content") case "replace_content": return hasOnlyVariant("replace_content") } }, requiredKeys: ["page_id", "type"], })