Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions packages/open-agent-kernel/HANDOVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ COS bucket/oak-workspaces/
└─ createAgent(config)
├─ Session(会话实例,含 send / getHistory / respondApproval / abort)
│ └─ runClaudeQuery() → Claude Agent SDK → 流式 SDKMessage
│ └─ event-translator.tsSessionEvent(message_delta / tool_call / tool_result / ...)
│ └─ AcpStreamAdapterAcpSessionUpdate(agent_message_chunk / tool_call / tool_confirm / ...)
├─ SessionStore(可选,持久化会话)
│ └─ CloudBaseSessionStore → SessionStoreDriver
Expand Down Expand Up @@ -174,8 +174,12 @@ src/
├── runtime/
│ ├── agent-builder.ts # buildClaudeQueryOptions(薄封装 Claude SDK)
│ ├── credential-factory.ts # model → ANTHROPIC_BASE_URL / AUTH_TOKEN
│ ├── event-translator.ts # SDKMessage → SessionEvent 翻译
│ └── prompt-builder.ts # system prompt 构建
├── acp/
│ └── types.ts # ACP session/update 类型
├── adapters/
│ ├── acp-stream-adapter.ts # 默认 SDKMessage → AcpSessionUpdate
│ └── types.ts # StreamAdapter 接口
├── resources/
│ ├── credential-provider.ts # CloudBase AI gateway APIKey 加载
│ └── name-resolver.ts # envId → 集合名/函数名/网关 URL 派生
Expand Down Expand Up @@ -233,21 +237,23 @@ const agent = createAgent({
**文件**: `src/public/create-agent.ts`(内部 `createSession()`)

核心方法:
- `send(input)` → `AsyncIterable<SessionEvent>`(流式事件
- `send(input)` → `AsyncIterable<AcpSessionUpdate>`(ACP 更新流
- `getHistory(opts)` → `MessageRecord[]`(消息历史查询)
- `respondApproval(opts)` → 注入审批决策并 resume
- `abort()` → 终止会话 + 释放沙箱
- `getState()` → JSON 序列化的会话引用

### 4.3 SessionEvent流式事件类型
### 4.3 AcpSessionUpdate默认流式输出

```typescript
type SessionEvent =
| { type: 'message_delta'; text: string }
| { type: 'tool_call'; toolUseId: string; toolName: string; input: unknown }
| { type: 'tool_result'; toolUseId: string; output: unknown; isError: boolean }
| { type: 'session_idle'; reason: 'completed' | 'aborted' | 'error' }
| { type: 'error'; error: Error }
type AcpSessionUpdate =
| { sessionUpdate: 'agent_message_chunk'; content: { type: 'text'; text: string } }
| { sessionUpdate: 'tool_call'; toolCallId: string; title: string; input?: unknown }
| { sessionUpdate: 'tool_call_update'; toolCallId: string; status: string; result?: unknown }
| { sessionUpdate: 'tool_confirm'; toolCallId: string; toolName: string; input: Record<string, unknown> }
| { sessionUpdate: 'ask_user'; toolCallId: string; questions: unknown[] }
| { sessionUpdate: 'agent_phase'; phase: 'preparing' | 'model_responding' | 'tool_executing' | 'compacting' | 'idle' }
| { sessionUpdate: 'log'; level: 'info' | 'error' | 'success' | 'command'; message: string }
```

### 4.4 SessionStore — 会话持久化
Expand Down Expand Up @@ -293,7 +299,7 @@ acquire() → CreateSandboxTool + StartSandboxInstance → SandboxInstance
**范式**: 流终止 + 重新进入(跨进程友好)

```
send() → PreToolUse hook 检测到 requireApproval → 发 approval_required 事件 → 流终止
send() → PreToolUse hook 检测到 requireApproval → ACP tool_confirm → 流终止
业务层展示给用户,收集决策
Expand All @@ -309,7 +315,7 @@ PreToolUse hook 从 store 读到决策 → 放行/拒绝
核心类型:
- `Agent` / `Session` — Agent 和会话接口
- `AgentConfig` — createAgent 配置
- `SessionEvent` — 流式事件
- `AcpSessionUpdate` — 默认 ACP 更新流
- `MessageRecord` / `MessagePart` — 消息记录和部件
- `SessionStoreDriver` / `SessionMessageMeta` — 存储驱动接口
- `SandboxRuntime` / `SandboxInstance` — 沙箱接口
Expand Down
61 changes: 24 additions & 37 deletions packages/open-agent-kernel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Open Agent Kernel(OAK)适合在 Node.js 服务端中构建 CloudBase Agent
- 让 Agent 使用 CloudBase 数据库、云存储、云函数、静态托管等资源。
- 把会话、审批状态、附件、用户长期记忆持久化到 CloudBase。
- 运行远程 sandbox,让 Agent 具备文件系统、Shell、代码执行和 CloudBase MCP 工具能力。
- OAK 本身只提供协议中立的 `AsyncIterable<SessionEvent>`,开发者自行接入 ACP / AG-UI / SSE / 自定义协议
- OAK 默认输出 ACP `session/update` 语义(`AsyncIterable<AcpSessionUpdate>`),可直接接入 Web/SSE/JSON-RPC 消费方

## 安装

Expand Down Expand Up @@ -404,7 +404,7 @@ sandbox: {

### HITL 工具审批

配置 `permissions.requireApproval` 后,命中的工具调用会暂停并发出 `tool_approval_required` 事件
配置 `permissions.requireApproval` 后,命中的工具调用会暂停并发出 ACP `tool_confirm` 更新

```typescript
const agent = createAgent({
Expand All @@ -423,8 +423,8 @@ const agent = createAgent({

```typescript
for await (const event of session.send('删除测试数据')) {
if (event.type === 'tool_approval_required') {
// 展示审批 UI,并保存 event.toolUseId
if (event.sessionUpdate === 'tool_confirm') {
// 展示审批 UI,并保存 event.toolCallId
}
}

Expand Down Expand Up @@ -676,7 +676,7 @@ Agent / Session 运行时 API 详见文末 [API 参考](#api-参考)。
| `09-sandbox-shared.ts` | shared sandbox | `config.local.json`(含 `credentials`) |
| `10-sandbox-cloudbase-tools.ts` | sandbox 内 CloudBase MCP | `config.local.json`(含 `credentials`) |
| `11-hitl-approval.ts` | 单进程 HITL | `config.local.json` |
| `12-hitl-acp-adapter.ts` | ACP 风格审批适配 | `config.local.json` |
| `12-hitl-acp-adapter.ts` | 内置 ACP 审批流 | `config.local.json` |
| `13-hitl-distributed-cloudbase.ts` | 分布式 HITL | `config.local.json`(含 `credentials`) |
| `14-session-history.ts` | 历史查询 / 聚合验证 | `config.local.json` |
| `15-skills.ts` | Skills | `config.local.json` |
Expand Down Expand Up @@ -768,8 +768,8 @@ await agent.sessions.delete(session.id)
|------|------|------|
| `id` | `string` | 会话 ID / conversationId(只读)。 |
| `userId` | `string` | 所属用户 ID(只读)。 |
| `send(input)` | `(input) => AsyncIterable<SessionEvent>` | 发送用户消息,返回事件流。 |
| `respondApproval(opts)` | `(opts) => AsyncIterable<SessionEvent>` | 注入 HITL 审批决策后继续运行。 |
| `send(input)` | `(input) => AsyncIterable<AcpSessionUpdate>` | 发送用户消息,返回 ACP 更新流。 |
| `respondApproval(opts)` | `(opts) => AsyncIterable<AcpSessionUpdate>` | 注入 HITL 审批决策后继续运行。 |
| `getHistory(opts?)` | `() => Promise<MessageRecord[]>` | 获取聚合后的历史消息(供 UI 渲染)。 |
| `clearHistory()` | `() => Promise<void>` | 清除消息元数据索引,不影响 SDK transcript。 |
| `getState()` | `() => Promise<string>` | 序列化当前 RunState 为 JSON。 |
Expand All @@ -788,8 +788,8 @@ await agent.sessions.delete(session.id)
```typescript
// 文本消息
for await (const event of session.send('你好')) {
if (event.type === 'message_delta') process.stdout.write(event.text)
if (event.type === 'session_idle') break // 本轮结束
if (event.sessionUpdate === 'agent_message_chunk') process.stdout.write(event.content.text)
if (event.sessionUpdate === 'agent_phase' && event.phase === 'idle') break
}

// 带附件
Expand All @@ -806,13 +806,13 @@ for await (const event of session.send({

#### `respondApproval(opts)`

收到 `tool_approval_required` 事件后,收集用户决策并继续运行:
收到 ACP `tool_confirm` 更新后,收集用户决策并继续运行:

```typescript
for await (const event of session.send('删除这个集合')) {
if (event.type === 'tool_approval_required') {
if (event.sessionUpdate === 'tool_confirm') {
for await (const resumed of session.respondApproval({
toolUseId: event.toolUseId,
toolUseId: event.toolCallId,
decision: { kind: 'allow', scope: 'once' }, // kind: allow | deny;scope: once | session | forever
})) {
// 决策注入后的后续事件
Expand All @@ -837,34 +837,21 @@ await session.snapshotWorkspace?.() // 手动触发 cwd 快照
const status = await session.getRestoreStatus?.() // 'full' | 'fresh' | 'partial' | 'failed' | null
```

### `SessionEvent`
### `AcpSessionUpdate`

`send()` 和 `respondApproval()` 返回的 `AsyncIterable<SessionEvent>` 中,常见事件类型
`send()` 和 `respondApproval()` 返回 `AsyncIterable<AcpSessionUpdate>`。常见更新类型

| 事件 | 含义 | 典型处理 |
|------|------|----------|
| `message_delta` | 模型输出增量文本 | 流式渲染到 UI |
| `message_complete` | 模型输出完整文本 | 落盘 / 展示最终回复 |
| `tool_call` | Agent 发起工具调用 | 展示工具名和参数 |
| `tool_result` | 工具执行结果 | 展示工具输出 |
| `tool_approval_required` | 工具需要人工审批 | 调 `respondApproval()` |
| `handoff` | 子 Agent 切换 | 展示 handoff 信息 |
| `session_idle` | 本轮运行结束 | `reason`: `completed` / `requires_action` / `aborted` / `error` |
| `error` | 运行错误 | 展示 `error.message` |
| `sessionUpdate` | 含义 | 典型处理 |
|-----------------|------|----------|
| `agent_message_chunk` | 模型输出增量文本 | 流式渲染 `content.text` |
| `tool_call` | Agent 发起工具调用 | 展示 `title` 和 `input` |
| `tool_call_update` | 工具参数或结果更新 | 根据 `status` 渲染进度 / 结果 |
| `tool_confirm` | 工具需要人工审批 | 调 `respondApproval()` |
| `ask_user` | Agent 主动向用户提问 | 收集回答后调 `respondAskUser()` |
| `agent_phase` | Agent 阶段变化 | `phase='idle'` 表示本轮空闲 |
| `log` | 运行日志 / 错误 | 展示 `message` |

```typescript
type SessionEvent =
| { type: 'message_delta'; text: string }
| { type: 'message_complete'; text: string }
| { type: 'tool_call'; toolUseId: string; toolName: string; input: unknown }
| { type: 'tool_result'; toolUseId: string; toolName: string; output: unknown; isError: boolean }
| { type: 'tool_approval_required'; toolUseId: string; toolName: string; input: unknown; runStateJson: string; hints?: {...} }
| { type: 'handoff'; fromAgent: string; toAgent: string }
| { type: 'session_idle'; reason: 'completed' | 'requires_action' | 'aborted' | 'error' }
| { type: 'error'; error: Error }
```

OAK 只提供协议中立的 `AsyncIterable<SessionEvent>`;接入 SSE / ACP / AG-UI 时由业务层做事件映射。
ACP 是 OAK 的默认输出协议;常规 `createAgent()` 无需传 `streamAdapter`。

## 常见问题

Expand Down
116 changes: 116 additions & 0 deletions packages/open-agent-kernel/docs/architecture-acp-stream-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Open Agent Kernel:内置 ACP 流式输出方案

> 文档状态:已落地(2026-06,v2 对齐标准 ACP 1.0.0)
> 适用范围:`@cloudbase/open-agent-kernel` 的 `session.send()` / `respond*()` 流式输出

## 决策

OAK 默认输出 ACP `session/update` 语义,**标准 variant 直接复用 `@agentclientprotocol/sdk@^1.0.0` 的 `SessionUpdate` 类型**,OAK 扩展定义在 `src/acp/types.ts` 并明确标注:

```typescript
const agent = createAgent({ envId, model, credentials })
const session = await agent.startSession({ userId: 'u1' })

for await (const update of session.send('你好')) {
// update: AcpSessionUpdate = SessionUpdate | OAK extensions
}
```

用户不需要声明 `streamAdapter`。`createAgent()` 内部默认使用内置 `AcpStreamAdapter`。

## 架构

```text
Claude Agent SDK query()
-> SDKMessage stream
-> AcpStreamAdapter
-> AsyncIterable<AcpSessionUpdate>
```

关键点:

- 标准 variant(13 个)直接来自 `@agentclientprotocol/sdk` 的 `SessionUpdate`。
- OAK 扩展 variant(6 个)定义在 `src/acp/types.ts`,处理标准 ACP 不覆盖的场景。
- 不对外暴露 raw `SDKMessage`。
- 不再公开 `SessionEvent`。
- `streamAdapter` 仅作为高级覆盖入口保留。

## SessionUpdate variant 清单

### 标准 variant(来自 `@agentclientprotocol/sdk@1.0.0`)

| `sessionUpdate` | 类型 | 说明 |
|-----------------|------|------|
| `user_message_chunk` | `ContentChunk` | 用户消息流式回放 |
| `agent_message_chunk` | `ContentChunk` | 模型文本输出 |
| `agent_thought_chunk` | `ContentChunk` | 模型思考流(替代旧 `thinking`) |
| `tool_call` | `ToolCall` | 工具调用开始(`rawInput` / `kind: ToolKind` / `locations`) |
| `tool_call_update` | `ToolCallUpdate` | 工具状态/结果更新(`rawOutput` / `content[]`) |
| `plan` | `Plan` | 执行计划 |
| `plan_update` | `PlanUpdate` | 计划更新 |
| `plan_removed` | `PlanRemoved` | 计划移除 |
| `available_commands_update` | `AvailableCommandsUpdate` | 可用斜杠命令 |
| `current_mode_update` | `CurrentModeUpdate` | 当前模式切换 |
| `config_option_update` | `ConfigOptionUpdate` | 配置选项更新 |
| `session_info_update` | `SessionInfoUpdate` | 会话信息更新 |
| `usage_update` | `UsageUpdate` | token / cost 用量 |

### OAK 扩展 variant(非标准,payload 兼容标准形状)

| `sessionUpdate` | 说明 | 对标标准概念 |
|-----------------|------|-------------|
| `request_permission` | HITL 审批请求(stop-and-resume 模式) | `session/request_permission` JSON-RPC(payload 镜像 `RequestPermissionRequest`) |
| `ask_user` | AskUserQuestion 问询(stop-and-resume) | `session/elicitation`(OAK 用独立 variant) |
| `log` | 错误 / 状态消息 | 无标准对应 |
| `artifact` | 部署产物通知 | 无标准对应 |
| `history_page` | 历史分页回放 | `session/load` 请求-响应(OAK 用 sessionUpdate 推一页) |
| `agent_phase` | 执行阶段指示器 | 无标准对应 |

## 主要映射(SDK → ACP)

| SDK 来源 | ACP `sessionUpdate` |
|----------|---------------------|
| `stream_event.content_block_delta.text_delta` | `agent_message_chunk` |
| `stream_event.content_block_delta.thinking_delta` | `agent_thought_chunk` |
| `stream_event.content_block_start.tool_use` | `tool_call`(`rawInput` / `kind: toolKindFromName()`) |
| `stream_event.input_json_delta` | `tool_call_update`(`rawInput`) |
| `assistant.tool_use` replay | `tool_call` 或 `tool_call_update`(去重) |
| `user.tool_result` | `tool_call_update`(`rawOutput` / `content[]`) |
| OAK approval/client-tool sentinel | `request_permission`(`toolCall: ToolCallUpdate` + `options: PermissionOption[]`) |
| OAK askUser sentinel | `ask_user` |
| `result` | `usage_update` + `agent_phase: idle` |

## `_meta.oak` 扩展命名空间

OAK 专有数据通过标准 `_meta.oak.*` 扩展传递,不污染标准字段:

- `_meta.oak.parentToolCallId` — 子 agent 工具链父 ID
- `_meta.oak.assistantMessageId` — SSE 关联用 assistant 消息 ID
- `_meta.oak.planContent` — ExitPlanMode 计划内容

## 文件结构

```text
packages/open-agent-kernel/src/
├── acp/
│ ├── index.ts # re-export 标准 + OAK 类型
│ └── types.ts # OAK 扩展定义
├── adapters/
│ ├── acp-stream-adapter.ts # SDKMessage → AcpSessionUpdate
│ ├── index.ts
│ └── types.ts
├── public/
│ ├── create-agent.ts
│ └── types.ts
└── runtime/
└── agent-builder.ts
```

## 验证

```bash
pnpm --filter @cloudbase/open-agent-kernel type-check
pnpm --filter @cloudbase/open-agent-kernel build
pnpm dlx tsx packages/open-agent-kernel/examples/20-acp-stream-adapter-fixture.ts
pnpm exec tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck packages/open-agent-kernel/examples/21-default-acp-session-contract.ts
```
Loading