merge: 00001KVZD10ED llm engine rename

This commit is contained in:
Keisuke Hirata 2026-06-25 22:54:25 +09:00
commit 254ecccba2
No known key found for this signature in database
202 changed files with 1129 additions and 1125 deletions

22
Cargo.lock generated
View File

@ -2102,7 +2102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "llm-worker"
name = "llm-engine"
version = "0.2.1"
dependencies = [
"async-trait",
@ -2110,7 +2110,7 @@ dependencies = [
"dotenv",
"eventsource-stream",
"futures",
"llm-worker-macros",
"llm-engine-macros",
"reqwest",
"schemars",
"serde",
@ -2127,7 +2127,7 @@ dependencies = [
]
[[package]]
name = "llm-worker-macros"
name = "llm-engine-macros"
version = "0.2.0"
dependencies = [
"proc-macro2",
@ -2242,7 +2242,7 @@ name = "manifest"
version = "0.1.0"
dependencies = [
"arc-swap",
"llm-worker",
"llm-engine",
"mlua",
"protocol",
"secrets",
@ -2373,7 +2373,7 @@ dependencies = [
"chrono",
"libc",
"lint-common",
"llm-worker",
"llm-engine",
"manifest",
"schemars",
"serde",
@ -2888,7 +2888,7 @@ dependencies = [
"futures-util",
"include_dir",
"libc",
"llm-worker",
"llm-engine",
"manifest",
"mcp",
"memory",
@ -3045,7 +3045,7 @@ dependencies = [
"async-trait",
"base64",
"chrono",
"llm-worker",
"llm-engine",
"manifest",
"reqwest",
"secrets",
@ -3901,7 +3901,7 @@ version = "0.1.0"
dependencies = [
"async-trait",
"futures",
"llm-worker",
"llm-engine",
"protocol",
"serde",
"serde_json",
@ -4352,7 +4352,7 @@ dependencies = [
"async-trait",
"chrono",
"fs4",
"llm-worker",
"llm-engine",
"project-record",
"schemars",
"serde",
@ -4535,7 +4535,7 @@ dependencies = [
"grep-searcher",
"html5ever",
"ignore",
"llm-worker",
"llm-engine",
"manifest",
"markup5ever_rcdom",
"pdf-extract",
@ -4715,7 +4715,7 @@ dependencies = [
"base64",
"client",
"crossterm 0.28.1",
"llm-worker",
"llm-engine",
"manifest",
"minijinja",
"pod-registry",

View File

@ -2,8 +2,8 @@
resolver = "2"
members = [
"crates/client",
"crates/llm-worker",
"crates/llm-worker-macros",
"crates/llm-engine",
"crates/llm-engine-macros",
"crates/session-store",
"crates/secrets",
"crates/manifest",
@ -29,8 +29,8 @@ members = [
]
default-members = [
"crates/client",
"crates/llm-worker",
"crates/llm-worker-macros",
"crates/llm-engine",
"crates/llm-engine-macros",
"crates/session-store",
"crates/secrets",
"crates/manifest",
@ -61,8 +61,8 @@ license = "MIT"
[workspace.dependencies]
# Internal crates
client = { path = "crates/client" }
llm-worker = { path = "crates/llm-worker", version = "0.2" }
llm-worker-macros = { path = "crates/llm-worker-macros", version = "0.2" }
llm-engine = { path = "crates/llm-engine", version = "0.2" }
llm-engine-macros = { path = "crates/llm-engine-macros", version = "0.2" }
manifest = { path = "crates/manifest" }
mcp = { path = "crates/mcp" }
lint-common = { path = "crates/lint-common" }

View File

@ -18,7 +18,7 @@ Does not own:
- product command names (`yoi`)
- Pod state authority (`pod`, `pod-store`, `session-store`)
- UI rendering (`tui`)
- Worker turn semantics (`llm-worker`)
- Engine turn semantics (`llm-engine`)
## Design notes

View File

@ -1,6 +1,6 @@
[package]
name = "llm-worker-macros"
description = "llm-worker's proc macros"
name = "llm-engine-macros"
description = "llm-engine's proc macros"
version = "0.2.0"
edition.workspace = true
license.workspace = true

View File

@ -1,8 +1,8 @@
# llm-worker-macros
# llm-engine-macros
## Role
`llm-worker-macros` provides procedural macros for declaring Rust methods as LLM-callable tools.
`llm-engine-macros` provides procedural macros for declaring Rust methods as LLM-callable tools.
## Boundaries

View File

@ -1,4 +1,4 @@
//! llm-worker-macros - Procedural macros for Tool generation
//! llm-engine-macros - Procedural macros for Tool generation
//!
//! Provides `#[tool_registry]` and `#[tool]` macros to
//! automatically generate `Tool` trait implementations from user-defined methods.
@ -215,7 +215,7 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
quote! {
match result {
Ok(val) => Ok(format!("{:?}", val).into()),
Err(e) => Err(::llm_worker::tool::ToolError::ExecutionFailed(format!("{}", e))),
Err(e) => Err(::llm_engine::tool::ToolError::ExecutionFailed(format!("{}", e))),
}
}
} else {
@ -252,7 +252,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| ::llm_worker::tool::ToolError::InvalidArgument(e.to_string()))?;
.map_err(|e| ::llm_engine::tool::ToolError::InvalidArgument(e.to_string()))?;
let result = #method_call #awaiter;
#result_handling
@ -268,23 +268,23 @@ fn generate_tool_impl(self_ty: &Type, method: &syn::ImplItemFn) -> proc_macro2::
}
#[async_trait::async_trait]
impl ::llm_worker::tool::Tool for #tool_struct_name {
async fn execute(&self, input_json: &str, ctx: ::llm_worker::tool::ToolExecutionContext) -> Result<::llm_worker::tool::ToolOutput, ::llm_worker::tool::ToolError> {
impl ::llm_engine::tool::Tool for #tool_struct_name {
async fn execute(&self, input_json: &str, ctx: ::llm_engine::tool::ToolExecutionContext) -> Result<::llm_engine::tool::ToolOutput, ::llm_engine::tool::ToolError> {
let _ = &ctx;
#execute_body
}
}
impl #self_ty {
/// Get ToolDefinition (for registering with Worker)
pub fn #definition_name(&self) -> ::llm_worker::tool::ToolDefinition {
/// Get ToolDefinition (for registering with Engine)
pub fn #definition_name(&self) -> ::llm_engine::tool::ToolDefinition {
let ctx = self.clone();
::std::sync::Arc::new(move || {
let schema = schemars::schema_for!(#args_struct_name);
let meta = ::llm_worker::tool::ToolMeta::new(#tool_name)
let meta = ::llm_engine::tool::ToolMeta::new(#tool_name)
.description(#description)
.input_schema(serde_json::to_value(schema).unwrap_or(serde_json::json!({})));
let tool: ::std::sync::Arc<dyn ::llm_worker::tool::Tool> =
let tool: ::std::sync::Arc<dyn ::llm_engine::tool::Tool> =
::std::sync::Arc::new(#tool_struct_name { ctx: ctx.clone() });
(meta, tool)
})

View File

@ -1,5 +1,5 @@
[package]
name = "llm-worker"
name = "llm-engine"
description = "A library for building autonomous LLM-powered systems"
version = "0.2.1"
edition.workspace = true
@ -17,7 +17,7 @@ tokio-util = "0.7"
reqwest = { version = "0.13", default-features = false, features = ["stream", "json", "native-tls", "http2"] }
eventsource-stream = "0.2"
zstd = "0.13"
llm-worker-macros = { workspace = true }
llm-engine-macros = { workspace = true }
[dev-dependencies]
clap = { version = "4.5", features = ["derive", "env"] }

View File

@ -1,17 +1,17 @@
# llm-worker
# llm-engine
## Role
`llm-worker` owns provider-independent model turn orchestration over committed history, tools, callbacks, retries, continuation, pruning, and compaction boundaries.
`llm-engine` owns provider-independent model turn orchestration over committed history, tools, callbacks, retries, continuation, pruning, and compaction boundaries.
## Boundaries
Owns:
- Worker history mutation and append contracts
- Engine history mutation and append contracts
- tool-call loop semantics
- pre-stream retry and stream-started continuation policy
- pruning/compaction coordination from the Worker perspective
- pruning/compaction coordination from the Engine perspective
- provider-neutral events/callbacks/interceptors
Does not own:
@ -23,7 +23,7 @@ Does not own:
## Design notes
The Worker is where turn lifecycle belongs because it sees history, in-flight usage, partial output, and tool-call state. It should not receive context-only volatile facts; model-affecting inputs must first be appended to history.
The Engine is where turn lifecycle belongs because it sees history, in-flight usage, partial output, and tool-call state. It should not receive context-only volatile facts; model-affecting inputs must first be appended to history.
## See also

View File

@ -1,12 +1,12 @@
# llm-worker アーキテクチャ
# llm-engine アーキテクチャ
## 概要
llm-workerは3層構成でLLMとのインタラクションを管理する。
llm-engineは3層構成でLLMとのインタラクションを管理する。
```
┌─────────────────────────────────────────┐
Worker (オーケストレーション) │
Engine (オーケストレーション) │
│ ターンループ / フック / ツール実行 │
│ Type-state: Mutable ↔ CacheLocked │
└───────────┬─────────────────────────────┘
@ -27,7 +27,7 @@ llm-workerは3層構成でLLMとのインタラクションを管理する。
| モジュール | 責務 | 要件 |
|---|---|---|
| `worker` | ターンループ、フック統合、ツール実行、Pause/Resume | R1, R4 |
| `engine` | ターンループ、フック統合、ツール実行、Pause/Resume | R1, R4 |
| `state` | Type-state (Mutable/CacheLocked) | R2 |
| `hook` | Hook trait、10フックポイント | R3, R4 |
| `tool` / `tool_server` | ツール定義・登録・実行 | R3 |
@ -42,7 +42,7 @@ llm-workerは3層構成でLLMとのインタラクションを管理する。
### リクエスト(送信)
```
Worker.history (Vec<Item>)
Engine.history (Vec<Item>)
→ build_request() → Request { items, tools, config }
→ Scheme.build_request() → プロバイダ固有JSON
→ Provider.stream() → HTTP POST
@ -55,7 +55,7 @@ HTTP SSE bytes
→ Scheme.parse_event() → Event (統一型)
→ Timeline.dispatch() → Handler.on_event()
→ TextBlockCollector / ToolCallCollector
Worker: 履歴に追加、ツール実行判定
Engine: 履歴に追加、ツール実行判定
```
## 内部型

View File

@ -1,4 +1,4 @@
# llm-worker 要件
# llm-engine 要件
## 前提
@ -12,23 +12,23 @@ c. ツール・フックの基本的なスキーマ自動化を提供する
メッセージの送信と生成のResume、一時停止/再開。
- `Worker::run()` でターンを開始
- `Engine::run()` でターンを開始
- フックから `Pause` を返してターンを一時停止
- `Worker::resume()` でユーザーメッセージを追加せず継続
- `Engine::resume()` でユーザーメッセージを追加せず継続
- AIは中断を認識せず、継続として処理する
**実装**: `worker.rs` — `resume()`, `get_pending_tool_calls()`, `WorkerResult::Paused`
**実装**: `engine.rs` — `resume()`, `get_pending_tool_calls()`, `EngineResult::Paused`
### R2: 暗黙的KVキャッシュ保証
キャッシュを破壊しうる操作を明示的にブロックせずとも、いつの間にかキャッシュ破壊してた状態にはしたくない。
- Type-stateパターン`Mutable` / `CacheLocked`)でコンパイル時に保証
- `Worker::lock()` でCacheLocked状態に遷移
- `Engine::lock()` でCacheLocked状態に遷移
- CacheLocked状態ではシステムプロンプトや履歴の変更APIが型レベルで利用不可
- `locked_prefix_len` でプレフィックスの不変性を追跡
**実装**: `state.rs` (sealed trait), `worker.rs` (state-specific impl blocks)
**実装**: `state.rs` (sealed trait), `engine.rs` (state-specific impl blocks)
### R3: ツール・フックスキーマ自動化
@ -36,13 +36,13 @@ c. ツール・フックの基本的なスキーマ自動化を提供する
- `#[tool_registry]` マクロでツールサーバーを自動構成
- `Hook` traitで10種のフックポイント
**実装**: `llm-worker-macros/`, `tool.rs`, `tool_server.rs`, `hook.rs`
**実装**: `llm-engine-macros/`, `tool.rs`, `tool_server.rs`, `hook.rs`
### R4: フックは上層の関心事
フックはLLMクライアント層ではなく、Worker(オーケストレーション)層に配置する。
フックはLLMクライアント層ではなく、Engine(オーケストレーション)層に配置する。
- LLMクライアント (`llm_client/`) はストリーミングとプロトコルのみ
- Worker層でフック実行、ツール統合、Pause/Resume制御
- Engine層でフック実行、ツール統合、Pause/Resume制御
**実装**: `worker.rs` (hook integration), `hook.rs` (trait definitions)
**実装**: `engine.rs` (hook integration), `hook.rs` (trait definitions)

View File

@ -1,10 +1,10 @@
//! Worker cancellation demo
//! Engine cancellation demo
//!
//! Example of cancelling from another thread during streaming
use llm_worker::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_worker::{Worker, WorkerResult};
use llm_engine::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::{Engine, EngineResult};
use std::time::Duration;
#[tokio::main]
@ -28,29 +28,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cap = scheme.default_capability();
let base_url = scheme.default_base_url().to_string();
let client = HttpTransport::new(scheme, model, base_url, ResolvedAuth::ApiKey(api_key), cap);
let worker = Worker::new(client);
let engine = Engine::new(client);
println!("🚀 Starting Worker...");
println!("🚀 Starting Engine...");
println!("💡 Will cancel after 2 seconds\n");
// Get cancel sender before run (Mutable state)
let cancel_tx = worker.cancel_sender();
let cancel_tx = engine.cancel_sender();
// Task: Cancel after 2 seconds
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await;
println!("\n🛑 Cancelling worker...");
println!("\n🛑 Cancelling engine...");
let _ = cancel_tx.send(()).await;
});
println!("📡 Sending request to LLM...");
match worker.run("Tell me a very long story about a brave knight. Make it as detailed as possible with many paragraphs.").await {
match engine.run("Tell me a very long story about a brave knight. Make it as detailed as possible with many paragraphs.").await {
Ok(out) => match out.result {
WorkerResult::Finished => println!("✅ Task completed normally"),
WorkerResult::Paused => println!("⏸️ Task paused"),
WorkerResult::LimitReached => println!("🔒 Turn limit reached"),
WorkerResult::Yielded => println!("↩️ Task yielded"),
EngineResult::Finished => println!("✅ Task completed normally"),
EngineResult::Paused => println!("⏸️ Task paused"),
EngineResult::LimitReached => println!("🔒 Turn limit reached"),
EngineResult::Yielded => println!("↩️ Task yielded"),
},
Err(e) => {
println!("❌ Task error: {}", e);

View File

@ -1,4 +1,4 @@
//! Interactive CLI client using Worker
//! Interactive CLI client using Engine
//!
//! A CLI application for interacting with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama).
//! Demonstrates tool registration and execution, and streaming response display.
@ -12,22 +12,22 @@
//! echo "OPENAI_API_KEY=your-api-key" >> .env
//!
//! # Anthropic (default)
//! cargo run --example worker_cli
//! cargo run --example engine_cli
//!
//! # Gemini
//! cargo run --example worker_cli -- --provider gemini
//! cargo run --example engine_cli -- --provider gemini
//!
//! # OpenAI
//! cargo run --example worker_cli -- --provider openai --model gpt-4o
//! cargo run --example engine_cli -- --provider openai --model gpt-4o
//!
//! # Ollama (local)
//! cargo run --example worker_cli -- --provider ollama --model llama3.2
//! cargo run --example engine_cli -- --provider ollama --model llama3.2
//!
//! # With options
//! cargo run --example worker_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
//! cargo run --example engine_cli -- --provider anthropic --model claude-3-haiku-20240307 --system "You are a helpful assistant."
//!
//! # Show help
//! cargo run --example worker_cli -- --help
//! cargo run --example engine_cli -- --help
//! ```
use std::collections::HashMap;
@ -39,8 +39,8 @@ use tracing::info;
use tracing_subscriber::EnvFilter;
use clap::{Parser, ValueEnum};
use llm_worker::{
Worker,
use llm_engine::{
Engine,
interceptor::{Interceptor, PostToolAction, ToolResultInfo},
llm_client::{
LlmClient,
@ -52,7 +52,7 @@ use llm_worker::{
},
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
};
use llm_worker_macros::tool_registry;
use llm_engine_macros::tool_registry;
// Required imports for macro expansion
use schemars;
@ -114,8 +114,8 @@ impl Provider {
/// Interactive CLI client supporting multiple LLM providers
#[derive(Parser, Debug)]
#[command(name = "worker-cli")]
#[command(about = "Interactive CLI client for multiple LLM providers using Worker")]
#[command(name = "engine-cli")]
#[command(about = "Interactive CLI client for multiple LLM providers using Engine")]
#[command(version)]
struct Args {
/// Provider to use
@ -393,7 +393,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv::dotenv().ok();
// Initialize logging
// Use RUST_LOG=debug cargo run --example worker_cli ... for detailed logs
// Use RUST_LOG=debug cargo run --example engine_cli ... for detailed logs
// Default is warn level, can be overridden with RUST_LOG environment variable
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
@ -408,7 +408,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!(
provider = ?args.provider,
model = ?args.model,
"Starting worker CLI"
"Starting engine CLI"
);
// Interactive mode or one-shot mode
@ -421,7 +421,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.unwrap_or_else(|| args.provider.default_model().to_string());
if is_interactive {
let title = format!("Worker CLI - {}", args.provider.display_name());
let title = format!("Engine CLI - {}", args.provider.display_name());
let border_len = title.len() + 6;
println!("{}", "".repeat(border_len));
println!("{}", title);
@ -453,34 +453,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
};
// Create Worker
let mut worker = Worker::new(client);
// Create Engine
let mut engine = Engine::new(client);
let tool_call_names = Arc::new(Mutex::new(HashMap::new()));
// Set system prompt
if let Some(ref system_prompt) = args.system {
worker.set_system_prompt(system_prompt);
engine.set_system_prompt(system_prompt);
}
// Register tools (unless --no-tools)
if !args.no_tools {
let app = AppContext;
worker.register_tool(app.get_current_time_definition());
worker.register_tool(app.calculate_definition());
engine.register_tool(app.get_current_time_definition());
engine.register_tool(app.calculate_definition());
}
// Register streaming display handlers
worker
engine
.timeline_mut()
.on_text_block(StreamingPrinter::new())
.on_tool_use_block(ToolCallPrinter::new(tool_call_names.clone()));
worker.set_interceptor(ToolResultPrinterPolicy::new(tool_call_names));
engine.set_interceptor(ToolResultPrinterPolicy::new(tool_call_names));
// One-shot mode
if let Some(prompt) = args.prompt {
match worker.run(&prompt).await {
match engine.run(&prompt).await {
Ok(_) => {}
Err(e) => {
eprintln!("\n❌ Error: {}", e);
@ -504,8 +504,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(());
}
let mut locked = match worker.run(first_input).await {
Ok(out) => out.worker,
let mut locked = match engine.run(first_input).await {
Ok(out) => out.engine,
Err(e) => {
eprintln!("\n❌ Error: {}", e);
return Ok(());

View File

@ -20,10 +20,10 @@ mod recorder;
mod scenarios;
use clap::{Parser, ValueEnum};
use llm_worker::llm_client::scheme::{
use llm_engine::llm_client::scheme::{
Scheme, anthropic::AnthropicScheme, gemini::GeminiScheme, openai_chat::OpenAIScheme,
};
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
fn make_transport<S: Scheme>(scheme: S, model: &str, auth: ResolvedAuth) -> HttpTransport<S> {
let cap = scheme.default_capability();
@ -225,7 +225,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
println!("\n✅ Done!");
println!("Run tests with: cargo test -p worker");
println!("Run tests with: cargo test -p engine");
Ok(())
}

View File

@ -8,7 +8,7 @@ use std::path::Path;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use futures::StreamExt;
use llm_worker::llm_client::{LlmClient, Request};
use llm_engine::llm_client::{LlmClient, Request};
/// Recorded event
#[derive(Debug, serde::Serialize, serde::Deserialize)]
@ -79,7 +79,7 @@ pub async fn record_request<C: LlmClient>(
}
// Save
let fixtures_dir = Path::new("worker/tests/fixtures").join(subdir);
let fixtures_dir = Path::new("engine/tests/fixtures").join(subdir);
fs::create_dir_all(&fixtures_dir)?;
let filepath = fixtures_dir.join(format!("{}.jsonl", output_name));

View File

@ -2,7 +2,7 @@
//!
//! Defines requests and output file names for each scenario
use llm_worker::llm_client::{Request, ToolDefinition};
use llm_engine::llm_client::{Request, ToolDefinition};
/// Test scenario
pub struct TestScenario {

View File

@ -1,7 +1,7 @@
//! Closure-based event callback API
//!
//! Provides a closure-based alternative to implementing `Handler<K>` directly.
//! Register callbacks on `Worker` via `on_text_block()`, `on_tool_use_block()`,
//! Register callbacks on `Engine` via `on_text_block()`, `on_tool_use_block()`,
//! `on_usage()`, etc.
use std::marker::PhantomData;
@ -18,13 +18,13 @@ use crate::tool::ToolCall;
/// Callback scope for a text block.
///
/// Passed to the setup closure registered with `Worker::on_text_block()`.
/// Passed to the setup closure registered with `Engine::on_text_block()`.
/// Register per-block callbacks via `on_delta()` and `on_stop()`.
///
/// # Examples
///
/// ```ignore
/// worker.on_text_block(|block| {
/// engine.on_text_block(|block| {
/// block.on_delta(|text| print!("{}", text));
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
/// });
@ -176,13 +176,13 @@ impl Handler<ThinkingBlockKind> for ClosureThinkingBlockHandler {
/// Callback scope for a tool use block.
///
/// Passed to the setup closure registered with `Worker::on_tool_use_block()`.
/// Passed to the setup closure registered with `Engine::on_tool_use_block()`.
/// The setup closure also receives `&ToolUseBlockStart` with `id` and `name`.
///
/// # Examples
///
/// ```ignore
/// worker.on_tool_use_block(|start, block| {
/// engine.on_tool_use_block(|start, block| {
/// println!("Tool: {} ({})", start.name, start.id);
/// block.on_delta(|json| { /* streaming JSON fragment */ });
/// block.on_stop(|call| println!("Done: {}", call.name));

View File

@ -22,19 +22,19 @@ use crate::{
ToolDefinition, error::is_retryable, event::Event, retry::RetryPolicy,
transport::DEFAULT_FIRST_STREAM_EVENT_TIMEOUT, types::parse_tool_arguments,
},
state::{Locked, Mutable, WorkerState},
state::{EngineState, Locked, Mutable},
timeline::event::{ErrorEvent, StatusEvent, UsageEvent},
timeline::{TextBlockCollector, ThinkingBlockCollector, Timeline, ToolCallCollector},
tool::{
ToolCall, ToolDefinition as WorkerToolDefinition, ToolError, ToolExecutionContext,
ToolCall, ToolDefinition as EngineToolDefinition, ToolError, ToolExecutionContext,
ToolOutputLimits, ToolResult, truncate_content,
},
tool_server::{ToolServer, ToolServerHandle},
};
/// Worker errors
/// Engine errors
#[derive(Debug, thiserror::Error)]
pub enum WorkerError {
pub enum EngineError {
/// Client error
#[error("Client error: {0}")]
Client(#[from] ClientError),
@ -60,17 +60,17 @@ pub enum ToolRegistryError {
DuplicateName(String),
}
/// Worker configuration
/// Engine configuration
#[derive(Debug, Clone, Default)]
pub struct WorkerConfig {
pub struct EngineConfig {
// Reserved for future extensions (currently empty)
_private: (),
}
/// Worker execution result (status)
/// Engine execution result (status)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkerResult {
pub enum EngineResult {
/// Completed (waiting for user input)
Finished,
/// Paused (can be resumed)
@ -85,14 +85,14 @@ pub enum WorkerResult {
Yielded,
}
/// Result of [`Worker<C, Mutable>::run()`] / [`Worker<C, Mutable>::resume()`].
/// Result of [`Engine<C, Mutable>::run()`] / [`Engine<C, Mutable>::resume()`].
///
/// Contains the `Locked` Worker (ready for subsequent runs) and the outcome.
pub struct RunOutput<C: LlmClient> {
/// The Worker, now in Locked state.
pub worker: Worker<C, Locked>,
/// Contains the `Locked` Engine (ready for subsequent runs) and the outcome.
pub struct EngineRunOutput<C: LlmClient> {
/// The Engine, now in Locked state.
pub engine: Engine<C, Locked>,
/// Outcome of the turn.
pub result: WorkerResult,
pub result: EngineResult,
}
/// Internal: tool execution result
@ -113,27 +113,27 @@ const MAX_STREAM_CONTINUATIONS: u32 = 3;
/// - [`Mutable`]: Initial state. System prompt, history, and tools can be freely edited.
/// - [`Locked`]: Cache-protected state. Prefix context is immutable; only `run()` / `resume()` are available.
///
/// Calling `run()` on a `Mutable` Worker consumes it and returns a
/// `Locked` Worker together with the result. This ensures the
/// Calling `run()` on a `Mutable` Engine consumes it and returns a
/// `Locked` Engine together with the result. This ensures the
/// cache prefix is fixed for optimal KV cache hit rate.
///
/// ```ignore
/// let mut worker = Worker::new(client)
/// let mut engine = Engine::new(client)
/// .system_prompt("You are a helpful assistant.");
/// worker.register_tool(my_tool);
/// engine.register_tool(my_tool);
///
/// // Mutable::run() consumes self → RunOutput { worker: Locked, result }
/// let out = worker.run("Hello").await?;
/// let mut worker = out.worker;
/// // Mutable::run() consumes self → EngineRunOutput { engine: Locked, result }
/// let out = engine.run("Hello").await?;
/// let mut engine = out.engine;
///
/// // Locked::run() borrows &mut self
/// worker.run("Follow-up").await?;
/// engine.run("Follow-up").await?;
///
/// // To edit between turns, unlock back to Mutable
/// let mut worker = worker.unlock();
/// worker.truncate_history(5);
/// let out = worker.run("Continue").await?;
/// let mut worker = out.worker;
/// let mut engine = engine.unlock();
/// engine.truncate_history(5);
/// let out = engine.run("Continue").await?;
/// let mut engine = out.engine;
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LlmRetryNotice {
@ -152,7 +152,7 @@ enum StreamCompletion {
Interrupted { reason: String },
}
pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
pub struct Engine<C: LlmClient, S: EngineState = Mutable> {
/// LLM client
client: C,
/// Retry policy for opening an LLM response stream.
@ -172,22 +172,22 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
interceptor: Box<dyn Interceptor>,
/// System prompt
system_prompt: Option<String>,
/// Item history (owned by Worker)
/// Item history (owned by Engine)
history: Vec<Item>,
/// History length at lock time (only meaningful in Locked state)
locked_prefix_len: usize,
/// AgentTurn count.
///
/// Once retry (`llm-worker-stream-continuation`) is implemented, an
/// Once retry (`llm-engine-stream-continuation`) is implemented, an
/// AgentTurn collapses N retried `LlmCall`s with identical input;
/// today retry is not implemented so AgentTurn and LlmCall fire 1:1
/// and the increment site (the LLM-call loop) is shared.
/// `max_turns` is interpreted as a per-`run()` AgentTurn cap.
turn_count: usize,
/// LlmCall count (per-Worker running counter, monotonic). Unlike
/// LlmCall count (per-Engine running counter, monotonic). Unlike
/// `turn_count` this never collapses retries.
llm_call_count: usize,
/// Tool execution batch count (per-Worker running counter, monotonic).
/// Tool execution batch count (per-Engine running counter, monotonic).
/// Each batch corresponds to one collected assistant tool-call set or one
/// resumed pending tool-call set.
tool_execution_batch_count: usize,
@ -212,7 +212,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
/// Pre-stream lifecycle callbacks for debugging stalls before provider
/// stream events become visible.
lifecycle_trace_cbs: Vec<Arc<dyn Fn(usize, usize, &str, &Value) + Send + Sync>>,
/// Non-fatal warning callbacks. Invoked when the Worker wants to
/// Non-fatal warning callbacks. Invoked when the Engine wants to
/// surface an advisory message to the upper layer (e.g. Pod) so it
/// can be forwarded to the user — distinct from `tracing::warn!`,
/// which is for developer-facing logs.
@ -223,7 +223,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
/// enters history.
tool_result_cbs: Vec<Box<dyn Fn(&ToolResult) + Send + Sync>>,
/// History-append callbacks. Invoked for non-streamed items when they
/// are appended to persistent worker history, so upper layers can
/// are appended to persistent engine history, so upper layers can
/// broadcast those items using history itself as the source of truth.
history_append_cbs: Vec<Box<dyn Fn(&Item) + Send + Sync>>,
/// Request configuration (max_tokens, temperature, etc.)
@ -260,7 +260,7 @@ pub struct Worker<C: LlmClient, S: WorkerState = Mutable> {
_state: PhantomData<S>,
}
impl<C: LlmClient, S: WorkerState> Worker<C, S> {
impl<C: LlmClient, S: EngineState> Engine<C, S> {
fn reset_interruption_state(&mut self) {
self.last_run_interrupted = false;
}
@ -269,7 +269,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
while self.cancel_rx.try_recv().is_ok() {}
}
/// Discard pending cancellation notifications while the worker is idle.
/// Discard pending cancellation notifications while the engine is idle.
///
/// Cancellation is a running-turn control signal. Callers that own a higher
/// level run state can use this before starting a new turn so an old idle
@ -296,7 +296,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.on_text_block(|block| {
/// engine.on_text_block(|block| {
/// block.on_delta(|text| print!("{}", text));
/// block.on_stop(|full_text| println!("\n--- {} chars ---", full_text.len()));
/// });
@ -335,7 +335,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.on_tool_use_block(|start, block| {
/// engine.on_tool_use_block(|start, block| {
/// println!("Tool: {} ({})", start.name, start.id);
/// block.on_delta(|json| { /* streaming JSON fragment */ });
/// block.on_stop(|call| println!("Done: {}", call.name));
@ -468,7 +468,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// Register a non-fatal warning callback.
///
/// The callback is invoked with a short human-readable message
/// whenever the Worker encounters a condition that should be
/// whenever the Engine encounters a condition that should be
/// surfaced to a human (e.g. tool output byte-cap truncation).
/// This channel is separate from `tracing::warn!`, which remains
/// in place for developer logs.
@ -498,7 +498,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
}
/// Register a callback invoked for items appended directly to worker
/// Register a callback invoked for items appended directly to engine
/// history outside streaming timeline callbacks.
pub fn on_history_append(&mut self, callback: impl Fn(&Item) + Send + Sync + 'static) {
self.history_append_cbs.push(Box::new(callback));
@ -639,7 +639,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.turn_count
}
/// Get the current LlmCall count (per-Worker running counter, never
/// Get the current LlmCall count (per-Engine running counter, never
/// collapsed by retry).
pub fn llm_call_count(&self) -> usize {
self.llm_call_count
@ -657,7 +657,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.set_max_tokens(4096);
/// engine.set_max_tokens(4096);
/// ```
pub fn set_max_tokens(&mut self, max_tokens: u32) {
self.request_config.max_tokens = Some(max_tokens);
@ -671,7 +671,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.set_temperature(0.7);
/// engine.set_temperature(0.7);
/// ```
pub fn set_temperature(&mut self, temperature: f32) {
self.request_config.temperature = Some(temperature);
@ -682,7 +682,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.set_top_p(0.9);
/// engine.set_top_p(0.9);
/// ```
pub fn set_top_p(&mut self, top_p: f32) {
self.request_config.top_p = Some(top_p);
@ -695,7 +695,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.set_top_k(40);
/// engine.set_top_k(40);
/// ```
pub fn set_top_k(&mut self, top_k: u32) {
self.request_config.top_k = Some(top_k);
@ -706,7 +706,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// # Examples
///
/// ```ignore
/// worker.add_stop_sequence("\n\n");
/// engine.add_stop_sequence("\n\n");
/// ```
pub fn add_stop_sequence(&mut self, sequence: impl Into<String>) {
self.request_config.stop_sequences.push(sequence.into());
@ -730,23 +730,23 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
/// Cancel execution
///
/// Interrupts currently running streaming or tool execution.
/// WorkerError::Cancelled is returned at the next event loop checkpoint.
/// EngineError::Cancelled is returned at the next event loop checkpoint.
///
/// # Examples
///
/// ```ignore
/// use std::sync::Arc;
/// let worker = Arc::new(Mutex::new(Worker::new(client)));
/// let engine = Arc::new(Mutex::new(Engine::new(client)));
///
/// // Run in another thread
/// let worker_clone = worker.clone();
/// let worker_clone = engine.clone();
/// tokio::spawn(async move {
/// let mut w = worker_clone.lock().unwrap();
/// w.run("Long task...").await
/// });
///
/// // Cancel
/// worker.lock().unwrap().cancel();
/// engine.lock().unwrap().cancel();
/// ```
pub fn cancel(&self) {
let _ = self.cancel_tx.try_send(());
@ -849,15 +849,15 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
///
async fn finalize_interruption<T>(
&mut self,
result: Result<T, WorkerError>,
) -> Result<T, WorkerError> {
result: Result<T, EngineError>,
) -> Result<T, EngineError> {
match result {
Ok(value) => Ok(value),
Err(err) => {
self.last_run_interrupted = true;
let reason = match &err {
WorkerError::Aborted(reason) => reason.clone(),
WorkerError::Cancelled => "Cancelled".to_string(),
EngineError::Aborted(reason) => reason.clone(),
EngineError::Cancelled => "Cancelled".to_string(),
_ => err.to_string(),
};
self.interceptor.on_abort(&reason).await;
@ -913,7 +913,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
async fn execute_tools(
&mut self,
tool_calls: Vec<ToolCall>,
) -> Result<ToolExecutionResult, WorkerError> {
) -> Result<ToolExecutionResult, EngineError> {
use futures::future::join_all;
// Map from tool call ID to (ToolCall, Meta, Tool, Context)
@ -953,7 +953,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
PreToolAction::Abort(reason) => {
self.last_run_interrupted = true;
return Err(WorkerError::Aborted(reason));
return Err(EngineError::Aborted(reason));
}
PreToolAction::Pause => {
self.last_run_interrupted = true;
@ -1010,7 +1010,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
self.timeline.abort_current_block();
self.last_run_interrupted = true;
return Err(WorkerError::Cancelled);
return Err(EngineError::Cancelled);
}
};
results.extend(synthetic_results);
@ -1032,7 +1032,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
PostToolAction::Continue => {}
PostToolAction::Abort(reason) => {
self.last_run_interrupted = true;
return Err(WorkerError::Aborted(reason));
return Err(EngineError::Aborted(reason));
}
}
// Reflect interceptor-modified results
@ -1084,14 +1084,14 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
/// Internal turn execution logic
async fn run_turn_loop(&mut self) -> Result<WorkerResult, WorkerError> {
async fn run_turn_loop(&mut self) -> Result<EngineResult, EngineError> {
self.reset_interruption_state();
let tool_definitions = self.build_tool_definitions();
info!(
item_count = self.history.len(),
tool_count = tool_definitions.len(),
"Starting worker run"
"Starting engine run"
);
// Resume pending tool calls from a previous Pause
@ -1109,7 +1109,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
info!("Execution cancelled");
self.timeline.abort_current_block();
self.last_run_interrupted = true;
return Err(WorkerError::Cancelled);
return Err(EngineError::Cancelled);
}
let current_turn = self.turn_count;
@ -1138,7 +1138,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
// Prune projection: if both the config and the savings
// estimator are configured, drop ToolResult.content from
// prunable candidates whose estimated savings meet the
// threshold. Worker does not own usage history itself; the
// threshold. Engine does not own usage history itself; the
// estimator is injected by the layer that does.
if let (Some(config), Some(token_estimator), Some(savings_estimator)) = (
&self.prune_config,
@ -1199,7 +1199,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
cb(current_turn);
}
self.last_run_interrupted = true;
return Err(WorkerError::Aborted(reason));
return Err(EngineError::Aborted(reason));
}
PreRequestAction::YieldWith(items) => {
self.append_history_items(items.clone());
@ -1209,7 +1209,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
cb(current_turn);
}
self.last_run_interrupted = true;
return Ok(WorkerResult::Yielded);
return Ok(EngineResult::Yielded);
}
PreRequestAction::Yield => {
info!("Yielded by interceptor");
@ -1217,7 +1217,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
cb(current_turn);
}
self.last_run_interrupted = true;
return Ok(WorkerResult::Yielded);
return Ok(EngineResult::Yielded);
}
PreRequestAction::ContinueWith(items) => {
self.append_history_items(items.clone());
@ -1227,7 +1227,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
// LlmCall boundary fires per LLM generation request — today
// 1:1 with AgentTurn, but retry (`llm-worker-stream-continuation`)
// 1:1 with AgentTurn, but retry (`llm-engine-stream-continuation`)
// will multiply this within a single AgentTurn.
let current_llm_call = self.llm_call_count;
for cb in &self.llm_call_start_cbs {
@ -1262,7 +1262,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
stream_continuations += 1;
if stream_continuations > MAX_STREAM_CONTINUATIONS {
self.last_run_interrupted = true;
return Err(WorkerError::Client(ClientError::Api {
return Err(EngineError::Client(ClientError::Api {
status: None,
code: None,
message: format!("LLM stream interrupted too many times: {reason}"),
@ -1315,7 +1315,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
match self.interceptor.on_turn_end(&self.history).await {
TurnEndAction::Finish => {
self.last_run_interrupted = false;
return Ok(WorkerResult::Finished);
return Ok(EngineResult::Finished);
}
TurnEndAction::ContinueWithMessages(additional) => {
self.append_history_items(additional);
@ -1323,7 +1323,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
TurnEndAction::Pause => {
self.last_run_interrupted = true;
return Ok(WorkerResult::Paused);
return Ok(EngineResult::Paused);
}
}
}
@ -1340,7 +1340,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
"Turn limit reached"
);
self.last_run_interrupted = false;
return Ok(WorkerResult::LimitReached);
return Ok(EngineResult::LimitReached);
}
}
}
@ -1351,7 +1351,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
request: Request,
turn: usize,
llm_call: usize,
) -> Result<ResponseStream, WorkerError> {
) -> Result<ResponseStream, EngineError> {
let policy = self.retry_policy.clone();
let started = Instant::now();
let mut failed_attempt: u32 = 0;
@ -1385,7 +1385,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
);
self.timeline.abort_current_block();
self.last_run_interrupted = true;
return Err(WorkerError::Cancelled);
return Err(EngineError::Cancelled);
}
};
@ -1417,7 +1417,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
);
self.timeline.abort_current_block();
self.last_run_interrupted = true;
return Err(WorkerError::Cancelled);
return Err(EngineError::Cancelled);
}
};
match first_event_result {
@ -1459,7 +1459,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
let next_failed_attempt = failed_attempt + 1;
if next_failed_attempt >= policy.max_attempts || !is_retryable(&err) {
self.last_run_interrupted = true;
return Err(WorkerError::Client(err));
return Err(EngineError::Client(err));
}
let wait = err
@ -1468,7 +1468,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
let elapsed = started.elapsed();
if elapsed + wait > policy.total_timeout {
self.last_run_interrupted = true;
return Err(WorkerError::Client(err));
return Err(EngineError::Client(err));
}
warn!(
@ -1497,7 +1497,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
self.timeline.abort_current_block();
self.last_run_interrupted = true;
return Err(WorkerError::Cancelled);
return Err(EngineError::Cancelled);
}
}
@ -1511,7 +1511,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
request: Request,
turn: usize,
llm_call: usize,
) -> Result<StreamCompletion, WorkerError> {
) -> Result<StreamCompletion, EngineError> {
debug!(
item_count = request.items.len(),
tool_count = request.tools.len(),
@ -1561,7 +1561,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.timeline.abort_current_block();
self.timeline.flush_usage();
self.last_run_interrupted = true;
return Err(WorkerError::Client(ClientError::Api {
return Err(EngineError::Client(ClientError::Api {
status: None,
code: err.code.clone(),
message: err.message.clone(),
@ -1579,7 +1579,7 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
self.timeline.abort_current_block();
self.timeline.flush_usage();
self.last_run_interrupted = true;
return Err(WorkerError::Cancelled);
return Err(EngineError::Cancelled);
}
}
}
@ -1595,11 +1595,11 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
async fn execute_and_commit_tools(
&mut self,
tool_calls: Vec<ToolCall>,
) -> Result<Option<WorkerResult>, WorkerError> {
) -> Result<Option<EngineResult>, EngineError> {
match self.execute_tools(tool_calls).await {
Ok(ToolExecutionResult::Paused) => {
self.last_run_interrupted = true;
Ok(Some(WorkerResult::Paused))
Ok(Some(EngineResult::Paused))
}
Ok(ToolExecutionResult::Completed(results)) => {
// Route per-result pushes through the callback path so
@ -1624,8 +1624,8 @@ impl<C: LlmClient, S: WorkerState> Worker<C, S> {
}
}
impl<C: LlmClient> Worker<C, Mutable> {
/// Create a new Worker (in Mutable state)
impl<C: LlmClient> Engine<C, Mutable> {
/// Create a new Engine (in Mutable state)
pub fn new(client: C) -> Self {
let text_block_collector = TextBlockCollector::new();
let tool_call_collector = ToolCallCollector::new();
@ -1684,13 +1684,13 @@ impl<C: LlmClient> Worker<C, Mutable> {
///
/// The factory is queued and executed at the next `run()` or `resume()` call.
/// Duplicate name detection occurs at that point and surfaces as
/// [`WorkerError::ToolRegistry`].
pub fn register_tool(&mut self, factory: WorkerToolDefinition) {
/// [`EngineError::ToolRegistry`].
pub fn register_tool(&mut self, factory: EngineToolDefinition) {
self.tool_server.register_tool(factory);
}
/// Register multiple tool factories for deferred initialization.
pub fn register_tools(&mut self, factories: impl IntoIterator<Item = WorkerToolDefinition>) {
pub fn register_tools(&mut self, factories: impl IntoIterator<Item = EngineToolDefinition>) {
self.tool_server.register_tools(factories);
}
@ -1719,7 +1719,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// # Examples
///
/// ```ignore
/// let worker = Worker::new(client)
/// let engine = Engine::new(client)
/// .system_prompt("You are a helpful assistant.")
/// .max_tokens(4096);
/// ```
@ -1733,7 +1733,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// # Examples
///
/// ```ignore
/// let worker = Worker::new(client)
/// let engine = Engine::new(client)
/// .temperature(0.7);
/// ```
pub fn temperature(mut self, temperature: f32) -> Self {
@ -1768,7 +1768,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// .with_max_tokens(4096)
/// .with_temperature(0.7);
///
/// let worker = Worker::new(client)
/// let engine = Engine::new(client)
/// .system_prompt("...")
/// .with_config(config);
/// ```
@ -1791,7 +1791,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// # Examples
///
/// ```ignore
/// let worker = Worker::new(client)
/// let engine = Engine::new(client)
/// .temperature(0.7)
/// .top_k(40)
/// .validate()?; // Error if using OpenAI since top_k is not supported
@ -1799,13 +1799,13 @@ impl<C: LlmClient> Worker<C, Mutable> {
///
/// # Returns
/// * `Ok(Self)` - Validation successful
/// * `Err(WorkerError::ConfigWarnings)` - Has unsupported settings
pub fn validate(self) -> Result<Self, WorkerError> {
/// * `Err(EngineError::ConfigWarnings)` - Has unsupported settings
pub fn validate(self) -> Result<Self, EngineError> {
let warnings = self.client.validate_config(&self.request_config);
if warnings.is_empty() {
Ok(self)
} else {
Err(WorkerError::ConfigWarnings(warnings))
Err(EngineError::ConfigWarnings(warnings))
}
}
@ -1820,7 +1820,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// Append items to history and notify history-append observers for each
/// item before it lands. This is the only public Mutable-state API for
/// growing worker history; callers that need session-log persistence must
/// growing engine history; callers that need session-log persistence must
/// install [`on_history_append`](Self::on_history_append) before calling it.
pub fn append_history(&mut self, items: impl IntoIterator<Item = Item>) {
self.append_history_items(items);
@ -1855,7 +1855,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// Apply configuration (reserved for future extensions)
#[allow(dead_code)]
pub fn config(self, _config: WorkerConfig) -> Self {
pub fn config(self, _config: EngineConfig) -> Self {
self
}
@ -1864,13 +1864,16 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// This is the primary entry point for first use. Equivalent to
/// `self.lock()` followed by `locked.run(user_input)`.
///
/// Subsequent runs can use [`Worker<C, Locked>::run()`] directly.
/// To edit state between turns, call [`unlock()`](Worker::unlock) first.
pub async fn run(self, user_input: impl Into<String>) -> Result<RunOutput<C>, WorkerError> {
/// Subsequent runs can use [`Engine<C, Locked>::run()`] directly.
/// To edit state between turns, call [`unlock()`](Engine::unlock) first.
pub async fn run(
self,
user_input: impl Into<String>,
) -> Result<EngineRunOutput<C>, EngineError> {
let mut locked = self.lock();
let result = locked.run(user_input).await?;
Ok(RunOutput {
worker: locked,
Ok(EngineRunOutput {
engine: locked,
result,
})
}
@ -1878,11 +1881,11 @@ impl<C: LlmClient> Worker<C, Mutable> {
/// Resume from Paused, consuming self and transitioning to Locked.
///
/// Used after `unlock()` → edit → resume.
pub async fn resume(self) -> Result<RunOutput<C>, WorkerError> {
pub async fn resume(self) -> Result<EngineRunOutput<C>, EngineError> {
let mut locked = self.lock();
let result = locked.resume().await?;
Ok(RunOutput {
worker: locked,
Ok(EngineRunOutput {
engine: locked,
result,
})
}
@ -1895,15 +1898,15 @@ impl<C: LlmClient> Worker<C, Mutable> {
///
/// Most callers should use [`run()`](Self::run) instead, which calls
/// this internally. Use `lock()` directly only when you need the
/// `Locked` worker back on error (e.g. in a persistence layer).
/// `Locked` engine back on error (e.g. in a persistence layer).
///
/// # Panics
///
/// Panics if a pending tool factory produces a duplicate name.
pub fn lock(self) -> Worker<C, Locked> {
pub fn lock(self) -> Engine<C, Locked> {
self.tool_server.flush_pending();
let locked_prefix_len = self.history.len();
Worker {
Engine {
client: self.client,
retry_policy: self.retry_policy,
timeline: self.timeline,
@ -1947,7 +1950,7 @@ impl<C: LlmClient> Worker<C, Mutable> {
}
}
impl<C: LlmClient> Worker<C, Locked> {
impl<C: LlmClient> Engine<C, Locked> {
/// Execute a turn
///
/// Adds a new user message to history and sends a request to the LLM.
@ -1955,7 +1958,7 @@ impl<C: LlmClient> Worker<C, Locked> {
pub async fn run(
&mut self,
user_input: impl Into<String>,
) -> Result<WorkerResult, WorkerError> {
) -> Result<EngineResult, EngineError> {
self.reset_interruption_state();
// Interceptor: on_prompt_submit
let mut user_item = Item::user_message(user_input);
@ -1963,7 +1966,7 @@ impl<C: LlmClient> Worker<C, Locked> {
PromptAction::Cancel(reason) => {
self.last_run_interrupted = true;
return self
.finalize_interruption(Err(WorkerError::Aborted(reason)))
.finalize_interruption(Err(EngineError::Aborted(reason)))
.await;
}
PromptAction::Continue => Vec::new(),
@ -1980,7 +1983,7 @@ impl<C: LlmClient> Worker<C, Locked> {
/// Resume execution (from Paused state)
///
/// Resumes turn processing from current state without adding a new user message.
pub async fn resume(&mut self) -> Result<WorkerResult, WorkerError> {
pub async fn resume(&mut self) -> Result<EngineResult, EngineError> {
self.reset_interruption_state();
let result = self.run_turn_loop().await;
self.finalize_interruption(result).await
@ -1995,8 +1998,8 @@ impl<C: LlmClient> Worker<C, Locked> {
///
/// Note: After this operation, subsequent requests may not hit the cache.
/// Use only when you need to edit history.
pub fn unlock(self) -> Worker<C, Mutable> {
Worker {
pub fn unlock(self) -> Engine<C, Mutable> {
Engine {
client: self.client,
retry_policy: self.retry_policy,
timeline: self.timeline,

View File

@ -1,4 +1,4 @@
//! Public event types for Worker layer
//! Public event types for Engine layer
//!
//! Re-exports from the canonical event definitions in llm_client.

View File

@ -32,7 +32,7 @@ pub trait Kind {
/// # Examples
///
/// ```ignore
/// use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind};
/// use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind};
///
/// struct TextCollector {
/// texts: Vec<String>,

View File

@ -1,8 +1,8 @@
//! Interceptor - control flow delegation for the Worker execution loop
//! Interceptor - control flow delegation for the Engine execution loop
//!
//! Defines the [`Interceptor`] trait that upper layers (e.g. Pod) implement
//! to inject orchestration decisions (approval, skip, pause, abort)
//! into the Worker's turn loop without the Worker knowing about
//! into the Engine's turn loop without the Engine knowing about
//! higher-level concepts.
use std::sync::Arc;
@ -36,13 +36,13 @@ pub enum PromptAction {
pub enum PreRequestAction {
/// Proceed normally.
Continue,
/// Proceed after appending these items to durable worker history.
/// Proceed after appending these items to durable engine history.
///
/// This is for upper-layer budget/status nudges that the model may react
/// to: the items are committed before the request so later turns can see
/// why the worker changed course.
/// why the engine changed course.
ContinueWith(Vec<Item>),
/// Yield after appending these items to durable worker history.
/// Yield after appending these items to durable engine history.
///
/// This is for host-mediated pre-request appends that must be visible to
/// usage accounting and compaction checks before the current LLM request is
@ -52,7 +52,7 @@ pub enum PreRequestAction {
Cancel(String),
/// Yield control to the caller for external processing.
///
/// The Worker exits the turn loop cleanly with `WorkerResult::Yielded`.
/// The Engine exits the turn loop cleanly with `EngineResult::Yielded`.
/// The caller is expected to resume execution later.
Yield,
}
@ -129,9 +129,9 @@ pub struct ToolResultInfo {
// Interceptor Trait
// =============================================================================
/// Intercepts the Worker execution loop at key decision points.
/// Intercepts the Engine execution loop at key decision points.
///
/// All methods have default implementations that let the Worker
/// All methods have default implementations that let the Engine
/// proceed without intervention. Upper layers (e.g. Pod) provide
/// richer implementations for approval flows, permission checks, etc.
#[async_trait]
@ -141,7 +141,7 @@ pub trait Interceptor: Send + Sync {
PromptAction::Continue
}
/// Items that should be **committed to `worker.history`** just
/// Items that should be **committed to `engine.history`** just
/// before the next LLM request. Returned items are `extend`ed into
/// the persistent history (and therefore picked up by the per-turn
/// clone that backs the LLM request, plus the usual
@ -164,12 +164,12 @@ pub trait Interceptor: Send + Sync {
}
/// Called before each LLM request. The context starts as a clone
/// of `worker.history` (after `pending_history_appends` and the
/// Worker's own prune projection have been applied).
/// of `engine.history` (after `pending_history_appends` and the
/// Engine's own prune projection have been applied).
///
/// Direct mutations to `context` remain request-local and are not persisted.
/// If an interceptor derives a human/model-visible nudge from the current
/// request context, return [`PreRequestAction::ContinueWith`] so the Worker
/// request context, return [`PreRequestAction::ContinueWith`] so the Engine
/// commits it to history before the request is sent.
async fn pre_llm_request(&self, _context: &mut Vec<Item>) -> PreRequestAction {
PreRequestAction::Continue
@ -194,7 +194,7 @@ pub trait Interceptor: Send + Sync {
async fn on_abort(&self, _reason: &str) {}
}
/// Default interceptor: no intervention. Worker proceeds through the loop
/// Default interceptor: no intervention. Engine proceeds through the loop
/// without any external control flow decisions.
pub(crate) struct DefaultInterceptor;

View File

@ -1,28 +1,28 @@
//! llm-worker - LLM Worker Library
//! llm-engine - LLM Engine Library
//!
//! Provides components for managing interactions with LLMs.
//!
//! # Main Components
//!
//! - [`Worker`] - Central component for managing LLM interactions
//! - [`Engine`] - Central component for managing LLM interactions
//! - [`tool::Tool`] - Tools that can be invoked by the LLM
//! - [`interceptor::Interceptor`] - Control-flow delegation for the execution loop
//! - Closure-based event callbacks via `Worker::on_text_block()`, `on_tool_use_block()`, etc.
//! - Closure-based event callbacks via `Engine::on_text_block()`, `on_tool_use_block()`, etc.
//!
//! # Quick Start
//!
//! ```ignore
//! use llm_worker::{Worker, Item};
//! use llm_engine::{Engine, Item};
//!
//! // Create a Worker
//! let mut worker = Worker::new(client)
//! // Create a Engine
//! let mut engine = Engine::new(client)
//! .system_prompt("You are a helpful assistant.");
//!
//! // Register tools (optional)
//! // worker.register_tool(my_tool_definition)?;
//! // engine.register_tool(my_tool_definition)?;
//!
//! // Run the interaction
//! let history = worker.run("Hello!").await?;
//! let history = engine.run("Hello!").await?;
//! ```
//!
//! # Cache Protection
@ -31,15 +31,15 @@
//! call `unlock_cache()` first; the next `run()` re-locks automatically.
//!
//! ```ignore
//! worker.run("user input").await?;
//! worker.unlock_cache();
//! worker.set_system_prompt("new prompt");
//! worker.run("next input").await?;
//! engine.run("user input").await?;
//! engine.unlock_cache();
//! engine.set_system_prompt("new prompt");
//! engine.run("next input").await?;
//! ```
mod engine;
mod handler;
mod message;
mod worker;
pub(crate) mod callback;
pub mod event;
@ -54,11 +54,12 @@ pub mod tool_server;
pub mod usage_record;
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
pub use engine::{
Engine, EngineConfig, EngineError, EngineResult, EngineRunOutput, LlmRetryNotice,
ToolRegistryError,
};
pub use handler::ToolUseBlockStart;
pub use interceptor::Interceptor;
pub use message::{ContentPart, Item, Message, Role};
pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult};
pub use usage_record::UsageRecord;
pub use worker::{
LlmRetryNotice, RunOutput, ToolRegistryError, Worker, WorkerConfig, WorkerError, WorkerResult,
};

View File

@ -1,7 +1,7 @@
//! `Scheme` 実装と通信層が要求する認証要件、および動的認証プロバイダ。
//!
//! マニフェスト側の型(`ModelConfig` / `SchemeKind` / `AuthRef`)は
//! `crates/manifest` に置き、llm-worker はそれを知らずに済む。
//! `crates/manifest` に置き、llm-engine はそれを知らずに済む。
//! `AuthRequirement` は scheme が宣言する「この scheme はどんな認証を
//! 期待するか」のランタイム記述で、manifest 側の `AuthRef` との
//! 照合(`AuthRef → ResolvedAuth` 変換の適否)は `crates/provider`
@ -36,7 +36,7 @@ pub enum AuthRequirement {
/// Codex OAuth のように access_token が refresh で更新されたり、
/// `ChatGPT-Account-Id` / `X-OpenAI-Fedramp` のような複数ヘッダを
/// 同時に注入する必要があるケースで使う。実体は `crates/provider`
/// 側に置き、llm-worker は trait を知るだけ。
/// 側に置き、llm-engine は trait を知るだけ。
///
/// 返したヘッダはそのまま `HeaderMap` に挿入される。`Authorization`
/// 含む scheme 既定の認証ヘッダは送出されないので、必要なら

View File

@ -81,7 +81,7 @@ impl Clone for Box<dyn LlmClient> {
/// `Box<dyn LlmClient>` に対する `LlmClient` の実装
///
/// これにより、動的ディスパッチを使用するクライアントも `Worker` で利用可能になる。
/// これにより、動的ディスパッチを使用するクライアントも `Engine` で利用可能になる。
#[async_trait]
impl LlmClient for Box<dyn LlmClient> {
async fn stream(&self, request: Request) -> Result<ResponseStream, ClientError> {

View File

@ -1,6 +1,6 @@
//! LLM response stream を開く前の transient error 向けリトライポリシー。
//!
//! Worker が `LlmClient::stream` の open error に対して `is_retryable` を見て
//! Engine が `LlmClient::stream` の open error に対して `is_retryable` を見て
//! retry / backoff / TUI event / cancellation をまとめて管理する。
//! SSE 読み出し開始後の失敗は対象外。
@ -8,8 +8,8 @@ use std::time::Duration;
/// 指数バックオフ + ジッター + 累積タイムアウトを表すポリシー。
///
/// `Default` は llm-worker 全体の固定値を返す。manifest 経由の上書きが
/// 必要になったら拡張する(現状は不要 → `tickets/llm-worker-transient-retry.md`)。
/// `Default` は llm-engine 全体の固定値を返す。manifest 経由の上書きが
/// 必要になったら拡張する(現状は不要 → `tickets/llm-engine-transient-retry.md`)。
#[derive(Debug, Clone)]
pub struct RetryPolicy {
/// 指数の基準値。`base * 2^attempt` を `cap` で頭打ちにした上限から

View File

@ -62,7 +62,7 @@ impl fmt::Debug for RequestTrace {
/// # Examples
///
/// ```ignore
/// use llm_worker::Item;
/// use llm_engine::Item;
///
/// let user = Item::user_message("Hello!");
/// let assistant = Item::assistant_message("Hi there!");

View File

@ -9,7 +9,7 @@
//! Prune は **コンテキスト射影** であり、history の変換ではない。
//! この crate が提供するのは pure な候補抽出 [`prunable_indices`] のみで、
//! 射影の適用は上位層(`pod::prune_hook` 等)が LLM に送る一時コンテキスト
//! に対してだけ行う。Worker の永続履歴は決して変更されない。
//! に対してだけ行う。Engine の永続履歴は決して変更されない。
//!
//! 保護境界は末尾 token budget で決めるが、この crate は usage 履歴を
//! 所有しない。prefix ごとの token 推定値と savings 推定は上位層から
@ -32,8 +32,8 @@ pub type TokenEstimator = Box<dyn Fn(&[Item]) -> Vec<TokenEstimate> + Send + Syn
/// Callback that estimates the token savings for projecting the
/// `ToolResult.content` out of `history[i]` for each `i` in `indices`.
///
/// Injected into [`crate::Worker`] via `set_savings_estimator` so the
/// Worker can make `min_savings` decisions without knowing about usage
/// Injected into [`crate::Engine`] via `set_savings_estimator` so the
/// Engine can make `min_savings` decisions without knowing about usage
/// measurement sources. Return `0` to signal "no data / refuse to prune".
///
/// 推定対象は「drop する範囲全体」ではなく「content を None にする差分」
@ -44,7 +44,7 @@ pub type SavingsEstimator = Box<dyn Fn(&[Item], &[usize]) -> u64 + Send + Sync>;
/// Result of one prune evaluation pass, surfaced to the optional
/// [`PruneObserver`] for instrumentation.
///
/// Worker は LLM リクエストごとに 1 回 prune の評価をし、その結果を
/// Engine は LLM リクエストごとに 1 回 prune の評価をし、その結果を
/// observer が登録されていればこの値で通知する。fire/skip の判定
/// 結果と、判定材料になった候補数 / 推定 savings / 保護領域の先頭 index を持つ。
#[derive(Debug, Clone)]
@ -61,7 +61,7 @@ pub struct PruneEvaluation {
}
/// Outcome of one prune evaluation. Each variant is one branch of the
/// "fire vs skip" decision tree the Worker walks before each LLM request.
/// "fire vs skip" decision tree the Engine walks before each LLM request.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PruneDecision {
/// `prunable_indices` が空 → 何もしない。

View File

@ -1,12 +1,12 @@
//! Worker State
//! Engine State
//!
//! State marker types for cache protection using the Type-state pattern.
//! Worker has state transitions from `Mutable` → `Locked`.
//! Engine has state transitions from `Mutable` → `Locked`.
/// Marker trait representing Worker state
/// Marker trait representing Engine state
///
/// This trait is sealed and cannot be implemented externally.
pub trait WorkerState: private::Sealed + Send + Sync + 'static {}
pub trait EngineState: private::Sealed + Send + Sync + 'static {}
mod private {
pub trait Sealed {}
@ -19,28 +19,28 @@ mod private {
/// - Editing message history (add, delete, clear)
/// - Registering tools and hooks
///
/// Can transition to [`Locked`] state via `Worker::lock()`.
/// Can transition to [`Locked`] state via `Engine::lock()`.
///
/// # Examples
///
/// ```ignore
/// use llm_worker::Worker;
/// use llm_engine::Engine;
///
/// let mut worker = Worker::new(client)
/// let mut engine = Engine::new(client)
/// .system_prompt("You are helpful.");
///
/// // History can be edited
/// worker.push_message(Message::user("Hello"));
/// worker.clear_history();
/// engine.push_message(Message::user("Hello"));
/// engine.clear_history();
///
/// // Lock to protected state
/// let locked = worker.lock();
/// let locked = engine.lock();
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct Mutable;
impl private::Sealed for Mutable {}
impl WorkerState for Mutable {}
impl EngineState for Mutable {}
/// Cache locked state (cache protected)
///
@ -51,10 +51,10 @@ impl WorkerState for Mutable {}
/// To ensure LLM API KV cache hits,
/// using this state during execution is recommended.
///
/// Can return to [`Mutable`] state via `Worker::unlock()`,
/// Can return to [`Mutable`] state via `Engine::unlock()`,
/// but note that cache protection will be released.
#[derive(Debug, Clone, Copy, Default)]
pub struct Locked;
impl private::Sealed for Locked {}
impl WorkerState for Locked {}
impl EngineState for Locked {}

View File

@ -1,7 +1,7 @@
//! Timeline層
//!
//! LLMからのイベントストリームを受信し、登録されたHandlerにディスパッチします。
//! 通常はWorker経由で使用しますが、直接使用することも可能です。
//! 通常はEngine経由で使用しますが、直接使用することも可能です。
use std::{collections::HashMap, marker::PhantomData};
@ -348,7 +348,7 @@ where
/// # Examples
///
/// ```ignore
/// use llm_worker::{Timeline, Handler, TextBlockKind, TextBlockEvent};
/// use llm_engine::{Timeline, Handler, TextBlockKind, TextBlockEvent};
///
/// struct MyHandler;
/// impl Handler<TextBlockKind> for MyHandler {

View File

@ -33,7 +33,7 @@ pub enum ToolError {
/// Outputs this small don't benefit from pruning.
pub const SUMMARY_THRESHOLD: usize = 200;
/// Byte-size caps applied to tool execution `content` at the Worker's
/// Byte-size caps applied to tool execution `content` at the Engine's
/// tool-execution boundary, before results enter conversation history.
///
/// Exists so a single oversized tool result (e.g. a wide `Glob` scan)
@ -131,7 +131,7 @@ impl From<String> for ToolOutput {
///
/// This metadata is intentionally not part of the provider-facing tool schema.
/// It lets host layers audit where a model-visible tool definition came from
/// while keeping execution and permission semantics in the normal Worker path.
/// while keeping execution and permission semantics in the normal Engine path.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolOrigin {
/// Origin kind, for example `plugin` or `builtin`.
@ -154,7 +154,7 @@ pub struct ToolOrigin {
/// Tool meta information (fixed at registration, immutable)
///
/// Generated from `ToolDefinition` factory and does not change after registration with Worker.
/// Generated from `ToolDefinition` factory and does not change after registration with Engine.
/// Used for sending tool definitions to LLM.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolMeta {
@ -205,7 +205,7 @@ impl ToolMeta {
/// Tool definition factory
///
/// When called, returns `(ToolMeta, Arc<dyn Tool>)`.
/// Called once during Worker registration, and the meta information and instance
/// Called once during Engine registration, and the meta information and instance
/// are cached at session scope.
///
/// # Examples
@ -219,22 +219,22 @@ impl ToolMeta {
/// Arc::new(MyToolImpl { state: 0 }) as Arc<dyn Tool>,
/// )
/// });
/// worker.register_tool(def)?;
/// engine.register_tool(def)?;
/// ```
pub type ToolDefinition = Arc<dyn Fn() -> (ToolMeta, Arc<dyn Tool>) + Send + Sync>;
/// Per-call context supplied by the worker when executing a tool call.
/// Per-call context supplied by the engine when executing a tool call.
///
/// The context identifies a tool call within one assistant response's tool-call
/// batch without imposing any scheduling policy on the worker. Tool
/// batch without imposing any scheduling policy on the engine. Tool
/// implementations may use it for response-local ordering, diagnostics, or
/// correlation, but it is intentionally not a handle to worker state, history,
/// correlation, but it is intentionally not a handle to engine state, history,
/// or session mutation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolExecutionContext {
/// Provider/tool-call id for the call being executed.
pub call_id: String,
/// Worker-local identity shared by all tool calls from one execution batch.
/// Engine-local identity shared by all tool calls from one execution batch.
pub batch_id: String,
/// Zero-based order of this call in the model-returned tool-call list.
pub call_index: usize,
@ -249,7 +249,7 @@ impl ToolExecutionContext {
}
}
/// Context for direct, non-worker calls in unit tests and low-level callers.
/// Context for direct, non-engine calls in unit tests and low-level callers.
pub fn direct() -> Self {
Self::new("direct", "direct", 0)
}
@ -285,13 +285,13 @@ impl Default for ToolExecutionContext {
/// }
///
/// // Register
/// worker.register_tool(app.search_definition())?;
/// engine.register_tool(app.search_definition())?;
/// ```
///
/// # Manual Implementation
///
/// ```ignore
/// use llm_worker::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
/// use llm_engine::tool::{Tool, ToolError, ToolExecutionContext, ToolMeta, ToolDefinition, ToolOutput};
/// use std::sync::Arc;
///
/// struct MyTool { counter: std::sync::atomic::AtomicUsize }

View File

@ -5,7 +5,7 @@ use thiserror::Error;
use crate::llm_client::ToolDefinition as LlmToolDefinition;
use crate::tool::{
Tool, ToolDefinition as WorkerToolDefinition, ToolExecutionContext, ToolMeta, ToolOutput,
Tool, ToolDefinition as EngineToolDefinition, ToolExecutionContext, ToolMeta, ToolOutput,
};
type ToolMap = HashMap<String, (ToolMeta, Arc<dyn Tool>)>;
@ -28,7 +28,7 @@ pub enum ToolServerError {
#[derive(Clone, Default)]
pub struct ToolServer {
tools: Arc<Mutex<ToolMap>>,
pending: Arc<Mutex<Vec<WorkerToolDefinition>>>,
pending: Arc<Mutex<Vec<EngineToolDefinition>>>,
}
impl ToolServer {
@ -50,7 +50,7 @@ impl ToolServer {
#[derive(Clone, Default)]
pub struct ToolServerHandle {
tools: Arc<Mutex<ToolMap>>,
pending: Arc<Mutex<Vec<WorkerToolDefinition>>>,
pending: Arc<Mutex<Vec<EngineToolDefinition>>>,
}
impl ToolServerHandle {
@ -58,8 +58,8 @@ impl ToolServerHandle {
///
/// The factory is **not** called here; it is stored and executed
/// when [`flush_pending`](Self::flush_pending) is called (typically
/// at the start of `Worker::run()`).
pub(crate) fn register_tool(&self, factory: WorkerToolDefinition) {
/// at the start of `Engine::run()`).
pub(crate) fn register_tool(&self, factory: EngineToolDefinition) {
self.pending
.lock()
.unwrap_or_else(|e| e.into_inner())
@ -67,14 +67,14 @@ impl ToolServerHandle {
}
/// Queue many tool factories for deferred initialization.
pub(crate) fn register_tools(&self, factories: impl IntoIterator<Item = WorkerToolDefinition>) {
pub(crate) fn register_tools(&self, factories: impl IntoIterator<Item = EngineToolDefinition>) {
let mut guard = self.pending.lock().unwrap_or_else(|e| e.into_inner());
guard.extend(factories);
}
/// Execute all pending factories and register the resulting tools.
///
/// Called implicitly by `Worker::lock()` before the first turn.
/// Called implicitly by `Engine::lock()` before the first turn.
/// Exposed as `pub` so higher layers (e.g. Pod) can force-materialise
/// tools earlier — for example when building a system-prompt template
/// context that needs the list of registered tool names. Redundant
@ -150,7 +150,7 @@ impl ToolServerHandle {
/// The factory is called immediately and the resulting tool overwrites
/// the entry with the same name. Returns `ToolNotFound` if the name
/// produced by the factory does not match any registered tool.
pub fn replace(&self, factory: WorkerToolDefinition) -> Result<(), ToolServerError> {
pub fn replace(&self, factory: EngineToolDefinition) -> Result<(), ToolServerError> {
let (meta, instance) = factory();
let mut guard = self.tools.lock().unwrap_or_else(|e| e.into_inner());
if !guard.contains_key(&meta.name) {

View File

@ -1,6 +1,6 @@
//! Closure callback API tests
//!
//! Tests for the closure-based event subscription API on Worker.
//! Tests for the closure-based event subscription API on Engine.
mod common;
@ -10,11 +10,11 @@ use std::time::Duration;
use async_trait::async_trait;
use common::MockLlmClient;
use llm_worker::Worker;
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
use llm_worker::llm_client::retry::RetryPolicy;
use llm_worker::llm_client::{ClientError, LlmClient, Request, ResponseStream};
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use llm_engine::Engine;
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent as ClientStatusEvent};
use llm_engine::llm_client::retry::RetryPolicy;
use llm_engine::llm_client::{ClientError, LlmClient, Request, ResponseStream};
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
#[derive(Clone)]
struct FailOnceClient {
@ -52,7 +52,7 @@ async fn test_callback_llm_retry_event() {
calls: Arc::new(AtomicUsize::new(0)),
events,
};
let mut worker = Worker::new(client).with_retry_policy(RetryPolicy {
let mut engine = Engine::new(client).with_retry_policy(RetryPolicy {
base: Duration::from_millis(1),
cap: Duration::from_millis(1),
max_attempts: 2,
@ -61,12 +61,12 @@ async fn test_callback_llm_retry_event() {
let notices = Arc::new(Mutex::new(Vec::new()));
let sink = notices.clone();
worker.on_llm_retry(move |llm_call, notice| {
engine.on_llm_retry(move |llm_call, notice| {
sink.lock().unwrap().push((llm_call, notice.clone()));
});
let result = worker.run("retry once").await;
assert!(result.is_ok(), "worker should succeed after one retry");
let result = engine.run("retry once").await;
assert!(result.is_ok(), "engine should succeed after one retry");
let notices = notices.lock().unwrap();
assert_eq!(notices.len(), 1);
@ -90,14 +90,14 @@ async fn test_callback_text_block_events() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let text_deltas = Arc::new(Mutex::new(Vec::new()));
let text_completes = Arc::new(Mutex::new(Vec::new()));
let deltas = text_deltas.clone();
let completes = text_completes.clone();
worker.on_text_block(move |block| {
engine.on_text_block(move |block| {
let d = deltas.clone();
block.on_delta(move |text| {
d.lock().unwrap().push(text.to_owned());
@ -108,9 +108,9 @@ async fn test_callback_text_block_events() {
});
});
// Mutable::run consumes self, returns (Locked, WorkerResult)
let result = worker.run("Greet me").await;
assert!(result.is_ok(), "Worker should complete");
// Mutable::run consumes self, returns (Locked, EngineResult)
let result = engine.run("Greet me").await;
assert!(result.is_ok(), "Engine should complete");
let deltas = text_deltas.lock().unwrap();
assert_eq!(deltas.len(), 2);
@ -136,14 +136,14 @@ async fn test_callback_tool_call_complete() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let tool_starts = Arc::new(Mutex::new(Vec::<(String, String)>::new()));
let tool_completes = Arc::new(Mutex::new(Vec::new()));
let starts = tool_starts.clone();
let completes = tool_completes.clone();
worker.on_tool_use_block(move |start, block| {
engine.on_tool_use_block(move |start, block| {
starts
.lock()
.unwrap()
@ -154,8 +154,8 @@ async fn test_callback_tool_call_complete() {
});
});
// Mutable::run consumes self, returns (Locked, WorkerResult)
let _ = worker.run("Weather please").await;
// Mutable::run consumes self, returns (Locked, EngineResult)
let _ = engine.run("Weather please").await;
let starts = tool_starts.lock().unwrap();
assert_eq!(starts.len(), 1);
@ -182,23 +182,23 @@ async fn test_callback_turn_events() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let turn_starts = Arc::new(Mutex::new(Vec::new()));
let turn_ends = Arc::new(Mutex::new(Vec::new()));
let starts = turn_starts.clone();
worker.on_turn_start(move |turn| {
engine.on_turn_start(move |turn| {
starts.lock().unwrap().push(turn);
});
let ends = turn_ends.clone();
worker.on_turn_end(move |turn| {
engine.on_turn_end(move |turn| {
ends.lock().unwrap().push(turn);
});
// Mutable::run consumes self, returns (Locked, WorkerResult)
let result = worker.run("Do something").await;
// Mutable::run consumes self, returns (Locked, EngineResult)
let result = engine.run("Do something").await;
assert!(result.is_ok());
let starts = turn_starts.lock().unwrap();
@ -221,7 +221,7 @@ impl Tool for FixedOutputTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
_ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
Ok(self.output.clone())
}
@ -253,9 +253,9 @@ async fn test_callback_tool_result_events() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
worker.register_tool(fixed_tool(
engine.register_tool(fixed_tool(
"fixed",
ToolOutput {
summary: "did the thing".into(),
@ -266,7 +266,7 @@ async fn test_callback_tool_result_events() {
let captured: Arc<Mutex<Vec<(String, String, Option<String>, bool)>>> =
Arc::new(Mutex::new(Vec::new()));
let sink = captured.clone();
worker.on_tool_result(move |result| {
engine.on_tool_result(move |result| {
sink.lock().unwrap().push((
result.tool_use_id.clone(),
result.summary.clone(),
@ -275,7 +275,7 @@ async fn test_callback_tool_result_events() {
));
});
let _ = worker.run("call it").await;
let _ = engine.run("call it").await;
let observed = captured.lock().unwrap();
assert_eq!(observed.len(), 1);
@ -296,7 +296,7 @@ impl Tool for ErroringTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
_ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
Err(ToolError::ExecutionFailed(self.message.clone()))
}
@ -328,14 +328,14 @@ async fn test_callback_tool_result_error_path() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
worker.register_tool(erroring_tool("erroring", "boom"));
engine.register_tool(erroring_tool("erroring", "boom"));
let captured: Arc<Mutex<Vec<(String, String, Option<String>, bool)>>> =
Arc::new(Mutex::new(Vec::new()));
let sink = captured.clone();
worker.on_tool_result(move |result| {
engine.on_tool_result(move |result| {
sink.lock().unwrap().push((
result.tool_use_id.clone(),
result.summary.clone(),
@ -344,7 +344,7 @@ async fn test_callback_tool_result_error_path() {
));
});
let _ = worker.run("fail it").await;
let _ = engine.run("fail it").await;
let observed = captured.lock().unwrap();
assert_eq!(observed.len(), 1);
@ -372,17 +372,17 @@ async fn test_callback_usage_events() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let usage_events = Arc::new(Mutex::new(Vec::new()));
let usages = usage_events.clone();
worker.on_usage(move |event| {
engine.on_usage(move |event| {
usages.lock().unwrap().push(event.clone());
});
// Mutable::run consumes self, returns (Locked, WorkerResult)
let _ = worker.run("Hello").await;
// Mutable::run consumes self, returns (Locked, EngineResult)
let _ = engine.run("Hello").await;
let usages = usage_events.lock().unwrap();
assert_eq!(usages.len(), 1);

View File

@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use futures::Stream;
use llm_worker::llm_client::event::{BlockType, DeltaContent, Event};
use llm_worker::llm_client::{ClientError, LlmClient, Request};
use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use llm_engine::llm_client::event::{BlockType, DeltaContent, Event};
use llm_engine::llm_client::{ClientError, LlmClient, Request};
use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use std::sync::atomic::{AtomicUsize, Ordering};
@ -272,7 +272,7 @@ pub fn assert_timeline_integration(subdir: &str) {
});
for event in &events {
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
let timeline_event: llm_engine::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
}

View File

@ -1,6 +1,6 @@
//! Worker fixture-based integration tests
//! Engine fixture-based integration tests
//!
//! Tests Worker behavior using recorded API responses.
//! Tests Engine behavior using recorded API responses.
//! Can run locally without API keys.
mod common;
@ -11,8 +11,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait;
use common::MockLlmClient;
use llm_worker::Worker;
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use llm_engine::Engine;
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
/// Fixture directory path
fn fixtures_dir() -> std::path::PathBuf {
@ -61,7 +61,7 @@ impl Tool for MockWeatherTool {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
_ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst);
@ -102,7 +102,7 @@ fn test_mock_client_from_fixture() {
/// Creates a client with programmatically constructed events instead of using fixture files.
#[test]
fn test_mock_client_from_events() {
use llm_worker::llm_client::event::Event;
use llm_engine::llm_client::event::Event;
// Specify events directly
let events = vec![
@ -116,54 +116,54 @@ fn test_mock_client_from_events() {
}
// =============================================================================
// Worker Tests with Fixtures
// Engine Tests with Fixtures
// =============================================================================
/// Verify that Worker can correctly process simple text responses
/// Verify that Engine can correctly process simple text responses
///
/// Uses simple_text.jsonl fixture to test scenarios without tool calls.
/// Skipped if fixture is not present.
#[tokio::test]
async fn test_worker_simple_text_response() {
async fn test_engine_simple_text_response() {
let fixture_path = fixtures_dir().join("simple_text.jsonl");
if !fixture_path.exists() {
println!("Fixture not found: {:?}, skipping test", fixture_path);
println!("Run: cargo run --example record_worker_test");
println!("Run: cargo run --example record_engine_test");
return;
}
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
let worker = Worker::new(client);
let engine = Engine::new(client);
// Send a simple message (Mutable::run consumes self, returns tuple)
let result = worker.run("Hello").await;
let result = engine.run("Hello").await;
assert!(result.is_ok(), "Worker should complete successfully");
assert!(result.is_ok(), "Engine should complete successfully");
}
/// Verify that Worker can correctly process responses containing tool calls
/// Verify that Engine can correctly process responses containing tool calls
///
/// Uses tool_call.jsonl fixture to test that MockWeatherTool is called.
/// Sets max_turns=1 to prevent loop after tool execution.
#[tokio::test]
async fn test_worker_tool_call() {
async fn test_engine_tool_call() {
let fixture_path = fixtures_dir().join("tool_call.jsonl");
if !fixture_path.exists() {
println!("Fixture not found: {:?}, skipping test", fixture_path);
println!("Run: cargo run --example record_worker_test");
println!("Run: cargo run --example record_engine_test");
return;
}
let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
// Register tool
let weather_tool = MockWeatherTool::new();
let tool_for_check = weather_tool.clone();
worker.register_tool(weather_tool.definition());
engine.register_tool(weather_tool.definition());
// Send message (Mutable::run consumes self, returns tuple)
let _result = worker.run("What's the weather in Tokyo?").await;
let _result = engine.run("What's the weather in Tokyo?").await;
// Verify tool was called
// Note: max_turns=1 so no request is sent after tool result
@ -174,13 +174,13 @@ async fn test_worker_tool_call() {
// But ends after 1 turn due to max_turns=1
}
/// Verify that Worker works without fixture files
/// Verify that Engine works without fixture files
///
/// Constructs event sequence programmatically and passes to MockLlmClient.
/// Useful when test independence is needed and external file dependency should be eliminated.
#[tokio::test]
async fn test_worker_with_programmatic_events() {
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
async fn test_engine_with_programmatic_events() {
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
// Construct event sequence programmatically
let events = vec![
@ -194,12 +194,12 @@ async fn test_worker_with_programmatic_events() {
];
let client = MockLlmClient::new(events);
let worker = Worker::new(client);
let engine = Engine::new(client);
// Mutable::run consumes self, returns tuple
let result = worker.run("Greet me").await;
let result = engine.run("Greet me").await;
assert!(result.is_ok(), "Worker should complete successfully");
assert!(result.is_ok(), "Engine should complete successfully");
}
/// Verify that ToolCallCollector correctly collects ToolCall from ToolUse block events
@ -208,8 +208,8 @@ async fn test_worker_with_programmatic_events() {
/// correctly extracts id, name, and input (JSON).
#[tokio::test]
async fn test_tool_call_collector_integration() {
use llm_worker::llm_client::event::Event;
use llm_worker::timeline::{Timeline, ToolCallCollector};
use llm_engine::llm_client::event::Event;
use llm_engine::timeline::{Timeline, ToolCallCollector};
// Event sequence containing ToolUse block
let events = vec![
@ -225,7 +225,7 @@ async fn test_tool_call_collector_integration() {
// Dispatch events
for event in &events {
let timeline_event: llm_worker::timeline::event::Event = event.clone().into();
let timeline_event: llm_engine::timeline::event::Event = event.clone().into();
timeline.dispatch(&timeline_event);
}

View File

@ -1,4 +1,4 @@
//! Worker state management tests
//! Engine state management tests
//!
//! Tests for state transitions using the Type-state pattern (Mutable/Locked)
//! and state preservation between turns.
@ -10,10 +10,10 @@ use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use common::MockLlmClient;
use llm_worker::Item;
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use llm_worker::{Worker, WorkerError};
use llm_engine::Item;
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use llm_engine::{Engine, EngineError};
// =============================================================================
// Mutable State Tests
@ -23,13 +23,13 @@ use llm_worker::{Worker, WorkerError};
#[test]
fn test_mutable_set_system_prompt() {
let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
assert!(worker.get_system_prompt().is_none());
assert!(engine.get_system_prompt().is_none());
worker.set_system_prompt("You are a helpful assistant.");
engine.set_system_prompt("You are a helpful assistant.");
assert_eq!(
worker.get_system_prompt(),
engine.get_system_prompt(),
Some("You are a helpful assistant.")
);
}
@ -38,41 +38,41 @@ fn test_mutable_set_system_prompt() {
#[test]
fn test_mutable_history_manipulation() {
let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
// Initial state is empty
assert!(worker.history().is_empty());
assert!(engine.history().is_empty());
// Add to history
worker.append_history(vec![Item::user_message("Hello")]);
worker.append_history(vec![Item::assistant_message("Hi there!")]);
assert_eq!(worker.history().len(), 2);
engine.append_history(vec![Item::user_message("Hello")]);
engine.append_history(vec![Item::assistant_message("Hi there!")]);
assert_eq!(engine.history().len(), 2);
// Append to history via the callback-aware API.
worker.append_history(vec![Item::user_message("How are you?")]);
assert_eq!(worker.history().len(), 3);
engine.append_history(vec![Item::user_message("How are you?")]);
assert_eq!(engine.history().len(), 3);
// Clear history
worker.clear_history();
assert!(worker.history().is_empty());
engine.clear_history();
assert!(engine.history().is_empty());
// Set history
let items = vec![
Item::user_message("Test"),
Item::assistant_message("Response"),
];
worker.set_history(items);
assert_eq!(worker.history().len(), 2);
engine.set_history(items);
assert_eq!(engine.history().len(), 2);
}
/// Verify that Worker can be constructed using builder pattern
/// Verify that Engine can be constructed using builder pattern
#[test]
fn test_mutable_builder_pattern() {
let client = MockLlmClient::new(vec![]);
let worker = Worker::new(client).system_prompt("System prompt");
let engine = Engine::new(client).system_prompt("System prompt");
assert_eq!(worker.get_system_prompt(), Some("System prompt"));
assert!(worker.history().is_empty());
assert_eq!(engine.get_system_prompt(), Some("System prompt"));
assert!(engine.history().is_empty());
}
/// Verify that multiple items can be added with append_history and callbacks fire.
@ -81,22 +81,22 @@ fn test_mutable_append_history() {
let client = MockLlmClient::new(vec![]);
let observed = Arc::new(Mutex::new(Vec::new()));
let observed_for_callback = Arc::clone(&observed);
let mut worker = Worker::new(client);
worker.on_history_append(move |item| {
let mut engine = Engine::new(client);
engine.on_history_append(move |item| {
if let Some(text) = item.as_text() {
observed_for_callback.lock().unwrap().push(text.to_string());
}
});
worker.append_history(vec![Item::user_message("First")]);
engine.append_history(vec![Item::user_message("First")]);
worker.append_history(vec![
engine.append_history(vec![
Item::assistant_message("Response 1"),
Item::user_message("Second"),
Item::assistant_message("Response 2"),
]);
assert_eq!(worker.history().len(), 4);
assert_eq!(engine.history().len(), 4);
assert_eq!(
observed.lock().unwrap().as_slice(),
["First", "Response 1", "Second", "Response 2"]
@ -139,7 +139,7 @@ impl Tool for CountingTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
_ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
self.calls.fetch_add(1, Ordering::SeqCst);
Ok(format!("{}-ok", self.name).into())
@ -150,11 +150,11 @@ impl Tool for CountingTool {
#[test]
fn test_mutable_can_register_tool() {
let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let tool = CountingTool::new("count_tool");
// register_tool is infallible (factory deferred to run-time flush)
worker.register_tool(tool.definition());
engine.register_tool(tool.definition());
}
// =============================================================================
@ -165,37 +165,37 @@ fn test_mutable_can_register_tool() {
#[test]
fn test_lock_transition() {
let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
worker.set_system_prompt("System");
worker.append_history(vec![Item::user_message("Hello")]);
worker.append_history(vec![Item::assistant_message("Hi")]);
engine.set_system_prompt("System");
engine.append_history(vec![Item::user_message("Hello")]);
engine.append_history(vec![Item::assistant_message("Hi")]);
// Lock
let locked_worker = worker.lock();
let locked_engine = engine.lock();
// History and system prompt are still accessible in Locked state
assert_eq!(locked_worker.get_system_prompt(), Some("System"));
assert_eq!(locked_worker.history().len(), 2);
assert_eq!(locked_worker.locked_prefix_len(), 2);
assert_eq!(locked_engine.get_system_prompt(), Some("System"));
assert_eq!(locked_engine.history().len(), 2);
assert_eq!(locked_engine.locked_prefix_len(), 2);
}
/// Verify that unlock() transitions from Locked -> Mutable state
#[test]
fn test_unlock_transition() {
let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
worker.append_history(vec![Item::user_message("Hello")]);
let locked_worker = worker.lock();
engine.append_history(vec![Item::user_message("Hello")]);
let locked_engine = engine.lock();
// Unlock
let mut worker = locked_worker.unlock();
let mut engine = locked_engine.unlock();
// History operations are available again in Mutable state
worker.append_history(vec![Item::assistant_message("Hi")]);
worker.clear_history();
assert!(worker.history().is_empty());
engine.append_history(vec![Item::assistant_message("Hi")]);
engine.clear_history();
assert!(engine.history().is_empty());
}
// =============================================================================
@ -204,7 +204,7 @@ fn test_unlock_transition() {
/// Verify that history is correctly updated after running a turn in Mutable state
#[tokio::test]
async fn test_mutable_run_updates_history() -> Result<(), WorkerError> {
async fn test_mutable_run_updates_history() -> Result<(), EngineError> {
let events = vec![
Event::text_block_start(0),
Event::text_delta(0, "Hello, I'm an assistant!"),
@ -215,14 +215,14 @@ async fn test_mutable_run_updates_history() -> Result<(), WorkerError> {
];
let client = MockLlmClient::new(events);
let worker = Worker::new(client);
let engine = Engine::new(client);
// Execute (Mutable::run consumes self, returns RunOutput)
let out = worker.run("Hi there").await?;
let worker = out.worker;
// Execute (Mutable::run consumes self, returns EngineRunOutput)
let out = engine.run("Hi there").await?;
let engine = out.engine;
// History is updated
let history = worker.history();
let history = engine.history();
assert_eq!(history.len(), 2); // user + assistant
// User message
@ -259,24 +259,24 @@ async fn test_locked_multi_turn_history_accumulation() {
],
]);
let worker = Worker::new(client).system_prompt("You are helpful.");
let engine = Engine::new(client).system_prompt("You are helpful.");
// Lock (after setting system prompt)
let mut locked_worker = worker.lock();
assert_eq!(locked_worker.locked_prefix_len(), 0); // No items yet
let mut locked_engine = engine.lock();
assert_eq!(locked_engine.locked_prefix_len(), 0); // No items yet
// Turn 1
let result1 = locked_worker.run("Hello!").await;
let result1 = locked_engine.run("Hello!").await;
assert!(result1.is_ok());
assert_eq!(locked_worker.history().len(), 2); // user + assistant
assert_eq!(locked_engine.history().len(), 2); // user + assistant
// Turn 2
let result2 = locked_worker.run("Can you help me?").await;
let result2 = locked_engine.run("Can you help me?").await;
assert!(result2.is_ok());
assert_eq!(locked_worker.history().len(), 4); // 2 * (user + assistant)
assert_eq!(locked_engine.history().len(), 4); // 2 * (user + assistant)
// Verify history contents
let history = locked_worker.history();
let history = locked_engine.history();
// Turn 1 user message
assert_eq!(history[0].as_text(), Some("Hello!"));
@ -313,29 +313,29 @@ async fn test_locked_prefix_len_tracking() {
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
// Add items beforehand
worker.append_history(vec![Item::user_message("Pre-existing message 1")]);
worker.append_history(vec![Item::assistant_message("Pre-existing response 1")]);
engine.append_history(vec![Item::user_message("Pre-existing message 1")]);
engine.append_history(vec![Item::assistant_message("Pre-existing response 1")]);
assert_eq!(worker.history().len(), 2);
assert_eq!(engine.history().len(), 2);
// Lock
let mut locked_worker = worker.lock();
assert_eq!(locked_worker.locked_prefix_len(), 2); // 2 items at lock time
let mut locked_engine = engine.lock();
assert_eq!(locked_engine.locked_prefix_len(), 2); // 2 items at lock time
// Execute turn
locked_worker.run("New message").await.unwrap();
locked_engine.run("New message").await.unwrap();
// History grows but locked_prefix_len remains unchanged
assert_eq!(locked_worker.history().len(), 4); // 2 + 2
assert_eq!(locked_worker.locked_prefix_len(), 2); // Unchanged
assert_eq!(locked_engine.history().len(), 4); // 2 + 2
assert_eq!(locked_engine.locked_prefix_len(), 2); // Unchanged
}
/// Verify that turn count is correctly incremented
#[tokio::test]
async fn test_turn_count_increment() -> Result<(), WorkerError> {
async fn test_turn_count_increment() -> Result<(), EngineError> {
let client = MockLlmClient::with_responses(vec![
vec![
Event::text_block_start(0),
@ -355,21 +355,21 @@ async fn test_turn_count_increment() -> Result<(), WorkerError> {
],
]);
let worker = Worker::new(client);
let engine = Engine::new(client);
assert_eq!(worker.turn_count(), 0);
assert_eq!(worker.llm_call_count(), 0);
assert_eq!(engine.turn_count(), 0);
assert_eq!(engine.llm_call_count(), 0);
// First run consumes Mutable, returns RunOutput
let mut worker = worker.run("First").await?.worker;
assert_eq!(worker.turn_count(), 1);
// First run consumes Mutable, returns EngineRunOutput
let mut engine = engine.run("First").await?.engine;
assert_eq!(engine.turn_count(), 1);
// Retry not yet implemented → AgentTurn:LlmCall is 1:1.
assert_eq!(worker.llm_call_count(), 1);
assert_eq!(engine.llm_call_count(), 1);
// Subsequent runs on Locked take &mut self
worker.run("Second").await?;
assert_eq!(worker.turn_count(), 2);
assert_eq!(worker.llm_call_count(), 2);
engine.run("Second").await?;
assert_eq!(engine.turn_count(), 2);
assert_eq!(engine.llm_call_count(), 2);
Ok(())
}
@ -386,14 +386,14 @@ async fn test_unlock_edit_relock() {
}),
]]);
let mut worker = Worker::new(client);
worker.append_history(vec![
let mut engine = Engine::new(client);
engine.append_history(vec![
Item::user_message("Hello"),
Item::assistant_message("Hi"),
]);
// Lock -> Unlock
let locked = worker.lock();
let locked = engine.lock();
assert_eq!(locked.locked_prefix_len(), 2);
let mut unlocked = locked.unlock();
@ -446,11 +446,11 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let tool_a = CountingTool::new("tool_a");
worker.register_tool(tool_a.definition());
engine.register_tool(tool_a.definition());
let mut locked = worker.lock();
let mut locked = engine.lock();
locked.run("first").await.expect("first run");
assert_eq!(tool_a.call_count(), 1, "tool_a should be called once");
@ -473,9 +473,9 @@ async fn test_lock_unlock_relock_tools_remain_effective() {
#[test]
fn test_system_prompt_preserved_in_locked_state() {
let client = MockLlmClient::new(vec![]);
let worker = Worker::new(client).system_prompt("Important system prompt");
let engine = Engine::new(client).system_prompt("Important system prompt");
let locked = worker.lock();
let locked = engine.lock();
assert_eq!(locked.get_system_prompt(), Some("Important system prompt"));
let unlocked = locked.unlock();
@ -489,9 +489,9 @@ fn test_system_prompt_preserved_in_locked_state() {
#[test]
fn test_system_prompt_change_after_unlock() {
let client = MockLlmClient::new(vec![]);
let worker = Worker::new(client).system_prompt("Original prompt");
let engine = Engine::new(client).system_prompt("Original prompt");
let locked = worker.lock();
let locked = engine.lock();
let mut unlocked = locked.unlock();
unlocked.set_system_prompt("New prompt");

View File

@ -1,18 +1,18 @@
//! Parallel tool execution tests
//!
//! Verify that Worker executes multiple tools in parallel.
//! Verify that Engine executes multiple tools in parallel.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use async_trait::async_trait;
use llm_worker::Worker;
use llm_worker::interceptor::{
use llm_engine::Engine;
use llm_engine::interceptor::{
Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo,
};
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::tool::{
use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_engine::tool::{
Tool, ToolDefinition, ToolError, ToolExecutionContext, ToolMeta, ToolOutput, ToolResult,
};
@ -64,7 +64,7 @@ impl Tool for SlowTool {
async fn execute(
&self,
_input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
_ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(self.delay_ms)).await;
@ -146,7 +146,7 @@ async fn test_parallel_tool_execution() {
}),
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let tool1 = SlowTool::new("slow_tool_1", 100);
let tool2 = SlowTool::new("slow_tool_2", 100);
let tool3 = SlowTool::new("slow_tool_3", 100);
@ -155,13 +155,13 @@ async fn test_parallel_tool_execution() {
let tool2_clone = tool2.clone();
let tool3_clone = tool3.clone();
worker.register_tool(tool1.definition());
worker.register_tool(tool2.definition());
worker.register_tool(tool3.definition());
engine.register_tool(tool1.definition());
engine.register_tool(tool2.definition());
engine.register_tool(tool3.definition());
let start = Instant::now();
// Mutable::run consumes self, returns (Locked, WorkerResult)
let _result = worker.run("Run all tools").await;
// Mutable::run consumes self, returns (Locked, EngineResult)
let _result = engine.run("Run all tools").await;
let elapsed = start.elapsed();
// Verify all tools were called
@ -206,14 +206,14 @@ async fn test_tool_execution_context_order_and_batch_id() {
}),
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let contexts = Arc::new(Mutex::new(Vec::new()));
worker.register_tool(ContextRecordingTool::new("record_a", contexts.clone()).definition());
worker.register_tool(ContextRecordingTool::new("record_b", contexts.clone()).definition());
worker.register_tool(ContextRecordingTool::new("record_c", contexts.clone()).definition());
engine.register_tool(ContextRecordingTool::new("record_a", contexts.clone()).definition());
engine.register_tool(ContextRecordingTool::new("record_b", contexts.clone()).definition());
engine.register_tool(ContextRecordingTool::new("record_c", contexts.clone()).definition());
let _ = worker.run("record contexts").await;
let _ = engine.run("record contexts").await;
let mut contexts = contexts.lock().unwrap().clone();
contexts.sort_by_key(|ctx| ctx.call_index);
@ -257,12 +257,12 @@ async fn test_tool_execution_context_batch_id_changes_between_batches() {
}),
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let contexts = Arc::new(Mutex::new(Vec::new()));
worker.register_tool(ContextRecordingTool::new("record", contexts.clone()).definition());
engine.register_tool(ContextRecordingTool::new("record", contexts.clone()).definition());
let _ = worker.run("record batches").await;
let _ = engine.run("record batches").await;
let contexts = contexts.lock().unwrap().clone();
assert_eq!(contexts.len(), 2);
@ -299,17 +299,17 @@ async fn test_tool_execution_context_for_skipped_and_synthetic_paths() {
}),
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let executed_contexts = Arc::new(Mutex::new(Vec::new()));
let pre_contexts = Arc::new(Mutex::new(Vec::new()));
let post_contexts = Arc::new(Mutex::new(Vec::new()));
worker
engine
.register_tool(ContextRecordingTool::new("record", executed_contexts.clone()).definition());
worker.register_tool(
engine.register_tool(
ContextRecordingTool::new("skip_tool", executed_contexts.clone()).definition(),
);
worker.register_tool(
engine.register_tool(
ContextRecordingTool::new("synthetic_tool", executed_contexts.clone()).definition(),
);
@ -341,12 +341,12 @@ async fn test_tool_execution_context_for_skipped_and_synthetic_paths() {
}
}
worker.set_interceptor(ContextPolicy {
engine.set_interceptor(ContextPolicy {
pre_contexts: pre_contexts.clone(),
post_contexts: post_contexts.clone(),
});
let _ = worker.run("record skipped and synthetic contexts").await;
let _ = engine.run("record skipped and synthetic contexts").await;
let mut pre_contexts = pre_contexts.lock().unwrap().clone();
pre_contexts.sort_by_key(|ctx| ctx.call_index);
@ -390,7 +390,7 @@ async fn test_before_tool_call_skip() {
];
let client = MockLlmClient::new(events);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let allowed_tool = SlowTool::new("allowed_tool", 10);
let blocked_tool = SlowTool::new("blocked_tool", 10);
@ -398,8 +398,8 @@ async fn test_before_tool_call_skip() {
let allowed_clone = allowed_tool.clone();
let blocked_clone = blocked_tool.clone();
worker.register_tool(allowed_tool.definition());
worker.register_tool(blocked_tool.definition());
engine.register_tool(allowed_tool.definition());
engine.register_tool(blocked_tool.definition());
// Policy to skip "blocked_tool"
struct BlockingPolicy;
@ -415,10 +415,10 @@ async fn test_before_tool_call_skip() {
}
}
worker.set_interceptor(BlockingPolicy);
engine.set_interceptor(BlockingPolicy);
// Mutable::run consumes self, returns (Locked, WorkerResult)
let _result = worker.run("Test hook").await;
// Mutable::run consumes self, returns (Locked, EngineResult)
let _result = engine.run("Test hook").await;
// allowed_tool is called, but blocked_tool is not
assert_eq!(
@ -458,7 +458,7 @@ async fn test_post_tool_call_modification() {
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
#[derive(Clone)]
struct SimpleTool;
@ -468,7 +468,7 @@ async fn test_post_tool_call_modification() {
async fn execute(
&self,
_: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
_ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
Ok("Original Result".to_string().into())
}
@ -483,7 +483,7 @@ async fn test_post_tool_call_modification() {
})
}
worker.register_tool(simple_tool_definition());
engine.register_tool(simple_tool_definition());
// Policy to modify results
struct ModifyingPolicy {
@ -500,14 +500,14 @@ async fn test_post_tool_call_modification() {
}
let modified_content = Arc::new(std::sync::Mutex::new(None));
worker.set_interceptor(ModifyingPolicy {
engine.set_interceptor(ModifyingPolicy {
modified_content: modified_content.clone(),
});
// Mutable::run consumes self, returns (Locked, WorkerResult)
let result = worker.run("Test modification").await;
// Mutable::run consumes self, returns (Locked, EngineResult)
let result = engine.run("Test modification").await;
assert!(result.is_ok(), "Worker should complete");
assert!(result.is_ok(), "Engine should complete");
// Verify hook was called and content was modified
let content = modified_content.lock().unwrap().clone();
@ -541,10 +541,10 @@ async fn test_before_tool_call_synthetic_result_committed() {
}),
],
]);
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
let blocked_tool = SlowTool::new("blocked_tool", 10);
let blocked_clone = blocked_tool.clone();
worker.register_tool(blocked_tool.definition());
engine.register_tool(blocked_tool.definition());
struct SyntheticPolicy;
@ -558,14 +558,14 @@ async fn test_before_tool_call_synthetic_result_committed() {
}
}
worker.set_interceptor(SyntheticPolicy);
engine.set_interceptor(SyntheticPolicy);
let result = worker.run("Test synthetic result").await.unwrap();
let result = engine.run("Test synthetic result").await.unwrap();
assert_eq!(blocked_clone.call_count(), 0, "Blocked tool should not run");
assert!(result.worker.history().iter().any(|item| matches!(
assert!(result.engine.history().iter().any(|item| matches!(
item,
llm_worker::Item::ToolResult {
llm_engine::Item::ToolResult {
call_id,
summary,
is_error: true,

View File

@ -1,6 +1,6 @@
//! Reasoning history round-trip 統合テスト
//!
//! Worker のストリーム → history append → 次リクエスト送出までの
//! Engine のストリーム → history append → 次リクエスト送出までの
//! ライフサイクルで `Item::Reasoning` が脱落せず保持されることを確認する。
//!
//! 検証点:
@ -14,9 +14,9 @@
mod common;
use common::MockLlmClient;
use llm_worker::Item;
use llm_worker::Worker;
use llm_worker::llm_client::event::{
use llm_engine::Engine;
use llm_engine::Item;
use llm_engine::llm_client::event::{
BlockMetadata, BlockStart, BlockStop, BlockType, Event, ReasoningBlockData, ResponseStatus,
StatusEvent,
};
@ -28,9 +28,9 @@ fn reasoning_block(text: impl Into<String>, data: ReasoningBlockData) -> Vec<Eve
block_type: BlockType::Thinking,
metadata: BlockMetadata::Thinking,
}),
Event::BlockDelta(llm_worker::llm_client::event::BlockDelta {
Event::BlockDelta(llm_engine::llm_client::event::BlockDelta {
index: 100,
delta: llm_worker::llm_client::event::DeltaContent::Thinking(text.into()),
delta: llm_engine::llm_client::event::DeltaContent::Thinking(text.into()),
}),
Event::BlockStop(BlockStop {
index: 100,
@ -42,7 +42,7 @@ fn reasoning_block(text: impl Into<String>, data: ReasoningBlockData) -> Vec<Eve
}
/// Anthropic 風: thinking ブロック → text → 終了 のシーケンス。
/// Worker history に Reasoning(signature 付き) → assistant_message が並ぶ。
/// Engine history に Reasoning(signature 付き) → assistant_message が並ぶ。
#[tokio::test]
async fn anthropic_thinking_round_trips_signature_into_history() {
let mut events = reasoning_block(
@ -64,11 +64,11 @@ async fn anthropic_thinking_round_trips_signature_into_history() {
}),
]);
let client = MockLlmClient::new(events);
let worker = Worker::new(client);
let out = worker.run("question?").await.expect("run ok");
let worker = out.worker;
let engine = Engine::new(client);
let out = engine.run("question?").await.expect("run ok");
let engine = out.engine;
let history = worker.history();
let history = engine.history();
// user / reasoning / assistant_message
assert_eq!(history.len(), 3, "history: {history:?}");
@ -108,11 +108,11 @@ async fn openai_reasoning_round_trips_encrypted_and_summary() {
}),
]);
let client = MockLlmClient::new(events);
let worker = Worker::new(client);
let out = worker.run("q").await.expect("run ok");
let worker = out.worker;
let engine = Engine::new(client);
let out = engine.run("q").await.expect("run ok");
let engine = out.engine;
let history = worker.history();
let history = engine.history();
match &history[1] {
Item::Reasoning {
text,
@ -154,19 +154,19 @@ async fn reasoning_precedes_text_in_assistant_burst() {
status: ResponseStatus::Completed,
}));
let client = MockLlmClient::new(events);
let worker = Worker::new(client);
let out = worker.run("q").await.expect("run ok");
let worker = out.worker;
let engine = Engine::new(client);
let out = engine.run("q").await.expect("run ok");
let engine = out.engine;
let history = worker.history();
let history = engine.history();
// user / reasoning(先頭) / assistant_message
assert!(matches!(history[1], Item::Reasoning { .. }));
assert_eq!(history[2].as_text(), Some("intermediate"));
}
/// resume シナリオ: history.json 由来の Item::Reasoning(signature) を Worker
/// resume シナリオ: history.json 由来の Item::Reasoning(signature) を Engine
/// 注入して run しても、次の outgoing request の `Request::items` にそのまま
/// 載って LLM へ渡る(worker は items を改変しない契約)。
/// 載って LLM へ渡る(engine は items を改変しない契約)。
#[tokio::test]
async fn injected_reasoning_survives_into_outgoing_request() {
use async_trait::async_trait;
@ -174,7 +174,7 @@ async fn injected_reasoning_survives_into_outgoing_request() {
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use llm_worker::llm_client::{ClientError, LlmClient, Request};
use llm_engine::llm_client::{ClientError, LlmClient, Request};
/// Request を 1 度だけキャプチャして空ストリームを返す client。
#[derive(Clone)]
@ -206,15 +206,15 @@ async fn injected_reasoning_survives_into_outgoing_request() {
captured: captured.clone(),
};
let mut worker = Worker::new(client);
let mut engine = Engine::new(client);
// resume: 既存 history を流し込む
worker.set_history(vec![
engine.set_history(vec![
Item::user_message("prior question"),
Item::reasoning("prior thinking").with_signature("SIG-PRIOR"),
Item::assistant_message("prior answer"),
]);
let _ = worker.run("follow up").await.expect("run ok");
let _ = engine.run("follow up").await.expect("run ok");
let req = captured
.lock()

View File

@ -9,8 +9,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use schemars;
use serde;
use llm_worker::ToolExecutionContext;
use llm_worker_macros::tool_registry;
use llm_engine::ToolExecutionContext;
use llm_engine_macros::tool_registry;
// =============================================================================
// Test: Basic Tool Generation

View File

@ -1,17 +1,17 @@
//! HTTP transport の単発 request / error classification テスト。
//!
//! Retry/backoff は Worker の lifecycle 管理に属するため、transport は 1 回だけ
//! Retry/backoff は Engine の lifecycle 管理に属するため、transport は 1 回だけ
//! request を送り、HTTP status / Retry-After を `ClientError` に載せて返す。
use futures::StreamExt;
use llm_worker::llm_client::LlmClient;
use llm_worker::llm_client::auth::AuthRequirement;
use llm_worker::llm_client::capability::ModelCapability;
use llm_worker::llm_client::error::ClientError;
use llm_worker::llm_client::event::Event;
use llm_worker::llm_client::scheme::Scheme;
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_worker::llm_client::types::Request;
use llm_engine::llm_client::LlmClient;
use llm_engine::llm_client::auth::AuthRequirement;
use llm_engine::llm_client::capability::ModelCapability;
use llm_engine::llm_client::error::ClientError;
use llm_engine::llm_client::event::Event;
use llm_engine::llm_client::scheme::Scheme;
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::llm_client::types::Request;
use serde_json::Value;
use std::time::Duration;
use wiremock::matchers::{method, path};

View File

@ -1,9 +1,9 @@
use llm_worker::Worker;
use llm_worker::llm_client::capability::{
use llm_engine::Engine;
use llm_engine::llm_client::capability::{
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
};
use llm_worker::llm_client::scheme::anthropic::AnthropicScheme;
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::llm_client::scheme::anthropic::AnthropicScheme;
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use std::sync::Arc;
fn main() {
@ -21,8 +21,8 @@ fn main() {
ResolvedAuth::None,
cap,
);
let worker = Worker::new(client);
let mut locked = worker.lock();
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused"));
let engine = Engine::new(client);
let mut locked = engine.lock();
let def: llm_engine::tool::ToolDefinition = Arc::new(|| panic!("unused"));
let _ = locked.register_tool(def);
}

View File

@ -1,8 +1,8 @@
error[E0599]: no method named `register_tool` found for struct `Worker<HttpTransport<AnthropicScheme>, Locked>` in the current scope
error[E0599]: no method named `register_tool` found for struct `Engine<HttpTransport<AnthropicScheme>, Locked>` in the current scope
--> tests/ui/locked_register_tool.rs:27:20
|
27 | let _ = locked.register_tool(def);
| ^^^^^^^^^^^^^ method not found in `Worker<HttpTransport<AnthropicScheme>, Locked>`
| ^^^^^^^^^^^^^ method not found in `Engine<HttpTransport<AnthropicScheme>, Locked>`
|
= note: the method was found for
- `Worker<C>`
- `Engine<C>`

View File

@ -1,9 +1,9 @@
use llm_worker::Worker;
use llm_worker::llm_client::capability::{
use llm_engine::Engine;
use llm_engine::llm_client::capability::{
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
};
use llm_worker::llm_client::scheme::anthropic::AnthropicScheme;
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_engine::llm_client::scheme::anthropic::AnthropicScheme;
use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use std::sync::Arc;
fn main() {
@ -21,8 +21,8 @@ fn main() {
ResolvedAuth::None,
cap,
);
let worker = Worker::new(client);
let handle = worker.tool_server_handle();
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused"));
let engine = Engine::new(client);
let handle = engine.tool_server_handle();
let def: llm_engine::tool::ToolDefinition = Arc::new(|| panic!("unused"));
let _ = handle.register_tool(def);
}

View File

@ -6,5 +6,5 @@ error[E0624]: method `register_tool` is private
|
::: src/tool_server.rs
|
| pub(crate) fn register_tool(&self, factory: WorkerToolDefinition) {
| pub(crate) fn register_tool(&self, factory: EngineToolDefinition) {
| ----------------------------------------------------------------- private method defined here

View File

@ -6,7 +6,7 @@ license.workspace = true
[dependencies]
arc-swap = "1"
llm-worker = { workspace = true }
llm-engine = { workspace = true }
mlua = { version = "0.11.4", features = ["lua54", "vendored", "serialize"] }
protocol = { workspace = true }
serde = { workspace = true, features = ["derive"] }

View File

@ -14,9 +14,9 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
// `ModelCapability` は `llm-worker` 側に定義される runtime 構造だが、
// `ModelCapability` は `llm-engine` 側に定義される runtime 構造だが、
// マニフェストで任意に override できるよう型だけ再エクスポートする。
pub use llm_worker::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort};
pub use llm_engine::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort};
/// Pod マニフェストの `[model]` セクション。
///

View File

@ -225,7 +225,7 @@ async fn permission_denial_style_shutdown_sends_no_tools_call() {
let mut client = McpStdioClient::connect(mock_server("tools-call-forbidden"), tight_limits())
.await
.expect("connect");
// This mirrors Worker pre-tool-call denial: the ordinary Tool execution body
// This mirrors Engine pre-tool-call denial: the ordinary Tool execution body
// is never entered, so the MCP server sees lifecycle shutdown but no call.
client.shutdown().await.expect("shutdown");
}

View File

@ -9,7 +9,7 @@ async-trait = { workspace = true }
chrono = { version = "0.4", features = ["serde"] }
libc = { workspace = true }
lint-common = { workspace = true }
llm-worker = { workspace = true }
llm-engine = { workspace = true }
manifest = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }

View File

@ -17,7 +17,7 @@ Owns:
Does not own:
- authoritative project records (`.yoi/tickets/`, git history)
- normal Pod turn orchestration (`llm-worker`)
- normal Pod turn orchestration (`llm-engine`)
- product CLI command shape (`yoi`)
- curated workflow definitions (`workflow`)

View File

@ -1,7 +1,7 @@
//! consolidation sub-Worker への最初のユーザー入力を組み立てる。
//! consolidation sub-Engine への最初のユーザー入力を組み立てる。
//!
//! extract (`extract::build_extract_input`) と同じ方針で、固定 schema の
//! markdown セクション列にしてサブWorker に渡す。`docs/plan/memory.md`
//! markdown セクション列にしてサブEngine に渡す。`docs/plan/memory.md`
//! §Consolidation 入力 / §整理材料 の項目に従い:
//!
//! 1. consumed staging エントリ全文(`source` 込み)
@ -19,7 +19,7 @@ use crate::consolidate::tidy::TidyHints;
use crate::usage::UsageReport;
use crate::workspace::{RecordKind, WorkspaceLayout};
/// consolidation sub-Worker の最初の user 入力。
/// consolidation sub-Engine の最初の user 入力。
pub fn build_consolidate_input(
layout: &WorkspaceLayout,
staging: &[StagingEntry],

Some files were not shown because too many files have changed in this diff Show More