概述
go-flashduty 是 Flashduty 官方开源的 Go 客户端,覆盖 Flashduty Open API 的每一个 REST 接口。它采用与 go-github 一致的设计风格——服务分组、类型化请求与响应、可组合传输层——并与 OpenAPI 规范保持严格 1:1:每个方法对应且仅对应一次 HTTP 调用,返回 (*T, *Response, error),不做任何跨接口的隐式聚合或增强。
SDK 的类型化接口由 Flashduty OpenAPI 规范生成,经单元测试覆盖,并针对线上 API 做过端到端验证。
SDK 故意保持”薄”。诸如短 ID 解析、跨接口编排等消费侧逻辑应放在调用方(CLI / MCP)中,而不是塞进 SDK 或滥用某个接口。这样 SDK 始终与 API 一一对应,可预测、可生成、可校验。
github.com/flashcatcloud/go-flashduty,包名为 flashduty,源码遵循 Apache-2.0 协议开源在 flashcatcloud/go-flashduty。
Open API 参考
全部接口的请求参数与响应字段说明。
命令行工具
在终端中直接操作 Flashduty 的 CLI。
安装
1
要求 Go 1.24+
请确认本地 Go 工具链版本不低于 1.24。
2
获取依赖
3
导入包
快速开始
以下是一个最小可运行示例:构造客户端、列出处于”已触发”状态的故障,并处理返回的三元组
(数据, *Response, error)。
创建客户端
NewClient 接收 app_key 与零到多个 Option。app_key 为空会直接返回错误。默认 Base URL 为 https://api.flashcat.cloud,默认 HTTP 超时为 30 秒,默认 User-Agent 为 go-flashduty。
私有化部署:用
WithBaseURL 将客户端指向您自有的 Flashduty 网关地址即可,其余调用方式完全不变。服务与方法
接口按服务分组挂在客户端上:调用约定统一为
client.<Service>.<Method>(ctx, req),返回 (*T, *Response, error)。例如 client.Incidents.List(ctx, req)、client.Sessions.Info(ctx, req)。
client.StatusPages.DraftCreate(POST /status-page/draft/create)把一次状态页事件草稿存下来,供人工在控制台审阅后发布,不会直接对外可见:draft 为任意 JSON,按原文存储、序列化后不超过 64 KB,其中 page_id、type(incident 或 maintenance)、name、message 会被校验;change_id(> 0 时表示追加到已有事件的一次更新)、status、affected_components 可选,新建 maintenance 时可用 start_time / end_time(Unix 秒)指定窗口。请求上的 source 是草稿来源标记(≤ 64 字符,如 ai_sre:sess_xxx);响应返回 draft_id(匹配 draft_[A-Za-z0-9]{22}),控制台的审阅链接即携带它。
client.Knowledge 对应 /safari/knowledge/* 的 9 个 API 操作:知识侧为 PackReadGet(获取账户知识)、PackReadList(列出知识)、PackWriteEnsure(确保知识存在)、PackWriteUpdate(变更知识作用域)、PackWriteDelete(删除知识);知识文件侧为 FileReadGet、FileReadList、FileWritePut(上传/覆盖)、FileWriteDelete。相关导出类型包括 KnowledgePackItem、KnowledgeFileItem、KnowledgeWarning 以及各 Knowledge*Request / Knowledge*Response。
client.Artifacts(AI SRE 产物)对应 /safari/artifact/* 的 11 个 API 操作:画廊读取侧 ReadGet(按 ID 获取单个已发布产物)、ReadList(分页列出调用方可见的产物,支持标题子串搜索、scope(all / personal / team)与 team_ids 过滤)、ReadGetFileState(批量探测会话展示文件(pf_ 前缀)是否已有上线产物,单次至多 50 个 ID);文件侧 ReadSign(为展示文件签发短期有效的下载/预览 URL,有效期 5 分钟,expires_in 固定 300 秒)与 ReadStream(凭签名 token 下载或预览文件,成功响应体是文件而非 JSON 信封,原始字节放在 Response.Raw);写入侧 WritePublish(把会话产生的文件发布为画廊产物)、WriteUpdate(重命名或转移个人/团队作用域)、WriteDelete(从画廊移除,源文件仍保留在会话中);公开分享 WriteShareEnable(开启匿名公开分享并返回公开链接,任何人凭链接即可查看、无需登录)、WriteShareRevoke(关闭分享,链接立即失效)、WriteShareSync(把公开快照刷新为最新内容——当 share_enabled 为 true 且 share_file_id 与 file_id 不一致时表示快照已过期,调用它刷新)。相关导出类型包括 PublishedArtifactItem、ArtifactShareState、SignedUrLs 以及各 Artifact*Request / Artifact*Response。
client.Members.MemberNotify(POST /member/notify,memberNotify)以调用方身份向账户成员发送邮件,仅可使用 AI SRE 会话凭据调用——其他凭据(包括普通 app_key)一律以 AccessDenied 拒绝(用 IsAccessDenied / ErrorCodeOf 判定)。请求字段:subject(必填,1–200 字符)与 html(必填,收件人看到的完整邮件正文,不超过 102,400 字节),person_ids 可选(至多 20 个且不可重复,省略或为空时只发送给调用方本人)。html 中的 script、meta、link、base 标签及 on* 事件属性会在发送前被静默清除;而 style、svg、iframe、object、embed、form、input、button 标签,没有 https src 的 img,或者链接不属于 http/https/mailto 的,会在入队前直接以 InvalidParameter 拒绝调用,并在错误信息中逐一列出触发的构造。投递是异步的,accepted 只表示邮件已入队;响应的 recipients[] 按收件人逐条返回(person_id 与 status),status 为 accepted 或 skipped,后者带 reason:not_member、no_email、email_disabled、duplicate、rate_limited(同一收件人每小时最多 20 封)、send_failed。响应只有 recipients[] 和可选的 agent_instructions 两个字段,不回显清洗后的邮件 HTML;当提交的 html 不符合默认邮件版式(缺少 max-width:600px 外层包裹表格)时会返回 agent_instructions,内容是写给调用的 AI SRE agent 的版式对齐指引——仅为建议,调用方有意选择的格式无需改动。
client.Diagnostics.QueryData 通过 POST /monit/query/data 执行同步查询,返回稳定的 query_result.v1 结构化结果(result.kind 为 frames / records / samples 之一),要求 monit-edge v0.65.0 及以上版本。日志模式和指标趋势分析统一使用 client.DataSources.ToolsInvoke,工具名称为 prometheus.metric_trends、loki.log_patterns 或 victorialogs.log_patterns。
client.Diagnostics.QueryExplore(POST /monit/query/explore,monit-read-query-explore)对已配置的数据源执行探索查询,返回数据源原生形态的 explore_result.v1:ExploreData.format 恒为 explore_result.v1,result.kind 取 frames(列式表格或时序,见 ExploreFrame / ExploreField)、samples(带标签的瞬时值 ExploreSample)或 logs(日志条目 ExploreLogEntry,最多 1000 条,被截断时 has_more 为 true、applied_limit 给出实际生效的上限),execution.effective_step_seconds 回显应用 max_data_points 与 min_step_seconds 之后实际使用的步长。请求字段:datasource_id(取自 /monit/datasource/list,必须是当前账户下的数据源)、expr(数据源原生语言的查询表达式,如 PromQL、LogsQL、SQL)、args(Grafana 风格宏变量替换的字符串键值;请求中必填,没有宏变量时传空表 map[string]string{}——Go 的 nil map 会序列化为 null,不符合 schema)与 execution(QueryExploreExecution:kind 取 instant / range / window,to_ms 在三种模式下都必填,range 另需 from_ms 与 max_data_points,window 另需 from_ms,min_step_seconds 仅在 range 下接受)。与 QueryData 的分工:需要数据源原生结果形态(含原始日志)时用 QueryExplore,需要稳定的 query_result.v1 契约时用 QueryData。该接口需要「数据源查看」(monit)权限,并要求受支持的部署运行 monit-edge v0.68.0 及以上版本。
client.Applications(RUM 应用)除应用管理外还覆盖 RUM 远程配置的 5 个方法:RemoteConfigReadGet(POST /rum/application/remote-config/get,读取当前生效的配置与 version)、RemoteConfigReadHistoryList(/rum/application/remote-config/history/list,分页列出已发布的历史版本)、RemoteConfigReadPreview(/rum/application/remote-config/preview,用模拟客户端的 env / app_version / sdk 上下文评估一份草稿配置,返回命中的 hit_rule_index 与最终下发的 values,不发布)、RemoteConfigWriteUpdate(/rum/application/remote-config/update,发布一份完整的新配置)、RemoteConfigWriteHistoryRevert(/rum/application/remote-config/history/revert,把历史版本的内容重新发布为新版本)。主要类型:RemoteConfig(enabled、activation、default、rules、custom、refresh_on_foreground)、RemoteConfigValues(四个可下调的 SDK 开关)、RemoteConfigRule(match 条件加 set 取值)、RemoteConfigHistoryItem(含 version、reason、content_hash、updated_by),以及 GetRemoteConfigRequest / ListRemoteConfigHistoryRequest / PreviewRemoteConfigRequest / UpdateRemoteConfigRequest / RevertRemoteConfigRequest 与各自的 *Response。应用本身的 WriteCreate / WriteUpdate / ReadInfo / ReadInfos / ReadList 还带 repositories 字段([]RUMApplicationRepository,仅存在于请求体,不是顶层标志):把应用关联到构建它的代码仓库,有序,第一项为主仓库,最多 10 项;每项为 Repo(owner/name 形式的 GitHub 仓库,必填)加可选的 Subdir(应用在仓库内所在的目录,相对仓库根目录,. 表示仓库根目录,空值保存为 .)。创建时传入即建立关联;更新时按 presence 处理——传入即整体替换、传空数组清空关联、省略则保持不变;ReadInfo / ReadInfos / ReadList 会把它回显在 RUMApplicationItem 上。关联本身不授予任何访问权限:AI 会话只能读取账户 GitHub App 安装已授权的仓库。该字段目前只在 API/SDK 层提供,控制台的应用管理页不暴露仓库关联设置。
client.DataSources.ToolsInvoke(POST /monit/datasource/tools/invoke,monit-datasource-tools-invoke)在某个已配置数据源上执行一个确定性工具:tool 名称由数据源类型前缀修饰(如 mysql.overview),params 为工具专属 JSON 参数(省略视为 {},显式 null 非法);除诊断工具外,入口支持 <type>.query 查询工具(prometheus、mysql、postgres、oracle、clickhouse、elasticsearch、loki、victorialogs、sls、tencent_cls),/monit/query/data 入口保持不变。该接口要求集群中所有在线可路由的 Edge 会话都支持 v0.71.0 基础调用协议(单个工具可能要求更新的实现),无工具目录、无自动重放、也不会回退到旧版 diagnose。请求体上限 128 KiB,完整成功响应上限 10 MiB,单工具超时至多 25 秒;响应为 DatasourceToolResult(data 为工具专属 JSON、永不为 null,summary 可选,出现 truncated 时其 reason 说明截断原因)。
调用 <type>.query 查询工具时用 NewDatasourceQueryInvokeRequest(datasourceID, tool, params) 构造请求,params 传对应的类型化结构(每个工具一种):PrometheusQueryParams、MySQLQueryParams、PostgresQueryParams、OracleQueryParams、ClickHouseQueryParams、ElasticsearchQueryParams、LokiQueryParams、VictoriaLogsQueryParams、SLSQueryParams、TencentCLSQueryParams。这些结构都由 expr 加 execution 组成(DatasourceQueryExecution:kind 取 instant / range / window,from_ms / to_ms 为 Unix 毫秒的 *int64;range 还要求 max_data_points,可选 min_step_seconds 抬高计算步长),可选字段都是指针——nil 时不发送该键,由服务端取默认值。查询工具的 params 不可为空:NewDatasourceQueryInvokeRequest(..., nil) 直接返回错误,与通用请求「省略即 {}」的规则不同。执行模式受数据源类型约束:Prometheus 与 Loki 用 instant / range(instant 只带 to_ms),SQL 类型(MySQL、Postgres、Oracle、ClickHouse 以及走 SQL 的 Elasticsearch)与 SLS、Tencent CLS 用 window,VictoriaLogs 原始日志用 window、统计查询用 instant / range;Loki、VictoriaLogs、SLS、Tencent CLS 的 limit / direction 只作用于原始日志检索,SLSQueryParams 另需 project / logstore,TencentCLSQueryParams 另需 region / topic_id / syntax。
client.DataSources.ReadPrometheusLabelValues(ctx, dataSourceID, labelName)(GET /monit/prometheus/api/v1/label/{label_name}/values,monit-prometheus-read-label-values)通过监控代理列出某个 Prometheus 兼容数据源上单个标签的全部取值:标签名放在 URL 路径上(SDK 会做路径转义),目标数据源由 X-DSID 请求头指定——SDK 用 dataSourceID 自动设置该头(传 0 或 labelName 为空都直接报错),ID 从 /monit/datasource/list 获取,必须是当前账户下 Prometheus 兼容类型的数据源。这是唯一一个成功响应体不是 Flashduty {request_id, data} 信封的方法:响应直接解码为 PrometheusLabelValuesResponse(status 为 success 时 data 是标签值字符串数组;为 error 时携带 errorType 与 error)。未到达数据源前的失败(X-DSID 缺失或非法、数据源不存在、数据源不是 Prometheus 类型)以 text/plain 返回,数据源自身的失败则用它自己的 JSON 错误形态;SDK 对两者一视同仁,把原始响应体原样放进 *ErrorResponse.Message。受支持的部署要求 monit-edge v0.35.0 及以上版本。
client.DataSources 的 payload 按 type_ident 选择类型专属配置块。当前允许的 type_ident 共 15 种:prometheus、loki、mysql、oracle、postgres、clickhouse、elasticsearch、sls、tencent_cls、victorialogs,以及新增的诊断专用类型 redis_node、redis_sentinel、mongodb_mongod、mongodb_mongos、kafka——诊断专用类型的 alerting_enabled 恒为 false(不阻止非告警查询与工具调用),且拒绝传 true。连接地址规则:Redis/MongoDB 诊断类型为单个 host:port(IPv6 需加方括号),不接受 URI、userinfo 或 query;kafka 为 1–32 个以逗号分隔、互不重复的 host:port bootstrap 地址(规范化后至多 4096 字符,payload 中不再有 broker 列表);mongodb_mongod / mongodb_mongos 的配置块中 auth_source 默认为 admin,用户名与密码必须成对配置,不支持客户端证书;Redis 节点配置的 database 默认为 0。诊断类型的 password 与 Kafka 的 tls_key 等敏感字段支持 ${env:NAME} 引用:响应中字面值会被省略(仅当存储值本身就是 ${env:...} 引用时才回显),更新时省略这些字段即保留原值,显式传空字符串则清除。
enabled 与 alerting_enabled 相互独立:enabled(业务执行开关)创建时默认 true,alerting_enabled(是否允许参与告警评估;告警还要求 enabled 为 true 且类型支持告警)创建时对告警类型默认 true、对诊断专用类型默认 false;二者更新时省略均保留当前值,显式传 null 非法;当有启用的告警规则引用该数据源时,将其 enabled 置为 false 会以 conflict 拒绝。另外 /monit/datasource/list 响应中 payload 恒为 null(列表查询不读 payload 列),create/update/info 响应中才会填充。
所有标识符、服务字段名与方法名均与生成代码保持一致。具体每个服务有哪些方法、请求与响应类型,请以
services_gen.go 与各服务文件,以及 Open API 参考 为准。响应时间戳
响应中的时间字段不再是裸整数,而是自描述的
Timestamp(Unix 秒)或 TimestampMilli(毫秒)类型(请求与响应共用的类型是例外,见下方警告)。它们序列化为本地时区的 RFC3339 字符串,因此 JSON、日志以及面向 LLM 的输出都直接可读;原始 epoch 仍只需一次方法调用即可取到。
- 序列化(出站):非零值序列化为带引号的 RFC3339 字符串(
TimestampMilli用 RFC3339Nano 以保留毫秒精度)。零值序列化为裸整数0——一个”未设置”哨兵,而非 1970 年的日期,并被json:",omitempty"丢弃。 - 反序列化(入站):既兼容数字 epoch(线上原始形态),也兼容 RFC3339 字符串(使序列化后的值可无损往返),还接受
null(→ 0)。
同样属于
AlertRuleV2(AlertRules.ReadInfoV2 / WriteCreateV2 / WriteUpdateV2 的返回类型)的 investigation_targets 也换了形状,仍在发送旧字段的调用方需要一并改:- 字段本身是
[]InvestigationTarget,最多 20 项且不允许重复;更新接口按 presence 处理——省略保留原配置,传[]清空。 - 每个入口是封闭的 tagged union,
Kind决定必须提供哪个子对象:dashboard(打开仪表盘面板)必须给Dashboard,query(打开 Explore 查询)必须给Query,提供另一个会被拒绝,入口内出现未知字段同样会被拒绝。 TimeRange(InvestigationTimeRange:BeforeSeconds与AfterSeconds)在每个已保存的入口上都必填——两个方向都非负,且至少一个大于 0。Dashboard入口(DashboardInvestigationTarget)的变量由原来的VariableBindings(键为仪表盘变量名,值为{source: event_label, key: 标签名})换成Variables:map[string]string,键为变量名、值为字面取值,可用{{ }}模板引用事件标签(旧类型InvestigationVariableBinding已不存在)。DashboardID与Variables必填,TargetID可选;DashboardID与TargetID都必须是规范的 UUIDv7。Query入口用QueryInvestigationTarget:DatasourceID(数据源 ID,必填)与Query(必填)都不可省,Query为DashboardQuery——Mode取instant/range/window,Expr是目标数据源原生语法的表达式且可用{{ }}模板,Args是原样透传的命名参数、不允许出现模板,MinStepSeconds仅在Mode为range时接受。
分页
所有列表接口共享
ListOptions,将其内嵌在请求结构体中即可。零值会被省略,不会覆盖服务端默认值(后端默认 p=1、limit=20)。
响应侧,
*Response 携带 Total、HasNextPage 与 SearchAfterCtx。推荐用 search-after 游标逐页遍历:
错误处理
Flashduty API 返回的未成功调用——无论是信封中携带了错误,还是 HTTP 状态非 2xx——都会返回
*ErrorResponse。它带有 Code、Message、可选的 Reason 与 RequestID 字段,排障时把 RequestID 提供给支持团队即可定位。Reason 携带服务端给出的可选原因(信封内的 DutyError 同样带有该字段,JSON 字段为 reason,omitempty);非空时它也会被追加到错误字符串末尾,形如 , reason X。
当 API 返回 429 时,错误被提升为 *RateLimitError:它内嵌 *ErrorResponse(所以 errors.As 取 *ErrorResponse 仍然成立),并额外带上 RetryAfter 提示。
对于生成接口,如果你收到的是带非 JSON 响应体的非 2xx 响应,SDK 会返回普通
error,而不是 *ErrorResponse。这表示响应来自网关、负载均衡器或代理等中间层,通常是请求超过了中间层超时。你可以重试请求,或将耗时较长的批量请求拆分为更小的批次;不要假定 errors.As(err, &apiErr) 能匹配此类错误。手写方法 client.DataSources.ReadPrometheusLabelValues 是例外:它不解析信封,而是把非 2xx 的原始响应体按原文放进 *ErrorResponse.Message 返回——平台的 text/plain 4xx/5xx 与数据源自身的 JSON 错误形态一视同仁,429 仍提升为 *RateLimitError。因此在这个方法上 errors.As(err, &apiErr) 是成立的,只是 Code 通常为空,需要读 Message 与 Response.StatusCode 判断原因。errors.As):
重试
核心客户端不内置自动重试。请通过传输层组合可选的
retry 子包——一个安全默认的重试型 http.RoundTripper。
github.com/flashcatcloud/go-flashduty/retry 的特性:
- 重试条件:HTTP 429、任意 5xx(状态码 ≥ 500),以及传输错误。其他 4xx 与所有 2xx/3xx 立即返回。
- 退避策略:确定性指数退避(
MinWait * 2^attempt,每次上限为MaxWait);无随机抖动。存在合法的整数Retry-After头时,以其为准(同样不超过MaxWait)。 - 安全回放:仅当请求体可回放(
req.Body为 nil 或req.GetBody非空)时才重试,每次重试都在请求的克隆上重建请求体,绝不修改调用方的原始*http.Request。SDK 构造的所有请求都设置了GetBody,因此 POST 请求体都可安全回放。 - 尊重取消:等待退避期间若请求 context 被取消,立即返回 context 错误。
流式导出
client.Sessions.Export 用于导出一个 AI SRE 会话的完整事件转录,返回的是 io.ReadCloser(NDJSON 流,application/x-ndjson),而非 JSON 信封。首行始终是一条 session_meta 信封,其后每行是一个会话事件;当 req.IncludeSubagents 为 true 时,每条 subagent_dispatch 行后会跟随子会话自身的完整事件流。
由于响应体可能很大,应当逐行读取并直接写入文件,不要把整段转录缓冲进内存。返回的 io.ReadCloser 是活动 HTTP 响应体,由调用方持有并必须 Close(defer 关闭即正确)。配合 NewExportScanner 可按行扫描,DecodeExportLine 可将一行解码为 ExportLine:
NewExportScanner 配置的单行缓冲区足以容纳转录中较宽的事件行(如 tool 输出、LLM 调用),不受默认 64KB token 上限限制。任何非 2xx 状态下,响应体仍是常规 JSON 错误信封——Export 会读取并关闭它,返回类型化错误(*ErrorResponse,429 时为 *RateLimitError),此时 io.ReadCloser 为 nil,与其他生成接口行为一致。