- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
- CRM
- AI Agent
- 人工智能
【免费下载链接】macro
Macro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — @-linked together with shared AI memory.
本文围绕 Macro 仓库中agent-harnessSDK 参考文档里ListAgentSessionPullRequestsResponses这一类型别名展开,讲清「列出某个 Agent 会话关联的 GitHub Pull Request」这条 API 的响应结构、字段语义、调用方式以及服务端实现链路。读完后你将能够准确地在 TypeScript 代码中使用该类型与相关 SDK 方法,并理解source、linkedBy、githubKey等字段在数据库与服务端代码中分别对应什么。
一、这个类型在 SDK 中的位置
ListAgentSessionPullRequestsResponses位于自动生成的 SDK 参考文档目录中:ListAgentSessionPullRequestsResponses.md。同目录下还有一组强相关的类型,它们共同构成这条接口的完整类型契约:
| 文档文件 | 作用 |
|---|---|
| ListAgentSessionPullRequestsData.md | 请求参数(路径参数)类型 |
| ListAgentSessionPullRequestsResponses.md | 按状态码索引的响应类型映射 |
| ListAgentSessionPullRequestsResponse.md | 从状态码映射中取出成功分支的联合类型 |
| ListAgentSessionPullRequestsErrors.md | 401/403/500 错误分支类型 |
| SessionPullRequestsResponse.md | 200 响应体本身 |
| SessionPullRequestLink.md | 单条关联记录 |
| PullRequestLinkSource.md | 关联来源枚举 |
值得注意的是,这些 Markdown 文件属于packages/sdk-docs生成产物,其对应的真实 TypeScript 类型位于 packages/sdk/generated/agent-harness/types.gen.ts,而调用方法在 packages/sdk/generated/agent-harness/sdk.gen.ts 中。也就是说,文档中的类型别名就是 OpenAPI → TypeScript 生成链路的最终形态,字段名与线上 JSON 完全一致。
二、响应类型逐层拆解
2.1 顶层:状态码 → 响应体映射
原文档给出的定义非常精简:
type ListAgentSessionPullRequestsResponses = { 200: SessionPullRequestsResponse; };它表达的是「以 HTTP 状态码为键」的响应映射。对应地,ListAgentSessionPullRequestsResponse用keyof取出这个映射的所有值,构成调用方实际拿到的联合类型:
type ListAgentSessionPullRequestsResponse = ListAgentSessionPullRequestsResponses[keyof ListAgentSessionPullRequestsResponses];这种「先按状态码建映射、再用keyof取并集」的写法是生成器常用模式:错误分支由另一个类型ListAgentSessionPullRequestsErrors承载,成功分支只有200一项,因此本条接口的成功类型实际上就是SessionPullRequestsResponse。
2.2 200 响应体:SessionPullRequestsResponse
type SessionPullRequestsResponse = { pullRequests: SessionPullRequestLink[]; };文档注释说明这是「与某个会话关联的 Pull Request 列表」,而pullRequests字段的描述是「Pull requests, oldest link first」,即按关联时间从早到晚排序。这一点在服务端 SQL 里有直接对应,后文会展开。
2.3 核心结构:SessionPullRequestLink
单个关联记录的字段最多,也是实际开发中最常被读取的部分:
type SessionPullRequestLink = { createdAt: string; githubKey: string; linkedBy?: string | null; source: PullRequestLinkSource; url: string; };| 字段 | 类型 | 是否必填 | 文档语义 |
|---|---|---|---|
createdAt | string | 是 | PR 与该会话建立关联的时间 |
githubKey | string | 是 | PR 的owner/repo/pull/number键 |
linkedBy? | string \| null | 否 | 由「人」关联时,记录关联者的 Macro 用户 ID |
source | PullRequestLinkSource | 是 | 谁把 PR 关联到该会话 |
url | string | 是 | PR 的 GitHub 链接 |
几个关键点需要强调:
githubKey与url不是同一份数据的两种写法那么简单。githubKey形如macro-inc/macro/pull/42,不含协议与域名;url是完整的https://github.com/...链接。服务端在读取时会由githubKey重新拼出url(见 2.5 节的实现证据),因此url始终是规范化后的形态。linkedBy是可选且可为null的。对于 Agent 自动关联的记录,不存在「关联人」,该字段为空;对于用户手动关联的记录,则保存了用户 ID。TypeScript 中必须写link.linkedBy ?? undefined之类的空值处理。createdAt在类型层面是string,它是 ISO-8601 时间字符串(服务端使用DateTime<Utc>序列化),使用时需自行new Date(createdAt)转换。
2.4 关联来源:PullRequestLinkSource
type PullRequestLinkSource = "agent" | "user";"agent":该 PR 是会话中的 Agent 自己打开的;"user":该 PR 是由某个人手动关联的。
这与服务端枚举一一对应。在 crates/agent_session/src/domain/pull_request_links.rs 中,PullRequestLinkSource被定义为Agent/User两个变体,并通过#[serde(rename_all = "snake_case")]序列化为小写下划线形式,恰好就是文档里的"agent"与"user"。
这个区分不是装饰性的,它带有明确的业务规则:Agent 建立的关联不能被用户解绑。接口的unlink文档直接写着「The pull request the session's agent opened stays linked.」,而数据库删除语句也限定source = 'user'。
2.5 请求与错误类型
请求侧极其简单,没有请求体也没有查询参数:
type ListAgentSessionPullRequestsData = { body?: never; path: { session_id: string }; query?: never; url: "/agent-sessions/{session_id}/pull-requests"; };session_id是 Agent 会话 UUID;body与query都标注为never,意味着生成的客户端不会序列化任何请求体——这是一条纯GET读取接口。错误侧:
type ListAgentSessionPullRequestsErrors = { 401: string; 403: string; 500: string; };三种错误都直接以字符串作为响应体,分别对应未认证、无权限、服务端异常。对照服务端 OpenAPI 声明(pull_requests.rs),确实只列出了 200/401/403/500 四类状态码,没有 404 —— 会话不存在或无权限会统一落在权限校验环节,而不是单独返回「未找到」。
三、如何在代码中调用
3.1 使用生成的底层 SDK 方法
在 packages/sdk/generated/agent-harness/sdk.gen.ts 中,方法签名如下:
public listAgentSessionPullRequests<ThrowOnError extends boolean = false>( options: Options<ListAgentSessionPullRequestsData, ThrowOnError> ): RequestResult< ListAgentSessionPullRequestsResponses, ListAgentSessionPullRequestsErrors, ThrowOnError > { return (options.client ?? this.client).get< ListAgentSessionPullRequestsResponses, ListAgentSessionPullRequestsErrors, ThrowOnError >({ url: '/agent-sessions/{session_id}/pull-requests', ...options }); }典型用法:
import { client } from "./generated/agent-harness/client.gen"; const { data, error } = await client.listAgentSessionPullRequests({ path: { session_id: "8f0f5c2e-..." }, throwOnError: false, }); if (error) { // error 的类型由 ListAgentSessionPullRequestsErrors 决定 console.error("failed to list PRs", error); } else { for (const link of data.pullRequests) { console.log( link.githubKey, // "owner/repo/pull/12" link.url, // "https://github.com/owner/repo/pull/12" link.source, // "agent" | "user" link.linkedBy ?? "-", // 人工关联者,可能为空 link.createdAt, // ISO-8601 字符串 ); } }注意data.pullRequests的顺序就是「最早关联的在前」,直接渲染列表即可,无需再次排序。
3.2 使用高层实体封装
如果使用 SDK 的实体层,AgentSession已经把响应映射成了更友好的对象。见 packages/sdk/src/entities/agent-sessions/agent-session.ts:
async pullRequests(): Promise<SessionPullRequest[]> { const { pullRequests } = unwrap( await this.client.agentHarness.listAgentSessionPullRequests({ path: { session_id: this.id }, }), ); return pullRequests.map((link) => ({ githubKey: link.githubKey, url: link.url, source: link.source, linkedBy: link.linkedBy ? User.byId(this.client, link.linkedBy) : undefined, createdAt: link.createdAt, })); }这段实现透露了两个工程细节:
session_id直接取自this.id,因此调用方只需持有AgentSession实例,不需要手动拼路径;linkedBy的字符串用户 ID 被就地转成了User引用对象,null/空值统一归一为undefined。这正是面对linkedBy?: string | null这种「可选且可空」字段的推荐处理方式。
配套的方法还有linkPullRequest(url)(PUT,返回 204)与unlinkPullRequest(url)(DELETE,url走 query 参数,返回 204),它们共享同一个路由/agent-sessions/{session_id}/pull-requests。
四、服务端实现链路与字段对应关系
4.1 路由与权限提取
路由定义在 pull_requests.rs:
Router::new() .route( "/{session_id}/pull-requests", get(list_session_pull_requests::<T, Access, Auth>) .put(link_session_pull_request::<T, Access, Auth>) .delete(unlink_session_pull_request::<T, Access, Auth>), )处理函数list_session_pull_requests的入参是一个AgentSessionAccessLevelExtractor<ViewAccessLevel, Access, Auth>,返回Json<SessionPullRequestsResponse>。也就是说:
- 读取只需
ViewAccessLevel,即可查看权限; - 关联/解绑需要
EditAccessLevel,即编辑权限。
这条权限边界直接解释了为什么错误列表里会出现 403:调用者对会话没有相应级别的权限时,请求在 extractor 阶段就被拒绝。
4.2 领域服务:只做透传
拿到访问凭据后,处理函数把access.entity_access_receipt交给state.service.session_pull_requests(...)。该能力由 traitSessionPullRequestLinks定义(pull_request_links.rs),实现落在SessionPullRequestLinkService:
async fn session_pull_requests( &self, access: &EntityAccessReceipt<ViewAccessLevel>, ) -> Result<Vec<SessionPullRequestLink>> { self.repo.session_pull_requests(session_id(access)?).await }session_id()会额外校验访问凭据的实体类型必须是EntityType::AgentSession,否则返回错误;然后把entity_id解析成 UUID 包装为AgentSessionId。这一步保证了「用别的实体(比如文档或任务)的凭据来查询 PR 列表」不会意外成立。
4.3 持久层:排序与原样返回
PostgreSQL 实现位于 crates/agent_session/src/outbound/postgres/pull_request.rs,关键 SQL 为:
SELECT github_key, source, linked_by, created_at FROM agent_session_pull_request WHERE agent_session_id = $1 ORDER BY created_at, github_key并逐行映射为响应结构:
SessionPullRequestLink { url: format!("https://github.com/{}", row.github_key), github_key: row.github_key, source: if row.source == "agent" { PullRequestLinkSource::Agent } else { PullRequestLinkSource::User }, linked_by: row.linked_by, created_at: row.created_at, }由此可以得到几条可验证的结论:
url确实是「由githubKey拼接而来」,不是存储的原始输入;- 排序键是
created_at,并以github_key作为稳定的次级排序,等价于文档所说的「oldest link first」; linked_by直接来自数据库列,TypeScript 侧的string | null与 SQL 的可空列严格对应;- 数据库中
source列用文本存储,读取时只有'agent'会被映射为Agent,其余一律视为User(这是一种宽松读取策略,写入侧则明确约束了取值)。
4.4 URL 规范化规则
githubKey之所以总是干净的owner/repo/pull/number,是因为写入前会先做规范化。规范化函数在 crates/agent_session/src/domain/pull_request.rs:
pub(crate) fn canonical_url(input: &str) -> Result<String> { let invalid = || AgentSessionError::InvalidPullRequestUrl; let (_, owner, repo, number) = lazy_regex::regex_captures!( r"\A(?i:https://github\.com)(?::443)?/([a-zA-Z0-9_.-]+)/([a-zA-Z0-9_.-]+)/pull/([0-9]+)/*(?:[?#][^\r\n]*)?\z", input ) .ok_or_else(invalid)?; let number: u64 = number.parse().map_err(|_| invalid())?; if number == 0 { return Err(invalid()); } Ok(format!("https://github.com/{owner}/{repo}/pull/{number}")) }从这段代码可以归纳出以下输入约束(对linkPullRequest的调用方尤其重要):
- 必须使用
https://github.com前缀,且协议大小写不敏感(正则里用了(?i:...)); - 允许显式携带
:443端口; - 路径必须严格是
/{owner}/{repo}/pull/{number},末尾多余斜杠会被吞掉; - 允许携带 query string 或 fragment(如
?diff=split#discussion_r123),但会被丢弃,不参与存储; number必须是正整数,/pull/0会被判定为非法;- 带凭据的 URL(例如
https://user:pass@github.com/...)不匹配该正则,直接被拒。
因此https://github.com/Owner/Repo/pull/12/files这类带子路径的 URL 是不合法的,调用方需要先裁剪。
五、与相邻接口的语义联动
这条读取接口在语义上和同组的其他几个接口是耦合的,理解它们才能正确使用source字段:
| 接口 | 方法 | 语义要点 |
|---|---|---|
/agent-sessions/{session_id}/pull-requests | GET | 列出会话关联的 PR(本篇主题) |
/agent-sessions/{session_id}/pull-requests | PUT | 以「人」的身份关联一个 PR,重复关联幂等 |
/agent-sessions/{session_id}/pull-requests?url= | DELETE | 解绑人工关联;Agent 开的 PR 保留 |
/agent-sessions/by-pull-request | POST | 反查:某个 PR 关联了哪些会话 |
/agent-sessions/by-pull-requests | POST | 批量反查,最多 100 个 URL |
其中几处细节值得记录:
- 用户关联是幂等的。SQL 使用
INSERT ... ON CONFLICT (agent_session_id, github_key) DO NOTHING,重复关联同一个 PR 不会产生第二条记录,也不会改变原有的source。 - Agent 关联优先级更高、且不能被降级。Agent 上报 PR 时使用
ON CONFLICT ... DO UPDATE SET source = 'agent', linked_by = NULL,即使此前是人工关联,也会被升级为 Agent 关联并清空关联人。这也解释了为什么解除人工关联只删source = 'user'的行。 - 反查接口做大小写不敏感匹配。
sessions_for_pull_request使用lower(github_key) = lower($1),而单会话列表接口则直接按agent_session_id返回存储原样的 key。 - 批量反查有上限。领域层常量
MAX_PULL_REQUESTS_PER_LOOKUP = 100(pull_request_links.rs),超出会返回TooManyPullRequests错误;批量响应中每个元素还会带上threadParent与originMessageId,用于说明「这个会话是从哪个频道/任务/CRM 记录的线程里发起的」。 - 批量反查只做一次权限判断/会话。实现里用
HashMap<AgentSessionId, bool>缓存可见性结果,避免同一会话因关联多个 PR 而重复鉴权。
如果你需要在 UI 中呈现「这个 PR 影响了哪些 Agent 会话」,用by-pull-requests;如果是要在会话详情面板列出「本次改动涉及的 PR」,就用本文的GET接口。
六、实战建议与易错点
1. 不要假设pullRequests为空等于「没有 PR」空数组只代表「没有关联记录」,不代表 Agent 没用过 PR。关联是靠 Agent 主动上报(set_pull_request)或用户手动关联建立的,属于显式行为。
2. 排序只在接口层保证,不要在下游再排序翻乱接口保证created_at升序;如果业务需要「最近关联在前」,请在客户端反转,而不是重新按githubKey排序。
3.linkedBy与source必须联合判断source === "agent"时linkedBy为空是正常现象,不应被视为数据缺失;反之source === "user"却拿到空linkedBy,才属于异常数据。参考实体的写法link.linkedBy ? User.byId(...) : undefined是稳妥做法。
4. 解绑要按「人」的语义理解用户点击「移除关联」时,如果该 PR 的source是"agent",DELETE 请求不会报错,但记录仍在。UI 上应据此禁用按钮或给出提示,避免用户误以为操作失败。
5. 关联前先规范化 URL由于canonical_url会拒绝/files、/commits等子路径与http://链接,前端在提交前应先裁剪成https://github.com/{owner}/{repo}/pull/{number},否则PUT会返回 400。
6. 身份认证不可省略GET请求依赖访问凭据来解析出AgentSessionAccessLevelExtractor<ViewAccessLevel, ...>,未认证会得到 401,权限不足会得到 403,二者在 SDK 的错误类型中被区分成两个字符串分支,便于分别处理登录跳转与权限提示。
七、如何验证这些行为
仓库中包含针对该领域逻辑的单元测试:crates/agent_session/src/domain/pull_request_links/test.rs。其中的StubLinks实现了SessionPullRequestLinkRepo,复刻了生产环境的三条关键规则:
link_pull_request在存在同会话同 key(大小写不敏感)记录时直接跳过,验证幂等;unlink_pull_request只在source == User时删除,验证「Agent 关联不可解绑」;session_pull_requests按会话过滤并组装SessionPullRequestLink,验证响应字段的构造方式。
服务端装配方面,可以在 services/agent_harness_service/src/main.rs 看到agent_session_pull_request_router的挂载位置,以及SessionPullRequestLinks服务的注入点。配合 services/agent_harness_service/src/api/swagger.rs 中列出的list_session_pull_requests、link_session_pull_request、unlink_session_pull_request、sessions_for_pull_request、sessions_for_pull_requests五个 OpenAPI 操作,就能完整还原从 SDK 类型文档到运行时路由的整条路径。
八、小结
ListAgentSessionPullRequestsResponses表面上只是「状态码映射到响应体」的一行类型别名,但它牵出的是 Macro Agent Harness 中一套完整的 PR 关联模型:
- 响应体
SessionPullRequestsResponse只有一个pullRequests数组,数组元素SessionPullRequestLink用githubKey承载稳定标识、用url承载可点击链接、用source+linkedBy表达关联主体、用createdAt表达时间顺序; - 请求侧只有路径参数
session_id,无 body、无 query,权限门槛是查看级别; - 服务端由
ViewAccessLevel提取器 + 领域服务 + PostgreSQL 单表查询三层组成,ORDER BY created_at, github_key落实了文档中的「oldest first」; - 写入路径经过严格的正则规范化,并用
ON CONFLICT保障幂等与「Agent 关联优先」的语义。
面向 SDK 使用者的最短结论是:调用listAgentSessionPullRequests({ path: { session_id } }),遍历data.pullRequests,并对linkedBy做空值兜底即可;而在设计「关联/解绑 PR」的交互时,务必记住source决定了这条记录能否被人为移除。
- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
- CRM
- AI Agent
- 人工智能
【免费下载链接】macro
Macro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — @-linked together with shared AI memory.
相关推荐
Macro agent-harness SDK 类型解析:`GetAgentSessionResponses` 与 `AgentSessionResponse` 响应契约全解
Macro agent harness SDK 类型解析: GetAgentSessionResponses 与 AgentSessionResponse 响应
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能Macro Agent Harness SDK 中 GetCodeExecutionErrors 错误契约解析
Macro Agent Harness SDK 中 GetCodeExecutionErrors 错误契约解析 本文聚焦 Macro 仓库中由 OpenAPI
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能Macro Agent Harness SDK 类型详解:ClassifyResponses 与 `POST /universal_input/classify` 响应契约
Macro Agent Harness SDK 类型详解:ClassifyResponses 与 POST /universal_input/classify
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考