GitHub
快速开始
通过 GitHub 蓝图,将经过验证的 webhook 入口和应用自有 API 行为添加到现有 Flue 项目中。在终端或你选择的编码智能体中运行以下命令:
flue add channel github
概述
该蓝图安装 @flue/github 和官方 @octokit/rest SDK。它会在 <source-root>/channels/github.ts 创建一个命名的 channel、项目自有的 Octokit client 和 issue 评论工具,然后将该工具连接到智能体。根据应用需求调整订阅的事件、分发的消息和工具。
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:
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。将两个凭据保存在项目现有的密钥系统中。
渠道模块
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 在内部确认,永远不会到达回调。
绑定工具
'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 绑定初始化凭据,并应验证其完整的目标构建。