☰
Sim 触发器开发规范:从目录结构到 Registry 注册的完整指南
2026/10/17 21:14:08 网站建设 项目流程

Sim 触发器开发规范:从目录结构到 Registry 注册的完整指南

【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000+ builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim

导读

本文基于 apps/sim/triggers/AGENTS.md 中的 Triggers Scope 规范,结合 Sim 开源仓库中 70+ 个服务、500+ 个触发器定义的源码实现,系统讲解 Sim 触发器的架构约定与开发方法论。读完本文,你将掌握:如何为一个新集成服务规划触发器目录与文件拆分、如何利用共享 helper 复用配置代码、如何设计对下游 blocks 友好且明确的输出结构,以及如何在TRIGGER_REGISTRY中注册触发器并遵循 ID 命名规范。

一、触发器在 Sim 中的定位

Sim 是用于构建、部署和监控 AI Agent 与工作流的协作平台。触发器(Trigger)是工作流执行的入口:它监听外部服务(GitHub、Slack、Linear、Jira、Stripe 等)或内部系统(Sim 工作区事件、表格新行)产生的事件,将事件数据转换为结构化输出,交给下游 blocks 处理。

从源码结构看,触发器体系由四个核心文件构成:

  • apps/sim/triggers/types.ts —— 定义TriggerConfig、TriggerOutput、TriggerRegistry类型契约;
  • apps/sim/triggers/registry.ts —— 集中注册全部触发器实例,是触发器的唯一事实来源;
  • apps/sim/triggers/index.ts —— 提供getTrigger、getAllTriggers、buildTriggerSubBlocks等查询与构建工具;
  • apps/sim/triggers/constants.ts —— 定义系统级 subblock ID、轮询 provider 集合、内部触发器 provider 集合等常量。

TRIGGER_REGISTRY中注册了 500+ 个触发器,覆盖 Airtable、Attio、Bitbucket、ClickUp、Confluence、Grain、HubSpot、Intercom、Jira、Notion、PagerDuty、QuickBooks、Salesforce、ServiceNow、Zendesk、Zoom 等大量 SaaS 服务,以及generic_webhook(通用 Webhook)、table_new_row(表格新行)、sim_workspace_event(Sim 工作区事件)等平台内置触发器。

二、目录结构与文件组织规范

AGENTS.md 明确规定:每个服务独立一个目录,使用 barrel exports,事件按文件拆分。

apps/sim/triggers/ ├── github/ # 服务目录 │ ├── index.ts # barrel export:统一导出该服务的所有触发器 │ ├── webhook.ts # 该服务的通用 webhook 触发器 │ ├── issue_opened.ts # 事件特定文件 │ ├── issue_closed.ts │ ├── pr_opened.ts │ ├── push.ts │ ├── ... │ └── utils.ts # 服务内共享工具 ├── linear/ │ ├── index.ts │ ├── webhook.ts │ ├── issue_created.ts │ ├── issue_updated.ts │ └── utils.ts ├── slack/ │ ├── index.ts │ ├── webhook.ts │ ├── oauth.ts # 基于 OAuth 凭据路由的触发器 │ ├── shared.ts │ └── capabilities.ts ├── registry.ts # 全局注册表 ├── types.ts # 类型契约 └── index.ts # 触发器查询/构建工具

以 apps/sim/triggers/github/index.ts 为例,它只是将事件特定文件逐一 re-export:

export { githubIssueClosedTrigger } from './issue_closed' export { githubIssueOpenedTrigger } from './issue_opened' export { githubPRMergedTrigger } from './pr_merged' export { githubPushTrigger } from './push' export { githubWebhookTrigger } from './webhook' // ...

这样做的收益在于:

  1. 单文件职责单一:每个事件触发器(如issue_opened.ts)只描述一个事件,便于 review 和测试;
  2. 导入路径稳定:外部只依赖@/triggers/github这个 barrel,新增事件不需要改动调用方;
  3. 按需加载友好:Tree-shaking 时只打包被引用的触发器。

三、TriggerConfig:一个触发器的完整契约

在 apps/sim/triggers/types.ts 中,每个触发器是一个TriggerConfig对象,字段如下:

字段类型说明
idstring触发器唯一 ID,遵循{provider}_{event}命名(见第六节)
namestring展示名称,如 "GitHub Issue Opened"
providerstring所属集成服务标识(如github、slack),用于身份校验与路由
descriptionstring一句话功能描述
versionstring触发器定义版本,如1.0.0
iconReact.ComponentType展示图标组件
subBlocksSubBlockConfig[]配置面板中的字段定义(下拉、输入框、说明文本等)
outputsRecord<string, TriggerOutput>触发器输出给下游 blocks 的数据结构
webhook{ method, headers }Webhook 请求方法(POST/GET/PUT/DELETE)与期望请求头
pollingboolean为true时表示基于轮询(cron 驱动)而非推送
deprecatedboolean为true时保留注册以兼容存量工作流,但从生成的文档中排除

其中deprecated字段的语义很关键:它专用于"被更新版本取代但旧工作流仍在引用"的触发器,例如 Grain 的 view-scoped 触发器在 v1 API 下线后仍保持注册,只是不再进入文档。

3.1 一个真实触发器剖析

以 apps/sim/triggers/github/issue_opened.ts 为例:

export const githubIssueOpenedTrigger: TriggerConfig = { id: 'github_issue_opened', name: 'GitHub Issue Opened', provider: 'github', description: 'Trigger workflow when a new issue is opened in a GitHub repository', version: '1.0.0', icon: GithubIcon, subBlocks: [ /* ... */ ], outputs: { /* ... */ }, webhook: { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-GitHub-Event': 'issues', 'X-Hub-Signature-256': 'sha256=...', }, }, }

webhook.headers中声明的X-GitHub-Event、X-GitHub-Delivery、X-Hub-Signature-256是 GitHub 投递事件时的真实请求头——AGENTS.md 要求"实现触发器前先研究服务的 webhook 模型",这正是研究结果在配置层面的落地:请求头声明既是文档,也是校验依据。

四、subBlocks:配置面板与多触发器类型切换

subBlocks描述触发器在画布/配置面板上的字段。AGENTS.md 特别要求:当集成模式需要时,主触发器必须支持在触发器类型之间切换。

4.1 selectedTriggerId 下拉切换机制

Sim 允许把同一服务的多个触发器合并进一个块,通过selectedTriggerId下拉在事件类型间切换。github_issue_opened.ts的subBlocks首字段就是这样的下拉,列出了该服务全部 11 个可选事件:

{ id: 'selectedTriggerId', title: 'Trigger Type', type: 'dropdown', mode: 'trigger', options: [ { label: 'Issue Opened', id: 'github_issue_opened' }, { label: 'Issue Closed', id: 'github_issue_closed' }, { label: 'PR Merged', id: 'github_pr_merged' }, { label: 'Code Push', id: 'github_push' }, // ... ], value: () => 'github_issue_opened', required: true, }

其余所有字段都通过condition: { field: 'selectedTriggerId', value: 'github_issue_opened' }与当前选中的事件绑定,只有选中对应事件时才显示该字段。从源码结构可以推断:这种"一个块内嵌多个触发器定义"的合并模式,是 Sim 保持画布简洁、同时支持丰富事件类型的关键设计。

4.2 命名空间机制:避免多触发器合并时的字段冲突

apps/sim/triggers/index.ts 的namespaceSubBlockId函数解决了合并冲突问题:当多个触发器被合并进同一块时,只对 display-only 的 subBlock(text类型或readOnly为true)且条件绑定selectedTriggerId的字段进行 ID 命名空间化(追加_${triggerId}后缀),而用户输入字段保持共享——这样切换触发器类型时,用户填写过的值(如 Webhook Secret)不会丢失:

if (subBlock.type === 'text' || subBlock.readOnly === true) { // display-only,若 condition 绑定 selectedTriggerId 则命名空间化 return { ...subBlock, id: `${subBlock.id}_${triggerId}` } } // 用户输入字段不命名空间化,值在切换时保留

同时SHARED_SUBBLOCK_IDS(当前含selectedTriggerId)被排除在命名空间化之外,因为它是切换的控制字段,本身就该共享。

4.3 系统级 subBlock 约定

apps/sim/triggers/constants.ts 定义了六类系统 subblock ID,它们提供 UI/UX 能力但不属于用户配置数据,不会被聚合进triggerConfig或作为用户字段校验:

ID用途
triggerCredentialsOAuth 凭据 subblock
triggerInstructions分步配置说明文本
webhookUrlDisplayWebhook URL 展示与复制
samplePayload示例 payload 展示
setupScript设置脚本代码(如 Apps Script)
scheduleInfo轮询触发器调度状态(下次/上次运行时间)

此外还有一组运行时元数据subblock ID(webhookId、triggerPath、triggerConfig、triggerId),它们保留在工作流状态中,但 diff 操作不得修改或清除——edit_workflow会拒绝写入这些 ID,防止 Copilot 或外部写入复活旧的配置结构。

五、共享 helper:减少同一服务内的重复配置

AGENTS.md 第三条要求:当同一服务的多个触发器需要 setup instructions、extra fields 或 output definitions 时,使用共享 helper。

5.1 buildTriggerSubBlocks

index.ts 中的buildTriggerSubBlocks提供了标准化的 subBlock 组装流水线:

export function buildTriggerSubBlocks(options: BuildTriggerSubBlocksOptions): SubBlockConfig[] { const { triggerId, triggerOptions, includeDropdown = false, setupInstructions, extraFields = [], fieldsBeforeWebhookUrl = [], providerWebhookUrl, webhookPlaceholder = 'Webhook URL will be generated', } = options // 1. 可选:selectedTriggerId 下拉 // 2. fieldsBeforeWebhookUrl(放在 URL 之前的字段) // 3. webhookUrlDisplay(只读、带复制按钮、condition 绑定 triggerId) // 4. extraFields(事件专属字段) // 5. triggerInstructions(分步配置说明) }

固定顺序为:Trigger Type 下拉 → 前置字段 → Webhook URL(只读 + 复制按钮)→ 事件专属字段 → Setup Instructions。这保证了同一服务内所有触发器的配置面板体验一致。

5.2 服务级共享文件

从目录结构可以观察到一个明显模式:多事件服务普遍带有webhook.ts、utils.ts、shared.ts这类共享文件。例如:

  • apps/sim/triggers/github/webhook.ts 提供通用的github_webhook(捕获任意 GitHub 事件),github/utils.ts承载公共的 outputs 定义与字段构造;
  • apps/sim/triggers/linear/webhook.ts 与linear/utils.ts同理,且 Linear 服务同时存在_v2后缀触发器(对应新版 webhook API),老版本通过deprecated标记退役;
  • apps/sim/triggers/slack/ 目录下的capabilities.ts、oauth.ts、shared.ts分别处理 Slack 能力探测、OAuth 凭据路由与共享逻辑。

5.3 自动注入 samplePayload

getTrigger在返回触发器配置时,会为 webhook/轮询类触发器自动注入一个只读的samplePayloadsubblock:调用generateMockPayloadFromOutputsDefinition(trigger.outputs)根据 outputs 定义生成 mock payload,并格式化为 JSON 展示(index.ts)。这正是 AGENTS.md 要求"outputs 定义明确"的直接收益——outputs 写得好,示例 payload 自动生成,无需人工维护。

六、Registry 注册与 ID 命名规范

AGENTS.md 最后一条要求:在triggers/registry.ts中注册触发器,ID 与集成命名方案保持一致。

6.1 注册方式

apps/sim/triggers/registry.ts 顶部从各服务 barrel 导入触发器,然后在TRIGGER_REGISTRY对象中按 ID 挂载:

import { githubIssueOpenedTrigger, githubPushTrigger, /* ... */ } from '@/triggers/github' // ... export const TRIGGER_REGISTRY: TriggerRegistry = { github_issue_opened: githubIssueOpenedTrigger, github_push: githubPushTrigger, // ...500+ 条 }

该文件共 1008 行,注册了 500+ 触发器。TriggerRegistry类型为Record<string, TriggerConfig>,isTriggerValid(triggerId)(index.ts)直接以triggerId in TRIGGER_REGISTRY判断合法性,因此漏注册 = 触发器不可用。

6.2 ID 命名方案

从 registry 可以归纳出明确的 snake_case 命名模式:{provider}_{event},动词位于事件名之前或之后均可,但必须与集成命名方案一致:

  • 事件型:github_issue_opened、linear_issue_created、notion_page_deleted、jira_sprint_started;
  • 对象型:salesforce_record_created、quickbooks_invoice_events;
  • 平台型:generic_webhook、table_new_row、sim_workspace_event、credential_group_event;
  • 轮询型:gmail_poller、google_calendar_poller、hubspot_poller、imap_poller、rss_poller(registry 中google_calendar_poller等 ID 即来自polling: true的触发器);
  • 凭据路由型:slack_oauth(按凭据路由而非按工作流 URL);
  • 版本后缀:linear_webhook_v2、grain_recording_added_v2(新 API 版本)。

注意gmail_poller这类轮询触发器在 registry 中的 ID 后缀为poller而非webhook,与常量文件中的轮询 provider 集合(gmail、google-calendar、google-drive、google-sheets、hubspot、imap、outlook、rss)一一对应。

七、触发器的三类运行模型

AGENTS.md 第一条要求"研究服务 webhook 模型",是因为 Sim 触发器按事件送达方式分为三类,实现前必须先判断服务属于哪一类:

7.1 Webhook 推送型(绝大多数)

服务向 Sim 的公开端点POST /api/webhooks/trigger/{path}推送事件。webhook-url.ts中的buildWebhookTriggerUrl负责拼接该 URL(${getBaseUrl()}/api/webhooks/trigger/${path})。配置面板中的webhookUrlDisplaysubblock 会展示这个可复制的 URL,配合triggerInstructions引导用户在服务控制台完成配置。

7.2 轮询型(cron 驱动)

Gmail、Google Calendar、Google Drive、Google Sheets、HubSpot、IMAP、Outlook、RSS 等服务不支持(或不适合)webhook 推送,采用定时轮询。constants.ts中的POLLING_PROVIDERS集合与isPollingWebhookProvider判断用于路由执行方式:轮询 provider 走完整任务队列(Trigger.dev),非轮询 provider 内联执行:

export const POLLING_PROVIDERS = new Set([ 'gmail', 'google-calendar', 'google-drive', 'google-sheets', 'hubspot', 'imap', 'outlook', 'rss', ])

这类触发器的 subBlocks 通常包含scheduleInfo(下次运行/上次运行时间)等调度展示字段。

7.3 内部触发型

credential-group、sim、table三个 provider 的触发器由 Sim 内部事件驱动(表格行事件、工作区事件),不经过外部 HTTP webhook。constants.ts中的INTERNAL_TRIGGER_PROVIDERS集合用于安全防护:它们的 webhook 行仍会注册路径,但公开触发路由必须拒绝投递——否则任何知道 block ID 的人都能伪造事件:

export const INTERNAL_TRIGGER_PROVIDERS = new Set(['credential-group', 'sim', 'table'])

八、Outputs:对下游 blocks 明确且有用

AGENTS.md 第四条要求"保持触发器输出对下游 blocks 明确且有用"(Keep trigger outputs explicit and useful for downstream blocks)。outputs定义是触发器与下游 blocks 之间的数据结构契约:

  • TriggerOutput支持嵌套结构、type声明与description描述,还可通过condition(OutputCondition)按触发器配置限制该输出在标签下拉中的可见性(types.ts);
  • 以github_issue_opened为例,其 outputs 完整描述了event_type、action、issue(含id/number/title/body/state/labels/assignees等)、repository(含full_name/stargazers_count/default_branch等)、sender五大部分,每个字段都有类型与语义说明;
  • 下游 blocks 通过 outputs 的字段路径引用事件数据;同时如前所述,outputs 还驱动samplePayload的自动生成。

从getTrigger的实现可以进一步确认:只有定义了outputs的 webhook/轮询触发器才会自动注入示例 payload,这强化了"outputs 必须明确"的规范动机。

九、触发器身份解析与路由

apps/sim/triggers/webhook-url.ts 中的resolveBlockTriggerId是触发器身份的唯一解析入口,它按优先级判定一个 block 实际部署为哪个触发器:

  1. 专用触发器 block:blockConfig.category === 'triggers'且类型合法,则 block 类型即触发器 ID;
  2. 工具 block 翻转模式:triggerMode为真时,读取selectedTriggerId子字段;
  3. 存量兼容:读取存储的triggerId子字段;
  4. 兜底:取配置声明的第一个可用触发器。

注释明确说明为何要"单源化":webhook 部署路径与任何推理 block 投递方式的层必须对同一问题给出相同答案,否则一个 block 可能按触发器 A 部署、却被另一层分类为触发器 B。

resolveBlockTriggerProvider则返回事件的投递身份(provider),注释强调:入站请求就是按这个身份做校验的——由slack路径服务的请求会校验 Slack 签名并解析 Slack 事件结构,因此两个不同 provider 的触发器无论如何相似,都不可能共享同一个 URL。这也解释了为什么注册 ID 的 provider 前缀必须准确。

十、开发一个新触发器的完整步骤清单

综合 AGENTS.md 的六条规则与仓库实现,为一个新服务添加触发器可按以下流程执行:

  1. 研究服务 webhook 模型:确认是推送型 webhook、轮询型还是内部事件型;确认事件结构、请求头、签名校验方式(对应规则 1);
  2. 创建服务目录:triggers/{service}/,目录内拆分webhook.ts(或poller定义)、各事件特定文件、utils.ts,并提供index.tsbarrel export(对应规则 2);
  3. 提取共享 helper:将 setup instructions、extra fields、outputs 定义放入共享文件,或使用buildTriggerSubBlocks组装,避免同服务内重复(对应规则 3);
  4. 实现触发器类型切换:若服务有多个事件,设置selectedTriggerId下拉,并让各字段通过condition绑定,使用户可在类型间切换(对应规则 4);
  5. 精确定义 outputs:逐字段声明类型与描述,供下游 blocks 引用并自动生成示例 payload(对应规则 5);
  6. 注册并保持命名一致:在registry.ts的TRIGGER_REGISTRY中挂载触发器,ID 遵循{provider}_{event}命名,轮询型用{provider}_poller(对应规则 6)。

总结

apps/sim/triggers/目录是 Sim 工作流生态的"入口层",其开发规范(AGENTS.md)虽然只有六条,但每条背后都有源码级的强约束:registry.ts的集中注册与 ID 命名、types.ts的TriggerConfig契约、index.ts的 subBlock 组装与命名空间机制、constants.ts的轮询/内部 provider 分类与运行元数据保护。理解这套规范,不仅能让你快速为 Sim 添加新的服务集成,也能理解为什么 Sim 能支撑 500+ 触发器定义而保持一致的画布体验与可靠的路由校验——这正是"规范先行、共享复用、输出明确"三原则的工程价值所在。

【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000+ builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询