merge: llm engine rename

This commit is contained in:
Keisuke Hirata 2026-06-25 23:10:27 +09:00
commit 20cfcfaca3
No known key found for this signature in database
205 changed files with 1416 additions and 1127 deletions

View File

@ -0,0 +1 @@
{"id":"orch-plan-20260625-132518-1","ticket_id":"00001KVZD10ED","kind":"accepted_plan","accepted_plan":{"summary":"Ticket `00001KVZD10ED` は implementation_ready。専用 worktree `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` と branch `work/00001KVZD10ED-llm-engine-rename` で、`llm-worker` / `llm-worker-macros` を `llm-engine` / `llm-engine-macros` に rename し、主要 public turn-engine 型を `Worker` から `Engine` 系へ rename する。責務移動や worker-runtime 実装、互換 alias は non-goal。","branch":"work/00001KVZD10ED-llm-engine-rename","worktree":"/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename","role_plan":"Orchestrator: accept/routing, worktree creation, final integration/validation/cleanup. Coder: repository-wide crate/type rename in dedicated child worktree. Reviewer: read-only review focusing on mechanical rename completeness, no compatibility alias, no behavior/authority movement, and validation evidence."},"author":"yoi-orchestrator","at":"2026-06-25T13:25:18Z"}

View File

@ -1,8 +1,8 @@
--- ---
title: 'llm-worker crateをllm-engineへ改名する' title: 'llm-worker crateをllm-engineへ改名する'
state: 'queued' state: 'done'
created_at: '2026-06-25T12:45:38Z' created_at: '2026-06-25T12:45:38Z'
updated_at: '2026-06-25T13:24:26Z' updated_at: '2026-06-25T13:57:29Z'
assignee: null assignee: null
queued_by: 'workspace-panel' queued_by: 'workspace-panel'
queued_at: '2026-06-25T13:24:26Z' queued_at: '2026-06-25T13:24:26Z'

View File

@ -30,4 +30,288 @@ Marked ready by `yoi ticket state`.
Ticket を `workspace-panel` が queued にしました。 Ticket を `workspace-panel` が queued にしました。
---
<!-- event: decision author: yoi-orchestrator at: 2026-06-25T13:26:00Z -->
## Decision
Routing decision: implementation_ready
Reason:
- Dashboard Queue により人間が Orchestrator routing を許可した queued Ticket として確認した。
- Ticket body は `llm-worker` / `llm-worker-macros` crate rename、Rust import path rename、major public type rename (`Worker` -> `Engine`, config/result/error names)、repo-wide references、validation を具体的に列挙している。
- `TicketRelationQuery` は blocking relation 0 件、`TicketOrchestrationPlanQuery` は routing 前 plan 0 件だった。
- bounded context check で repository-wide `llm-worker` / `llm_worker` references を確認し、主な変更 surface は `crates/llm-worker`, `crates/llm-worker-macros`, workspace/dependency metadata, `pod`/`manifest` imports, docs/tests/examples/Nix/Cargo.lock である。これは大きめだが mechanical rename task として明確で、責務移動や WorkerRuntime 実装は non-goal として分離されている。
- risk は API/naming churn だが、受け入れ条件と validation が明確で、Coder の bounded implementation latitude に収まる。planning return が必要な未決定事項は見つからない。
Evidence checked:
- Ticket body / thread: `item.md`, `thread.md`。thread は create、planning->ready、ready->queued のみで未解決 blocker は記録されていない。
- Relations / orchestration plan: relation 0 件、routing 前 plan 0 件。accepted plan `orch-plan-20260625-132518-1` を記録済み。
- Code context: `git grep``crates/llm-worker`, `crates/llm-worker-macros`, `llm_worker`, `llm_worker_macros`, `Worker` imports/examples/tests/docs references を確認。
- Workspace state: `/home/hare/Projects/yoi/.worktree/orchestration` は clean。queued Ticket はこの 1 件、inprogress Ticket は 0 件。
IntentPacket:
Intent:
- LLM turn-processing crate を `llm-worker` から `llm-engine` へ rename し、public turn-engine主体型を `Worker` から `Engine` 系へ rename することで、今後の Runtime-scoped Worker concept と衝突しない package/API naming に整理する。
Binding decisions / invariants:
- `llm-engine` は LLM turn engine。Runtime / Worker identity / process lifecycle / socket protocol / session file authority は持たない。
- 責務移動は最小限。provider request/stream handling、tool-call loop、reasoning/usage/retry/continuation/history/callback semantics は変えない。
- `crates/llm-worker` / `crates/llm-worker-macros` は残さない。
- `llm-worker` / `llm_worker` / `llm_worker_macros` compatibility alias は作らない。
- `pod` crate and dependents should compile against `llm_engine::Engine` and renamed config/result/error types.
- New `worker-runtime` crate or Pod->Worker migration is non-goal.
Requirements / acceptance criteria:
- `crates/llm-engine` and `crates/llm-engine-macros` exist; old directories gone.
- Cargo package/dependency names and Rust import paths use `llm-engine` / `llm_engine` and `llm-engine-macros` / `llm_engine_macros`.
- Public turn-engine type is `llm_engine::Engine`, not `Worker`; config/result/error names no longer use `Worker` for the turn engine concept.
- Repository-wide old references are gone except intentional historical/migration notes if any.
- `pod`, `manifest`, `yoi`, examples/tests/docs/Nix/Cargo.lock update consistently.
- Existing runtime behavior is unchanged except names.
- Validation target includes `cargo test -p llm-engine`, `cargo test -p pod`, `cargo check -p yoi`, `git diff --check`, `nix build .#yoi --no-link`.
Implementation latitude:
- Result/output type exact names may follow Ticket guidance (`EngineRunResult`, `EngineRunOutput`, etc.) as long as public API no longer presents LLM turn engine as Worker.
- Internal file names may be renamed for clarity (`worker.rs` -> `engine.rs`) if practical; otherwise public module/API must be clean.
- Historical ticket ids or changelog-like references may remain only if clearly intentional and not active API/docs guidance.
Escalate if:
- Rename requires behavior changes to provider streaming/tool-loop/history semantics.
- A compatibility alias appears necessary to make internal crates compile.
- Existing macro/test generated names cannot be renamed without broader procedural macro redesign.
- `cargo test -p pod` failure is not the known prompt guidance snapshot caveat but a rename regression.
Validation:
- `cargo test -p llm-engine`
- `cargo test -p pod`
- `cargo check -p yoi`
- `git diff --check`
- `nix build .#yoi --no-link`
- Add focused grep evidence for old names.
Current code map:
- Primary: `crates/llm-worker`, `crates/llm-worker-macros`, workspace `Cargo.toml`, `Cargo.lock`, `package.nix`
- Secondary: dependent imports in `crates/pod`, `crates/manifest`, examples/tests/docs/resources as found by grep。
- Avoid: worker-runtime implementation, Pod runtime migration, session/socket authority changes, provider/tool-loop behavior changes。
Critical risks / reviewer focus:
- stale old crate/import/type references。
- hidden compatibility alias left behind。
- behavior changes mixed into mechanical rename。
- `Worker` remaining as public turn-engine主体名 where it conflicts with Runtime Worker concept。
- Nix/Cargo lock/hash updates missed。
Next action:
- `queued -> inprogress` を記録してから worktree-workflow で `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` を作成し、multi-agent-workflow で Coder/Reviewer sibling loop に進める。
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-25T13:26:35Z from: queued to: inprogress reason: orchestrator_acceptance_unblocked field: state -->
## State changed
Orchestrator acceptance: queued -> inprogress
- 直前確認で `TicketShow` は state `queued`、blocking relation は 0 件、accepted plan `orch-plan-20260625-132518-1` を確認した。
- routing decision と IntentPacket は Ticket thread に記録済み。
- orchestration worktree は clean。この Ticket 用 worktree/branch はまだ作成していない。
- これ以降に worktree-workflow で `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` を作成し、multi-agent-workflow に接続する。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-25T13:28:20Z -->
## Implementation report
Implementation orchestration update:
- worktree-workflow に従い、implementation worktree `/home/hare/Projects/yoi/.worktree/00001KVZD10ED-llm-engine-rename` と branch `work/00001KVZD10ED-llm-engine-rename` を作成した。
- child worktree では tracked `.yoi` project records を見える状態にし、`.yoi/memory`、local/runtime/log/lock/secret-like `.yoi` paths を sparse-checkout で除外する方針にした。
- Coder Pod `yoi-coder-00001KVZD10ED-llm-engine` を sibling として起動した。write scope は child worktree に限定し、runtime workspace root `/home/hare/Projects/yoi` は startup 要件を満たすため non-recursive read のみにした。
- Coder には root/original workspace と orchestration worktree で read/write/validation/git operation を行わないよう明示した。
---
<!-- event: implementation_report author: yoi-coder-00001KVZD10ED-llm-engine at: 2026-06-25T13:47:26Z -->
## Implementation report
Implementation report:
対象 Ticket:
- `00001KVZD10ED``llm-worker crateをllm-engineへ改名する`
実装概要:
- `crates/llm-worker``crates/llm-engine`
- `crates/llm-worker-macros``crates/llm-engine-macros`
- Cargo package / workspace dependency 名を `llm-engine` / `llm-engine-macros` へ更新。
- Rust import path を `llm_engine` / `llm_engine_macros` へ更新。
- turn-engine API を rename。
- `Worker``Engine`
- `WorkerConfig``EngineConfig`
- `WorkerError``EngineError`
- `WorkerResult``EngineResult`
- `WorkerState``EngineState`
- `RunOutput``EngineRunOutput`
- `pod` 側の engine 保持 API / error / alert source 等も rename fallout として更新。
- docs / tests / examples / Nix / `Cargo.lock` / `package.nix` hash を更新。
- 旧 crate directory は削除済みで、互換 alias は追加していない。
Commit:
- `292fc4ea refactor: rename llm worker crate to engine`
Validation:
- `cargo test -p llm-engine`: success
- `cargo test -p pod`: success
- 既知 caveat の prompt guidance snapshot failure は今回発生しなかった。
- `cargo check -p yoi`: success
- `git diff --check`: success
- `nix build .#yoi --no-link`: success
Grep evidence:
- `.yoi/tickets/**``docs/report/**` を除外した active tree で以下の旧 active references は 0 件。
- `llm-worker`
- `llm_worker`
- `llm-worker-macros`
- `llm_worker_macros`
- `crates/llm-engine` / `crates/llm-engine-macros` 内の旧 public Worker API 名も 0 件。
- `\bWorker\b`
- `WorkerConfig`
- `WorkerError`
- `WorkerResult`
- `WorkerState`
- historical `.yoi/tickets` / `docs/report` 内には旧語が残る。count: 786。これは Ticket 本文・過去 report 類の履歴文脈として意図的に残している。
Residual risks / notes:
- 互換 alias は入れていないため、外部未追跡コードが `llm-worker` / `llm_worker::Worker` を直接参照している場合は破壊的変更になる。ただし Ticket の binding invariant 通り。
- merge / Ticket close / worktree cleanup は未実施。
---
<!-- event: review author: yoi-reviewer-00001KVZD10ED-llm-engine at: 2026-06-25T13:54:15Z status: approve -->
## Review: approve
Review result: approve
確認結果、Ticket `00001KVZD10ED` の IntentPacket / acceptance criteria に対してブロッカーは見つかりませんでした。
Read-only 確認:
- 対象 worktree / branch / commit:
- `work/00001KVZD10ED-llm-engine-rename`
- HEAD `292fc4ea`
- base `22598710` は ancestor。
- Ticket 記録:
- `thread.md` の IntentPacket / acceptance criteria / reviewer focus を確認。
- 要件は `llm-worker``llm-engine`、public turn-engine API を `Worker` 系から `Engine` 系へ rename、互換 alias なし、責務移動なし。
- crate / directory:
- `crates/llm-engine`, `crates/llm-engine-macros` が存在。
- `crates/llm-worker`, `crates/llm-worker-macros` は存在しない。
- Cargo / Nix:
- root `Cargo.toml` の members/default-members/workspace deps が `llm-engine` / `llm-engine-macros`
- `crates/pod/Cargo.toml` 等の依存も `llm-engine`
- `Cargo.lock` は旧 package 名から新 package 名へ置換済み。
- `package.nix``cargoHash` 更新を確認。
- public API:
- `crates/llm-engine/src/lib.rs``Engine`, `EngineConfig`, `EngineError`, `EngineResult`, `EngineRunOutput` を export。
- `WorkerConfig` / `WorkerError` / `WorkerResult` / `WorkerState` / bare `RunOutput` は active tree で検出されず。
- `llm-engine` / `llm-engine-macros` 内に `pub type Worker` 等の互換 alias は見つからず。
- stale reference:
- `.yoi/tickets/**``docs/report/**` を除いた tracked active tree で `llm-worker`, `llm_worker`, `llm-worker-macros`, `llm_worker_macros` は検出されず。
- behavior / responsibility risk:
- `crates/llm-worker/src/worker.rs``crates/llm-engine/src/engine.rs` は正規化比較で import order / formatting 程度の差分に収まっていることを確認。
- `crates/llm-engine/README.md` に Pod names / sockets / process lifecycle / scope delegation / durable Pod state を持たない境界が明記されており、Runtime/Worker identity や socket/session authority の責務移動は見当たらない。
- whitespace:
- `git diff --check 22598710..HEAD`: success。
未再実行:
- `cargo test -p llm-engine`
- `cargo test -p pod`
- `cargo check -p yoi`
- `nix build .#yoi --no-link`
上記は reviewer の read-only/no-write 条件を優先して再実行していない。coder 報告では成功済み。
Non-blocking note:
- `pod` 側には local variable や既存 runtime/manifest 文脈としての `worker` 語が残っているが、今回の acceptance 対象である old crate/import/API 名や `llm_engine::Worker` 互換 alias ではないため問題なし。
---
<!-- event: implementation_report author: yoi-orchestrator at: 2026-06-25T13:57:16Z -->
## Implementation report
Integration outcome:
- Coder `yoi-coder-00001KVZD10ED-llm-engine` が implementation branch `work/00001KVZD10ED-llm-engine-rename` に実装を commit した。
- implementation commit: `292fc4ea refactor: rename llm worker crate to engine`
- Reviewer `yoi-reviewer-00001KVZD10ED-llm-engine` は read-only review で `approve`。old crate directories removal、Cargo/Nix consistency、public Engine API、old active references なし、compatibility alias なし、behavior/responsibility movement なしを確認した。
- Orchestrator worktree `/home/hare/Projects/yoi/.worktree/orchestration` で branch `work/00001KVZD10ED-llm-engine-rename` を merge 済み。
- merge commit: `254ecccb merge: 00001KVZD10ED llm engine rename`
Implemented behavior:
- `crates/llm-worker` -> `crates/llm-engine`
- `crates/llm-worker-macros` -> `crates/llm-engine-macros`
- Cargo package/dependency/import path を `llm-engine` / `llm_engine`, `llm-engine-macros` / `llm_engine_macros` に更新。
- Public turn-engine API を Engine 系へ rename。
- `Worker` -> `Engine`
- `WorkerConfig` -> `EngineConfig`
- `WorkerError` -> `EngineError`
- `WorkerResult` -> `EngineResult`
- `WorkerState` -> `EngineState`
- `RunOutput` -> `EngineRunOutput`
- `pod`, `manifest`, docs, examples, tests, Nix, `Cargo.lock`, `package.nix` hash を更新。
- old crate directories は削除済み。compatibility alias は追加していない。
Validation in Orchestrator worktree:
- `cargo test -p llm-engine`: success
- `cargo test -p pod`: success
- `cargo check -p yoi`: success
- active old reference grep excluding `.yoi/tickets/**` and `docs/report/**`: success, none found for:
- `llm-worker`
- `llm_worker`
- `llm-worker-macros`
- `llm_worker_macros`
- `crates/llm-engine` / `crates/llm-engine-macros` old public Worker API grep: success, none found for:
- `\bWorker\b`
- `WorkerConfig`
- `WorkerError`
- `WorkerResult`
- `WorkerState`
- `git diff --check`: success
- `nix build .#yoi --no-link`: success
Notes:
- Historical `.yoi/tickets/**` and `docs/report/**` references to old names remain as durable history/report context and are not active API/docs guidance.
- Unlike recent Plugin full `cargo test -p pod` runs, this validation passed `cargo test -p pod` fully.
Next action:
- Mark Ticket done after this integration/validation evidence.
- Then stop related child Pods and remove only the child implementation worktree/branch.
---
<!-- event: state_changed author: yoi-orchestrator at: 2026-06-25T13:57:29Z from: inprogress to: done reason: merged_validated field: state -->
## State changed
Implementation is merged into the orchestration branch and validated.
Evidence:
- merge commit: `254ecccb merge: 00001KVZD10ED llm engine rename`
- reviewer result: approve
- validation in `/home/hare/Projects/yoi/.worktree/orchestration` succeeded:
- `cargo test -p llm-engine`
- `cargo test -p pod`
- `cargo check -p yoi`
- active old reference grep excluding `.yoi/tickets/**` and `docs/report/**`
- old public Worker API grep in `crates/llm-engine` / `crates/llm-engine-macros`
- `git diff --check`
- `nix build .#yoi --no-link`
Closure is not performed here; this state records implementation completion after merge/validation.
--- ---

22
Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,17 @@
# llm-worker # llm-engine
## Role ## 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 ## Boundaries
Owns: Owns:
- Worker history mutation and append contracts - Engine history mutation and append contracts
- tool-call loop semantics - tool-call loop semantics
- pre-stream retry and stream-started continuation policy - 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 - provider-neutral events/callbacks/interceptors
Does not own: Does not own:
@ -23,7 +23,7 @@ Does not own:
## Design notes ## 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 ## 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 │ │ 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 | | `state` | Type-state (Mutable/CacheLocked) | R2 |
| `hook` | Hook trait、10フックポイント | R3, R4 | | `hook` | Hook trait、10フックポイント | R3, R4 |
| `tool` / `tool_server` | ツール定義・登録・実行 | R3 | | `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 } → build_request() → Request { items, tools, config }
→ Scheme.build_request() → プロバイダ固有JSON → Scheme.build_request() → プロバイダ固有JSON
→ Provider.stream() → HTTP POST → Provider.stream() → HTTP POST
@ -55,7 +55,7 @@ HTTP SSE bytes
→ Scheme.parse_event() → Event (統一型) → Scheme.parse_event() → Event (統一型)
→ Timeline.dispatch() → Handler.on_event() → Timeline.dispatch() → Handler.on_event()
→ TextBlockCollector / ToolCallCollector → TextBlockCollector / ToolCallCollector
Worker: 履歴に追加、ツール実行判定 Engine: 履歴に追加、ツール実行判定
``` ```
## 内部型 ## 内部型

View File

@ -1,4 +1,4 @@
# llm-worker 要件 # llm-engine 要件
## 前提 ## 前提
@ -12,23 +12,23 @@ c. ツール・フックの基本的なスキーマ自動化を提供する
メッセージの送信と生成のResume、一時停止/再開。 メッセージの送信と生成のResume、一時停止/再開。
- `Worker::run()` でターンを開始 - `Engine::run()` でターンを開始
- フックから `Pause` を返してターンを一時停止 - フックから `Pause` を返してターンを一時停止
- `Worker::resume()` でユーザーメッセージを追加せず継続 - `Engine::resume()` でユーザーメッセージを追加せず継続
- AIは中断を認識せず、継続として処理する - AIは中断を認識せず、継続として処理する
**実装**: `worker.rs` — `resume()`, `get_pending_tool_calls()`, `WorkerResult::Paused` **実装**: `engine.rs` — `resume()`, `get_pending_tool_calls()`, `EngineResult::Paused`
### R2: 暗黙的KVキャッシュ保証 ### R2: 暗黙的KVキャッシュ保証
キャッシュを破壊しうる操作を明示的にブロックせずとも、いつの間にかキャッシュ破壊してた状態にはしたくない。 キャッシュを破壊しうる操作を明示的にブロックせずとも、いつの間にかキャッシュ破壊してた状態にはしたくない。
- Type-stateパターン`Mutable` / `CacheLocked`)でコンパイル時に保証 - Type-stateパターン`Mutable` / `CacheLocked`)でコンパイル時に保証
- `Worker::lock()` でCacheLocked状態に遷移 - `Engine::lock()` でCacheLocked状態に遷移
- CacheLocked状態ではシステムプロンプトや履歴の変更APIが型レベルで利用不可 - CacheLocked状態ではシステムプロンプトや履歴の変更APIが型レベルで利用不可
- `locked_prefix_len` でプレフィックスの不変性を追跡 - `locked_prefix_len` でプレフィックスの不変性を追跡
**実装**: `state.rs` (sealed trait), `worker.rs` (state-specific impl blocks) **実装**: `state.rs` (sealed trait), `engine.rs` (state-specific impl blocks)
### R3: ツール・フックスキーマ自動化 ### R3: ツール・フックスキーマ自動化
@ -36,13 +36,13 @@ c. ツール・フックの基本的なスキーマ自動化を提供する
- `#[tool_registry]` マクロでツールサーバーを自動構成 - `#[tool_registry]` マクロでツールサーバーを自動構成
- `Hook` traitで10種のフックポイント - `Hook` traitで10種のフックポイント
**実装**: `llm-worker-macros/`, `tool.rs`, `tool_server.rs`, `hook.rs` **実装**: `llm-engine-macros/`, `tool.rs`, `tool_server.rs`, `hook.rs`
### R4: フックは上層の関心事 ### R4: フックは上層の関心事
フックはLLMクライアント層ではなく、Worker(オーケストレーション)層に配置する。 フックはLLMクライアント層ではなく、Engine(オーケストレーション)層に配置する。
- LLMクライアント (`llm_client/`) はストリーミングとプロトコルのみ - 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 //! Example of cancelling from another thread during streaming
use llm_worker::llm_client::scheme::{Scheme, anthropic::AnthropicScheme}; use llm_engine::llm_client::scheme::{Scheme, anthropic::AnthropicScheme};
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth}; use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use llm_worker::{Worker, WorkerResult}; use llm_engine::{Engine, EngineResult};
use std::time::Duration; use std::time::Duration;
#[tokio::main] #[tokio::main]
@ -28,29 +28,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cap = scheme.default_capability(); let cap = scheme.default_capability();
let base_url = scheme.default_base_url().to_string(); let base_url = scheme.default_base_url().to_string();
let client = HttpTransport::new(scheme, model, base_url, ResolvedAuth::ApiKey(api_key), cap); 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"); println!("💡 Will cancel after 2 seconds\n");
// Get cancel sender before run (Mutable state) // Get cancel sender before run (Mutable state)
let cancel_tx = worker.cancel_sender(); let cancel_tx = engine.cancel_sender();
// Task: Cancel after 2 seconds // Task: Cancel after 2 seconds
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await; tokio::time::sleep(Duration::from_secs(2)).await;
println!("\n🛑 Cancelling worker..."); println!("\n🛑 Cancelling engine...");
let _ = cancel_tx.send(()).await; let _ = cancel_tx.send(()).await;
}); });
println!("📡 Sending request to LLM..."); 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 { Ok(out) => match out.result {
WorkerResult::Finished => println!("✅ Task completed normally"), EngineResult::Finished => println!("✅ Task completed normally"),
WorkerResult::Paused => println!("⏸️ Task paused"), EngineResult::Paused => println!("⏸️ Task paused"),
WorkerResult::LimitReached => println!("🔒 Turn limit reached"), EngineResult::LimitReached => println!("🔒 Turn limit reached"),
WorkerResult::Yielded => println!("↩️ Task yielded"), EngineResult::Yielded => println!("↩️ Task yielded"),
}, },
Err(e) => { Err(e) => {
println!("❌ Task error: {}", 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). //! A CLI application for interacting with multiple LLM providers (Anthropic, Gemini, OpenAI, Ollama).
//! Demonstrates tool registration and execution, and streaming response display. //! Demonstrates tool registration and execution, and streaming response display.
@ -12,22 +12,22 @@
//! echo "OPENAI_API_KEY=your-api-key" >> .env //! echo "OPENAI_API_KEY=your-api-key" >> .env
//! //!
//! # Anthropic (default) //! # Anthropic (default)
//! cargo run --example worker_cli //! cargo run --example engine_cli
//! //!
//! # Gemini //! # Gemini
//! cargo run --example worker_cli -- --provider gemini //! cargo run --example engine_cli -- --provider gemini
//! //!
//! # OpenAI //! # OpenAI
//! cargo run --example worker_cli -- --provider openai --model gpt-4o //! cargo run --example engine_cli -- --provider openai --model gpt-4o
//! //!
//! # Ollama (local) //! # Ollama (local)
//! cargo run --example worker_cli -- --provider ollama --model llama3.2 //! cargo run --example engine_cli -- --provider ollama --model llama3.2
//! //!
//! # With options //! # 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 //! # Show help
//! cargo run --example worker_cli -- --help //! cargo run --example engine_cli -- --help
//! ``` //! ```
use std::collections::HashMap; use std::collections::HashMap;
@ -39,8 +39,8 @@ use tracing::info;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use llm_worker::{ use llm_engine::{
Worker, Engine,
interceptor::{Interceptor, PostToolAction, ToolResultInfo}, interceptor::{Interceptor, PostToolAction, ToolResultInfo},
llm_client::{ llm_client::{
LlmClient, LlmClient,
@ -52,7 +52,7 @@ use llm_worker::{
}, },
timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind}, timeline::{Handler, TextBlockEvent, TextBlockKind, ToolUseBlockEvent, ToolUseBlockKind},
}; };
use llm_worker_macros::tool_registry; use llm_engine_macros::tool_registry;
// Required imports for macro expansion // Required imports for macro expansion
use schemars; use schemars;
@ -114,8 +114,8 @@ impl Provider {
/// Interactive CLI client supporting multiple LLM providers /// Interactive CLI client supporting multiple LLM providers
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "worker-cli")] #[command(name = "engine-cli")]
#[command(about = "Interactive CLI client for multiple LLM providers using Worker")] #[command(about = "Interactive CLI client for multiple LLM providers using Engine")]
#[command(version)] #[command(version)]
struct Args { struct Args {
/// Provider to use /// Provider to use
@ -393,7 +393,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
// Initialize logging // 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 // 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")); 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!( info!(
provider = ?args.provider, provider = ?args.provider,
model = ?args.model, model = ?args.model,
"Starting worker CLI" "Starting engine CLI"
); );
// Interactive mode or one-shot mode // 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()); .unwrap_or_else(|| args.provider.default_model().to_string());
if is_interactive { 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; let border_len = title.len() + 6;
println!("{}", "".repeat(border_len)); println!("{}", "".repeat(border_len));
println!("{}", title); println!("{}", title);
@ -453,34 +453,34 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}; };
// Create Worker // Create Engine
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
let tool_call_names = Arc::new(Mutex::new(HashMap::new())); let tool_call_names = Arc::new(Mutex::new(HashMap::new()));
// Set system prompt // Set system prompt
if let Some(ref system_prompt) = args.system { 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) // Register tools (unless --no-tools)
if !args.no_tools { if !args.no_tools {
let app = AppContext; let app = AppContext;
worker.register_tool(app.get_current_time_definition()); engine.register_tool(app.get_current_time_definition());
worker.register_tool(app.calculate_definition()); engine.register_tool(app.calculate_definition());
} }
// Register streaming display handlers // Register streaming display handlers
worker engine
.timeline_mut() .timeline_mut()
.on_text_block(StreamingPrinter::new()) .on_text_block(StreamingPrinter::new())
.on_tool_use_block(ToolCallPrinter::new(tool_call_names.clone())); .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 // One-shot mode
if let Some(prompt) = args.prompt { if let Some(prompt) = args.prompt {
match worker.run(&prompt).await { match engine.run(&prompt).await {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
eprintln!("\n❌ Error: {}", e); eprintln!("\n❌ Error: {}", e);
@ -504,8 +504,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(()); return Ok(());
} }
let mut locked = match worker.run(first_input).await { let mut locked = match engine.run(first_input).await {
Ok(out) => out.worker, Ok(out) => out.engine,
Err(e) => { Err(e) => {
eprintln!("\n❌ Error: {}", e); eprintln!("\n❌ Error: {}", e);
return Ok(()); return Ok(());

View File

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

View File

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

View File

@ -2,7 +2,7 @@
//! //!
//! Defines requests and output file names for each scenario //! 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 /// Test scenario
pub struct TestScenario { pub struct TestScenario {

View File

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

View File

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

View File

@ -32,7 +32,7 @@ pub trait Kind {
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```ignore
/// use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind}; /// use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind};
/// ///
/// struct TextCollector { /// struct TextCollector {
/// texts: Vec<String>, /// 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 //! Defines the [`Interceptor`] trait that upper layers (e.g. Pod) implement
//! to inject orchestration decisions (approval, skip, pause, abort) //! 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. //! higher-level concepts.
use std::sync::Arc; use std::sync::Arc;
@ -36,13 +36,13 @@ pub enum PromptAction {
pub enum PreRequestAction { pub enum PreRequestAction {
/// Proceed normally. /// Proceed normally.
Continue, 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 /// 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 /// 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>), 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 /// This is for host-mediated pre-request appends that must be visible to
/// usage accounting and compaction checks before the current LLM request is /// usage accounting and compaction checks before the current LLM request is
@ -52,7 +52,7 @@ pub enum PreRequestAction {
Cancel(String), Cancel(String),
/// Yield control to the caller for external processing. /// 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. /// The caller is expected to resume execution later.
Yield, Yield,
} }
@ -129,9 +129,9 @@ pub struct ToolResultInfo {
// Interceptor Trait // 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 /// proceed without intervention. Upper layers (e.g. Pod) provide
/// richer implementations for approval flows, permission checks, etc. /// richer implementations for approval flows, permission checks, etc.
#[async_trait] #[async_trait]
@ -141,7 +141,7 @@ pub trait Interceptor: Send + Sync {
PromptAction::Continue 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 /// before the next LLM request. Returned items are `extend`ed into
/// the persistent history (and therefore picked up by the per-turn /// the persistent history (and therefore picked up by the per-turn
/// clone that backs the LLM request, plus the usual /// 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 /// Called before each LLM request. The context starts as a clone
/// of `worker.history` (after `pending_history_appends` and the /// of `engine.history` (after `pending_history_appends` and the
/// Worker's own prune projection have been applied). /// Engine's own prune projection have been applied).
/// ///
/// Direct mutations to `context` remain request-local and are not persisted. /// Direct mutations to `context` remain request-local and are not persisted.
/// If an interceptor derives a human/model-visible nudge from the current /// 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. /// commits it to history before the request is sent.
async fn pre_llm_request(&self, _context: &mut Vec<Item>) -> PreRequestAction { async fn pre_llm_request(&self, _context: &mut Vec<Item>) -> PreRequestAction {
PreRequestAction::Continue PreRequestAction::Continue
@ -194,7 +194,7 @@ pub trait Interceptor: Send + Sync {
async fn on_abort(&self, _reason: &str) {} 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. /// without any external control flow decisions.
pub(crate) struct DefaultInterceptor; 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. //! Provides components for managing interactions with LLMs.
//! //!
//! # Main Components //! # 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 //! - [`tool::Tool`] - Tools that can be invoked by the LLM
//! - [`interceptor::Interceptor`] - Control-flow delegation for the execution loop //! - [`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 //! # Quick Start
//! //!
//! ```ignore //! ```ignore
//! use llm_worker::{Worker, Item}; //! use llm_engine::{Engine, Item};
//! //!
//! // Create a Worker //! // Create a Engine
//! let mut worker = Worker::new(client) //! let mut engine = Engine::new(client)
//! .system_prompt("You are a helpful assistant."); //! .system_prompt("You are a helpful assistant.");
//! //!
//! // Register tools (optional) //! // Register tools (optional)
//! // worker.register_tool(my_tool_definition)?; //! // engine.register_tool(my_tool_definition)?;
//! //!
//! // Run the interaction //! // Run the interaction
//! let history = worker.run("Hello!").await?; //! let history = engine.run("Hello!").await?;
//! ``` //! ```
//! //!
//! # Cache Protection //! # Cache Protection
@ -31,15 +31,15 @@
//! call `unlock_cache()` first; the next `run()` re-locks automatically. //! call `unlock_cache()` first; the next `run()` re-locks automatically.
//! //!
//! ```ignore //! ```ignore
//! worker.run("user input").await?; //! engine.run("user input").await?;
//! worker.unlock_cache(); //! engine.unlock_cache();
//! worker.set_system_prompt("new prompt"); //! engine.set_system_prompt("new prompt");
//! worker.run("next input").await?; //! engine.run("next input").await?;
//! ``` //! ```
mod engine;
mod handler; mod handler;
mod message; mod message;
mod worker;
pub(crate) mod callback; pub(crate) mod callback;
pub mod event; pub mod event;
@ -54,11 +54,12 @@ pub mod tool_server;
pub mod usage_record; pub mod usage_record;
pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope}; pub use callback::{TextBlockScope, ThinkingBlockScope, ToolUseBlockScope};
pub use engine::{
Engine, EngineConfig, EngineError, EngineResult, EngineRunOutput, LlmRetryNotice,
ToolRegistryError,
};
pub use handler::ToolUseBlockStart; pub use handler::ToolUseBlockStart;
pub use interceptor::Interceptor; pub use interceptor::Interceptor;
pub use message::{ContentPart, Item, Message, Role}; pub use message::{ContentPart, Item, Message, Role};
pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult}; pub use tool::{ToolCall, ToolExecutionContext, ToolOutputLimits, ToolResult};
pub use usage_record::UsageRecord; pub use usage_record::UsageRecord;
pub use worker::{
LlmRetryNotice, RunOutput, ToolRegistryError, Worker, WorkerConfig, WorkerError, WorkerResult,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
//! Worker State //! Engine State
//! //!
//! State marker types for cache protection using the Type-state pattern. //! 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. /// 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 { mod private {
pub trait Sealed {} pub trait Sealed {}
@ -19,28 +19,28 @@ mod private {
/// - Editing message history (add, delete, clear) /// - Editing message history (add, delete, clear)
/// - Registering tools and hooks /// - Registering tools and hooks
/// ///
/// Can transition to [`Locked`] state via `Worker::lock()`. /// Can transition to [`Locked`] state via `Engine::lock()`.
/// ///
/// # Examples /// # Examples
/// ///
/// ```ignore /// ```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."); /// .system_prompt("You are helpful.");
/// ///
/// // History can be edited /// // History can be edited
/// worker.push_message(Message::user("Hello")); /// engine.push_message(Message::user("Hello"));
/// worker.clear_history(); /// engine.clear_history();
/// ///
/// // Lock to protected state /// // Lock to protected state
/// let locked = worker.lock(); /// let locked = engine.lock();
/// ``` /// ```
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Mutable; pub struct Mutable;
impl private::Sealed for Mutable {} impl private::Sealed for Mutable {}
impl WorkerState for Mutable {} impl EngineState for Mutable {}
/// Cache locked state (cache protected) /// Cache locked state (cache protected)
/// ///
@ -51,10 +51,10 @@ impl WorkerState for Mutable {}
/// To ensure LLM API KV cache hits, /// To ensure LLM API KV cache hits,
/// using this state during execution is recommended. /// 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. /// but note that cache protection will be released.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct Locked; pub struct Locked;
impl private::Sealed for Locked {} impl private::Sealed for Locked {}
impl WorkerState for Locked {} impl EngineState for Locked {}

View File

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

View File

@ -33,7 +33,7 @@ pub enum ToolError {
/// Outputs this small don't benefit from pruning. /// Outputs this small don't benefit from pruning.
pub const SUMMARY_THRESHOLD: usize = 200; 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. /// tool-execution boundary, before results enter conversation history.
/// ///
/// Exists so a single oversized tool result (e.g. a wide `Glob` scan) /// 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. /// 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 /// 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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolOrigin { pub struct ToolOrigin {
/// Origin kind, for example `plugin` or `builtin`. /// Origin kind, for example `plugin` or `builtin`.
@ -154,7 +154,7 @@ pub struct ToolOrigin {
/// Tool meta information (fixed at registration, immutable) /// 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. /// Used for sending tool definitions to LLM.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolMeta { pub struct ToolMeta {
@ -205,7 +205,7 @@ impl ToolMeta {
/// Tool definition factory /// Tool definition factory
/// ///
/// When called, returns `(ToolMeta, Arc<dyn Tool>)`. /// 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. /// are cached at session scope.
/// ///
/// # Examples /// # Examples
@ -219,22 +219,22 @@ impl ToolMeta {
/// Arc::new(MyToolImpl { state: 0 }) as Arc<dyn Tool>, /// 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>; 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 /// 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 /// 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. /// or session mutation.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolExecutionContext { pub struct ToolExecutionContext {
/// Provider/tool-call id for the call being executed. /// Provider/tool-call id for the call being executed.
pub call_id: String, 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, pub batch_id: String,
/// Zero-based order of this call in the model-returned tool-call list. /// Zero-based order of this call in the model-returned tool-call list.
pub call_index: usize, 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 { pub fn direct() -> Self {
Self::new("direct", "direct", 0) Self::new("direct", "direct", 0)
} }
@ -285,13 +285,13 @@ impl Default for ToolExecutionContext {
/// } /// }
/// ///
/// // Register /// // Register
/// worker.register_tool(app.search_definition())?; /// engine.register_tool(app.search_definition())?;
/// ``` /// ```
/// ///
/// # Manual Implementation /// # Manual Implementation
/// ///
/// ```ignore /// ```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; /// use std::sync::Arc;
/// ///
/// struct MyTool { counter: std::sync::atomic::AtomicUsize } /// 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::llm_client::ToolDefinition as LlmToolDefinition;
use crate::tool::{ 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>)>; type ToolMap = HashMap<String, (ToolMeta, Arc<dyn Tool>)>;
@ -28,7 +28,7 @@ pub enum ToolServerError {
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct ToolServer { pub struct ToolServer {
tools: Arc<Mutex<ToolMap>>, tools: Arc<Mutex<ToolMap>>,
pending: Arc<Mutex<Vec<WorkerToolDefinition>>>, pending: Arc<Mutex<Vec<EngineToolDefinition>>>,
} }
impl ToolServer { impl ToolServer {
@ -50,7 +50,7 @@ impl ToolServer {
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct ToolServerHandle { pub struct ToolServerHandle {
tools: Arc<Mutex<ToolMap>>, tools: Arc<Mutex<ToolMap>>,
pending: Arc<Mutex<Vec<WorkerToolDefinition>>>, pending: Arc<Mutex<Vec<EngineToolDefinition>>>,
} }
impl ToolServerHandle { impl ToolServerHandle {
@ -58,8 +58,8 @@ impl ToolServerHandle {
/// ///
/// The factory is **not** called here; it is stored and executed /// The factory is **not** called here; it is stored and executed
/// when [`flush_pending`](Self::flush_pending) is called (typically /// when [`flush_pending`](Self::flush_pending) is called (typically
/// at the start of `Worker::run()`). /// at the start of `Engine::run()`).
pub(crate) fn register_tool(&self, factory: WorkerToolDefinition) { pub(crate) fn register_tool(&self, factory: EngineToolDefinition) {
self.pending self.pending
.lock() .lock()
.unwrap_or_else(|e| e.into_inner()) .unwrap_or_else(|e| e.into_inner())
@ -67,14 +67,14 @@ impl ToolServerHandle {
} }
/// Queue many tool factories for deferred initialization. /// 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()); let mut guard = self.pending.lock().unwrap_or_else(|e| e.into_inner());
guard.extend(factories); guard.extend(factories);
} }
/// Execute all pending factories and register the resulting tools. /// 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 /// Exposed as `pub` so higher layers (e.g. Pod) can force-materialise
/// tools earlier — for example when building a system-prompt template /// tools earlier — for example when building a system-prompt template
/// context that needs the list of registered tool names. Redundant /// 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 factory is called immediately and the resulting tool overwrites
/// the entry with the same name. Returns `ToolNotFound` if the name /// the entry with the same name. Returns `ToolNotFound` if the name
/// produced by the factory does not match any registered tool. /// 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 (meta, instance) = factory();
let mut guard = self.tools.lock().unwrap_or_else(|e| e.into_inner()); let mut guard = self.tools.lock().unwrap_or_else(|e| e.into_inner());
if !guard.contains_key(&meta.name) { if !guard.contains_key(&meta.name) {

View File

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

View File

@ -8,9 +8,9 @@ use std::sync::{Arc, Mutex};
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use llm_worker::llm_client::event::{BlockType, DeltaContent, Event}; use llm_engine::llm_client::event::{BlockType, DeltaContent, Event};
use llm_worker::llm_client::{ClientError, LlmClient, Request}; use llm_engine::llm_client::{ClientError, LlmClient, Request};
use llm_worker::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline}; use llm_engine::timeline::{Handler, TextBlockEvent, TextBlockKind, Timeline};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
@ -272,7 +272,7 @@ pub fn assert_timeline_integration(subdir: &str) {
}); });
for event in &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); 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. //! Can run locally without API keys.
mod common; mod common;
@ -11,8 +11,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait; use async_trait::async_trait;
use common::MockLlmClient; use common::MockLlmClient;
use llm_worker::Worker; use llm_engine::Engine;
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput}; use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
/// Fixture directory path /// Fixture directory path
fn fixtures_dir() -> std::path::PathBuf { fn fixtures_dir() -> std::path::PathBuf {
@ -61,7 +61,7 @@ impl Tool for MockWeatherTool {
async fn execute( async fn execute(
&self, &self,
input_json: &str, input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext, _ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> { ) -> Result<ToolOutput, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst); 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. /// Creates a client with programmatically constructed events instead of using fixture files.
#[test] #[test]
fn test_mock_client_from_events() { fn test_mock_client_from_events() {
use llm_worker::llm_client::event::Event; use llm_engine::llm_client::event::Event;
// Specify events directly // Specify events directly
let events = vec![ 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. /// Uses simple_text.jsonl fixture to test scenarios without tool calls.
/// Skipped if fixture is not present. /// Skipped if fixture is not present.
#[tokio::test] #[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"); let fixture_path = fixtures_dir().join("simple_text.jsonl");
if !fixture_path.exists() { if !fixture_path.exists() {
println!("Fixture not found: {:?}, skipping test", fixture_path); 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; return;
} }
let client = MockLlmClient::from_fixture(&fixture_path).unwrap(); 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) // 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. /// Uses tool_call.jsonl fixture to test that MockWeatherTool is called.
/// Sets max_turns=1 to prevent loop after tool execution. /// Sets max_turns=1 to prevent loop after tool execution.
#[tokio::test] #[tokio::test]
async fn test_worker_tool_call() { async fn test_engine_tool_call() {
let fixture_path = fixtures_dir().join("tool_call.jsonl"); let fixture_path = fixtures_dir().join("tool_call.jsonl");
if !fixture_path.exists() { if !fixture_path.exists() {
println!("Fixture not found: {:?}, skipping test", fixture_path); 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; return;
} }
let client = MockLlmClient::from_fixture(&fixture_path).unwrap(); let client = MockLlmClient::from_fixture(&fixture_path).unwrap();
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
// Register tool // Register tool
let weather_tool = MockWeatherTool::new(); let weather_tool = MockWeatherTool::new();
let tool_for_check = weather_tool.clone(); 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) // 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 // Verify tool was called
// Note: max_turns=1 so no request is sent after tool result // 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 // 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. /// Constructs event sequence programmatically and passes to MockLlmClient.
/// Useful when test independence is needed and external file dependency should be eliminated. /// Useful when test independence is needed and external file dependency should be eliminated.
#[tokio::test] #[tokio::test]
async fn test_worker_with_programmatic_events() { async fn test_engine_with_programmatic_events() {
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
// Construct event sequence programmatically // Construct event sequence programmatically
let events = vec![ let events = vec![
@ -194,12 +194,12 @@ async fn test_worker_with_programmatic_events() {
]; ];
let client = MockLlmClient::new(events); let client = MockLlmClient::new(events);
let worker = Worker::new(client); let engine = Engine::new(client);
// Mutable::run consumes self, returns tuple // 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 /// 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). /// correctly extracts id, name, and input (JSON).
#[tokio::test] #[tokio::test]
async fn test_tool_call_collector_integration() { async fn test_tool_call_collector_integration() {
use llm_worker::llm_client::event::Event; use llm_engine::llm_client::event::Event;
use llm_worker::timeline::{Timeline, ToolCallCollector}; use llm_engine::timeline::{Timeline, ToolCallCollector};
// Event sequence containing ToolUse block // Event sequence containing ToolUse block
let events = vec![ let events = vec![
@ -225,7 +225,7 @@ async fn test_tool_call_collector_integration() {
// Dispatch events // Dispatch events
for event in &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); 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) //! Tests for state transitions using the Type-state pattern (Mutable/Locked)
//! and state preservation between turns. //! and state preservation between turns.
@ -10,10 +10,10 @@ use std::sync::{Arc, Mutex};
use async_trait::async_trait; use async_trait::async_trait;
use common::MockLlmClient; use common::MockLlmClient;
use llm_worker::Item; use llm_engine::Item;
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput}; use llm_engine::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
use llm_worker::{Worker, WorkerError}; use llm_engine::{Engine, EngineError};
// ============================================================================= // =============================================================================
// Mutable State Tests // Mutable State Tests
@ -23,13 +23,13 @@ use llm_worker::{Worker, WorkerError};
#[test] #[test]
fn test_mutable_set_system_prompt() { fn test_mutable_set_system_prompt() {
let client = MockLlmClient::new(vec![]); 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!( assert_eq!(
worker.get_system_prompt(), engine.get_system_prompt(),
Some("You are a helpful assistant.") Some("You are a helpful assistant.")
); );
} }
@ -38,41 +38,41 @@ fn test_mutable_set_system_prompt() {
#[test] #[test]
fn test_mutable_history_manipulation() { fn test_mutable_history_manipulation() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
// Initial state is empty // Initial state is empty
assert!(worker.history().is_empty()); assert!(engine.history().is_empty());
// Add to history // Add to history
worker.append_history(vec![Item::user_message("Hello")]); engine.append_history(vec![Item::user_message("Hello")]);
worker.append_history(vec![Item::assistant_message("Hi there!")]); engine.append_history(vec![Item::assistant_message("Hi there!")]);
assert_eq!(worker.history().len(), 2); assert_eq!(engine.history().len(), 2);
// Append to history via the callback-aware API. // Append to history via the callback-aware API.
worker.append_history(vec![Item::user_message("How are you?")]); engine.append_history(vec![Item::user_message("How are you?")]);
assert_eq!(worker.history().len(), 3); assert_eq!(engine.history().len(), 3);
// Clear history // Clear history
worker.clear_history(); engine.clear_history();
assert!(worker.history().is_empty()); assert!(engine.history().is_empty());
// Set history // Set history
let items = vec![ let items = vec![
Item::user_message("Test"), Item::user_message("Test"),
Item::assistant_message("Response"), Item::assistant_message("Response"),
]; ];
worker.set_history(items); engine.set_history(items);
assert_eq!(worker.history().len(), 2); 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] #[test]
fn test_mutable_builder_pattern() { fn test_mutable_builder_pattern() {
let client = MockLlmClient::new(vec![]); 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_eq!(engine.get_system_prompt(), Some("System prompt"));
assert!(worker.history().is_empty()); assert!(engine.history().is_empty());
} }
/// Verify that multiple items can be added with append_history and callbacks fire. /// 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 client = MockLlmClient::new(vec![]);
let observed = Arc::new(Mutex::new(Vec::new())); let observed = Arc::new(Mutex::new(Vec::new()));
let observed_for_callback = Arc::clone(&observed); let observed_for_callback = Arc::clone(&observed);
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
worker.on_history_append(move |item| { engine.on_history_append(move |item| {
if let Some(text) = item.as_text() { if let Some(text) = item.as_text() {
observed_for_callback.lock().unwrap().push(text.to_string()); 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::assistant_message("Response 1"),
Item::user_message("Second"), Item::user_message("Second"),
Item::assistant_message("Response 2"), Item::assistant_message("Response 2"),
]); ]);
assert_eq!(worker.history().len(), 4); assert_eq!(engine.history().len(), 4);
assert_eq!( assert_eq!(
observed.lock().unwrap().as_slice(), observed.lock().unwrap().as_slice(),
["First", "Response 1", "Second", "Response 2"] ["First", "Response 1", "Second", "Response 2"]
@ -139,7 +139,7 @@ impl Tool for CountingTool {
async fn execute( async fn execute(
&self, &self,
_input_json: &str, _input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext, _ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> { ) -> Result<ToolOutput, ToolError> {
self.calls.fetch_add(1, Ordering::SeqCst); self.calls.fetch_add(1, Ordering::SeqCst);
Ok(format!("{}-ok", self.name).into()) Ok(format!("{}-ok", self.name).into())
@ -150,11 +150,11 @@ impl Tool for CountingTool {
#[test] #[test]
fn test_mutable_can_register_tool() { fn test_mutable_can_register_tool() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
let tool = CountingTool::new("count_tool"); let tool = CountingTool::new("count_tool");
// register_tool is infallible (factory deferred to run-time flush) // 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] #[test]
fn test_lock_transition() { fn test_lock_transition() {
let client = MockLlmClient::new(vec![]); let client = MockLlmClient::new(vec![]);
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
worker.set_system_prompt("System"); engine.set_system_prompt("System");
worker.append_history(vec![Item::user_message("Hello")]); engine.append_history(vec![Item::user_message("Hello")]);
worker.append_history(vec![Item::assistant_message("Hi")]); engine.append_history(vec![Item::assistant_message("Hi")]);
// Lock // Lock
let locked_worker = worker.lock(); let locked_engine = engine.lock();
// History and system prompt are still accessible in Locked state // History and system prompt are still accessible in Locked state
assert_eq!(locked_worker.get_system_prompt(), Some("System")); assert_eq!(locked_engine.get_system_prompt(), Some("System"));
assert_eq!(locked_worker.history().len(), 2); assert_eq!(locked_engine.history().len(), 2);
assert_eq!(locked_worker.locked_prefix_len(), 2); assert_eq!(locked_engine.locked_prefix_len(), 2);
} }
/// Verify that unlock() transitions from Locked -> Mutable state /// Verify that unlock() transitions from Locked -> Mutable state
#[test] #[test]
fn test_unlock_transition() { fn test_unlock_transition() {
let client = MockLlmClient::new(vec![]); 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")]); engine.append_history(vec![Item::user_message("Hello")]);
let locked_worker = worker.lock(); let locked_engine = engine.lock();
// Unlock // Unlock
let mut worker = locked_worker.unlock(); let mut engine = locked_engine.unlock();
// History operations are available again in Mutable state // History operations are available again in Mutable state
worker.append_history(vec![Item::assistant_message("Hi")]); engine.append_history(vec![Item::assistant_message("Hi")]);
worker.clear_history(); engine.clear_history();
assert!(worker.history().is_empty()); 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 /// Verify that history is correctly updated after running a turn in Mutable state
#[tokio::test] #[tokio::test]
async fn test_mutable_run_updates_history() -> Result<(), WorkerError> { async fn test_mutable_run_updates_history() -> Result<(), EngineError> {
let events = vec![ let events = vec![
Event::text_block_start(0), Event::text_block_start(0),
Event::text_delta(0, "Hello, I'm an assistant!"), 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 client = MockLlmClient::new(events);
let worker = Worker::new(client); let engine = Engine::new(client);
// Execute (Mutable::run consumes self, returns RunOutput) // Execute (Mutable::run consumes self, returns EngineRunOutput)
let out = worker.run("Hi there").await?; let out = engine.run("Hi there").await?;
let worker = out.worker; let engine = out.engine;
// History is updated // History is updated
let history = worker.history(); let history = engine.history();
assert_eq!(history.len(), 2); // user + assistant assert_eq!(history.len(), 2); // user + assistant
// User message // 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) // Lock (after setting system prompt)
let mut locked_worker = worker.lock(); let mut locked_engine = engine.lock();
assert_eq!(locked_worker.locked_prefix_len(), 0); // No items yet assert_eq!(locked_engine.locked_prefix_len(), 0); // No items yet
// Turn 1 // Turn 1
let result1 = locked_worker.run("Hello!").await; let result1 = locked_engine.run("Hello!").await;
assert!(result1.is_ok()); assert!(result1.is_ok());
assert_eq!(locked_worker.history().len(), 2); // user + assistant assert_eq!(locked_engine.history().len(), 2); // user + assistant
// Turn 2 // 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!(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 // Verify history contents
let history = locked_worker.history(); let history = locked_engine.history();
// Turn 1 user message // Turn 1 user message
assert_eq!(history[0].as_text(), Some("Hello!")); 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 // Add items beforehand
worker.append_history(vec![Item::user_message("Pre-existing message 1")]); engine.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::assistant_message("Pre-existing response 1")]);
assert_eq!(worker.history().len(), 2); assert_eq!(engine.history().len(), 2);
// Lock // Lock
let mut locked_worker = worker.lock(); let mut locked_engine = engine.lock();
assert_eq!(locked_worker.locked_prefix_len(), 2); // 2 items at lock time assert_eq!(locked_engine.locked_prefix_len(), 2); // 2 items at lock time
// Execute turn // Execute turn
locked_worker.run("New message").await.unwrap(); locked_engine.run("New message").await.unwrap();
// History grows but locked_prefix_len remains unchanged // History grows but locked_prefix_len remains unchanged
assert_eq!(locked_worker.history().len(), 4); // 2 + 2 assert_eq!(locked_engine.history().len(), 4); // 2 + 2
assert_eq!(locked_worker.locked_prefix_len(), 2); // Unchanged assert_eq!(locked_engine.locked_prefix_len(), 2); // Unchanged
} }
/// Verify that turn count is correctly incremented /// Verify that turn count is correctly incremented
#[tokio::test] #[tokio::test]
async fn test_turn_count_increment() -> Result<(), WorkerError> { async fn test_turn_count_increment() -> Result<(), EngineError> {
let client = MockLlmClient::with_responses(vec![ let client = MockLlmClient::with_responses(vec![
vec![ vec![
Event::text_block_start(0), 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!(engine.turn_count(), 0);
assert_eq!(worker.llm_call_count(), 0); assert_eq!(engine.llm_call_count(), 0);
// First run consumes Mutable, returns RunOutput // First run consumes Mutable, returns EngineRunOutput
let mut worker = worker.run("First").await?.worker; let mut engine = engine.run("First").await?.engine;
assert_eq!(worker.turn_count(), 1); assert_eq!(engine.turn_count(), 1);
// Retry not yet implemented → AgentTurn:LlmCall is 1: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 // Subsequent runs on Locked take &mut self
worker.run("Second").await?; engine.run("Second").await?;
assert_eq!(worker.turn_count(), 2); assert_eq!(engine.turn_count(), 2);
assert_eq!(worker.llm_call_count(), 2); assert_eq!(engine.llm_call_count(), 2);
Ok(()) Ok(())
} }
@ -386,14 +386,14 @@ async fn test_unlock_edit_relock() {
}), }),
]]); ]]);
let mut worker = Worker::new(client); let mut engine = Engine::new(client);
worker.append_history(vec![ engine.append_history(vec![
Item::user_message("Hello"), Item::user_message("Hello"),
Item::assistant_message("Hi"), Item::assistant_message("Hi"),
]); ]);
// Lock -> Unlock // Lock -> Unlock
let locked = worker.lock(); let locked = engine.lock();
assert_eq!(locked.locked_prefix_len(), 2); assert_eq!(locked.locked_prefix_len(), 2);
let mut unlocked = locked.unlock(); 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"); 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"); locked.run("first").await.expect("first run");
assert_eq!(tool_a.call_count(), 1, "tool_a should be called once"); 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] #[test]
fn test_system_prompt_preserved_in_locked_state() { fn test_system_prompt_preserved_in_locked_state() {
let client = MockLlmClient::new(vec![]); 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")); assert_eq!(locked.get_system_prompt(), Some("Important system prompt"));
let unlocked = locked.unlock(); let unlocked = locked.unlock();
@ -489,9 +489,9 @@ fn test_system_prompt_preserved_in_locked_state() {
#[test] #[test]
fn test_system_prompt_change_after_unlock() { fn test_system_prompt_change_after_unlock() {
let client = MockLlmClient::new(vec![]); 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(); let mut unlocked = locked.unlock();
unlocked.set_system_prompt("New prompt"); unlocked.set_system_prompt("New prompt");

View File

@ -1,18 +1,18 @@
//! Parallel tool execution tests //! 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::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use async_trait::async_trait; use async_trait::async_trait;
use llm_worker::Worker; use llm_engine::Engine;
use llm_worker::interceptor::{ use llm_engine::interceptor::{
Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo, Interceptor, PostToolAction, PreToolAction, ToolCallInfo, ToolResultInfo,
}; };
use llm_worker::llm_client::event::{Event, ResponseStatus, StatusEvent}; use llm_engine::llm_client::event::{Event, ResponseStatus, StatusEvent};
use llm_worker::tool::{ use llm_engine::tool::{
Tool, ToolDefinition, ToolError, ToolExecutionContext, ToolMeta, ToolOutput, ToolResult, Tool, ToolDefinition, ToolError, ToolExecutionContext, ToolMeta, ToolOutput, ToolResult,
}; };
@ -64,7 +64,7 @@ impl Tool for SlowTool {
async fn execute( async fn execute(
&self, &self,
_input_json: &str, _input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext, _ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> { ) -> Result<ToolOutput, ToolError> {
self.call_count.fetch_add(1, Ordering::SeqCst); self.call_count.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(self.delay_ms)).await; 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 tool1 = SlowTool::new("slow_tool_1", 100);
let tool2 = SlowTool::new("slow_tool_2", 100); let tool2 = SlowTool::new("slow_tool_2", 100);
let tool3 = SlowTool::new("slow_tool_3", 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 tool2_clone = tool2.clone();
let tool3_clone = tool3.clone(); let tool3_clone = tool3.clone();
worker.register_tool(tool1.definition()); engine.register_tool(tool1.definition());
worker.register_tool(tool2.definition()); engine.register_tool(tool2.definition());
worker.register_tool(tool3.definition()); engine.register_tool(tool3.definition());
let start = Instant::now(); let start = Instant::now();
// Mutable::run consumes self, returns (Locked, WorkerResult) // Mutable::run consumes self, returns (Locked, EngineResult)
let _result = worker.run("Run all tools").await; let _result = engine.run("Run all tools").await;
let elapsed = start.elapsed(); let elapsed = start.elapsed();
// Verify all tools were called // 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())); let contexts = Arc::new(Mutex::new(Vec::new()));
worker.register_tool(ContextRecordingTool::new("record_a", contexts.clone()).definition()); engine.register_tool(ContextRecordingTool::new("record_a", contexts.clone()).definition());
worker.register_tool(ContextRecordingTool::new("record_b", contexts.clone()).definition()); engine.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_c", contexts.clone()).definition());
let _ = worker.run("record contexts").await; let _ = engine.run("record contexts").await;
let mut contexts = contexts.lock().unwrap().clone(); let mut contexts = contexts.lock().unwrap().clone();
contexts.sort_by_key(|ctx| ctx.call_index); 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())); 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(); let contexts = contexts.lock().unwrap().clone();
assert_eq!(contexts.len(), 2); 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 executed_contexts = Arc::new(Mutex::new(Vec::new()));
let pre_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())); let post_contexts = Arc::new(Mutex::new(Vec::new()));
worker engine
.register_tool(ContextRecordingTool::new("record", executed_contexts.clone()).definition()); .register_tool(ContextRecordingTool::new("record", executed_contexts.clone()).definition());
worker.register_tool( engine.register_tool(
ContextRecordingTool::new("skip_tool", executed_contexts.clone()).definition(), ContextRecordingTool::new("skip_tool", executed_contexts.clone()).definition(),
); );
worker.register_tool( engine.register_tool(
ContextRecordingTool::new("synthetic_tool", executed_contexts.clone()).definition(), 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(), pre_contexts: pre_contexts.clone(),
post_contexts: post_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(); let mut pre_contexts = pre_contexts.lock().unwrap().clone();
pre_contexts.sort_by_key(|ctx| ctx.call_index); 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 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 allowed_tool = SlowTool::new("allowed_tool", 10);
let blocked_tool = SlowTool::new("blocked_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 allowed_clone = allowed_tool.clone();
let blocked_clone = blocked_tool.clone(); let blocked_clone = blocked_tool.clone();
worker.register_tool(allowed_tool.definition()); engine.register_tool(allowed_tool.definition());
worker.register_tool(blocked_tool.definition()); engine.register_tool(blocked_tool.definition());
// Policy to skip "blocked_tool" // Policy to skip "blocked_tool"
struct BlockingPolicy; 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) // Mutable::run consumes self, returns (Locked, EngineResult)
let _result = worker.run("Test hook").await; let _result = engine.run("Test hook").await;
// allowed_tool is called, but blocked_tool is not // allowed_tool is called, but blocked_tool is not
assert_eq!( 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)] #[derive(Clone)]
struct SimpleTool; struct SimpleTool;
@ -468,7 +468,7 @@ async fn test_post_tool_call_modification() {
async fn execute( async fn execute(
&self, &self,
_: &str, _: &str,
_ctx: llm_worker::tool::ToolExecutionContext, _ctx: llm_engine::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> { ) -> Result<ToolOutput, ToolError> {
Ok("Original Result".to_string().into()) 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 // Policy to modify results
struct ModifyingPolicy { struct ModifyingPolicy {
@ -500,14 +500,14 @@ async fn test_post_tool_call_modification() {
} }
let modified_content = Arc::new(std::sync::Mutex::new(None)); let modified_content = Arc::new(std::sync::Mutex::new(None));
worker.set_interceptor(ModifyingPolicy { engine.set_interceptor(ModifyingPolicy {
modified_content: modified_content.clone(), modified_content: modified_content.clone(),
}); });
// Mutable::run consumes self, returns (Locked, WorkerResult) // Mutable::run consumes self, returns (Locked, EngineResult)
let result = worker.run("Test modification").await; 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 // Verify hook was called and content was modified
let content = modified_content.lock().unwrap().clone(); 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_tool = SlowTool::new("blocked_tool", 10);
let blocked_clone = blocked_tool.clone(); let blocked_clone = blocked_tool.clone();
worker.register_tool(blocked_tool.definition()); engine.register_tool(blocked_tool.definition());
struct SyntheticPolicy; 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_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, item,
llm_worker::Item::ToolResult { llm_engine::Item::ToolResult {
call_id, call_id,
summary, summary,
is_error: true, is_error: true,

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
use llm_worker::Worker; use llm_engine::Engine;
use llm_worker::llm_client::capability::{ use llm_engine::llm_client::capability::{
CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport, CacheStrategy, ModelCapability, StructuredOutput, ToolCallingSupport,
}; };
use llm_worker::llm_client::scheme::anthropic::AnthropicScheme; use llm_engine::llm_client::scheme::anthropic::AnthropicScheme;
use llm_worker::llm_client::transport::{HttpTransport, ResolvedAuth}; use llm_engine::llm_client::transport::{HttpTransport, ResolvedAuth};
use std::sync::Arc; use std::sync::Arc;
fn main() { fn main() {
@ -21,8 +21,8 @@ fn main() {
ResolvedAuth::None, ResolvedAuth::None,
cap, cap,
); );
let worker = Worker::new(client); let engine = Engine::new(client);
let mut locked = worker.lock(); let mut locked = engine.lock();
let def: llm_worker::tool::ToolDefinition = Arc::new(|| panic!("unused")); let def: llm_engine::tool::ToolDefinition = Arc::new(|| panic!("unused"));
let _ = locked.register_tool(def); 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 --> tests/ui/locked_register_tool.rs:27:20
| |
27 | let _ = locked.register_tool(def); 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 = note: the method was found for
- `Worker<C>` - `Engine<C>`

View File

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

View File

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

View File

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

View File

@ -14,9 +14,9 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// `ModelCapability` は `llm-worker` 側に定義される runtime 構造だが、 // `ModelCapability` は `llm-engine` 側に定義される runtime 構造だが、
// マニフェストで任意に override できるよう型だけ再エクスポートする。 // マニフェストで任意に override できるよう型だけ再エクスポートする。
pub use llm_worker::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort}; pub use llm_engine::llm_client::capability::{ModelCapability, ReasoningControl, ReasoningEffort};
/// Pod マニフェストの `[model]` セクション。 /// 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()) let mut client = McpStdioClient::connect(mock_server("tools-call-forbidden"), tight_limits())
.await .await
.expect("connect"); .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. // is never entered, so the MCP server sees lifecycle shutdown but no call.
client.shutdown().await.expect("shutdown"); client.shutdown().await.expect("shutdown");
} }

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