跳转到内容

输入关键词开始搜索文档。

Notion

最后更新 查看 Markdown 源文件@flue/notion

快速开始

通过 Notion 蓝图,将经过验证的 webhook 入口和应用自有 API 行为添加到现有 Flue 项目中。在终端或你选择的编码智能体中运行以下命令:

flue add channel notion

概述

该蓝图安装 @flue/notion 和官方 @notionhq/client SDK,在源码根目录创建 channels/notion.ts 模块,包含命名的 channel 和项目自有的 client 导出,并修改选定的智能体以绑定页面检索工具。

src/channels/notion.ts(节选)
import { createNotionChannel } from '@flue/notion';
import { dispatch } from '@flue/runtime';
import { Client } from '@notionhq/client';
import { Assistant } from '../agents/assistant.ts';

export const client = new Client({ auth: process.env.NOTION_API_KEY! });

export const channel = createNotionChannel({
  webhookSecret: process.env.NOTION_WEBHOOK_SECRET!,
  async webhook({ payload }) {
    if (payload.type !== 'page.content_updated') return;
    const pageId = payload.data.page_id;
    if (!pageId) return;

    await dispatch(Assistant, {
      id: channel.instanceId(pageId),
      message: {
        kind: 'signal',
        type: 'notion.page.content_updated',
        body: pageId,
        attributes: {
          pageId,
          workspaceId: payload.workspace_id,
        },
      },
    });
  },
});

页面内容更新会被接收进绑定到该页面的智能体;其他事件类型被忽略。完整的生成模块还处理评论事件,并让绑定的智能体通过 Notion SDK 检索页面内容和添加评论。

挂载渠道

渠道仅在 app.ts 挂载的位置提供 HTTP 路由。挂载模块中命名导出的 channel:

src/app.ts
import { channel as notion } from './channels/notion.ts';

app.route('/channels/notion', notion.route());

channel.route() 是一个纯路由器工厂,相对于挂载路径提供渠道声明的路由。本指南中的 webhook 路径假定使用常规的 /channels/notion 挂载;不同的挂载路径会相应调整。分发目标智能体模块携带 'use agent' 指令——该指令负责注册,因此仅用于分发的智能体不需要自己的 HTTP 挂载。

配置

变量用途
NOTION_WEBHOOK_SECRET必需 — 验证入站 webhook 交付。
NOTION_API_KEY必需 — 认证出站 Notion SDK 调用。

它安装 @flue/notion 和官方 @notionhq/client SDK。蓝图创建一个渠道模块,导出命名的 channel 和项目自有的 client。

在 Notion 集成设置中配置 webhook URL:

https://example.com/channels/notion/webhook

Webhook 密钥和 API 密钥是单独的凭据。

渠道模块

src/channels/notion.ts
import { createNotionChannel } from '@flue/notion';
import { defineTool, dispatch, useModel } from '@flue/runtime';
import { Client } from '@notionhq/client';
import { Assistant } from '../agents/assistant.ts';

export const client = new Client({ auth: process.env.NOTION_API_KEY! });

export const channel = createNotionChannel({
  webhookSecret: process.env.NOTION_WEBHOOK_SECRET!,

  // 路径:/channels/notion/webhook
  async webhook({ payload }) {
    // `payload.type` 是 Notion 事件类型。
    // 过滤是应用策略:在 Notion 中配置你想要的事件。
    switch (payload.type) {
      case 'page.content_updated': {
        const pageId = payload.data.page_id;
        if (!pageId) return;

        await dispatch(Assistant, {
          id: channel.instanceId(pageId),
          message: {
            kind: 'signal',
            type: 'notion.page.content_updated',
            body: pageId,
            attributes: {
              pageId,
              workspaceId: payload.workspace_id,
            },
          },
        });
        return;
      }
      case 'comment.created': {
        // 评论事件可以触发操作。
        return;
      }
      default:
        return;
    }
  },
});

export function retrievePage(ref: { pageId: string }) {
  return defineTool({
    name: 'retrieve_notion_page',
    description: '检索绑定到此智能体的 Notion 页面内容。',
    async run() {
      const page = await client.pages.retrieve({ page_id: ref.pageId });
      const blocks = await client.blocks.children.list({ block_id: ref.pageId });
      return { output: { page, blocks: blocks.results } };
    },
  });
}

export function addComment(ref: { pageId: string }) {
  return defineTool({
    name: 'add_notion_comment',
    description: '在绑定到此智能体的 Notion 页面上添加评论。',
    input: v.object({ text: v.pipe(v.string(), v.minLength(1)) }),
    async run({ data: { text } }) {
      const result = await client.comments.create({
        parent: { page_id: ref.pageId },
        rich_text: [{ type: 'text', text: { content: text } }],
      });
      return { output: { commentId: result.id } };
    },
  });
}

每个经过验证的事件都会以其原生的 Notion webhook 负载转发。选择对哪些事件采取行动是应用策略——在 Notion 中配置 webhook 订阅,并在处理程序中根据 payload.type 进行分支。

绑定工具

src/agents/assistant.ts
'use agent';
import { useInitialData, useModel, useTool } from '@flue/runtime';
import { retrievePage, addComment } from '../channels/notion.ts';

const initialData = v.object({ pageId: v.string() });

export function Assistant() {
  useModel('anthropic/claude-haiku-4-5');
  const data = useInitialData<v.InferOutput<typeof initialData>>();
  if (!data) throw new Error('此智能体由 Notion 渠道分发创建。');
  useTool(retrievePage(data));
  useTool(addComment(data));
  return '审查 Notion 页面内容更新,并在适当时添加简洁的评论。';
}

Assistant.initialData = initialData;

模型选择评论内容。它不选择任意的 Notion 页面、凭据或 API 方法。工具接受来自可信应用代码的 pageId。

交付和运行时行为

Notion 交付是至少一次的。该渠道是无状态的,不会对事件进行去重。在不允许重复接收时,在分发之前在应用自有的持久存储中声明事件 ID。

该包在 Node 和 Cloudflare Workers 上运行,需要 Flue 的 nodejs_compat 设置。

参阅 @flue/notion README。