feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
use std ::collections ::HashMap ;
use std ::sync ::Arc ;
2026-04-01 04:30:24 +00:00
use std ::sync ::{ Mutex as StdMutex , OnceLock } ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
use std ::time ::Duration ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
use api ::{
2026-04-02 11:31:53 +09:00
AnthropicClient , ApiClient , ApiError , AuthSource , ContentBlockDelta , ContentBlockDeltaEvent ,
2026-04-01 05:45:27 +00:00
ContentBlockStartEvent , InputContentBlock , InputMessage , MessageDeltaEvent , MessageRequest ,
2026-04-02 11:31:53 +09:00
OutputContentBlock , PromptCache , PromptCacheConfig , ProviderClient , StreamEvent , ToolChoice ,
ToolDefinition ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
} ;
use serde_json ::json ;
2026-04-01 04:30:29 +00:00
use telemetry ::{ ClientIdentity , MemoryTelemetrySink , SessionTracer , TelemetryEvent } ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
use tokio ::io ::{ AsyncReadExt , AsyncWriteExt } ;
use tokio ::net ::TcpListener ;
use tokio ::sync ::Mutex ;
2026-04-01 04:30:24 +00:00
fn env_lock ( ) -> std ::sync ::MutexGuard < 'static , ( ) > {
static LOCK : OnceLock < StdMutex < ( ) > > = OnceLock ::new ( ) ;
LOCK . get_or_init ( | | StdMutex ::new ( ( ) ) )
. lock ( )
. unwrap_or_else ( std ::sync ::PoisonError ::into_inner )
}
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
#[ tokio::test ]
async fn send_message_posts_json_and_parses_response ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let body = concat! (
" { " ,
" \" id \" : \" msg_test \" , " ,
" \" type \" : \" message \" , " ,
" \" role \" : \" assistant \" , " ,
" \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Hello from Claude \" }], " ,
" \" model \" : \" claude-3-7-sonnet-latest \" , " ,
" \" stop_reason \" : \" end_turn \" , " ,
" \" stop_sequence \" :null, " ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
" \" usage \" :{ \" input_tokens \" :12, \" output_tokens \" :4}, " ,
" \" request_id \" : \" req_body_123 \" " ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
" } "
) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
let server = spawn_server (
state . clone ( ) ,
vec! [ http_response ( " 200 OK " , " application/json " , body ) ] ,
)
. await ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
2026-04-01 18:57:50 +09:00
let client = ApiClient ::new ( " test-key " )
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
. with_auth_token ( Some ( " proxy-token " . to_string ( ) ) )
. with_base_url ( server . base_url ( ) ) ;
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " request should succeed " ) ;
assert_eq! ( response . id , " msg_test " ) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
assert_eq! ( response . total_tokens ( ) , 16 ) ;
assert_eq! ( response . request_id . as_deref ( ) , Some ( " req_body_123 " ) ) ;
2026-04-01 06:25:26 +00:00
assert_eq! ( response . usage . cache_creation_input_tokens , 0 ) ;
assert_eq! ( response . usage . cache_read_input_tokens , 0 ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
assert_eq! (
response . content ,
vec! [ OutputContentBlock ::Text {
text : " Hello from Claude " . to_string ( ) ,
} ]
) ;
let captured = state . lock ( ) . await ;
let request = captured . first ( ) . expect ( " server should capture request " ) ;
assert_eq! ( request . method , " POST " ) ;
assert_eq! ( request . path , " /v1/messages " ) ;
assert_eq! (
request . headers . get ( " x-api-key " ) . map ( String ::as_str ) ,
Some ( " test-key " )
) ;
assert_eq! (
request . headers . get ( " authorization " ) . map ( String ::as_str ) ,
Some ( " Bearer proxy-token " )
) ;
2026-04-01 04:30:29 +00:00
assert_eq! (
request . headers . get ( " anthropic-version " ) . map ( String ::as_str ) ,
Some ( " 2023-06-01 " )
) ;
assert_eq! (
request . headers . get ( " user-agent " ) . map ( String ::as_str ) ,
2026-05-25 12:36:45 +09:00
Some ( " claude-code/0.1.3 " )
2026-04-01 05:45:28 +00:00
) ;
assert_eq! (
request . headers . get ( " anthropic-beta " ) . map ( String ::as_str ) ,
2026-04-01 05:55:25 +00:00
Some ( " claude-code-20250219,prompt-caching-scope-2026-01-05 " )
2026-04-01 04:30:29 +00:00
) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let body : serde_json ::Value =
serde_json ::from_str ( & request . body ) . expect ( " request body should be json " ) ;
assert_eq! (
body . get ( " model " ) . and_then ( serde_json ::Value ::as_str ) ,
Some ( " claude-3-7-sonnet-latest " )
) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
assert! ( body . get ( " stream " ) . is_none ( ) ) ;
assert_eq! ( body [ " tools " ] [ 0 ] [ " name " ] , json! ( " get_weather " ) ) ;
assert_eq! ( body [ " tool_choice " ] [ " type " ] , json! ( " auto " ) ) ;
2026-04-07 14:21:50 +09:00
assert! (
body . get ( " betas " ) . is_none ( ) ,
" betas must travel via the anthropic-beta header, not the request body "
2026-04-01 05:55:25 +00:00
) ;
feat: Rust port of Claude Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: Claude-compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- rusty-claude-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
2026-04-05 16:39:58 +00:00
#[ tokio::test ]
async fn send_message_blocks_oversized_requests_before_the_http_call ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [ http_response ( " 200 OK " , " application/json " , " {} " ) ] ,
)
. await ;
let client = AnthropicClient ::new ( " test-key " ) . with_base_url ( server . base_url ( ) ) ;
let error = client
. send_message ( & MessageRequest {
model : " claude-sonnet-4-6 " . to_string ( ) ,
max_tokens : 64_000 ,
messages : vec ! [ InputMessage {
role : " user " . to_string ( ) ,
content : vec ! [ InputContentBlock ::Text {
text : " x " . repeat ( 600_000 ) ,
} ] ,
} ] ,
system : Some ( " Keep the answer short. " . to_string ( ) ) ,
tools : None ,
tool_choice : None ,
stream : false ,
2026-04-08 11:43:51 +09:00
.. Default ::default ( )
2026-04-05 16:39:58 +00:00
} )
. await
. expect_err ( " oversized request should fail local context-window preflight " ) ;
assert! ( matches! ( error , ApiError ::ContextWindowExceeded { .. } ) ) ;
assert! (
state . lock ( ) . await . is_empty ( ) ,
" preflight failure should avoid any upstream HTTP request "
) ;
}
2026-04-01 04:30:29 +00:00
#[ tokio::test ]
async fn send_message_applies_request_profile_and_records_telemetry ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [ http_response_with_headers (
" 200 OK " ,
" application/json " ,
concat! (
" { " ,
" \" id \" : \" msg_profile \" , " ,
" \" type \" : \" message \" , " ,
" \" role \" : \" assistant \" , " ,
" \" content \" :[{ \" type \" : \" text \" , \" text \" : \" ok \" }], " ,
" \" model \" : \" claude-3-7-sonnet-latest \" , " ,
" \" stop_reason \" : \" end_turn \" , " ,
" \" stop_sequence \" :null, " ,
2026-04-01 06:15:15 +00:00
" \" usage \" :{ \" input_tokens \" :1, \" cache_creation_input_tokens \" :2, \" cache_read_input_tokens \" :3, \" output_tokens \" :1} " ,
2026-04-01 04:30:29 +00:00
" } "
) ,
& [ ( " request-id " , " req_profile_123 " ) ] ,
) ] ,
)
. await ;
let sink = Arc ::new ( MemoryTelemetrySink ::default ( ) ) ;
let client = AnthropicClient ::new ( " test-key " )
. with_base_url ( server . base_url ( ) )
2026-04-01 05:45:28 +00:00
. with_client_identity ( ClientIdentity ::new ( " claude-code " , " 9.9.9 " ) . with_runtime ( " rust-cli " ) )
2026-04-01 04:30:29 +00:00
. with_beta ( " tools-2026-04-01 " )
. with_extra_body_param ( " metadata " , json! ( { " source " : " clawd-code " } ) )
. with_session_tracer ( SessionTracer ::new ( " session-telemetry " , sink . clone ( ) ) ) ;
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " request should succeed " ) ;
assert_eq! ( response . request_id . as_deref ( ) , Some ( " req_profile_123 " ) ) ;
let captured = state . lock ( ) . await ;
let request = captured . first ( ) . expect ( " server should capture request " ) ;
assert_eq! (
request . headers . get ( " anthropic-beta " ) . map ( String ::as_str ) ,
2026-04-01 05:55:25 +00:00
Some ( " claude-code-20250219,prompt-caching-scope-2026-01-05,tools-2026-04-01 " )
2026-04-01 04:30:29 +00:00
) ;
assert_eq! (
request . headers . get ( " user-agent " ) . map ( String ::as_str ) ,
2026-04-01 05:45:28 +00:00
Some ( " claude-code/9.9.9 " )
2026-04-01 04:30:29 +00:00
) ;
let body : serde_json ::Value =
serde_json ::from_str ( & request . body ) . expect ( " request body should be json " ) ;
assert_eq! ( body [ " metadata " ] [ " source " ] , json! ( " clawd-code " ) ) ;
2026-04-07 14:21:50 +09:00
assert! (
body . get ( " betas " ) . is_none ( ) ,
" betas must travel via the anthropic-beta header, not the request body "
2026-04-01 05:45:28 +00:00
) ;
2026-04-01 04:30:29 +00:00
let events = sink . events ( ) ;
2026-04-01 06:15:15 +00:00
assert_eq! ( events . len ( ) , 6 ) ;
2026-04-01 04:30:29 +00:00
assert! ( matches! (
& events [ 0 ] ,
TelemetryEvent ::HttpRequestStarted {
session_id ,
attempt : 1 ,
method ,
path ,
..
} if session_id = = " session-telemetry " & & method = = " POST " & & path = = " /v1/messages "
) ) ;
assert! ( matches! (
& events [ 1 ] ,
TelemetryEvent ::SessionTrace ( trace ) if trace . name = = " http_request_started "
) ) ;
assert! ( matches! (
& events [ 2 ] ,
TelemetryEvent ::HttpRequestSucceeded {
request_id ,
status : 200 ,
..
} if request_id . as_deref ( ) = = Some ( " req_profile_123 " )
) ) ;
assert! ( matches! (
& events [ 3 ] ,
TelemetryEvent ::SessionTrace ( trace ) if trace . name = = " http_request_succeeded "
) ) ;
2026-04-01 06:15:15 +00:00
assert! ( matches! (
& events [ 4 ] ,
TelemetryEvent ::Analytics ( event )
if event . namespace = = " api "
& & event . action = = " message_usage "
& & event . properties . get ( " request_id " ) = = Some ( & json! ( " req_profile_123 " ) )
& & event . properties . get ( " total_tokens " ) = = Some ( & json! ( 7 ) )
& & event . properties . get ( " estimated_cost_usd " ) = = Some ( & json! ( " $0.0001 " ) )
) ) ;
assert! ( matches! (
& events [ 5 ] ,
TelemetryEvent ::SessionTrace ( trace ) if trace . name = = " analytics "
) ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
#[ tokio::test ]
2026-04-01 06:25:26 +00:00
async fn send_message_parses_prompt_cache_token_usage_from_response ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let body = concat! (
" { " ,
" \" id \" : \" msg_cache_tokens \" , " ,
" \" type \" : \" message \" , " ,
" \" role \" : \" assistant \" , " ,
" \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Cache tokens \" }], " ,
" \" model \" : \" claude-3-7-sonnet-latest \" , " ,
" \" stop_reason \" : \" end_turn \" , " ,
" \" stop_sequence \" :null, " ,
" \" usage \" :{ \" input_tokens \" :12, \" cache_creation_input_tokens \" :321, \" cache_read_input_tokens \" :654, \" output_tokens \" :4} " ,
" } "
) ;
let server = spawn_server (
state ,
vec! [ http_response ( " 200 OK " , " application/json " , body ) ] ,
)
. await ;
let client = AnthropicClient ::new ( " test-key " ) . with_base_url ( server . base_url ( ) ) ;
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " request should succeed " ) ;
assert_eq! ( response . usage . input_tokens , 12 ) ;
assert_eq! ( response . usage . cache_creation_input_tokens , 321 ) ;
assert_eq! ( response . usage . cache_read_input_tokens , 654 ) ;
assert_eq! ( response . usage . output_tokens , 4 ) ;
}
2026-04-07 13:35:30 +09:00
#[ tokio::test ]
async fn given_empty_usage_object_when_send_message_parses_response_then_usage_defaults_to_zero ( ) {
// given
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let body = concat! (
" { " ,
" \" id \" : \" msg_empty_usage \" , " ,
" \" type \" : \" message \" , " ,
" \" role \" : \" assistant \" , " ,
" \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Hello from Claude \" }], " ,
" \" model \" : \" claude-3-7-sonnet-latest \" , " ,
" \" stop_reason \" : \" end_turn \" , " ,
" \" stop_sequence \" :null, " ,
" \" usage \" :{} " ,
" } "
) ;
let server = spawn_server (
state ,
vec! [ http_response ( " 200 OK " , " application/json " , body ) ] ,
)
. await ;
let client = AnthropicClient ::new ( " test-key " ) . with_base_url ( server . base_url ( ) ) ;
// when
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " response with empty usage object should still parse " ) ;
// then
assert_eq! ( response . id , " msg_empty_usage " ) ;
assert_eq! ( response . total_tokens ( ) , 0 ) ;
assert_eq! ( response . usage . input_tokens , 0 ) ;
assert_eq! ( response . usage . cache_creation_input_tokens , 0 ) ;
assert_eq! ( response . usage . cache_read_input_tokens , 0 ) ;
assert_eq! ( response . usage . output_tokens , 0 ) ;
}
feat: Rust port of Claude Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: Claude-compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- rusty-claude-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
#[ tokio::test ]
2026-04-01 04:40:17 +00:00
#[ allow(clippy::await_holding_lock) ]
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
async fn stream_message_parses_sse_events_with_tool_use ( ) {
2026-04-01 04:30:24 +00:00
let _guard = env_lock ( ) ;
let temp_root = std ::env ::temp_dir ( ) . join ( format! (
" api-stream-cache-{}-{} " ,
std ::process ::id ( ) ,
std ::time ::SystemTime ::now ( )
. duration_since ( std ::time ::UNIX_EPOCH )
. expect ( " time " )
. as_nanos ( )
) ) ;
std ::env ::set_var ( " CLAUDE_CONFIG_HOME " , & temp_root ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let sse = concat! (
" event: message_start \n " ,
2026-04-01 06:25:26 +00:00
" data: { \" type \" : \" message_start \" , \" message \" :{ \" id \" : \" msg_stream \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" :null, \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :8, \" cache_creation_input_tokens \" :13, \" cache_read_input_tokens \" :21, \" output_tokens \" :0}}} \n \n " ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
" event: content_block_start \n " ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
" data: { \" type \" : \" content_block_start \" , \" index \" :0, \" content_block \" :{ \" type \" : \" tool_use \" , \" id \" : \" toolu_123 \" , \" name \" : \" get_weather \" , \" input \" :{}}} \n \n " ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
" event: content_block_delta \n " ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
" data: { \" type \" : \" content_block_delta \" , \" index \" :0, \" delta \" :{ \" type \" : \" input_json_delta \" , \" partial_json \" : \" { \\ \" city \\ \" : \\ \" Paris \\ \" } \" }} \n \n " ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
" event: content_block_stop \n " ,
" data: { \" type \" : \" content_block_stop \" , \" index \" :0} \n \n " ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
" event: message_delta \n " ,
2026-04-01 06:25:26 +00:00
" data: { \" type \" : \" message_delta \" , \" delta \" :{ \" stop_reason \" : \" tool_use \" , \" stop_sequence \" :null}, \" usage \" :{ \" input_tokens \" :8, \" cache_creation_input_tokens \" :34, \" cache_read_input_tokens \" :55, \" output_tokens \" :1}} \n \n " ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
" event: message_stop \n " ,
" data: { \" type \" : \" message_stop \" } \n \n " ,
" data: [DONE] \n \n "
) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
let server = spawn_server (
state . clone ( ) ,
vec! [ http_response_with_headers (
" 200 OK " ,
" text/event-stream " ,
sse ,
& [ ( " request-id " , " req_stream_456 " ) ] ,
) ] ,
)
. await ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
2026-04-01 18:57:50 +09:00
let client = ApiClient ::new ( " test-key " )
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
. with_auth_token ( Some ( " proxy-token " . to_string ( ) ) )
2026-04-01 04:30:24 +00:00
. with_base_url ( server . base_url ( ) )
. with_prompt_cache ( PromptCache ::new ( " stream-session " ) ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let mut stream = client
. stream_message ( & sample_request ( false ) )
. await
. expect ( " stream should start " ) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
assert_eq! ( stream . request_id ( ) , Some ( " req_stream_456 " ) ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let mut events = Vec ::new ( ) ;
while let Some ( event ) = stream
. next_event ( )
. await
. expect ( " stream event should parse " )
{
events . push ( event ) ;
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
assert_eq! ( events . len ( ) , 6 ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
assert! ( matches! ( events [ 0 ] , StreamEvent ::MessageStart ( _ ) ) ) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
assert! ( matches! (
events [ 1 ] ,
StreamEvent ::ContentBlockStart ( ContentBlockStartEvent {
content_block : OutputContentBlock ::ToolUse { .. } ,
..
} )
) ) ;
assert! ( matches! (
events [ 2 ] ,
StreamEvent ::ContentBlockDelta ( ContentBlockDeltaEvent {
delta : ContentBlockDelta ::InputJsonDelta { .. } ,
..
} )
) ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
assert! ( matches! ( events [ 3 ] , StreamEvent ::ContentBlockStop ( _ ) ) ) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
assert! ( matches! (
events [ 4 ] ,
StreamEvent ::MessageDelta ( MessageDeltaEvent { .. } )
) ) ;
assert! ( matches! ( events [ 5 ] , StreamEvent ::MessageStop ( _ ) ) ) ;
match & events [ 1 ] {
StreamEvent ::ContentBlockStart ( ContentBlockStartEvent {
content_block : OutputContentBlock ::ToolUse { name , input , .. } ,
..
} ) = > {
assert_eq! ( name , " get_weather " ) ;
assert_eq! ( input , & json! ( { } ) ) ;
}
other = > panic! ( " expected tool_use block, got {other:?} " ) ,
}
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let captured = state . lock ( ) . await ;
let request = captured . first ( ) . expect ( " server should capture request " ) ;
assert! ( request . body . contains ( " \" stream \" :true " ) ) ;
2026-04-01 04:30:24 +00:00
2026-04-01 04:40:17 +00:00
let cache_stats = client
2026-04-01 04:30:24 +00:00
. prompt_cache_stats ( )
. expect ( " prompt cache stats should exist " ) ;
2026-04-01 04:40:17 +00:00
assert_eq! ( cache_stats . tracked_requests , 1 ) ;
2026-04-01 06:25:26 +00:00
assert_eq! ( cache_stats . last_cache_creation_input_tokens , Some ( 34 ) ) ;
assert_eq! ( cache_stats . last_cache_read_input_tokens , Some ( 55 ) ) ;
2026-04-01 04:40:17 +00:00
assert_eq! (
cache_stats . last_cache_source . as_deref ( ) ,
Some ( " api-response " )
) ;
2026-04-01 04:30:24 +00:00
std ::fs ::remove_dir_all ( temp_root ) . expect ( " cleanup temp root " ) ;
std ::env ::remove_var ( " CLAUDE_CONFIG_HOME " ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
#[ tokio::test ]
async fn retries_retryable_failures_before_succeeding ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [
http_response (
" 429 Too Many Requests " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" rate_limit_error \" , \" message \" : \" slow down \" }} " ,
) ,
http_response (
" 200 OK " ,
" application/json " ,
" { \" id \" : \" msg_retry \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Recovered \" }], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" : \" end_turn \" , \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :3, \" output_tokens \" :2}} " ,
) ,
] ,
)
. await ;
2026-04-01 18:57:50 +09:00
let client = ApiClient ::new ( " test-key " )
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
. with_base_url ( server . base_url ( ) )
. with_retry_policy ( 2 , Duration ::from_millis ( 1 ) , Duration ::from_millis ( 2 ) ) ;
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " retry should eventually succeed " ) ;
assert_eq! ( response . total_tokens ( ) , 5 ) ;
assert_eq! ( state . lock ( ) . await . len ( ) , 2 ) ;
}
2026-04-01 05:45:27 +00:00
#[ tokio::test ]
async fn provider_client_dispatches_anthropic_requests ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [ http_response (
" 200 OK " ,
" application/json " ,
" { \" id \" : \" msg_provider \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Dispatched \" }], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" : \" end_turn \" , \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :3, \" output_tokens \" :2}} " ,
) ] ,
)
. await ;
let client = ProviderClient ::from_model_with_anthropic_auth (
" claude-sonnet-4-6 " ,
Some ( AuthSource ::ApiKey ( " test-key " . to_string ( ) ) ) ,
)
. expect ( " anthropic provider client should be constructed " ) ;
let client = match client {
ProviderClient ::Anthropic ( client ) = > {
ProviderClient ::Anthropic ( client . with_base_url ( server . base_url ( ) ) )
}
other = > panic! ( " expected anthropic provider, got {other:?} " ) ,
} ;
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " provider-dispatched request should succeed " ) ;
assert_eq! ( response . total_tokens ( ) , 5 ) ;
let captured = state . lock ( ) . await ;
let request = captured . first ( ) . expect ( " server should capture request " ) ;
assert_eq! ( request . path , " /v1/messages " ) ;
assert_eq! (
request . headers . get ( " x-api-key " ) . map ( String ::as_str ) ,
Some ( " test-key " )
) ;
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
#[ tokio::test ]
async fn surfaces_retry_exhaustion_for_persistent_retryable_errors ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [
http_response (
" 503 Service Unavailable " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" overloaded_error \" , \" message \" : \" busy \" }} " ,
) ,
http_response (
" 503 Service Unavailable " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" overloaded_error \" , \" message \" : \" still busy \" }} " ,
) ,
] ,
)
. await ;
2026-04-01 18:57:50 +09:00
let client = ApiClient ::new ( " test-key " )
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
. with_base_url ( server . base_url ( ) )
. with_retry_policy ( 1 , Duration ::from_millis ( 1 ) , Duration ::from_millis ( 2 ) ) ;
let error = client
. send_message ( & sample_request ( false ) )
. await
. expect_err ( " persistent 503 should fail " ) ;
match error {
ApiError ::RetriesExhausted {
attempts ,
last_error ,
} = > {
assert_eq! ( attempts , 2 ) ;
assert! ( matches! (
* last_error ,
ApiError ::Api {
status : reqwest ::StatusCode ::SERVICE_UNAVAILABLE ,
retryable : true ,
..
}
) ) ;
}
other = > panic! ( " expected retries exhausted, got {other:?} " ) ,
}
}
2026-04-07 14:51:12 +09:00
#[ tokio::test ]
async fn retries_multiple_retryable_failures_with_exponential_backoff_and_jitter ( ) {
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [
http_response (
" 429 Too Many Requests " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" rate_limit_error \" , \" message \" : \" slow down \" }} " ,
) ,
http_response (
" 500 Internal Server Error " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" api_error \" , \" message \" : \" boom \" }} " ,
) ,
http_response (
" 503 Service Unavailable " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" overloaded_error \" , \" message \" : \" busy \" }} " ,
) ,
http_response (
" 429 Too Many Requests " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" rate_limit_error \" , \" message \" : \" slow down again \" }} " ,
) ,
http_response (
" 503 Service Unavailable " ,
" application/json " ,
" { \" type \" : \" error \" , \" error \" :{ \" type \" : \" overloaded_error \" , \" message \" : \" still busy \" }} " ,
) ,
http_response (
" 200 OK " ,
" application/json " ,
" { \" id \" : \" msg_exp_retry \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Recovered after 5 \" }], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" : \" end_turn \" , \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :3, \" output_tokens \" :2}} " ,
) ,
] ,
)
. await ;
let client = ApiClient ::new ( " test-key " )
. with_base_url ( server . base_url ( ) )
. with_retry_policy ( 8 , Duration ::from_millis ( 1 ) , Duration ::from_millis ( 4 ) ) ;
let started_at = std ::time ::Instant ::now ( ) ;
let response = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " 8-retry policy should absorb 5 retryable failures " ) ;
let elapsed = started_at . elapsed ( ) ;
assert_eq! ( response . total_tokens ( ) , 5 ) ;
assert_eq! (
state . lock ( ) . await . len ( ) ,
6 ,
" client should issue 1 original + 5 retry requests before the 200 "
) ;
// Jittered sleeps are bounded by 2 * max_backoff per retry (base + jitter),
// so 5 sleeps fit comfortably below this upper bound with generous slack.
assert! (
elapsed < Duration ::from_secs ( 5 ) ,
" retries should complete promptly, took {elapsed:?} "
) ;
}
2026-04-01 04:30:24 +00:00
#[ tokio::test ]
2026-04-01 04:40:17 +00:00
#[ allow(clippy::await_holding_lock) ]
2026-04-01 04:30:24 +00:00
async fn send_message_reuses_recent_completion_cache_entries ( ) {
let _guard = env_lock ( ) ;
let temp_root = std ::env ::temp_dir ( ) . join ( format! (
" api-prompt-cache-{}-{} " ,
std ::process ::id ( ) ,
std ::time ::SystemTime ::now ( )
. duration_since ( std ::time ::UNIX_EPOCH )
. expect ( " time " )
. as_nanos ( )
) ) ;
std ::env ::set_var ( " CLAUDE_CONFIG_HOME " , & temp_root ) ;
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state . clone ( ) ,
vec! [ http_response (
" 200 OK " ,
" application/json " ,
" { \" id \" : \" msg_cached \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Cached once \" }], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" : \" end_turn \" , \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :3, \" cache_creation_input_tokens \" :5, \" cache_read_input_tokens \" :4000, \" output_tokens \" :2}} " ,
) ] ,
)
. await ;
let client = AnthropicClient ::new ( " test-key " )
. with_base_url ( server . base_url ( ) )
. with_prompt_cache ( PromptCache ::new ( " integration-session " ) ) ;
let first = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " first request should succeed " ) ;
let second = client
. send_message ( & sample_request ( false ) )
. await
. expect ( " second request should reuse cache " ) ;
assert_eq! ( first . content , second . content ) ;
assert_eq! ( state . lock ( ) . await . len ( ) , 1 ) ;
2026-04-01 04:40:17 +00:00
let cache_stats = client
2026-04-01 04:30:24 +00:00
. prompt_cache_stats ( )
. expect ( " prompt cache stats should exist " ) ;
2026-04-01 04:40:17 +00:00
assert_eq! ( cache_stats . completion_cache_hits , 1 ) ;
assert_eq! ( cache_stats . completion_cache_misses , 1 ) ;
assert_eq! ( cache_stats . completion_cache_writes , 1 ) ;
2026-04-01 04:30:24 +00:00
std ::fs ::remove_dir_all ( temp_root ) . expect ( " cleanup temp root " ) ;
std ::env ::remove_var ( " CLAUDE_CONFIG_HOME " ) ;
}
#[ tokio::test ]
2026-04-01 04:40:17 +00:00
#[ allow(clippy::await_holding_lock) ]
2026-04-01 04:30:24 +00:00
async fn send_message_tracks_unexpected_prompt_cache_breaks ( ) {
let _guard = env_lock ( ) ;
let temp_root = std ::env ::temp_dir ( ) . join ( format! (
" api-prompt-break-{}-{} " ,
std ::process ::id ( ) ,
std ::time ::SystemTime ::now ( )
. duration_since ( std ::time ::UNIX_EPOCH )
. expect ( " time " )
. as_nanos ( )
) ) ;
std ::env ::set_var ( " CLAUDE_CONFIG_HOME " , & temp_root ) ;
let state = Arc ::new ( Mutex ::new ( Vec ::< CapturedRequest > ::new ( ) ) ) ;
let server = spawn_server (
state ,
vec! [
http_response (
" 200 OK " ,
" application/json " ,
" { \" id \" : \" msg_one \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[{ \" type \" : \" text \" , \" text \" : \" One \" }], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" : \" end_turn \" , \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :3, \" cache_creation_input_tokens \" :5, \" cache_read_input_tokens \" :6000, \" output_tokens \" :2}} " ,
) ,
http_response (
" 200 OK " ,
" application/json " ,
" { \" id \" : \" msg_two \" , \" type \" : \" message \" , \" role \" : \" assistant \" , \" content \" :[{ \" type \" : \" text \" , \" text \" : \" Two \" }], \" model \" : \" claude-3-7-sonnet-latest \" , \" stop_reason \" : \" end_turn \" , \" stop_sequence \" :null, \" usage \" :{ \" input_tokens \" :3, \" cache_creation_input_tokens \" :0, \" cache_read_input_tokens \" :1000, \" output_tokens \" :2}} " ,
) ,
] ,
)
. await ;
let request = sample_request ( false ) ;
let client = AnthropicClient ::new ( " test-key " )
. with_base_url ( server . base_url ( ) )
2026-04-02 11:31:53 +09:00
. with_prompt_cache ( PromptCache ::with_config ( PromptCacheConfig {
2026-04-01 04:30:24 +00:00
session_id : " break-session " . to_string ( ) ,
completion_ttl : Duration ::from_secs ( 0 ) ,
2026-04-02 11:31:53 +09:00
.. PromptCacheConfig ::default ( )
2026-04-01 04:30:24 +00:00
} ) ) ;
client
. send_message ( & request )
. await
. expect ( " first response should succeed " ) ;
client
. send_message ( & request )
. await
. expect ( " second response should succeed " ) ;
2026-04-01 04:40:17 +00:00
let cache_stats = client
2026-04-01 04:30:24 +00:00
. prompt_cache_stats ( )
. expect ( " prompt cache stats should exist " ) ;
2026-04-01 04:40:17 +00:00
assert_eq! ( cache_stats . unexpected_cache_breaks , 1 ) ;
2026-04-01 04:30:24 +00:00
assert_eq! (
2026-04-01 04:40:17 +00:00
cache_stats . last_break_reason . as_deref ( ) ,
2026-04-01 04:30:24 +00:00
Some ( " cache read tokens dropped while prompt fingerprint remained stable " )
) ;
std ::fs ::remove_dir_all ( temp_root ) . expect ( " cleanup temp root " ) ;
std ::env ::remove_var ( " CLAUDE_CONFIG_HOME " ) ;
}
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
#[ tokio::test ]
#[ ignore = " requires ANTHROPIC_API_KEY and network access " ]
async fn live_stream_smoke_test ( ) {
2026-04-01 18:57:50 +09:00
let client = ApiClient ::from_env ( ) . expect ( " ANTHROPIC_API_KEY must be set " ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let mut stream = client
. stream_message ( & MessageRequest {
model : std ::env ::var ( " ANTHROPIC_MODEL " )
. unwrap_or_else ( | _ | " claude-3-7-sonnet-latest " . to_string ( ) ) ,
max_tokens : 32 ,
messages : vec ! [ InputMessage ::user_text (
" Reply with exactly: hello from rust " ,
) ] ,
system : None ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
tools : None ,
tool_choice : None ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
stream : false ,
2026-04-08 11:43:51 +09:00
.. Default ::default ( )
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
} )
. await
. expect ( " live stream should start " ) ;
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
while let Some ( _event ) = stream
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
. next_event ( )
. await
. expect ( " live stream should yield events " )
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
{ }
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
#[ derive(Debug, Clone, PartialEq, Eq) ]
struct CapturedRequest {
method : String ,
path : String ,
headers : HashMap < String , String > ,
body : String ,
}
struct TestServer {
base_url : String ,
join_handle : tokio ::task ::JoinHandle < ( ) > ,
}
impl TestServer {
fn base_url ( & self ) -> String {
self . base_url . clone ( )
}
}
impl Drop for TestServer {
fn drop ( & mut self ) {
self . join_handle . abort ( ) ;
}
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
async fn spawn_server (
state : Arc < Mutex < Vec < CapturedRequest > > > ,
responses : Vec < String > ,
) -> TestServer {
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
let listener = TcpListener ::bind ( " 127.0.0.1:0 " )
. await
. expect ( " listener should bind " ) ;
let address = listener
. local_addr ( )
. expect ( " listener should have local addr " ) ;
let join_handle = tokio ::spawn ( async move {
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
for response in responses {
let ( mut socket , _ ) = listener . accept ( ) . await . expect ( " server should accept " ) ;
let mut buffer = Vec ::new ( ) ;
let mut header_end = None ;
loop {
let mut chunk = [ 0_ u8 ; 1024 ] ;
let read = socket
. read ( & mut chunk )
. await
. expect ( " request read should succeed " ) ;
if read = = 0 {
break ;
}
buffer . extend_from_slice ( & chunk [ .. read ] ) ;
if let Some ( position ) = find_header_end ( & buffer ) {
header_end = Some ( position ) ;
break ;
}
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
let header_end = header_end . expect ( " request should include headers " ) ;
let ( header_bytes , remaining ) = buffer . split_at ( header_end ) ;
let header_text =
String ::from_utf8 ( header_bytes . to_vec ( ) ) . expect ( " headers should be utf8 " ) ;
let mut lines = header_text . split ( " \r \n " ) ;
let request_line = lines . next ( ) . expect ( " request line should exist " ) ;
let mut parts = request_line . split_whitespace ( ) ;
let method = parts . next ( ) . expect ( " method should exist " ) . to_string ( ) ;
let path = parts . next ( ) . expect ( " path should exist " ) . to_string ( ) ;
let mut headers = HashMap ::new ( ) ;
let mut content_length = 0_ usize ;
for line in lines {
if line . is_empty ( ) {
continue ;
}
let ( name , value ) = line . split_once ( ':' ) . expect ( " header should have colon " ) ;
let value = value . trim ( ) . to_string ( ) ;
if name . eq_ignore_ascii_case ( " content-length " ) {
content_length = value . parse ( ) . expect ( " content length should parse " ) ;
}
headers . insert ( name . to_ascii_lowercase ( ) , value ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
let mut body = remaining [ 4 .. ] . to_vec ( ) ;
while body . len ( ) < content_length {
let mut chunk = vec! [ 0_ u8 ; content_length - body . len ( ) ] ;
let read = socket
. read ( & mut chunk )
. await
. expect ( " body read should succeed " ) ;
if read = = 0 {
break ;
}
body . extend_from_slice ( & chunk [ .. read ] ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
state . lock ( ) . await . push ( CapturedRequest {
method ,
path ,
headers ,
body : String ::from_utf8 ( body ) . expect ( " body should be utf8 " ) ,
} ) ;
socket
. write_all ( response . as_bytes ( ) )
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
. await
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
. expect ( " response write should succeed " ) ;
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
} ) ;
TestServer {
base_url : format ! ( " http://{address} " ) ,
join_handle ,
}
}
fn find_header_end ( bytes : & [ u8 ] ) -> Option < usize > {
bytes . windows ( 4 ) . position ( | window | window = = b " \r \n \r \n " )
}
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
fn http_response ( status : & str , content_type : & str , body : & str ) -> String {
http_response_with_headers ( status , content_type , body , & [ ] )
}
fn http_response_with_headers (
status : & str ,
content_type : & str ,
body : & str ,
headers : & [ ( & str , & str ) ] ,
) -> String {
let mut extra_headers = String ::new ( ) ;
for ( name , value ) in headers {
use std ::fmt ::Write as _ ;
write! ( & mut extra_headers , " {name}: {value} \r \n " ) . expect ( " header write should succeed " ) ;
}
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
format! (
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
" HTTP/1.1 {status} \r \n content-type: {content_type} \r \n {extra_headers}content-length: {} \r \n connection: close \r \n \r \n {body} " ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
body . len ( )
)
}
fn sample_request ( stream : bool ) -> MessageRequest {
MessageRequest {
model : " claude-3-7-sonnet-latest " . to_string ( ) ,
max_tokens : 64 ,
feat: merge 2nd round from all rcc/* sessions
- api: tool_use parsing, message_delta, request_id tracking, retry logic
- tools: extended tool suite (WebSearch, WebFetch, Agent, etc.)
- cli: live streamed conversations, session restore, compact commands
- runtime: config loading, system prompt builder, token usage, compaction
2026-03-31 17:43:25 +00:00
messages : vec ! [ InputMessage {
role : " user " . to_string ( ) ,
content : vec ! [
InputContentBlock ::Text {
text : " Say hello " . to_string ( ) ,
} ,
InputContentBlock ::ToolResult {
tool_use_id : " toolu_prev " . to_string ( ) ,
content : vec ! [ api ::ToolResultContentBlock ::Json {
value : json ! ( { " forecast " : " sunny " } ) ,
} ] ,
is_error : false ,
} ,
] ,
} ] ,
system : Some ( " Use tools when needed " . to_string ( ) ) ,
tools : Some ( vec! [ ToolDefinition {
name : " get_weather " . to_string ( ) ,
description : Some ( " Fetches the weather " . to_string ( ) ) ,
input_schema : json ! ( {
" type " : " object " ,
" properties " : { " city " : { " type " : " string " } } ,
" required " : [ " city " ]
} ) ,
} ] ) ,
tool_choice : Some ( ToolChoice ::Auto ) ,
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
stream ,
2026-04-08 11:43:51 +09:00
.. Default ::default ( )
feat: Rust port of Claw Code CLI
Crates:
- api: Anthropic Messages API client with SSE streaming
- tools: compatible tool implementations (Bash, Read, Write, Edit, Glob, Grep + extended suite)
- runtime: conversation loop, session persistence, permissions, system prompt builder
- claw-cli: terminal UI with markdown rendering, syntax highlighting, spinners
- commands: subcommand definitions
- compat-harness: upstream TS parity verification
All crates pass cargo fmt/clippy/test.
2026-03-31 17:43:09 +00:00
}
}