- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
- 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 体系里,不同编码代理(Macro 内置 in-memory agent、用户的 Cursor 账户、配对的 macrod 运行时)都会向客户端“自报”一组可调会话设置,例如模型、思考等级等。AgentConfigOptionDto就是这套设置在对外 SDK 中的统一结构。本文以该类型文档为核心骨架,结合仓库中的 Rust 投影实现、HTTP 适配器、测试用例与 TypeScript 生成代码,逐字段拆解它的类型契约、来源与使用方式,读完后你可以独立完成“发现设置 → 渲染 UI → 下发修改”的完整闭环。
一、类型定位:它是协议里的哪一层
原文档对该类型只有一句定义性描述:
AgentConfigOptionDto=
AgentConfigKindDto&{ category?: string | null; description?: string | null; id: string; name: string }One agent-advertised ACP session setting.
这短短几行包含三个关键信息,值得先展开:
- 它是 TypeScript 的交叉类型(intersection type),不是单一接口。
AgentConfigOptionDto= 通用外壳字段(id/name/description/category)交叉AgentConfigKindDto(形态相关的type、currentValue,以及 select 专属的options)。因此它同时具备“元数据”和“值状态”两类信息。 - 它是 ACP(Agent Client Protocol)语义的投影。仓库中的类型注释直接写明 “One agent-advertisedACPsession setting”,说明这些字段并非 Macro 自己发明的业务概念,而是把 ACP 的
SessionConfigOption转换成了稳定的对外词汇。 - 它是“被代理声明的”而不是“被 Macro 定义的”。字段注释里反复出现 “supplied by the agent”“advertised by the agent”“Opaque”,意味着 Macro 只做搬运与规范化,不解释具体语义,也不对取值做枚举约束。
AgentConfigOptionDto在仓库中有两处权威来源,互为镜像:
| 角色 | 文件 | 说明 |
|---|---|---|
| Rust 定义(真相源) | crates/agent_harness/src/inbound/capability_discovery.rs | 带utoipa::ToSchema的 DTO,用于生成 OpenAPI |
| TypeScript 定义(生成物) | packages/sdk/generated/agent-harness/types.gen.ts | 由@hey-api/openapi-ts自动生成,文件头即声明自动生成 |
| 文档页(本文主体) | AgentConfigOptionDto.md | 由类型定义派生的参考文档 |
在 Rust 侧,DTO 与领域对象之间还有一层转换:领域层用AgentCapabilities { config_options: Vec<SessionConfigOption> }表达同一份数据(crates/agent_harness/src/domain/capability_discovery.rs),HTTP 层再通过From<AgentCapabilities> for DiscoverAgentCapabilitiesResponse把它映射成AgentConfigOptionDto(同文件)。这条“ACP → 领域 → DTO → SDK”的链路,是理解每个字段含义的最佳切入点。
二、逐字段拆解
2.1 通用外壳字段(交叉部分)
| 字段 | 类型 | 是否必填 | 原文档描述 | 技术含义 |
|---|---|---|---|---|
id | string | 必填 | Opaque id used to change this setting. | 修改该设置时回传的不透明标识,对应 ACP 的option.id,最终用于session/set_config_option的configId |
name | string | 必填 | Display label supplied by the agent. | 代理给出的展示名,例如Model;UI 直接渲染,不做翻译映射 |
description? | string \| null | 可选 | Optional explanatory copy. | 补充说明文案,可能为空 |
category? | string \| null | 可选 | ACP semantic category, such asmodelorthought_level. | ACP 语义分类,用于客户端做分组/图标/排序等启发式处理 |
id的“不透明”(opaque)属性非常关键:文档没有承诺它一定是可枚举常量,虽然后测用例中确实出现了"model",但契约层面应视为字符串。仓库测试就断言了首个选项的id为"model"(crates/agent_harness/src/domain/capability_discovery/test.rs),这属于测试夹具的取值,而非类型保证。
category同样是“语义提示”而不是“值域约束”。原文档只举了model与thought_level两个例子。从源码结构看,它的取值是从 ACP 枚举经serde_json序列化后取字符串得到的:
category: option .category .as_ref() .and_then(|category| serde_json::to_value(category).ok()) .and_then(|value| value.as_str().map(str::to_owned)),这段代码位于 crates/folds/agent_fold/src/domain/session_config.rs。它意味着:能被序列化成字符串的分类会被保留,无法序列化的情形会退化为None(在 TS 中表现为undefined或null)。因此客户端必须容忍category缺失,不能把category === 'model'当作前提。
2.2 形态字段:AgentConfigKindDto 的两种联合成员
AgentConfigKindDto是一个以type为判别字段的联合类型(AgentConfigKindDto.md):
export type AgentConfigKindDto = { currentValue: string; // Current opaque value. options: Array<AgentConfigSelectOptionDto>; // Ordered values supplied by the agent. type: 'select'; } | { currentValue: boolean; // Current value. type: 'boolean'; };| 成员 | 字段 | 类型 | 含义 |
|---|---|---|---|
select | type | 'select' | 单值选择器 |
select | currentValue | string | 当前选中的不透明值,与options[].value同一取值空间 |
select | options | AgentConfigSelectOptionDto[] | 由代理给出、保持顺序的候选项 |
boolean | type | 'boolean' | 开关型设置 |
boolean | currentValue | boolean | 当前开/关状态 |
注意currentValue在两种成员下类型不同:select是string,boolean是boolean。这正是使用type做判别收窄(narrowing)的原因,也是不能把它统一当作字符串处理的根本原因。
2.3 候选项:AgentConfigSelectOptionDto
select.options的元素类型见 AgentConfigSelectOptionDto.md:
| 字段 | 类型 | 必填 | 含义 |
|---|---|---|---|
value | string | 必填 | 被选中时回传给代理的不透明值 |
name | string | 必填 | 展示标签 |
description? | string \| null | 可选 | 该取值的提供方说明 |
group? | string \| null | 可选 | 提供方给出的分组标题 |
value与currentValue的关系是“候选项集合中的一员”。文档没有要求currentValue一定出现在options中,也没有要求value全局唯一,因此比较时建议直接用严格相等,而不是依赖索引位置。
group字段的来源值得一提:ACP 允许 select 的选项以“分组”形式下发,Macro 在投影时会把分组拍平为一维数组,同时把组名写入每一项的group:
SessionConfigSelectOptions::Grouped(groups) => groups .iter() .flat_map(|group| { group .options .iter() .map(|option| project_select_option(option, Some(group.name.as_str()))) }) .collect(),见 crates/folds/agent_fold/src/domain/session_config.rs。因此客户端若要还原分组 UI,应按group字段做稳定分组(保留首次出现顺序),而不是假设数组本身是二维的。未分组场景下group为None。
三、从 ACP 到 DTO:投影过程的三个实现细节
理解投影实现,可以解释文档中那些“看起来没写”的边界行为。
3.1 只保留已识别的形态,未知形态被静默丢弃
session_config_options使用filter_map+project_option:
pub fn session_config_options(options: &[AcpConfigOption]) -> Vec<SessionConfigOption> { options.iter().filter_map(project_option).collect() }其中project_option对AcpConfigKind做match,Select与Boolean之外的分支直接return None;Select内部若options既不是Ungrouped也不是Grouped,同样返回None(crates/folds/agent_fold/src/domain/session_config.rs)。
结论很明确:configOptions是“Macro 能理解的子集”,而不是“代理宣称的全部”。当你看到数组长度小于代理实际能力时,这不是丢包,而是投影层的有意收敛。这一点在原类型文档中无法直接看出,但对排障非常重要。
3.2 两种“无能力”场景返回空数组而非报错
领域服务提供AgentCapabilities::unsupported(),返回config_options: Vec::new()(crates/agent_harness/src/domain/capability_discovery.rs)。也就是说,某些 provider 天生不提供会话设置时,HTTP 仍然是 200,只是configOptions: []。客户端应把空数组视作正常状态(隐藏设置面板),而不是异常。
3.3 DTO 转换保留了字段顺序与层级
From<AgentCapabilities>的实现把领域结构逐层克隆到 DTO:id、name、description、category原样传递,kind按Select/Boolean分支映射,options逐项映射(crates/agent_harness/src/inbound/capability_discovery.rs)。序列化层面,结构体标了#[serde(rename_all = "camelCase")],枚举内部又用rename_all_fields = "camelCase"并显式rename = "currentValue",这解释了为什么 Rust 侧叫current_value,而对外的 JSON/TS 是currentValue。
四、获取这些选项:POST /agent-capabilities/discover
AgentConfigOptionDto的数组并不是随便就能拿到的,它是能力发现接口响应体DiscoverAgentCapabilitiesResponse.configOptions的元素类型(types.gen.ts)。
4.1 请求结构
请求体DiscoverAgentCapabilitiesRequest(types.gen.ts):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
harness | CapabilityHarnessDto | 必填 | 要探测的 provider |
harnessId | string \| null | 视情况 | macrod必填,其它目标禁止传 |
model | string \| null | 可选 | 指定要检查哪个模型下的会话设置 |
harness的取值来自CapabilityHarnessDto,其枚举标注了#[serde(rename_all = "kebab-case")](crates/agent_harness/src/inbound/capability_discovery.rs),因此线上取值为字符串:
| 枚举成员 | 线上取值 | 含义 |
|---|---|---|
InMemory | in-memory | Macro 进程内 agent |
Cursor | cursor | 调用者自己的 Cursor 账户 |
Macrod | macrod | 已配对的 macrod 运行时 |
一个探测 Cursor 账户在特定模型下可调项的最小请求:
POST /agent-capabilities/discover Authorization: Bearer <token> Content-Type: application/json { "harness": "cursor", "model": "claude-sonnet-4-5" }响应示例(字段形态与文档一致,取值为示例数据):
{ "configOptions": [ { "id": "model", "name": "Model", "description": "The model this session runs on.", "category": "model", "type": "select", "currentValue": "fast", "options": [ { "value": "fast", "name": "Fast" }, { "value": "deep", "name": "Deep", "description": "Slower, more thorough." } ] }, { "id": "auto_approve", "name": "Auto approve tools", "category": null, "description": null, "type": "boolean", "currentValue": false } ] }注意description与category可以是null;type决定后面是否存在options。
4.2harnessId的强约束
领域服务的分发逻辑对(harness, harness_id)组合做了显式校验(crates/agent_harness/src/domain/capability_discovery.rs):
in-memory/cursor目标必须不传harnessId,否则BadRequest;macrod目标必须传harnessId,缺失时报harnessId is required for macrod;macrod目标在探测前会先经过HarnessCapabilityAccess::can_use授权检查,未通过直接Forbidden,不会触发一次无谓的运行时调用。
这点在测试中也有直接覆盖:macrod_authorizes_before_dispatch_and_projects_the_catalog验证了“先授权后分发”的顺序(crates/agent_harness/src/domain/capability_discovery/test.rs)。
4.3 错误码与 DTO 的关系
HTTP 适配器把领域错误映射为状态码(crates/agent_harness/src/inbound/capability_discovery.rs):
| 状态码 | 触发条件 | 客户端应如何处理 |
|---|---|---|
200 | 探测成功(含空数组) | 正常渲染设置 |
400 | harnessId与该 harness 组合非法 | 修正请求,不重试 |
401 | 未认证 | 重新登录 |
403 | 配对 harness 对调用者不可见 | 提示无权限 |
409 | macrod 运行时或 Cursor 账户未连接 | 引导用户连接后重试 |
504 | macrod 探测超时 | 可退避重试 |
502 | provider 探测失败 | 记录原始错误文本 |
其中超时是领域服务的构造参数之一(AgentCapabilitiesServiceImpl::new(..., timeout),同文件),由组合根注入,类型定义本身不承载该语义。
五、下发修改:与 AgentConfigOptionDto 配套的 AgentSetConfigOptionAction
拿到id与候选项value之后,修改设置依靠AgentSetConfigOptionAction(AgentSetConfigOptionAction.md):
AgentSetConfigOptionAction=
{ configId: string; value: string }Ask the agent to change one advertised select-style session setting.
| 字段 | 类型 | 说明 |
|---|---|---|
configId | string | 由代理声明的不透明 ACP config id,即AgentConfigOptionDto.id |
value | string | 该选项声明的某个 select 值,即options[].value |
这是一个两字段动作,正好与AgentConfigOptionDto的id和options[].value一一对应。原文档特意强调 “select-style”,说明该动作只适用于type: 'select'。boolean型设置没有配套的 DTO 动作类型,不应把布尔值塞进value字符串字段,否则会破坏契约。
在动作联合体中,该结构以type: 'setConfigOption'作为判别值(types.gen.ts):
export type AgentAction = (AgentPromptAction & { type: 'prompt' }) | (AgentSetModelAction & { type: 'setModel' }) | (AgentSetConfigOptionAction & { type: 'setConfigOption' }) | { type: 'compact' } | { type: 'stop' } | (AgentRespondElicitationAction & { type: 'respondElicitation' }) | (AgentPermissionAction & { type: 'respondToPermission' });请求示例:
{ "type": "setConfigOption", "configId": "model", "value": "deep" }5.1 服务端如何消费这个动作
Rust 领域层把动作转换为 ACP 的 JSON-RPC 请求。测试changing_the_model_persists_it_and_tells_the_running_agent明确断言了线上方法名为session/set_config_option,并且修改是持久化 + 通知运行中代理双写:
assert!( sent.iter().any(|message| matches!( message, ToRuntimeMessage::Acp(AcpMessage(RawJsonRpcMessage::Request(request))) if request.method.as_ref() == "session/set_config_option" )), "the running agent is told, got {sent:?}" );见 crates/agent_harness/src/domain/service/test.rs。这条测试原本针对“切换模型”场景,而模型切换在协议层走的正是同一个session/set_config_option通道——这也解释了为什么AgentConfigOptionDto的示例id常常是model。
5.2 队列中的不可编辑性
AgentSetConfigOptionAction进入动作队列后,属于不可编辑的一类。队列实现里对edit_prompt显式拒绝这些动作(crates/agent_harness/src/domain/queue.rs):
AgentAction::SetModel(_) | AgentAction::SetConfigOption(_) | AgentAction::Compact | AgentAction::Stop | AgentAction::RespondElicitation(_) | AgentAction::RespondToPermission(_) => Err(QueueError::NotEditable),因此 UI 层不应把“设置变更”当作可自由改写的草稿提示词;若要撤销,应重新提交一次相反取值的动作,而不是编辑历史条目。
六、TypeScript 侧的完整用法
生成的 SDK 已经封装了发现接口,方法名为discoverAgentCapabilitiesHandler,使用 Bearer 认证并固定Content-Type: application/json(packages/sdk/generated/agent-harness/sdk.gen.ts)。
6.1 发现并渲染
import type { AgentConfigOptionDto } from './generated/agent-harness/types.gen'; const { data } = await sdk.discoverAgentCapabilitiesHandler({ body: { harness: 'macrod', harnessId: '<paired-harness-uuid>' }, }); const options: AgentConfigOptionDto[] = data.configOptions; // 按 type 收窄后分别渲染 for (const option of options) { if (option.type === 'select') { renderSelect(option.id, option.name, option.currentValue, option.options); } else { // option.type === 'boolean' renderToggle(option.id, option.name, option.currentValue); } }option.type === 'select'分支内,currentValue会被收窄为string,并且options字段才存在;else分支中currentValue为boolean。这就是为什么文档把AgentConfigKindDto设计成带判别字段的联合类型,而不是可选字段的大对象。
6.2 按group还原分组
function groupOptions(options: AgentConfigSelectOptionDto[]) { const groups = new Map<string, AgentConfigSelectOptionDto[]>(); for (const opt of options) { const key = opt.group ?? ''; // 未分组归入空串分组 (groups.get(key) ?? groups.set(key, []).get(key)!).push(opt); } return groups; }由于投影层已经把 ACP 的分组拍平(见第 3 节),这里的分组是基于字段重建的,顺序依赖数组本身的有序性。
6.3 提交变更
await sdk.controlAgentSessionHandler({ body: { action: { type: 'setConfigOption', configId: option.id, value: chosen.value }, }, });上面示例中的方法名与请求外层字段属于调用方视角的示意;真正与本文类型契约绑定的是两点:动作对象必须携带type: 'setConfigOption',并且只包含configId与value两个字段。
七、容易被忽略的六个工程细节
- 不要枚举
id。id被定义为 “Opaque id”,具体取值由代理决定;测试夹具里的"model"只是夹具。 - 不要假设
currentValue一定在options里。文档未作此保证;UI 应先查找匹配项,找不到时降级展示原始值。 configOptions可能为空数组。AgentCapabilities::unsupported()就是这种形态,属于正常语义。- 未知形态会被丢弃。投影层只保留
select/boolean,见 session_config.rs。 boolean设置没有对应动作类型。AgentSetConfigOptionAction的value是string,文档也只承诺 select-style。macrod探测先鉴权再分发,因此403与409的语义不同:前者是权限问题,重试无益;后者是连接问题,可引导重连。
八、相关文档与源码索引
- 主类型文档:AgentConfigOptionDto
- 形态联合:AgentConfigKindDto
- 候选项结构:AgentConfigSelectOptionDto
- 配套动作:AgentSetConfigOptionAction
- DTO 定义:crates/agent_harness/src/inbound/capability_discovery.rs
- 领域服务与分发校验:crates/agent_harness/src/domain/capability_discovery.rs
- ACP 投影实现:crates/folds/agent_fold/src/domain/session_config.rs
- 领域测试:crates/agent_harness/src/domain/capability_discovery/test.rs
- 动作下发测试:crates/agent_harness/src/domain/service/test.rs
- 生成 SDK:packages/sdk/generated/agent-harness/sdk.gen.ts、types.gen.ts
- 后端
- 前端
- 协同办公
- 即时通讯
- 企业应用
- 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.
相关推荐
IB_Robot 统一模型推理架构:策略、感知、TTS 与抓取模型共享的 ModelRuntimeHandle 控制面
IB_Robot 统一模型推理架构:策略、感知、TTS 与抓取模型共享的 ModelRuntimeHandle 控制面 IB_Robot(openEuler E
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能Macro agent_trigger 深度解析:Agent 会话触发判定与 Prompt 上下文装配
Macro agent_trigger 深度解析:Agent 会话触发判定与 Prompt 上下文装配 本篇聚焦 Macro 仓库中的 agent_trigge
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能agent_trigger 深度解析:Macro 中 Agent 会话的触发判定与提示上下文装配
agent_trigger 深度解析:Macro 中 Agent 会话的触发判定与提示上下文装配 agent_trigger 是 Macro 里负责"要不要为一
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考