update: Merge worker-types crate into worker crate

This commit is contained in:
Keisuke Hirata 2026-01-08 22:15:11 +09:00
parent a01f19b112
commit 7d398fa6de
41 changed files with 1195 additions and 503 deletions

View File

@ -1,109 +0,0 @@
---
description: ドキュメントコメントの書き方ガイドライン
---
# ドキュメントコメント スタイルガイド
## 基本原則
1. **利用者視点で書く**: 「何をするものか」「どう使うか」を先に、「なぜそう実装したか」は後に
2. **型パラメータはバッククォートで囲む**: `Handler<K>` ✓ / Handler<K>
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<K>からErasedHandler<K>へのラッパー
/// 各Handlerは独自のScope型を持つため、Timelineで保持するには型消去が必要
// ✅ 良い例(利用者視点)
/// `Handler<K>`を`ErasedHandler<K>`として扱うためのラッパー
///
/// 通常は直接使用せず、`Timeline::on_text_block()`などのメソッド経由で
/// 自動的にラップされます。
```
### メソッド
```rust
// ❌ 悪い例(処理内容の説明のみ)
/// ツールを登録する
// ✅ 良い例(何が起きるか、どう使うか)
/// ツールを登録する
///
/// 登録されたツールはLLMからの呼び出しで自動的に実行されます。
/// 同名のツールを登録した場合、後から登録したものが優先されます。
///
/// # Examples
///
/// ```
/// use worker::{Worker, Tool};
///
/// worker.register_tool(MyTool::new());
/// ```
```
### 型パラメータ
```rust
// ❌ HTMLタグとして解釈されてしまう
/// Handler<K>を保持するフィールド
// ✅ バッククォートで囲む
/// `Handler<K>`を保持するフィールド
```
## ドキュメントの配置
| 項目 | 配置場所 |
|-----|---------|
| 型/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行目は「何をするものか」を利用者視点で説明しているか
- [ ] 型パラメータ (`<T>`, `<K>` 等) はバッククォートで囲んでいるか
- [ ] 主要なpub APIにはExamplesがあるか
- [ ] Examplesの`use`パスは`worker::`になっているか
- [ ] `cargo doc --no-deps`で警告が出ないか

View File

@ -4,32 +4,3 @@
- クレートに依存関係を追加・更新する際は必ず - クレートに依存関係を追加・更新する際は必ず
`cargo`コマンドを使い、`Cargo.toml`を直接手で書き換えず、必ずコマンド経由で管理すること。 `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`の片方向依存を維持すること。

13
Cargo.lock generated
View File

@ -2167,7 +2167,6 @@ dependencies = [
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"worker-macros", "worker-macros",
"worker-types",
] ]
[[package]] [[package]]
@ -2177,18 +2176,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
"worker-types",
]
[[package]]
name = "worker-types"
version = "0.1.0"
dependencies = [
"async-trait",
"schemars",
"serde",
"serde_json",
"thiserror 2.0.17",
] ]
[[package]] [[package]]

View File

@ -2,6 +2,5 @@
resolver = "2" resolver = "2"
members = [ members = [
"worker", "worker",
"worker-types",
"worker-macros", "worker-macros",
] ]

View File

@ -1 +1,5 @@
# llm-worker-rs # 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.

View File

@ -11,4 +11,3 @@ proc-macro = true
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "2", features = ["full"] } syn = { version = "2", features = ["full"] }
worker-types = { path = "../worker-types" }

View File

@ -193,7 +193,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
quote! { quote! {
match result { match result {
Ok(val) => Ok(format!("{:?}", val)), Ok(val) => Ok(format!("{:?}", val)),
Err(e) => Err(worker_types::ToolError::ExecutionFailed(format!("{}", e))), Err(e) => Err(::worker::tool::ToolError::ExecutionFailed(format!("{}", e))),
} }
} }
} else { } else {
@ -230,7 +230,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} else { } else {
quote! { quote! {
let args: #args_struct_name = serde_json::from_str(input_json) 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; let result = self.ctx.#method_name(#(#arg_names),*)#awaiter;
#result_handling #result_handling
@ -246,7 +246,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl worker_types::Tool for #tool_struct_name { impl ::worker::tool::Tool for #tool_struct_name {
fn name(&self) -> &str { fn name(&self) -> &str {
#tool_name #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!({})) serde_json::to_value(schema).unwrap_or(serde_json::json!({}))
} }
async fn execute(&self, input_json: &str) -> Result<String, worker_types::ToolError> { async fn execute(&self, input_json: &str) -> Result<String, ::worker::tool::ToolError> {
#execute_body #execute_body
} }
} }

View File

@ -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"

View File

@ -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::*;

View File

@ -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) {}
}

View File

@ -13,8 +13,7 @@ serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }
tracing = "0.1" tracing = "0.1"
worker-macros = { path = "../worker-macros" } worker-macros = { path = "../worker-macros", version = "0.1" }
worker-types = { path = "../worker-types" }
[dev-dependencies] [dev-dependencies]
clap = { version = "4.5.54", features = ["derive", "env"] } clap = { version = "4.5.54", features = ["derive", "env"] }

446
worker/src/event.rs Normal file
View File

@ -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<u64>,
}
/// 使用量イベント
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct UsageEvent {
/// 入力トークン数
pub input_tokens: Option<u64>,
/// 出力トークン数
pub output_tokens: Option<u64>,
/// 合計トークン数
pub total_tokens: Option<u64>,
/// キャッシュ読み込みトークン数
pub cache_read_input_tokens: Option<u64>,
/// キャッシュ作成トークン数
pub cache_creation_input_tokens: Option<u64>,
}
/// ステータスイベント
#[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<String>,
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<StopReason>,
}
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<String>) -> Self {
Event::BlockDelta(BlockDelta {
index,
delta: DeltaContent::Text(text.into()),
})
}
/// テキストブロック停止イベントを作成
pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self {
Event::BlockStop(BlockStop {
index,
block_type: BlockType::Text,
stop_reason,
})
}
/// ツール使用ブロック開始イベントを作成
pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> 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<String>) -> 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<crate::timeline::event::ResponseStatus> 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<crate::timeline::event::BlockType> 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<crate::timeline::event::BlockMetadata> 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<crate::timeline::event::DeltaContent> 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<crate::timeline::event::StopReason> 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<crate::timeline::event::PingEvent> for PingEvent {
fn from(value: crate::timeline::event::PingEvent) -> Self {
PingEvent {
timestamp: value.timestamp,
}
}
}
impl From<crate::timeline::event::UsageEvent> 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<crate::timeline::event::StatusEvent> for StatusEvent {
fn from(value: crate::timeline::event::StatusEvent) -> Self {
StatusEvent {
status: value.status.into(),
}
}
}
impl From<crate::timeline::event::ErrorEvent> for ErrorEvent {
fn from(value: crate::timeline::event::ErrorEvent) -> Self {
ErrorEvent {
code: value.code,
message: value.message,
}
}
}
impl From<crate::timeline::event::BlockStart> 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<crate::timeline::event::BlockDelta> for BlockDelta {
fn from(value: crate::timeline::event::BlockDelta) -> Self {
BlockDelta {
index: value.index,
delta: value.delta.into(),
}
}
}
impl From<crate::timeline::event::BlockStop> 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<crate::timeline::event::BlockAbort> 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<crate::timeline::event::Event> 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()),
}
}
}

View File

@ -4,7 +4,7 @@
//! カスタムハンドラを実装してTimelineに登録することで、 //! カスタムハンドラを実装してTimelineに登録することで、
//! ストリームイベントを受信できます。 //! ストリームイベントを受信できます。
use crate::event::*; use crate::timeline::event::*;
// ============================================================================= // =============================================================================
// Kind Trait // Kind Trait
@ -32,7 +32,7 @@ pub trait Kind {
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```ignore
/// use worker::{Handler, TextBlockKind, TextBlockEvent}; /// use worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
/// ///
/// struct TextCollector { /// struct TextCollector {
/// texts: Vec<String>, /// texts: Vec<String>,

View File

@ -109,7 +109,8 @@ pub enum HookError {
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```ignore
/// use worker::{WorkerHook, ControlFlow, HookError, ToolCall, TurnResult, Message}; /// use worker::hook::{ControlFlow, HookError, ToolCall, TurnResult, WorkerHook};
/// use worker::Message;
/// ///
/// struct ValidationHook; /// struct ValidationHook;
/// ///

View File

@ -39,55 +39,18 @@
pub mod llm_client; pub mod llm_client;
pub mod timeline; 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; mod worker;
// ============================================================================= // =============================================================================
// トップレベル公開(最も頻繁に使う型) // トップレベル公開(最も頻繁に使う型)
// ============================================================================= // =============================================================================
pub use message::{ContentPart, Message, MessageContent, Role};
pub use worker::{Worker, WorkerConfig, WorkerError}; 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};
}

View File

@ -2,11 +2,9 @@
use std::pin::Pin; use std::pin::Pin;
use crate::llm_client::{ClientError, Request, event::Event};
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use worker_types::Event;
use crate::llm_client::{ClientError, Request};
/// LLMクライアントのtrait /// LLMクライアントのtrait
/// ///

View File

@ -1,7 +1,6 @@
//! イベント型 //! LLMクライアント層のイベント型
//! //!
//! LLMからのストリーミングレスポンスを表現するイベント型。 //! 各LLMプロバイダからのストリーミングレスポンスを表現するイベント型。
//! Timeline層がこのイベントを受信し、ハンドラにディスパッチします。
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,6 +1,7 @@
//! LLMクライアント層 //! LLMクライアント層
//! //!
//! 各LLMプロバイダと通信し、統一された[`Event`](crate::event::Event)ストリームを出力します。 //! 各LLMプロバイダと通信し、統一された[`Event`](crate::llm_client::event::Event)
//! ストリームを出力します。
//! //!
//! # サポートするプロバイダ //! # サポートするプロバイダ
//! //!
@ -17,6 +18,7 @@
pub mod client; pub mod client;
pub mod error; pub mod error;
pub mod event;
pub mod types; pub mod types;
pub mod providers; pub mod providers;
@ -24,4 +26,5 @@ pub mod scheme;
pub use client::*; pub use client::*;
pub use error::*; pub use error::*;
pub use event::*;
pub use types::*; pub use types::*;

View File

@ -4,13 +4,13 @@
use std::pin::Pin; use std::pin::Pin;
use crate::llm_client::{
ClientError, LlmClient, Request, event::Event, scheme::anthropic::AnthropicScheme,
};
use async_trait::async_trait; use async_trait::async_trait;
use eventsource_stream::Eventsource; use eventsource_stream::Eventsource;
use futures::{Stream, StreamExt, TryStreamExt, future::ready}; use futures::{Stream, StreamExt, TryStreamExt, future::ready};
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
use worker_types::Event;
use crate::llm_client::{ClientError, LlmClient, Request, scheme::anthropic::AnthropicScheme};
/// Anthropic クライアント /// Anthropic クライアント
pub struct AnthropicClient { pub struct AnthropicClient {
@ -156,7 +156,8 @@ impl LlmClient for AnthropicClient {
if let Some(block_type) = current_block_type.take() { if let Some(block_type) = current_block_type.take() {
// 正しいブロックタイプで上書き // 正しいブロックタイプで上書き
// (Event::BlockStopの中身を置換) // (Event::BlockStopの中身を置換)
evt = Event::BlockStop(worker_types::BlockStop { evt =
Event::BlockStop(crate::llm_client::event::BlockStop {
block_type, block_type,
..stop.clone() ..stop.clone()
}); });

View File

@ -4,13 +4,13 @@
use std::pin::Pin; use std::pin::Pin;
use crate::llm_client::{
ClientError, LlmClient, Request, event::Event, scheme::gemini::GeminiScheme,
};
use async_trait::async_trait; use async_trait::async_trait;
use eventsource_stream::Eventsource; use eventsource_stream::Eventsource;
use futures::{Stream, StreamExt, TryStreamExt}; use futures::{Stream, StreamExt, TryStreamExt};
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
use worker_types::Event;
use crate::llm_client::{ClientError, LlmClient, Request, scheme::gemini::GeminiScheme};
/// Gemini クライアント /// Gemini クライアント
pub struct GeminiClient { pub struct GeminiClient {

View File

@ -5,13 +5,12 @@
use std::pin::Pin; 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 async_trait::async_trait;
use futures::Stream; use futures::Stream;
use worker_types::Event;
use crate::llm_client::{
ClientError, LlmClient, Request, providers::openai::OpenAIClient, scheme::openai::OpenAIScheme,
};
/// Ollama クライアント /// Ollama クライアント
/// ///

View File

@ -4,13 +4,13 @@
use std::pin::Pin; use std::pin::Pin;
use crate::llm_client::{
ClientError, LlmClient, Request, event::Event, scheme::openai::OpenAIScheme,
};
use async_trait::async_trait; use async_trait::async_trait;
use eventsource_stream::Eventsource; use eventsource_stream::Eventsource;
use futures::{Stream, StreamExt, TryStreamExt}; use futures::{Stream, StreamExt, TryStreamExt};
use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue}; use reqwest::header::{CONTENT_TYPE, HeaderMap, HeaderValue};
use worker_types::Event;
use crate::llm_client::{ClientError, LlmClient, Request, scheme::openai::OpenAIScheme};
/// OpenAI クライアント /// OpenAI クライアント
pub struct OpenAIClient { pub struct OpenAIClient {

View File

@ -2,13 +2,14 @@
//! //!
//! Anthropic Messages APIのSSEイベントをパースし、統一Event型に変換 //! Anthropic Messages APIのSSEイベントをパースし、統一Event型に変換
use serde::Deserialize; use crate::llm_client::{
use worker_types::{ ClientError,
BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent, Event, event::{
PingEvent, ResponseStatus, StatusEvent, UsageEvent, BlockDelta, BlockMetadata, BlockStart, BlockStop, BlockType, DeltaContent, ErrorEvent,
Event, PingEvent, ResponseStatus, StatusEvent, UsageEvent,
},
}; };
use serde::Deserialize;
use crate::llm_client::ClientError;
use super::AnthropicScheme; use super::AnthropicScheme;

View File

@ -2,12 +2,11 @@
//! //!
//! Google Gemini APIのSSEイベントをパースし、統一Event型に変換 //! Google Gemini APIのSSEイベントをパースし、統一Event型に変換
use serde::Deserialize; use crate::llm_client::{
use worker_types::{ ClientError,
BlockMetadata, BlockStart, BlockStop, BlockType, Event, StopReason, UsageEvent, event::{BlockMetadata, BlockStart, BlockStop, BlockType, Event, StopReason, UsageEvent},
}; };
use serde::Deserialize;
use crate::llm_client::ClientError;
use super::GeminiScheme; use super::GeminiScheme;
@ -231,7 +230,7 @@ impl GeminiScheme {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use worker_types::DeltaContent; use crate::llm_client::event::DeltaContent;
#[test] #[test]
fn test_parse_text_response() { fn test_parse_text_response() {

View File

@ -1,9 +1,10 @@
//! OpenAI SSEイベントパース //! OpenAI SSEイベントパース
use crate::llm_client::{
ClientError,
event::{Event, StopReason, UsageEvent},
};
use serde::Deserialize; use serde::Deserialize;
use worker_types::{Event, StopReason, UsageEvent};
use crate::llm_client::ClientError;
use super::OpenAIScheme; use super::OpenAIScheme;
@ -155,7 +156,7 @@ impl OpenAIScheme {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use worker_types::DeltaContent; use crate::llm_client::event::DeltaContent;
#[test] #[test]
fn test_parse_text_delta() { fn test_parse_text_delta() {
@ -188,7 +189,7 @@ mod tests {
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
if let Event::BlockStart(start) = &events[0] { if let Event::BlockStart(start) = &events[0] {
assert_eq!(start.index, 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!(id, "call_abc");
assert_eq!(name, "get_weather"); assert_eq!(name, "get_weather");
} else { } else {

View File

@ -1,14 +1,145 @@
//! WorkerSubscriber統合 //! イベント購読
//! //!
//! WorkerSubscriberをTimeline層のHandlerとしてブリッジする実装 //! LLMからのストリーミングイベントをリアルタイムで受信するためのトレイト。
//! UIへのストリーム表示やプログレス表示に使用します。
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use worker_types::{ use crate::{
ErrorEvent, ErrorKind, Handler, StatusEvent, StatusKind, TextBlockEvent, TextBlockKind, handler::{
ToolCall, ToolUseBlockEvent, ToolUseBlockKind, UsageEvent, UsageKind, WorkerSubscriber, 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ハンドラにブリッジ // SubscriberAdapter - WorkerSubscriberをTimelineハンドラにブリッジ
// ============================================================================= // =============================================================================

View File

@ -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<u64>,
}
/// 使用量イベント
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct UsageEvent {
/// 入力トークン数
pub input_tokens: Option<u64>,
/// 出力トークン数
pub output_tokens: Option<u64>,
/// 合計トークン数
pub total_tokens: Option<u64>,
/// キャッシュ読み込みトークン数
pub cache_read_input_tokens: Option<u64>,
/// キャッシュ作成トークン数
pub cache_creation_input_tokens: Option<u64>,
}
/// ステータスイベント
#[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<String>,
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<StopReason>,
}
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<String>) -> Self {
Event::BlockDelta(BlockDelta {
index,
delta: DeltaContent::Text(text.into()),
})
}
/// テキストブロック停止イベントを作成
pub fn text_block_stop(index: usize, stop_reason: Option<StopReason>) -> Self {
Event::BlockStop(BlockStop {
index,
block_type: BlockType::Text,
stop_reason,
})
}
/// ツール使用ブロック開始イベントを作成
pub fn tool_use_start(index: usize, id: impl Into<String>, name: impl Into<String>) -> 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<String>) -> 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<crate::llm_client::event::ResponseStatus> 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<crate::llm_client::event::BlockType> 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<crate::llm_client::event::BlockMetadata> 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<crate::llm_client::event::DeltaContent> 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<crate::llm_client::event::StopReason> 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<crate::llm_client::event::PingEvent> for PingEvent {
fn from(value: crate::llm_client::event::PingEvent) -> Self {
PingEvent {
timestamp: value.timestamp,
}
}
}
impl From<crate::llm_client::event::UsageEvent> 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<crate::llm_client::event::StatusEvent> for StatusEvent {
fn from(value: crate::llm_client::event::StatusEvent) -> Self {
StatusEvent {
status: value.status.into(),
}
}
}
impl From<crate::llm_client::event::ErrorEvent> for ErrorEvent {
fn from(value: crate::llm_client::event::ErrorEvent) -> Self {
ErrorEvent {
code: value.code,
message: value.message,
}
}
}
impl From<crate::llm_client::event::BlockStart> 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<crate::llm_client::event::BlockDelta> for BlockDelta {
fn from(value: crate::llm_client::event::BlockDelta) -> Self {
BlockDelta {
index: value.index,
delta: value.delta.into(),
}
}
}
impl From<crate::llm_client::event::BlockStop> 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<crate::llm_client::event::BlockAbort> 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<crate::llm_client::event::Event> 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()),
}
}
}

View File

@ -9,25 +9,39 @@
//! - [`TextBlockCollector`] - テキストブロックを収集するHandler //! - [`TextBlockCollector`] - テキストブロックを収集するHandler
//! - [`ToolCallCollector`] - ツール呼び出しを収集するHandler //! - [`ToolCallCollector`] - ツール呼び出しを収集するHandler
pub mod event;
mod text_block_collector; mod text_block_collector;
mod timeline; mod timeline;
mod tool_call_collector; mod tool_call_collector;
// 公開API // 公開API
pub use event::*;
pub use text_block_collector::TextBlockCollector; pub use text_block_collector::TextBlockCollector;
pub use timeline::{ErasedHandler, HandlerWrapper, Timeline}; pub use timeline::{ErasedHandler, HandlerWrapper, Timeline};
pub use tool_call_collector::ToolCallCollector; pub use tool_call_collector::ToolCallCollector;
// worker-typesからのre-export // 型定義からのre-export
pub use worker_types::{ pub use crate::handler::{
// Core traits
Handler, Kind,
// Block Kinds
TextBlockKind, ThinkingBlockKind, ToolUseBlockKind,
// Block Events
TextBlockEvent, TextBlockStart, TextBlockStop,
ThinkingBlockEvent, ThinkingBlockStart, ThinkingBlockStop,
ToolUseBlockEvent, ToolUseBlockStart, ToolUseBlockStop,
// Meta Kinds // 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,
}; };

View File

@ -3,8 +3,8 @@
//! TimelineのTextBlockHandler として登録され、 //! TimelineのTextBlockHandler として登録され、
//! ストリーム中のテキストブロックを収集する。 //! ストリーム中のテキストブロックを収集する。
use crate::handler::{Handler, TextBlockEvent, TextBlockKind};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use worker_types::{Handler, TextBlockEvent, TextBlockKind};
/// TextBlockから収集したテキスト情報を保持 /// TextBlockから収集したテキスト情報を保持
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -85,7 +85,7 @@ impl Handler<TextBlockKind> for TextBlockCollector {
mod tests { mod tests {
use super::*; use super::*;
use crate::timeline::Timeline; use crate::timeline::Timeline;
use worker_types::Event; use crate::timeline::event::Event;
/// TextBlockCollectorが単一のテキストブロックを正しく収集することを確認 /// TextBlockCollectorが単一のテキストブロックを正しく収集することを確認
#[test] #[test]

View File

@ -5,7 +5,8 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use worker_types::*; use super::event::*;
use crate::handler::*;
// ============================================================================= // =============================================================================
// Type-erased Handler // Type-erased Handler

View File

@ -3,8 +3,11 @@
//! TimelineのToolUseBlockHandler として登録され、 //! TimelineのToolUseBlockHandler として登録され、
//! ストリーム中のToolUseブロックを収集する。 //! ストリーム中のToolUseブロックを収集する。
use crate::{
handler::{Handler, ToolUseBlockEvent, ToolUseBlockKind},
hook::ToolCall,
};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use worker_types::{Handler, ToolCall, ToolUseBlockEvent, ToolUseBlockKind};
/// ToolUseブロックから収集したツール呼び出し情報を保持 /// ToolUseブロックから収集したツール呼び出し情報を保持
/// ///
@ -98,7 +101,7 @@ impl Handler<ToolUseBlockKind> for ToolCallCollector {
mod tests { mod tests {
use super::*; use super::*;
use crate::timeline::Timeline; use crate::timeline::Timeline;
use worker_types::Event; use crate::timeline::event::Event;
#[test] #[test]
fn test_collect_single_tool_call() { fn test_collect_single_tool_call() {

View File

@ -43,7 +43,7 @@ pub enum ToolError {
/// # 手動実装 /// # 手動実装
/// ///
/// ```ignore /// ```ignore
/// use worker::{Tool, ToolError}; /// use worker::tool::{Tool, ToolError};
/// use serde_json::{json, Value}; /// use serde_json::{json, Value};
/// ///
/// struct MyTool; /// struct MyTool;

View File

@ -5,15 +5,17 @@ use std::sync::{Arc, Mutex};
use futures::StreamExt; use futures::StreamExt;
use tracing::{debug, info, trace, warn}; use tracing::{debug, info, trace, warn};
use crate::timeline::{TextBlockCollector, Timeline, ToolCallCollector}; use crate::{
use crate::llm_client::{ClientError, LlmClient, Request, ToolDefinition}; ContentPart, Message, MessageContent, Role,
use crate::subscriber_adapter::{ hook::{ControlFlow, HookError, ToolCall, ToolResult, TurnResult, WorkerHook},
llm_client::{ClientError, LlmClient, Request, ToolDefinition},
state::{Locked, Mutable, WorkerState},
subscriber::{
ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter, ErrorSubscriberAdapter, StatusSubscriberAdapter, TextBlockSubscriberAdapter,
ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, ToolUseBlockSubscriberAdapter, UsageSubscriberAdapter, WorkerSubscriber,
}; },
use worker_types::{ timeline::{TextBlockCollector, Timeline, ToolCallCollector},
ContentPart, ControlFlow, HookError, Locked, Message, MessageContent, Mutable, Tool, ToolCall, tool::{Tool, ToolError},
ToolError, ToolResult, TurnResult, WorkerHook, WorkerState, WorkerSubscriber,
}; };
// ============================================================================= // =============================================================================
@ -321,7 +323,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
Some(Message { Some(Message {
role: worker_types::Role::Assistant, role: Role::Assistant,
content: MessageContent::Parts(parts), content: MessageContent::Parts(parts),
}) })
} }
@ -337,39 +339,36 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
// メッセージを追加 // メッセージを追加
for msg in &self.history { for msg in &self.history {
// worker-types::Message から llm_client::Message への変換 // Message から llm_client::Message への変換
request = request.message(crate::llm_client::Message { request = request.message(crate::llm_client::Message {
role: match msg.role { role: match msg.role {
worker_types::Role::User => crate::llm_client::Role::User, Role::User => crate::llm_client::Role::User,
worker_types::Role::Assistant => crate::llm_client::Role::Assistant, Role::Assistant => crate::llm_client::Role::Assistant,
}, },
content: match &msg.content { content: match &msg.content {
worker_types::MessageContent::Text(t) => { MessageContent::Text(t) => crate::llm_client::MessageContent::Text(t.clone()),
crate::llm_client::MessageContent::Text(t.clone()) MessageContent::ToolResult {
}
worker_types::MessageContent::ToolResult {
tool_use_id, tool_use_id,
content, content,
} => crate::llm_client::MessageContent::ToolResult { } => crate::llm_client::MessageContent::ToolResult {
tool_use_id: tool_use_id.clone(), tool_use_id: tool_use_id.clone(),
content: content.clone(), content: content.clone(),
}, },
worker_types::MessageContent::Parts(parts) => { MessageContent::Parts(parts) => crate::llm_client::MessageContent::Parts(
crate::llm_client::MessageContent::Parts(
parts parts
.iter() .iter()
.map(|p| match p { .map(|p| match p {
worker_types::ContentPart::Text { text } => { ContentPart::Text { text } => {
crate::llm_client::ContentPart::Text { text: text.clone() } crate::llm_client::ContentPart::Text { text: text.clone() }
} }
worker_types::ContentPart::ToolUse { id, name, input } => { ContentPart::ToolUse { id, name, input } => {
crate::llm_client::ContentPart::ToolUse { crate::llm_client::ContentPart::ToolUse {
id: id.clone(), id: id.clone(),
name: name.clone(), name: name.clone(),
input: input.clone(), input: input.clone(),
} }
} }
worker_types::ContentPart::ToolResult { ContentPart::ToolResult {
tool_use_id, tool_use_id,
content, content,
} => crate::llm_client::ContentPart::ToolResult { } => crate::llm_client::ContentPart::ToolResult {
@ -378,8 +377,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}, },
}) })
.collect(), .collect(),
) ),
}
}, },
}); });
} }
@ -550,7 +548,8 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
} }
} }
let event = event_result?; 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"); debug!(event_count = event_count, "Stream completed");

View File

@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex};
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use worker::llm_client::event::{BlockType, DeltaContent, Event};
use worker::llm_client::{ClientError, LlmClient, Request}; use worker::llm_client::{ClientError, LlmClient, Request};
use worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline}; use worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use worker_types::{BlockType, DeltaContent, Event};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -267,7 +267,8 @@ pub fn assert_timeline_integration(subdir: &str) {
}); });
for event in &events { 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(); let texts = collected.lock().unwrap();

View File

@ -8,10 +8,9 @@ use std::time::{Duration, Instant};
use async_trait::async_trait; use async_trait::async_trait;
use worker::Worker; use worker::Worker;
use worker_types::{ use worker::hook::{ControlFlow, HookError, ToolCall, ToolResult, WorkerHook};
ControlFlow, Event, HookError, ResponseStatus, StatusEvent, Tool, ToolCall, ToolError, use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
ToolResult, WorkerHook, use worker::tool::{Tool, ToolError};
};
mod common; mod common;
use common::MockLlmClient; use common::MockLlmClient;

View File

@ -7,12 +7,12 @@ mod common;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use common::MockLlmClient; use common::MockLlmClient;
use worker::subscriber::WorkerSubscriber;
use worker::Worker; use worker::Worker;
use worker_types::{ use worker::hook::ToolCall;
ErrorEvent, Event, ResponseStatus, StatusEvent, TextBlockEvent, ToolCall, ToolUseBlockEvent, use worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
UsageEvent, use worker::subscriber::WorkerSubscriber;
}; use worker::timeline::event::{ErrorEvent, StatusEvent, UsageEvent};
use worker::timeline::{TextBlockEvent, ToolUseBlockEvent};
// ============================================================================= // =============================================================================
// Test Subscriber // Test Subscriber
@ -101,7 +101,7 @@ async fn test_subscriber_text_block_events() {
Event::text_delta(0, "Hello, "), Event::text_delta(0, "Hello, "),
Event::text_delta(0, "World!"), Event::text_delta(0, "World!"),
Event::text_block_stop(0, None), Event::text_block_stop(0, None),
Event::Status(StatusEvent { Event::Status(ClientStatusEvent {
status: ResponseStatus::Completed, 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#"{"city":"#),
Event::tool_input_delta(0, r#""Tokyo"}"#), Event::tool_input_delta(0, r#""Tokyo"}"#),
Event::tool_use_stop(0), Event::tool_use_stop(0),
Event::Status(StatusEvent { Event::Status(ClientStatusEvent {
status: ResponseStatus::Completed, status: ResponseStatus::Completed,
}), }),
]; ];
@ -172,7 +172,7 @@ async fn test_subscriber_turn_events() {
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Done!"), Event::text_delta(0, "Done!"),
Event::text_block_stop(0, None), Event::text_block_stop(0, None),
Event::Status(StatusEvent { Event::Status(ClientStatusEvent {
status: ResponseStatus::Completed, status: ResponseStatus::Completed,
}), }),
]; ];
@ -210,7 +210,7 @@ async fn test_subscriber_usage_events() {
Event::text_delta(0, "Hello"), Event::text_delta(0, "Hello"),
Event::text_block_stop(0, None), Event::text_block_stop(0, None),
Event::usage(100, 50), Event::usage(100, 50),
Event::Status(StatusEvent { Event::Status(ClientStatusEvent {
status: ResponseStatus::Completed, status: ResponseStatus::Completed,
}), }),
]; ];

View File

@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use schemars; use schemars;
use serde; use serde;
use worker::tool::Tool;
use worker_macros::tool_registry; use worker_macros::tool_registry;
use worker_types::Tool;
// ============================================================================= // =============================================================================
// Test: Basic Tool Generation // Test: Basic Tool Generation

View File

@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait; use async_trait::async_trait;
use common::MockLlmClient; use common::MockLlmClient;
use worker::Worker; use worker::Worker;
use worker_types::{Tool, ToolError}; use worker::tool::{Tool, ToolError};
/// フィクスチャディレクトリのパス /// フィクスチャディレクトリのパス
fn fixtures_dir() -> std::path::PathBuf { fn fixtures_dir() -> std::path::PathBuf {
@ -100,7 +100,7 @@ fn test_mock_client_from_fixture() {
/// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。 /// fixtureファイルを使わず、プログラムでイベントを構築してクライアントを作成する。
#[test] #[test]
fn test_mock_client_from_events() { fn test_mock_client_from_events() {
use worker_types::Event; use worker::llm_client::event::Event;
// 直接イベントを指定 // 直接イベントを指定
let events = vec![ let events = vec![
@ -178,7 +178,7 @@ async fn test_worker_tool_call() {
/// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。 /// テストの独立性を高め、外部ファイルへの依存を排除したい場合に有用。
#[tokio::test] #[tokio::test]
async fn test_worker_with_programmatic_events() { async fn test_worker_with_programmatic_events() {
use worker_types::{Event, ResponseStatus, StatusEvent}; use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
// プログラムでイベントシーケンスを構築 // プログラムでイベントシーケンスを構築
let events = vec![ let events = vec![
@ -205,8 +205,8 @@ async fn test_worker_with_programmatic_events() {
/// id, name, inputJSONを正しく抽出できることを検証する。 /// id, name, inputJSONを正しく抽出できることを検証する。
#[tokio::test] #[tokio::test]
async fn test_tool_call_collector_integration() { async fn test_tool_call_collector_integration() {
use worker::llm_client::event::Event;
use worker::timeline::{Timeline, ToolCallCollector}; use worker::timeline::{Timeline, ToolCallCollector};
use worker_types::Event;
// ToolUseブロックを含むイベントシーケンス // ToolUseブロックを含むイベントシーケンス
let events = vec![ let events = vec![
@ -222,7 +222,8 @@ async fn test_tool_call_collector_integration() {
// イベントをディスパッチ // イベントをディスパッチ
for event in &events { for event in &events {
timeline.dispatch(event); let timeline_event: worker::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
} }
// 収集されたToolCallを確認 // 収集されたToolCallを確認

View File

@ -7,7 +7,8 @@ mod common;
use common::MockLlmClient; use common::MockLlmClient;
use worker::Worker; use worker::Worker;
use worker_types::{Event, Message, MessageContent, ResponseStatus, StatusEvent}; use worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use worker::{Message, MessageContent};
// ============================================================================= // =============================================================================
// Mutable状態のテスト // Mutable状態のテスト