Skip to content
Merged
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
4 changes: 2 additions & 2 deletions docs/architecture/core-decomposition.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,15 @@ flowchart TB

### 7.3 适配层(Adapters)

适配层负责协议、transport、外部 provider 和宿主通信转换,物理位置是 `src/crates/adapters`。其中 `ai-adapters` 负责 AI provider 请求/响应与 stream 协议,`api-layer` 负责产品宿主共用的后端 API adapter,`transport` 负责事件投递和 host transport adapter,`webdriver` 负责 WebDriver 协议和浏览器自动化 adapter。适配层不拥有产品能力选择,也不承载可复用 OS service 实现。
适配层负责协议、transport、外部 provider 和宿主通信转换,物理位置是 `src/crates/adapters`。其中 `ai-adapters` 负责 AI provider 请求/响应映射和 provider stream 协议解析,解析结果应转换为 execution 层拥有的统一 stream 契约;`api-layer` 负责产品宿主共用的后端 API adapter,`transport` 负责事件投递和 host transport adapter,`webdriver` 负责 WebDriver 协议和浏览器自动化 adapter。适配层不拥有产品能力选择,也不承载可复用 OS service 实现。

### 7.4 服务实现层(Services)

服务实现层负责接触本地系统和 runtime infrastructure 的可复用具体实现,物理位置是 `src/crates/services`。其中 `services-core` 承载轻量 service primitive,`services-integrations` 承载 MCP、Git、remote、file watch 和产品领域 port 的具体实现,`terminal` 承载 PTY、shell integration 和 terminal session infrastructure。服务实现层可以实现 `contracts`、`execution` 或 `product-domains` 定义的 port,但不选择产品 profile,也不直接暴露 UI/协议入口。

### 7.5 执行原语层(Execution Primitives)

执行原语层提供 provider-neutral 的 runtime building blocks,物理位置是 `src/crates/execution`。`agent-runtime`、`agent-stream`、`harness`、`runtime-services`、`tool-contracts`、`tool-provider-groups` 和 `tool-execution` 分别定义 agent loop facts、stream normalization、workflow descriptor、typed service bundle、tool manifest / permission / result policy、tool group facts 和低层 tool execution helper。当前 Cargo package / lib 名保持兼容,但物理目录按职责命名。它们只能依赖稳定契约或明确的 provider-neutral DTO,不直接创建 Tauri handle、filesystem manager、Git provider、MCP client、AI client 或 host process。
执行原语层提供 provider-neutral 的 runtime building blocks,物理位置是 `src/crates/execution`。`agent-runtime`、`agent-stream`、`harness`、`runtime-services`、`tool-contracts`、`tool-provider-groups` 和 `tool-execution` 分别定义 agent loop facts、统一 stream DTO / tool-call 累积 / replay 契约、workflow descriptor、typed service bundle、tool manifest / permission / result policy、tool group facts 和低层 tool execution helper。当前 Cargo package / lib 名保持兼容,但物理目录按职责命名。它们只能依赖稳定契约或明确的 provider-neutral DTO,不直接创建 Tauri handle、filesystem manager、Git provider、MCP client、AI client 或 host process。

### 7.6 稳定契约与产品领域层(Stable Contracts and Product Domains)

Expand Down
1 change: 1 addition & 0 deletions docs/plans/core-decomposition-completed.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- `src/crates` 已按六层物理布局整理:`interfaces/`、`assembly/`、`adapters/`、`services/`、`execution/`、`contracts/`。
- 旧 `surfaces` 和 `providers` 目标层级已被移除:协议入口归入 `interfaces`,协议/transport/provider 转换归入 `adapters`,OS/runtime infrastructure 具体实现归入 `services`。
- execution 下 tool 相关目录已按职责命名:`tool-contracts`、`tool-provider-groups`、`tool-execution`。Cargo package / lib 名保持兼容。
- `agent-stream` 已成为统一 stream DTO、tool-call 累积和 replay 契约 owner;provider stream 解析测试归属 `ai-adapters`。
- AGENTS、README、DeepReview path classifier、core boundary rules 和 Cargo workspace path 已同步到当前分层。

## 2. 已建立的保护
Expand Down
1 change: 1 addition & 0 deletions docs/plans/core-decomposition-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Desktop / CLI / ACP 仍通过 `bitfun-core/product-full` 获取完整能力;Server / Web / Mobile Web 不直接依赖 core,但尚未完成按交付形态裁剪最小 feature / dependency。
- `runtime-services` 已有 typed builder、capability availability 和 core product runtime provider adapter,但不少 concrete provider 仍在 core 创建或持有。
- `tool-contracts` 已承接 provider-neutral tool manifest、admission、catalog、result policy 等纯策略;`tool-execution` 只承接部分低层 IO/search helper;Bash、terminal lifecycle、indexed search、remote shell、permission wait、checkpoint orchestration 和完整 execution pipeline 仍未完全迁移。
- `agent-stream` 已承接统一 stream DTO、tool-call 累积和 replay 契约;provider SSE / 响应解析测试归属 `ai-adapters`。
- `harness` 当前主要承接 descriptor / route plan / registry contract;Deep Review、DeepResearch、MiniApp 的 concrete workflow execution 仍在 core 或产品路径。
- `product-domains` 已承接 MiniApp / function-agent 的部分纯领域逻辑;worker、host side effect、AI acquisition、marker IO 等 concrete path 仍未完成 owner 迁移。

Expand Down
6 changes: 4 additions & 2 deletions src/crates/adapters/ai-adapters/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Scope: this guide applies to `src/crates/adapters/ai-adapters`.

`bitfun-ai-adapters` owns provider-specific request/response mapping and stream
normalization. Keep provider quirks here instead of leaking them into core tool
contracts or product runtime logic.
protocol parsing. Keep provider quirks here, then convert stream chunks into the
provider-neutral contracts owned by `bitfun-agent-stream`.

## Guardrails

Expand All @@ -16,6 +16,8 @@ contracts or product runtime logic.
count.
- Do not change shared stream or usage semantics without updating the focused
adapter tests and downstream usage expectations.
- Do not move provider-neutral stream DTOs, replay policy, or tool-call
accumulation ownership back into this crate.

## Verification

Expand Down
7 changes: 7 additions & 0 deletions src/crates/adapters/ai-adapters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ crate-type = ["rlib"]

[dependencies]
anyhow = { workspace = true }
bitfun-agent-stream = { path = "../../execution/agent-stream" }
bitfun-core-types = { path = "../../contracts/core-types" }
chrono = { workspace = true }
eventsource-stream = { workspace = true }
Expand All @@ -22,3 +23,9 @@ serde_json = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
urlencoding = { workspace = true }

[dev-dependencies]
async-trait = { workspace = true }
axum = { workspace = true }
bitfun-events = { path = "../../contracts/events" }
tokio-util = { workspace = true }
7 changes: 3 additions & 4 deletions src/crates/adapters/ai-adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ This crate owns the portable AI integration layer:

- provider request building
- provider-specific message conversion
- SSE / stream parsing
- streamed tool-call aggregation
- SSE / stream parsing into provider-neutral stream contracts
- shared AI-facing transport types
- provider model discovery
- connection health checks
Expand All @@ -26,8 +25,8 @@ and re-exports this crate where convenient.

- `client`: shared HTTP transport, retries, aggregation, health checks
- `providers`: OpenAI / Anthropic / Gemini request and discovery adapters
- `stream`: provider SSE parsing into unified streaming events
- `tool_call_accumulator`: reconstruct structured tool calls from streamed deltas
- `stream`: provider SSE parsing into unified streaming events from `bitfun-agent-stream`
- `tool_call_accumulator`: compatibility re-export; canonical implementation lives in `bitfun-agent-stream`
- `types`: portable request/response/config/message types

## Design Rule
Expand Down
82 changes: 1 addition & 81 deletions src/crates/adapters/ai-adapters/src/stream/types/unified.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::borrow::Cow;
use std::fmt;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedToolCall {
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_index: Option<usize>,
pub id: Option<String>,
pub name: Option<String>,
pub arguments: Option<String>,
#[serde(default)]
pub arguments_is_snapshot: bool,
}

/// Unified AI response format
#[derive(Clone, Serialize, Deserialize, Default)]
pub struct UnifiedResponse {
pub text: Option<String>,
pub reasoning_content: Option<String>,
/// Signature for Anthropic extended thinking (returned in multi-turn conversations)
#[serde(skip_serializing_if = "Option::is_none")]
pub thinking_signature: Option<String>,
pub tool_call: Option<UnifiedToolCall>,
pub usage: Option<UnifiedTokenUsage>,
pub finish_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_metadata: Option<Value>,
}

impl fmt::Debug for UnifiedResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let reasoning_summary = self.reasoning_content.as_ref().map(|s| {
if s.len() > 100 {
let end = s
.char_indices()
.take_while(|(i, _)| *i < 100)
.last()
.map(|(i, c)| i + c.len_utf8())
.unwrap_or(0);
// Guard against multi-byte chars pushing end past the string length
let end = end.min(s.len());
Cow::Owned(format!("{}... ({} bytes)", &s[..end], s.len()))
} else {
Cow::Borrowed(s.as_str())
}
});
f.debug_struct("UnifiedResponse")
.field("text", &self.text)
.field("reasoning_content", &reasoning_summary)
.field("thinking_signature", &"<omitted>")
.field("tool_call", &self.tool_call)
.field("usage", &self.usage)
.field("finish_reason", &self.finish_reason)
.field("provider_metadata", &"<omitted>")
.finish()
}
}

/// Unified token usage statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedTokenUsage {
pub prompt_token_count: u32,
pub candidates_token_count: u32,
pub total_token_count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning_token_count: Option<u32>,
/// Cache READ tokens (i.e., served from cache this call). Universal across
/// providers: OpenAI `cached_tokens`, DeepSeek `prompt_cache_hit_tokens`,
/// Anthropic `cache_read_input_tokens`, Gemini `cachedContentTokenCount`.
/// Hit rate consumers must use this as numerator and `prompt_token_count`
/// as denominator.
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_content_token_count: Option<u32>,
/// Cache WRITE tokens (only Anthropic reports this per-token; others either
/// have no creation concept or bill creation by storage time). Disjoint from
/// `cached_content_token_count`. Do NOT include in hit-rate numerator.
#[serde(skip_serializing_if = "Option::is_none", default)]
pub cache_creation_token_count: Option<u32>,
}
pub use bitfun_agent_stream::{UnifiedResponse, UnifiedTokenUsage, UnifiedToolCall};
Loading
Loading