跳转到内容

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

GitHub

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

快速开始

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

flue add channel github

概述

该蓝图安装 @flue/github 和官方 @octokit/rest SDK。它会在 <source-root>/channels/github.ts 创建一个命名的 channel、项目自有的 Octokit client 和 issue 评论工具,然后将该工具连接到智能体。根据应用需求调整订阅的事件、分发的消息和工具。

src/channels/github.ts(节选)
import { Octokit } from '@octokit/rest';
import { createGitHubChannel } from '@flue/github';
import { dispatch } from '@flue/runtime';
import { Assistant } from '../agents/assistant.ts';

export const client = new Octokit({ auth: process.env.GITHUB_TOKEN });

export const channel = createGitHubChannel({
  webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
  async webhook({ delivery }) {
    if (delivery.name !== 'issue_comment' || delivery.payload.action !== 'created') return;
    const { repository, issue, comment, sender, installation } = delivery.payload;
    const issueRef = {
      owner: repository.owner.login,
      repo: repository.name,
      issueNumber: issue.number,
    };

    await dispatch(Assistant, {
      id: channel.instanceId(issueRef),
      // 在此事件创建实例时记录一次;之后忽略。
      initialData: {
        owner: issueRef.owner,
        repo: issueRef.repo,
        issueNumber: issueRef.issueNumber,
        openedBy: issue.user.login,
        title: issue.title,
      },
      message: {
        kind: 'signal',
        type: 'github.issue_comment.created',
        body: comment.body,
        attributes: {
          deliveryId: delivery.deliveryId,
          ...(installation === undefined ? {} : { installationId: String(installation.id) }),
          owner: issueRef.owner,
          repo: issueRef.repo,
          issueNumber: String(issueRef.issueNumber),
          sender: sender.login,
          title: issue.title,
          commentId: String(comment.id),
        },
      },
    });
  },
});

新创建的 issue 评论会被接收进绑定到该仓库和 issue 的智能体;其他经过验证的交付会收到空的成功响应。完整的生成模块还处理 pull request 审查评论,并让绑定的智能体通过 Octokit 发布 issue 或 pull request 评论。Cloudflare 目标保留项目的凭据约定,并在 Flue 的 nodejs_compat 配置下运行 Octokit 的 Fetch 路径。

挂载渠道

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

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

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

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

配置

变量用途
GITHUB_WEBHOOK_SECRET必需 — 验证入站交付。
GITHUB_TOKEN必需 — 认证出站 Octokit 调用。

它安装 @flue/github 用于经过验证的入口,以及官方 @octokit/rest SDK 用于出站 API 调用。它创建 src/channels/github.ts 并导出命名的 channel 和 client。

将 GitHub webhook URL 配置为:

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

如果 flue() 挂载在外部前缀下,请包含该前缀。将内容类型设置为 application/json(入口仅接受 JSON;不接受表单编码的交付),设置 webhook 密钥,并订阅应用处理的最小事件集。该示例使用 Issue comments 和 Pull request review comments。将两个凭据保存在项目现有的密钥系统中。

渠道模块

src/channels/github.ts
import { createGitHubChannel } from '@flue/github';
import { defineTool, dispatch } from '@flue/runtime';
import { Octokit } from '@octokit/rest';
import * as v from 'valibot';
import { Assistant } from '../agents/assistant.ts';

export const client = new Octokit({
  auth: process.env.GITHUB_TOKEN,
});

export const channel = createGitHubChannel({
  webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,

  // 路径:/channels/github/webhook
  async webhook({ delivery }) {
    // `delivery.name` 是 X-GitHub-Event 值,可将 `delivery.payload` 缩窄为
    // 原生 @octokit/webhooks-types 事件。过滤是应用策略:在 GitHub 中
    // 订阅你想要的事件,并在此处进行分支处理。
    if (delivery.name === 'issue_comment' && delivery.payload.action === 'created') {
      const { repository, issue, comment, sender, installation } = delivery.payload;
      const issueRef = {
        owner: repository.owner.login,
        repo: repository.name,
        issueNumber: issue.number,
      };
      await dispatch(Assistant, {
        id: channel.instanceId(issueRef),
        // 在此事件创建实例时记录一次;之后忽略。
        initialData: {
          owner: issueRef.owner,
          repo: issueRef.repo,
          issueNumber: issueRef.issueNumber,
          openedBy: issue.user.login,
          title: issue.title,
        },
        message: {
          kind: 'signal',
          type: 'github.issue_comment.created',
          body: comment.body,
          attributes: {
            deliveryId: delivery.deliveryId,
            ...(installation === undefined ? {} : { installationId: String(installation.id) }),
            owner: issueRef.owner,
            repo: issueRef.repo,
            issueNumber: String(issueRef.issueNumber),
            sender: sender.login,
            title: issue.title,
            commentId: String(comment.id),
          },
        },
      });
      return;
    }

    if (delivery.name === 'pull_request_review_comment' && delivery.payload.action === 'created') {
      const { repository, pull_request, comment, sender, installation } = delivery.payload;
      const issueRef = {
        owner: repository.owner.login,
        repo: repository.name,
        issueNumber: pull_request.number,
      };
      await dispatch(Assistant, {
        id: channel.instanceId(issueRef),
        // 在此事件创建实例时记录一次;之后忽略。
        initialData: {
          owner: issueRef.owner,
          repo: issueRef.repo,
          issueNumber: issueRef.issueNumber,
          openedBy: pull_request.user.login,
          title: pull_request.title,
        },
        message: {
          kind: 'signal',
          type: 'github.pull_request_review_comment.created',
          body: comment.body,
          attributes: {
            deliveryId: delivery.deliveryId,
            ...(installation === undefined ? {} : { installationId: String(installation.id) }),
            owner: issueRef.owner,
            repo: issueRef.repo,
            issueNumber: String(issueRef.issueNumber),
            sender: sender.login,
            title: pull_request.title,
            commentId: String(comment.id),
            // 回复附着在线程中的顶层审查评论上。
            threadId: String(comment.in_reply_to_id ?? comment.id),
            path: comment.path,
            ...(comment.line === null || comment.line === undefined
              ? {}
              : { line: String(comment.line) }),
          },
        },
      });
      return;
    }
  },
});

export function commentOnIssue(ref: { owner: string; repo: string; issueNumber: number }) {
  return defineTool({
    name: 'comment_on_github_issue',
    description: '在绑定到此智能体的 GitHub issue 或 pull request 上发表评论。',
    input: v.object({ body: v.pipe(v.string(), v.minLength(1)) }),
    async run({ data: { body } }) {
      const result = await client.rest.issues.createComment({
        owner: ref.owner,
        repo: ref.repo,
        issue_number: ref.issueNumber,
        body,
      });
      return { output: { commentId: result.data.id } };
    },
  });
}

每个经过验证的非 ping 交付都会以其原生的 @octokit/webhooks-types 负载转发。delivery.name 是 X-GitHub-Event 值,用于区分 delivery.payload,将其缩窄为匹配的事件类型(例如 name: 'issues' 给出 IssuesEvent 负载)。没有固定的支持事件列表,也没有规范化层:负载保留 GitHub 自己的字段名称和嵌套(payload.repository.owner.login、payload.issue.number、payload.comment.in_reply_to_id)。选择对哪些事件采取行动是应用策略——在 GitHub 中订阅它们,并在处理程序中根据 delivery.name(以及在需要时根据 delivery.payload.action)进行分支。GitHub ping 在内部确认,永远不会到达回调。

绑定工具

src/agents/assistant.ts
'use agent';
import { useInitialData, useModel, useTool } from '@flue/runtime';
import * as v from 'valibot';
import { commentOnIssue } from '../channels/github.ts';

const initialData = v.object({
  owner: v.string(),
  repo: v.string(),
  issueNumber: v.number(),
  openedBy: v.string(),
  title: v.string(),
});

export function Assistant() {
  useModel('anthropic/claude-haiku-4-5');
  const data = useInitialData<v.InferOutput<typeof initialData>>();
  if (!data) throw new Error('此智能体由 GitHub 渠道分发创建。');
  useTool(commentOnIssue(data));
  return `审查该 issue,并在适当时发布简洁的分类评论。"${data.title}" 由 ${data.openedBy} 创建。`;
}

Assistant.initialData = initialData;

initialData 是实例的创建数据:在事件创建实例时记录一次,之后忽略,因此渠道在每次分发时传递它。智能体的 initialData 静态属性在实例创建时验证分发的 initialData;useInitialData() 在每次渲染时返回解析后的值。Pull request 使用其 issue 编号进行 issue 评论。模型选择评论内容;可信代码绑定仓库和 issue。渠道 - 智能体的循环导入是被支持的,因为两个导入的绑定在延迟回调或智能体函数体内都是只读的。

GitHub 期望在十秒内收到 2xx 响应,且不会自动重试。该包不强制处理程序截止时间;将十秒窗口视为指导原则,快速承认持久工作,而不是阻塞响应。该渠道是无状态的,不会对交付 ID 进行去重,因此在不允许重复接收时,在分发之前在应用存储中声明 delivery.deliveryId。失败的交付可以在 GitHub 上使用相同的交付 ID 进行检查和手动重发。

Octokit 的 REST 方法使用 Fetch,该示例的类型化 issues.createComment() 操作已在 workerd 中使用 Flue 所需的 nodejs_compat 配置进行了测试。Cloudflare 项目可以通过 process.env 或类型化的 Worker 绑定初始化凭据,并应验证其完整的目标构建。

参阅 @flue/github README。