diff --git a/.agent/workflows/documentation.md b/.agent/workflows/documentation.md deleted file mode 100644 index f65148e..0000000 --- a/.agent/workflows/documentation.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -description: ドキュメントコメントの書き方ガイドライン ---- - -# ドキュメントコメント スタイルガイド - -## 基本原則 - -1. **利用者視点で書く**: 「何をするものか」「どう使うか」を先に、「なぜそう実装したか」は後に -2. **型パラメータはバッククォートで囲む**: `Handler` ✓ / Handler ✗ -3. **Examplesは`worker::`パスで書く**: re-export先のパスを使用 - -## 構造テンプレート - -```rust -/// [1行目: 何をするものか - 利用者が最初に知りたいこと] -/// -/// [詳細説明: いつ使うか、なぜ使うか、注意点など] -/// -/// # Examples -/// -/// ``` -/// use worker::SomeType; -/// -/// let instance = SomeType::new(); -/// instance.do_something(); -/// ``` -/// -/// # Notes (オプション) -/// -/// 実装上の注意事項や制限があれば記載 -pub struct SomeType { ... } -``` - -## 良い例・悪い例 - -### 構造体/Trait - -```rust -// ❌ 悪い例(実装視点) -/// HandlerからErasedHandlerへのラッパー -/// 各Handlerは独自のScope型を持つため、Timelineで保持するには型消去が必要 - -// ✅ 良い例(利用者視点) -/// `Handler`を`ErasedHandler`として扱うためのラッパー -/// -/// 通常は直接使用せず、`Timeline::on_text_block()`などのメソッド経由で -/// 自動的にラップされます。 -``` - -### メソッド - -```rust -// ❌ 悪い例(処理内容の説明のみ) -/// ツールを登録する - -// ✅ 良い例(何が起きるか、どう使うか) -/// ツールを登録する -/// -/// 登録されたツールはLLMからの呼び出しで自動的に実行されます。 -/// 同名のツールを登録した場合、後から登録したものが優先されます。 -/// -/// # Examples -/// -/// ``` -/// use worker::{Worker, Tool}; -/// -/// worker.register_tool(MyTool::new()); -/// ``` -``` - -### 型パラメータ - -```rust -// ❌ HTMLタグとして解釈されてしまう -/// Handlerを保持するフィールド - -// ✅ バッククォートで囲む -/// `Handler`を保持するフィールド -``` - -## ドキュメントの配置 - -| 項目 | 配置場所 | -|-----|---------| -| 型/trait/関数のdoc | 定義元のクレート(worker-types等) | -| モジュールdoc (`//!`) | 各クレートのlib.rsに書く | -| 実装詳細 | 実装コメント (`//`) を使用 | -| 利用者向けでない内部型 | `#[doc(hidden)]`または`pub(crate)` | - -## Examplesのuseパス - -re-exportされる型のExamplesでは、最終的な公開パスを使用: - -```rust -// worker-types/src/tool.rs でも -/// # Examples -/// ``` -/// use worker::Tool; // ✓ worker_types::Tool ではなく -/// ``` -``` - -## チェックリスト - -- [ ] 1行目は「何をするものか」を利用者視点で説明しているか -- [ ] 型パラメータ (``, `` 等) はバッククォートで囲んでいるか -- [ ] 主要なpub APIにはExamplesがあるか -- [ ] Examplesの`use`パスは`worker::`になっているか -- [ ] `cargo doc --no-deps`で警告が出ないか diff --git a/AGENTS.md b/AGENTS.md index b75be37..a13e47b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,32 +4,3 @@ - クレートに依存関係を追加・更新する際は必ず `cargo`コマンドを使い、`Cargo.toml`を直接手で書き換えず、必ずコマンド経由で管理すること。 - -## worker-types - -`worker-types` クレートには次の条件を満たす型だけを置く。 - -1. **共有セマンティクスの源泉** - - ランタイム(`worker`)、proc-macro(`worker-macros`)、外部利用者のすべてで同じ定義を共有したい値型。 - - 例: `BlockId`, `ProviderEvent`, `ToolArgumentsDelta` などイベント/DTO群。 - -2. **シリアライズ境界を越えるもの** - - serde経由でプロセス外へ渡したり、APIレスポンスとして公開するもの。 - - ロジックを持たない純粋なデータキャリアに限定する。 - -3. **依存の最小化が必要な型** - - `serde`, `serde_json` 程度の軽量依存で収まる。 - -4. **マクロが直接参照する型** - - 属性/derive/proc-macro が型に対してコード生成する場合は `worker-macros` -> - `worker-types` の単方向依存を維持するため、対象型を `worker-types` に置く。 - -5. **副作用を伴わないこと** - - `worker-types` 内では I/O・状態保持・スレッド操作などの副作用を禁止。 - - 振る舞いを持つ場合でも `impl` - は純粋な計算か軽量ユーティリティのみに留める。 - -この基準に当てはまらない型(例えばクライアント状態管理、エラー型で追加依存が必要、プロバイダ固有ロジックなど)は -`worker` クレート側に配置し、どうしても公開が必要なら `worker` -経由で再エクスポートする。 何にせよ、`worker` -> -`worker-types`の片方向依存を維持すること。 diff --git a/Cargo.lock b/Cargo.lock index 214b53f..838462a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2167,7 +2167,6 @@ dependencies = [ "tracing", "tracing-subscriber", "worker-macros", - "worker-types", ] [[package]] @@ -2177,18 +2176,6 @@ dependencies = [ "proc-macro2", "quote", "syn", - "worker-types", -] - -[[package]] -name = "worker-types" -version = "0.1.0" -dependencies = [ - "async-trait", - "schemars", - "serde", - "serde_json", - "thiserror 2.0.17", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cc8b8cd..911275c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,5 @@ resolver = "2" members = [ "worker", - "worker-types", "worker-macros", ] diff --git a/README.md b/README.md index 5861971..4b285f6 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ # llm-worker-rs + +Rusty, Efficient, and Agentic LLM Client Library + +`llm-worker-rs` is a Rust library designed for building robust and efficient LLM applications. It unifies interactions with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama) under a single abstraction, with type-safe state management, efficient context caching, and a powerful event-driven architecture. diff --git a/worker-macros/Cargo.toml b/worker-macros/Cargo.toml index 04ae47a..ceb290f 100644 --- a/worker-macros/Cargo.toml +++ b/worker-macros/Cargo.toml @@ -11,4 +11,3 @@ proc-macro = true proc-macro2 = "1" quote = "1" syn = { version = "2", features = ["full"] } -worker-types = { path = "../worker-types" } diff --git a/worker-macros/src/lib.rs b/worker-macros/src/lib.rs index 46c31a1..5a59c60 100644 --- a/worker-macros/src/lib.rs +++ b/worker-macros/src/lib.rs @@ -193,7 +193,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2:: quote! { match result { Ok(val) => Ok(format!("{:?}", val)), - Err(e) => Err(worker_types::ToolError::ExecutionFailed(format!("{}", e))), + Err(e) => Err(::worker::tool::ToolError::ExecutionFailed(format!("{}", e))), } } } else { @@ -230,7 +230,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2:: } else { quote! { let args: #args_struct_name = serde_json::from_str(input_json) - .map_err(|e| worker_types::ToolError::InvalidArgument(e.to_string()))?; + .map_err(|e| ::worker::tool::ToolError::InvalidArgument(e.to_string()))?; let result = self.ctx.#method_name(#(#arg_names),*)#awaiter; #result_handling @@ -246,7 +246,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2:: } #[async_trait::async_trait] - impl worker_types::Tool for #tool_struct_name { + impl ::worker::tool::Tool for #tool_struct_name { fn name(&self) -> &str { #tool_name } @@ -260,7 +260,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2:: serde_json::to_value(schema).unwrap_or(serde_json::json!({})) } - async fn execute(&self, input_json: &str) -> Result { + async fn execute(&self, input_json: &str) -> Result { #execute_body } } diff --git a/worker-types/Cargo.toml b/worker-types/Cargo.toml deleted file mode 100644 index c19db01..0000000 --- a/worker-types/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "worker-types" -version = "0.1.0" -edition = "2024" -publish = false - -[dependencies] -async-trait = "0.1.89" -schemars = "1.2.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "2.0.17" diff --git a/worker-types/src/lib.rs b/worker-types/src/lib.rs deleted file mode 100644 index bec2c26..0000000 --- a/worker-types/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! worker-types - LLMワーカーの型定義 -//! -//! このクレートは`worker`クレートで使用される型を提供します。 -//! 通常は直接使用せず、`worker`クレート経由で利用してください。 -//! -//! ```ignore -//! use worker::{Event, Message, Tool, WorkerHook}; -//! ``` - -mod event; -mod handler; -mod hook; -mod message; -mod state; -mod subscriber; -mod tool; - -pub use event::*; -pub use handler::*; -pub use hook::*; -pub use message::*; -pub use state::*; -pub use subscriber::*; -pub use tool::*; diff --git a/worker-types/src/subscriber.rs b/worker-types/src/subscriber.rs deleted file mode 100644 index 8276f9e..0000000 --- a/worker-types/src/subscriber.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! イベント購読 -//! -//! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。 -//! UIへのストリーム表示やプログレス表示に使用します。 - -use crate::{ErrorEvent, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent, UsageEvent}; - -// ============================================================================= -// WorkerSubscriber Trait -// ============================================================================= - -/// LLMからのストリーミングイベントを購読するトレイト -/// -/// Workerに登録すると、テキスト生成やツール呼び出しのイベントを -/// リアルタイムで受信できます。UIへのストリーム表示に最適です。 -/// -/// # 受信できるイベント -/// -/// - **ブロックイベント**: テキスト、ツール使用(スコープ付き) -/// - **メタイベント**: 使用量、ステータス、エラー -/// - **完了イベント**: テキスト完了、ツール呼び出し完了 -/// - **ターン制御**: ターン開始、ターン終了 -/// -/// # Examples -/// -/// ```ignore -/// use worker::{WorkerSubscriber, TextBlockEvent}; -/// -/// struct StreamPrinter; -/// -/// impl WorkerSubscriber for StreamPrinter { -/// type TextBlockScope = (); -/// type ToolUseBlockScope = (); -/// -/// fn on_text_block(&mut self, _: &mut (), event: &TextBlockEvent) { -/// if let TextBlockEvent::Delta(text) = event { -/// print!("{}", text); // リアルタイム出力 -/// } -/// } -/// -/// fn on_text_complete(&mut self, text: &str) { -/// println!("\n--- Complete: {} chars ---", text.len()); -/// } -/// } -/// -/// // Workerに登録 -/// worker.subscribe(StreamPrinter); -/// ``` -pub trait WorkerSubscriber: Send { - // ========================================================================= - // スコープ型(ブロックイベント用) - // ========================================================================= - - /// テキストブロック処理用のスコープ型 - /// - /// ブロック開始時にDefault::default()で生成され、 - /// ブロック終了時に破棄される。 - type TextBlockScope: Default + Send; - - /// ツール使用ブロック処理用のスコープ型 - type ToolUseBlockScope: Default + Send; - - // ========================================================================= - // ブロックイベント(スコープ管理あり) - // ========================================================================= - - /// テキストブロックイベント - /// - /// Start/Delta/Stopのライフサイクルを持つ。 - /// scopeはブロック開始時に生成され、終了時に破棄される。 - #[allow(unused_variables)] - fn on_text_block(&mut self, scope: &mut Self::TextBlockScope, event: &TextBlockEvent) {} - - /// ツール使用ブロックイベント - /// - /// Start/InputJsonDelta/Stopのライフサイクルを持つ。 - #[allow(unused_variables)] - fn on_tool_use_block( - &mut self, - scope: &mut Self::ToolUseBlockScope, - event: &ToolUseBlockEvent, - ) { - } - - // ========================================================================= - // 単発イベント(スコープ不要) - // ========================================================================= - - /// 使用量イベント - #[allow(unused_variables)] - fn on_usage(&mut self, event: &UsageEvent) {} - - /// ステータスイベント - #[allow(unused_variables)] - fn on_status(&mut self, event: &StatusEvent) {} - - /// エラーイベント - #[allow(unused_variables)] - fn on_error(&mut self, event: &ErrorEvent) {} - - // ========================================================================= - // 累積イベント(Worker層で追加) - // ========================================================================= - - /// テキスト完了イベント - /// - /// テキストブロックが完了した時点で、累積されたテキスト全体が渡される。 - /// ブロック処理後の最終結果を受け取るのに便利。 - #[allow(unused_variables)] - fn on_text_complete(&mut self, text: &str) {} - - /// ツール呼び出し完了イベント - /// - /// ツール使用ブロックが完了した時点で、完全なToolCallが渡される。 - #[allow(unused_variables)] - fn on_tool_call_complete(&mut self, call: &ToolCall) {} - - // ========================================================================= - // ターン制御 - // ========================================================================= - - /// ターン開始時 - /// - /// `turn`は0から始まるターン番号。 - #[allow(unused_variables)] - fn on_turn_start(&mut self, turn: usize) {} - - /// ターン終了時 - #[allow(unused_variables)] - fn on_turn_end(&mut self, turn: usize) {} -} diff --git a/worker/Cargo.toml b/worker/Cargo.toml index 2060d03..8f43d93 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -13,8 +13,7 @@ serde_json = "1.0" thiserror = "1.0" tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] } tracing = "0.1" -worker-macros = { path = "../worker-macros" } -worker-types = { path = "../worker-types" } +worker-macros = { path = "../worker-macros", version = "0.1" } [dev-dependencies] clap = { version = "4.5.54", features = ["derive", "env"] } diff --git a/worker/src/event.rs b/worker/src/event.rs new file mode 100644 index 0000000..a913c4a --- /dev/null +++ b/worker/src/event.rs @@ -0,0 +1,446 @@ +//! Worker層の公開イベント型 +//! +//! 外部利用者に公開するためのイベント表現。 + +use serde::{Deserialize, Serialize}; + +// ============================================================================= +// Core Event Types (from llm_client layer) +// ============================================================================= + +/// LLMからのストリーミングイベント +/// +/// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして +/// 統一的に処理されます。 +/// +/// # イベントの種類 +/// +/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error` +/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort` +/// +/// # ブロックのライフサイクル +/// +/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop` +/// の順序でイベントが発生します。 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Event { + /// ハートビート + Ping(PingEvent), + /// トークン使用量 + Usage(UsageEvent), + /// ストリームのステータス変化 + Status(StatusEvent), + /// エラー発生 + Error(ErrorEvent), + + /// ブロック開始(テキスト、ツール使用等) + BlockStart(BlockStart), + /// ブロックの差分データ + BlockDelta(BlockDelta), + /// ブロック正常終了 + BlockStop(BlockStop), + /// ブロック中断 + BlockAbort(BlockAbort), +} + +// ============================================================================= +// Meta Events +// ============================================================================= + +/// Pingイベント(ハートビート) +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct PingEvent { + pub timestamp: Option, +} + +/// 使用量イベント +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct UsageEvent { + /// 入力トークン数 + pub input_tokens: Option, + /// 出力トークン数 + pub output_tokens: Option, + /// 合計トークン数 + pub total_tokens: Option, + /// キャッシュ読み込みトークン数 + pub cache_read_input_tokens: Option, + /// キャッシュ作成トークン数 + pub cache_creation_input_tokens: Option, +} + +/// ステータスイベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StatusEvent { + pub status: ResponseStatus, +} + +/// レスポンスステータス +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ResponseStatus { + /// ストリーム開始 + Started, + /// 正常完了 + Completed, + /// キャンセルされた + Cancelled, + /// エラー発生 + Failed, +} + +/// エラーイベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ErrorEvent { + pub code: Option, + pub message: String, +} + +// ============================================================================= +// Block Types +// ============================================================================= + +/// ブロックの種別 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum BlockType { + /// テキスト生成 + Text, + /// 思考 (Claude Extended Thinking等) + Thinking, + /// ツール呼び出し + ToolUse, + /// ツール結果 + ToolResult, +} + +/// ブロック開始イベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockStart { + /// ブロックのインデックス + pub index: usize, + /// ブロックの種別 + pub block_type: BlockType, + /// ブロック固有のメタデータ + pub metadata: BlockMetadata, +} + +impl BlockStart { + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// ブロックのメタデータ +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum BlockMetadata { + Text, + Thinking, + ToolUse { id: String, name: String }, + ToolResult { tool_use_id: String }, +} + +/// ブロックデルタイベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockDelta { + /// ブロックのインデックス + pub index: usize, + /// デルタの内容 + pub delta: DeltaContent, +} + +/// デルタの内容 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum DeltaContent { + /// テキストデルタ + Text(String), + /// 思考デルタ + Thinking(String), + /// ツール引数のJSON部分文字列 + InputJson(String), +} + +impl DeltaContent { + /// デルタのブロック種別を取得 + pub fn block_type(&self) -> BlockType { + match self { + DeltaContent::Text(_) => BlockType::Text, + DeltaContent::Thinking(_) => BlockType::Thinking, + DeltaContent::InputJson(_) => BlockType::ToolUse, + } + } +} + +/// ブロック停止イベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockStop { + /// ブロックのインデックス + pub index: usize, + /// ブロックの種別 + pub block_type: BlockType, + /// 停止理由 + pub stop_reason: Option, +} + +impl BlockStop { + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// ブロック中断イベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockAbort { + /// ブロックのインデックス + pub index: usize, + /// ブロックの種別 + pub block_type: BlockType, + /// 中断理由 + pub reason: String, +} + +impl BlockAbort { + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// 停止理由 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum StopReason { + /// 自然終了 + EndTurn, + /// 最大トークン数到達 + MaxTokens, + /// ストップシーケンス到達 + StopSequence, + /// ツール使用 + ToolUse, +} + +// ============================================================================= +// Builder / Factory helpers +// ============================================================================= + +impl Event { + /// テキストブロック開始イベントを作成 + pub fn text_block_start(index: usize) -> Self { + Event::BlockStart(BlockStart { + index, + block_type: BlockType::Text, + metadata: BlockMetadata::Text, + }) + } + + /// テキストデルタイベントを作成 + pub fn text_delta(index: usize, text: impl Into) -> Self { + Event::BlockDelta(BlockDelta { + index, + delta: DeltaContent::Text(text.into()), + }) + } + + /// テキストブロック停止イベントを作成 + pub fn text_block_stop(index: usize, stop_reason: Option) -> Self { + Event::BlockStop(BlockStop { + index, + block_type: BlockType::Text, + stop_reason, + }) + } + + /// ツール使用ブロック開始イベントを作成 + pub fn tool_use_start(index: usize, id: impl Into, name: impl Into) -> Self { + Event::BlockStart(BlockStart { + index, + block_type: BlockType::ToolUse, + metadata: BlockMetadata::ToolUse { + id: id.into(), + name: name.into(), + }, + }) + } + + /// ツール引数デルタイベントを作成 + pub fn tool_input_delta(index: usize, json: impl Into) -> Self { + Event::BlockDelta(BlockDelta { + index, + delta: DeltaContent::InputJson(json.into()), + }) + } + + /// ツール使用ブロック停止イベントを作成 + pub fn tool_use_stop(index: usize) -> Self { + Event::BlockStop(BlockStop { + index, + block_type: BlockType::ToolUse, + stop_reason: Some(StopReason::ToolUse), + }) + } + + /// 使用量イベントを作成 + pub fn usage(input_tokens: u64, output_tokens: u64) -> Self { + Event::Usage(UsageEvent { + input_tokens: Some(input_tokens), + output_tokens: Some(output_tokens), + total_tokens: Some(input_tokens + output_tokens), + cache_read_input_tokens: None, + cache_creation_input_tokens: None, + }) + } + + /// Pingイベントを作成 + pub fn ping() -> Self { + Event::Ping(PingEvent { timestamp: None }) + } +} + +// ============================================================================= +// Conversions: timeline::event -> worker::event +// ============================================================================= + +impl From for ResponseStatus { + fn from(value: crate::timeline::event::ResponseStatus) -> Self { + match value { + crate::timeline::event::ResponseStatus::Started => ResponseStatus::Started, + crate::timeline::event::ResponseStatus::Completed => ResponseStatus::Completed, + crate::timeline::event::ResponseStatus::Cancelled => ResponseStatus::Cancelled, + crate::timeline::event::ResponseStatus::Failed => ResponseStatus::Failed, + } + } +} + +impl From for BlockType { + fn from(value: crate::timeline::event::BlockType) -> Self { + match value { + crate::timeline::event::BlockType::Text => BlockType::Text, + crate::timeline::event::BlockType::Thinking => BlockType::Thinking, + crate::timeline::event::BlockType::ToolUse => BlockType::ToolUse, + crate::timeline::event::BlockType::ToolResult => BlockType::ToolResult, + } + } +} + +impl From for BlockMetadata { + fn from(value: crate::timeline::event::BlockMetadata) -> Self { + match value { + crate::timeline::event::BlockMetadata::Text => BlockMetadata::Text, + crate::timeline::event::BlockMetadata::Thinking => BlockMetadata::Thinking, + crate::timeline::event::BlockMetadata::ToolUse { id, name } => { + BlockMetadata::ToolUse { id, name } + } + crate::timeline::event::BlockMetadata::ToolResult { tool_use_id } => { + BlockMetadata::ToolResult { tool_use_id } + } + } + } +} + +impl From for DeltaContent { + fn from(value: crate::timeline::event::DeltaContent) -> Self { + match value { + crate::timeline::event::DeltaContent::Text(text) => DeltaContent::Text(text), + crate::timeline::event::DeltaContent::Thinking(text) => DeltaContent::Thinking(text), + crate::timeline::event::DeltaContent::InputJson(json) => DeltaContent::InputJson(json), + } + } +} + +impl From for StopReason { + fn from(value: crate::timeline::event::StopReason) -> Self { + match value { + crate::timeline::event::StopReason::EndTurn => StopReason::EndTurn, + crate::timeline::event::StopReason::MaxTokens => StopReason::MaxTokens, + crate::timeline::event::StopReason::StopSequence => StopReason::StopSequence, + crate::timeline::event::StopReason::ToolUse => StopReason::ToolUse, + } + } +} + +impl From for PingEvent { + fn from(value: crate::timeline::event::PingEvent) -> Self { + PingEvent { + timestamp: value.timestamp, + } + } +} + +impl From for UsageEvent { + fn from(value: crate::timeline::event::UsageEvent) -> Self { + UsageEvent { + input_tokens: value.input_tokens, + output_tokens: value.output_tokens, + total_tokens: value.total_tokens, + cache_read_input_tokens: value.cache_read_input_tokens, + cache_creation_input_tokens: value.cache_creation_input_tokens, + } + } +} + +impl From for StatusEvent { + fn from(value: crate::timeline::event::StatusEvent) -> Self { + StatusEvent { + status: value.status.into(), + } + } +} + +impl From for ErrorEvent { + fn from(value: crate::timeline::event::ErrorEvent) -> Self { + ErrorEvent { + code: value.code, + message: value.message, + } + } +} + +impl From for BlockStart { + fn from(value: crate::timeline::event::BlockStart) -> Self { + BlockStart { + index: value.index, + block_type: value.block_type.into(), + metadata: value.metadata.into(), + } + } +} + +impl From for BlockDelta { + fn from(value: crate::timeline::event::BlockDelta) -> Self { + BlockDelta { + index: value.index, + delta: value.delta.into(), + } + } +} + +impl From for BlockStop { + fn from(value: crate::timeline::event::BlockStop) -> Self { + BlockStop { + index: value.index, + block_type: value.block_type.into(), + stop_reason: value.stop_reason.map(Into::into), + } + } +} + +impl From for BlockAbort { + fn from(value: crate::timeline::event::BlockAbort) -> Self { + BlockAbort { + index: value.index, + block_type: value.block_type.into(), + reason: value.reason, + } + } +} + +impl From for Event { + fn from(value: crate::timeline::event::Event) -> Self { + match value { + crate::timeline::event::Event::Ping(p) => Event::Ping(p.into()), + crate::timeline::event::Event::Usage(u) => Event::Usage(u.into()), + crate::timeline::event::Event::Status(s) => Event::Status(s.into()), + crate::timeline::event::Event::Error(e) => Event::Error(e.into()), + crate::timeline::event::Event::BlockStart(s) => Event::BlockStart(s.into()), + crate::timeline::event::Event::BlockDelta(d) => Event::BlockDelta(d.into()), + crate::timeline::event::Event::BlockStop(s) => Event::BlockStop(s.into()), + crate::timeline::event::Event::BlockAbort(a) => Event::BlockAbort(a.into()), + } + } +} diff --git a/worker-types/src/handler.rs b/worker/src/handler.rs similarity index 97% rename from worker-types/src/handler.rs rename to worker/src/handler.rs index 783cf06..c88ada0 100644 --- a/worker-types/src/handler.rs +++ b/worker/src/handler.rs @@ -4,7 +4,7 @@ //! カスタムハンドラを実装してTimelineに登録することで、 //! ストリームイベントを受信できます。 -use crate::event::*; +use crate::timeline::event::*; // ============================================================================= // Kind Trait @@ -32,7 +32,7 @@ pub trait Kind { /// # Examples /// /// ```ignore -/// use worker::{Handler, TextBlockKind, TextBlockEvent}; +/// use worker::timeline::{Handler, TextBlockEvent, TextBlockKind}; /// /// struct TextCollector { /// texts: Vec, diff --git a/worker-types/src/hook.rs b/worker/src/hook.rs similarity index 98% rename from worker-types/src/hook.rs rename to worker/src/hook.rs index 2bf6004..3f1bd4d 100644 --- a/worker-types/src/hook.rs +++ b/worker/src/hook.rs @@ -109,7 +109,8 @@ pub enum HookError { /// # Examples /// /// ```ignore -/// use worker::{WorkerHook, ControlFlow, HookError, ToolCall, TurnResult, Message}; +/// use worker::hook::{ControlFlow, HookError, ToolCall, TurnResult, WorkerHook}; +/// use worker::Message; /// /// struct ValidationHook; /// diff --git a/worker/src/lib.rs b/worker/src/lib.rs index 6180d9e..602c00d 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -39,55 +39,18 @@ pub mod llm_client; pub mod timeline; -mod subscriber_adapter; +pub mod event; +mod handler; +pub mod hook; +mod message; +pub mod state; +pub mod subscriber; +pub mod tool; mod worker; // ============================================================================= // トップレベル公開(最も頻繁に使う型) // ============================================================================= +pub use message::{ContentPart, Message, MessageContent, Role}; pub use worker::{Worker, WorkerConfig, WorkerError}; -pub use worker_types::{ContentPart, Message, MessageContent, Role}; - -// ============================================================================= -// 意味のあるモジュールとして公開 -// ============================================================================= - -/// ツール定義 -/// -/// LLMから呼び出し可能なツールを定義するためのトレイトと型。 -pub mod tool { - pub use worker_types::{Tool, ToolError}; -} - -/// Hook機能 -/// -/// ターンの進行・ツール実行に介入するためのトレイトと型。 -pub mod hook { - pub use worker_types::{ControlFlow, HookError, ToolCall, ToolResult, TurnResult, WorkerHook}; -} - -/// イベント購読 -/// -/// LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。 -pub mod subscriber { - pub use worker_types::WorkerSubscriber; -} - -/// イベント型 -/// -/// LLMからのストリーミングレスポンスを表現するイベント型。 -/// Timeline層を直接使用する場合に必要です。 -pub mod event { - pub use worker_types::{ - BlockAbort, BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, - ErrorEvent, Event, PingEvent, ResponseStatus, StatusEvent, StopReason, UsageEvent, - }; -} - -/// Worker状態 -/// -/// Type-stateパターンによるキャッシュ保護のための状態マーカー型。 -pub mod state { - pub use worker_types::{Locked, Mutable, WorkerState}; -} diff --git a/worker/src/llm_client/client.rs b/worker/src/llm_client/client.rs index bc725ff..77d9ea3 100644 --- a/worker/src/llm_client/client.rs +++ b/worker/src/llm_client/client.rs @@ -2,11 +2,9 @@ use std::pin::Pin; +use crate::llm_client::{ClientError, Request, event::Event}; use async_trait::async_trait; use futures::Stream; -use worker_types::Event; - -use crate::llm_client::{ClientError, Request}; /// LLMクライアントのtrait /// diff --git a/worker-types/src/event.rs b/worker/src/llm_client/event.rs similarity index 97% rename from worker-types/src/event.rs rename to worker/src/llm_client/event.rs index 5321400..66894f4 100644 --- a/worker-types/src/event.rs +++ b/worker/src/llm_client/event.rs @@ -1,7 +1,6 @@ -//! イベント型 +//! LLMクライアント層のイベント型 //! -//! LLMからのストリーミングレスポンスを表現するイベント型。 -//! Timeline層がこのイベントを受信し、ハンドラにディスパッチします。 +//! 各LLMプロバイダからのストリーミングレスポンスを表現するイベント型。 use serde::{Deserialize, Serialize}; diff --git a/worker/src/llm_client/mod.rs b/worker/src/llm_client/mod.rs index e2b7477..7dfc6c7 100644 --- a/worker/src/llm_client/mod.rs +++ b/worker/src/llm_client/mod.rs @@ -1,6 +1,7 @@ //! LLMクライアント層 //! -//! 各LLMプロバイダと通信し、統一された[`Event`](crate::event::Event)ストリームを出力します。 +//! 各LLMプロバイダと通信し、統一された[`Event`](crate::llm_client::event::Event) +//! ストリームを出力します。 //! //! # サポートするプロバイダ //! @@ -17,6 +18,7 @@ pub mod client; pub mod error; +pub mod event; pub mod types; pub mod providers; @@ -24,4 +26,5 @@ pub mod scheme; pub use client::*; pub use error::*; +pub use event::*; pub use types::*; diff --git a/worker/src/llm_client/providers/anthropic.rs b/worker/src/llm_client/providers/anthropic.rs index 5090564..509ab96 100644 --- a/worker/src/llm_client/providers/anthropic.rs +++ b/worker/src/llm_client/providers/anthropic.rs @@ -4,13 +4,13 @@ use std::pin::Pin; +use crate::llm_client::{ + ClientError, LlmClient, Request, event::Event, scheme::anthropic::AnthropicScheme, +}; use async_trait::async_trait; use eventsource_stream::Eventsource; use futures::{Stream, StreamExt, TryStreamExt, future::ready}; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; -use worker_types::Event; - -use crate::llm_client::{ClientError, LlmClient, Request, scheme::anthropic::AnthropicScheme}; /// Anthropic クライアント pub struct AnthropicClient { @@ -156,10 +156,11 @@ impl LlmClient for AnthropicClient { if let Some(block_type) = current_block_type.take() { // 正しいブロックタイプで上書き // (Event::BlockStopの中身を置換) - evt = Event::BlockStop(worker_types::BlockStop { - block_type, - ..stop.clone() - }); + evt = + Event::BlockStop(crate::llm_client::event::BlockStop { + block_type, + ..stop.clone() + }); } } _ => {} diff --git a/worker/src/llm_client/providers/gemini.rs b/worker/src/llm_client/providers/gemini.rs index 679770e..b0cd751 100644 --- a/worker/src/llm_client/providers/gemini.rs +++ b/worker/src/llm_client/providers/gemini.rs @@ -4,13 +4,13 @@ use std::pin::Pin; +use crate::llm_client::{ + ClientError, LlmClient, Request, event::Event, scheme::gemini::GeminiScheme, +}; use async_trait::async_trait; use eventsource_stream::Eventsource; use futures::{Stream, StreamExt, TryStreamExt}; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; -use worker_types::Event; - -use crate::llm_client::{ClientError, LlmClient, Request, scheme::gemini::GeminiScheme}; /// Gemini クライアント pub struct GeminiClient { diff --git a/worker/src/llm_client/providers/ollama.rs b/worker/src/llm_client/providers/ollama.rs index e813f7e..3a93e61 100644 --- a/worker/src/llm_client/providers/ollama.rs +++ b/worker/src/llm_client/providers/ollama.rs @@ -5,13 +5,12 @@ use std::pin::Pin; +use crate::llm_client::{ + ClientError, LlmClient, Request, event::Event, providers::openai::OpenAIClient, + scheme::openai::OpenAIScheme, +}; use async_trait::async_trait; use futures::Stream; -use worker_types::Event; - -use crate::llm_client::{ - ClientError, LlmClient, Request, providers::openai::OpenAIClient, scheme::openai::OpenAIScheme, -}; /// Ollama クライアント /// diff --git a/worker/src/llm_client/providers/openai.rs b/worker/src/llm_client/providers/openai.rs index 6da17e1..eec1f19 100644 --- a/worker/src/llm_client/providers/openai.rs +++ b/worker/src/llm_client/providers/openai.rs @@ -4,13 +4,13 @@ use std::pin::Pin; +use crate::llm_client::{ + ClientError, LlmClient, Request, event::Event, scheme::openai::OpenAIScheme, +}; use async_trait::async_trait; use eventsource_stream::Eventsource; use futures::{Stream, StreamExt, TryStreamExt}; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; -use worker_types::Event; - -use crate::llm_client::{ClientError, LlmClient, Request, scheme::openai::OpenAIScheme}; /// OpenAI クライアント pub struct OpenAIClient { diff --git a/worker/src/llm_client/scheme/anthropic/events.rs b/worker/src/llm_client/scheme/anthropic/events.rs index 10e1a38..9975755 100644 --- a/worker/src/llm_client/scheme/anthropic/events.rs +++ b/worker/src/llm_client/scheme/anthropic/events.rs @@ -2,13 +2,14 @@ //! //! Anthropic Messages APIのSSEイベントをパースし、統一Event型に変換 -use serde::Deserialize; -use worker_types::{ - BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent, Event, - PingEvent, ResponseStatus, StatusEvent, UsageEvent, +use crate::llm_client::{ + ClientError, + event::{ + BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent, + Event, PingEvent, ResponseStatus, StatusEvent, UsageEvent, + }, }; - -use crate::llm_client::ClientError; +use serde::Deserialize; use super::AnthropicScheme; diff --git a/worker/src/llm_client/scheme/gemini/events.rs b/worker/src/llm_client/scheme/gemini/events.rs index 0fd1fb7..6cf25d3 100644 --- a/worker/src/llm_client/scheme/gemini/events.rs +++ b/worker/src/llm_client/scheme/gemini/events.rs @@ -2,12 +2,11 @@ //! //! Google Gemini APIのSSEイベントをパースし、統一Event型に変換 -use serde::Deserialize; -use worker_types::{ - BlockMetadata, BlockStart, BlockStop, BlockType, Event, StopReason, UsageEvent, +use crate::llm_client::{ + ClientError, + event::{BlockMetadata, BlockStart, BlockStop, BlockType, Event, StopReason, UsageEvent}, }; - -use crate::llm_client::ClientError; +use serde::Deserialize; use super::GeminiScheme; @@ -231,7 +230,7 @@ impl GeminiScheme { #[cfg(test)] mod tests { use super::*; - use worker_types::DeltaContent; + use crate::llm_client::event::DeltaContent; #[test] fn test_parse_text_response() { diff --git a/worker/src/llm_client/scheme/openai/events.rs b/worker/src/llm_client/scheme/openai/events.rs index b7e0eb8..2bb9ce7 100644 --- a/worker/src/llm_client/scheme/openai/events.rs +++ b/worker/src/llm_client/scheme/openai/events.rs @@ -1,9 +1,10 @@ //! OpenAI SSEイベントパース +use crate::llm_client::{ + ClientError, + event::{Event, StopReason, UsageEvent}, +}; use serde::Deserialize; -use worker_types::{Event, StopReason, UsageEvent}; - -use crate::llm_client::ClientError; use super::OpenAIScheme; @@ -155,7 +156,7 @@ impl OpenAIScheme { #[cfg(test)] mod tests { use super::*; - use worker_types::DeltaContent; + use crate::llm_client::event::DeltaContent; #[test] fn test_parse_text_delta() { @@ -188,7 +189,7 @@ mod tests { assert_eq!(events.len(), 1); if let Event::BlockStart(start) = &events[0] { assert_eq!(start.index, 0); - if let worker_types::BlockMetadata::ToolUse { id, name } = &start.metadata { + if let crate::llm_client::event::BlockMetadata::ToolUse { id, name } = &start.metadata { assert_eq!(id, "call_abc"); assert_eq!(name, "get_weather"); } else { diff --git a/worker-types/src/message.rs b/worker/src/message.rs similarity index 100% rename from worker-types/src/message.rs rename to worker/src/message.rs diff --git a/worker-types/src/state.rs b/worker/src/state.rs similarity index 100% rename from worker-types/src/state.rs rename to worker/src/state.rs diff --git a/worker/src/subscriber_adapter.rs b/worker/src/subscriber.rs similarity index 57% rename from worker/src/subscriber_adapter.rs rename to worker/src/subscriber.rs index 4d2dd8e..e22ab7b 100644 --- a/worker/src/subscriber_adapter.rs +++ b/worker/src/subscriber.rs @@ -1,14 +1,145 @@ -//! WorkerSubscriber統合 +//! イベント購読 //! -//! WorkerSubscriberをTimeline層のHandlerとしてブリッジする実装 +//! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。 +//! UIへのストリーム表示やプログレス表示に使用します。 use std::sync::{Arc, Mutex}; -use worker_types::{ - ErrorEvent, ErrorKind, Handler, StatusEvent, StatusKind, TextBlockEvent, TextBlockKind, - ToolCall, ToolUseBlockEvent, ToolUseBlockKind, UsageEvent, UsageKind, WorkerSubscriber, +use crate::{ + handler::{ + ErrorKind, Handler, StatusKind, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, + ToolUseBlockKind, UsageKind, + }, + hook::ToolCall, + timeline::event::{ErrorEvent, StatusEvent, UsageEvent}, }; +// ============================================================================= +// WorkerSubscriber Trait +// ============================================================================= + +/// LLMからのストリーミングイベントを購読するトレイト +/// +/// Workerに登録すると、テキスト生成やツール呼び出しのイベントを +/// リアルタイムで受信できます。UIへのストリーム表示に最適です。 +/// +/// # 受信できるイベント +/// +/// - **ブロックイベント**: テキスト、ツール使用(スコープ付き) +/// - **メタイベント**: 使用量、ステータス、エラー +/// - **完了イベント**: テキスト完了、ツール呼び出し完了 +/// - **ターン制御**: ターン開始、ターン終了 +/// +/// # Examples +/// +/// ```ignore +/// use worker::subscriber::WorkerSubscriber; +/// use worker::timeline::TextBlockEvent; +/// +/// struct StreamPrinter; +/// +/// impl WorkerSubscriber for StreamPrinter { +/// type TextBlockScope = (); +/// type ToolUseBlockScope = (); +/// +/// fn on_text_block(&mut self, _: &mut (), event: &TextBlockEvent) { +/// if let TextBlockEvent::Delta(text) = event { +/// print!("{}", text); // リアルタイム出力 +/// } +/// } +/// +/// fn on_text_complete(&mut self, text: &str) { +/// println!("\n--- Complete: {} chars ---", text.len()); +/// } +/// } +/// +/// // Workerに登録 +/// worker.subscribe(StreamPrinter); +/// ``` +pub trait WorkerSubscriber: Send { + // ========================================================================= + // スコープ型(ブロックイベント用) + // ========================================================================= + + /// テキストブロック処理用のスコープ型 + /// + /// ブロック開始時にDefault::default()で生成され、 + /// ブロック終了時に破棄される。 + type TextBlockScope: Default + Send; + + /// ツール使用ブロック処理用のスコープ型 + type ToolUseBlockScope: Default + Send; + + // ========================================================================= + // ブロックイベント(スコープ管理あり) + // ========================================================================= + + /// テキストブロックイベント + /// + /// Start/Delta/Stopのライフサイクルを持つ。 + /// scopeはブロック開始時に生成され、終了時に破棄される。 + #[allow(unused_variables)] + fn on_text_block(&mut self, scope: &mut Self::TextBlockScope, event: &TextBlockEvent) {} + + /// ツール使用ブロックイベント + /// + /// Start/InputJsonDelta/Stopのライフサイクルを持つ。 + #[allow(unused_variables)] + fn on_tool_use_block( + &mut self, + scope: &mut Self::ToolUseBlockScope, + event: &ToolUseBlockEvent, + ) { + } + + // ========================================================================= + // 単発イベント(スコープ不要) + // ========================================================================= + + /// 使用量イベント + #[allow(unused_variables)] + fn on_usage(&mut self, event: &UsageEvent) {} + + /// ステータスイベント + #[allow(unused_variables)] + fn on_status(&mut self, event: &StatusEvent) {} + + /// エラーイベント + #[allow(unused_variables)] + fn on_error(&mut self, event: &ErrorEvent) {} + + // ========================================================================= + // 累積イベント(Worker層で追加) + // ========================================================================= + + /// テキスト完了イベント + /// + /// テキストブロックが完了した時点で、累積されたテキスト全体が渡される。 + /// ブロック処理後の最終結果を受け取るのに便利。 + #[allow(unused_variables)] + fn on_text_complete(&mut self, text: &str) {} + + /// ツール呼び出し完了イベント + /// + /// ツール使用ブロックが完了した時点で、完全なToolCallが渡される。 + #[allow(unused_variables)] + fn on_tool_call_complete(&mut self, call: &ToolCall) {} + + // ========================================================================= + // ターン制御 + // ========================================================================= + + /// ターン開始時 + /// + /// `turn`は0から始まるターン番号。 + #[allow(unused_variables)] + fn on_turn_start(&mut self, turn: usize) {} + + /// ターン終了時 + #[allow(unused_variables)] + fn on_turn_end(&mut self, turn: usize) {} +} + // ============================================================================= // SubscriberAdapter - WorkerSubscriberをTimelineハンドラにブリッジ // ============================================================================= diff --git a/worker/src/timeline/event.rs b/worker/src/timeline/event.rs new file mode 100644 index 0000000..84117e1 --- /dev/null +++ b/worker/src/timeline/event.rs @@ -0,0 +1,448 @@ +//! Timeline層のイベント型 +//! +//! Timelineが受け取り、各Handlerへディスパッチするイベント表現。 + +use serde::{Deserialize, Serialize}; + +// ============================================================================= +// Core Event Types (from llm_client layer) +// ============================================================================= + +/// LLMからのストリーミングイベント +/// +/// 各LLMプロバイダからのレスポンスは、この`Event`のストリームとして +/// 統一的に処理されます。 +/// +/// # イベントの種類 +/// +/// - **メタイベント**: `Ping`, `Usage`, `Status`, `Error` +/// - **ブロックイベント**: `BlockStart`, `BlockDelta`, `BlockStop`, `BlockAbort` +/// +/// # ブロックのライフサイクル +/// +/// テキストやツール呼び出しは、`BlockStart` → `BlockDelta`(複数) → `BlockStop` +/// の順序でイベントが発生します。 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Event { + /// ハートビート + Ping(PingEvent), + /// トークン使用量 + Usage(UsageEvent), + /// ストリームのステータス変化 + Status(StatusEvent), + /// エラー発生 + Error(ErrorEvent), + + /// ブロック開始(テキスト、ツール使用等) + BlockStart(BlockStart), + /// ブロックの差分データ + BlockDelta(BlockDelta), + /// ブロック正常終了 + BlockStop(BlockStop), + /// ブロック中断 + BlockAbort(BlockAbort), +} + +// ============================================================================= +// Meta Events +// ============================================================================= + +/// Pingイベント(ハートビート) +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct PingEvent { + pub timestamp: Option, +} + +/// 使用量イベント +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] +pub struct UsageEvent { + /// 入力トークン数 + pub input_tokens: Option, + /// 出力トークン数 + pub output_tokens: Option, + /// 合計トークン数 + pub total_tokens: Option, + /// キャッシュ読み込みトークン数 + pub cache_read_input_tokens: Option, + /// キャッシュ作成トークン数 + pub cache_creation_input_tokens: Option, +} + +/// ステータスイベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StatusEvent { + pub status: ResponseStatus, +} + +/// レスポンスステータス +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ResponseStatus { + /// ストリーム開始 + Started, + /// 正常完了 + Completed, + /// キャンセルされた + Cancelled, + /// エラー発生 + Failed, +} + +/// エラーイベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ErrorEvent { + pub code: Option, + pub message: String, +} + +// ============================================================================= +// Block Types +// ============================================================================= + +/// ブロックの種別 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum BlockType { + /// テキスト生成 + Text, + /// 思考 (Claude Extended Thinking等) + Thinking, + /// ツール呼び出し + ToolUse, + /// ツール結果 + ToolResult, +} + +/// ブロック開始イベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockStart { + /// ブロックのインデックス + pub index: usize, + /// ブロックの種別 + pub block_type: BlockType, + /// ブロック固有のメタデータ + pub metadata: BlockMetadata, +} + +impl BlockStart { + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// ブロックのメタデータ +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum BlockMetadata { + Text, + Thinking, + ToolUse { id: String, name: String }, + ToolResult { tool_use_id: String }, +} + +/// ブロックデルタイベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockDelta { + /// ブロックのインデックス + pub index: usize, + /// デルタの内容 + pub delta: DeltaContent, +} + +/// デルタの内容 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum DeltaContent { + /// テキストデルタ + Text(String), + /// 思考デルタ + Thinking(String), + /// ツール引数のJSON部分文字列 + InputJson(String), +} + +impl DeltaContent { + /// デルタのブロック種別を取得 + pub fn block_type(&self) -> BlockType { + match self { + DeltaContent::Text(_) => BlockType::Text, + DeltaContent::Thinking(_) => BlockType::Thinking, + DeltaContent::InputJson(_) => BlockType::ToolUse, + } + } +} + +/// ブロック停止イベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockStop { + /// ブロックのインデックス + pub index: usize, + /// ブロックの種別 + pub block_type: BlockType, + /// 停止理由 + pub stop_reason: Option, +} + +impl BlockStop { + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// ブロック中断イベント +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct BlockAbort { + /// ブロックのインデックス + pub index: usize, + /// ブロックの種別 + pub block_type: BlockType, + /// 中断理由 + pub reason: String, +} + +impl BlockAbort { + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// 停止理由 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum StopReason { + /// 自然終了 + EndTurn, + /// 最大トークン数到達 + MaxTokens, + /// ストップシーケンス到達 + StopSequence, + /// ツール使用 + ToolUse, +} + +// ============================================================================= +// Builder / Factory helpers +// ============================================================================= + +impl Event { + /// テキストブロック開始イベントを作成 + pub fn text_block_start(index: usize) -> Self { + Event::BlockStart(BlockStart { + index, + block_type: BlockType::Text, + metadata: BlockMetadata::Text, + }) + } + + /// テキストデルタイベントを作成 + pub fn text_delta(index: usize, text: impl Into) -> Self { + Event::BlockDelta(BlockDelta { + index, + delta: DeltaContent::Text(text.into()), + }) + } + + /// テキストブロック停止イベントを作成 + pub fn text_block_stop(index: usize, stop_reason: Option) -> Self { + Event::BlockStop(BlockStop { + index, + block_type: BlockType::Text, + stop_reason, + }) + } + + /// ツール使用ブロック開始イベントを作成 + pub fn tool_use_start(index: usize, id: impl Into, name: impl Into) -> Self { + Event::BlockStart(BlockStart { + index, + block_type: BlockType::ToolUse, + metadata: BlockMetadata::ToolUse { + id: id.into(), + name: name.into(), + }, + }) + } + + /// ツール引数デルタイベントを作成 + pub fn tool_input_delta(index: usize, json: impl Into) -> Self { + Event::BlockDelta(BlockDelta { + index, + delta: DeltaContent::InputJson(json.into()), + }) + } + + /// ツール使用ブロック停止イベントを作成 + pub fn tool_use_stop(index: usize) -> Self { + Event::BlockStop(BlockStop { + index, + block_type: BlockType::ToolUse, + stop_reason: Some(StopReason::ToolUse), + }) + } + + /// 使用量イベントを作成 + pub fn usage(input_tokens: u64, output_tokens: u64) -> Self { + Event::Usage(UsageEvent { + input_tokens: Some(input_tokens), + output_tokens: Some(output_tokens), + total_tokens: Some(input_tokens + output_tokens), + cache_read_input_tokens: None, + cache_creation_input_tokens: None, + }) + } + + /// Pingイベントを作成 + pub fn ping() -> Self { + Event::Ping(PingEvent { timestamp: None }) + } +} + +// ============================================================================= +// Conversions: llm_client::event -> timeline::event +// ============================================================================= + +impl From for ResponseStatus { + fn from(value: crate::llm_client::event::ResponseStatus) -> Self { + match value { + crate::llm_client::event::ResponseStatus::Started => ResponseStatus::Started, + crate::llm_client::event::ResponseStatus::Completed => ResponseStatus::Completed, + crate::llm_client::event::ResponseStatus::Cancelled => ResponseStatus::Cancelled, + crate::llm_client::event::ResponseStatus::Failed => ResponseStatus::Failed, + } + } +} + +impl From for BlockType { + fn from(value: crate::llm_client::event::BlockType) -> Self { + match value { + crate::llm_client::event::BlockType::Text => BlockType::Text, + crate::llm_client::event::BlockType::Thinking => BlockType::Thinking, + crate::llm_client::event::BlockType::ToolUse => BlockType::ToolUse, + crate::llm_client::event::BlockType::ToolResult => BlockType::ToolResult, + } + } +} + +impl From for BlockMetadata { + fn from(value: crate::llm_client::event::BlockMetadata) -> Self { + match value { + crate::llm_client::event::BlockMetadata::Text => BlockMetadata::Text, + crate::llm_client::event::BlockMetadata::Thinking => BlockMetadata::Thinking, + crate::llm_client::event::BlockMetadata::ToolUse { id, name } => { + BlockMetadata::ToolUse { id, name } + } + crate::llm_client::event::BlockMetadata::ToolResult { tool_use_id } => { + BlockMetadata::ToolResult { tool_use_id } + } + } + } +} + +impl From for DeltaContent { + fn from(value: crate::llm_client::event::DeltaContent) -> Self { + match value { + crate::llm_client::event::DeltaContent::Text(text) => DeltaContent::Text(text), + crate::llm_client::event::DeltaContent::Thinking(text) => DeltaContent::Thinking(text), + crate::llm_client::event::DeltaContent::InputJson(json) => { + DeltaContent::InputJson(json) + } + } + } +} + +impl From for StopReason { + fn from(value: crate::llm_client::event::StopReason) -> Self { + match value { + crate::llm_client::event::StopReason::EndTurn => StopReason::EndTurn, + crate::llm_client::event::StopReason::MaxTokens => StopReason::MaxTokens, + crate::llm_client::event::StopReason::StopSequence => StopReason::StopSequence, + crate::llm_client::event::StopReason::ToolUse => StopReason::ToolUse, + } + } +} + +impl From for PingEvent { + fn from(value: crate::llm_client::event::PingEvent) -> Self { + PingEvent { + timestamp: value.timestamp, + } + } +} + +impl From for UsageEvent { + fn from(value: crate::llm_client::event::UsageEvent) -> Self { + UsageEvent { + input_tokens: value.input_tokens, + output_tokens: value.output_tokens, + total_tokens: value.total_tokens, + cache_read_input_tokens: value.cache_read_input_tokens, + cache_creation_input_tokens: value.cache_creation_input_tokens, + } + } +} + +impl From for StatusEvent { + fn from(value: crate::llm_client::event::StatusEvent) -> Self { + StatusEvent { + status: value.status.into(), + } + } +} + +impl From for ErrorEvent { + fn from(value: crate::llm_client::event::ErrorEvent) -> Self { + ErrorEvent { + code: value.code, + message: value.message, + } + } +} + +impl From for BlockStart { + fn from(value: crate::llm_client::event::BlockStart) -> Self { + BlockStart { + index: value.index, + block_type: value.block_type.into(), + metadata: value.metadata.into(), + } + } +} + +impl From for BlockDelta { + fn from(value: crate::llm_client::event::BlockDelta) -> Self { + BlockDelta { + index: value.index, + delta: value.delta.into(), + } + } +} + +impl From for BlockStop { + fn from(value: crate::llm_client::event::BlockStop) -> Self { + BlockStop { + index: value.index, + block_type: value.block_type.into(), + stop_reason: value.stop_reason.map(Into::into), + } + } +} + +impl From for BlockAbort { + fn from(value: crate::llm_client::event::BlockAbort) -> Self { + BlockAbort { + index: value.index, + block_type: value.block_type.into(), + reason: value.reason, + } + } +} + +impl From for Event { + fn from(value: crate::llm_client::event::Event) -> Self { + match value { + crate::llm_client::event::Event::Ping(p) => Event::Ping(p.into()), + crate::llm_client::event::Event::Usage(u) => Event::Usage(u.into()), + crate::llm_client::event::Event::Status(s) => Event::Status(s.into()), + crate::llm_client::event::Event::Error(e) => Event::Error(e.into()), + crate::llm_client::event::Event::BlockStart(s) => Event::BlockStart(s.into()), + crate::llm_client::event::Event::BlockDelta(d) => Event::BlockDelta(d.into()), + crate::llm_client::event::Event::BlockStop(s) => Event::BlockStop(s.into()), + crate::llm_client::event::Event::BlockAbort(a) => Event::BlockAbort(a.into()), + } + } +} diff --git a/worker/src/timeline/mod.rs b/worker/src/timeline/mod.rs index 0df211f..1f944a2 100644 --- a/worker/src/timeline/mod.rs +++ b/worker/src/timeline/mod.rs @@ -9,25 +9,39 @@ //! - [`TextBlockCollector`] - テキストブロックを収集するHandler //! - [`ToolCallCollector`] - ツール呼び出しを収集するHandler +pub mod event; mod text_block_collector; mod timeline; mod tool_call_collector; // 公開API +pub use event::*; pub use text_block_collector::TextBlockCollector; pub use timeline::{ErasedHandler, HandlerWrapper, Timeline}; pub use tool_call_collector::ToolCallCollector; -// worker-typesからのre-export -pub use worker_types::{ - // Core traits - Handler, Kind, - // Block Kinds - TextBlockKind, ThinkingBlockKind, ToolUseBlockKind, - // Block Events - TextBlockEvent, TextBlockStart, TextBlockStop, - ThinkingBlockEvent, ThinkingBlockStart, ThinkingBlockStop, - ToolUseBlockEvent, ToolUseBlockStart, ToolUseBlockStop, +// 型定義からのre-export +pub use crate::handler::{ // Meta Kinds - ErrorKind, PingKind, StatusKind, UsageKind, + ErrorKind, + // Core traits + Handler, + Kind, + PingKind, + StatusKind, + // Block Events + TextBlockEvent, + // Block Kinds + TextBlockKind, + TextBlockStart, + TextBlockStop, + ThinkingBlockEvent, + ThinkingBlockKind, + ThinkingBlockStart, + ThinkingBlockStop, + ToolUseBlockEvent, + ToolUseBlockKind, + ToolUseBlockStart, + ToolUseBlockStop, + UsageKind, }; diff --git a/worker/src/timeline/text_block_collector.rs b/worker/src/timeline/text_block_collector.rs index 455934e..f7d6f24 100644 --- a/worker/src/timeline/text_block_collector.rs +++ b/worker/src/timeline/text_block_collector.rs @@ -3,8 +3,8 @@ //! TimelineのTextBlockHandler として登録され、 //! ストリーム中のテキストブロックを収集する。 +use crate::handler::{Handler, TextBlockEvent, TextBlockKind}; use std::sync::{Arc, Mutex}; -use worker_types::{Handler, TextBlockEvent, TextBlockKind}; /// TextBlockから収集したテキスト情報を保持 #[derive(Debug, Default)] @@ -85,7 +85,7 @@ impl Handler for TextBlockCollector { mod tests { use super::*; use crate::timeline::Timeline; - use worker_types::Event; + use crate::timeline::event::Event; /// TextBlockCollectorが単一のテキストブロックを正しく収集することを確認 #[test] diff --git a/worker/src/timeline/timeline.rs b/worker/src/timeline/timeline.rs index 7d85f85..a619085 100644 --- a/worker/src/timeline/timeline.rs +++ b/worker/src/timeline/timeline.rs @@ -5,7 +5,8 @@ use std::marker::PhantomData; -use worker_types::*; +use super::event::*; +use crate::handler::*; // ============================================================================= // Type-erased Handler diff --git a/worker/src/timeline/tool_call_collector.rs b/worker/src/timeline/tool_call_collector.rs index 9817ccb..853ec5e 100644 --- a/worker/src/timeline/tool_call_collector.rs +++ b/worker/src/timeline/tool_call_collector.rs @@ -3,8 +3,11 @@ //! TimelineのToolUseBlockHandler として登録され、 //! ストリーム中のToolUseブロックを収集する。 +use crate::{ + handler::{Handler, ToolUseBlockEvent, ToolUseBlockKind}, + hook::ToolCall, +}; use std::sync::{Arc, Mutex}; -use worker_types::{Handler, ToolCall, ToolUseBlockEvent, ToolUseBlockKind}; /// ToolUseブロックから収集したツール呼び出し情報を保持 /// @@ -98,7 +101,7 @@ impl Handler for ToolCallCollector { mod tests { use super::*; use crate::timeline::Timeline; - use worker_types::Event; + use crate::timeline::event::Event; #[test] fn test_collect_single_tool_call() { diff --git a/worker-types/src/tool.rs b/worker/src/tool.rs similarity index 98% rename from worker-types/src/tool.rs rename to worker/src/tool.rs index aa4b472..5c5d527 100644 --- a/worker-types/src/tool.rs +++ b/worker/src/tool.rs @@ -43,7 +43,7 @@ pub enum ToolError { /// # 手動実装 /// /// ```ignore -/// use worker::{Tool, ToolError}; +/// use worker::tool::{Tool, ToolError}; /// use serde_json::{json, Value}; /// /// struct MyTool; diff --git a/worker/src/worker.rs b/worker/src/worker.rs index fe6aafb..b5f2318 100644 --- a/worker/src/worker.rs +++ b/worker/src/worker.rs @@ -5,15 +5,17 @@ use std::sync::{Arc, Mutex}; use futures::StreamExt; use tracing::{debug, info, trace, warn}; -use crate::timeline::{TextBlockCollector, Timeline, ToolCallCollector}; -use crate::llm_client::{ClientError, LlmClient, Request, ToolDefinition}; -use crate::subscriber_adapter::{ - ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter, - ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, -}; -use worker_types::{ - ContentPart, ControlFlow, HookError, Locked, Message, MessageContent, Mutable, Tool, ToolCall, - ToolError, ToolResult, TurnResult, WorkerHook, WorkerState, WorkerSubscriber, +use crate::{ + ContentPart, Message, MessageContent, Role, + hook::{ControlFlow, HookError, ToolCall, ToolResult, TurnResult, WorkerHook}, + llm_client::{ClientError, LlmClient, Request, ToolDefinition}, + state::{Locked, Mutable, WorkerState}, + subscriber::{ + ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter, + ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, WorkerSubscriber, + }, + timeline::{TextBlockCollector, Timeline, ToolCallCollector}, + tool::{Tool, ToolError}, }; // ============================================================================= @@ -321,7 +323,7 @@ impl Worker { } Some(Message { - role: worker_types::Role::Assistant, + role: Role::Assistant, content: MessageContent::Parts(parts), }) } @@ -337,49 +339,45 @@ impl Worker { // メッセージを追加 for msg in &self.history { - // worker-types::Message から llm_client::Message への変換 + // Message から llm_client::Message への変換 request = request.message(crate::llm_client::Message { role: match msg.role { - worker_types::Role::User => crate::llm_client::Role::User, - worker_types::Role::Assistant => crate::llm_client::Role::Assistant, + Role::User => crate::llm_client::Role::User, + Role::Assistant => crate::llm_client::Role::Assistant, }, content: match &msg.content { - worker_types::MessageContent::Text(t) => { - crate::llm_client::MessageContent::Text(t.clone()) - } - worker_types::MessageContent::ToolResult { + MessageContent::Text(t) => crate::llm_client::MessageContent::Text(t.clone()), + MessageContent::ToolResult { tool_use_id, content, } => crate::llm_client::MessageContent::ToolResult { tool_use_id: tool_use_id.clone(), content: content.clone(), }, - worker_types::MessageContent::Parts(parts) => { - crate::llm_client::MessageContent::Parts( - parts - .iter() - .map(|p| match p { - worker_types::ContentPart::Text { text } => { - crate::llm_client::ContentPart::Text { text: text.clone() } + MessageContent::Parts(parts) => crate::llm_client::MessageContent::Parts( + parts + .iter() + .map(|p| match p { + ContentPart::Text { text } => { + crate::llm_client::ContentPart::Text { text: text.clone() } + } + ContentPart::ToolUse { id, name, input } => { + crate::llm_client::ContentPart::ToolUse { + id: id.clone(), + name: name.clone(), + input: input.clone(), } - worker_types::ContentPart::ToolUse { id, name, input } => { - crate::llm_client::ContentPart::ToolUse { - id: id.clone(), - name: name.clone(), - input: input.clone(), - } - } - worker_types::ContentPart::ToolResult { - tool_use_id, - content, - } => crate::llm_client::ContentPart::ToolResult { - tool_use_id: tool_use_id.clone(), - content: content.clone(), - }, - }) - .collect(), - ) - } + } + ContentPart::ToolResult { + tool_use_id, + content, + } => crate::llm_client::ContentPart::ToolResult { + tool_use_id: tool_use_id.clone(), + content: content.clone(), + }, + }) + .collect(), + ), }, }); } @@ -550,7 +548,8 @@ impl Worker { } } let event = event_result?; - self.timeline.dispatch(&event); + let timeline_event: crate::timeline::event::Event = event.into(); + self.timeline.dispatch(&timeline_event); } debug!(event_count = event_count, "Stream completed"); diff --git a/worker/tests/common/mod.rs b/worker/tests/common/mod.rs index 2a6dfcd..86352aa 100644 --- a/worker/tests/common/mod.rs +++ b/worker/tests/common/mod.rs @@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; use futures::Stream; +use worker::llm_client::event::{BlockType, DeltaContent, Event}; use worker::llm_client::{ClientError, LlmClient, Request}; use worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline}; -use worker_types::{BlockType, DeltaContent, Event}; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -267,7 +267,8 @@ pub fn assert_timeline_integration(subdir: &str) { }); for event in &events { - timeline.dispatch(event); + let timeline_event: worker::timeline::event::Event = event.clone().into(); + timeline.dispatch(&timeline_event); } let texts = collected.lock().unwrap(); diff --git a/worker/tests/parallel_execution_test.rs b/worker/tests/parallel_execution_test.rs index f01b6e9..b7210d4 100644 --- a/worker/tests/parallel_execution_test.rs +++ b/worker/tests/parallel_execution_test.rs @@ -8,10 +8,9 @@ use std::time::{Duration, Instant}; use async_trait::async_trait; use worker::Worker; -use worker_types::{ - ControlFlow, Event, HookError, ResponseStatus, StatusEvent, Tool, ToolCall, ToolError, - ToolResult, WorkerHook, -}; +use worker::hook::{ControlFlow, HookError, ToolCall, ToolResult, WorkerHook}; +use worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; +use worker::tool::{Tool, ToolError}; mod common; use common::MockLlmClient; diff --git a/worker/tests/subscriber_test.rs b/worker/tests/subscriber_test.rs index f9a6147..e84e301 100644 --- a/worker/tests/subscriber_test.rs +++ b/worker/tests/subscriber_test.rs @@ -7,12 +7,12 @@ mod common; use std::sync::{Arc, Mutex}; use common::MockLlmClient; -use worker::subscriber::WorkerSubscriber; use worker::Worker; -use worker_types::{ - ErrorEvent, Event, ResponseStatus, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent, - UsageEvent, -}; +use worker::hook::ToolCall; +use worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent}; +use worker::subscriber::WorkerSubscriber; +use worker::timeline::event::{ErrorEvent, StatusEvent, UsageEvent}; +use worker::timeline::{TextBlockEvent, ToolUseBlockEvent}; // ============================================================================= // Test Subscriber @@ -101,7 +101,7 @@ async fn test_subscriber_text_block_events() { Event::text_delta(0, "Hello, "), Event::text_delta(0, "World!"), Event::text_block_stop(0, None), - Event::Status(StatusEvent { + Event::Status(ClientStatusEvent { status: ResponseStatus::Completed, }), ]; @@ -141,7 +141,7 @@ async fn test_subscriber_tool_call_complete() { Event::tool_input_delta(0, r#"{"city":"#), Event::tool_input_delta(0, r#""Tokyo"}"#), Event::tool_use_stop(0), - Event::Status(StatusEvent { + Event::Status(ClientStatusEvent { status: ResponseStatus::Completed, }), ]; @@ -172,7 +172,7 @@ async fn test_subscriber_turn_events() { Event::text_block_start(0), Event::text_delta(0, "Done!"), Event::text_block_stop(0, None), - Event::Status(StatusEvent { + Event::Status(ClientStatusEvent { status: ResponseStatus::Completed, }), ]; @@ -210,7 +210,7 @@ async fn test_subscriber_usage_events() { Event::text_delta(0, "Hello"), Event::text_block_stop(0, None), Event::usage(100, 50), - Event::Status(StatusEvent { + Event::Status(ClientStatusEvent { status: ResponseStatus::Completed, }), ]; diff --git a/worker/tests/tool_macro_test.rs b/worker/tests/tool_macro_test.rs index 9cb6d4d..5807be2 100644 --- a/worker/tests/tool_macro_test.rs +++ b/worker/tests/tool_macro_test.rs @@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use schemars; use serde; +use worker::tool::Tool; use worker_macros::tool_registry; -use worker_types::Tool; // ============================================================================= // Test: Basic Tool Generation diff --git a/worker/tests/worker_fixtures.rs b/worker/tests/worker_fixtures.rs index 9a43b3d..ebc4f97 100644 --- a/worker/tests/worker_fixtures.rs +++ b/worker/tests/worker_fixtures.rs @@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use async_trait::async_trait; use common::MockLlmClient; use worker::Worker; -use worker_types::{Tool, ToolError}; +use worker::tool::{Tool, ToolError}; /// フィクスチャディレクトリのパス fn fixtures_dir() -> std::path::PathBuf { @@ -100,7 +100,7 @@ fn test_mock_client_from_fixture() { /// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。 #[test] fn test_mock_client_from_events() { - use worker_types::Event; + use worker::llm_client::event::Event; // 直接イベントを指定 let events = vec![ @@ -178,7 +178,7 @@ async fn test_worker_tool_call() { /// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。 #[tokio::test] async fn test_worker_with_programmatic_events() { - use worker_types::{Event, ResponseStatus, StatusEvent}; + use worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; // プログラムでイベントシーケンスを構築 let events = vec![ @@ -205,8 +205,8 @@ async fn test_worker_with_programmatic_events() { /// id, name, input(JSON)を正しく抽出できることを検証する。 #[tokio::test] async fn test_tool_call_collector_integration() { + use worker::llm_client::event::Event; use worker::timeline::{Timeline, ToolCallCollector}; - use worker_types::Event; // ToolUseブロックを含むイベントシーケンス let events = vec![ @@ -222,7 +222,8 @@ async fn test_tool_call_collector_integration() { // イベントをディスパッチ for event in &events { - timeline.dispatch(event); + let timeline_event: worker::timeline::event::Event = event.clone().into(); + timeline.dispatch(&timeline_event); } // 収集されたToolCallを確認 diff --git a/worker/tests/worker_state_test.rs b/worker/tests/worker_state_test.rs index 7b7e6ed..cb0c264 100644 --- a/worker/tests/worker_state_test.rs +++ b/worker/tests/worker_state_test.rs @@ -7,7 +7,8 @@ mod common; use common::MockLlmClient; use worker::Worker; -use worker_types::{Event, Message, MessageContent, ResponseStatus, StatusEvent}; +use worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; +use worker::{Message, MessageContent}; // ============================================================================= // Mutable状態のテスト